commit 6852832fe86ff159878f16264fb5f8d2ef14cd285442dfe94554ea4852257643 Author: Mukan Erkin Törük Date: Mon May 11 03:18:28 2026 +0300 initial: sovereign Mukan Network fork diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..4c437c3 --- /dev/null +++ b/.clang-format @@ -0,0 +1,116 @@ +--- +Language: Proto +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: true +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +RawStringFormats: + - Delimiters: + - pb + Language: TextProto + BasedOnStyle: google +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 8 +UseTab: Never +... + diff --git a/.github/.codespellignore b/.github/.codespellignore new file mode 100644 index 0000000..24fd5c6 --- /dev/null +++ b/.github/.codespellignore @@ -0,0 +1,2 @@ +clientA +connectionA diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000..6ada633 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,42 @@ +--- +name: Bug Report +about: Create a report to help us squash bugs! + +--- + + + + + +## Summary of Bug + + + +## Expected Behaviour + + + +## Version + + + +## Steps to Reproduce + + + +____ + +#### For Admin Use + +- [ ] Not duplicate issue +- [ ] Appropriate labels applied +- [ ] Appropriate contributors tagged/assigned +- [ ] Estimate provided diff --git a/.github/ISSUE_TEMPLATE/epic-tracker.md b/.github/ISSUE_TEMPLATE/epic-tracker.md new file mode 100644 index 0000000..d143f8b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/epic-tracker.md @@ -0,0 +1,64 @@ +--- +name: Epic Tracker +about: Create an issue to track feature epic progress + +--- + + + +## Requirements document + + + +## IBC spec + + + +## ADRs + + + +## Milestones + + + +## Implementation issues + + + +## QA scenarios + + + +## Automated e2e tests + + + +## Pre-releases + + + +## Checklist + + + +- [ ] Internal audit(s) +- [ ] External audit(s) +- [ ] Documentation +- [ ] Swagger +- [ ] Integration with relayers: + - [ ] Hermes + - [ ] Rly + +____ + +#### For Admin Use + +- [ ] Not duplicate issue +- [ ] Appropriate labels applied +- [ ] Appropriate contributors tagged/assigned diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 0000000..96c2fe1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,40 @@ +--- +name: Feature Request +about: Create a proposal to request a feature + +--- + + + +## Summary + + + +## Problem Definition + + + +## Use cases + + + +## Proposal + + + +____ + +#### For Admin Use + +- [ ] Not duplicate issue +- [ ] Appropriate labels applied +- [ ] Appropriate contributors tagged/assigned +- [ ] Estimate provided diff --git a/.github/ISSUE_TEMPLATE/release-tracker.md b/.github/ISSUE_TEMPLATE/release-tracker.md new file mode 100644 index 0000000..611a4d8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/release-tracker.md @@ -0,0 +1,83 @@ +--- +name: Release tracker +about: Create an issue to track release progress +--- + + + +## Milestones + + + +## IBC spec compatibility + + + +## QA + +### Backwards compatibility + + + +- [ ] [Compatibility tests](https://github.com/cosmos/ibc-go/actions/workflows/e2e-compatibility.yaml) pass for the release branch. +- [ ] [Upgrade tests](https://github.com/cosmos/ibc-go/actions/workflows/e2e-upgrade.yaml) pass. +- [ ] Manual test with ledger signing. + +### Other testing + +## Migration + + + +## Checklist + + + +- [ ] Bump [go package version](https://github.com/cosmos/ibc-go/blob/main/go.mod#L3). +- [ ] Change all imports starting with `github.com/cosmos/ibc-go/v{x}` to `github.com/cosmos/ibc-go/v{x+1}`. +- [ ] Branch off main to create release branch in the form of `release/vx.y.z` and add branch protection rules. +- [ ] Add branch protection rules to new release branch. +- [ ] Add backport task to [`mergify.yml`](https://github.com/cosmos/ibc-go/blob/main/.github/mergify.yml) +- [ ] Upgrade ibc-go version in [interchaintest](https://github.com/cosmos/interchaintest). +- [ ] Check Swagger is up-to-date. + +## Post-release checklist + +- [ ] Update [`CHANGELOG.md`](https://github.com/cosmos/ibc-go/blob/main/CHANGELOG.md) +- [ ] Update the table of supported release lines (and End of Life dates) in [`RELEASES.md`](https://github.com/cosmos/ibc-go/blob/main/RELEASES.md): + - Add the new release line. + - Remove any release lines that might have become discontinued. +- [ ] Update [version matrix](https://github.com/cosmos/ibc-go/blob/main/RELEASES.md#version-matrix) in `RELEASES.md`: + - Add the new release. + - Remove any tags that might not be recommended anymore. +- [ ] Update the list of [supported release lines in README.md](https://github.com/cosmos/ibc-go#releases), if necessary. +- [ ] Update docs site: + - [ ] Update permalinks with links of the released tag. + - [ ] If the release is occurring on the main branch, on the latest version, then run `npm run docusaurus docs:version vX.Y.Z` in the `docs/` directory. (where `X.Y.Z` is the new version number) + - [ ] If the release is occurring on an older release branch, then make a PR to the main branch called `docs: new release vX.Y.Z` doing the following: + - [ ] Update the content of the docs found in `docs/versioned_docs/version-vx.y.z` if needed. (where `x.y.z` is the previous version number) + - [ ] Update the version number of the older release branch by changing the version number of the older release branch in: + - [ ] In `docs/versions.json`. + - [ ] Rename `docs/versioned_sidebars/version-vx.y.z-sidebars.json` + - [ ] Rename `docs/versioned_docs/version-vx.y.z` +- [ ] Ensure annotations on tests are correct as per the [compatibility test tool](../../scripts/compatibility.md): + - Add the new release. + - Remove any tags that might not be recommended anymore. +- [ ] Update the manual [e2e `simd`](https://github.com/cosmos/ibc-go/blob/main/.github/workflows/e2e-manual-simd.yaml) test workflow: + - Remove any tags that might not be recommended anymore. +- [ ] After changes to docs site are deployed, check [ibc.cosmos.network](https://ibc.cosmos.network) is updated. +- [ ] Open issue in [SDK tutorials repo](https://github.com/cosmos/sdk-tutorials) to update tutorials to the released version of ibc-go. + +--- + +#### For Admin Use + +- [ ] Not duplicate issue +- [ ] Appropriate labels applied +- [ ] Appropriate contributors tagged/assigned diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..c7431fe --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,41 @@ + + +## Description + + + +closes: #XXXX + +--- + +Before we can merge this PR, please make sure that all the following items have been +checked off. If any of the checklist items are not applicable, please leave them but +write a little note why. + +- [ ] Linked to GitHub issue with discussion and accepted design, OR link to spec that describes this work. +- [ ] Include changelog entry when appropriate (e.g. chores should be omitted from changelog). +- [ ] Wrote unit and integration [tests](https://github.com/cosmos/ibc-go/blob/main/testing/README.md#ibc-testing-package) if relevant. +- [ ] Updated documentation (`docs/`) if anything is changed. +- [ ] Added `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code) if relevant. +- [ ] Self-reviewed `Files changed` in the GitHub PR explorer. +- [ ] Provide a [conventional commit message](https://github.com/cosmos/ibc-go/blob/main/docs/dev/pull-requests.md#commit-messages) to follow the repository standards. + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b6fc6c8 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,39 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 + + - package-ecosystem: gomod + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - dependencies + + - package-ecosystem: gomod + directory: "/e2e" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - dependencies + + - package-ecosystem: gomod + directory: "/modules/light-clients/08-wasm" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - dependencies + + - package-ecosystem: gomod + directory: "/simapp" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - dependencies diff --git a/.github/mergify.yml b/.github/mergify.yml new file mode 100644 index 0000000..9286f34 --- /dev/null +++ b/.github/mergify.yml @@ -0,0 +1,84 @@ +queue_rules: + - name: default + queue_conditions: + - "#approved-reviews-by>=1" + - base=main + - label=automerge + commit_message_template: | + {{ title }} (#{{ number }}) + {{ body }} + merge_conditions: + - "#approved-reviews-by>=1" + - base=main + - label=automerge + merge_method: squash + +pull_request_rules: + - name: backport patches to v0.2.x callbacks ibc-go v7.3.x branch + conditions: + - base=main + - label=backport-callbacks-to-v0.2.x+ibc-go-v7.3.x + actions: + backport: + branches: + - callbacks/release/v0.2.x+ibc-go-v7.3.x + - name: backport patches to v0.2.x callbacks ibc-go v8.0.x branch + conditions: + - base=main + - label=backport-callbacks-to-v0.2.x+ibc-go-v8.0.x + actions: + backport: + branches: + - callbacks/release/v0.2.x+ibc-go-v8.0.x + - name: backport patches to v0.4.x wasm ibc-go v7.4.x & wasmvm 1.5.x branch + conditions: + - base=main + - label=backport-wasm-v0.4.x+ibc-go-v7.4.x-wasmvm-v1.5.x + actions: + backport: + branches: + - 08-wasm/release/v0.4.x+ibc-go-v7.4.x-wasmvm-v1.5.x + - name: backport patches to v0.5.x wasm ibc-go v8.4.x & wasmvm 2.1.x branch + conditions: + - base=main + - label=backport-wasm-v0.5.x+ibc-go-v8.4.x-wasmvm-v2.1.x + actions: + backport: + branches: + - 08-wasm/release/v0.5.x+ibc-go-v8.4.x-wasmvm-v2.1.x + - name: backport patches to v7.10.x branch + conditions: + - base=main + - label=backport-to-v7.10.x + actions: + backport: + branches: + - release/v7.10.x + - name: backport patches to v8.7.x branch + conditions: + - base=main + - label=backport-to-v8.7.x + actions: + backport: + branches: + - release/v8.7.x + - name: backport patches to v10.2.x branch + conditions: + - base=main + - label=backport-to-v10.2.x + actions: + backport: + branches: + - release/v10.2.x + - name: backport patches to v10.3.x branch + conditions: + - base=main + - label=backport-to-v10.3.x + actions: + backport: + branches: + - release/v10.3.x + - name: automerge to main with label automerge and branch protection passing + conditions: [] + actions: + queue: diff --git a/.github/workflows/build-simd-image-from-tag.yml b/.github/workflows/build-simd-image-from-tag.yml new file mode 100644 index 0000000..228704d --- /dev/null +++ b/.github/workflows/build-simd-image-from-tag.yml @@ -0,0 +1,42 @@ +name: Build Simd Image +on: + workflow_dispatch: + inputs: + tag: + description: 'The tag of the image to build' + required: true + type: string + ibc-go-version: + description: 'The ibc-go version to be added as a label' + required: true + type: string + +env: + REGISTRY: ghcr.io + ORG: cosmos + IMAGE_NAME: ibc-go-simd + GIT_TAG: "${{ inputs.tag }}" + +jobs: + build-image-at-tag: + runs-on: depot-ubuntu-22.04-4 + permissions: + packages: write + contents: read + steps: + - uses: actions/checkout@v4 + with: + ref: "${{ env.GIT_TAG }}" + fetch-depth: 0 + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push image + run: | + # remove any `/` characters from the docker tag and replace them with a - + docker_tag="$(echo $GIT_TAG | sed 's/[^a-zA-Z0-9\.]/-/g')" + docker build . -t "${REGISTRY}/${ORG}/${IMAGE_NAME}:${docker_tag}" --build-arg IBC_GO_VERSION=${{ inputs.ibc-go-version }} + docker push "${REGISTRY}/${ORG}/${IMAGE_NAME}:${docker_tag}" diff --git a/.github/workflows/build-wasm-simd-image-from-tag.yml b/.github/workflows/build-wasm-simd-image-from-tag.yml new file mode 100644 index 0000000..43e4289 --- /dev/null +++ b/.github/workflows/build-wasm-simd-image-from-tag.yml @@ -0,0 +1,108 @@ +name: Build Wasm Simd Image +on: + workflow_dispatch: + inputs: + tag: + description: 'The tag of the image to build' + required: true + type: string + +env: + REGISTRY: ghcr.io + ORG: cosmos + IMAGE_NAME: ibc-go-wasm-simd + GIT_TAG: "${{ inputs.tag }}" + +jobs: + build-image-at-tag: + permissions: + packages: write + contents: read + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-24.04 + platform: linux/amd64 + - os: ubuntu-24.04-arm + platform: linux/arm64 + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + with: + ref: "${{ env.GIT_TAG }}" + fetch-depth: 0 + + # TODO: #7885 Get rid of this script, it is super unecessary and can probably be done in the Dockerfile or a bash script + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + - name: Install dependencies + run: make python-install-deps + - name: Get arguments + run: echo "LIBWASM_VERSION=$(scripts/get-libwasm-version.py --get-version)" >> $GITHUB_ENV + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + id: build + uses: docker/build-push-action@v6 + with: + platforms: ${{ matrix.platform }} + file: modules/light-clients/08-wasm/Dockerfile + build-args: LIBWASM_VERSION=${{ env.LIBWASM_VERSION }} + outputs: type=image,"name=${{ env.REGISTRY }}/${{ env.ORG }}/${{ env.IMAGE_NAME }}",push-by-digest=true,name-canonical=true,push=true + + - name: Export digest + run: | + mkdir -p ${{ runner.temp }}/digests + digest="${{ steps.build.outputs.digest }}" + touch "${{ runner.temp }}/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ matrix.os }} # If we end up running more builds on the same OS, we need to differentiate more here + path: ${{ runner.temp }}/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + runs-on: depot-ubuntu-22.04-4 + permissions: + packages: write + contents: read + needs: + - build-image-at-tag + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: ${{ runner.temp }}/digests + pattern: digests-* + merge-multiple: true + + - name: Get docker tag + # remove all `/` or `+` characters from the docker tag and replace them with a -. + # this ensures the docker tag is valid. + run: echo "DOCKER_TAG=$(echo $GIT_TAG | sed 's/[^a-zA-Z0-9\.]/-/g')" >> $GITHUB_ENV + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Create manifest list and push + working-directory: ${{ runner.temp }}/digests + run: | + docker buildx imagetools create --tag ${{ env.REGISTRY }}/${{ env.ORG }}/${{ env.IMAGE_NAME }}:${{ env.DOCKER_TAG }} $(printf '${{ env.REGISTRY }}/${{ env.ORG }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..db22f8c --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,81 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ main ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ main ] + schedule: + - cron: '37 21 * * 4' + +jobs: + analyze: + name: Analyze + runs-on: depot-ubuntu-22.04-4 + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - uses: technote-space/get-diff-action@v6.1.2 + with: + PATTERNS: | + **/**.go + go.mod + go.sum + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + queries: crypto-com/cosmos-sdk-codeql@main,security-and-quality + if: env.GIT_DIFF + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + if: env.GIT_DIFF + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + if: env.GIT_DIFF diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..030a4a3 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,70 @@ +name: Docker Build & Push Simapp (main) +# Build & Push builds the simapp docker image on every push to main and +# and pushes the image to https://ghcr.io/cosmos/ibc-go-simd +on: + workflow_dispatch: + push: + branches: + - main + - release/v* + paths: + - '.github/workflows/docker.yml' + - '**.go' + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ibc-go-simd + +jobs: + docker-build: + runs-on: depot-ubuntu-22.04-4 + permissions: + packages: write + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/cosmos/${{ env.IMAGE_NAME }} + + - name: Compute release branch tag + id: reltag + if: startsWith(github.ref_name, 'release/v') + shell: bash + run: | + RAW="${GITHUB_REF_NAME}" + SANITIZED="${RAW//\//-}" + echo "full_tag=${{ env.REGISTRY }}/cosmos/${{ env.IMAGE_NAME }}:branch-${SANITIZED}" >> "$GITHUB_OUTPUT" + + - name: Build Docker image + uses: docker/build-push-action@v6 + with: + context: . + tags: ${{ steps.meta.outputs.tags }} + build-args: | + IBC_GO_VERSION=${{ github.ref_name }} + + - name: Test simd is runnable + run: | + docker run --rm ${{ steps.meta.outputs.tags }} + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Push Docker image + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: | + ${{ steps.meta.outputs.tags }} + ${{ steps.reltag.outputs.full_tag }} + build-args: | + IBC_GO_VERSION=${{ github.ref_name }} diff --git a/.github/workflows/docs-check.yml b/.github/workflows/docs-check.yml new file mode 100644 index 0000000..b94aa70 --- /dev/null +++ b/.github/workflows/docs-check.yml @@ -0,0 +1,36 @@ +name: Check docs build +on: + merge_group: + pull_request: + branches: + - main + paths: + - 'docs/**' + - '.github/workflows/check-docs.yml' + +jobs: + build: + runs-on: depot-ubuntu-22.04-4 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: docs/package-lock.json + + - name: Install dependencies + run: cd docs && npm ci + - name: Test build website + run: cd docs && npm run build + + lint: + runs-on: depot-ubuntu-22.04-4 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: DavidAnson/markdownlint-cli2-action@v20 + with: + globs: ./docs/docs/**/*.md + diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml new file mode 100644 index 0000000..2b0be24 --- /dev/null +++ b/.github/workflows/docs-deploy.yml @@ -0,0 +1,34 @@ +# This deploy-docs workflow was created based on instructions from: +# https://docusaurus.io/docs/deployment +name: Deploy to GitHub Pages + +on: + workflow_dispatch: + push: + branches: + - main + paths: + - "docs/**" + - .github/workflows/deploy-docs.yml + +jobs: + deploy: + name: Deploy to GitHub Pages + runs-on: depot-ubuntu-22.04-4 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: docs/package-lock.json + + - name: Build website + run: make build-docs + + - name: Deploy 🚀 + uses: JamesIves/github-pages-deploy-action@v4.7.3 + with: + branch: gh-pages + folder: docs/build + single-commit: true diff --git a/.github/workflows/e2e-compatibility-workflow-call.yaml b/.github/workflows/e2e-compatibility-workflow-call.yaml new file mode 100644 index 0000000..f2c494b --- /dev/null +++ b/.github/workflows/e2e-compatibility-workflow-call.yaml @@ -0,0 +1,74 @@ +on: + workflow_call: + inputs: + test-file: + description: 'The test file' + required: true + type: string + release-version: + description: 'the release tag, e.g. release-v7.3.0' + required: true + type: string + chain: + description: 'Should be one of chain-a, chain-b or all. Split up workflows into multiple (chain-a and chain-b) versions if the job limit is exceeded.' + required: false + type: string + default: all + +jobs: + load-test-matrix: + outputs: + test-matrix: ${{ steps.set-test-matrix.outputs.test-matrix }} + runs-on: depot-ubuntu-22.04-4 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + - run: pip install -r requirements.txt + - run: | + # use jq -c to compact the full json contents into a single line. This is required when using the json body + # to create the matrix in the following job. + test_matrix="$(python scripts/generate-compatibility-json.py --file ${{ inputs.test-file }} --release-version ${{ inputs.release-version }} --chain ${{ inputs.chain }})" + echo "test-matrix=$test_matrix" >> $GITHUB_OUTPUT + id: set-test-matrix + + e2e: + runs-on: depot-ubuntu-22.04-4 + needs: load-test-matrix + # this job is skipped if the test-matrix generated is empty. i.e. if the file was not present. + # this allows us to not have to handle special case versions which may not have certain tests run against them. + if: needs.load-test-matrix.outputs.test-matrix + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.load-test-matrix.outputs.test-matrix) }} + steps: + - name: Checkout the ibc-go repo + uses: actions/checkout@v4 + with: + repository: cosmos/ibc-go + - uses: actions/setup-go@v5 + with: + go-version: '1.23' + cache-dependency-path: 'e2e/go.sum' + - name: Run e2e Test + run: | + cd e2e + make e2e-test test=${{ matrix.test }} + env: + # each test has its own set of variables to specify which images are used. + # Note: this is significant as the standard behaviour when running e2es on PRs + # is that there is a set of env vars that are the same for each run. e.g. the same docker image is used + # for every test. With compatibility tests, each test may be running different combinations of images. + CHAIN_A_TAG: '${{ matrix.chain-a }}' + CHAIN_B_TAG: '${{ matrix.chain-b }}' + RELAYER_ID: '${{ matrix.relayer-type }}' + - name: Upload Diagnostics + uses: actions/upload-artifact@v4 + # we only want to upload logs on test failures. + if: ${{ failure() }} + continue-on-error: true + with: + name: '${{ matrix.entrypoint }}-${{ matrix.test }}' + path: e2e/diagnostics + retention-days: 5 diff --git a/.github/workflows/e2e-compatibility.yaml b/.github/workflows/e2e-compatibility.yaml new file mode 100644 index 0000000..b63ca9c --- /dev/null +++ b/.github/workflows/e2e-compatibility.yaml @@ -0,0 +1,230 @@ +# Runs compatibility tests for ibc-go. +# Can be triggered manually by setting values for release-branch and ibc-go-version. +# On a weekly schedule with default values of 'main' for release-branch and 'main-cron-job' for ibc-go-version. +name: Compatibility E2E +on: + schedule: + # run on 20:00 on Sunday. + - cron: '0 20 * * 6' + workflow_dispatch: + inputs: + release-branch: + description: 'Release branch to test' + required: true + type: choice + options: + - release/v7.10.x + - release/v8.7.x + - release/v10.3.x + - main + ibc-go-version: + description: 'The version of ibc-go that is going to be released' + required: true + type: string + +env: + REGISTRY: ghcr.io + ORG: cosmos + IMAGE_NAME: ibc-go-simd + RELEASE_BRANCH: ${{ inputs.release-branch || 'main' }} + IBC_GO_VERSION: ${{ inputs.ibc-go-version || 'latest' }} + +jobs: + determine-image-tag: + runs-on: depot-ubuntu-22.04-4 + outputs: + release-version: ${{ steps.set-release-version.outputs.release-version }} + steps: + - run: | + # we sanitize the release branch name. Docker images cannot contain "/" + # characters so we replace them with a "-". + release_version="$(echo $RELEASE_BRANCH | sed 's/\//-/')" + echo "release-version=$release_version" >> $GITHUB_OUTPUT + id: set-release-version + + # build-release-images builds all docker images that are relevant for the compatibility tests. If a single release + # branch is specified, only that image will be built, e.g. release-v6.0.x. + build-release-images: + runs-on: depot-ubuntu-22.04-4 + permissions: + packages: write + contents: read + strategy: + matrix: + release-branch: + - release/v7.10.x + - release/v8.7.x + - release/v10.3.x + - main + steps: + - uses: actions/checkout@v4 + if: env.RELEASE_BRANCH == matrix.release-branch + with: + ref: "${{ matrix.release-branch }}" + fetch-depth: 0 + - name: Log in to the Container registry + if: env.RELEASE_BRANCH == matrix.release-branch + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build image + if: env.RELEASE_BRANCH == matrix.release-branch + run: | + docker_tag="$(echo ${{ matrix.release-branch }} | sed 's/[^a-zA-Z0-9\.]/-/g')" + docker build . -t "${REGISTRY}/${ORG}/${IMAGE_NAME}:$docker_tag" --build-arg IBC_GO_VERSION=${{ env.IBC_GO_VERSION }} + docker push "${REGISTRY}/${ORG}/${IMAGE_NAME}:$docker_tag" + - name: Display image details + if: env.RELEASE_BRANCH == matrix.release-branch + run: | + docker_tag="$(echo ${{ matrix.release-branch }} | sed 's/[^a-zA-Z0-9\.]/-/g')" + docker inspect "${REGISTRY}/${ORG}/${IMAGE_NAME}:$docker_tag" + + client-test: + needs: + - build-release-images + - determine-image-tag + uses: ./.github/workflows/e2e-compatibility-workflow-call.yaml + with: + test-file: "e2e/tests/core/02-client/client_test.go" + release-version: "${{ needs.determine-image-tag.outputs.release-version }}" + + connection-test: + needs: + - build-release-images + - determine-image-tag + uses: ./.github/workflows/e2e-compatibility-workflow-call.yaml + with: + test-file: "e2e/tests/core/03-connection/connection_test.go" + release-version: "${{ needs.determine-image-tag.outputs.release-version }}" + + ica-base-test-a: + needs: + - build-release-images + - determine-image-tag + uses: ./.github/workflows/e2e-compatibility-workflow-call.yaml + with: + test-file: "e2e/tests/interchain_accounts/base_test.go" + release-version: "${{ needs.determine-image-tag.outputs.release-version }}" + chain: "chain-a" + + ica-base-test-b: + needs: + - build-release-images + - determine-image-tag + uses: ./.github/workflows/e2e-compatibility-workflow-call.yaml + with: + test-file: "e2e/tests/interchain_accounts/base_test.go" + release-version: "${{ needs.determine-image-tag.outputs.release-version }}" + chain: "chain-b" + + ica-gov-test: + needs: + - build-release-images + - determine-image-tag + uses: ./.github/workflows/e2e-compatibility-workflow-call.yaml + with: + test-file: "e2e/tests/interchain_accounts/gov_test.go" + release-version: "${{ needs.determine-image-tag.outputs.release-version }}" + + ica-groups-test: + needs: + - build-release-images + - determine-image-tag + uses: ./.github/workflows/e2e-compatibility-workflow-call.yaml + with: + test-file: "e2e/tests/interchain_accounts/groups_test.go" + release-version: "${{ needs.determine-image-tag.outputs.release-version }}" + + ica-localhost-test: + needs: + - build-release-images + - determine-image-tag + uses: ./.github/workflows/e2e-compatibility-workflow-call.yaml + with: + test-file: "e2e/tests/interchain_accounts/localhost_test.go" + release-version: "${{ needs.determine-image-tag.outputs.release-version }}" + + ica-params-test: + needs: + - build-release-images + - determine-image-tag + uses: ./.github/workflows/e2e-compatibility-workflow-call.yaml + with: + test-file: "e2e/tests/interchain_accounts/params_test.go" + release-version: "${{ needs.determine-image-tag.outputs.release-version }}" + + ica-query-test: + needs: + - build-release-images + - determine-image-tag + uses: ./.github/workflows/e2e-compatibility-workflow-call.yaml + with: + test-file: "e2e/tests/interchain_accounts/query_test.go" + release-version: "${{ needs.determine-image-tag.outputs.release-version }}" + + transfer-base-test-a: + needs: + - build-release-images + - determine-image-tag + uses: ./.github/workflows/e2e-compatibility-workflow-call.yaml + with: + test-file: "e2e/tests/transfer/base_test.go" + release-version: "${{ needs.determine-image-tag.outputs.release-version }}" + chain: "chain-a" + + transfer-base-test-b: + needs: + - build-release-images + - determine-image-tag + uses: ./.github/workflows/e2e-compatibility-workflow-call.yaml + with: + test-file: "e2e/tests/transfer/base_test.go" + release-version: "${{ needs.determine-image-tag.outputs.release-version }}" + chain: "chain-b" + + transfer-authz-test: + needs: + - build-release-images + - determine-image-tag + uses: ./.github/workflows/e2e-compatibility-workflow-call.yaml + with: + test-file: "e2e/tests/transfer/authz_test.go" + release-version: "${{ needs.determine-image-tag.outputs.release-version }}" + + transfer-localhost-test: + needs: + - build-release-images + - determine-image-tag + uses: ./.github/workflows/e2e-compatibility-workflow-call.yaml + with: + test-file: "e2e/tests/transfer/localhost_test.go" + release-version: "${{ needs.determine-image-tag.outputs.release-version }}" + + transfer-send-enabled-test: + needs: + - build-release-images + - determine-image-tag + uses: ./.github/workflows/e2e-compatibility-workflow-call.yaml + with: + test-file: "e2e/tests/transfer/send_enabled_test.go" + release-version: "${{ needs.determine-image-tag.outputs.release-version }}" + + transfer-receive-test: + needs: + - build-release-images + - determine-image-tag + uses: ./.github/workflows/e2e-compatibility-workflow-call.yaml + with: + test-file: "e2e/tests/transfer/send_receive_test.go" + release-version: "${{ needs.determine-image-tag.outputs.release-version }}" + + upgrade-genesis-test: + needs: + - build-release-images + - determine-image-tag + uses: ./.github/workflows/e2e-compatibility-workflow-call.yaml + with: + test-file: "e2e/tests/upgrades/genesis_test.go" + release-version: "${{ needs.determine-image-tag.outputs.release-version }}" diff --git a/.github/workflows/e2e-test-workflow-call.yml b/.github/workflows/e2e-test-workflow-call.yml new file mode 100644 index 0000000..b171633 --- /dev/null +++ b/.github/workflows/e2e-test-workflow-call.yml @@ -0,0 +1,279 @@ +on: + workflow_call: + inputs: + test-entry-point: + description: 'Test entry point' + required: false + type: string + default: '' # empty string means run all tests + temp-run-full-suite: + description: 'This flag exists to run a hard coded set of tests and will be phased out' + required: false + type: boolean + default: false + test: + description: 'test name to run as standalone' + required: false + type: string + default: '' + test-exclusions: + description: 'Comma separated list of tests to skip' + required: false + type: string + default: '' # empty string means don't skip any test. + chain-image: + description: 'The image to use for chains' + required: false + type: string + default: 'ghcr.io/cosmos/ibc-go-simd' + chain-a-tag: + description: 'The tag to use for chain A' + required: true + type: string + default: main + chain-b-tag: + default: main + description: 'The tag to use for chain B' + required: true + type: string + # upgrade-plan-name is only required during upgrade tests, and is otherwise ignored. + upgrade-plan-name: + default: '' + description: 'The upgrade plan name' + required: false + type: string + build-and-push-docker-image: + description: 'Flag to specify if the docker image should be built and pushed beforehand' + required: false + type: boolean + default: false + build-and-push-docker-image-wasm: + description: 'Flag to specify if the wasm docker image should be built and pushed beforehand' + required: false + type: boolean + default: false + upload-logs: + description: 'Specify flag to indicate that logs should be uploaded on failure' + required: false + type: boolean + default: false + e2e-config-path: + description: 'Specify relative or absolute path of config file for test' + required: false + type: string + default: 'ci-e2e-config.yaml' + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ibc-go-simd + IMAGE_NAME_WASM: ibc-go-wasm-simd + +jobs: + # test-details exists to provide an easy way to see the inputs for the e2e test. + test-details: + runs-on: depot-ubuntu-22.04-4 + steps: + - name: Display Inputs + run: | + echo "Chain Image: ${{ inputs.chain-image }}" + echo "Chain A Tag: ${{ inputs.chain-a-tag }}" + echo "Chain B Tag: ${{ inputs.chain-b-tag }}" + echo "Upgrade Plan Name: ${{ inputs.upgrade-plan-name }}" + echo "Test Entry Point: ${{ inputs.test-entry-point }}" + echo "Test: ${{ inputs.test }}" + echo "Github Ref Name: ${{ github.ref_name }}" + + # we skip individual steps rather than the full job as e2e-tests will not run if this task + # is skipped. But will run if every individual task is skipped. There is no current way of conditionally needing + # a job. + docker-build: + runs-on: depot-ubuntu-22.04-4 + steps: + - uses: actions/checkout@v4 + if: ${{ inputs.build-and-push-docker-image }} + - name: Log in to the Container registry + if: ${{ inputs.build-and-push-docker-image }} + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + if: ${{ inputs.build-and-push-docker-image }} + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/cosmos/${{ env.IMAGE_NAME }} + + - name: Build and push Docker image + if: ${{ inputs.build-and-push-docker-image }} + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + build-args: | + IBC_GO_VERSION=${{ github.ref_name }} + + docker-build-wasm: + runs-on: depot-ubuntu-22.04-4 + steps: + - uses: actions/checkout@v4 + if: ${{ inputs.build-and-push-docker-image-wasm }} + + - uses: actions/setup-python@v5 + if: ${{ inputs.build-and-push-docker-image-wasm }} + with: + python-version: '3.10' + + - name: Install dependencies + if: ${{ inputs.build-and-push-docker-image-wasm }} + run: make python-install-deps + + - name: Determine Build arguments + if: ${{ inputs.build-and-push-docker-image-wasm }} + id: build-args + run: | + echo "version=$(scripts/get-libwasm-version.py --get-version)" >> $GITHUB_OUTPUT + echo "checksum=$(scripts/get-libwasm-version.py --get-checksum)" >> $GITHUB_OUTPUT + + - name: Log in to the Container registry + if: ${{ inputs.build-and-push-docker-image-wasm }} + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + if: ${{ inputs.build-and-push-docker-image-wasm }} + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/cosmos/${{ env.IMAGE_NAME_WASM }} + + - name: Build and push Docker image + if: ${{ inputs.build-and-push-docker-image-wasm }} + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + file: modules/light-clients/08-wasm/Dockerfile + build-args: | + LIBWASM_VERSION=${{ steps.build-args.outputs.version }} + LIBWASM_CHECKSUM=${{ steps.build-args.outputs.checksum }} + + + # dynamically build a matrix of test/test suite pairs to run. + # this job runs a go tool located at cmd/build_test_matrix/main.go. + # it walks the e2e/test directory in order to locate all test suite / test name + # pairs. The output of this job can be fed in as input to a workflow matrix and + # will expand to jobs which will run all tests present. + build-test-matrix: + runs-on: depot-ubuntu-22.04-4 + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - uses: actions/checkout@v4 + with: + repository: cosmos/ibc-go + - uses: actions/setup-go@v5 + with: + go-version: '1.23' + - id: set-matrix + run: | + output=$(go run cmd/build_test_matrix/main.go) + echo "matrix=$output" >> $GITHUB_OUTPUT + env: + TEST_ENTRYPOINT: '${{ inputs.test-entry-point }}' + TEST_EXCLUSIONS: '${{ inputs.test-exclusions }}' + TEST_NAME: '${{ inputs.test }}' + + # e2e-tests runs the actual go test command to trigger the test. + # the tests themselves are configured via environment variables to specify + # things like chain and relayer images and tags. + e2e-tests: + runs-on: depot-ubuntu-22.04-4 + needs: + - build-test-matrix + - docker-build + - docker-build-wasm + env: + CHAIN_IMAGE: '${{ inputs.chain-image }}' + CHAIN_UPGRADE_PLAN: '${{ inputs.upgrade-plan-name }}' + CHAIN_A_TAG: '${{ inputs.chain-a-tag }}' + CHAIN_B_TAG: '${{ inputs.chain-b-tag }}' + E2E_CONFIG_PATH: '${{ inputs.e2e-config-path }}' + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.build-test-matrix.outputs.matrix) }} + steps: + - uses: actions/checkout@v4 + with: + repository: cosmos/ibc-go + - uses: actions/setup-go@v5 + with: + go-version: '1.23' + cache-dependency-path: 'e2e/go.sum' + - name: Run e2e Test + id: e2e_test + run: | + cd e2e + make e2e-test test=${{ matrix.test }} + - name: Upload Diagnostics + uses: actions/upload-artifact@v4 + if: ${{ failure() && inputs.upload-logs }} + continue-on-error: true + with: + name: '${{ matrix.entrypoint }}-${{ matrix.test }}' + path: e2e/diagnostics + retention-days: 5 + + e2e-test-suites: + # temporary flag. eventually this field will not exist and this will be the default. + if: ${{ inputs.temp-run-full-suite }} + runs-on: depot-ubuntu-22.04-4 + needs: + - build-test-matrix + - docker-build + - docker-build-wasm + env: + CHAIN_IMAGE: '${{ inputs.chain-image }}' + CHAIN_A_TAG: '${{ inputs.chain-a-tag }}' + CHAIN_B_TAG: '${{ inputs.chain-b-tag }}' + E2E_CONFIG_PATH: '${{ inputs.e2e-config-path }}' + strategy: + fail-fast: false + matrix: + include: + # for now we explicitly specify this test suite. + - entrypoint: TestTransferTestSuite + - entrypoint: TestAuthzTransferTestSuite + - entrypoint: TestTransferTestSuiteSendReceive + - entrypoint: TestTransferTestSuiteSendEnabled + - entrypoint: TestTransferLocalhostTestSuite + - entrypoint: TestConnectionTestSuite + - entrypoint: TestInterchainAccountsGovTestSuite + steps: + - uses: actions/checkout@v4 + with: + repository: cosmos/ibc-go + - uses: actions/setup-go@v5 + with: + go-version: '1.23' + cache-dependency-path: 'e2e/go.sum' + - name: Run e2e Test + id: e2e_test + run: | + cd e2e + make e2e-suite entrypoint=${{ matrix.entrypoint }} + - name: Upload Diagnostics + uses: actions/upload-artifact@v4 + if: ${{ failure() && inputs.upload-logs }} + continue-on-error: true + with: + name: '${{ matrix.entrypoint }}-${{ matrix.test }}' + path: e2e/diagnostics + retention-days: 5 diff --git a/.github/workflows/e2e-upgrade.yaml b/.github/workflows/e2e-upgrade.yaml new file mode 100644 index 0000000..f4899dd --- /dev/null +++ b/.github/workflows/e2e-upgrade.yaml @@ -0,0 +1,82 @@ +name: Tests / E2E Upgrade +on: + workflow_dispatch: + pull_request: + branches: + - main + paths: + # upgrade tests will run on any changes to the upgrade_test.go file, + # and changes to the workflow itself. + - 'e2e/tests/upgrades/*.go' + - '.github/workflows/e2e-upgrade.yaml' + schedule: + - cron: '0 0 * * *' + +env: + DOCKER_IMAGE_NAME: ghcr.io/cosmos/ibc-go-simd + +jobs: + e2e-upgrade-tests: + runs-on: depot-ubuntu-22.04-4 + strategy: + fail-fast: false + matrix: + test-config: [ + { + tag: v6.1.0, + upgrade-plan: v7, + test: TestV6ToV7ChainUpgrade + }, + { + tag: v7.0.0, + upgrade-plan: v7.1, + test: TestV7ToV7_1ChainUpgrade + }, + { + tag: v7.10.0, + upgrade-plan: v8, + test: TestV7ToV8ChainUpgrade + }, + { + tag: v8.0.0, + upgrade-plan: v8.1, + test: TestV8ToV8_1ChainUpgrade + }, + { + tag: v8.7.0, + upgrade-plan: v10, + test: TestV8ToV10ChainUpgrade + }, + { + tag: v8.7.0, + upgrade-plan: v10, + test: TestV8ToV10ChainUpgrade_Localhost + }, + ] + steps: + - uses: actions/checkout@v4 + with: + repository: cosmos/ibc-go + - uses: actions/setup-go@v5 + with: + go-version: '1.23' + cache-dependency-path: 'e2e/go.sum' + - name: Run e2e Test + id: e2e_test + env: + CHAIN_IMAGE: '${{ env.DOCKER_IMAGE_NAME }}' + CHAIN_A_TAG: "${{ matrix['test-config'].tag }}" + CHAIN_B_TAG: "${{ matrix['test-config'].tag }}" + CHAIN_UPGRADE_PLAN: "${{ matrix['test-config']['upgrade-plan'] }}" + E2E_CONFIG_PATH: 'ci-e2e-config.yaml' + run: | + cd e2e + make e2e-test test=${{ matrix['test-config'].test }} + - name: Upload Diagnostics + uses: actions/upload-artifact@v4 + if: ${{ failure() }} + continue-on-error: true + with: + name: "${{ matrix['test-config'].test }}" + path: e2e/diagnostics + retention-days: 5 diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml new file mode 100644 index 0000000..a738fd3 --- /dev/null +++ b/.github/workflows/e2e.yaml @@ -0,0 +1,118 @@ +name: Tests / E2E +on: + workflow_dispatch: + pull_request: + paths: + - '**/*.go' + - '.github/workflows/e2e.yaml' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +env: + DOCKER_IMAGE_NAME: ghcr.io/cosmos/ibc-go-simd + +jobs: + determine-image-tag: + runs-on: depot-ubuntu-22.04-4 + outputs: + simd-tag: ${{ steps.get-tag.outputs.simd-tag }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: '1.23' + - id: get-tag + run: | + if [ -z "${{ github.event.pull_request.number }}" ] + then + echo "simd-tag=main" >> $GITHUB_OUTPUT + else + tag="pr-${{ github.event.pull_request.number }}" + echo "Using tag $tag" + echo "simd-tag=$tag" >> $GITHUB_OUTPUT + fi + + docker-build: + runs-on: depot-ubuntu-22.04-4 + permissions: + packages: write + contents: read + steps: + - uses: actions/checkout@v4 + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.DOCKER_IMAGE_NAME }} + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + build-args: | + IBC_GO_VERSION=${{ github.ref_name }} + + build-test-matrix: + runs-on: depot-ubuntu-22.04-4 + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - uses: actions/checkout@v4 + with: + repository: cosmos/ibc-go + - uses: actions/setup-go@v5 + with: + go-version: '1.23' + cache-dependency-path: 'go.sum' + - id: set-matrix + run: | + output=$(go run cmd/build_test_matrix/main.go) + echo "matrix=$output" >> $GITHUB_OUTPUT + env: + TEST_EXCLUSIONS: 'TestUpgradeTestSuite' + + e2e-tests: + runs-on: depot-ubuntu-22.04-4 + needs: + - determine-image-tag + - build-test-matrix + - docker-build + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.build-test-matrix.outputs.matrix) }} + steps: + - uses: actions/checkout@v4 + with: + repository: cosmos/ibc-go + - uses: actions/setup-go@v5 + with: + go-version: '1.23' + cache-dependency-path: 'e2e/go.sum' + - name: Run e2e Test + id: e2e_test + env: + CHAIN_IMAGE: '${{ env.DOCKER_IMAGE_NAME }}' + CHAIN_A_TAG: '${{ needs.determine-image-tag.outputs.simd-tag }}' + CHAIN_B_TAG: '${{ needs.determine-image-tag.outputs.simd-tag }}' + E2E_CONFIG_PATH: 'ci-e2e-config.yaml' + run: | + cd e2e + make e2e-test test=${{ matrix.test }} + - name: Upload Diagnostics + uses: actions/upload-artifact@v4 + if: ${{ failure() }} + continue-on-error: true + with: + name: '${{ matrix.entrypoint }}-${{ matrix.test }}' + path: e2e/diagnostics + retention-days: 5 diff --git a/.github/workflows/golangci.yml b/.github/workflows/golangci.yml new file mode 100644 index 0000000..a58ce6b --- /dev/null +++ b/.github/workflows/golangci.yml @@ -0,0 +1,31 @@ +name: golangci-lint +on: + push: + pull_request: +permissions: + contents: read + # Optional: allow read access to pull request. Use with `only-new-issues` option. + pull-requests: read + +jobs: + golangci: + name: lint + runs-on: depot-ubuntu-22.04-4 + strategy: + matrix: + working-directory: ['.', 'modules/light-clients/08-wasm', 'e2e'] + steps: + - uses: actions/setup-go@v5 + with: + go-version: '1.23' + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: golangci-lint + uses: golangci/golangci-lint-action@v8.0.0 + with: + version: v2.1 + only-new-issues: true + args: --timeout 10m + working-directory: ${{ matrix.working-directory }} diff --git a/.github/workflows/proto-breaking-check.yml b/.github/workflows/proto-breaking-check.yml new file mode 100644 index 0000000..1ef5a81 --- /dev/null +++ b/.github/workflows/proto-breaking-check.yml @@ -0,0 +1,16 @@ +name: proto breaking check +# proto breaking check workflow checks if Protobuf file contains breaking changes. +# This workflow runs when a PR that targets Protobuf is opened. +on: + merge_group: + pull_request: + paths: + - "proto/**/*.proto" + +jobs: + proto-breaking-check: + runs-on: depot-ubuntu-22.04-4 + steps: + - uses: actions/checkout@v4 + - name: Run proto-breaking check + run: make proto-check-breaking \ No newline at end of file diff --git a/.github/workflows/proto-registry.yml b/.github/workflows/proto-registry.yml new file mode 100644 index 0000000..781978c --- /dev/null +++ b/.github/workflows/proto-registry.yml @@ -0,0 +1,28 @@ +name: Buf-Push +# Protobuf runs buf (https://buf.build/) push updated proto files to https://buf.build/cosmos/ibc +# This workflow is only run when a .proto file has been changed +on: + workflow_dispatch: + push: + branches: + - main + paths: + - "proto/**" + tags: + - 'v*.*.*' + +jobs: + push: + runs-on: depot-ubuntu-22.04-4 + steps: + - uses: actions/checkout@v4 + - uses: bufbuild/buf-action@v1 + with: + token: ${{ secrets.BUF_TOKEN }} + setup_only: false + github_token: ${{ secrets.GITHUB_TOKEN }} + input: "proto" + push: true + lint: false + format: false + breaking: false diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..133b99e --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,86 @@ +name: Tests / Code Coverage +# Tests / Code Coverage workflow runs unit tests and uploads a code coverage report +# This workflow is run on pushes to main & every pull requests +on: + merge_group: + pull_request: + push: + branches: + - main + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: depot-ubuntu-22.04-4 + strategy: + matrix: + go-arch: ['amd64', 'arm64'] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: '1.23' + - name: Install compiler for arm64. + if: matrix.go-arch == 'arm64' + run: | + sudo apt-get update + sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu build-essential + echo "CC=aarch64-linux-gnu-gcc" >> $GITHUB_ENV + + - name: Build ibc-go + run: GOARCH=${{ matrix.go-arch }} LEDGER_ENABLED=false make build + + - name: Build 08-wasm + run: | + cd modules/light-clients/08-wasm + GOARCH=${{ matrix.go-arch }} CGO_ENABLED=1 go build ./... + + - name: Build e2e + run: | + cd e2e + find ./tests -type d | while IFS= read -r dir + do + if ls "${dir}"/*.go >/dev/null 2>&1; then + CGO_ENABLED=1 GOARCH=${{ matrix.go-arch }} go test -c "$dir" + fi + done + + unit-tests: + runs-on: depot-ubuntu-22.04-4 + strategy: + matrix: + module: [ + { + name: ibc-go, + path: . + }, + { + name: 08-wasm, + path: ./modules/light-clients/08-wasm + }, + { + name: e2e, + path: ./e2e, + additional-args: '-tags="test_e2e"' + } + ] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: '1.23' + cache-dependency-path: '${{ matrix.module.path }}/go.sum' + + - name: test & coverage report creation + run: | + cd ${{ matrix.module.path }} && go test -mod=readonly -coverprofile=profile.out -covermode=atomic ${{ matrix.module.additional-args }} ./... + + - uses: codecov/codecov-action@v5 + with: + fail_ci_if_error: true + files: ${{ matrix.module.path }}/profile.out + flags: ${{ matrix.module.name }} + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6d3148b --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +build/ +*.test +*.bench +.DS_Store +*.log diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..8b06c71 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,139 @@ +version: "2" +run: + tests: true +linters: + default: none + enable: + - errcheck + - goconst + - gocritic + - gosec + - govet + - ineffassign + - misspell + - nakedret + - revive + - staticcheck + - thelper + - unconvert + - unparam + - unused + settings: + gocritic: + disabled-checks: + - appendAssign + gosec: + excludes: + - G101 + - G107 + - G115 + - G404 + confidence: medium + revive: + enable-all-rules: true + rules: + - name: redundant-import-alias + disabled: true + - name: use-any + disabled: true + - name: if-return + disabled: true + - name: max-public-structs + disabled: true + - name: cognitive-complexity + disabled: true + - name: argument-limit + disabled: true + - name: cyclomatic + disabled: true + - name: file-header + disabled: true + - name: function-length + disabled: true + - name: function-result-limit + disabled: true + - name: line-length-limit + disabled: true + - name: flag-parameter + disabled: true + - name: add-constant + disabled: true + - name: empty-lines + disabled: true + - name: banned-characters + disabled: true + - name: deep-exit + disabled: true + - name: confusing-results + disabled: true + - name: unused-parameter + disabled: true + - name: modifies-value-receiver + disabled: true + - name: early-return + disabled: true + - name: confusing-naming + disabled: true + - name: defer + disabled: true + - name: unused-parameter + disabled: true + - name: unhandled-error + arguments: + - fmt.Printf + - fmt.Print + - fmt.Println + - myFunction + disabled: false + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + rules: + - linters: + - revive + text: differs only by capitalization to method + - linters: + - gosec + text: Use of weak random number generator + - linters: + - gosec + text: 'G115: integer overflow conversion' + - linters: + - staticcheck + text: 'SA1019:' + - linters: + - gosec + text: 'G115: integer overflow conversion' + paths: + - third_party$ + - builtin$ + - examples$ +issues: + max-issues-per-linter: 10000 + max-same-issues: 10000 +formatters: + enable: + - gci + - gofumpt + settings: + gci: + sections: + - standard + - default + - blank + - dot + - prefix(cosmossdk.io) + - prefix(github.com/cosmos/cosmos-sdk) + - prefix(github.com/cometbft/cometbft) + - prefix(github.com/cosmos/ibc-go) + custom-order: true + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4a570a8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,1829 @@ + + +# Changelog + +## [Unreleased] + +* [\#8573](https://github.com/cosmos/ibc-go/pull/8573) Support custom address codecs in transfer. + +## [v10.3.0](https://github.com/cosmos/ibc-go/releases/tag/v10.3.0) - 2025-06-06 + +### Features + +### Dependencies + +* [\#8369](https://github.com/cosmos/ibc-go/pull/8369) Bump **github.com/CosmWasm/wasmvm** to **2.2.4** +* [\#8369](https://github.com/cosmos/ibc-go/pull/8369) Bump **github.com/ethereum/go-ethereum** to **1.15.11** + +### API Breaking + +### State Machine Breaking + +### Improvements + +* (core/api) [\#8303](https://github.com/cosmos/ibc-go/pull/8303) Prefix-based routing in IBCv2 Router +- (apps/callbacks) [\#8353](https://github.com/cosmos/ibc-go/pull/8353) Add field in callbacks data for custom calldata + +### Bug Fixes + +### Testing API + +* [\#8371](https://github.com/cosmos/ibc-go/pull/8371) e2e: Create only necessary number of chains for e2e suite. +* [\#8375](https://github.com/cosmos/ibc-go/pull/8375) feat: parse IBC v2 packets from ABCI events + +## [v10.2.0](https://github.com/cosmos/ibc-go/releases/tag/v10.2.0) - 2025-04-30 + +### Features + +* (light-clients/07-tendermint) [\#8185](https://github.com/cosmos/ibc-go/pull/8185) Allow scaling of trusting period for client upgrades + +### Dependencies + +* [\#8254](https://github.com/cosmos/ibc-go/pull/8254) Bump **github.com/cosmos/cosmos-sdk** to **0.53.0** +* [\#8326](https://github.com/cosmos/ibc-go/pull/8329) Bump **cosmossdk.io/x/upgrade** to **0.2.0** +* [\#8326](https://github.com/cosmos/ibc-go/pull/8326) Bump **cosmossdk.io/api** to **0.9.2** +* [\#8293](https://github.com/cosmos/ibc-go/pull/8293) Bump **cosmossdk.io/math** to **1.5.3** +* [\#8254](https://github.com/cosmos/ibc-go/pull/8254) Bump **cosmossdk.io/core** to **0.11.3** +* [\#8254](https://github.com/cosmos/ibc-go/pull/8254) Bump **cosmossdk.io/store** to **1.1.2** +* [\#8254](https://github.com/cosmos/ibc-go/pull/8254) Bump **cosmossdk.io/x/tx** to **0.14.0** +* [\#8253](https://github.com/cosmos/ibc-go/pull/8253) Bump **cosmossdk.io/errors** to **1.0.2** +* [\#8253](https://github.com/cosmos/ibc-go/pull/8253) Bump **cosmossdk.io/log** to **1.5.1** +* [\#8253](https://github.com/cosmos/ibc-go/pull/8253) Bump **github.com/cometbft/cometbft** to **0.38.17** +* [\#8264](https://github.com/cosmos/ibc-go/pull/8264) Bump **github.com/prysmaticlabs/prysm** to **v5.3.0** + +### Bug Fixes + +* [\#8287](https://github.com/cosmos/ibc-go/pull/8287) rename total_escrow REST query from `denoms` to `total_escrow` + +## [v10.1.0](https://github.com/cosmos/ibc-go/releases/tag/v10.1.0) - 2025-03-14 + +### Security Fixes + +* Fix [ISA-2025-001](https://github.com/cosmos/ibc-go/security/advisories/GHSA-4wf3-5qj9-368v) security vulnerability. +* Fix [ASA-2025-004](https://github.com/cosmos/ibc-go/security/advisories/GHSA-jg6f-48ff-5xrw) security vulnerability. + +### Features + +* (core) [\#7505](https://github.com/cosmos/ibc-go/pull/7505) Add IBC Eureka (IBC v2) implementation, enabling more efficient IBC packet handling without channel dependencies, bringing significant performance improvements. +* (apps/transfer) [\#7650](https://github.com/cosmos/ibc-go/pull/7650) Add support for transfer of entire balance for vesting accounts. +* (apps/wasm) [\#5079](https://github.com/cosmos/ibc-go/pull/5079) 08-wasm light client proxy module for wasm clients by. +* (core/02-client) [\#7936](https://github.com/cosmos/ibc-go/pull/7936) Clientv2 module. +* (core/04-channel) [\#7933](https://github.com/cosmos/ibc-go/pull/7933) Channel-v2 genesis. +* (core/04-channel, core/api) [\#7934](https://github.com/cosmos/ibc-go/pull/7934) - Callbacks Eureka. +* (light-clients/09-localhost) [\#6683](https://github.com/cosmos/ibc-go/pull/6683) Make 09-localhost stateless. +* (core, app) [\#6902](https://github.com/cosmos/ibc-go/pull/6902) Add channel version to core app callbacks. + +### Dependencies + +* [\#8181](https://github.com/cosmos/ibc-go/pull/8181) Bump **github.com/cosmos/cosmos-sdk** to **0.50.13** +* [\#7932](https://github.com/cosmos/ibc-go/pull/7932) Bump **go** to **1.23** +* [\#7330](https://github.com/cosmos/ibc-go/pull/7330) Bump **cosmossdk.io/api** to **0.7.6** +* [\#6828](https://github.com/cosmos/ibc-go/pull/6828) Bump **cosmossdk.io/core** to **0.11.1** +* [\#7182](https://github.com/cosmos/ibc-go/pull/7182) Bump **cosmossdk.io/log** to **1.4.1** +* [\#7264](https://github.com/cosmos/ibc-go/pull/7264) Bump **cosmossdk.io/store** to **1.1.1** +* [\#7585](https://github.com/cosmos/ibc-go/pull/7585) Bump **cosmossdk.io/math** to **1.4.0** +* [\#7540](https://github.com/cosmos/ibc-go/pull/7540) Bump **github.com/cometbft/cometbft** to **0.38.15** +* [\#6828](https://github.com/cosmos/ibc-go/pull/6828) Bump **cosmossdk.io/x/upgrade** to **0.1.4** +* [\#8124](https://github.com/cosmos/ibc-go/pull/8124) Bump **cosmossdk.io/x/tx** to **0.13.7** +* [\#7942](https://github.com/cosmos/ibc-go/pull/7942) Bump **github.com/cosmos/cosmos-db** to **1.1.1** +* [\#7224](https://github.com/cosmos/ibc-go/pull/7224) Bump **github.com/cosmos/ics23/go** to **0.11.0** + +### API Breaking + +* (core, apps) [\#7213](https://github.com/cosmos/ibc-go/pull/7213) Remove capabilities from `SendPacket`. +* (core, apps) [\#7225](https://github.com/cosmos/ibc-go/pull/7225) Remove capabilities from `WriteAcknowledgement`. +* (core, apps) [\#7232](https://github.com/cosmos/ibc-go/pull/7232) Remove capabilities from channel handshake methods. +* (core, apps) [\#7270](https://github.com/cosmos/ibc-go/pull/7270) Remove remaining dependencies on capability module. +* (core, apps) [\#4811](https://github.com/cosmos/ibc-go/pull/4811) Use expected interface for legacy params subspace +* (core/04-channel) [\#7239](https://github.com/cosmos/ibc-go/pull/7239) Removed function `LookupModuleByChannel` +* (core/05-port) [\#7252](https://github.com/cosmos/ibc-go/pull/7252) Removed function `LookupModuleByPort` +* (core/24-host) [\#7239](https://github.com/cosmos/ibc-go/pull/7239) Removed function `ChannelCapabilityPath` +* (apps/27-interchain-accounts) [\#7239](https://github.com/cosmos/ibc-go/pull/7239) The following functions have been removed: `AuthenticateCapability`, `ClaimCapability` +* (apps/27-interchain-accounts) [\#7961](https://github.com/cosmos/ibc-go/pull/7961) Removed `absolute-timeouts` flag from `send-tx` in the ICA CLI. +* (apps/transfer) [\#7239](https://github.com/cosmos/ibc-go/pull/7239) The following functions have been removed: `BindPort`, `AuthenticateCapability`, `ClaimCapability` +* (capability) [\#7279](https://github.com/cosmos/ibc-go/pull/7279) The module `capability` has been removed. +* (testing) [\#7305](https://github.com/cosmos/ibc-go/pull/7305) Added `TrustedValidators` map to `TestChain`. This removes the dependency on the `x/staking` module for retrieving trusted validator sets at a given height, and removes the `GetTrustedValidators` method from the `TestChain` struct. +* (23-commitment) [\#7486](https://github.com/cosmos/ibc-go/pull/7486) Remove unimplemented `BatchVerifyMembership` and `BatchVerifyNonMembership` functions +* (core/02-client, light-clients) [\#5806](https://github.com/cosmos/ibc-go/pull/5806) Decouple light client routing from their encoding structure. +* (core/04-channel) [\#5991](https://github.com/cosmos/ibc-go/pull/5991) The client CLI `QueryLatestConsensusState` has been removed. +* (light-clients/06-solomachine) [\#6037](https://github.com/cosmos/ibc-go/pull/6037) Remove `Initialize` function from `ClientState` and move logic to `Initialize` function of `LightClientModule`. +* (light-clients/06-solomachine) [\#6230](https://github.com/cosmos/ibc-go/pull/6230) Remove `GetTimestampAtHeight`, `Status` and `UpdateStateOnMisbehaviour` functions from `ClientState` and move logic to functions of `LightClientModule`. +* (core/02-client) [\#6084](https://github.com/cosmos/ibc-go/pull/6084) Removed `stakingKeeper` as an argument to `NewKeeper` and replaced with a `ConsensusHost` implementation. +* (testing) [\#6070](https://github.com/cosmos/ibc-go/pull/6070) Remove `AssertEventsLegacy` function. +* (core) [\#6138](https://github.com/cosmos/ibc-go/pull/6138) Remove `Router` reference from IBC core keeper and use instead the router on the existing `PortKeeper` reference. +* (core/04-channel) [\#6023](https://github.com/cosmos/ibc-go/pull/6023) Remove emission of non-hexlified event attributes `packet_data` and `packet_ack`. +* (core) [\#6320](https://github.com/cosmos/ibc-go/pull/6320) Remove unnecessary `Proof` interface from `exported` package. +* (core/05-port) [\#6341](https://github.com/cosmos/ibc-go/pull/6341) Modify `UnmarshalPacketData` interface to take in the context, portID, and channelID. This allows for packet data's to be unmarshaled based on the channel version. +* (apps/27-interchain-accounts) [\#6433](https://github.com/cosmos/ibc-go/pull/6433) Use UNORDERED as the default ordering for new ICA channels. +* (apps/transfer) [\#6440](https://github.com/cosmos/ibc-go/pull/6440) Remove `GetPrefixedDenom`. +* (apps/transfer) [\#6508](https://github.com/cosmos/ibc-go/pull/6508) Remove the `DenomTrace` type. +* (apps/27-interchain-accounts) [\#6598](https://github.com/cosmos/ibc-go/pull/6598) Mark the `requests` repeated field of `MsgModuleQuerySafe` non-nullable. +* (23-commmitment) [\#6644](https://github.com/cosmos/ibc-go/pull/6644) Introduce `commitment.v2.MerklePath` to include `repeated bytes` in favour of `repeated string`. This supports using merkle path keys which include non UTF-8 encoded runes. +* (23-commmitment) [\#6870](https://github.com/cosmos/ibc-go/pull/6870) Remove `commitment.v1.MerklePath` in favour of `commitment.v2.MerklePath`. +* (apps/27-interchain-accounts) [\#6749](https://github.com/cosmos/ibc-go/pull/6749) The ICA controller `NewIBCMiddleware` constructor function sets by default the auth module to nil. +* (core, core/02-client) [\#6763](https://github.com/cosmos/ibc-go/pull/6763) Move prometheus metric labels for 02-client and core into a separate `metrics` package on core. +* (core/02-client) [\#6777](https://github.com/cosmos/ibc-go/pull/6777) The `NewClientProposalHandler` of `02-client` has been removed. +* (core/types) [\#6794](https://github.com/cosmos/ibc-go/pull/6794) The composite interface `QueryServer` has been removed from package `core/types`. Please use the granular `QueryServer` interfaces provided by each core submodule. +* (light-clients/06-solomachine) [\#6888](https://github.com/cosmos/ibc-go/pull/6888) Remove `TypeClientMisbehaviour` constant and the `Type` method on `Misbehaviour`. +* (light-clients/06-solomachine, light-clients/07-tendermint) [\#6891](https://github.com/cosmos/ibc-go/pull/6891) The `VerifyMembership` and `VerifyNonMembership` functions of solomachine's `ClientState` have been made private. The `VerifyMembership`, `VerifyNonMembership`, `GetTimestampAtHeight`, `Status` and `Initialize` functions of tendermint's `ClientState` have been made private. +* (core/04-channel) [\#6902](https://github.com/cosmos/ibc-go/pull/6902) Add channel version to core application callbacks. +* (core/03-connection, core/02-client) [\#6937](https://github.com/cosmos/ibc-go/pull/6937) Remove 'ConsensusHost' interface, also removing self client and consensus state validation in the connection handshake. +* (core/24-host) [\#6882](https://github.com/cosmos/ibc-go/issues/6882) All functions ending in `Path` have been removed from 24-host in favour of their sybling functions ending in `Key`. +* (23-commmitment) [\#6633](https://github.com/cosmos/ibc-go/pull/6633) MerklePath has been changed to use `repeated bytes` in favour of `repeated strings`. +* (23-commmitment) [\#6644](https://github.com/cosmos/ibc-go/pull/6644) Introduce `commitment.v2.MerklePath` to include `repeated bytes` in favour of `repeated string`. This supports using merkle path keys which include non UTF-8 encoded runes. +* (23-commmitment) [\#6870](https://github.com/cosmos/ibc-go/pull/6870) Remove `commitment.v1.MerklePath` in favour of `commitment.v2.MerklePath`. +* [\#6923](https://github.com/cosmos/ibc-go/pull/6923) The JSON msg API for `VerifyMembershipMsg` and `VerifyNonMembershipMsg` payloads for client contract `SudoMsg` has been updated. The field `path` has been changed to `merkle_path`. This change requires updates to 08-wasm client contracts for integration. +* (apps/callbacks) [\#7000](https://github.com/cosmos/ibc-go/pull/7000) Add base application version to contract keeper callbacks. +* (light-clients/08-wasm) [\#5154](https://github.com/cosmos/ibc-go/pull/5154) Use bytes in wasm contract api instead of wrapped. +* (core, core/08-wasm) [\#5397](https://github.com/cosmos/ibc-go/pull/5397) Add coordinator Setup functions to the Path type. +* (core/05-port) [\#6341](https://github.com/cosmos/ibc-go/pull/6341) Modify `UnmarshalPacketData` interface to take in the context, portID, and channelID. This allows for packet data's to be unmarshaled based on the channel version. +* (core/02-client) [\#6863](https://github.com/cosmos/ibc-go/pull/6863) remove ClientStoreProvider interface in favour of concrete type. +* (core/05-port) [\#6988](https://github.com/cosmos/ibc-go/pull/6988) Modify `UnmarshalPacketData` interface to return the underlying application version. +* (apps/27-interchain-accounts) [\#7053](https://github.com/cosmos/ibc-go/pull/7053) Remove ICS27 channel capability migration introduced in v6. +* (apps/27-interchain-accounts) [\#8002](https://github.com/cosmos/ibc-go/issues/8002) Remove ICS-29: fee middleware. +* (core/04-channel) [\#8053](https://github.com/cosmos/ibc-go/issues/8053) Remove channel upgradability. + +### State Machine Breaking + +* (light-clients/06-solomachine) [\#6313](https://github.com/cosmos/ibc-go/pull/6313) Fix: No-op to avoid panicking on `UpdateState` for invalid misbehaviour submissions. +* (apps/callbacks) [\#8014](https://github.com/cosmos/ibc-go/pull/8014) Callbacks will now return an error acknowledgement if the recvPacket callback fails. This reverts all app callback changes whereas before we only reverted the callback changes. We also error on all callbacks if the callback data is set but malformed whereas before we ignored the error and continued processing. +* (apps/callbacks) [\#5349](https://github.com/cosmos/ibc-go/pull/5349) Check if clients params are duplicates. +* (apps/transfer) [\#6268](https://github.com/cosmos/ibc-go/pull/6268) Use memo strings instead of JSON keys in `AllowedPacketData` of transfer authorization. +* (light-clients/07-tendermint) Fix: No-op to avoid panicking on `UpdateState` for invalid misbehaviour submissions. +* (light-clients/06-solomachine) [\#6313](https://github.com/cosmos/ibc-go/pull/6313) Fix: No-op to avoid panicking on `UpdateState` for invalid misbehaviour submissions. + +### Improvements + +* (testing)[\#7430](https://github.com/cosmos/ibc-go/pull/7430) Update the block proposer in test chains for each block. +* (apps/27-interchain-accounts) [\#5533](https://github.com/cosmos/ibc-go/pull/5533) ICA host sets the host connection ID on `OnChanOpenTry`, so that ICA controller implementations are not obliged to set the value on `OnChanOpenInit` if they are not able. +* (core/02-client, core/03-connection, apps/27-interchain-accounts) [\#6256](https://github.com/cosmos/ibc-go/pull/6256) Add length checking of array fields in messages. +* (light-clients/08-wasm) [\#5146](https://github.com/cosmos/ibc-go/pull/5146) Use global wasm VM instead of keeping an additional reference in keeper. +* (core/04-channels) [\#7935](https://github.com/cosmos/ibc-go/pull/7935) Limit payload size for both v1 and v2 packet. +* (core/runtime) [\#7601](https://github.com/cosmos/ibc-go/pull/7601) - IBC core runtime env. +* (core/08-wasm) [\#5294](https://github.com/cosmos/ibc-go/pull/5294) Don't panic during any store operations. +* (apps) [\#5305](https://github.com/cosmos/ibc-go/pull/5305)- Remove GetSigners from `sdk.Msg` implementations. +* (apps) [\#/5778](https://github.com/cosmos/ibc-go/pull/5778) Use json for marshalling/unmarshalling transfer packet data. +* (core/08-wasm) [\#5785](https://github.com/cosmos/ibc-go/pull/5785) Allow module safe queries in ICA. +* (core/ante) [\#6278](https://github.com/cosmos/ibc-go/pull/6278) Performance: Exclude pruning from tendermint client updates in ante handler executions. +* (core/ante) [\#6302](https://github.com/cosmos/ibc-go/pull/6302) Performance: Skip app callbacks during RecvPacket execution in checkTx within the redundant relay ante handler. +* (core/ante) [\#6280](https://github.com/cosmos/ibc-go/pull/6280) Performance: Skip redundant proof checking in RecvPacket execution in reCheckTx within the redundant relay ante handler. +* [\#6716](https://github.com/cosmos/ibc-go/pull/6716) Add `HasModule` to capability keeper to allow checking if a scoped module already exists. + +### Bug Fixes + +* (apps/27-interchain-accounts) [\#7277](https://github.com/cosmos/ibc-go/pull/7277) Use `GogoResolver` when populating module query safe allow list to avoid panics from unresolvable protobuf dependencies. +* (core/04-channel) [\#7342](https://github.com/cosmos/ibc-go/pull/7342) Read Tx cmd flags including from address to avoid Address cannot be empty error when upgrade-channels via cli. +* (core/03-connection) [\#7397](https://github.com/cosmos/ibc-go/pull/7397) Skip the genesis validation connectionID for localhost client. +* (apps/27-interchain-accounts) [\#6377](https://github.com/cosmos/ibc-go/pull/6377) Generate ICA simtest proposals only for provided keepers. + +### Testing API + +* [\#7688](https://github.com/cosmos/ibc-go/pull/7688) Added `SendMsgsWithSender` to `TestChain`. +* [\#7430](https://github.com/cosmos/ibc-go/pull/7430) Update block proposer in testing +* [\#5493](https://github.com/cosmos/ibc-go/pull/5493) Add IBCClientHeader func for endpoint and update tests +* [\#6685](https://github.com/cosmos/ibc-go/pull/6685) Configure relayers to watch only channels associated with an individual test +* [\#6758](https://github.com/cosmos/ibc-go/pull/6758) Tokens are successfully forwarded from A to C through B + +## [v8.5.0](https://github.com/cosmos/ibc-go/releases/tag/v8.5.0) - 2024-08-30 + +### Dependencies + +* [\#6828](https://github.com/cosmos/ibc-go/pull/6828) Bump Cosmos SDK to v0.50.9. +* [\#7222](https://github.com/cosmos/ibc-go/pull/7221) Update ics23 to v0.11.0. + +### State Machine Breaking + +* (core/03-connection) [\#7129](https://github.com/cosmos/ibc-go/pull/7129) Remove verification of self client and consensus state from connection handshake. + +## [v8.4.0](https://github.com/cosmos/ibc-go/releases/tag/v8.4.0) - 2024-07-29 + +### Improvements + +* (core/04-channel) [\#6871](https://github.com/cosmos/ibc-go/pull/6871) Add channel ordering to write acknowledgement event. + +### Features + +* (apps/transfer) [\#6877](https://github.com/cosmos/ibc-go/pull/6877) Added the possibility to transfer the entire user balance of a particular denomination by using [`UnboundedSpendLimit`](https://github.com/cosmos/ibc-go/blob/beb2d93b58835ddb9ed8e7624988f1e66b849251/modules/apps/transfer/types/token.go#L56-L58) as the token amount. + +### Bug Fixes + +* (core/04-channel) [\#6935](https://github.com/cosmos/ibc-go/pull/6935) Check upgrade compatibility in `ChanUpgradeConfirm`. + +## [v8.3.2](https://github.com/cosmos/ibc-go/releases/tag/v8.3.2) - 2024-06-20 + +### Dependencies + +* [\#6614](https://github.com/cosmos/ibc-go/pull/6614) Bump Cosmos SDK to v0.50.7. + +### Improvements + +* (apps/27-interchain-accounts) [\#6436](https://github.com/cosmos/ibc-go/pull/6436) Refactor ICA host keeper instantiation method to avoid panic related to proto files. + +## [v8.3.1](https://github.com/cosmos/ibc-go/releases/tag/v8.3.1) - 2024-05-22 + +### Improvements + +* (core/ante) [\#6302](https://github.com/cosmos/ibc-go/pull/6302) Performance: Skip app callbacks during RecvPacket execution in checkTx within the redundant relay ante handler. +* (core/ante) [\#6280](https://github.com/cosmos/ibc-go/pull/6280) Performance: Skip redundant proof checking in RecvPacket execution in reCheckTx within the redundant relay ante handler. +* (core/ante) [\#6306](https://github.com/cosmos/ibc-go/pull/6306) Performance: Skip misbehaviour checks in UpdateClient flow and skip signature checks in reCheckTx mode. + +## [v8.3.0](https://github.com/cosmos/ibc-go/releases/tag/v8.3.0) - 2024-05-16 + +### Dependencies + +* [\#6300](https://github.com/cosmos/ibc-go/pull/6300) Bump Cosmos SDK to v0.50.6 and CometBFT to v0.38.7. + +### State Machine Breaking + +* (light-clients/07-tendermint) [\#6276](https://github.com/cosmos/ibc-go/pull/6276) Fix: No-op to avoid panicking on `UpdateState` for invalid misbehaviour submissions. + +### Improvements + +* (apps/27-interchain-accounts, apps/transfer, apps/29-fee) [\#6253](https://github.com/cosmos/ibc-go/pull/6253) Allow channel handshake to succeed if fee middleware is wired up on one side, but not the other. +* (apps/27-interchain-accounts) [\#6251](https://github.com/cosmos/ibc-go/pull/6251) Use `UNORDERED` as the default ordering for new ICA channels. +* (apps/transfer) [\#6268](https://github.com/cosmos/ibc-go/pull/6268) Use memo strings instead of JSON keys in `AllowedPacketData` of transfer authorization. +* (core/ante) [\#6278](https://github.com/cosmos/ibc-go/pull/6278) Performance: Exclude pruning from tendermint client updates in ante handler executions. +* (core/ante) [\#6302](https://github.com/cosmos/ibc-go/pull/6302) Performance: Skip app callbacks during RecvPacket execution in checkTx within the redundant relay ante handler. +* (core/ante) [\#6280](https://github.com/cosmos/ibc-go/pull/6280) Performance: Skip redundant proof checking in RecvPacket execution in reCheckTx within the redundant relay ante handler. + +### Features + +* (core) [\#6055](https://github.com/cosmos/ibc-go/pull/6055) Introduce a new interface `ConsensusHost` used to validate an IBC `ClientState` and `ConsensusState` against the host chain's underlying consensus parameters. +* (core/02-client) [\#5821](https://github.com/cosmos/ibc-go/pull/5821) Add rpc `VerifyMembershipProof` (querier approach for conditional clients). +* (core/04-channel) [\#5788](https://github.com/cosmos/ibc-go/pull/5788) Add `NewErrorAcknowledgementWithCodespace` to allow codespaces in ack errors. +* (apps/27-interchain-accounts) [\#5785](https://github.com/cosmos/ibc-go/pull/5785) Introduce a new tx message that ICA host submodule can use to query the chain (only those marked with `module_query_safe`) and write the responses to the acknowledgement. + +### Bug Fixes + +* (apps/27-interchain-accounts) [\#6167](https://github.com/cosmos/ibc-go/pull/6167) Fixed an edge case bug where migrating params for a pre-existing ica module which implemented controller functionality only could panic when migrating params for newly added host, and align controller param migration with host. +* (app/29-fee) [\#6255](https://github.com/cosmos/ibc-go/pull/6255) Delete refunded fees from state if some fee(s) cannot be refunded on channel closure. + +## [v8.2.0](https://github.com/cosmos/ibc-go/releases/tag/v8.2.0) - 2024-04-05 + +### Dependencies + +* [\#5975](https://github.com/cosmos/ibc-go/pull/5975) Bump Cosmos SDK to v0.50.5. + +### Improvements + +* (proto) [\#5987](https://github.com/cosmos/ibc-go/pull/5987) Add wasm proto files. + +## [v8.1.0](https://github.com/cosmos/ibc-go/releases/tag/v8.1.0) - 2024-01-31 + +### Dependencies + +* [\#5663](https://github.com/cosmos/ibc-go/pull/5663) Bump Cosmos SDK to v0.50.3 and CometBFT to v0.38.2. + +### State Machine Breaking + +* (apps/27-interchain-accounts) [\#5442](https://github.com/cosmos/ibc-go/pull/5442) Increase the maximum allowed length for the memo field of `InterchainAccountPacketData`. + +### Improvements + +* (core/02-client) [\#5429](https://github.com/cosmos/ibc-go/pull/5429) Add wildcard `"*"` to allow all clients in `AllowedClients` param. +* (core) [\#5541](https://github.com/cosmos/ibc-go/pull/5541) Enable emission of events on erroneous IBC application callbacks by appending a prefix to all event type and attribute keys. + +### Features + +* (core/04-channel) [\#1613](https://github.com/cosmos/ibc-go/pull/1613) Channel upgradability. +* (apps/transfer) [\#5280](https://github.com/cosmos/ibc-go/pull/5280) Add list of allowed packet data keys to `Allocation` of `TransferAuthorization`. +* (apps/27-interchain-accounts) [\#5633](https://github.com/cosmos/ibc-go/pull/5633) Allow setting new and upgrading existing ICA (ordered) channels to use unordered ordering. + +### Bug Fixes + +* (apps/27-interchain-accounts) [\#5343](https://github.com/cosmos/ibc-go/pull/5343) Add check if controller is enabled in `sendTx` before sending packet to host. +* (apps/29-fee) [\#5441](https://github.com/cosmos/ibc-go/pull/5441) Allow setting the relayer address as payee. + +## [v8.0.1](https://github.com/cosmos/ibc-go/releases/tag/v8.0.1) - 2024-01-31 + +### Dependencies + +* [\#5718](https://github.com/cosmos/ibc-go/pull/5718) Update Cosmos SDK to v0.50.3 and CometBFT to v0.38.2. + +### Improvements + +* (core) [\#5541](https://github.com/cosmos/ibc-go/pull/5541) Enable emission of events on erroneous IBC application callbacks by appending a prefix to all event type and attribute keys. + +## [v8.0.0](https://github.com/cosmos/ibc-go/releases/tag/v8.0.0) - 2023-11-10 + +### Dependencies + +* [\#5038](https://github.com/cosmos/ibc-go/pull/5038) Bump SDK v0.50.1 and cometBFT v0.38. +* [\#4398](https://github.com/cosmos/ibc-go/pull/4398) Update all modules to go 1.21. + +### API Breaking + +* (core) [\#4703](https://github.com/cosmos/ibc-go/pull/4703) Make `PortKeeper` field of `IBCKeeper` a pointer. +* (core/23-commitment) [\#4459](https://github.com/cosmos/ibc-go/pull/4459) Remove `Pretty` and `String` custom implementations of `MerklePath`. +* [\#3205](https://github.com/cosmos/ibc-go/pull/3205) Make event emission functions unexported. +* (apps/27-interchain-accounts, apps/transfer) [\#3253](https://github.com/cosmos/ibc-go/pull/3253) Rename `IsBound` to `HasCapability`. +* (apps/27-interchain-accounts, apps/transfer) [\#3303](https://github.com/cosmos/ibc-go/pull/3303) Make `HasCapability` private. +* [\#3303](https://github.com/cosmos/ibc-go/pull/3856) Add missing/remove unnecessary gogoproto directive. +* (apps/27-interchain-accounts) [\#3967](https://github.com/cosmos/ibc-go/pull/3967) Add encoding type as argument to ICA encoding/decoding functions. +* (core) [\#3867](https://github.com/cosmos/ibc-go/pull/3867) Remove unnecessary event attribute from INIT handshake msgs. +* (core/04-channel) [\#3806](https://github.com/cosmos/ibc-go/pull/3806) Remove unused `EventTypeTimeoutPacketOnClose`. +* (testing) [\#4018](https://github.com/cosmos/ibc-go/pull/4018) Allow failure expectations when using `chain.SendMsgs`. + +### State Machine Breaking + +* (apps/transfer, apps/27-interchain-accounts, app/29-fee) [\#4992](https://github.com/cosmos/ibc-go/pull/4992) Set validation for length of string fields. + +### Improvements + +* [\#3304](https://github.com/cosmos/ibc-go/pull/3304) Remove unnecessary defer func statements. +* (apps/29-fee) [\#3054](https://github.com/cosmos/ibc-go/pull/3054) Add page result to ics29-fee queries. +* (apps/27-interchain-accounts, apps/transfer) [\#3077](https://github.com/cosmos/ibc-go/pull/3077) Add debug level logging for the error message which is discarded when generating a failed acknowledgement. +* (core/03-connection) [\#3244](https://github.com/cosmos/ibc-go/pull/3244) Cleanup 03-connection msg validate basic test. +* (core/02-client) [\#3514](https://github.com/cosmos/ibc-go/pull/3514) Add check for the client status in `CreateClient`. +* (apps/29-fee) [\#4100](https://github.com/cosmos/ibc-go/pull/4100) Adding `MetadataFromVersion` to `29-fee` package `types`. +* (apps/29-fee) [\#4290](https://github.com/cosmos/ibc-go/pull/4290) Use `types.MetadataFromVersion` helper function for callback handlers. +* (core/04-channel) [\#4155](https://github.com/cosmos/ibc-go/pull/4155) Adding `IsOpen` and `IsClosed` methods to `Channel` type. +* (core/03-connection) [\#4110](https://github.com/cosmos/ibc-go/pull/4110) Remove `Version` interface and casting functions from 03-connection. +* (core) [\#4835](https://github.com/cosmos/ibc-go/pull/4835) Use expected interface for legacy params subspace parameter of keeper constructor functions. + +### Features + +* (capability) [\#3097](https://github.com/cosmos/ibc-go/pull/3097) Migrate capability module from Cosmos SDK to ibc-go. +* (core/02-client) [\#3640](https://github.com/cosmos/ibc-go/pull/3640) Migrate client params to be self managed. +* (core/03-connection) [\#3650](https://github.com/cosmos/ibc-go/pull/3650) Migrate connection params to be self managed. +* (apps/transfer) [\#3553](https://github.com/cosmos/ibc-go/pull/3553) Migrate transfer parameters to be self managed (#3553) +* (apps/27-interchain-accounts) [\#3520](https://github.com/cosmos/ibc-go/pull/3590) Migrate ica/controller parameters to be self managed (#3590) +* (apps/27-interchain-accounts) [\#3520](https://github.com/cosmos/ibc-go/pull/3520) Migrate ica/host to params to be self managed. +* (apps/transfer) [\#3104](https://github.com/cosmos/ibc-go/pull/3104) Add metadata for IBC tokens. +* [\#4620](https://github.com/cosmos/ibc-go/pull/4620) Migrate to gov v1 via the additions of `MsgRecoverClient` and `MsgIBCSoftwareUpgrade`. The legacy proposal types `ClientUpdateProposal` and `UpgradeProposal` have been deprecated and will be removed in the next major release. + +### Bug Fixes + +* (apps/transfer) [\#4709](https://github.com/cosmos/ibc-go/pull/4709) Order query service RPCs to fix availability of denom traces endpoint when no args are provided. +* (core/04-channel) [\#3357](https://github.com/cosmos/ibc-go/pull/3357) Handle unordered channels in `NextSequenceReceive` query. +* (e2e) [\#3402](https://github.com/cosmos/ibc-go/pull/3402) Allow retries for messages signed by relayer. +* (core/04-channel) [\#3417](https://github.com/cosmos/ibc-go/pull/3417) Add missing query for next sequence send. +* (testing) [\#4630](https://github.com/cosmos/ibc-go/pull/4630) Update `testconfig` to use revision formatted chain IDs. +* (core/04-channel) [\#4706](https://github.com/cosmos/ibc-go/pull/4706) Retrieve correct next send sequence for packets in unordered channels. +* (core/02-client) [\#4746](https://github.com/cosmos/ibc-go/pull/4746) Register implementations against `govtypes.Content` interface. +* (apps/27-interchain-accounts) [\#4944](https://github.com/cosmos/ibc-go/pull/4944) Add missing proto interface registration. +* (core/02-client) [\#5020](https://github.com/cosmos/ibc-go/pull/5020) Fix expect pointer error when unmarshalling misbehaviour file. + +### Documentation + +* [\#3133](https://github.com/cosmos/ibc-go/pull/3133) Add linter for markdown documents. +* [\#4693](https://github.com/cosmos/ibc-go/pull/4693) Migrate docs to docusaurus. + +### Testing + +* [\#3138](https://github.com/cosmos/ibc-go/pull/3138) Use `testing.TB` instead of `testing.T` to support benchmarks and fuzz tests. +* [\#3980](https://github.com/cosmos/ibc-go/pull/3980) Change `sdk.Events` usage to `[]abci.Event` in the testing package. +* [\#3986](https://github.com/cosmos/ibc-go/pull/3986) Add function `RelayPacketWithResults`. +* [\#4182](https://github.com/cosmos/ibc-go/pull/4182) Return current validator set when requesting current height in `GetValsAtHeight`. +* [\#4319](https://github.com/cosmos/ibc-go/pull/4319) Fix in `TimeoutPacket` function to use counterparty `portID`/`channelID` in `GetNextSequenceRecv` query. +* [\#4180](https://github.com/cosmos/ibc-go/pull/4180) Remove unused function `simapp.SetupWithGenesisAccounts`. + +### Miscellaneous Tasks + +* (apps/27-interchain-accounts) [\#4677](https://github.com/cosmos/ibc-go/pull/4677) Remove ica store key. +* [\#4724](https://github.com/cosmos/ibc-go/pull/4724) Add `HasValidateBasic` compiler assertions to messages. +* [\#4725](https://github.com/cosmos/ibc-go/pull/4725) Add fzf selection for config files. +* [\#4741](https://github.com/cosmos/ibc-go/pull/4741) Panic with error. +* [\#3186](https://github.com/cosmos/ibc-go/pull/3186) Migrate all SDK errors to the new errors go module. +* [\#3216](https://github.com/cosmos/ibc-go/pull/3216) Modify `simapp` to fulfill the SDK `runtime.AppI` interface. +* [\#3290](https://github.com/cosmos/ibc-go/pull/3290) Remove `gogoproto` yaml tags from proto files. +* [\#3439](https://github.com/cosmos/ibc-go/pull/3439) Use nil pointer pattern to check for interface compliance. +* [\#3433](https://github.com/cosmos/ibc-go/pull/3433) Add tests for `acknowledgement.Acknowledgement()`. +* (core, apps/29-fee) [\#3462](https://github.com/cosmos/ibc-go/pull/3462) Add missing `nil` check and corresponding tests for query handlers. +* (light-clients/07-tendermint, light-clients/06-solomachine) [\#3571](https://github.com/cosmos/ibc-go/pull/3571) Delete unused `GetProofSpecs` functions. +* (core) [\#3616](https://github.com/cosmos/ibc-go/pull/3616) Add debug log for redundant relay. +* (core) [\#3892](https://github.com/cosmos/ibc-go/pull/3892) Add deprecated option to `create_localhost` field. +* (core) [\#3893](https://github.com/cosmos/ibc-go/pull/3893) Add deprecated option to `MsgSubmitMisbehaviour`. +* (apps/transfer, apps/29-fee) [\#4570](https://github.com/cosmos/ibc-go/pull/4570) Remove `GetSignBytes` from 29-fee and transfer msgs. +* [\#3630](https://github.com/cosmos/ibc-go/pull/3630) Add annotation to Msg service. + +## [v7.8.0](https://github.com/cosmos/ibc-go/releases/tag/v7.8.0) - 2024-08-30 + +### State Machine Breaking + +* (core/03-connection) [\#7128](https://github.com/cosmos/ibc-go/pull/7128) Remove verification of self client and consensus state from connection handshake. + +## [v7.7.0](https://github.com/cosmos/ibc-go/releases/tag/v7.7.0) - 2024-07-29 + +### Dependencies + +* [\#6943](https://github.com/cosmos/ibc-go/pull/6943) Update Cosmos SDK to v0.47.13. + +### Features + +* (apps/transfer) [\#6877](https://github.com/cosmos/ibc-go/pull/6877) Added the possibility to transfer the entire user balance of a particular denomination by using [`UnboundedSpendLimit`](https://github.com/cosmos/ibc-go/blob/715f00eef8727da41db25fdd4763b709bdbba07e/modules/apps/transfer/types/transfer_authorization.go#L253-L255) as the token amount. + +### Bug Fixes + +## [v7.6.0](https://github.com/cosmos/ibc-go/releases/tag/v7.6.0) - 2024-06-20 + +### State Machine Breaking + +* (apps/transfer, apps/27-interchain-accounts, app/29-fee) [\#4992](https://github.com/cosmos/ibc-go/pull/4992) Set validation for length of string fields. + +## [v7.5.2](https://github.com/cosmos/ibc-go/releases/tag/v7.5.2) - 2024-06-20 + +### Dependencies + +* [\#6613](https://github.com/cosmos/ibc-go/pull/6613) Update Cosmos SDK to v0.47.12. + +### Improvements + +* (apps/27-interchain-accounts) [\#6436](https://github.com/cosmos/ibc-go/pull/6436) Refactor ICA host keeper instantiation method to avoid panic related to proto files. + +## [v7.5.1](https://github.com/cosmos/ibc-go/releases/tag/v7.5.1) - 2024-05-22 + +### Improvements + +* (core/ante) [\#6302](https://github.com/cosmos/ibc-go/pull/6302) Performance: Skip app callbacks during RecvPacket execution in checkTx within the redundant relay ante handler. +* (core/ante) [\#6280](https://github.com/cosmos/ibc-go/pull/6280) Performance: Skip redundant proof checking in RecvPacket execution in reCheckTx within the redundant relay ante handler. +* (core/ante) [\#6306](https://github.com/cosmos/ibc-go/pull/6306) Performance: Skip misbehaviour checks in UpdateClient flow and skip signature checks in reCheckTx mode. + +## [v7.5.0](https://github.com/cosmos/ibc-go/releases/tag/v7.5.0) - 2024-05-14 + +### Dependencies + +* [\#6254](https://github.com/cosmos/ibc-go/pull/6254) Update Cosmos SDK to v0.47.11 and CometBFT to v0.37.5. + +### State Machine Breaking + +* (light-clients/07-tendermint) [\#6276](https://github.com/cosmos/ibc-go/pull/6276) Fix: No-op to avoid panicking on `UpdateState` for invalid misbehaviour submissions. + +### Improvements + +* (apps/27-interchain-accounts) [\#6147](https://github.com/cosmos/ibc-go/pull/6147) Emit an event signalling that the host submodule is disabled. +* (testing) [\#6180](https://github.com/cosmos/ibc-go/pull/6180) Add version to tm abci headers in ibctesting. +* (apps/27-interchain-accounts, apps/transfer, apps/29-fee) [\#6253](https://github.com/cosmos/ibc-go/pull/6253) Allow channel handshake to succeed if fee middleware is wired up on one side, but not the other. +* (apps/transfer) [\#6268](https://github.com/cosmos/ibc-go/pull/6268) Use memo strings instead of JSON keys in `AllowedPacketData` of transfer authorization. + +### Features + +* (apps/27-interchain-accounts) [\#5633](https://github.com/cosmos/ibc-go/pull/5633) Allow new ICA channels to use unordered ordering. +* (apps/27-interchain-accounts) [\#5785](https://github.com/cosmos/ibc-go/pull/5785) Introduce a new tx message that ICA host submodule can use to query the chain (only those marked with `module_query_safe`) and write the responses to the acknowledgement. + +### Bug Fixes + +* (apps/29-fee) [\#6255](https://github.com/cosmos/ibc-go/pull/6255) Delete already refunded fees from state if some fee(s) cannot be refunded on channel closure. + +## [v7.4.0](https://github.com/cosmos/ibc-go/releases/tag/v7.4.0) - 2024-04-05 + +## [v7.3.2](https://github.com/cosmos/ibc-go/releases/tag/v7.3.2) - 2024-01-31 + +### Dependencies + +* [\#5717](https://github.com/cosmos/ibc-go/pull/5717) Update Cosmos SDK to v0.47.8 and CometBFT to v0.37.4. + +### Improvements + +* (core) [\#5541](https://github.com/cosmos/ibc-go/pull/5541) Enable emission of events on erroneous IBC application callbacks by appending a prefix to all event type and attribute keys. + +### Bug Fixes + +* (apps/27-interchain-accounts) [\#4944](https://github.com/cosmos/ibc-go/pull/4944) Add missing proto interface registration. + +## [v7.3.1](https://github.com/cosmos/ibc-go/releases/tag/v7.3.1) - 2023-10-20 + +### Dependencies + +* [\#4539](https://github.com/cosmos/ibc-go/pull/4539) Update Cosmos SDK to v0.47.5. + +### Improvements + +* (apps/27-interchain-accounts) [\#4537](https://github.com/cosmos/ibc-go/pull/4537) Add argument to `generate-packet-data` cli to choose the encoding format for the messages in the ICA packet data. + +### Bug Fixes + +* (apps/transfer) [\#4709](https://github.com/cosmos/ibc-go/pull/4709) Order query service RPCs to fix availability of denom traces endpoint when no args are provided. + +## [v7.3.0](https://github.com/cosmos/ibc-go/releases/tag/v7.3.0) - 2023-08-31 + +### Dependencies + +* [\#4122](https://github.com/cosmos/ibc-go/pull/4122) Update Cosmos SDK to v0.47.4. + +### Improvements + +* [\#4187](https://github.com/cosmos/ibc-go/pull/4187) Adds function `WithICS4Wrapper` to keepers to allow to set the middleware after the keeper's creation. +* (light-clients/06-solomachine) [\#4429](https://github.com/cosmos/ibc-go/pull/4429) Remove IBC key from path of bytes signed by solomachine and not escape the path. + +### Features + +* (apps/27-interchain-accounts) [\#3796](https://github.com/cosmos/ibc-go/pull/3796) Adds support for json tx encoding for interchain accounts. +* [\#4188](https://github.com/cosmos/ibc-go/pull/4188) Adds optional `PacketDataUnmarshaler` interface that allows a middleware to request the packet data to be unmarshaled by the base application. +* [\#4199](https://github.com/cosmos/ibc-go/pull/4199) Adds optional `PacketDataProvider` interface for retrieving custom packet data stored on behalf of another application. +* [\#4200](https://github.com/cosmos/ibc-go/pull/4200) Adds optional `PacketData` interface which application's packet data may implement. + +### Bug Fixes + +* (04-channel) [\#4476](https://github.com/cosmos/ibc-go/pull/4476) Use UTC time in log messages for packet timeout error. +* (testing) [\#4483](https://github.com/cosmos/ibc-go/pull/4483) Use the correct revision height when querying trusted validator set. + +## [v7.2.3](https://github.com/cosmos/ibc-go/releases/tag/v7.2.3) - 2024-01-31 + +### Dependencies + +* [\#5716](https://github.com/cosmos/ibc-go/pull/5716) Update Cosmos SDK to v0.47.8 and CometBFT to v0.37.4. + +### Improvements + +* (core) [\#5541](https://github.com/cosmos/ibc-go/pull/5541) Enable emission of events on erroneous IBC application callbacks by appending a prefix to all event type and attribute keys. + +## [v7.2.2](https://github.com/cosmos/ibc-go/releases/tag/v7.2.2) - 2023-10-20 + +### Dependencies + +* [\#4539](https://github.com/cosmos/ibc-go/pull/4539) Update Cosmos SDK to v0.47.5. + +### Bug Fixes + +* (apps/transfer) [\#4709](https://github.com/cosmos/ibc-go/pull/4709) Order query service RPCs to fix availability of denom traces endpoint when no args are provided. + +## [v7.2.1](https://github.com/cosmos/ibc-go/releases/tag/v7.2.1) - 2023-08-31 + +### Bug Fixes + +* (04-channel) [\#4476](https://github.com/cosmos/ibc-go/pull/4476) Use UTC time in log messages for packet timeout error. +* (testing) [\#4483](https://github.com/cosmos/ibc-go/pull/4483) Use the correct revision height when querying trusted validator set. + +## [v7.2.0](https://github.com/cosmos/ibc-go/releases/tag/v7.2.0) - 2023-06-22 + +### Dependencies + +* [\#3810](https://github.com/cosmos/ibc-go/pull/3810) Update Cosmos SDK to v0.47.3. +* [\#3862](https://github.com/cosmos/ibc-go/pull/3862) Update CometBFT to v0.37.2. + +### State Machine Breaking + +* [\#3907](https://github.com/cosmos/ibc-go/pull/3907) Re-implemented missing functions of `LegacyMsg` interface to fix transaction signing with ledger. + +## [v7.1.0](https://github.com/cosmos/ibc-go/releases/tag/v7.1.0) - 2023-06-09 + +### Dependencies + +* [\#3542](https://github.com/cosmos/ibc-go/pull/3542) Update Cosmos SDK to v0.47.2 and CometBFT to v0.37.1. +* [\#3457](https://github.com/cosmos/ibc-go/pull/3457) Update to ics23 v0.10.0. + +### Improvements + +* (apps/transfer) [\#3454](https://github.com/cosmos/ibc-go/pull/3454) Support transfer authorization unlimited spending when the max `uint256` value is provided as limit. + +### Features + +* (light-clients/09-localhost) [\#3229](https://github.com/cosmos/ibc-go/pull/3229) Implementation of v2 of localhost loopback client. +* (apps/transfer) [\#3019](https://github.com/cosmos/ibc-go/pull/3019) Add state entry to keep track of total amount of tokens in escrow. + +### Bug Fixes + +* (core/04-channel) [\#3346](https://github.com/cosmos/ibc-go/pull/3346) Properly handle ordered channels in `UnreceivedPackets` query. +* (core/04-channel) [\#3593](https://github.com/cosmos/ibc-go/pull/3593) `SendPacket` now correctly returns `ErrClientNotFound` in favour of `ErrConsensusStateNotFound`. + +## [v7.0.1](https://github.com/cosmos/ibc-go/releases/tag/v7.0.1) - 2023-05-25 + +### Bug Fixes + +* [\#3346](https://github.com/cosmos/ibc-go/pull/3346) Properly handle ordered channels in `UnreceivedPackets` query. + +## [v7.0.0](https://github.com/cosmos/ibc-go/releases/tag/v7.0.0) - 2023-03-17 + +### Dependencies + +* [\#2672](https://github.com/cosmos/ibc-go/issues/2672) Update to cosmos-sdk v0.47. +* [\#3175](https://github.com/cosmos/ibc-go/issues/3175) Migrate to cometbft v0.37. + +### API Breaking + +* (core) [\#2897](https://github.com/cosmos/ibc-go/pull/2897) Remove legacy migrations required for upgrading from Stargate release line to ibc-go >= v1.x.x. +* (core/02-client) [\#2856](https://github.com/cosmos/ibc-go/pull/2856) Rename `IterateClients` to `IterateClientStates`. The function now takes a prefix argument which may be used for prefix iteration over the client store. +* (light-clients/tendermint)[\#1768](https://github.com/cosmos/ibc-go/pull/1768) Removed `AllowUpdateAfterExpiry`, `AllowUpdateAfterMisbehaviour` booleans as they are deprecated (see ADR026) +* (06-solomachine) [\#1679](https://github.com/cosmos/ibc-go/pull/1679) Remove `types` sub-package from `06-solomachine` lightclient directory. +* (07-tendermint) [\#1677](https://github.com/cosmos/ibc-go/pull/1677) Remove `types` sub-package from `07-tendermint` lightclient directory. +* (06-solomachine) [\#1687](https://github.com/cosmos/ibc-go/pull/1687) Bump `06-solomachine` protobuf version from `v2` to `v3`. +* (06-solomachine) [\#1687](https://github.com/cosmos/ibc-go/pull/1687) Removed `DataType` enum and associated message types from `06-solomachine`. `DataType` has been removed from `SignBytes` and `SignatureAndData` in favour of `path`. +* (02-client) [\#598](https://github.com/cosmos/ibc-go/pull/598) The client state and consensus state return value has been removed from `VerifyUpgradeAndUpdateState`. Light client implementations must update the client state and consensus state after verifying a valid client upgrade. +* (06-solomachine) [\#1100](https://github.com/cosmos/ibc-go/pull/1100) Remove `GetClientID` function from 06-solomachine `Misbehaviour` type. +* (06-solomachine) [\#1100](https://github.com/cosmos/ibc-go/pull/1100) Deprecate `ClientId` field in 06-solomachine `Misbehaviour` type. +* (07-tendermint) [\#1097](https://github.com/cosmos/ibc-go/pull/1097) Remove `GetClientID` function from 07-tendermint `Misbehaviour` type. +* (07-tendermint) [\#1097](https://github.com/cosmos/ibc-go/pull/1097) Deprecate `ClientId` field in 07-tendermint `Misbehaviour` type. +* (modules/core/exported) [\#1107](https://github.com/cosmos/ibc-go/pull/1107) Merging the `Header` and `Misbehaviour` interfaces into a single `ClientMessage` type. +* (06-solomachine)[\#1906](https://github.com/cosmos/ibc-go/pull/1906/files) Removed `AllowUpdateAfterProposal` boolean as it has been deprecated (see 01_concepts of the solo machine spec for more details). +* (07-tendermint) [\#1896](https://github.com/cosmos/ibc-go/pull/1896) Remove error return from `IterateConsensusStateAscending` in `07-tendermint`. +* (apps/27-interchain-accounts) [\#2638](https://github.com/cosmos/ibc-go/pull/2638) Interchain accounts host and controller Keepers now expects a keeper which fulfills the expected `exported.ScopedKeeper` interface for the capability keeper. +* (06-solomachine) [\#2761](https://github.com/cosmos/ibc-go/pull/2761) Removed deprecated `ClientId` field from `Misbehaviour` and `allow_update_after_proposal` field from `ClientState`. +* (apps) [\#3154](https://github.com/cosmos/ibc-go/pull/3154) Remove unused `ProposalContents` function. +* (apps) [\#3149](https://github.com/cosmos/ibc-go/pull/3149) Remove legacy interface function `RandomizedParams`, which is no longer used. +* (light-clients/06-solomachine) [\#2941](https://github.com/cosmos/ibc-go/pull/2941) Remove solomachine header sequence. +* (core) [\#2982](https://github.com/cosmos/ibc-go/pull/2982) Moved the ibc module name into the exported package. + +### State Machine Breaking + +* (06-solomachine) [\#2744](https://github.com/cosmos/ibc-go/pull/2744) `Misbehaviour.ValidateBasic()` now only enforces that signature data does not match when the signature paths are different. +* (06-solomachine) [\#2748](https://github.com/cosmos/ibc-go/pull/2748) Adding sentinel value for header path in 06-solomachine. +* (apps/29-fee) [\#2942](https://github.com/cosmos/ibc-go/pull/2942) Check `x/bank` send enabled before escrowing fees. +* (core/04-channel) [\#3009](https://github.com/cosmos/ibc-go/pull/3009) Change check to disallow optimistic sends. + +### Improvements + +* (core) [\#3082](https://github.com/cosmos/ibc-go/pull/3082) Add `HasConnection` and `HasChannel` methods. +* (tests) [\#2926](https://github.com/cosmos/ibc-go/pull/2926) Lint tests +* (apps/transfer) [\#2643](https://github.com/cosmos/ibc-go/pull/2643) Add amount, denom, and memo to transfer event emission. +* (core) [\#2746](https://github.com/cosmos/ibc-go/pull/2746) Allow proof height to be zero for all core IBC `sdk.Msg` types that contain proofs. +* (light-clients/06-solomachine) [\#2746](https://github.com/cosmos/ibc-go/pull/2746) Discard proofHeight for solo machines and use the solo machine sequence instead. +* (modules/light-clients/07-tendermint) [\#1713](https://github.com/cosmos/ibc-go/pull/1713) Allow client upgrade proposals to update `TrustingPeriod`. See ADR-026 for context. +* (modules/core/02-client) [\#1188](https://github.com/cosmos/ibc-go/pull/1188/files) Routing `MsgSubmitMisbehaviour` to `UpdateClient` keeper function. Deprecating `SubmitMisbehaviour` endpoint. +* (modules/core/02-client) [\#1208](https://github.com/cosmos/ibc-go/pull/1208) Replace `CheckHeaderAndUpdateState` usage in 02-client with calls to `VerifyClientMessage`, `CheckForMisbehaviour`, `UpdateStateOnMisbehaviour` and `UpdateState`. +* (modules/light-clients/09-localhost) [\#1187](https://github.com/cosmos/ibc-go/pull/1187/) Removing localhost light client implementation as it is not functional. An upgrade handler is provided in `modules/migrations/v5` to prune `09-localhost` clients and consensus states from the store. +* (modules/core/02-client) [\#1186](https://github.com/cosmos/ibc-go/pull/1186) Removing `GetRoot` function from ConsensusState interface in `02-client`. `GetRoot` is unused by core IBC. +* (modules/core/02-client) [\#1196](https://github.com/cosmos/ibc-go/pull/1196) Adding VerifyClientMessage to ClientState interface. +* (modules/core/02-client) [\#1198](https://github.com/cosmos/ibc-go/pull/1198) Adding UpdateStateOnMisbehaviour to ClientState interface. +* (modules/core/02-client) [\#1170](https://github.com/cosmos/ibc-go/pull/1170) Updating `ClientUpdateProposal` to set client state in lightclient implementations `CheckSubstituteAndUpdateState` methods. +* (modules/core/02-client) [\#1197](https://github.com/cosmos/ibc-go/pull/1197) Adding `CheckForMisbehaviour` to `ClientState` interface. +* (modules/core/02-client) [\#1210](https://github.com/cosmos/ibc-go/pull/1210) Removing `CheckHeaderAndUpdateState` from `ClientState` interface & associated light client implementations. +* (modules/core/02-client) [\#1212](https://github.com/cosmos/ibc-go/pull/1212) Removing `CheckMisbehaviourAndUpdateState` from `ClientState` interface & associated light client implementations. +* (modules/core/exported) [\#1206](https://github.com/cosmos/ibc-go/pull/1206) Adding new method `UpdateState` to `ClientState` interface. +* (modules/core/02-client) [\#1741](https://github.com/cosmos/ibc-go/pull/1741) Emitting a new `upgrade_chain` event upon setting upgrade consensus state. +* (client) [\#724](https://github.com/cosmos/ibc-go/pull/724) `IsRevisionFormat` and `IsClientIDFormat` have been updated to disallow newlines before the dash used to separate the chainID and revision number, and the client type and client sequence. +* (02-client/cli) [\#897](https://github.com/cosmos/ibc-go/pull/897) Remove `GetClientID()` from `Misbehaviour` interface. Submit client misbehaviour cli command requires an explicit client id now. +* (06-solomachine) [\#1972](https://github.com/cosmos/ibc-go/pull/1972) Solo machine implementation of `ZeroCustomFields` fn now panics as the fn is only used for upgrades which solo machine does not support. +* (light-clients/06-solomachine) Moving `verifyMisbehaviour` function from update.go to misbehaviour_handle.go. +* [\#2434](https://github.com/cosmos/ibc-go/pull/2478) Removed all `TypeMsg` constants +* (modules/core/exported) [\#2539](https://github.com/cosmos/ibc-go/pull/2539) Removing `GetVersions` from `ConnectionI` interface. +* (core/02-connection) [\#2419](https://github.com/cosmos/ibc-go/pull/2419) Add optional proof data to proto definitions of `MsgConnectionOpenTry` and `MsgConnectionOpenAck` for host state machines that are unable to introspect their own consensus state. +* (light-clients/07-tendermint) [\#3046](https://github.com/cosmos/ibc-go/pull/3046) Moved non-verification misbehaviour checks to `CheckForMisbehaviour`. +* (apps/29-fee) [\#2975](https://github.com/cosmos/ibc-go/pull/2975) Adding distribute fee events to ics29. +* (light-clients/07-tendermint) [\#2965](https://github.com/cosmos/ibc-go/pull/2965) Prune expired `07-tendermint` consensus states on duplicate header updates. +* (light-clients) [\#2736](https://github.com/cosmos/ibc-go/pull/2736) Updating `VerifyMembership` and `VerifyNonMembership` methods to use `Path` interface. +* (light-clients) [\#3113](https://github.com/cosmos/ibc-go/pull/3113) Align light client module names. + +### Features + +* (apps/transfer) [\#3079](https://github.com/cosmos/ibc-go/pull/3079) Added authz support for ics20. +* (core/02-client) [\#2824](https://github.com/cosmos/ibc-go/pull/2824) Add genesis migrations for v6 to v7. The migration migrates the solo machine client state definition, removes all solo machine consensus states and removes the localhost client. +* (core/24-host) [\#2856](https://github.com/cosmos/ibc-go/pull/2856) Add `PrefixedClientStorePath` and `PrefixedClientStoreKey` functions to 24-host +* (core/02-client) [\#2819](https://github.com/cosmos/ibc-go/pull/2819) Add automatic in-place store migrations to remove the localhost client and migrate existing solo machine definitions. +* (light-clients/06-solomachine) [\#2826](https://github.com/cosmos/ibc-go/pull/2826) Add `AppModuleBasic` for the 06-solomachine client and remove solo machine type registration from core IBC. Chains must register the `AppModuleBasic` of light clients. +* (light-clients/07-tendermint) [\#2825](https://github.com/cosmos/ibc-go/pull/2825) Add `AppModuleBasic` for the 07-tendermint client and remove tendermint type registration from core IBC. Chains must register the `AppModuleBasic` of light clients. +* (light-clients/07-tendermint) [\#2800](https://github.com/cosmos/ibc-go/pull/2800) Add optional in-place store migration function to prune all expired tendermint consensus states. +* (core/24-host) [\#2820](https://github.com/cosmos/ibc-go/pull/2820) Add `MustParseClientStatePath` which parses the clientID from a client state key path. +* (testing/simapp) [\#2842](https://github.com/cosmos/ibc-go/pull/2842) Adding the new upgrade handler for v6 -> v7 to simapp which prunes expired Tendermint consensus states. +* (testing) [\#2829](https://github.com/cosmos/ibc-go/pull/2829) Add `AssertEvents` which asserts events against expected event map. + +### Bug Fixes + +* (testing) [\#3295](https://github.com/cosmos/ibc-go/pull/3295) The function `SetupWithGenesisValSet` will set the baseapp chainID before running `InitChain` +* (light-clients/solomachine) [\#1839](https://github.com/cosmos/ibc-go/pull/1839) Fixed usage of the new diversifier in validation of changing diversifiers for the solo machine. The current diversifier must sign over the new diversifier. +* (light-clients/07-tendermint) [\#1674](https://github.com/cosmos/ibc-go/pull/1674) Submitted ClientState is zeroed out before checking the proof in order to prevent the proposal from containing information governance is not actually voting on. +* (modules/core/02-client)[\#1676](https://github.com/cosmos/ibc-go/pull/1676) ClientState must be zeroed out for `UpgradeProposals` to pass validation. This prevents a proposal containing information governance is not actually voting on. +* (core/02-client) [\#2510](https://github.com/cosmos/ibc-go/pull/2510) Fix client ID validation regex to conform closer to spec. +* (apps/transfer) [\#3045](https://github.com/cosmos/ibc-go/pull/3045) Allow value with slashes in URL template. +* (apps/27-interchain-accounts) [\#2601](https://github.com/cosmos/ibc-go/pull/2601) Remove bech32 check from owner address on ICA controller msgs RegisterInterchainAccount and SendTx. +* (apps/transfer) [\#2651](https://github.com/cosmos/ibc-go/pull/2651) Skip emission of unpopulated memo field in ics20. +* (apps/27-interchain-accounts) [\#2682](https://github.com/cosmos/ibc-go/pull/2682) Avoid race conditions in ics27 handshakes. +* (light-clients/06-solomachine) [\#2741](https://github.com/cosmos/ibc-go/pull/2741) Added check for empty path in 06-solomachine. +* (light-clients/07-tendermint) [\#3022](https://github.com/cosmos/ibc-go/pull/3022) Correctly close iterator in `07-tendermint` store. +* (core/02-client) [\#3010](https://github.com/cosmos/ibc-go/pull/3010) Update `Paginate` to use `FilterPaginate` in `ClientStates` and `ConnectionChannels` grpc queries. + +## [v6.3.0](https://github.com/cosmos/ibc-go/releases/tag/v6.3.0) - 2024-04-05 + +## [v6.2.1](https://github.com/cosmos/ibc-go/releases/tag/v6.2.1) - 2023-10-20 + +### Bug Fixes + +* (apps/transfer) [\#3045](https://github.com/cosmos/ibc-go/pull/3045) allow value with slashes in URL template for `denom_traces` and `denom_hashes` queries. +* (apps/transfer) [\#4709](https://github.com/cosmos/ibc-go/pull/4709) Order query service RPCs to fix availability of denom traces endpoint when no args are provided. + +## [v6.2.0](https://github.com/cosmos/ibc-go/releases/tag/v6.2.0) - 2023-05-31 + +### Dependencies + +* [\#3393](https://github.com/cosmos/ibc-go/pull/3393) Bump Cosmos SDK to v0.46.12 and replace Tendermint with CometBFT v0.34.37. + +### Improvements + +* (core) [\#3082](https://github.com/cosmos/ibc-go/pull/3082) Add `HasConnection` and `HasChannel` methods. +* (apps/transfer) [\#3454](https://github.com/cosmos/ibc-go/pull/3454) Support transfer authorization unlimited spending when the max `uint256` value is provided as limit. + +### Features + +* [\#3079](https://github.com/cosmos/ibc-go/pull/3079) Add authz support for ics20. + +### Bug Fixes + +* [\#3346](https://github.com/cosmos/ibc-go/pull/3346) Properly handle ordered channels in `UnreceivedPackets` query. + +## [v6.1.2](https://github.com/cosmos/ibc-go/releases/tag/v6.1.2) - 2023-10-20 + +### Bug Fixes + +* (apps/transfer) [\#3045](https://github.com/cosmos/ibc-go/pull/3045) allow value with slashes in URL template for `denom_traces` and `denom_hashes` queries. +* (apps/transfer) [\#4709](https://github.com/cosmos/ibc-go/pull/4709) Order query service RPCs to fix availability of denom traces endpoint when no args are provided. + +## [v6.1.1](https://github.com/cosmos/ibc-go/releases/tag/v6.1.1) - 2023-05-25 + +### Bug Fixes + +* [\#3346](https://github.com/cosmos/ibc-go/pull/3346) Properly handle ordered channels in `UnreceivedPackets` query. + +## [v6.1.0](https://github.com/cosmos/ibc-go/releases/tag/v6.1.0) - 2022-12-20 + +### Dependencies + +* [\#2945](https://github.com/cosmos/ibc-go/pull/2945) Bump Cosmos SDK to v0.46.7 and Tendermint to v0.34.24. + +### State Machine Breaking + +* (apps/29-fee) [\#2942](https://github.com/cosmos/ibc-go/pull/2942) Check `x/bank` send enabled before escrowing fees. + +## [v6.0.0](https://github.com/cosmos/ibc-go/releases/tag/v6.0.0) - 2022-12-09 + +### Dependencies + +* [\#2868](https://github.com/cosmos/ibc-go/pull/2868) Bump ICS 23 to v0.9.0. +* [\#2458](https://github.com/cosmos/ibc-go/pull/2458) Bump Cosmos SDK to v0.46.2 +* [\#2784](https://github.com/cosmos/ibc-go/pull/2784) Bump Cosmos SDK to v0.46.6 and Tendermint to v0.34.23. + +### API Breaking + +* (apps/27-interchain-accounts) [\#2607](https://github.com/cosmos/ibc-go/pull/2607) `SerializeCosmosTx` now takes in a `[]proto.Message` instead of `[]sdk.Msg`. +* (apps/transfer) [\#2446](https://github.com/cosmos/ibc-go/pull/2446) Remove `SendTransfer` function in favor of a private `sendTransfer` function. All IBC transfers must be initiated with `MsgTransfer`. +* (apps/29-fee) [\#2395](https://github.com/cosmos/ibc-go/pull/2395) Remove param space from ics29 NewKeeper function. The field was unused. +* (apps/27-interchain-accounts) [\#2133](https://github.com/cosmos/ibc-go/pull/2133) Generates genesis protos in a separate directory to avoid circular import errors. The protobuf package name has changed for the genesis types. +* (apps/27-interchain-accounts) [\#2638](https://github.com/cosmos/ibc-go/pull/2638) Interchain accounts host and controller Keepers now expects a keeper which fulfills the expected `exported.ScopedKeeper` interface for the capability keeper. +* (transfer) [\#2638](https://github.com/cosmos/ibc-go/pull/2638) Transfer Keeper now expects a keeper which fulfills the expected `exported.ScopedKeeper` interface for the capability keeper. +* (05-port) [\#2638](https://github.com/cosmos/ibc-go/pull/2638) Port Keeper now expects a keeper which fulfills the expected `exported.ScopedKeeper` interface for the capability keeper. +* (04-channel) [\#2638](https://github.com/cosmos/ibc-go/pull/2638) Channel Keeper now expects a keeper which fulfills the expected `exported.ScopedKeeper` interface for the capability keeper. +* (core/04-channel)[\#1703](https://github.com/cosmos/ibc-go/pull/1703) Update `SendPacket` API to take in necessary arguments and construct rest of packet rather than taking in entire packet. The generated packet sequence is returned by the `SendPacket` function. +* (modules/apps/27-interchain-accounts) [\#2433](https://github.com/cosmos/ibc-go/pull/2450) Renamed icatypes.PortPrefix to icatypes.ControllerPortPrefix & icatypes.PortID to icatypes.HostPortID +* (testing) [\#2567](https://github.com/cosmos/ibc-go/pull/2567) Modify `SendPacket` API of `Endpoint` to match the API of `SendPacket` in 04-channel. + +### State Machine Breaking + +* (apps/transfer) [\#2651](https://github.com/cosmos/ibc-go/pull/2651) Introduce `mustProtoMarshalJSON` for ics20 packet data marshalling which will skip emission (marshalling) of the memo field if unpopulated (empty). +* (27-interchain-accounts) [\#2590](https://github.com/cosmos/ibc-go/pull/2590) Removing port prefix requirement from the ICA host channel handshake +* (transfer) [\#2377](https://github.com/cosmos/ibc-go/pull/2377) Adding `sequence` to `MsgTransferResponse`. +* (light-clients/07-tendermint) [\#2555](https://github.com/cosmos/ibc-go/pull/2555) Forbid negative values for `TrustingPeriod`, `UnbondingPeriod` and `MaxClockDrift` (as specified in ICS-07). +* (core/04-channel) [\#2973](https://github.com/cosmos/ibc-go/pull/2973) Write channel state before invoking app callbacks in ack and confirm channel handshake steps. + +### Improvements + +* (apps/27-interchain-accounts) [\#2134](https://github.com/cosmos/ibc-go/pull/2134) Adding upgrade handler to ICS27 `controller` submodule for migration of channel capabilities. This upgrade handler migrates ownership of channel capabilities from the underlying application to the ICS27 `controller` submodule. +* (apps/27-interchain-accounts) [\#2102](https://github.com/cosmos/ibc-go/pull/2102) ICS27 controller middleware now supports a nil underlying application. This allows chains to make use of interchain accounts with existing auth mechanisms such as x/group and x/gov. +* (apps/27-interchain-accounts) [\#2157](https://github.com/cosmos/ibc-go/pull/2157) Adding `IsMiddlewareEnabled` functionality to enforce calls to ICS27 msg server to *not* route to the underlying application. +* (apps/27-interchain-accounts) [\#2146](https://github.com/cosmos/ibc-go/pull/2146) ICS27 controller now claims the channel capability passed via ibc core, and passes `nil` to the underlying app callback. The channel capability arg in `SendTx` is now ignored and looked up internally. +* (apps/27-interchain-accounts) [\#2177](https://github.com/cosmos/ibc-go/pull/2177) Adding `IsMiddlewareEnabled` flag to interchain accounts `ActiveChannel` genesis type. +* (apps/27-interchain-accounts) [\#2140](https://github.com/cosmos/ibc-go/pull/2140) Adding migration handler to ICS27 `controller` submodule to assert ownership of channel capabilities and set middleware enabled flag for existing channels. The ICS27 module consensus version has been bumped from 1 to 2. +* (core/04-channel) [\#2304](https://github.com/cosmos/ibc-go/pull/2304) Adding `GetAllChannelsWithPortPrefix` function which filters channels based on a provided port prefix. +* (apps/27-interchain-accounts) [\#2248](https://github.com/cosmos/ibc-go/pull/2248) Adding call to underlying app in `OnChanCloseConfirm` callback of the controller submodule and adding relevant unit tests. +* (apps/27-interchain-accounts) [\#2251](https://github.com/cosmos/ibc-go/pull/2251) Adding `msgServer` struct to controller submodule that embeds the `Keeper` struct. +* (apps/27-interchain-accounts) [\#2290](https://github.com/cosmos/ibc-go/pull/2290) Changed `DefaultParams` function in `host` submodule to allow all messages by default. Defined a constant named `AllowAllHostMsgs` for `host` module to keep wildcard "*" string which allows all messages. +* (apps/27-interchain-accounts) [\#2297](https://github.com/cosmos/ibc-go/pull/2297) Adding cli command to generate ICS27 packet data. +* (modules/core/keeper) [\#1728](https://github.com/cosmos/ibc-go/pull/2399) Updated channel callback errors to include portID & channelID for better identification of errors. +* (testing) [\#2657](https://github.com/cosmos/ibc-go/pull/2657) Carry `ProposerAddress` through committed blocks. Allow `DefaultGenTxGas` to be modified. +* (core/03-connection) [\#2745](https://github.com/cosmos/ibc-go/pull/2745) Adding `ConnectionParams` grpc query and CLI to 03-connection. +* (apps/29-fee) [\#2786](https://github.com/cosmos/ibc-go/pull/2786) Save gas by checking key existence with `KVStore`'s `Has` method. + +### Features + +* (apps/27-interchain-accounts) [\#2147](https://github.com/cosmos/ibc-go/pull/2147) Adding a `SubmitTx` gRPC endpoint for the ICS27 Controller module which allows owners of interchain accounts to submit transactions. This replaces the previously existing need for authentication modules to implement this standard functionality. +* (testing/simapp) [\#2190](https://github.com/cosmos/ibc-go/pull/2190) Adding the new `x/group` cosmos-sdk module to simapp. +* (apps/transfer) [\#2595](https://github.com/cosmos/ibc-go/pull/2595) Adding optional memo field to `FungibleTokenPacketData` and `MsgTransfer`. + +### Bug Fixes + +* (modules/core/keeper) [\#2403](https://github.com/cosmos/ibc-go/pull/2403) Added a function in keeper to cater for blank pointers. +* (apps/transfer) [\#2679](https://github.com/cosmos/ibc-go/pull/2679) Check `x/bank` send enabled. +* (modules/core/keeper) [\#2745](https://github.com/cosmos/ibc-go/pull/2745) Fix request wiring for `UpgradedConsensusState` in core query server. + +## [v5.4.0](https://github.com/cosmos/ibc-go/releases/tag/v5.4.0) - 2024-04-05 + +## [v5.3.2](https://github.com/cosmos/ibc-go/releases/tag/v5.3.2) - 2023-10-20 + +### Bug Fixes + +* (apps/transfer) [\#3045](https://github.com/cosmos/ibc-go/pull/3045) allow value with slashes in URL template for `denom_traces` and `denom_hashes` queries. +* (apps/transfer) [\#4709](https://github.com/cosmos/ibc-go/pull/4709) Order query service RPCs to fix availability of denom traces endpoint when no args are provided. + +## [v5.3.1](https://github.com/cosmos/ibc-go/releases/tag/v5.3.1) - 2023-05-25 + +### Bug Fixes + +* [\#3346](https://github.com/cosmos/ibc-go/pull/3346) Properly handle ordered channels in `UnreceivedPackets` query. + +## [v5.3.0](https://github.com/cosmos/ibc-go/releases/tag/v5.3.0) - 2023-05-04 + +### Dependencies + +* [\#3354](https://github.com/cosmos/ibc-go/pull/3354) Bump Cosmos SDK to v0.46.12 and replace Tendermint with CometBFT v0.34.27. + +## [v5.2.1](https://github.com/cosmos/ibc-go/releases/tag/v5.2.1) - 2023-05-25 + +### Bug Fixes + +* [\#3346](https://github.com/cosmos/ibc-go/pull/3346) Properly handle ordered channels in `UnreceivedPackets` query. + +## [v5.2.0](https://github.com/cosmos/ibc-go/releases/tag/v5.2.0) - 2022-12-20 + +### Dependencies + +* [\#2868](https://github.com/cosmos/ibc-go/pull/2868) Bump ICS 23 to v0.9.0. +* [\#2944](https://github.com/cosmos/ibc-go/pull/2944) Bump Cosmos SDK to v0.46.7 and Tendermint to v0.34.24. + +### State Machine Breaking + +* (apps/29-fee) [\#2942](https://github.com/cosmos/ibc-go/pull/2942) Check `x/bank` send enabled before escrowing fees. + +### Improvements + +* (apps/29-fee) [\#2786](https://github.com/cosmos/ibc-go/pull/2786) Save gas by checking key existence with `KVStore`'s `Has` method. + +## [v5.1.0](https://github.com/cosmos/ibc-go/releases/tag/v5.1.0) - 2022-11-09 + +### Dependencies + +* [\#2647](https://github.com/cosmos/ibc-go/pull/2647) Bump Cosmos SDK to v0.46.4 and Tendermint to v0.34.22. + +### State Machine Breaking + +* (apps/transfer) [\#2651](https://github.com/cosmos/ibc-go/pull/2651) Introduce `mustProtoMarshalJSON` for ics20 packet data marshalling which will skip emission (marshalling) of the memo field if unpopulated (empty). +* (27-interchain-accounts) [\#2590](https://github.com/cosmos/ibc-go/pull/2590) Removing port prefix requirement from the ICA host channel handshake +* (transfer) [\#2377](https://github.com/cosmos/ibc-go/pull/2377) Adding `sequence` to `MsgTransferResponse`. + +### Improvements + +* (testing) [\#2657](https://github.com/cosmos/ibc-go/pull/2657) Carry `ProposerAddress` through committed blocks. Allow `DefaultGenTxGas` to be modified. + +### Features + +* (apps/transfer) [\#2595](https://github.com/cosmos/ibc-go/pull/2595) Adding optional memo field to `FungibleTokenPacketData` and `MsgTransfer`. + +### Bug Fixes + +* (apps/transfer) [\#2679](https://github.com/cosmos/ibc-go/pull/2679) Check `x/bank` send enabled. + +## [v5.0.1](https://github.com/cosmos/ibc-go/releases/tag/v5.0.1) - 2022-10-27 + +### Dependencies + +* [\#2623](https://github.com/cosmos/ibc-go/pull/2623) Bump SDK version to v0.46.3 and Tendermint version to v0.34.22. + +## [v5.0.0](https://github.com/cosmos/ibc-go/releases/tag/v5.0.0) - 2022-09-28 + +### Dependencies + +* [\#1653](https://github.com/cosmos/ibc-go/pull/1653) Bump SDK version to v0.46 +* [\#2124](https://github.com/cosmos/ibc-go/pull/2124) Bump SDK version to v0.46.1 + +### API Breaking + +* (testing)[\#2028](https://github.com/cosmos/ibc-go/pull/2028) New interface `ibctestingtypes.StakingKeeper` added and set for the testing app `StakingKeeper` setup. +* (core/04-channel) [\#1418](https://github.com/cosmos/ibc-go/pull/1418) `NewPacketId` has been renamed to `NewPacketID` to comply with go linting rules. +* (core/ante) [\#1418](https://github.com/cosmos/ibc-go/pull/1418) `AnteDecorator` has been renamed to `RedundancyDecorator` to comply with go linting rules and to give more clarity to the purpose of the Decorator. +* (core/ante) [\#1820](https://github.com/cosmos/ibc-go/pull/1418) `RedundancyDecorator` has been renamed to `RedundantRelayDecorator` to make the name for explicit. +* (testing) [\#1418](https://github.com/cosmos/ibc-go/pull/1418) `MockIBCApp` has been renamed to `IBCApp` and `MockEmptyAcknowledgement` has been renamed to `EmptyAcknowledgement` to comply with go linting rules +* (apps/27-interchain-accounts) [\#2058](https://github.com/cosmos/ibc-go/pull/2058) Added `MessageRouter` interface and replaced `*baseapp.MsgServiceRouter` with it. The controller and host keepers of apps/27-interchain-accounts have been updated to use it. +* (apps/27-interchain-accounts)[\#2302](https://github.com/cosmos/ibc-go/pull/2302) Handle unwrapping of channel version in interchain accounts channel reopening handshake flow. The `host` submodule `Keeper` now requires an `ICS4Wrapper` similarly to the `controller` submodule. + +### Improvements + +* (27-interchain-accounts) [\#1352](https://github.com/cosmos/ibc-go/pull/1352) Add support for Cosmos-SDK simulation to ics27 module. +* (linting) [\#1418](https://github.com/cosmos/ibc-go/pull/1418) Fix linting errors, resulting compatibility with go1.18 linting style, golangci-lint 1.46.2 and the revivie linter. This caused breaking changes in core/04-channel, core/ante, and the testing library. + +### Features + +* (apps/27-interchain-accounts) [\#2193](https://github.com/cosmos/ibc-go/pull/2193) Adding `InterchainAccount` gRPC query endpoint to ICS27 `controller` submodule to allow users to retrieve registered interchain account addresses. + +### Bug Fixes + +* (27-interchain-accounts) [\#2308](https://github.com/cosmos/ibc-go/pull/2308) Nil checks have been added to ensure services are not registered for nil host or controller keepers. +* (makefile) [\#1785](https://github.com/cosmos/ibc-go/pull/1785) Fetch the correct versions of protocol buffers dependencies from tendermint, cosmos-sdk, and ics23. +* (modules/core/04-channel)[\#1919](https://github.com/cosmos/ibc-go/pull/1919) Fixed formatting of sequence for packet "acknowledgement written" logs. + +## [v4.6.0](https://github.com/cosmos/ibc-go/releases/tag/v4.6.0) - 2024-04-05 + +## [v4.5.1](https://github.com/cosmos/ibc-go/releases/tag/v4.5.1) - 2023-10-20 + +### Bug Fixes + +* (apps/transfer) [\#3045](https://github.com/cosmos/ibc-go/pull/3045) allow value with slashes in URL template for `denom_traces` and `denom_hashes` queries. +* (apps/transfer) [\#4709](https://github.com/cosmos/ibc-go/pull/4709) Order query service RPCs to fix availability of denom traces endpoint when no args are provided. + +## [v4.5.0](https://github.com/cosmos/ibc-go/releases/tag/v4.5.0) - 2023-10-03 + +### Dependencies + +* [\#4738](https://github.com/cosmos/ibc-go/pull/4738) Bump Cosmos SDK to v0.45.16. +* [\#4782](https://github.com/cosmos/ibc-go/pull/4782) Bump ics23 to v0.9.1. + +## [v4.4.3](https://github.com/cosmos/ibc-go/releases/tag/v4.4.3) - 2023-10-20 + +### Bug Fixes + +* (apps/transfer) [\#3045](https://github.com/cosmos/ibc-go/pull/3045) allow value with slashes in URL template for `denom_traces` and `denom_hashes` queries. +* (apps/transfer) [\#4709](https://github.com/cosmos/ibc-go/pull/4709) Order query service RPCs to fix availability of denom traces endpoint when no args are provided. + +## [v4.4.2](https://github.com/cosmos/ibc-go/releases/tag/v4.4.2) - 2023-05-25 + +### Bug Fixes + +* [\#3662](https://github.com/cosmos/ibc-go/pull/3662) Retract v4.1.2 and v4.2.1. + +## [v4.4.1](https://github.com/cosmos/ibc-go/releases/tag/v4.4.1) - 2023-05-25 + +### Bug Fixes + +* [\#3346](https://github.com/cosmos/ibc-go/pull/3346) Properly handle ordered channels in `UnreceivedPackets` query. + +## [v4.4.0](https://github.com/cosmos/ibc-go/releases/tag/v4.4.0) - 2023-04-25 + +### Dependencies + +* [\#3416](https://github.com/cosmos/ibc-go/pull/3416) Bump Cosmos SDK to v0.45.15 and replace Tendermint with CometBFT v0.34.27. + +## [v4.3.1](https://github.com/cosmos/ibc-go/releases/tag/v4.3.1) - 2023-05-25 + +### Bug Fixes + +* [\#3346](https://github.com/cosmos/ibc-go/pull/3346) Properly handle ordered channels in `UnreceivedPackets` query. + +## [v4.3.0](https://github.com/cosmos/ibc-go/releases/tag/v4.3.0) - 2023-01-24 + +### Dependencies + +* [\#3049](https://github.com/cosmos/ibc-go/pull/3049) Bump Cosmos SDK to v0.45.12. +* [\#2868](https://github.com/cosmos/ibc-go/pull/2868) Bump ics23 to v0.9.0. + +### State Machine Breaking + +* (core/04-channel) [\#2973](https://github.com/cosmos/ibc-go/pull/2973) Write channel state before invoking app callbacks in ack and confirm channel handshake steps. + +### Improvements + +* (apps/29-fee) [\#2786](https://github.com/cosmos/ibc-go/pull/2786) Save gas on `IsFeeEnabled`. + +### Bug Fixes + +* (apps/29-fee) [\#2942](https://github.com/cosmos/ibc-go/pull/2942) Check `x/bank` send enabled before escrowing fees. + +### Documentation + +* [\#2737](https://github.com/cosmos/ibc-go/pull/2737) Fix migration/docs for ICA controller middleware. + +### Miscellaneous Tasks + +* [\#2772](https://github.com/cosmos/ibc-go/pull/2772) Integrated git cliff into the code base to automate generation of changelogs. + +## [v4.2.2](https://github.com/cosmos/ibc-go/releases/tag/v4.2.2) - 2023-05-25 + +### Bug Fixes + +* [\#3661](https://github.com/cosmos/ibc-go/pull/3661) Revert state-machine breaking improvement from PR [#2786](https://github.com/cosmos/ibc-go/pull/2786). + +## [v4.2.1](https://github.com/cosmos/ibc-go/releases/tag/v4.2.1) - 2023-05-25 + +### Dependencies + +* [\#2868](https://github.com/cosmos/ibc-go/pull/2868) Bump ICS 23 to v0.9.0. + +### Improvements + +* (apps/29-fee) [\#2786](https://github.com/cosmos/ibc-go/pull/2786) Save gas by checking key existence with `KVStore`'s `Has` method. + +### Bug Fixes + +* [\#3346](https://github.com/cosmos/ibc-go/pull/3346) Properly handle ordered channels in `UnreceivedPackets` query. + +## [v4.2.0](https://github.com/cosmos/ibc-go/releases/tag/v4.2.0) - 2022-11-07 + +### Dependencies + +* [\#2588](https://github.com/cosmos/ibc-go/pull/2588) Bump SDK version to v0.45.10 and Tendermint to v0.34.22. + +### State Machine Breaking + +* (apps/transfer) [\#2651](https://github.com/cosmos/ibc-go/pull/2651) Introduce `mustProtoMarshalJSON` for ics20 packet data marshalling which will skip emission (marshalling) of the memo field if unpopulated (empty). +* (27-interchain-accounts) [\#2590](https://github.com/cosmos/ibc-go/pull/2590) Removing port prefix requirement from the ICA host channel handshake +* (transfer) [\#2377](https://github.com/cosmos/ibc-go/pull/2377) Adding `sequence` to `MsgTransferResponse`. + +### Features + +* (apps/transfer) [\#2595](https://github.com/cosmos/ibc-go/pull/2595) Adding optional memo field to `FungibleTokenPacketData` and `MsgTransfer`. + +### Bug Fixes + +* (apps/transfer) [\#2679](https://github.com/cosmos/ibc-go/pull/2679) Check `x/bank` send enabled. + +## [v4.1.3](https://github.com/cosmos/ibc-go/releases/tag/v4.1.3) - 2023-05-25 + +### Bug Fixes + +* [\#3660](https://github.com/cosmos/ibc-go/pull/3660) Revert state-machine breaking improvement from PR [#2786](https://github.com/cosmos/ibc-go/pull/2786). + +## [v4.1.2](https://github.com/cosmos/ibc-go/releases/tag/v4.1.2) - 2023-05-25 + +### Dependencies + +* [\#2868](https://github.com/cosmos/ibc-go/pull/2868) Bump ICS 23 to v0.9.0. + +### Improvements + +* (apps/29-fee) [\#2786](https://github.com/cosmos/ibc-go/pull/2786) Save gas by checking key existence with `KVStore`'s `Has` method. + +### Bug Fixes + +* [\#3346](https://github.com/cosmos/ibc-go/pull/3346) Properly handle ordered channels in `UnreceivedPackets` query. + +## [v4.1.1](https://github.com/cosmos/ibc-go/releases/tag/v4.1.1) - 2022-10-27 + +### Dependencies + +* [\#2624](https://github.com/cosmos/ibc-go/pull/2624) Bump SDK version to v0.45.10 and Tendermint to v0.34.22. + +## [v4.1.0](https://github.com/cosmos/ibc-go/releases/tag/v4.1.0) - 2022-09-20 + +### Dependencies + +* [\#2288](https://github.com/cosmos/ibc-go/pull/2288) Bump SDK version to v0.45.8 and Tendermint to v0.34.21. + +### Features + +* (apps/27-interchain-accounts) [\#2193](https://github.com/cosmos/ibc-go/pull/2193) Adding `InterchainAccount` gRPC query endpoint to ICS27 `controller` submodule to allow users to retrieve registered interchain account addresses. + +### Bug Fixes + +* (27-interchain-accounts) [\#2308](https://github.com/cosmos/ibc-go/pull/2308) Nil checks have been added to ensure services are not registered for nil host or controller keepers. + +## [v4.0.1](https://github.com/cosmos/ibc-go/releases/tag/v4.0.1) - 2022-09-15 + +### Dependencies + +* [\#2287](https://github.com/cosmos/ibc-go/pull/2287) Bump SDK version to v0.45.8 and Tendermint to v0.34.21. + +## [v4.0.0](https://github.com/cosmos/ibc-go/releases/tag/v4.0.0) - 2022-08-12 + +### Dependencies + +* [\#1627](https://github.com/cosmos/ibc-go/pull/1627) Bump Go version to 1.18 +* [\#1905](https://github.com/cosmos/ibc-go/pull/1905) Bump SDK version to v0.45.7 + +### API Breaking + +* (core/04-channel) [\#1792](https://github.com/cosmos/ibc-go/pull/1792) Remove `PreviousChannelID` from `NewMsgChannelOpenTry` arguments. `MsgChannelOpenTry.ValidateBasic()` returns error if the deprecated `PreviousChannelID` is not empty. +* (core/03-connection) [\#1797](https://github.com/cosmos/ibc-go/pull/1797) Remove `PreviousConnectionID` from `NewMsgConnectionOpenTry` arguments. `MsgConnectionOpenTry.ValidateBasic()` returns error if the deprecated `PreviousConnectionID` is not empty. +* (modules/core/03-connection) [\#1672](https://github.com/cosmos/ibc-go/pull/1672) Remove crossing hellos from connection handshakes. The `PreviousConnectionId` in `MsgConnectionOpenTry` has been deprecated. +* (modules/core/04-channel) [\#1317](https://github.com/cosmos/ibc-go/pull/1317) Remove crossing hellos from channel handshakes. The `PreviousChannelId` in `MsgChannelOpenTry` has been deprecated. +* (transfer) [\#1250](https://github.com/cosmos/ibc-go/pull/1250) Deprecate `GetTransferAccount` since the `transfer` module account is never used. +* (channel) [\#1283](https://github.com/cosmos/ibc-go/pull/1283) The `OnChanOpenInit` application callback now returns a version string in line with the latest [spec changes](https://github.com/cosmos/ibc/pull/629). +* (modules/29-fee)[\#1338](https://github.com/cosmos/ibc-go/pull/1338) Renaming `Result` field in `IncentivizedAcknowledgement` to `AppAcknowledgement`. +* (modules/29-fee)[\#1343](https://github.com/cosmos/ibc-go/pull/1343) Renaming `KeyForwardRelayerAddress` to `KeyRelayerAddressForAsyncAck`, and `ParseKeyForwardRelayerAddress` to `ParseKeyRelayerAddressForAsyncAck`. +* (apps/27-interchain-accounts)[\#1432](https://github.com/cosmos/ibc-go/pull/1432) Updating `RegisterInterchainAccount` to include an additional `version` argument, supporting ICS29 fee middleware functionality in ICS27 interchain accounts. +* (apps/27-interchain-accounts)[\#1565](https://github.com/cosmos/ibc-go/pull/1565) Removing `NewErrorAcknowledgement` in favour of `channeltypes.NewErrorAcknowledgement`. +* (transfer)[\#1565](https://github.com/cosmos/ibc-go/pull/1565) Removing `NewErrorAcknowledgement` in favour of `channeltypes.NewErrorAcknowledgement`. +* (channel)[\#1565](https://github.com/cosmos/ibc-go/pull/1565) Updating `NewErrorAcknowledgement` to accept an error instead of a string and removing the possibility of non-deterministic writes to application state. +* (core/04-channel)[\#1636](https://github.com/cosmos/ibc-go/pull/1636) Removing `SplitChannelVersion` and `MergeChannelVersions` functions since they are not used. + +### State Machine Breaking + +* (apps/transfer) [\#1907](https://github.com/cosmos/ibc-go/pull/1907) Blocked module account addresses are no longer allowed to send IBC transfers. +* (apps/27-interchain-accounts) [\#1882](https://github.com/cosmos/ibc-go/pull/1882) Explicitly check length of interchain account packet data in favour of nil check. + +### Improvements + +* (app/20-transfer) [\#1680](https://github.com/cosmos/ibc-go/pull/1680) Adds migration to correct any malformed trace path information of tokens with denoms that contains slashes. The transfer module consensus version has been bumped to 2. +* (app/20-transfer) [\#1730](https://github.com/cosmos/ibc-go/pull/1730) parse the ics20 denomination provided via a packet using the channel identifier format specified by ibc-go. +* (cleanup) [\#1335](https://github.com/cosmos/ibc-go/pull/1335/) `gofumpt -w -l .` to standardize the code layout more strictly than `go fmt ./...` +* (middleware) [\#1022](https://github.com/cosmos/ibc-go/pull/1022) Add `GetAppVersion` to the ICS4Wrapper interface. This function should be used by IBC applications to obtain their own version since the version set in the channel structure may be wrapped many times by middleware. +* (modules/core/04-channel) [\#1232](https://github.com/cosmos/ibc-go/pull/1232) Updating params on `NewPacketId` and moving to bottom of file. +* (app/29-fee) [\#1305](https://github.com/cosmos/ibc-go/pull/1305) Change version string for fee module to `ics29-1` +* (app/29-fee) [\#1341](https://github.com/cosmos/ibc-go/pull/1341) Check if the fee module is locked and if the fee module is enabled before refunding all fees +* (transfer) [\#1414](https://github.com/cosmos/ibc-go/pull/1414) Emitting Sender address from `fungible_token_packet` events in `OnRecvPacket` and `OnAcknowledgementPacket`. +* (testing/simapp) [\#1397](https://github.com/cosmos/ibc-go/pull/1397) Adding mock module to maccperms and adding check to ensure mock module is not a blocked account address. +* (core/02-client) [\#1570](https://github.com/cosmos/ibc-go/pull/1570) Emitting an event when handling an upgrade client proposal. +* (modules/light-clients/07-tendermint) [\#1713](https://github.com/cosmos/ibc-go/pull/1713) Allow client upgrade proposals to update `TrustingPeriod`. See ADR-026 for context. +* (core/client) [\#1740](https://github.com/cosmos/ibc-go/pull/1740) Add `cosmos_proto.implements_interface` to adhere to guidelines in [Cosmos SDK ADR 019](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-019-protobuf-state-encoding.md#safe-usage-of-any) for annotating `google.protobuf.Any` types + +### Features + +* [\#276](https://github.com/cosmos/ibc-go/pull/276) Adding the Fee Middleware module v1 +* (apps/29-fee) [\#1229](https://github.com/cosmos/ibc-go/pull/1229) Adding CLI commands for getting all unrelayed incentivized packets and packet by packet-id. +* (apps/29-fee) [\#1224](https://github.com/cosmos/ibc-go/pull/1224) Adding Query/CounterpartyAddress and CLI to ICS29 fee middleware +* (apps/29-fee) [\#1225](https://github.com/cosmos/ibc-go/pull/1225) Adding Query/FeeEnabledChannel and Query/FeeEnabledChannels with CLIs to ICS29 fee middleware. +* (modules/apps/29-fee) [\#1230](https://github.com/cosmos/ibc-go/pull/1230) Adding CLI command for getting incentivized packets for a specific channel-id. + +### Bug Fixes + +* (apps/29-fee) [\#1774](https://github.com/cosmos/ibc-go/pull/1774) Change non nil relayer assertion to non empty to avoid import/export issues for genesis upgrades. +* (apps/29-fee) [\#1278](https://github.com/cosmos/ibc-go/pull/1278) The URI path for the query to get all incentivized packets for a specific channel did not follow the same format as the rest of queries. +* (modules/core/04-channel)[\#1919](https://github.com/cosmos/ibc-go/pull/1919) Fixed formatting of sequence for packet "acknowledgement written" logs. + +## [v3.4.0](https://github.com/cosmos/ibc-go/releases/tag/v3.4.0) - 2022-11-07 + +### Dependencies + +* [\#2589](https://github.com/cosmos/ibc-go/pull/2589) Bump SDK version to v0.45.10 and Tendermint to v0.34.22. + +### State Machine Breaking + +* (apps/transfer) [\#2651](https://github.com/cosmos/ibc-go/pull/2651) Introduce `mustProtoMarshalJSON` for ics20 packet data marshalling which will skip emission (marshalling) of the memo field if unpopulated (empty). +* (27-interchain-accounts) [\#2590](https://github.com/cosmos/ibc-go/pull/2590) Removing port prefix requirement from the ICA host channel handshake +* (transfer) [\#2377](https://github.com/cosmos/ibc-go/pull/2377) Adding `sequence` to `MsgTransferResponse`. + +### Features + +* (apps/transfer) [\#2595](https://github.com/cosmos/ibc-go/pull/2595) Adding optional memo field to `FungibleTokenPacketData` and `MsgTransfer`. + +### Bug Fixes + +* (apps/transfer) [\#2679](https://github.com/cosmos/ibc-go/pull/2679) Check `x/bank` send enabled. + +## [v3.3.1](https://github.com/cosmos/ibc-go/releases/tag/v3.3.1) - 2022-10-27 + +### Dependencies + +* [\#2621](https://github.com/cosmos/ibc-go/pull/2621) Bump SDK version to v0.45.10 and Tendermint to v0.34.22. + +## [v3.3.0](https://github.com/cosmos/ibc-go/releases/tag/v3.3.0) - 2022-09-20 + +### Dependencies + +* [\#2286](https://github.com/cosmos/ibc-go/pull/2286) Bump SDK version to v0.45.8 and Tendermint to v0.34.21. + +### Features + +* (apps/27-interchain-accounts) [\#2193](https://github.com/cosmos/ibc-go/pull/2193) Adding `InterchainAccount` gRPC query endpoint to ICS27 `controller` submodule to allow users to retrieve registered interchain account addresses. + +### Bug Fixes + +* (27-interchain-accounts) [\#2308](https://github.com/cosmos/ibc-go/pull/2308) Nil checks have been added to ensure services are not registered for nil host or controller keepers. + +## [v3.2.1](https://github.com/cosmos/ibc-go/releases/tag/v3.2.1) - 2022-09-15 + +### Dependencies + +* [\#2285](https://github.com/cosmos/ibc-go/pull/2285) Bump SDK version to v0.45.8 and Tendermint to v0.34.21. + +## [v3.2.0](https://github.com/cosmos/ibc-go/releases/tag/v3.2.0) - 2022-08-12 + +### Dependencies + +* [\#1627](https://github.com/cosmos/ibc-go/pull/1627) Bump Go version to 1.18 +* [\#1905](https://github.com/cosmos/ibc-go/pull/1905) Bump SDK version to v0.45.7 + +### State Machine Breaking + +* (apps/transfer) [\#1907](https://github.com/cosmos/ibc-go/pull/1907) Blocked module account addresses are no longer allowed to send IBC transfers. +* (apps/27-interchain-accounts) [\#1882](https://github.com/cosmos/ibc-go/pull/1882) Explicitly check length of interchain account packet data in favour of nil check. + +### Improvements + +* (core/02-client) [\#1570](https://github.com/cosmos/ibc-go/pull/1570) Emitting an event when handling an upgrade client proposal. +* (modules/light-clients/07-tendermint) [\#1713](https://github.com/cosmos/ibc-go/pull/1713) Allow client upgrade proposals to update `TrustingPeriod`. See ADR-026 for context. +* (app/20-transfer) [\#1680](https://github.com/cosmos/ibc-go/pull/1680) Adds migration to correct any malformed trace path information of tokens with denoms that contains slashes. The transfer module consensus version has been bumped to 2. +* (app/20-transfer) [\#1730](https://github.com/cosmos/ibc-go/pull/1730) parse the ics20 denomination provided via a packet using the channel identifier format specified by ibc-go. +* (core/client) [\#1740](https://github.com/cosmos/ibc-go/pull/1740) Add `cosmos_proto.implements_interface` to adhere to guidelines in [Cosmos SDK ADR 019](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-019-protobuf-state-encoding.md#safe-usage-of-any) for annotating `google.protobuf.Any` types + +### Bug Fixes + +* (modules/core/04-channel)[\#1919](https://github.com/cosmos/ibc-go/pull/1919) Fixed formatting of sequence for packet "acknowledgement written" logs. + +## [v3.1.1](https://github.com/cosmos/ibc-go/releases/tag/v3.1.1) - 2022-08-02 + +### Dependencies + +* [\#1525](https://github.com/cosmos/ibc-go/pull/1525) Bump SDK version to v0.45.5 + +### Improvements + +* (core/02-client) [\#1570](https://github.com/cosmos/ibc-go/pull/1570) Emitting an event when handling an upgrade client proposal. +* (core/client) [\#1740](https://github.com/cosmos/ibc-go/pull/1740) Add `cosmos_proto.implements_interface` to adhere to guidelines in [Cosmos SDK ADR 019](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-019-protobuf-state-encoding.md#safe-usage-of-any) for annotating `google.protobuf.Any` types + +## [v3.1.0](https://github.com/cosmos/ibc-go/releases/tag/v3.1.0) - 2022-06-14 + +### Dependencies + +* [\#1300](https://github.com/cosmos/ibc-go/pull/1300) Bump SDK version to v0.45.4 + +### Improvements + +* (transfer) [\#1342](https://github.com/cosmos/ibc-go/pull/1342) `DenomTrace` grpc now takes in either an `ibc denom` or a `hash` instead of only accepting a `hash`. +* (modules/core/04-channel) [\#1160](https://github.com/cosmos/ibc-go/pull/1160) Improve `uint64 -> string` performance in `Logger`. +* (modules/core/04-channel) [\#1279](https://github.com/cosmos/ibc-go/pull/1279) Add selected channel version to MsgChanOpenInitResponse and MsgChanOpenTryResponse. Emit channel version during OpenInit/OpenTry +* (modules/core/keeper) [\#1284](https://github.com/cosmos/ibc-go/pull/1284) Add sanity check for the keepers passed into `ibckeeper.NewKeeper`. `ibckeeper.NewKeeper` now panics if any of the keepers passed in is empty. +* (transfer) [\#1414](https://github.com/cosmos/ibc-go/pull/1414) Emitting Sender address from `fungible_token_packet` events in `OnRecvPacket` and `OnAcknowledgementPacket`. +* (modules/core/04-channel) [\#1464](https://github.com/cosmos/ibc-go/pull/1464) Emit a channel close event when an ordered channel is closed. +* (modules/light-clients/07-tendermint) [\#1118](https://github.com/cosmos/ibc-go/pull/1118) Deprecating `AllowUpdateAfterExpiry` and `AllowUpdateAfterMisbehaviour`. See ADR-026 for context. + +### Features + +* (modules/core/02-client) [\#1336](https://github.com/cosmos/ibc-go/pull/1336) Adding Query/ConsensusStateHeights gRPC for fetching the height of every consensus state associated with a client. +* (modules/apps/transfer) [\#1416](https://github.com/cosmos/ibc-go/pull/1416) Adding gRPC endpoint for getting an escrow account for a given port-id and channel-id. +* (modules/apps/27-interchain-accounts) [\#1512](https://github.com/cosmos/ibc-go/pull/1512) Allowing ICA modules to handle all message types with "*". + +### Bug Fixes + +* (modules/core/04-channel) [\#1130](https://github.com/cosmos/ibc-go/pull/1130) Call `packet.GetSequence()` rather than passing func in `WriteAcknowledgement` log output +* (apps/transfer) [\#1451](https://github.com/cosmos/ibc-go/pull/1451) Fixing the support for base denoms that contain slashes. + +## [v3.0.2](https://github.com/cosmos/ibc-go/releases/tag/v3.0.2) - 2022-08-02 + +### Improvements + +* (core/02-client) [\#1570](https://github.com/cosmos/ibc-go/pull/1570) Emitting an event when handling an upgrade client proposal. +* (core/client) [\#1740](https://github.com/cosmos/ibc-go/pull/1740) Add `cosmos_proto.implements_interface` to adhere to guidelines in [Cosmos SDK ADR 019](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-019-protobuf-state-encoding.md#safe-usage-of-any) for annotating `google.protobuf.Any` types + +## [v3.0.1](https://github.com/cosmos/ibc-go/releases/tag/v3.0.1) - 2022-06-14 + +### Dependencies + +* [\#1300](https://github.com/cosmos/ibc-go/pull/1300) Bump SDK version to v0.45.4 + +### Improvements + +* (transfer) [\#1342](https://github.com/cosmos/ibc-go/pull/1342) `DenomTrace` grpc now takes in either an `ibc denom` or a `hash` instead of only accepting a `hash`. +* (modules/core/04-channel) [\#1160](https://github.com/cosmos/ibc-go/pull/1160) Improve `uint64 -> string` performance in `Logger`. +* (modules/core/keeper) [\#1284](https://github.com/cosmos/ibc-go/pull/1284) Add sanity check for the keepers passed into `ibckeeper.NewKeeper`. `ibckeeper.NewKeeper` now panics if any of the keepers passed in is empty. +* (transfer) [\#1414](https://github.com/cosmos/ibc-go/pull/1414) Emitting Sender address from `fungible_token_packet` events in `OnRecvPacket` and `OnAcknowledgementPacket`. +* (modules/core/04-channel) [\#1464](https://github.com/cosmos/ibc-go/pull/1464) Emit a channel close event when an ordered channel is closed. + +### Bug Fixes + +* (modules/core/04-channel) [\#1130](https://github.com/cosmos/ibc-go/pull/1130) Call `packet.GetSequence()` rather than passing func in `WriteAcknowledgement` log output + +## [v3.0.0](https://github.com/cosmos/ibc-go/releases/tag/v3.0.0) - 2022-03-15 + +### Dependencies + +* [\#404](https://github.com/cosmos/ibc-go/pull/404) Bump Go version to 1.17 +* [\#851](https://github.com/cosmos/ibc-go/pull/851) Bump SDK version to v0.45.1 +* [\#948](https://github.com/cosmos/ibc-go/pull/948) Bump ics23/go to v0.7 +* (core) [\#709](https://github.com/cosmos/ibc-go/pull/709) Replace github.com/pkg/errors with stdlib errors + +### API Breaking + +* (testing) [\#939](https://github.com/cosmos/ibc-go/pull/939) Support custom power reduction for testing. +* (modules/core/05-port) [\#1086](https://github.com/cosmos/ibc-go/pull/1086) Added `counterpartyChannelID` argument to IBCModule.OnChanOpenAck +* (channel) [\#848](https://github.com/cosmos/ibc-go/pull/848) Added `ChannelId` to MsgChannelOpenInitResponse +* (testing) [\#813](https://github.com/cosmos/ibc-go/pull/813) The `ack` argument to the testing function `RelayPacket` has been removed as it is no longer needed. +* (testing) [\#774](https://github.com/cosmos/ibc-go/pull/774) Added `ChainID` arg to `SetupWithGenesisValSet` on the testing app. `Coordinator` generated ChainIDs now starts at index 1 +* (transfer) [\#675](https://github.com/cosmos/ibc-go/pull/675) Transfer `NewKeeper` now takes in an ICS4Wrapper. The ICS4Wrapper may be the IBC Channel Keeper when ICS20 is not used in a middleware stack. The ICS4Wrapper is required for applications wishing to connect middleware to ICS20. +* (core) [\#650](https://github.com/cosmos/ibc-go/pull/650) Modify `OnChanOpenTry` IBC application module callback to return the negotiated app version. The version passed into the `MsgChanOpenTry` has been deprecated and will be ignored by core IBC. +* (core) [\#629](https://github.com/cosmos/ibc-go/pull/629) Removes the `GetProofSpecs` from the ClientState interface. This function was previously unused by core IBC. +* (transfer) [\#517](https://github.com/cosmos/ibc-go/pull/517) Separates the ICS 26 callback functions from `AppModule` into a new type `IBCModule` for ICS 20 transfer. +* (modules/core/02-client) [\#536](https://github.com/cosmos/ibc-go/pull/536) `GetSelfConsensusState` return type changed from bool to error. +* (channel) [\#644](https://github.com/cosmos/ibc-go/pull/644) Removes `CounterpartyHops` function from the ChannelKeeper. +* (testing) [\#776](https://github.com/cosmos/ibc-go/pull/776) Adding helper fn to generate capability name for testing callbacks +* (testing) [\#892](https://github.com/cosmos/ibc-go/pull/892) IBC Mock modules store the scoped keeper and portID within the IBCMockApp. They also maintain reference to the AppModule to update the AppModule's list of IBC applications it references. Allows for the mock module to be reused as a base application in middleware stacks. +* (channel) [\#882](https://github.com/cosmos/ibc-go/pull/882) The `WriteAcknowledgement` API now takes `exported.Acknowledgement` instead of a byte array +* (modules/core/ante) [\#950](https://github.com/cosmos/ibc-go/pull/950) Replaces the channel keeper with the IBC keeper in the IBC `AnteDecorator` in order to execute the entire message and be able to reject redundant messages that are in the same block as the non-redundant messages. + +### State Machine Breaking + +* (transfer) [\#818](https://github.com/cosmos/ibc-go/pull/818) Error acknowledgements returned from Transfer `OnRecvPacket` now include a deterministic ABCI code and error message. + +### Improvements + +* (client) [\#888](https://github.com/cosmos/ibc-go/pull/888) Add `GetTimestampAtHeight` to `ClientState` +* (interchain-accounts) [\#1037](https://github.com/cosmos/ibc-go/pull/1037) Add a function `InitModule` to the interchain accounts `AppModule`. This function should be called within the upgrade handler when adding the interchain accounts module to a chain. It should be called in place of InitGenesis (set the consensus version in the version map). +* (testing) [\#942](https://github.com/cosmos/ibc-go/pull/942) `NewTestChain` will create 4 validators in validator set by default. A new constructor function `NewTestChainWithValSet` is provided for test writers who want custom control over the validator set of test chains. +* (testing) [\#904](https://github.com/cosmos/ibc-go/pull/904) Add `ParsePacketFromEvents` function to the testing package. Useful when sending/relaying packets via the testing package. +* (testing) [\#893](https://github.com/cosmos/ibc-go/pull/893) Support custom private keys for testing. +* (testing) [\#810](https://github.com/cosmos/ibc-go/pull/810) Additional testing function added to `Endpoint` type called `RecvPacketWithResult`. Performs the same functionality as the existing `RecvPacket` function but also returns the message result. `path.RelayPacket` no longer uses the provided acknowledgement argument and instead obtains the acknowledgement via MsgRecvPacket events. +* (connection) [\#721](https://github.com/cosmos/ibc-go/pull/721) Simplify connection handshake error messages when unpacking client state. +* (channel) [\#692](https://github.com/cosmos/ibc-go/pull/692) Minimize channel logging by only emitting the packet sequence, source port/channel, destination port/channel upon packet receives, acknowledgements and timeouts. +* [\#383](https://github.com/cosmos/ibc-go/pull/383) Adds helper functions for merging and splitting middleware versions from the underlying app version. +* (modules/core/05-port) [\#288](https://github.com/cosmos/ibc-go/pull/288) Making the 05-port keeper function IsBound public. The IsBound function checks if the provided portID is already binded to a module. +* (client) [\#724](https://github.com/cosmos/ibc-go/pull/724) `IsRevisionFormat` and `IsClientIDFormat` have been updated to disallow newlines before the dash used to separate the chainID and revision number, and the client type and client sequence. +* (channel) [\#644](https://github.com/cosmos/ibc-go/pull/644) Adds `GetChannelConnection` to the ChannelKeeper. This function returns the connectionID and connection state associated with a channel. +* (channel) [\647](https://github.com/cosmos/ibc-go/pull/647) Reorganizes channel handshake handling to set channel state after IBC application callbacks. +* (interchain-accounts) [\#1466](https://github.com/cosmos/ibc-go/pull/1466) Emit event when there is an acknowledgement during `OnRecvPacket`. + +### Features + +* [\#432](https://github.com/cosmos/ibc-go/pull/432) Introduce `MockIBCApp` struct to the mock module. Allows the mock module to be reused to perform custom logic on each IBC App interface function. This might be useful when testing out IBC applications written as middleware. +* [\#380](https://github.com/cosmos/ibc-go/pull/380) Adding the Interchain Accounts module v1 +* [\#679](https://github.com/cosmos/ibc-go/pull/679) New CLI command `query ibc-transfer denom-hash ` to get the denom hash for a denom trace; this might be useful for debug + +### Bug Fixes + +* (testing) [\#884](https://github.com/cosmos/ibc-go/pull/884) Add and use in simapp a custom ante handler that rejects redundant transactions +* (transfer) [\#978](https://github.com/cosmos/ibc-go/pull/978) Support base denoms with slashes in denom validation +* (client) [\#941](https://github.com/cosmos/ibc-go/pull/941) Classify client states without consensus states as expired +* (channel) [\#995](https://github.com/cosmos/ibc-go/pull/995) Call `packet.GetSequence()` rather than passing func in `AcknowledgePacket` log output + +## [v2.5.0](https://github.com/cosmos/ibc-go/releases/tag/v2.5.0) - 2022-11-07 + +### Dependencies + +* [\#2578](https://github.com/cosmos/ibc-go/pull/2578) Bump SDK version to v0.45.10 and Tendermint to v0.34.22. + +### State Machine Breaking + +* (apps/transfer) [\#2651](https://github.com/cosmos/ibc-go/pull/2651) Introduce `mustProtoMarshalJSON` for ics20 packet data marshalling which will skip emission (marshalling) of the memo field if unpopulated (empty). +* (transfer) [\#2377](https://github.com/cosmos/ibc-go/pull/2377) Adding `sequence` to `MsgTransferResponse`. + +### Features + +* (apps/transfer) [\#2595](https://github.com/cosmos/ibc-go/pull/2595) Adding optional memo field to `FungibleTokenPacketData` and `MsgTransfer`. + +### Bug Fixes + +* (apps/transfer) [\#2679](https://github.com/cosmos/ibc-go/pull/2679) Check `x/bank` send enabled. + +## [v2.4.2](https://github.com/cosmos/ibc-go/releases/tag/v2.4.2) - 2022-10-27 + +### Dependencies + +* [\#2622](https://github.com/cosmos/ibc-go/pull/2622) Bump SDK version to v0.45.10 and Tendermint to v0.34.22. + +## [v2.4.1](https://github.com/cosmos/ibc-go/releases/tag/v2.4.1) - 2022-09-15 + +### Dependencies + +* [\#2284](https://github.com/cosmos/ibc-go/pull/2284) Bump SDK version to v0.45.8 and Tendermint to v0.34.21. + +## [v2.4.0](https://github.com/cosmos/ibc-go/releases/tag/v2.4.0) - 2022-08-12 + +### Dependencies + +* [\#1627](https://github.com/cosmos/ibc-go/pull/1627) Bump Go version to 1.18 +* [\#1905](https://github.com/cosmos/ibc-go/pull/1905) Bump SDK version to v0.45.7 + +### State Machine Breaking + +* (apps/transfer) [\#1907](https://github.com/cosmos/ibc-go/pull/1907) Blocked module account addresses are no longer allowed to send IBC transfers. + +### Improvements + +* (modules/light-clients/07-tendermint) [\#1713](https://github.com/cosmos/ibc-go/pull/1713) Allow client upgrade proposals to update `TrustingPeriod`. See ADR-026 for context. +* (core/02-client) [\#1570](https://github.com/cosmos/ibc-go/pull/1570) Emitting an event when handling an upgrade client proposal. +* (app/20-transfer) [\#1680](https://github.com/cosmos/ibc-go/pull/1680) Adds migration to correct any malformed trace path information of tokens with denoms that contains slashes. The transfer module consensus version has been bumped to 2. +* (app/20-transfer) [\#1730](https://github.com/cosmos/ibc-go/pull/1730) parse the ics20 denomination provided via a packet using the channel identifier format specified by ibc-go. +* (core/client) [\#1740](https://github.com/cosmos/ibc-go/pull/1740) Add `cosmos_proto.implements_interface` to adhere to guidelines in [Cosmos SDK ADR 019](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-019-protobuf-state-encoding.md#safe-usage-of-any) for annotating `google.protobuf.Any` types + +### Bug Fixes + +* (modules/core/04-channel)[\#1919](https://github.com/cosmos/ibc-go/pull/1919) Fixed formatting of sequence for packet "acknowledgement written" logs. + +## [v2.3.1](https://github.com/cosmos/ibc-go/releases/tag/v2.3.1) - 2022-08-02 + +### Dependencies + +* [\#1525](https://github.com/cosmos/ibc-go/pull/1525) Bump SDK version to v0.45.5 + +### Improvements + +* (core/02-client) [\#1570](https://github.com/cosmos/ibc-go/pull/1570) Emitting an event when handling an upgrade client proposal. +* (core/client) [\#1740](https://github.com/cosmos/ibc-go/pull/1740) Add `cosmos_proto.implements_interface` to adhere to guidelines in [Cosmos SDK ADR 019](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-019-protobuf-state-encoding.md#safe-usage-of-any) for annotating `google.protobuf.Any` types + +## [v2.3.0](https://github.com/cosmos/ibc-go/releases/tag/v2.3.0) - 2022-06-14 + +### Dependencies + +* [\#404](https://github.com/cosmos/ibc-go/pull/404) Bump Go version to 1.17 +* [\#1300](https://github.com/cosmos/ibc-go/pull/1300) Bump SDK version to v0.45.4 + +### Improvements + +* (transfer) [\#1342](https://github.com/cosmos/ibc-go/pull/1342) `DenomTrace` grpc now takes in either an `ibc denom` or a `hash` instead of only accepting a `hash`. +* (modules/core/04-channel) [\#1160](https://github.com/cosmos/ibc-go/pull/1160) Improve `uint64 -> string` performance in `Logger`. +* (modules/core/keeper) [\#1284](https://github.com/cosmos/ibc-go/pull/1284) Add sanity check for the keepers passed into `ibckeeper.NewKeeper`. `ibckeeper.NewKeeper` now panics if any of the keepers passed in is empty. +* (transfer) [\#1414](https://github.com/cosmos/ibc-go/pull/1414) Emitting Sender address from `fungible_token_packet` events in `OnRecvPacket` and `OnAcknowledgementPacket`. +* (modules/core/04-channel) [\#1464](https://github.com/cosmos/ibc-go/pull/1464) Emit a channel close event when an ordered channel is closed. +* (modules/light-clients/07-tendermint) [\#1118](https://github.com/cosmos/ibc-go/pull/1118) Deprecating `AllowUpdateAfterExpiry` and `AllowUpdateAfterMisbehaviour`. See ADR-026 for context. + +### Features + +* (modules/core/02-client) [\#1336](https://github.com/cosmos/ibc-go/pull/1336) Adding Query/ConsensusStateHeights gRPC for fetching the height of every consensus state associated with a client. +* (modules/apps/transfer) [\#1416](https://github.com/cosmos/ibc-go/pull/1416) Adding gRPC endpoint for getting an escrow account for a given port-id and channel-id. + +### Bug Fixes + +* (modules/core/04-channel) [\#1130](https://github.com/cosmos/ibc-go/pull/1130) Call `packet.GetSequence()` rather than passing func in `WriteAcknowledgement` log output +* (apps/transfer) [\#1451](https://github.com/cosmos/ibc-go/pull/1451) Fixing the support for base denoms that contain slashes. + +## [v2.2.2](https://github.com/cosmos/ibc-go/releases/tag/v2.2.2) - 2022-08-02 + +### Improvements + +* (core/02-client) [\#1570](https://github.com/cosmos/ibc-go/pull/1570) Emitting an event when handling an upgrade client proposal. +* (core/client) [\#1740](https://github.com/cosmos/ibc-go/pull/1740) Add `cosmos_proto.implements_interface` to adhere to guidelines in [Cosmos SDK ADR 019](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-019-protobuf-state-encoding.md#safe-usage-of-any) for annotating `google.protobuf.Any` types + +## [v2.2.1](https://github.com/cosmos/ibc-go/releases/tag/v2.2.1) - 2022-06-14 + +### Improvements + +* (transfer) [\#1342](https://github.com/cosmos/ibc-go/pull/1342) `DenomTrace` grpc now takes in either an `ibc denom` or a `hash` instead of only accepting a `hash`. +* (modules/core/04-channel) [\#1160](https://github.com/cosmos/ibc-go/pull/1160) Improve `uint64 -> string` performance in `Logger`. +* (modules/core/keeper) [\#1284](https://github.com/cosmos/ibc-go/pull/1284) Add sanity check for the keepers passed into `ibckeeper.NewKeeper`. `ibckeeper.NewKeeper` now panics if any of the keepers passed in is empty. +* (transfer) [\#1414](https://github.com/cosmos/ibc-go/pull/1414) Emitting Sender address from `fungible_token_packet` events in `OnRecvPacket` and `OnAcknowledgementPacket`. +* (modules/core/04-channel) [\#1464](https://github.com/cosmos/ibc-go/pull/1464) Emit a channel close event when an ordered channel is closed. + +### Bug Fixes + +* (modules/core/04-channel) [\#1130](https://github.com/cosmos/ibc-go/pull/1130) Call `packet.GetSequence()` rather than passing func in `WriteAcknowledgement` log output + +## [v2.2.0](https://github.com/cosmos/ibc-go/releases/tag/v2.2.0) - 2022-03-15 + +### Dependencies + +* [\#851](https://github.com/cosmos/ibc-go/pull/851) Bump SDK version to v0.45.1 + +## [v2.1.2](https://github.com/cosmos/ibc-go/releases/tag/v2.1.2) - 2022-08-02 + +### Improvements + +* (core/02-client) [\#1570](https://github.com/cosmos/ibc-go/pull/1570) Emitting an event when handling an upgrade client proposal. +* (core/client) [\#1740](https://github.com/cosmos/ibc-go/pull/1740) Add `cosmos_proto.implements_interface` to adhere to guidelines in [Cosmos SDK ADR 019](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-019-protobuf-state-encoding.md#safe-usage-of-any) for annotating `google.protobuf.Any` types + +## [v2.1.1](https://github.com/cosmos/ibc-go/releases/tag/v2.1.1) - 2022-06-14 + +### Dependencies + +* [\#1268](https://github.com/cosmos/ibc-go/pull/1268) Bump SDK version to v0.44.8 and Tendermint to version 0.34.19 + +### Improvements + +* (transfer) [\#1342](https://github.com/cosmos/ibc-go/pull/1342) `DenomTrace` grpc now takes in either an `ibc denom` or a `hash` instead of only accepting a `hash`. +* (modules/core/keeper) [\#1284](https://github.com/cosmos/ibc-go/pull/1284) Add sanity check for the keepers passed into `ibckeeper.NewKeeper`. `ibckeeper.NewKeeper` now panics if any of the keepers passed in is empty. +* (transfer) [\#1414](https://github.com/cosmos/ibc-go/pull/1414) Emitting Sender address from `fungible_token_packet` events in `OnRecvPacket` and `OnAcknowledgementPacket`. +* (modules/core/04-channel) [\#1464](https://github.com/cosmos/ibc-go/pull/1464) Emit a channel close event when an ordered channel is closed. + +### Bug Fixes + +* (modules/core/04-channel) [\#1130](https://github.com/cosmos/ibc-go/pull/1130) Call `packet.GetSequence()` rather than passing func in `WriteAcknowledgement` log output + +## [v2.1.0](https://github.com/cosmos/ibc-go/releases/tag/v2.1.0) - 2022-03-15 + +### Dependencies + +* [\#1084](https://github.com/cosmos/ibc-go/pull/1084) Bump SDK version to v0.44.6 +* [\#948](https://github.com/cosmos/ibc-go/pull/948) Bump ics23/go to v0.7 + +### State Machine Breaking + +* (transfer) [\#818](https://github.com/cosmos/ibc-go/pull/818) Error acknowledgements returned from Transfer `OnRecvPacket` now include a deterministic ABCI code and error message. + +### Features + +* [\#679](https://github.com/cosmos/ibc-go/pull/679) New CLI command `query ibc-transfer denom-hash ` to get the denom hash for a denom trace; this might be useful for debug + +### Bug Fixes + +* (client) [\#941](https://github.com/cosmos/ibc-go/pull/941) Classify client states without consensus states as expired +* (transfer) [\#978](https://github.com/cosmos/ibc-go/pull/978) Support base denoms with slashes in denom validation +* (channel) [\#995](https://github.com/cosmos/ibc-go/pull/995) Call `packet.GetSequence()` rather than passing func in `AcknowledgePacket` log output + +## [v2.0.3](https://github.com/cosmos/ibc-go/releases/tag/v2.0.3) - 2022-02-03 + +### Improvements + +* (channel) [\#692](https://github.com/cosmos/ibc-go/pull/692) Minimize channel logging by only emitting the packet sequence, source port/channel, destination port/channel upon packet receives, acknowledgements and timeouts. + +## [v2.0.2](https://github.com/cosmos/ibc-go/releases/tag/v2.0.2) - 2021-12-15 + +### Dependencies + +* [\#589](https://github.com/cosmos/ibc-go/pull/589) Bump SDK version to v0.44.5 + +### Bug Fixes + +* (modules/core) [\#603](https://github.com/cosmos/ibc-go/pull/603) Fix module name emitted as part of `OnChanOpenInit` event. Replacing `connection` module name with `channel`. + +## [v2.0.1](https://github.com/cosmos/ibc-go/releases/tag/v2.0.1) - 2021-12-05 + +### Dependencies + +* [\#567](https://github.com/cosmos/ibc-go/pull/567) Bump SDK version to v0.44.4 + +### Improvements + +* (02-client) [\#568](https://github.com/cosmos/ibc-go/pull/568) In IBC `transfer` cli command use local clock time as reference for relative timestamp timeout if greater than the block timestamp queried from the latest consensus state corresponding to the counterparty channel. +* [\#583](https://github.com/cosmos/ibc-go/pull/583) Move third_party/proto/confio/proofs.proto to third_party/proto/proofs.proto to enable proto service reflection. Migrate `buf` from v1beta1 to v1. + +### Bug Fixes + +* (02-client) [\#500](https://github.com/cosmos/ibc-go/pull/500) Fix IBC `update-client proposal` cli command to expect correct number of args. + +## [v2.0.0](https://github.com/cosmos/ibc-go/releases/tag/v2.0.0) - 2021-11-09 + +### Dependencies + +* [\#489](https://github.com/cosmos/ibc-go/pull/489) Bump Tendermint to v0.34.14 +* [\#503](https://github.com/cosmos/ibc-go/pull/503) Bump SDK version to v0.44.3 + +### API Breaking + +* (core) [\#227](https://github.com/cosmos/ibc-go/pull/227) Remove sdk.Result from application callbacks +* (transfer) [\#350](https://github.com/cosmos/ibc-go/pull/350) Change FungibleTokenPacketData to use a string for the Amount field. This enables token transfers with amounts previously restricted by uint64. Up to the maximum uint256 value is supported. + +### Features + +* [\#384](https://github.com/cosmos/ibc-go/pull/384) Added `NegotiateAppVersion` method to `IBCModule` interface supported by a gRPC query service in `05-port`. This provides routing of requests to the desired application module callback, which in turn performs application version negotiation. + +## [v1.5.0](https://github.com/cosmos/ibc-go/releases/tag/v1.5.0) - 2022-06-14 + +### Dependencies + +* [\#404](https://github.com/cosmos/ibc-go/pull/404) Bump Go version to 1.17 +* [\#1300](https://github.com/cosmos/ibc-go/pull/1300) Bump SDK version to v0.45.4 + +### Improvements + +* (transfer) [\#1342](https://github.com/cosmos/ibc-go/pull/1342) `DenomTrace` grpc now takes in either an `ibc denom` or a `hash` instead of only accepting a `hash`. +* (modules/core/04-channel) [\#1160](https://github.com/cosmos/ibc-go/pull/1160) Improve `uint64 -> string` performance in `Logger`. +* (modules/core/keeper) [\#1284](https://github.com/cosmos/ibc-go/pull/1284) Add sanity check for the keepers passed into `ibckeeper.NewKeeper`. `ibckeeper.NewKeeper` now panics if any of the keepers passed in is empty. +* (transfer) [\#1414](https://github.com/cosmos/ibc-go/pull/1414) Emitting Sender address from `fungible_token_packet` events in `OnRecvPacket` and `OnAcknowledgementPacket`. +* (modules/core/04-channel) [\#1464](https://github.com/cosmos/ibc-go/pull/1464) Emit a channel close event when an ordered channel is closed. +* (modules/light-clients/07-tendermint) [\#1118](https://github.com/cosmos/ibc-go/pull/1118) Deprecating `AllowUpdateAfterExpiry` and `AllowUpdateAfterMisbehaviour`. See ADR-026 for context. + +### Features + +* (modules/core/02-client) [\#1336](https://github.com/cosmos/ibc-go/pull/1336) Adding Query/ConsensusStateHeights gRPC for fetching the height of every consensus state associated with a client. +* (modules/apps/transfer) [\#1416](https://github.com/cosmos/ibc-go/pull/1416) Adding gRPC endpoint for getting an escrow account for a given port-id and channel-id. + +### Bug Fixes + +* (modules/core/04-channel) [\#1130](https://github.com/cosmos/ibc-go/pull/1130) Call `packet.GetSequence()` rather than passing func in `WriteAcknowledgement` log output +* (apps/transfer) [\#1451](https://github.com/cosmos/ibc-go/pull/1451) Fixing the support for base denoms that contain slashes. + +## [v1.4.1](https://github.com/cosmos/ibc-go/releases/tag/v1.4.1) - 2022-06-14 + +### Improvements + +* (transfer) [\#1342](https://github.com/cosmos/ibc-go/pull/1342) `DenomTrace` grpc now takes in either an `ibc denom` or a `hash` instead of only accepting a `hash`. +* (modules/core/04-channel) [\#1160](https://github.com/cosmos/ibc-go/pull/1160) Improve `uint64 -> string` performance in `Logger`. +* (modules/core/keeper) [\#1284](https://github.com/cosmos/ibc-go/pull/1284) Add sanity check for the keepers passed into `ibckeeper.NewKeeper`. `ibckeeper.NewKeeper` now panics if any of the keepers passed in is empty. +* (transfer) [\#1414](https://github.com/cosmos/ibc-go/pull/1414) Emitting Sender address from `fungible_token_packet` events in `OnRecvPacket` and `OnAcknowledgementPacket`. +* (modules/core/04-channel) [\#1464](https://github.com/cosmos/ibc-go/pull/1464) Emit a channel close event when an ordered channel is closed. + +### Bug Fixes + +* (modules/core/04-channel) [\#1130](https://github.com/cosmos/ibc-go/pull/1130) Call `packet.GetSequence()` rather than passing func in `WriteAcknowledgement` log output + +## [v1.4.0](https://github.com/cosmos/ibc-go/releases/tag/v1.4.0) - 2022-03-15 + +### Dependencies + +* [\#851](https://github.com/cosmos/ibc-go/pull/851) Bump SDK version to v0.45.1 + +## [v1.3.1](https://github.com/cosmos/ibc-go/releases/tag/v1.3.1) - 2022-06-14 + +### Dependencies + +* [\#1267](https://github.com/cosmos/ibc-go/pull/1267) Bump SDK version to v0.44.8 and Tendermint to version 0.34.19 + +### Improvements + +* (transfer) [\#1342](https://github.com/cosmos/ibc-go/pull/1342) `DenomTrace` grpc now takes in either an `ibc denom` or a `hash` instead of only accepting a `hash`. +* (modules/core/04-channel) [\#1160](https://github.com/cosmos/ibc-go/pull/1160) Improve `uint64 -> string` performance in `Logger`. +* (modules/core/keeper) [\#1284](https://github.com/cosmos/ibc-go/pull/1284) Add sanity check for the keepers passed into `ibckeeper.NewKeeper`. `ibckeeper.NewKeeper` now panics if any of the keepers passed in is empty. +* (transfer) [\#1414](https://github.com/cosmos/ibc-go/pull/1414) Emitting Sender address from `fungible_token_packet` events in `OnRecvPacket` and `OnAcknowledgementPacket`. +* (modules/core/04-channel) [\#1464](https://github.com/cosmos/ibc-go/pull/1464) Emit a channel close event when an ordered channel is closed. + +### Bug Fixes + +* (modules/core/04-channel) [\#1130](https://github.com/cosmos/ibc-go/pull/1130) Call `packet.GetSequence()` rather than passing func in `WriteAcknowledgement` log output + +## [v1.3.0](https://github.com/cosmos/ibc-go/releases/tag/v1.3.0) - 2022-03-15 + +### Dependencies + +* [\#1073](https://github.com/cosmos/ibc-go/pull/1073) Bump SDK version to v0.44.6 +* [\#948](https://github.com/cosmos/ibc-go/pull/948) Bump ics23/go to v0.7 + +### State Machine Breaking + +* (transfer) [\#818](https://github.com/cosmos/ibc-go/pull/818) Error acknowledgements returned from Transfer `OnRecvPacket` now include a deterministic ABCI code and error message. + +### Features + +* [\#679](https://github.com/cosmos/ibc-go/pull/679) New CLI command `query ibc-transfer denom-hash ` to get the denom hash for a denom trace; this might be useful for debug + +### Bug Fixes + +* (client) [\#941](https://github.com/cosmos/ibc-go/pull/941) Classify client states without consensus states as expired +* (transfer) [\#978](https://github.com/cosmos/ibc-go/pull/978) Support base denoms with slashes in denom validation +* (channel) [\#995](https://github.com/cosmos/ibc-go/pull/995) Call `packet.GetSequence()` rather than passing func in `AcknowledgePacket` log output + +## [v1.2.6](https://github.com/cosmos/ibc-go/releases/tag/v1.2.6) - 2022-02-03 + +### Improvements + +* (channel) [\#692](https://github.com/cosmos/ibc-go/pull/692) Minimize channel logging by only emitting the packet sequence, source port/channel, destination port/channel upon packet receives, acknowledgements and timeouts. + +## [v1.2.5](https://github.com/cosmos/ibc-go/releases/tag/v1.2.5) - 2021-12-15 + +### Dependencies + +* [\#589](https://github.com/cosmos/ibc-go/pull/589) Bump SDK version to v0.44.5 + +### Bug Fixes + +* (modules/core) [\#603](https://github.com/cosmos/ibc-go/pull/603) Fix module name emitted as part of `OnChanOpenInit` event. Replacing `connection` module name with `channel`. + +## [v1.2.4](https://github.com/cosmos/ibc-go/releases/tag/v1.2.4) - 2021-12-05 + +### Dependencies + +* [\#567](https://github.com/cosmos/ibc-go/pull/567) Bump SDK version to v0.44.4 + +### Improvements + +* [\#583](https://github.com/cosmos/ibc-go/pull/583) Move third_party/proto/confio/proofs.proto to third_party/proto/proofs.proto to enable proto service reflection. Migrate `buf` from v1beta1 to v1. + +## [v1.2.3](https://github.com/cosmos/ibc-go/releases/tag/v1.2.3) - 2021-11-09 + +### Dependencies + +* [\#489](https://github.com/cosmos/ibc-go/pull/489) Bump Tendermint to v0.34.14 +* [\#503](https://github.com/cosmos/ibc-go/pull/503) Bump SDK version to v0.44.3 + +## [v1.2.2](https://github.com/cosmos/ibc-go/releases/tag/v1.2.2) - 2021-10-15 + +### Dependencies + +* [\#485](https://github.com/cosmos/ibc-go/pull/485) Bump SDK version to v0.44.2 + +## [v1.2.1](https://github.com/cosmos/ibc-go/releases/tag/v1.2.1) - 2021-10-04 + +### Dependencies + +* [\#455](https://github.com/cosmos/ibc-go/pull/455) Bump SDK version to v0.44.1 + +## [v1.2.0](https://github.com/cosmos/ibc-go/releases/tag/v1.2.0) - 2021-09-10 + +### State Machine Breaking + +* (24-host) [\#344](https://github.com/cosmos/ibc-go/pull/344) Increase port identifier limit to 128 characters. + +### Improvements + +* [\#373](https://github.com/cosmos/ibc-go/pull/375) Added optional field `PacketCommitmentSequences` to `QueryPacketAcknowledgementsRequest` to provide filtering of packet acknowledgements. + +### Features + +* [\#372](https://github.com/cosmos/ibc-go/pull/372) New CLI command `query ibc client status ` to get the current activity status of a client. + +### Dependencies + +* [\#386](https://github.com/cosmos/ibc-go/pull/386) Bump [tendermint](https://github.com/tendermint/tendermint) from v0.34.12 to v0.34.13. + +## [v1.1.6](https://github.com/cosmos/ibc-go/releases/tag/v1.1.6) - 2022-01-25 + +### Improvements + +* (channel) [\#692](https://github.com/cosmos/ibc-go/pull/692) Minimize channel logging by only emitting the packet sequence, source port/channel, destination port/channel upon packet receives, acknowledgements and timeouts. + +## [v1.1.5](https://github.com/cosmos/ibc-go/releases/tag/v1.1.5) - 2021-12-15 + +### Dependencies + +* [\#589](https://github.com/cosmos/ibc-go/pull/589) Bump SDK version to v0.44.5 + +### Bug Fixes + +* (modules/core) [\#603](https://github.com/cosmos/ibc-go/pull/603) Fix module name emitted as part of `OnChanOpenInit` event. Replacing `connection` module name with `channel`. + +## [v1.1.4](https://github.com/cosmos/ibc-go/releases/tag/v1.1.4) - 2021-12-05 + +### Dependencies + +* [\#567](https://github.com/cosmos/ibc-go/pull/567) Bump SDK version to v0.44.4 + +### Improvements + +* [\#583](https://github.com/cosmos/ibc-go/pull/583) Move third_party/proto/confio/proofs.proto to third_party/proto/proofs.proto to enable proto service reflection. Migrate `buf` from v1beta1 to v1. + +## [v1.1.3](https://github.com/cosmos/ibc-go/releases/tag/v1.1.3) - 2021-11-09 + +### Dependencies + +* [\#489](https://github.com/cosmos/ibc-go/pull/489) Bump Tendermint to v0.34.14 +* [\#503](https://github.com/cosmos/ibc-go/pull/503) Bump SDK version to v0.44.3 + +## [v1.1.2](https://github.com/cosmos/ibc-go/releases/tag/v1.1.2) - 2021-10-15 + +* [\#485](https://github.com/cosmos/ibc-go/pull/485) Bump SDK version to v0.44.2 + +## [v1.1.1](https://github.com/cosmos/ibc-go/releases/tag/v1.1.1) - 2021-10-04 + +### Dependencies + +* [\#455](https://github.com/cosmos/ibc-go/pull/455) Bump SDK version to v0.44.1 + +## [v1.1.0](https://github.com/cosmos/ibc-go/releases/tag/v1.1.0) - 2021-09-03 + +### Dependencies + +* [\#367](https://github.com/cosmos/ibc-go/pull/367) Bump [cosmos-sdk](https://github.com/cosmos/cosmos-sdk) from 0.43 to 0.44. + +## [v1.0.1](https://github.com/cosmos/ibc-go/releases/tag/v1.0.1) - 2021-08-25 + +### Improvements + +* [\#343](https://github.com/cosmos/ibc-go/pull/343) Create helper functions for publishing of packet sent and acknowledgement sent events. + +## [v1.0.0](https://github.com/cosmos/ibc-go/releases/tag/v1.0.0) - 2021-08-10 + +### Bug Fixes + +* (07-tendermint) [\#241](https://github.com/cosmos/ibc-go/pull/241) Ensure tendermint client state latest height revision number matches chain id revision number. +* (07-tendermint) [\#234](https://github.com/cosmos/ibc-go/pull/234) Use sentinel value for the consensus state root set during a client upgrade. This prevents genesis validation from failing. +* (modules) [\#223](https://github.com/cosmos/ibc-go/pull/223) Use correct Prometheus format for metric labels. +* (06-solomachine) [\#214](https://github.com/cosmos/ibc-go/pull/214) Disable defensive timestamp check in SendPacket for solo machine clients. +* (07-tendermint) [\#210](https://github.com/cosmos/ibc-go/pull/210) Export all consensus metadata on genesis restarts for tendermint clients. +* (core) [\#200](https://github.com/cosmos/ibc-go/pull/200) Fixes incorrect export of IBC identifier sequences. Previously, the next identifier sequence for clients/connections/channels was not set during genesis export. This resulted in the next identifiers being generated on the new chain to reuse old identifiers (the sequences began again from 0). +* (02-client) [\#192](https://github.com/cosmos/ibc-go/pull/192) Fix IBC `query ibc client header` cli command. Support historical queries for query header/node-state commands. +* (modules/light-clients/06-solomachine) [\#153](https://github.com/cosmos/ibc-go/pull/153) Fix solo machine proof height sequence mismatch bug. +* (modules/light-clients/06-solomachine) [\#122](https://github.com/cosmos/ibc-go/pull/122) Fix solo machine merkle prefix casting bug. +* (modules/light-clients/06-solomachine) [\#120](https://github.com/cosmos/ibc-go/pull/120) Fix solo machine handshake verification bug. +* (modules/light-clients/06-solomachine) [\#153](https://github.com/cosmos/ibc-go/pull/153) fix solo machine connection handshake failure at `ConnectionOpenAck`. + +### API Breaking + +* (04-channel) [\#220](https://github.com/cosmos/ibc-go/pull/220) Channel legacy handler functions were removed. Please use the MsgServer functions or directly call the channel keeper's handshake function. +* (modules) [\#206](https://github.com/cosmos/ibc-go/pull/206) Expose `relayer sdk.AccAddress` on `OnRecvPacket`, `OnAcknowledgementPacket`, `OnTimeoutPacket` module callbacks to enable incentivization. +* (02-client) [\#181](https://github.com/cosmos/ibc-go/pull/181) Remove 'InitialHeight' from UpdateClient Proposal. Only copy over latest consensus state from substitute client. +* (06-solomachine) [\#169](https://github.com/cosmos/ibc-go/pull/169) Change FrozenSequence to boolean in solomachine ClientState. The solo machine proto package has been bumped from `v1` to `v2`. +* (module/core/02-client) [\#165](https://github.com/cosmos/ibc-go/pull/165) Remove GetFrozenHeight from the ClientState interface. +* (modules) [\#166](https://github.com/cosmos/ibc-go/pull/166) Remove GetHeight from the misbehaviour interface. The `consensus_height` attribute has been removed from Misbehaviour events. +* (modules) [\#162](https://github.com/cosmos/ibc-go/pull/162) Remove deprecated Handler types in core IBC and the ICS 20 transfer module. +* (modules/core) [\#161](https://github.com/cosmos/ibc-go/pull/161) Remove Type(), Route(), GetSignBytes() from 02-client, 03-connection, and 04-channel messages. +* (modules) [\#140](https://github.com/cosmos/ibc-go/pull/140) IsFrozen() client state interface changed to Status(). gRPC `ClientStatus` route added. +* (modules/core) [\#109](https://github.com/cosmos/ibc-go/pull/109) Remove connection and channel handshake CLI commands. +* (modules) [\#107](https://github.com/cosmos/ibc-go/pull/107) Modify OnRecvPacket callback to return an acknowledgement which indicates if it is successful or not. Callback state changes are discarded for unsuccessful acknowledgements only. +* (modules) [\#108](https://github.com/cosmos/ibc-go/pull/108) All message constructors take the signer as a string to prevent upstream bugs. The `String()` function for an SDK Acc Address relies on external context. +* (transfer) [\#275](https://github.com/cosmos/ibc-go/pull/275) Remove 'ChanCloseInit' function from transfer keeper. ICS20 does not close channels. + +### State Machine Breaking + +* (modules/light-clients/07-tendermint) [\#99](https://github.com/cosmos/ibc-go/pull/99) Enforce maximum chain-id length for tendermint client. +* (modules/light-clients/07-tendermint) [\#141](https://github.com/cosmos/ibc-go/pull/141) Allow a new form of misbehaviour that proves counterparty chain breaks time monotonicity, automatically enforce monotonicity in UpdateClient and freeze client if monotonicity is broken. +* (modules/light-clients/07-tendermint) [\#141](https://github.com/cosmos/ibc-go/pull/141) Freeze the client if there's a conflicting header submitted for an existing consensus state. +* (modules/core/02-client) [\#8405](https://github.com/cosmos/cosmos-sdk/pull/8405) Refactor IBC client update governance proposals to use a substitute client to update a frozen or expired client. +* (modules/core/02-client) [\#8673](https://github.com/cosmos/cosmos-sdk/pull/8673) IBC upgrade logic moved to 02-client and an IBC UpgradeProposal is added. +* (modules/core/03-connection) [\#171](https://github.com/cosmos/ibc-go/pull/171) Introduces a new parameter `MaxExpectedTimePerBlock` to allow connections to calculate and enforce a block delay that is proportional to time delay set by connection. +* (core) [\#268](https://github.com/cosmos/ibc-go/pull/268) Perform a no-op on redundant relay messages. Previous behaviour returned an error. Now no state change will occur and no error will be returned. + +### Improvements + +* (04-channel) [\#220](https://github.com/cosmos/ibc-go/pull/220) Channel handshake events are now emitted with the channel keeper. +* (core/02-client) [\#205](https://github.com/cosmos/ibc-go/pull/205) Add in-place and genesis migrations from SDK v0.42.0 to ibc-go v1.0.0. Solo machine protobuf definitions are migrated from v1 to v2. All solo machine consensus states are pruned. All expired tendermint consensus states are pruned. +* (modules/core) [\#184](https://github.com/cosmos/ibc-go/pull/184) Improve error messages. Uses unique error codes to indicate already relayed packets. +* (07-tendermint) [\#182](https://github.com/cosmos/ibc-go/pull/182) Remove duplicate checks in upgrade logic. +* (modules/core/04-channel) [\#7949](https://github.com/cosmos/cosmos-sdk/issues/7949) Standardized channel `Acknowledgement` moved to its own file. Codec registration redundancy removed. +* (modules/core/04-channel) [\#144](https://github.com/cosmos/ibc-go/pull/144) Introduced a `packet_data_hex` attribute to emit the hex-encoded packet data in events. This allows for raw binary (proto-encoded message) to be sent over events and decoded correctly on relayer. Original `packet_data` is DEPRECATED. All relayers and IBC event consumers are encouraged to switch to `packet_data_hex` as soon as possible. +* (core/04-channel) [\#197](https://github.com/cosmos/ibc-go/pull/197) Introduced a `packet_ack_hex` attribute to emit the hex-encoded acknowledgement in events. This allows for raw binary (proto-encoded message) to be sent over events and decoded correctly on relayer. Original `packet_ack` is DEPRECATED. All relayers and IBC event consumers are encouraged to switch to `packet_ack_hex` as soon as possible. +* (modules/light-clients/07-tendermint) [\#125](https://github.com/cosmos/ibc-go/pull/125) Implement efficient iteration of consensus states and pruning of earliest expired consensus state on UpdateClient. +* (modules/light-clients/07-tendermint) [\#141](https://github.com/cosmos/ibc-go/pull/141) Return early in case there's a duplicate update call to save Gas. +* (modules/core/ante) [\#235](https://github.com/cosmos/ibc-go/pull/235) Introduces a new IBC Antedecorator that will reject transactions that only contain redundant packet messages (and accompany UpdateClient msgs). This will prevent relayers from wasting fees by submitting messages for packets that have already been processed by previous relayer(s). The Antedecorator is only applied on CheckTx and RecheckTx and is therefore optional for each node. + +### Features + +* [\#198](https://github.com/cosmos/ibc-go/pull/198) New CLI command `query ibc-transfer escrow-address ` to get the escrow address for a channel; can be used to then query balance of escrowed tokens + +### Client Breaking Changes + +* (02-client/cli) [\#196](https://github.com/cosmos/ibc-go/pull/196) Rename `node-state` cli command to `self-consensus-state`. + +## IBC in the Cosmos SDK Repository + +The IBC module was originally released in [v0.40.0](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.40.0) of the SDK. +Please see the [Release Notes](https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/RELEASE_NOTES.md). + +The IBC module is also contained in the releases for [v0.41.x](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.41.0) and [v0.42.x](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.42.0). +Please see the Release Notes for [v0.41.x](https://github.com/cosmos/cosmos-sdk/blob/v0.41.0/RELEASE_NOTES.md) and [v0.42.x](https://github.com/cosmos/cosmos-sdk/blob/v0.42.0/RELEASE_NOTES.md). + +The IBC module was removed in the commit hash [da064e13d56add466548135739c5860a9f7ed842](https://github.com/cosmos/cosmos-sdk/commit/da064e13d56add466548135739c5860a9f7ed842) on the SDK. The release for SDK v0.43.0 will be the first release without the IBC module. + +Backports should be made to the [release/v0.42.x](https://github.com/cosmos/cosmos-sdk/tree/release/v0.42.x) branch on the SDK. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..9efa379 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at . The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f489727 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,78 @@ +# Contributing to ibc-go + +Thank you for considering making contributions to ibc-go! 🎉👍 + +## Code of conduct + +This project and everyone participating in it is governed by ibc-go's [code of conduct](./CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code + +## How can I contribute? + +Contributing to this repository can mean many things such as participating in discussions or proposing code changes. To ensure a smooth workflow for all contributors, the general procedure for contributing has been established: + +### Reporting bugs + +If you find that something is not working as expected, please open an issue using the [bug report template](https://github.com/cosmos/ibc-go/blob/main/.github/ISSUE_TEMPLATE/bug-report.md) and provide as much information possible: how can the bug be reproduced? What's the expected behavior? What version is affected? + +This is also true if you plan to fix the bug yourself and submit a PR. As a general rule, we want contributing pull requests to reference an existing issue. See [Submitting pull requests](#submitting-pull-requests) + +### Proposing improvements or new features + +New features or improvements should be written in an issue using the [new feature template](https://github.com/cosmos/ibc-go/blob/main/.github/ISSUE_TEMPLATE/feature-request.md). Please include in the issue as many details as possible: what use case(s) would this new feature or improvement enable? Why are those use cases important or helpful? what user group would benefit? The team will evaluate and engage with you in a discussion of the proposal, which could have different outcomes: + +- the core ibc-go team deciding to implement this feature and adding it to their planning, +- agreeing to support external contributors to implement it with the goal of merging it eventually in ibc-go, +- discarding the suggestion if deemed not aligned with the objectives of ibc-go; +- or proposing (in the case of applications or light clients) to be developed and maintained in a separate repository. + +Unless the change is a minor bug fix with minor code changes, and you want to submit a pull request, please make sure to write a Github issue for it before opening the pull request. + +### Architecture Decision Records (ADR) + +When proposing an architecture decision for the ibc-go, please create an [ADR](./docs/architecture/README.md) so further discussions can be made. We are following this process so all involved parties are in agreement before any party begins coding the proposed implementation. Please use the [ADR template](./docs/architecture/adr.template.md) to scaffold any new ADR. If you would like to see some examples of how these are written refer to ibc-go's [ADRs](./docs/architecture/). ADRs are solidified designs that will be implemented in ibc-go (and do not have a spec). They should document the architecture that will be built. Most design feedback should be gathered before the initial draft of the ADR. ADR's can/should be written for any design decisions we make which may be changed at some point in the future. + +### Participating in discussions + +New features or improvements are sometimes also debated in [discussions](https://github.com/cosmos/ibc-go/discussions). Sharing feedback or ideas there is very helpful for us. high level discussions that may get a lot of comments on a variety of different aspects, design aspects still being considered. + +### Submitting pull requests + +Before opening a pull request, make sure there is an accompanying issue that has been assigned to you. +In the case of smaller changes, opening a pull request without being assigned to the issue **can** be accepted, but to avoid having to redesign or discard your work due to the change no longer being needed, asking to be assigned to the issue is the safest course of action. We welcome contributors, but we have put in place these guidelines to safeguard the time of both external and core contributors. + +Unless you feel confident your change will be accepted (see [Unwanted pull requests](#unwanted-pull-requests)) you should first create an issue to discuss your change with us. This lets us all discuss the design and proposed implementation of your change, which helps ensure your time is well spent and that your contribution will be accepted. + +Looking for a good place to start contributing? The issue tracker is always the first place to go. Issues are triaged to categorize them: + +- Check out some [`good first issue`s](https://github.com/cosmos/ibc-go/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). These are issues whose scope of work should be pretty clearly specified and they are best suited for developers new to ibc-go (i.e. no deep knowledge of Cosmos SDK or ibc-go is required). For example, some of these issues may involve improving the logging, emitting new events or removing unused code. +- Or pick up a [`help wanted`](https://github.com/cosmos/ibc-go/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) issue. These issues should be a bit more involved than the good first issues and the developer working on them would benefit from some familiarity already with the codebase. These types of issues may involve adding new (or extending the functionality of existing) gRPC endpoints, bumping the version of Cosmos SDK or Tendermint or fixing bugs. + +If you would like to contribute, follow this process: + +1. If the issue is a proposal, ensure that the proposal has been accepted. +2. Ensure that nobody else has already begun working on this issue. If they have, make sure to contact them to collaborate. +3. If nobody has been assigned for the issue and you would like to work on it, comment on the issue to inform the community of your intentions to begin work. Then we will be able to assign the issue to you, making it visible for others that this issue is being tackled. If you end up not creating a pull request for this issue, please comment on the issue as well, so that it can be assigned to somebody else. +4. Follow standard GitHub best practices: fork the repo, branch from the HEAD of `main`, make some commits, and submit a PR to `main`. For core developers working within the ibc-go repo, branches must be named with the convention `{moniker}/{issue#}-branch-name` to ensure a clear ownership of branches. +5. Feel free to submit the pull request in `Draft` mode, even if the work is not complete, as this indicates to the community you are working on something and allows them to provide comments early in the development process. +6. When the code is complete it can be marked `Ready for Review`. +7. Be sure to include a relevant changelog entry in the [Commit Message / Changelog Entry section of pull request description](https://github.com/cosmos/ibc-go/blob/main/.github/PULL_REQUEST_TEMPLATE.md#commit-message--changelog-entry) so that we can add changelog entry when merging the pull request. Please follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and use one of the commit types mentioned in the [Commit messages section of the pull request guidelines](./docs/dev/pull-requests.md#commit-messages). + +Please make sure to check out our [Pull request guidelines](./docs/dev/pull-requests.md) for more information. + +#### Unwanted pull requests + +To ensure the core maintainers time are spent well, we have certain pull requests we want to avoid: + +- Any non-minor pull requests without an **assigned** issue +- Any non-minor bug fixes without an issue (ideally, also assigned, but we are less strict on this) +- Spelling mistakes/changes (instead, try to fix our CI so that it would be able to catch it automatically - that would be useful) + +## Relevant development docs + +- [Project structure](./docs/dev/project-structure.md) +- [Development setup](./docs/dev/development-setup.md) +- [Go style guide](./docs/dev/go-style-guide.md) +- [Documentation guide](./docs/README.md) +- [Writing tests](./testing/README.md) +- [Pull request guidelines](./docs/dev/pull-requests.md) +- [Release process](./docs/dev/release-management.md) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..385e373 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +FROM golang:1.23.8-alpine AS builder +ARG IBC_GO_VERSION + +RUN set -eux; apk add --no-cache gcc git libusb-dev linux-headers make musl-dev; + +ENV GOPATH="" + +# ensure the ibc go version is being specified for this image. +RUN test -n "${IBC_GO_VERSION}" + +# Copy relevant files before go mod download. Replace directives to local paths break if local +# files are not copied before go mod download. +ADD internal internal +ADD simapp simapp +ADD testing testing +ADD modules modules +ADD LICENSE LICENSE + +COPY contrib/devtools/Makefile contrib/devtools/Makefile +COPY Makefile . + +COPY go.mod . +COPY go.sum . + +RUN go mod download + +RUN make build + +FROM alpine:3.21 +ARG IBC_GO_VERSION + +LABEL "org.cosmos.ibc-go"="${IBC_GO_VERSION}" + +COPY --from=builder /go/build/simd /bin/simd + +ENTRYPOINT ["simd"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a5cec31 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fc55d9a --- /dev/null +++ b/Makefile @@ -0,0 +1,407 @@ +#!/usr/bin/make -f + +PACKAGES_NOSIMULATION=$(shell go list ./... | grep -v '/simulation') +PACKAGES_SIMTEST=$(shell go list ./... | grep '/simulation') +CHANGED_GO_FILES := $(shell git diff --name-only | grep .go$$ | grep -v pb.go) +ALL_GO_FILES := $(shell find . -regex ".*\.go$$" | grep -v pb.go) +VERSION := $(shell echo $(shell git describe --always) | sed 's/^v//') +COMMIT := $(shell git log -1 --format='%H') +LEDGER_ENABLED ?= true +BINDIR ?= $(GOPATH)/bin +BUILDDIR ?= $(CURDIR)/build +SIMAPP = ./simapp +MOCKS_DIR = $(CURDIR)/tests/mocks +HTTPS_GIT := https://github.com/cosmos/ibc-go.git +DOCKER := $(shell which docker) +PROJECT_NAME = $(shell git remote get-url origin | xargs basename -s .git) + +export GO111MODULE = on + +# process build tags + +build_tags = netgo +ifeq ($(LEDGER_ENABLED),true) + ifeq ($(OS),Windows_NT) + GCCEXE = $(shell where gcc.exe 2> NUL) + ifeq ($(GCCEXE),) + $(error gcc.exe not installed for ledger support, please install or set LEDGER_ENABLED=false) + else + build_tags += ledger + endif + else + UNAME_S = $(shell uname -s) + ifeq ($(UNAME_S),OpenBSD) + $(warning OpenBSD detected, disabling ledger support (https://github.com/cosmos/cosmos-sdk/issues/1988)) + else + GCC = $(shell command -v gcc 2> /dev/null) + ifeq ($(GCC),) + $(error gcc not installed for ledger support, please install or set LEDGER_ENABLED=false) + else + build_tags += ledger + endif + endif + endif +endif + +ifeq (cleveldb,$(findstring cleveldb,$(COSMOS_BUILD_OPTIONS))) + build_tags += gcc +endif +build_tags += $(BUILD_TAGS) +build_tags := $(strip $(build_tags)) + +whitespace := +whitespace += $(whitespace) +comma := , +build_tags_comma_sep := $(subst $(whitespace),$(comma),$(build_tags)) + +# process linker flags + +ldflags = -X github.com/cosmos/cosmos-sdk/version.Name=sim \ + -X github.com/cosmos/cosmos-sdk/version.AppName=simd \ + -X github.com/cosmos/cosmos-sdk/version.Version=$(VERSION) \ + -X github.com/cosmos/cosmos-sdk/version.Commit=$(COMMIT) \ + -X "github.com/cosmos/cosmos-sdk/version.BuildTags=$(build_tags_comma_sep)" + +# DB backend selection +ifeq (cleveldb,$(findstring cleveldb,$(COSMOS_BUILD_OPTIONS))) + ldflags += -X github.com/cosmos/cosmos-sdk/types.DBBackend=cleveldb +endif +ifeq (badgerdb,$(findstring badgerdb,$(COSMOS_BUILD_OPTIONS))) + ldflags += -X github.com/cosmos/cosmos-sdk/types.DBBackend=badgerdb +endif +# handle rocksdb +ifeq (rocksdb,$(findstring rocksdb,$(COSMOS_BUILD_OPTIONS))) + CGO_ENABLED=1 + BUILD_TAGS += rocksdb + ldflags += -X github.com/cosmos/cosmos-sdk/types.DBBackend=rocksdb +endif +# handle boltdb +ifeq (boltdb,$(findstring boltdb,$(COSMOS_BUILD_OPTIONS))) + BUILD_TAGS += boltdb + ldflags += -X github.com/cosmos/cosmos-sdk/types.DBBackend=boltdb +endif + +ifeq (,$(findstring nostrip,$(COSMOS_BUILD_OPTIONS))) + ldflags += -w -s +endif +ldflags += $(LDFLAGS) +ldflags := $(strip $(ldflags)) +BUILD_FLAGS := -tags "$(build_tags)" -ldflags '$(ldflags)' +# check for nostrip option +ifeq (,$(findstring nostrip,$(COSMOS_BUILD_OPTIONS))) + BUILD_FLAGS += -trimpath +endif + +#? all: Run tools build lint test +all: build lint test + +# The below include contains the tools and runsim targets. +include contrib/devtools/Makefile + +############################################################################### +### Build ### +############################################################################### + +BUILD_TARGETS := build install + +#? tidy-all: Run go mod tidy for all modules +tidy-all: + ./scripts/go-mod-tidy-all.sh + +#? build: Build simapp and build_test_matrix +build: BUILD_ARGS=-o $(BUILDDIR)/ + +#? build-linux: Build simapp and build_test_matrix for GOOS=linux GOARCH=amd64 +build-linux: + GOOS=linux GOARCH=amd64 LEDGER_ENABLED=false $(MAKE) build + +$(BUILD_TARGETS): go.sum $(BUILDDIR)/ + cd simapp && go $@ -mod=readonly $(BUILD_FLAGS) $(BUILD_ARGS) ./... + +$(BUILDDIR)/: + mkdir -p $(BUILDDIR)/ + +.PHONY: build build-linux + +#? distclean: Run `make clean` +distclean: clean + +#? clean: Clean some auto generated directories +clean: + rm -rf \ + $(BUILDDIR)/ \ + artifacts/ \ + tmp-swagger-gen/ + +.PHONY: distclean clean + +#? build-docker-wasm: Build wasm simapp with specified tag. +build-docker-wasm: + ./scripts/build-wasm-simapp-docker.sh $(tag) + +build-docker-local: + docker build -t ghcr.io/cosmos/ibc-go-simd:local --build-arg IBC_GO_VERSION=local . + +.PHONY: build-docker-wasm + +############################################################################### +### Tools & Dependencies ### +############################################################################### + +go.sum: go.mod + echo "Ensure dependencies have not been modified ..." >&2 + go mod verify + go mod tidy + +#? python-install-deps: Install python dependencies +python-install-deps: + @echo "Installing python dependencies..." + @pip3 install --upgrade pip + @pip3 install -r requirements.txt + +############################################################################### +### Documentation ### +############################################################################### + +#? godocs: Generate go documentation +godocs: + @echo "--> Wait a few seconds and visit http://localhost:6060/pkg/github.com/cosmos/cosmos-sdk/types" + godoc -http=:6060 + +#? build-docs: Build documentation +build-docs: + @cd docs && npm ci && npm run build + +#? serve-docs: Run docs server +serve-docs: + @cd docs && npm run serve + +# If the DOCS_VERSION variable is not set, display an error message and exit +ifndef DOCS_VERSION +#? tag-docs-version: Tag the docs version +tag-docs-version: + @echo "Error: DOCS_VERSION is not set. Use 'make tag-docs-version DOCS_VERSION=' to set it. For example: 'make tag-docs-version DOCS_VERSION=v8.0.x'" + @exit 1 +else +tag-docs-version: + @cd docs && npm run docusaurus docs:version $(DOCS_VERSION) +endif + +check-docs-links: + @command -v lychee >/dev/null 2>&1 || { echo "ERROR: lychee is not installed (https://lychee.cli.rs/installation/)" >&2; exit 1; } + @echo "Checking links in documentation..." + @lychee --root-dir $(CURDIR)/docs/docs \ + --cache \ + --cache-exclude-status 429 \ + --max-cache-age 1w \ + --retry-wait-time 30 \ + --max-retries 25 \ + --max-concurrency 25 \ + --remap '($(CURDIR)/docs)(/docs/)(architecture/|events/)([^#]+?)(#[^#]+)?$$ $$1/$$3/$$4.md' \ + './docs/docs' + +lint-docs: + @command -v markdownlint-cli2 >/dev/null 2>&1 || { echo "ERROR: markdownlint-cli2 is not installed (https://github.com/DavidAnson/markdownlint-cli2#install)" >&2; exit 1; } + @echo "Linting documentation..." + @markdownlint-cli2 ./docs/docs/**/*.md + +.PHONY: build-docs serve-docs tag-docs-version + +############################################################################### +### Tests & Simulation ### +############################################################################### + +# make init-simapp initializes a single local node network +# it is useful for testing and development +# Usage: make install && make init-simapp && simd start +# Warning: make init-simapp will remove all data in simapp home directory +#? init-simapp: Run scripts/init-simapp.sh +init-simapp: + ./scripts/init-simapp.sh + +#? test: Run make test-unit +test: test-unit + +#? test-all: Run all test +test-all: test-unit test-ledger-mock test-race test-cover + +TEST_PACKAGES=./... +TEST_TARGETS := test-unit test-unit-amino test-unit-proto test-ledger-mock test-race test-ledger test-race + +# Test runs-specific rules. To add a new test target, just add +# a new rule, customise ARGS or TEST_PACKAGES ad libitum, and +# append the new rule to the TEST_TARGETS list. +test-unit: ARGS=-tags='cgo ledger test_ledger_mock test_e2e' +test-unit-amino: ARGS=-tags='ledger test_ledger_mock test_amino' +test-ledger: ARGS=-tags='cgo ledger' +test-ledger-mock: ARGS=-tags='ledger test_ledger_mock' +test-race: ARGS=-race -tags='cgo ledger test_ledger_mock' +test-race: TEST_PACKAGES=$(PACKAGES_NOSIMULATION) +$(TEST_TARGETS): run-tests + +# check-* compiles and collects tests without running them +# note: go test -c doesn't support multiple packages yet (https://github.com/golang/go/issues/15513) +CHECK_TEST_TARGETS := check-test-unit check-test-unit-amino +check-test-unit: ARGS=-tags='cgo ledger test_ledger_mock' +check-test-unit-amino: ARGS=-tags='ledger test_ledger_mock test_amino' +$(CHECK_TEST_TARGETS): EXTRA_ARGS=-run=none +$(CHECK_TEST_TARGETS): run-tests + +ARGS += -tags "$(test_tags)" +#? run-tests: Runs the go test command for all modules +run-tests: + @ARGS="$(ARGS)" TEST_PACKAGES=$(TEST_PACKAGES) EXTRA_ARGS="$(EXTRA_ARGS)" python3 ./scripts/go-test-all.py + +.PHONY: run-tests test test-all $(TEST_TARGETS) + +#? test-sim-nondeterminism: Run non-determinism test for simapp +test-sim-nondeterminism: + @echo "Running non-determinism test..." + @go test -mod=readonly $(SIMAPP) -run TestAppStateDeterminism -Enabled=true \ + -NumBlocks=100 -BlockSize=200 -Commit=true -Period=0 -v -timeout 24h + +test-sim-custom-genesis-fast: + @echo "Running custom genesis simulation..." + @echo "By default, ${HOME}/.gaiad/config/genesis.json will be used." + @go test -mod=readonly $(SIMAPP) -run TestFullAppSimulation -Genesis=${HOME}/.gaiad/config/genesis.json \ + -Enabled=true -NumBlocks=100 -BlockSize=200 -Commit=true -Seed=99 -Period=5 -v -timeout 24h + +test-sim-import-export: runsim + @echo "Running application import/export simulation. This may take several minutes..." + @$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(SIMAPP) -ExitOnFail 50 5 TestAppImportExport + +test-sim-after-import: runsim + @echo "Running application simulation-after-import. This may take several minutes..." + @$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(SIMAPP) -ExitOnFail 50 5 TestAppSimulationAfterImport + +test-sim-custom-genesis-multi-seed: runsim + @echo "Running multi-seed custom genesis simulation..." + @echo "By default, ${HOME}/.gaiad/config/genesis.json will be used." + @$(BINDIR)/runsim -Genesis=${HOME}/.gaiad/config/genesis.json -SimAppPkg=$(SIMAPP) -ExitOnFail 400 5 TestFullAppSimulation + +test-sim-multi-seed-long: runsim + @echo "Running long multi-seed application simulation. This may take awhile!" + @$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(SIMAPP) -ExitOnFail 500 50 TestFullAppSimulation + +test-sim-multi-seed-short: runsim + @echo "Running short multi-seed application simulation. This may take awhile!" + @$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(SIMAPP) -ExitOnFail 50 10 TestFullAppSimulation + +test-sim-benchmark-invariants: + @echo "Running simulation invariant benchmarks..." + @go test -mod=readonly $(SIMAPP) -benchmem -bench=BenchmarkInvariants -run=^$ \ + -Enabled=true -NumBlocks=1000 -BlockSize=200 \ + -Period=1 -Commit=true -Seed=57 -v -timeout 24h + +.PHONY: \ +test-sim-nondeterminism \ +test-sim-custom-genesis-fast \ +test-sim-import-export \ +test-sim-after-import \ +test-sim-custom-genesis-multi-seed \ +test-sim-multi-seed-short \ +test-sim-multi-seed-long \ +test-sim-benchmark-invariants + +SIM_NUM_BLOCKS ?= 500 +SIM_BLOCK_SIZE ?= 200 +SIM_COMMIT ?= true + +#? test-sim-benchmark: Run application benchmark +test-sim-benchmark: + @echo "Running application benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!" + @go test -mod=readonly -benchmem -run=^$$ $(SIMAPP) -bench ^BenchmarkFullAppSimulation$$ \ + -Enabled=true -NumBlocks=$(SIM_NUM_BLOCKS) -BlockSize=$(SIM_BLOCK_SIZE) -Commit=$(SIM_COMMIT) -timeout 24h + +#? test-sim-profile: Run application benchmark and output cpuprofile, memprofile +test-sim-profile: + @echo "Running application benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!" + @go test -mod=readonly -benchmem -run=^$$ $(SIMAPP) -bench ^BenchmarkFullAppSimulation$$ \ + -Enabled=true -NumBlocks=$(SIM_NUM_BLOCKS) -BlockSize=$(SIM_BLOCK_SIZE) -Commit=$(SIM_COMMIT) -timeout 24h -cpuprofile cpu.out -memprofile mem.out + +.PHONY: test-sim-profile test-sim-benchmark + +#? test-cover: Run contrib/test_cover.sh +test-cover: + @export VERSION=$(VERSION); bash -x contrib/test_cover.sh +.PHONY: test-cover + +#? benchmark: Run benchmark tests +benchmark: + @go test -mod=readonly -bench=. $(PACKAGES_NOSIMULATION) +.PHONY: benchmark + +############################################################################### +### Linting ### +############################################################################### + +#? setup-pre-commit: Set pre commit git hook +setup-pre-commit: + @cp .git/hooks/pre-commit .git/hooks/pre-commit.bak 2>/dev/null || true + @echo "Installing pre-commit hook..." + @ln -sf ../../scripts/hooks/pre-commit.sh .git/hooks/pre-commit + @echo "Pre-commit hook was installed at .git/hooks/pre-commit" + +#? lint: Run golangci-lint on all modules +lint: + @echo "--> Running linter" + @./scripts/go-lint-all.sh --timeout=15m + +#? lint-fix: Run golangci-lint and fix issues on all modules +lint-fix: + @echo "--> Running linter" + @./scripts/go-lint-all.sh --fix + +#? format: Run gofumpt and misspell +format: + find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "./docs/client/statik/statik.go" -not -path "./tests/mocks/*" -not -name '*.pb.go' -not -name '*.pb.gw.go' | xargs gofumpt -w + find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" -not -path "./docs/client/statik/statik.go" -not -path "./tests/mocks/*" -not -name '*.pb.go' -not -name '*.pb.gw.go' | xargs misspell -w +.PHONY: format + +.PHONY: lint lint-fix format + +############################################################################### +### Protobuf ### +############################################################################### + +protoVer=0.14.0 +protoImageName=ghcr.io/cosmos/proto-builder:$(protoVer) +protoImage=$(DOCKER) run --rm -v $(CURDIR):/workspace --workdir /workspace $(protoImageName) + +#? proto-all: Format, lint and generate Protobuf files +proto-all: proto-format proto-lint proto-gen + +#? proto-gen: Generate Protobuf files +proto-gen: + @echo "Generating Protobuf files" + @$(protoImage) sh ./scripts/protocgen.sh + +#? proto-swagger-gen: Generate Protobuf Swagger +proto-swagger-gen: + @echo "Generating Protobuf Swagger" + @$(protoImage) sh ./scripts/protoc-swagger-gen.sh + +#? proto-format: Format Protobuf files +proto-format: + @$(protoImage) find ./ -name "*.proto" -exec clang-format -i {} \; + +#? proto-lint: Lint Protobuf files +proto-lint: + @$(protoImage) buf lint --error-format=json + +#? proto-check-breaking: Check if Protobuf file contains breaking changes +proto-check-breaking: + @$(protoImage) buf breaking --against $(HTTPS_GIT)#branch=main + +#? proto-update-deps: Update Protobuf dependencies +proto-update-deps: + @echo "Updating Protobuf dependencies" + $(DOCKER) run --rm -v $(CURDIR)/proto:/workspace --workdir /workspace $(protoImageName) buf mod update + +.PHONY: proto-all proto-gen proto-gen-any proto-swagger-gen proto-format proto-lint proto-check-breaking proto-update-deps + +#? help: Get more info on make commands +help: Makefile + @echo " Choose a command run in "$(PROJECT_NAME)":" + @sed -n 's/^#?//p' $< | column -t -s ':' | sort | sed -e 's/^/ /' +.PHONY: help diff --git a/README.md b/README.md new file mode 100644 index 0000000..eabc225 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +

+ Mukan IBC +

+ +

+ The sovereign inter-blockchain communication layer of the Mukan Network, forked from IBC-go. +

+ +## Overview + +**Mukan IBC** is the Inter-Blockchain Communication (IBC) protocol implementation for the Mukan Network. It is a permanent hard-fork of [IBC-go v10.4.0](https://github.com/cosmos/ibc-go), updated to reference the sovereign Mukan Network stack. + +### Key Differences from IBC-go + +- All upstream dependencies updated to reference `git.cw.tr/mukan-network/...` instead of original Cosmos GitHub paths. +- Future: Cross-chain UMC transfers and PoJ-verified IBC channel authentication. + +## Integration + +Mukan IBC is used by [Mukan Core](https://git.cw.tr/mukan-network/mukan-core) to enable cross-chain communication. + +```go +require git.cw.tr/mukan-network/mukan-ibc v10.4.0-mukan.1 +``` + +## License + +Licensed under the **GNU General Public License v3.0 (GPLv3)**. + +*Original IBC-go components remain under their respective Apache 2.0 licenses where applicable.* diff --git a/RELEASES.md b/RELEASES.md new file mode 100644 index 0000000..8cfcaf1 --- /dev/null +++ b/RELEASES.md @@ -0,0 +1,163 @@ +# Releases + +IBC-Go follows [semantic versioning](https://semver.org), but with the following deviations: + +- A state-machine breaking change will result in an increase of the minor version Y (x.Y.z | x > 0). +- An API breaking change will result in an increase of the major number (X.y.z | x > 0). Please note that these changes **will be backwards compatible** (as opposed to canonical semantic versioning; read [Backwards compatibility](#backwards-compatibility) for a detailed explanation). + +This is visually explained in the following decision tree: + +

+ Releases decision tree +

+ +When bumping the dependencies of [Cosmos SDK](https://github.com/cosmos/cosmos-sdk) and [CometBFT](https://github.com/cometbft/cometbft) we will only treat patch releases as non state-machine breaking. + +## Backwards compatibility + +[ibc-go](https://github.com/cosmos/ibc-go) and the [IBC protocol specification](https://github.com/cosmos/ibc) maintain different versions. Furthermore, ibc-go serves several different user groups (chains, IBC app developers, relayers, IBC light client developers). Each of these groups has different expectations of what *backwards compatible* means. It simply isn't possible to categorize a change as backwards or non backwards compatible for all user groups. We are primarily interested in when our API breaks and when changes are state machine breaking (thus requiring a coordinated upgrade). This is scoping the meaning of ibc-go to that of those interacting with the code (IBC app developers, relayers, IBC light client developers), not chains using IBC to communicate (that should be encapsulated by the IBC protocol specification versioning). + +To summarize: **All our ibc-go releases allow chains to communicate successfully with any chain running any version of our code**. That is to say, we are still using IBC protocol specification v1.0 (v10 will also include support for the IBC protocol specification v2.0) + +## Release cycle + +IBC-Go follows a traditional release cycle involving an alpha, beta, and rc (release candidate) releases before finalizing a new version. As ibc-go works in a non-traditional area, we apply our own interpretation to each release type. We reserve the right to make both go API breaking changes and state machine breaking changes throughout the entire release cycle. The stable release guarantees do not go into affect until a final release is performed. + +It is never advisable to use a non-final release in production. + +### Alpha + +Alpha releases are intended to make available new features as soon as they are functional. No correctness guarantees are made and alpha releases **may** contain serious security vulnerabilities, bugs, and lack of user tooling, so long as they don't affect the core functionality. + +Initial users of alpha releases are expected to be advanced, patient, and capable of handling unusual errors. Very basic integration testing will be performed by the ibc-go development team before alpha releases. + +An internal audit is typically performed before the alpha release allowing the development team to gauge the maturity and stability of changes included in the next release. + +### Beta + +Beta releases are intended to signal design stability. While the go API is still subject to change, the core design of the new features should not be. Developers integrating the new features should expect to handle breaking changes when upgrading to RC's. + +Beta releases should not be made with known bugs or security vulnerabilities. Beta releases should focus on ironing out remaining bugs and filling out the UX functionality required by a final release. Beta releases should have a clearly defined scope of the features that will be included in the release. Only highly requested feature additions should be acted upon in this phase. + +When the development team has determined a release is ready to enter the RC phase, a final security audit should be performed. The security audit should be limited to looking for bugs and security vulnerabilities. Code improvements may be noted, but they should not be acted upon unless highly desirable. + +### RC + +RC's are release candidates. Final releases should contain little to no changes in comparison to the latest RC. Changes included in between RC releases should be limited to: + +- Improved testing +- UX additions +- Bug fixes +- Highly requested changes by the community + +A release should not be finalized until the development team and the external community have done sufficient integration tests on the targeted release. + +## Stable Release Policy + +The beginning of a new major release series is marked by the release of a new major version. A major release series is comprised of all minor and patch releases made under the same major version number. The series continues to receive bug fixes (released as minor or patch releases) until it reaches end of life. The date when a major release series reaches end of life is determined by one of the two following methods: + +- If the next major release is made within the first 6 months, then the end of life date of the major release series is 18 months after its initial release. +- If the next major release is made 6 months after the initial release, then the end of life date of the major release series is 12 months after the release date of the next major release. + +For example, if the current major release series is v1 and was released on January 1st, 2022, then v1 will be supported at least until January 1st, 2023. If v2 is published on August 1st 2022, then v1's end of life will be March 1st, 2023. + +Only the following major release series have a stable release status. All missing minor release versions have been discontinued. + +We reserve the right to drop support for releases if they are deemed unused (for example, because the Cosmos SDK version they depend on is not used or has been deprecated). Likewise, we also reserve the right to drop support for pre v1.0 versions of modules if we deem them unnecessary to maintain (we are only looking to give support for stable major releases). + +### ibc-go + +|Release|End of Life Date| +|-------|----------------| +|`v7.10.x`|March 17, 2025| +|`v8.7.x`|May 10, 2025| + +### Callbacks middleware + +|Release|End of Life Date| +|-------|----------------| +|`v0.2.x+ibc-go-v7.3.x`|March 17, 2025| +|`v0.2.x+ibc-go-v8.0.x`|May 10, 2025| + +### `08-wasm` light client proxy module + +|Release|End of Life Date| +|-------|----------------| +|`v0.3.x+ibc-go-v7.4.x-wasmvm-v1.5.x`|March 17, 2025| +|`v0.4.x+ibc-go-v8.4.x-wasmvm-v2.0.x`|May 10, 2025| + +### What pull requests will be included in stable patch-releases? + +Pull requests that fix bugs and add features that fall in the following categories: + +- **Severe regressions**. +- Bugs that may cause **client applications** to be **largely unusable**. +- Bugs that may cause **state corruption or data loss**. +- Bugs that may directly or indirectly cause a **security vulnerability**. +- Non-breaking features that are strongly requested by the community. +- Non-breaking CLI improvements that are strongly requested by the community. + +### What pull requests will NOT be automatically included in stable patch-releases? + +As rule of thumb, the following changes will **NOT** be automatically accepted into stable point-releases: + +- **State machine changes**, unless the previous behaviour would result in a consensus halt. +- **Protobuf-breaking changes**. +- **Client-breaking changes**, i.e. changes that prevent gRPC, HTTP and RPC clients to continue interacting with the node without any change. +- **API-breaking changes**, i.e. changes that prevent client applications to *build without modifications* to the client application's source code. +- **CLI-breaking changes**, i.e. changes that require usage changes for CLI users. + +## Deprecation notice + +Code that is marked as deprecated in a release will be removed 2 major releases afterwards. For example: deprecation notice is added in v8.3.0, then code will be deleted in v10.0.0. + +## Version matrix + +### ibc-go + +Versions of Golang, Cosmos SDK and CometBFT used by ibc-go in the currently active releases: + +| Go | ibc-go | Cosmos SDK | Tendermint/CometBFT | +|----|--------|------------|---------------------| +| 1.19 | v7.10.0 | v0.47.13 | v0.37.5 | +| 1.21 | v8.7.0 | v0.50.9 | v0.38.11 | + +### Callbacks middleware + +Versions of Golang, ibc-go, Cosmos SDK and CometBFT used by callbacks middleware in the currently active releases: + +| Go | callbacks | ibc-go | Cosmos SDK | Tendermint/CometBFT | +|----|-----------|--------|------------|---------------------| +| 1.19 | v0.2.0+ibc-go-v7.3 | v7.3.0 | v0.47.5 | v0.37.2 | +| 1.21 | v0.2.0+ibc-go-v8.0 | v8.0.0 | v0.50.1 | v0.38.0 | + +### `08-wasm` light client proxy module + +Versions of Golang, ibc-go, Cosmos SDK and CometBFT used by `08-wasm` module in the currently active releases: + +| Go | 08-wasm | ibc-go | Cosmos SDK | Tendermint/CometBFT | +|----|-----------|--------|------------|---------------------| +| 1.19 | v0.3.1+ibc-go-v7.4-wasmvm-v1.5 | v7.4.0 | v0.47.8 | v0.37.4 | +| 1.21 | v0.4.1+ibc-go-v8.4-wasmvm-v2.0 | v8.4.0 | v0.50.7 | v0.38.9 | + +## Graphics + +The decision tree above was generated with the following code: + +```text +%%{init: + {'theme': 'default', + 'themeVariables': + {'fontFamily': 'verdana', 'fontSize': '13px'} + } +}%% +flowchart TD + A(Change):::c --> B{API breaking?} + B:::c --> |Yes| C(Increase major version):::c + B:::c --> |No| D{state-machine breaking?} + D:::c --> |Yes| G(Increase minor version):::c + D:::c --> |No| H(Increase patch version):::c + classDef c fill:#eee,stroke:#aaa +``` + +using [Mermaid](https://mermaid-js.github.io)'s [live editor](https://mermaid.live). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..3549eb4 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,16 @@ +# How to Report a Security Bug + +If you believe you have found a security vulnerability in the Interchain Stack, you can report it to our primary vulnerability disclosure channel, the [Cosmos HackerOne Bug Bounty program](https://hackerone.com/cosmos?type=team). + + +If you prefer to report an issue via email, you may send a bug report to [security@interchain.io](mailto:security@interchain.io) with the issue details, reproduction, impact, and other information. Please submit only one unique email thread per vulnerability. Any issues reported via email are ineligible for bounty rewards. + +Artifacts from an email report are saved at the time the email is triaged. Please note: our team is not able to monitor dynamic content (e.g. a Google Docs link that is edited after receipt) throughout the lifecycle of a report. If you would like to share additional information or modify previous information, please include it in an additional reply as an additional attachment. + +Please DO NOT file a public issue in this repository to report a security vulnerability. + +# Coordinated Vulnerability Disclosure Policy and Safe Harbor + +For the most up-to-date version of the policies that govern vulnerability disclosure, please consult the [HackerOne program page](https://hackerone.com/cosmos?type=team&view_policy=true). + +The policy hosted on HackerOne is the official Coordinated Vulnerability Disclosure policy and Safe Harbor for the Interchain Stack, and the teams and infrastructure it supports, and it supersedes previous security policies that have been used in the past by individual teams and projects with targets in scope of the program. diff --git a/buf.work.yaml b/buf.work.yaml new file mode 100644 index 0000000..8d2415b --- /dev/null +++ b/buf.work.yaml @@ -0,0 +1,8 @@ +# Generated by "buf config migrate-v1beta1". Edit as necessary, and +# remove this comment when you're finished. +# +# This workspace file points to the roots found in your +# previous "buf.yaml" configuration. +version: v1 +directories: + - proto diff --git a/cmd/build_test_matrix/main.go b/cmd/build_test_matrix/main.go new file mode 100644 index 0000000..89d0fe7 --- /dev/null +++ b/cmd/build_test_matrix/main.go @@ -0,0 +1,195 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "go/ast" + "go/parser" + "go/token" + "io/fs" + "os" + "path/filepath" + "slices" + "sort" + "strings" +) + +const ( + testNamePrefix = "Test" + testFileNameSuffix = "_test.go" + e2eTestDirectory = "e2e" + // testEntryPointEnv specifies a single test function to run if provided. + testEntryPointEnv = "TEST_ENTRYPOINT" + // testExclusionsEnv is a comma separated list of test function names that will not be included + // in the results of this script. + testExclusionsEnv = "TEST_EXCLUSIONS" + // testNameEnv if provided returns a single test entry so that only one test is actually run. + testNameEnv = "TEST_NAME" +) + +// GithubActionTestMatrix represents +type GithubActionTestMatrix struct { + Include []TestSuitePair `json:"include"` +} + +type TestSuitePair struct { + Test string `json:"test"` + EntryPoint string `json:"entrypoint"` +} + +func main() { + githubActionMatrix, err := getGithubActionMatrixForTests(e2eTestDirectory, getTestToRun(), getTestEntrypointToRun(), getExcludedTestFunctions()) + if err != nil { + fmt.Printf("error generating github action json: %s", err) + os.Exit(1) + } + + ghBytes, err := json.Marshal(githubActionMatrix) + if err != nil { + fmt.Printf("error marshalling github action json: %s", err) + os.Exit(1) + } + fmt.Println(string(ghBytes)) +} + +// getTestEntrypointToRun returns the specified test function to run if present, otherwise +// it returns an empty string which will result in running all test suites. +func getTestEntrypointToRun() string { + testSuite, ok := os.LookupEnv(testEntryPointEnv) + if !ok { + return "" + } + return testSuite +} + +// getTestToRun returns the specified test function to run if present. +// If specified, only this test will be run. +func getTestToRun() string { + testName, ok := os.LookupEnv(testNameEnv) + if !ok { + return "" + } + return testName +} + +// getExcludedTestFunctions returns a list of test functions that we don't want to run. +func getExcludedTestFunctions() []string { + exclusions, ok := os.LookupEnv(testExclusionsEnv) + if !ok { + return nil + } + return strings.Split(exclusions, ",") +} + +// getGithubActionMatrixForTests returns a json string representing the contents that should go in the matrix +// field in a github action workflow. This string can be used with `fromJSON(str)` to dynamically build +// the workflow matrix to include all E2E tests under the e2eRootDirectory directory. +func getGithubActionMatrixForTests(e2eRootDirectory, testName string, suite string, excludedItems []string) (GithubActionTestMatrix, error) { + testSuiteMapping := map[string][]string{} + fset := token.NewFileSet() + err := filepath.Walk(e2eRootDirectory, func(path string, info fs.FileInfo, err error) error { + if err != nil { + return fmt.Errorf("error walking e2e directory: %s", err) + } + + // only look at test files + if !strings.HasSuffix(path, testFileNameSuffix) { + return nil + } + + f, err := parser.ParseFile(fset, path, nil, 0) + if err != nil { + return fmt.Errorf("failed parsing file: %s", err) + } + + suiteNameForFile, testCases, err := extractSuiteAndTestNames(f) + if err != nil { + return nil + } + + if testName != "" && slices.Contains(testCases, testName) { + testCases = []string{testName} + } + + if slices.Contains(excludedItems, suiteNameForFile) { + return nil + } + + if suite == "" || suiteNameForFile == suite { + testSuiteMapping[suiteNameForFile] = testCases + } + + return nil + }) + if err != nil { + return GithubActionTestMatrix{}, err + } + + gh := GithubActionTestMatrix{ + Include: []TestSuitePair{}, + } + + for testSuiteName, testCases := range testSuiteMapping { + for _, testCaseName := range testCases { + gh.Include = append(gh.Include, TestSuitePair{ + Test: testCaseName, + EntryPoint: testSuiteName, + }) + } + } + + if len(gh.Include) == 0 { + return GithubActionTestMatrix{}, errors.New("no test cases found") + } + + // Sort the test cases by name so that the order is consistent. + sort.SliceStable(gh.Include, func(i, j int) bool { + return gh.Include[i].Test < gh.Include[j].Test + }) + + if testName != "" && len(gh.Include) != 1 { + return GithubActionTestMatrix{}, fmt.Errorf("expected exactly 1 test in the output matrix but got %d", len(gh.Include)) + } + + return gh, nil +} + +// extractSuiteAndTestNames extracts the name of the test suite function as well +// as all tests associated with it in the same file. +func extractSuiteAndTestNames(file *ast.File) (string, []string, error) { + var suiteNameForFile string + var testCases []string + + for _, d := range file.Decls { + if f, ok := d.(*ast.FuncDecl); ok { + functionName := f.Name.Name + if isTestSuiteMethod(f) { + if suiteNameForFile != "" { + return "", nil, fmt.Errorf("found a second test function: %s when %s was already found", f.Name.Name, suiteNameForFile) + } + suiteNameForFile = functionName + continue + } + if isTestFunction(f) { + testCases = append(testCases, functionName) + } + } + } + if suiteNameForFile == "" { + return "", nil, fmt.Errorf("file %s had no test suite test case", file.Name.Name) + } + return suiteNameForFile, testCases, nil +} + +// isTestSuiteMethod returns true if the function is a test suite function. +// e.g. func TestFeeMiddlewareTestSuite(t *testing.T) { ... } +func isTestSuiteMethod(f *ast.FuncDecl) bool { + return strings.HasPrefix(f.Name.Name, testNamePrefix) && len(f.Type.Params.List) == 1 +} + +// isTestFunction returns true if the function name starts with "Test" and has no parameters. +// as test suite functions do not accept a *testing.T. +func isTestFunction(f *ast.FuncDecl) bool { + return strings.HasPrefix(f.Name.Name, testNamePrefix) && len(f.Type.Params.List) == 0 +} diff --git a/cmd/build_test_matrix/main_test.go b/cmd/build_test_matrix/main_test.go new file mode 100644 index 0000000..624b620 --- /dev/null +++ b/cmd/build_test_matrix/main_test.go @@ -0,0 +1,191 @@ +package main + +import ( + "os" + "path" + "sort" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +const ( + nonTestFile = "not_test_file.go" + goTestFileNameOne = "first_go_file_test.go" + goTestFileNameTwo = "second_go_file_test.go" +) + +func TestGetGithubActionMatrixForTests(t *testing.T) { + t.Run("empty dir with no test cases fails", func(t *testing.T) { + testingDir := t.TempDir() + _, err := getGithubActionMatrixForTests(testingDir, "", "", nil) + assert.Error(t, err) + }) + + t.Run("only test functions are picked up", func(t *testing.T) { + testingDir := t.TempDir() + createFileWithTestSuiteAndTests(t, "FeeMiddlewareTestSuite", "TestA", "TestB", testingDir, goTestFileNameOne) + + gh, err := getGithubActionMatrixForTests(testingDir, "", "", nil) + assert.NoError(t, err) + + expected := GithubActionTestMatrix{ + Include: []TestSuitePair{ + { + EntryPoint: "TestFeeMiddlewareTestSuite", + Test: "TestA", + }, + { + EntryPoint: "TestFeeMiddlewareTestSuite", + Test: "TestB", + }, + }, + } + assertGithubActionTestMatricesEqual(t, expected, gh) + }) + + t.Run("all files are picked up", func(t *testing.T) { + testingDir := t.TempDir() + createFileWithTestSuiteAndTests(t, "FeeMiddlewareTestSuite", "TestA", "TestB", testingDir, goTestFileNameOne) + createFileWithTestSuiteAndTests(t, "TransferTestSuite", "TestC", "TestD", testingDir, goTestFileNameTwo) + + gh, err := getGithubActionMatrixForTests(testingDir, "", "", nil) + assert.NoError(t, err) + + expected := GithubActionTestMatrix{ + Include: []TestSuitePair{ + { + EntryPoint: "TestTransferTestSuite", + Test: "TestC", + }, + { + EntryPoint: "TestFeeMiddlewareTestSuite", + Test: "TestA", + }, + { + EntryPoint: "TestFeeMiddlewareTestSuite", + Test: "TestB", + }, + { + EntryPoint: "TestTransferTestSuite", + Test: "TestD", + }, + }, + } + + assertGithubActionTestMatricesEqual(t, expected, gh) + }) + + t.Run("single test can be specified", func(t *testing.T) { + testingDir := t.TempDir() + createFileWithTestSuiteAndTests(t, "FeeMiddlewareTestSuite", "TestA", "TestB", testingDir, goTestFileNameOne) + createFileWithTestSuiteAndTests(t, "TransferTestSuite", "TestC", "TestD", testingDir, goTestFileNameTwo) + + gh, err := getGithubActionMatrixForTests(testingDir, "TestA", "TestFeeMiddlewareTestSuite", nil) + assert.NoError(t, err) + + expected := GithubActionTestMatrix{ + Include: []TestSuitePair{ + { + EntryPoint: "TestFeeMiddlewareTestSuite", + Test: "TestA", + }, + }, + } + + assertGithubActionTestMatricesEqual(t, expected, gh) + }) + + t.Run("error if single test doesn't exist", func(t *testing.T) { + testingDir := t.TempDir() + createFileWithTestSuiteAndTests(t, "FeeMiddlewareTestSuite", "TestA", "TestB", testingDir, goTestFileNameOne) + + _, err := getGithubActionMatrixForTests(testingDir, "TestThatDoesntExist", "TestFeeMiddlewareTestSuite", nil) + assert.Error(t, err) + }) + + t.Run("non test files are skipped", func(t *testing.T) { + testingDir := t.TempDir() + createFileWithTestSuiteAndTests(t, "FeeMiddlewareTestSuite", "TestA", "TestB", testingDir, nonTestFile) + + gh, err := getGithubActionMatrixForTests(testingDir, "", "", nil) + assert.Error(t, err) + assert.Empty(t, gh.Include) + }) + + t.Run("fails when there are multiple suite runs", func(t *testing.T) { + testingDir := t.TempDir() + createFileWithTestSuiteAndTests(t, "FeeMiddlewareTestSuite", "TestA", "TestB", testingDir, nonTestFile) + + fileWithTwoSuites := `package foo +func SuiteOne(t *testing.T) { + suite.Run(t, new(FeeMiddlewareTestSuite)) +} + +func SuiteTwo(t *testing.T) { + suite.Run(t, new(FeeMiddlewareTestSuite)) +} + +type FeeMiddlewareTestSuite struct {} +` + + err := os.WriteFile(path.Join(testingDir, goTestFileNameOne), []byte(fileWithTwoSuites), os.FileMode(0o777)) + assert.NoError(t, err) + + _, err = getGithubActionMatrixForTests(testingDir, "", "", nil) + assert.Error(t, err) + }) +} + +func assertGithubActionTestMatricesEqual(t *testing.T, expected, actual GithubActionTestMatrix) { + t.Helper() + // sort by both suite and test as the order of the end result does not matter as + // all tests will be run. + sort.SliceStable(expected.Include, func(i, j int) bool { + memberI := expected.Include[i] + memberJ := expected.Include[j] + if memberI.EntryPoint == memberJ.EntryPoint { + return memberI.Test < memberJ.Test + } + return memberI.EntryPoint < memberJ.EntryPoint + }) + + sort.SliceStable(actual.Include, func(i, j int) bool { + memberI := actual.Include[i] + memberJ := actual.Include[j] + if memberI.EntryPoint == memberJ.EntryPoint { + return memberI.Test < memberJ.Test + } + return memberI.EntryPoint < memberJ.EntryPoint + }) + assert.Equal(t, expected.Include, actual.Include) +} + +func goTestFileContents(suiteName, fnName1, fnName2 string) string { + replacedSuiteName := strings.ReplaceAll(`package foo + +func TestSuiteName(t *testing.T) { + suite.Run(t, new(SuiteName)) +} + +type SuiteName struct {} + +func (s *SuiteName) fnName1() {} +func (s *SuiteName) fnName2() {} + +func (s *SuiteName) suiteHelper() {} + +func helper() {} +`, "SuiteName", suiteName) + + replacedFn1Name := strings.ReplaceAll(replacedSuiteName, "fnName1", fnName1) + return strings.ReplaceAll(replacedFn1Name, "fnName2", fnName2) +} + +func createFileWithTestSuiteAndTests(t *testing.T, suiteName, fn1Name, fn2Name, dir, filename string) { + t.Helper() + goFileContents := goTestFileContents(suiteName, fn1Name, fn2Name) + err := os.WriteFile(path.Join(dir, filename), []byte(goFileContents), os.FileMode(0o777)) + assert.NoError(t, err) +} diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..f301b7c --- /dev/null +++ b/codecov.yml @@ -0,0 +1,24 @@ +coverage: + precision: 2 + range: + - 70.0 + - 100.0 + round: down + status: + project: + default: + target: auto + threshold: 0% + base: auto +comment: + require_changes: "coverage_drop OR uncovered_patch" # Only comment when coverage drops or there is uncovered code in the commit +ignore: +- "**/*.pb.go" +- "**/*.pb.gw.go" +- "docs" +- "simapp" +- "testing" +- "modules/light-clients/08-wasm/testing" +- "scripts" +- "contrib" +- "cmd" diff --git a/contrib/devtools/Makefile b/contrib/devtools/Makefile new file mode 100644 index 0000000..740edca --- /dev/null +++ b/contrib/devtools/Makefile @@ -0,0 +1,76 @@ +### +# Find OS and Go environment +# GO contains the Go binary +# FS contains the OS file separator +### +ifeq ($(OS),Windows_NT) + GO := $(shell where go.exe 2> NUL) + FS := "\\" +else + GO := $(shell command -v go 2> /dev/null) + FS := "/" +endif + +ifeq ($(GO),) + $(error could not find go. Is it in PATH? $(GO)) +endif + +############################################################################### +### Functions ### +############################################################################### + +go_get = $(if $(findstring Windows_NT,$(OS)),\ +IF NOT EXIST $(GITHUBDIR)$(FS)$(1)$(FS) ( mkdir $(GITHUBDIR)$(FS)$(1) ) else (cd .) &\ +IF NOT EXIST $(GITHUBDIR)$(FS)$(1)$(FS)$(2)$(FS) ( cd $(GITHUBDIR)$(FS)$(1) && git clone https://github.com/$(1)/$(2) ) else (cd .) &\ +,\ +mkdir -p $(GITHUBDIR)$(FS)$(1) &&\ +(test ! -d $(GITHUBDIR)$(FS)$(1)$(FS)$(2) && cd $(GITHUBDIR)$(FS)$(1) && git clone https://github.com/$(1)/$(2)) || true &&\ +)\ +cd $(GITHUBDIR)$(FS)$(1)$(FS)$(2) && git fetch origin && git checkout -q $(3) + +mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) +mkfile_dir := $(shell cd $(shell dirname $(mkfile_path)); pwd) + + +############################################################################### +### Tools ### +############################################################################### + +PREFIX ?= /usr/local +BIN ?= $(PREFIX)/bin +UNAME_S ?= $(shell uname -s) +UNAME_M ?= $(shell uname -m) + +GOPATH ?= $(shell $(GO) env GOPATH) +GITHUBDIR := $(GOPATH)$(FS)src$(FS)github.com + +BUF_VERSION ?= 0.11.0 + +TOOLS_DESTDIR ?= $(GOPATH)/bin +STATIK = $(TOOLS_DESTDIR)/statik +RUNSIM = $(TOOLS_DESTDIR)/runsim + +tools: tools-stamp +tools-stamp: statik runsim + # Create dummy file to satisfy dependency and avoid + # rebuilding when this Makefile target is hit twice + # in a row. + touch $@ + +# Install the runsim binary +statik: $(STATIK) +$(STATIK): + @echo "Installing statik..." + @go install github.com/rakyll/statik@v0.1.6 + +# Install the runsim binary +runsim: $(RUNSIM) +$(RUNSIM): + @echo "Installing runsim..." + @go install github.com/cosmos/tools/cmd/runsim@v1.0.0 + +tools-clean: + rm -f $(STATIK) $(GOLANGCI_LINT) $(RUNSIM) + rm -f tools-stamp + +.PHONY: tools-clean statik runsim diff --git a/contrib/test_cover.sh b/contrib/test_cover.sh new file mode 100644 index 0000000..a33d842 --- /dev/null +++ b/contrib/test_cover.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -e + +PKGS=$(go list ./... | grep -v '/simapp') + +set -e +echo "mode: atomic" > coverage.txt +for pkg in ${PKGS[@]}; do + go test -v -timeout 30m -race -coverprofile=profile.out -covermode=atomic -tags='ledger test_ledger_mock' "$pkg" + if [ -f profile.out ]; then + tail -n +2 profile.out >> coverage.txt; + rm profile.out + fi +done diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..67e47a5 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,20 @@ +# Dependencies +/node_modules + +# Production +/build + +# Generated files +.docusaurus +.cache-loader + +# Misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/docs/.markdownlint-cli2.jsonc b/docs/.markdownlint-cli2.jsonc new file mode 100644 index 0000000..dbcf321 --- /dev/null +++ b/docs/.markdownlint-cli2.jsonc @@ -0,0 +1,7 @@ +// This file is used by markdownlint-cli2 to configure the linting process +// in conjunction with .markdownlint.jsonc. +{ + "ignores": [ + "node_modules/**" + ] +} diff --git a/docs/.markdownlint.jsonc b/docs/.markdownlint.jsonc new file mode 100644 index 0000000..d0065cb --- /dev/null +++ b/docs/.markdownlint.jsonc @@ -0,0 +1,27 @@ +{ + "default": true, + "MD003": { + "style": "atx" + }, // https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md003---heading-style + "MD004": { + "style": "dash" + }, // https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md004---unordered-list-style + "MD007": { + "indent": 4 + }, // https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md007---unordered-list-indentation + "MD009": false, // https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md009---trailing-spaces + "MD010": false, // https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md010---hard-tabs + "MD013": false, // https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md013---line-length + "MD024": false, // https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md024---multiple-headings-with-the-same-content + "MD025": false, // https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md025---multiple-top-level-headings-in-the-same-document + "MD029": false, // https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md029---ordered-list-item-prefix + "MD033": false, // https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md033---inline-html + "MD036": false, // https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md036---emphasis-used-instead-of-a-heading + "MD041": false, // https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md041---first-line-in-a-file-should-be-a-top-level-heading + "MD049": { + "style": "asterisk" + }, // https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md049---emphasis-style-should-be-consistent + "MD050": { + "style": "asterisk" + } // https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md050---strong-style-should-be-consistent +} diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..c5a9404 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,289 @@ +# IBC-Go Documentation + +Welcome to the IBC-Go documentation! This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. + +## Table of Contents + +- [IBC-Go Documentation](#ibc-go-documentation) + - [Table of Contents](#table-of-contents) + - [Configuration](#configuration) + - [Local Development and Deployment](#local-development-and-deployment) + - [Installation](#installation) + - [Local Development](#local-development) + - [Build](#build) + - [Serve](#serve) + - [Updating the Documentation](#updating-the-documentation) + - [Best practices](#best-practices) + - [File and Directory Naming Conventions](#file-and-directory-naming-conventions) + - [Code Blocks](#code-blocks) + - [Links](#links) + - [Multi-Documentation Linking](#multi-documentation-linking) + - [Static Assets](#static-assets) + - [Raw Assets](#raw-assets) + - [Technical writing course](#technical-writing-course) + - [Versioning](#versioning) + - [Terminology](#terminology) + - [Overview](#overview) + - [Tagging a new version](#tagging-a-new-version) + - [Adding a new version](#adding-a-new-version) + - [Updating an existing version](#updating-an-existing-version) + - [Deleting a version](#deleting-a-version) + +## Configuration + +Docusaurus configuration file is located at `./docusaurus.config.js`. This file contains the configuration for the sidebar, navbar, footer, and other settings. Sidebars are created in `./sidebars.js`. + +## Local Development and Deployment + +### Installation + +```bash +npm install +``` + +### Local Development + +```bash +npm start +``` + +This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. However, in the local development environment, some plugins like `@docusaurus/plugin-client-redirects`, will not work at all. This is why the landing page is an error page in the local development environment, and why you have to click on the correct docs version to see the documentation. This is not the case in the production environment. To view the production environment, you must [build](#build) and [serve](#serve) the website locally. + +### Build + +```bash +npm run build +``` + +This command generates static content into the `build` directory and can be served using any static contents hosting service. + +### Serve + +```bash +npm run serve +``` + +This command starts a local production server and opens up a browser window. + +### Lint + +From the root of the repo: +```bash +make lint-docs +``` + +This command will run `markdownlint-cli2` (if you don't have it installed, see the [install docs](https://github.com/DavidAnson/markdownlint-cli2#install) and lint all markdown files in `./docs/docs` (i.e. it will not lint versioned docs). + +### Check links + +From the root of the repo: +```bash +make check-docs-link +``` + +This command will run `lychee` (if you don't have it, see the [install docs](https://lychee.cli.rs/installation/)) and check all links in `./docs/docs` (i.e. it will not check versioned docs). + +Since a lot of our links are to github, this command easily gets rate limited, so it has been set up with a long retry sequence for links. You may need to run it multiple times to check all links. +The results (except rate limit responses) are cached for 1 week, so once you have run it, it will not keep checking the same links twice (this is primarly to help with rate limiting). + +## Updating the Documentation + +The documentation website is autogenerated from the markdown files found in [docs](./docs) directory. Each directory in `./docs/` represents a category to be displayed in the sidebar. If you create a new directory, you must create a `_category_.json` file in that directory with the following contents: + +```json +{ + "label": "Sidebar Label", + "position": 1, // position of the category in the sidebar + "link": null +} +``` + +The `position` key above is used to order the categories in the sidebar. This position key pertains to the order of this category in the parent directory. + +If you create a new markdown file within a category (`.docs/` directory is itself a category), you must add the following frontmatter to the top of the markdown file: + +```yaml +--- +title: Title of the file # title of the file in the sidebar +sidebar_label: Sidebar Label # title of the file in the sidebar +sidebar_position: 1 # position of the file in the sidebar +slug: /migrations/v5-to-v6 # the url of the file +--- +``` + +The `link` key in `_category_.json` determines if the category has an introductory page that comes before any content pages. If `link` is `null`, then the category does not have an introductory page. If there is a markdown file you wish to link, you should do + +```json +{ + "label": "Sidebar Label", + "position": 1, // position of the category in the sidebar + "link": { "type": "doc", "id": "intro" } +} +``` + +The `id` key can be defined in the frontmatter of the markdown file. Or, you can use the id tag as an extension to the url of the current page. For example, the following frontmatter on a markdown file in the same directory as the `_category_.json` file shown above will link to the markdown file: + +```yaml +--- +title: Title +sidebar_label: Sidebar Label +sidebar_position: 0 # should be zero for intro pages +slug: /ibc/upgrades/intro +--- +``` + +## Best practices + +- Check the spelling and grammar, even if you have to copy and paste from an external source. +- Use simple sentences. Easy-to-read sentences mean the reader can quickly use the guidance you share. +- Try to express your thoughts in a concise and clean way. +- Either Leave a space or use a `-` between the acronyms ADR and ICS and the corresponding number (e.g. ADR 008 or ADR-008, and ICS 27 or ICS-27). +- Don't overuse `code` format when writing in plain English. +- Follow Google developer documentation [style guide](https://developers.google.com/style). +- Check the meaning of words in Microsoft's [A-Z word list and term collections](https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/term-collections/accessibility-terms) (use the search input!). +- We recommend using RFC keywords in user documentation (lowercase). The RFC keywords are: "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL. They are to be interpreted as described in [RFC 2119](https://datatracker.ietf.org/doc/html/rfc2119). +- Lint the markdown files for documentation with [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli). Run `make docs-lint` (you will need to have `markdownlint-cli` installed, so please follow the [installation instructions](https://github.com/igorshubovych/markdownlint-cli#installation)). + +### File and Directory Naming Conventions + +Inside `/docs/docs/`: + +- All files should be named in `kebab-case`. +- All files should have a two digit prefix, indicating the order in which they should be read and displayed in their respective categories. For example, `01-overview.md` should be read before `02-integration.md`. If this order changes, the prefix should be updated. Note that the ordering is enforced by the frontmatter and not the file name. +- **All files that end in `.template.md` will be ignored by the build process.** +- The prefix `00-` is reserved for root links of categories (if a category has a root link this should be defined in `_category_.json`). For example, see [`docs/01-ibc/05-upgrades/00-intro.md`](./docs/01-ibc/05-upgrades/00-intro.md) and [`docs/01-ibc/05-upgrades/_category_.json`](./docs/01-ibc/05-upgrades/_category_.json). +- All category directories should be named in `kebab-case`. +- All category directories must have a `_category_.json` file. +- All category directories should have a two digit prefix (except for the root `./docs` category), indicating the order in which they should be read and displayed in their respective categories. For example, contents of `./docs/01-ibc/03-apps/` should be read before `./docs/01-ibc/07-relayer.md`. If this order changes, the prefix should be updated. Note that the ordering is enforced by the frontmatter of the markdown files and `_category_.json` files, not the file name. +- The images for each documentation should be kept in the same directory as the markdown file that uses them. This will likely require creating a new directory for each new category. The goal of this is to make versioning easier, discourage repeated use of the image, and make it easier to find images. + +### Code Blocks + +Code blocks in docusaurus are super-powered, read more about them [here](https://docusaurus.io/docs/markdown-features/code-blocks). Three most important features for us are: + +1. We can add a `title` to the code block, which will be displayed above the code block. (This should be used to display the file path of the code block.) +2. We can add a `reference` tag to the code block, which will reference github to create the code block. **You should always use hyperlinks in reference codeblocks.** Here is what a typical code block should look like: + +````ignore +```go reference title="modules/apps/transfer/keeper/keeper.go" +https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/transfer/keeper/keeper.go#L19-L31 +``` +```` + +3. We can highlight lines in the code block by adding `// highlight-next-line` before the line we want to highlight. For example, we should use this to highlight diffs. Here is an example: + +````ignore +```go +import ( + ... + // highlight-next-line ++ ibctm "github.com/cosmos/ibc-go/v6/modules/light-clients/07-tendermint" + ... +) +``` +```` + +### Links + +In docusaurus, there are three ways to link to other pages: + +1. File Paths (relative or absolute) +2. URLs (relative or absolute) +3. Hyperlinks + +In this section, we will discuss when to use each. + +#### Multi-Documentation Linking + +Technically, there are four docs being maintained in this repo: + +1. Found in `docs/docs/` (this is the one displayed on the website in the "Documentation" tab) +2. Found in `docs/architecture/` (this is the one displayed on the website in the "Architecture Decision Records" tab) +3. Found in `docs/events/` (depreciated, this is not displayed on the website, but is hosted under `/events/` url) +4. Found in `docs/params/` (depreciated, this is not displayed on the website, but is hosted under `/params/` url) + +When referencing a markdown file, you should use relative file paths if they are in the same docs directory from above. For example, if you are in `docs/docs/01-ibc` and want to link to `docs/docs/02-apps/01-transfer/01-overview.md`, you should use the relative link `../02-apps/01-transfer/01-overview.md`. + +If the file you are referencing is in a different docs directory, you should use an absolute URL. For example, if you are in `docs/docs/01-ibc` and want to link to `docs/architecture/adr-001-coin-source-tracing.md`, you should use the absolute URL (not absolute file path), in this case `/architecture/adr-001-coin-source-tracing`. You can find the absolute URL by looking at the slug in the frontmatter of the markdown file you want to link to. If the frontmatter slug is not set (such as in `docs/architecture/adr-001-coin-source-tracing.md`), you should use the url that docusaurus generates for it. You can find this by looking at the url of the page in the browser. + +Note that when referencing any file outside of the parent `docs/` directory, you should always use a hyperlink. + +#### Static Assets + +Static assets are the non-code files that are directly copied to the build output. They include **images**, stylesheets, favicons, fonts, etc. + +By default, you are suggested to put these assets in the `static/` directory. Every file you put into that directory will be copied into the root of the generated build folder with the directory hierarchy preserved. E.g. if you add a file named `sun.jpg` to the static folder, it will be copied to `build/sun.jpg`. + +These assets should be referenced using absolute URLs. For example, if you have an image in `static/img/cosmos-logo-bw.png`, you should reference it using `/img/cosmos-logo-bw.png`. + +#### Raw Assets + +If you want to link a raw file, you should link to it using `@site` + its base path. For example, if you want to link to the raw markdown file `/architecture/adr.template.md`, you should use the absolute URL `@site/architecture/adr.template.md`. + +### Technical writing course + +Google provides a free [course](https://developers.google.com/tech-writing/overview) for technical writing. + +## Versioning + +Versioning only applies to documentation and not the ADRs found in the `./architecture/` directory. + +### Terminology + +- Current version: The version placed in the `.docs/` folder. This version is the one that is displayed on the website by default, referred to as next. +- Latest version: This version is defined in `./docusaurus.config.js` file under the `lastVersion` key. + +### Overview + +A typical versioned doc site looks like below: + +```ignore +docs/ +├── sidebars.json # sidebar for the current docs version +├── docs/ # docs directory for the current docs version +│ ├── 01-foo/ +│ │ └── 01-bar.md # https://mysite.com/docs/next/01-foo/01-bar +│ └── 00-intro.md # https://mysite.com/docs/next/00-intro +├── versions.json # file to indicate what versions are available +├── versioned_docs/ +│ ├── version-v1.1.0/ +│ │ ├── 01-foo/ +│ │ │ └── 01-bar.md # https://mysite.com/docs/01-foo/01-bar +│ │ └── 00-intro.md +│ └── version-v1.0.0/ +│ ├── 01-foo/ +│ │ └── 01-bar.md # https://mysite.com/docs/v1.0.0/01-foo/01-bar +│ └── 00-intro.md +├── versioned_sidebars/ +│ ├── version-v1.1.0-sidebars.json +│ └── version-v1.0.0-sidebars.json +├── docusaurus.config.js +└── package.json +``` + +The `./versions.json` file is a list of version names, ordered from newest to oldest. + +### Tagging a new version + +It is possible to tag the current version of the docs as a new version. This will create the appropriate files in `./versioned_docs/` and `./versioned_sidebars/` directories, and modify the `./versions.json` file. To do this, run the following command: + +```bash +npm run docusaurus docs:version v7.1.0 +``` + +### Adding a new version + +To add a new version: + +1. Create a new directory in `./versioned_docs/` called `version-vX.Y.Z` where `X.Y.Z` is the version number. This directory should contain the markdown files for the new version. +2. Create a new file in `./versioned_sidebars/` called `version-vX.Y.Z-sidebars.json`. This file should contain the sidebar for the new version. +3. Add the version to the `./versions.json` file. The list should be ordered from newest to oldest. +4. If needed, make any configuration changes in `./docusaurus.config.js`. For example, updating the `lastVersion` key in `./docusaurus.config.js` to the latest version. + +### Updating an existing version + +You can update multiple docs versions at the same time because each directory in `./versioned_docs/` represents specific routes when published. Make changes by editing the markdown files in the appropriate version directory. + +### Deleting a version + +When a version is no longer supported, you can delete it by removing it from `versions.json` and deleting the corresponding files in `./versioned_docs/` and `./versioned_sidebars/`. diff --git a/docs/architecture/README.md b/docs/architecture/README.md new file mode 100644 index 0000000..31d4fd0 --- /dev/null +++ b/docs/architecture/README.md @@ -0,0 +1,48 @@ +--- +sidebar_position: 1 +--- + +# Architecture Decision Records (ADR) + +This is a location to record all high-level architecture decisions in the ibc-go project. + +You can read more about the ADR concept in this [blog post](https://product.reverb.com/documenting-architecture-decisions-the-reverb-way-a3563bb24bd0#.78xhdix6t). + +An ADR should provide: + +- Context on the relevant goals and the current state +- Proposed changes to achieve the goals +- Summary of pros and cons +- References +- Changelog + +Note the distinction between an ADR and a spec. The ADR provides the context, intuition, reasoning, and +justification for a change in architecture, or for the architecture of something +new. The spec is much more compressed and streamlined summary of everything as +it is or should be. + +If recorded decisions turned out to be lacking, convene a discussion, record the new decisions here, and then modify the code to match. + +Note the context/background should be written in the present tense. + +To suggest an ADR, please make use of the [ADR template](https://github.com/cosmos/ibc-go/blob/main/docs/architecture/adr.template.md) provided. + +## Table of Contents + +| ADR \# | Description | Status | +| ------ | ----------- | ------ | +| [001](./adr-001-coin-source-tracing.md) | ICS-20 coin denomination format | Accepted, Implemented | +| [002](./adr-002-go-module-versioning.md) | Go module versioning | Accepted | +| [003](./adr-003-ics27-acknowledgement.md) | ICS27 acknowledgement format | Accepted | +| [004](./adr-004-ics29-lock-fee-module.md) | ICS29 module locking upon escrow out of balance | Accepted | +| [005](./adr-005-consensus-height-events.md) | `UpdateClient` events - `ClientState` consensus heights | Accepted | +| [006](./adr-006-02-client-refactor.md) | ICS02 client refactor | Accepted | +| [007](./adr-007-solomachine-signbytes.md) | ICS06 Solo machine sign bytes | Accepted | +| [008](./adr-008-app-caller-cbs.md) | Callback to IBC Actors | Accepted | +| [009](./adr-009-v6-ics27-msgserver.md) | ICS27 message server addition | Accepted | +| [010](./adr-010-light-clients-as-sdk-modules.md) | IBC light clients as SDK modules | Accepted | +| [011](./adr-011-transfer-total-escrow-state-entry.md) | ICS20 state entry for total amount of tokens in escrow | Accepted | +| [015](./adr-015-ibc-packet-receiver.md) | IBC Packet Routing | Accepted | +| [025](./adr-025-ibc-passive-channels.md) | IBC passive channels | Deprecated | +| [026](./adr-026-ibc-client-recovery-mechanisms.md) | IBC client recovery mechanisms | Accepted | +| [027](./adr-027-ibc-wasm.md) | Wasm based light clients | Accepted | diff --git a/docs/architecture/adr-001-coin-source-tracing.md b/docs/architecture/adr-001-coin-source-tracing.md new file mode 100644 index 0000000..7982404 --- /dev/null +++ b/docs/architecture/adr-001-coin-source-tracing.md @@ -0,0 +1,375 @@ +# ADR 001: Coin Source Tracing + +## Changelog + +- 2020-07-09: Initial Draft +- 2020-08-11: Implementation changes + +## Status + +Accepted, Implemented + +## Context + +The specification for IBC cross-chain fungible token transfers +([ICS20](https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer)), needs to +be aware of the origin of any token denomination in order to relay a `Packet` which contains the sender +and recipient addresses in the +[`FungibleTokenPacketData`](https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#data-structures). + +The Packet relay sending works based in 2 cases (per +[specification](https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#packet-relay) and [Colin Axnér](https://github.com/colin-axner)'s description): + +1. Sender chain is acting as the source zone. The coins are transferred +to an escrow address (i.e locked) on the sender chain and then transferred +to the receiving chain through IBC TAO logic. It is expected that the +receiving chain will mint vouchers to the receiving address. + +2. Sender chain is acting as the sink zone. The coins (vouchers) are burned +on the sender chain and then transferred to the receiving chain through IBC +TAO logic. It is expected that the receiving chain, which had previously +sent the original denomination, will unescrow the fungible token and send +it to the receiving address. + +Another way of thinking of source and sink zones is through the token's +timeline. Each send to any chain other than the one it was previously +received from is a movement forwards in the token's timeline. This causes +trace to be added to the token's history and the destination port and +destination channel to be prefixed to the denomination. In these instances +the sender chain is acting as the source zone. When the token is sent back +to the chain it previously received from, the prefix is removed. This is +a backwards movement in the token's timeline and the sender chain +is acting as the sink zone. + +### Example + +Assume the following channel connections exist and that all channels use the port ID `transfer`: + +- chain `A` has channels with chain `B` and chain `C` with the IDs `channelToB` and `channelToC`, respectively +- chain `B` has channels with chain `A` and chain `C` with the IDs `channelToA` and `channelToC`, respectively +- chain `C` has channels with chain `A` and chain `B` with the IDs `channelToA` and `channelToB`, respectively + +These steps of transfer between chains occur in the following order: `A -> B -> C -> A -> C`. In particular: + +1. `A -> B`: sender chain is source zone. `A` sends packet with `denom` (escrowed on `A`), `B` receives `denom` and mints and sends voucher `transfer/channelToA/denom` to recipient. +2. `B -> C`: sender chain is source zone. `B` sends packet with `transfer/channelToA/denom` (escrowed on `B`), `C` receives `transfer/channelToA/denom` and mints and sends voucher `transfer/channelToB/transfer/channelToA/denom` to recipient. +3. `C -> A`: sender chain is source zone. `C` sends packet with `transfer/channelToB/transfer/channelToA/denom` (escrowed on `C`), `A` receives `transfer/channelToB/transfer/channelToA/denom` and mints and sends voucher `transfer/channelToC/transfer/channelToB/transfer/channelToA/denom` to recipient. +4. `A -> C`: sender chain is sink zone. `A` sends packet with `transfer/channelToC/transfer/channelToB/transfer/channelToA/denom` (burned on `A`), `C` receives `transfer/channelToC/transfer/channelToB/transfer/channelToA/denom`, and unescrows and sends `transfer/channelToB/transfer/channelToA/denom` to recipient. + +The token has a final denomination on chain `C` of `transfer/channelToB/transfer/channelToA/denom`, where `transfer/channelToB/transfer/channelToA` is the trace information. + +In this context, upon a receive of a cross-chain fungible token transfer, if the sender chain is the source of the token, the protocol prefixes the denomination with the port and channel identifiers in the following format: + +```typescript +prefix + denom = {destPortN}/{destChannelN}/.../{destPort0}/{destChannel0}/denom +``` + +Example: transferring `100 uatom` from port `HubPort` and channel `HubChannel` on the Hub to +Ethermint's port `EthermintPort` and channel `EthermintChannel` results in `100 +EthermintPort/EthermintChannel/uatom`, where `EthermintPort/EthermintChannel/uatom` is the new +denomination on the receiving chain. + +In the case those tokens are transferred back to the Hub (i.e the **source** chain), the prefix is +trimmed and the token denomination updated to the original one. + +### Problem + +The problem of adding additional information to the coin denomination is twofold: + +1. The ever increasing length if tokens are transferred to zones other than the source: + +If a token is transferred `n` times via IBC to a sink chain, the token denom will contain `n` pairs +of prefixes, as shown on the format example above. This poses a problem because, while port and +channel identifiers have a maximum length of 64 each, the SDK `Coin` type only accepts denoms up to +64 characters. Thus, a single cross-chain token, which again, is composed by the port and channels +identifiers plus the base denomination, can exceed the length validation for the SDK `Coins`. + +This can result in undesired behaviours such as tokens not being able to be transferred to multiple +sink chains if the denomination exceeds the length or unexpected `panics` due to denomination +validation failing on the receiving chain. + +2. The existence of special characters and uppercase letters on the denomination: + +In the SDK every time a `Coin` is initialized through the constructor function `NewCoin`, a validation +of a coin's denom is performed according to a +[Regex](https://github.com/cosmos/cosmos-sdk/blob/a940214a4923a3bf9a9161cd14bd3072299cd0c9/types/coin.go#L583), +where only lowercase alphanumeric characters are accepted. While this is desirable for native denominations +to keep a clean UX, it presents a challenge for IBC as ports and channels might be randomly +generated with special and uppercase characters as per the [ICS 024 - Host +Requirements](https://github.com/cosmos/ibc/tree/master/spec/core/ics-024-host-requirements#paths-identifiers-separators) +specification. + +## Decision + +The issues outlined above, are applicable only to SDK-based chains, and thus the proposed solution +are do not require specification changes that would result in modification to other implementations +of the ICS20 spec. + +Instead of adding the identifiers on the coin denomination directly, the proposed solution hashes +the denomination prefix in order to get a consistent length for all the cross-chain fungible tokens. + +This will be used for internal storage only, and when transferred via IBC to a different chain, the +denomination specified on the packed data will be the full prefix path of the identifiers needed to +trace the token back to the originating chain, as specified on ICS20. + +The new proposed format will be the following: + +```go +ibcDenom = "ibc/" + hash(trace path + "/" + base denom) +``` + +The hash function will be a SHA256 hash of the fields of the `DenomTrace`: + +```protobuf +// DenomTrace contains the base denomination for ICS20 fungible tokens and the source tracing +// information +message DenomTrace { + // chain of port/channel identifiers used for tracing the source of the fungible token + string path = 1; + // base denomination of the relayed fungible token + string base_denom = 2; +} +``` + +The `IBCDenom` function constructs the `Coin` denomination used when creating the ICS20 fungible token packet data: + +```go +// Hash returns the hex bytes of the SHA256 hash of the DenomTrace fields using the following formula: +// +// hash = sha256(tracePath + "/" + baseDenom) +func (dt DenomTrace) Hash() tmbytes.HexBytes { + return tmhash.Sum(dt.Path + "/" + dt.BaseDenom) +} + +// IBCDenom a coin denomination for an ICS20 fungible token in the format 'ibc/{hash(tracePath + baseDenom)}'. +// If the trace is empty, it will return the base denomination. +func (dt DenomTrace) IBCDenom() string { + if dt.Path != "" { + return fmt.Sprintf("ibc/%s", dt.Hash()) + } + return dt.BaseDenom +} +``` + +### `x/ibc-transfer` Changes + +In order to retrieve the trace information from an IBC denomination, a lookup table needs to be +added to the `ibc-transfer` module. These values need to also be persisted between upgrades, meaning +that a new `[]DenomTrace` `GenesisState` field state needs to be added to the module: + +```go +// GetDenomTrace retrieves the full identifiers trace and base denomination from the store. +func (k Keeper) GetDenomTrace(ctx Context, denomTraceHash []byte) (DenomTrace, bool) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.KeyDenomTrace(traceHash)) + if bz == nil { + return &DenomTrace, false + } + + var denomTrace DenomTrace + k.cdc.MustUnmarshalBinaryBare(bz, &denomTrace) + return denomTrace, true +} + +// HasDenomTrace checks if a the key with the given trace hash exists on the store. +func (k Keeper) HasDenomTrace(ctx Context, denomTraceHash []byte) bool { + store := ctx.KVStore(k.storeKey) + return store.Has(types.KeyTrace(denomTraceHash)) +} + +// SetDenomTrace sets a new {trace hash -> trace} pair to the store. +func (k Keeper) SetDenomTrace(ctx Context, denomTrace DenomTrace) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinaryBare(&denomTrace) + store.Set(types.KeyTrace(denomTrace.Hash()), bz) +} +``` + +The `MsgTransfer` will validate that the `Coin` denomination from the `Token` field contains a valid +hash, if the trace info is provided, or that the base denominations matches: + +```go +func (msg MsgTransfer) ValidateBasic() error { + // ... + return ValidateIBCDenom(msg.Token.Denom) +} +``` + +```go +// ValidateIBCDenom validates that the given denomination is either: +// +// - A valid base denomination (eg: 'uatom') +// - A valid fungible token representation (i.e 'ibc/{hash}') per ADR 001 https://github.com/cosmos/ibc-go/blob/main/docs/architecture/adr-001-coin-source-tracing.md +func ValidateIBCDenom(denom string) error { + denomSplit := strings.SplitN(denom, "/", 2) + + switch { + case strings.TrimSpace(denom) == "", + len(denomSplit) == 1 && denomSplit[0] == "ibc", + len(denomSplit) == 2 && (denomSplit[0] != "ibc" || strings.TrimSpace(denomSplit[1]) == ""): + return sdkerrors.Wrapf(ErrInvalidDenomForTransfer, "denomination should be prefixed with the format 'ibc/{hash(trace + \"/\" + %s)}'", denom) + + case denomSplit[0] == denom && strings.TrimSpace(denom) != "": + return sdk.ValidateDenom(denom) + } + + if _, err := ParseHexHash(denomSplit[1]); err != nil { + return Wrapf(err, "invalid denom trace hash %s", denomSplit[1]) + } + + return nil +} +``` + +The denomination trace info only needs to be updated when token is received: + +- Receiver is **source** chain: The receiver created the token and must have the trace lookup already stored (if necessary *ie* native token case wouldn't need a lookup). +- Receiver is **not source** chain: Store the received info. For example, during step 1, when chain `B` receives `transfer/channelToA/denom`. + +```go +// SendTransfer +// ... + + fullDenomPath := token.Denom + +// deconstruct the token denomination into the denomination trace info +// to determine if the sender is the source chain +if strings.HasPrefix(token.Denom, "ibc/") { + fullDenomPath, err = k.DenomPathFromHash(ctx, token.Denom) + if err != nil { + return err + } +} + +if types.SenderChainIsSource(sourcePort, sourceChannel, fullDenomPath) { +//... +``` + +```go +// DenomPathFromHash returns the full denomination path prefix from an ibc denom with a hash +// component. +func (k Keeper) DenomPathFromHash(ctx sdk.Context, denom string) (string, error) { + hexHash := denom[4:] + hash, err := ParseHexHash(hexHash) + if err != nil { + return "", Wrap(ErrInvalidDenomForTransfer, err.Error()) + } + + denomTrace, found := k.GetDenomTrace(ctx, hash) + if !found { + return "", Wrap(ErrTraceNotFound, hexHash) + } + + fullDenomPath := denomTrace.GetFullDenomPath() + return fullDenomPath, nil +} +``` + +```go +// OnRecvPacket +// ... + +// This is the prefix that would have been prefixed to the denomination +// on sender chain IF and only if the token originally came from the +// receiving chain. +// +// NOTE: We use SourcePort and SourceChannel here, because the counterparty +// chain would have prefixed with DestPort and DestChannel when originally +// receiving this coin as seen in the "sender chain is the source" condition. +if ReceiverChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), data.Denom) { + // sender chain is not the source, unescrow tokens + + // remove prefix added by sender chain + voucherPrefix := types.GetDenomPrefix(packet.GetSourcePort(), packet.GetSourceChannel()) + unprefixedDenom := data.Denom[len(voucherPrefix):] + token := sdk.NewCoin(unprefixedDenom, sdk.NewIntFromUint64(data.Amount)) + + // unescrow tokens + escrowAddress := types.GetEscrowAddress(packet.GetDestPort(), packet.GetDestChannel()) + return k.bankKeeper.SendCoins(ctx, escrowAddress, receiver, sdk.NewCoins(token)) +} + +// sender chain is the source, mint vouchers + +// since SendPacket did not prefix the denomination, we must prefix denomination here +sourcePrefix := types.GetDenomPrefix(packet.GetDestPort(), packet.GetDestChannel()) +// NOTE: sourcePrefix contains the trailing "/" +prefixedDenom := sourcePrefix + data.Denom + +// construct the denomination trace from the full raw denomination +denomTrace := types.ParseDenomTrace(prefixedDenom) + +// set the value to the lookup table if not stored already +traceHash := denomTrace.Hash() +if !k.HasDenomTrace(ctx, traceHash) { + k.SetDenomTrace(ctx, traceHash, denomTrace) +} + +voucherDenom := denomTrace.IBCDenom() +voucher := sdk.NewCoin(voucherDenom, sdk.NewIntFromUint64(data.Amount)) + +// mint new tokens if the source of the transfer is the same chain +if err := k.bankKeeper.MintCoins( + ctx, types.ModuleName, sdk.NewCoins(voucher), +); err != nil { + return err +} + +// send to receiver +return k.bankKeeper.SendCoinsFromModuleToAccount( + ctx, types.ModuleName, receiver, sdk.NewCoins(voucher), +) +``` + +```go +func NewDenomTraceFromRawDenom(denom string) DenomTrace{ + denomSplit := strings.Split(denom, "/") + trace := "" + if len(denomSplit) > 1 { + trace = strings.Join(denomSplit[:len(denomSplit)-1], "/") + } + return DenomTrace{ + BaseDenom: denomSplit[len(denomSplit)-1], + Trace: trace, + } +} +``` + +One final remark is that the `FungibleTokenPacketData` will remain the same, i.e with the prefixed full denomination, since the receiving chain may not be an SDK-based chain. + +### Coin Changes + +The coin denomination validation will need to be updated to reflect these changes. In particular, the denomination validation +function will now: + +- Accept slash separators (`"/"`) and uppercase characters (due to the `HexBytes` format) +- Bump the maximum character length to 128, as the hex representation used by Tendermint's + `HexBytes` type contains 64 characters. + +Additional validation logic, such as verifying the length of the hash, the may be added to the bank module in the future if the [custom base denomination validation](https://github.com/cosmos/cosmos-sdk/pull/6755) is integrated into the SDK. + +### Positive + +- Clearer separation of the source tracing behaviour of the token (transfer prefix) from the original + `Coin` denomination +- Consistent validation of `Coin` fields (i.e no special characters, fixed max length) +- Cleaner `Coin` and standard denominations for IBC +- No additional fields to SDK `Coin` + +### Negative + +- Store each set of tracing denomination identifiers on the `ibc-transfer` module store +- Clients will have to fetch the base denomination every time they receive a new relayed fungible token over IBC. This can be mitigated using a map/cache for already seen hashes on the client side. Other forms of mitigation, would be opening a websocket connection subscribe to incoming events. + +### Neutral + +- Slight difference with the ICS20 spec +- Additional validation logic for IBC coins on the `ibc-transfer` module +- Additional genesis fields +- Slightly increases the gas usage on cross-chain transfers due to access to the store. This should + be inter-block cached if transfers are frequent. + +## References + +- [ICS 20 - Fungible token transfer](https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer) +- [Custom Coin Denomination validation](https://github.com/cosmos/cosmos-sdk/pull/6755) diff --git a/docs/architecture/adr-002-go-module-versioning.md b/docs/architecture/adr-002-go-module-versioning.md new file mode 100644 index 0000000..fce0d09 --- /dev/null +++ b/docs/architecture/adr-002-go-module-versioning.md @@ -0,0 +1,112 @@ +# ADR 002: Go module versioning + +## Changelog + +- 05/01/2022: initial draft + +## Status + +Accepted + +## Context + +The IBC module was originally developed in the Cosmos SDK and released during the Stargate release series (v0.42). +It was subsequently migrated to its own repository, ibc-go. +The first official release on ibc-go was v1.0.0. +v1.0.0 was decided to be used instead of v0.1.0 primarily for the following reasons: + +- Maintaining compatibility with the IBC specification v1 requires stronger support/guarantees. +- Using the major, minor, and patch numbers allows for easier communication of what breaking changes are included in a release. +- The IBC module is being used by numerous high value projects which require stability. + +### Problems + +#### Go module version must be incremented + +When a Go module is released under v1.0.0, all following releases must follow Go semantic versioning. +Thus when the go API is broken, the Go module major version **must** be incremented. +For example, changing the go package version from `v2` to `v3` bumps the import from `github.com/cosmos/ibc-go/v2` to `github.com/cosmos/ibc-go/v3`. + +If the Go module version is not incremented then attempting to go get a module @v3.0.0 without the suffix results in: +`invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v3` + +Version validation was added in Go 1.13. This means that in order to release a v3.0.0 git tag without a /v3 suffix on the module definition, the tag must explicitly **not** contain a go.mod file. +Not including a go.mod in our release is not a viable option. + +#### Attempting to import multiple go module versions for ibc-go + +Attempting to import two versions of ibc-go, such as `github.com/cosmos/ibc-go/v2` and `github.com/cosmos/ibc-go/v3`, will result in multiple issues. + +The Cosmos SDK does global registration of error and governance proposal types. +The errors and proposals used in ibc-go would need to now register their naming based on the go module version. + +The more concerning problem is that protobuf definitions will also reach a namespace collision. +ibc-go and the Cosmos SDK in general rely heavily on using extended functions for go structs generated from protobuf definitions. +This requires the go structs to be defined in the same package as the extended functions. +Thus, bumping the import versioning causes the protobuf definitions to be generated in two places (in v2 and v3). +When registering these types at compile time, the go compiler will panic. +The generated types need to be registered against the proto codec, but there exist two definitions for the same name. + +The protobuf conflict policy can be overridden via the environment variable `GOLANG_PROTOBUF_REGISTRATION_CONFLICT`, but it is possible this could lead to various runtime errors or unexpected behaviour (see [here](https://github.com/protocolbuffers/protobuf-go/blob/master/reflect/protoregistry/registry.go#L46)). +More information [here](https://developers.google.com/protocol-buffers/docs/reference/go/faq#namespace-conflict) on namespace conflicts for protobuf versioning. + +### Potential solutions + +#### Changing the protobuf definition version + +The protobuf definitions all have a type URL containing the protobuf version for this type. +Changing the protobuf version would solve the namespace collision which arise from importing multiple versions of ibc-go, but it leads to new issues. + +In the Cosmos SDK, `Any`s are unpacked and decoded using the type URL. +Changing the type URL thus is creating a distinctly different type. +The same registration on the proto codec cannot be used to unpack the new type. +For example: + +All Cosmos SDK messages are packed into `Any`s. If we incremented the protobuf version for our IBC messages, clients which submitted the v1 of our Cosmos SDK messages would now be rejected since the old type is not registered on the codec. +The clients must know to submit the v2 of these messages. This pushes the burden of versioning onto relayers and wallets. + +A more serious problem is that the `ClientState` and `ConsensusState` are packed as `Any`s. Changing the protobuf versioning of these types would break compatibility with IBC specification v1. + +#### Moving protobuf definitions to their own go module + +The protobuf definitions could be moved to their own go module which uses 0.x versioning and will never go to 1.0. +This prevents the Go module version from being incremented with breaking changes. +It also requires all extended functions to live in the same Go module, disrupting the existing code structure. + +The version that implements this change will still be incompatible with previous versions, but future versions could be imported together without namespace collisions. +For example, let's say this solution is implemented in v3. Then + +`github.com/cosmos/ibc-go/v2` cannot be imported with any other ibc-go version + +`github.com/cosmos/ibc-go/v3` cannot be imported with any previous ibc-go versions + +`github.com/cosmos/ibc-go/v4` may be imported with ibc-go versions v3+ + +`github.com/cosmos/ibc-go/v5` may be imported with ibc-go versions v3+ + +## Decision + +Supporting importing multiple versions of ibc-go requires a non-trivial amount of complexity. +It is unclear when a user of the ibc-go code would need multiple versions of ibc-go. +Until there is an overwhelming reason to support importing multiple versions of ibc-go: + +**Major releases cannot be imported simultaneously**. +Releases should focus on keeping backwards compatibility for go code clients, within reason. +Old functionality should be marked as deprecated and there should exist upgrade paths between major versions. +Deprecated functionality may be removed when no clients rely on that functionality. +How this is determined is to be decided. + +**Error and proposal type registration will not be changed between go module version increments**. +This explicitly stops external clients from trying to import two major versions (potentially risking a bug due to the instability of proto name collisions override). + +## Consequences + +This only affects clients relying directly on the go code. + +### Positive + +### Negative + +Multiple ibc-go versions cannot be imported. + +### Neutral diff --git a/docs/architecture/adr-003-ics27-acknowledgement.md b/docs/architecture/adr-003-ics27-acknowledgement.md new file mode 100644 index 0000000..ff5e719 --- /dev/null +++ b/docs/architecture/adr-003-ics27-acknowledgement.md @@ -0,0 +1,120 @@ +# ADR 003: ICS27 Acknowledgement Format + +## Changelog + +- January 28th, 2022: Initial Draft + +## Status + +Accepted + +## Context + +Upon receiving an IBC packet, an IBC application can optionally return an acknowledgement. +This acknowledgement will be hashed and written into state. Thus any changes to the information included in an acknowledgement are state machine breaking. + +ICS27 executes transactions on behalf of a controller chain. Information such as the message result or message error may be returned from other SDK modules outside the control of the ICS27 module. +It might be very valuable to return message execution information inside the ICS27 acknowledgement so that controller chain interchain account auth modules can act upon this information. +Only deterministic information returned from the message execution is allowed to be returned in the packet acknowledgement otherwise the network will halt due to a fork in the expected app hash. + +## Decision + +At the time of this writing, Tendermint includes the following information in the [ABCI.ResponseDeliverTx](https://github.com/tendermint/tendermint/blob/release/v0.34.13/types/results.go#L47-#L53): + +```go +// deterministicResponseDeliverTx strips non-deterministic fields from +// ResponseDeliverTx and returns another ResponseDeliverTx. +func deterministicResponseDeliverTx(response *abci.ResponseDeliverTx) *abci.ResponseDeliverTx { + return &abci.ResponseDeliverTx{ + Code: response.Code, + Data: response.Data, + GasWanted: response.GasWanted, + GasUsed: response.GasUsed, + } +} +``` + +### Successful acknowledgements + +Successful acknowledgements should return information about the transaction execution. +Given the deterministic fields in the `abci.ResponseDeliverTx`, the transaction `Data` can be used to indicate information about the transaction execution. +The `abci.ResponseDeliverTx.Data` will be set in the ICS27 packet acknowledgement upon successful transaction execution. + +The format for the `abci.ResponseDeliverTx.Data` is constructed by the SDK. + +At the time of this writing, the next major release of the SDK will change the format for constructing the transaction response data. + +#### v0.45 format + +The current version, v0.45 constructs the transaction response as follows: + +```go +proto.Marshal(&sdk.TxMsgData{ + Data: []*sdk.MsgData{msgResponses...}, +} +``` + +Where `msgResponses` is a slice of `*sdk.MsgData`. +The `MsgData.MsgType` contains the `sdk.MsgTypeURL` of the `sdk.Msg` being executed. +The `MsgData.Data` contains the proto marshaled `MsgResponse` for the associated message executed. + +#### Next major version format + +The next major version will construct the transaction response as follows: + +```go +proto.Marshal(&sdk.TxMsgData{ + MsgResponses: []*codectypes.Any{msgResponses...}, +} +``` + +Where `msgResponses` is a slice of the `MsgResponse`s packed into `Any`s. + +#### Forwards compatible approach + +A forwards compatible approach was deemed infeasible. +The `handler` provided by the `MsgServiceRouter` will only include the `*sdk.Result` and an error (if one occurred). +In v0.45 of the SDK, the `*sdk.Result.Data` will contain the MsgResponse marshaled data. +However, the MsgResponse is not packed and marshaled as a `*codectypes.Any`, thus making it impossible from a generalized point of view to unmarshal the bytes. +If the bytes could be unmarshaled, then they could be packed into an `*codectypes.Any` in anticipation of the upcoming format. + +Intercepting the MsgResponse before it becomes marshaled requires replicating this [code](https://github.com/cosmos/cosmos-sdk/blob/dfd47f5b449f558a855da284a9a7eabbfbad435d/baseapp/msg_service_router.go#L109-#L128). +It may not even be possible to replicate the linked code. The method handler would need to be accessed somehow. + +For these reasons it is deemed infeasible to attempt a forwards compatible approach. + +ICA auth developers can interpret which format was used when constructing the transaction response by checking if the `sdk.TxMsgData.Data` field is non-empty. +If the `sdk.TxMsgData.Data` field is not empty then the format for v0.45 was used, otherwise ICA auth developers can assume the transaction response uses the newer format. + +#### Decision + +Replicate the transaction response format as provided by the current SDK version. +When the SDK version changes, adjust the transaction response format to use the updated transaction response format. +Include the transaction response bytes in the result channel acknowledgement. + +A test has been [written](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/host/ibc_module_test.go#L716-#L774) to fail if the `MsgResponse` is no longer included in consensus. + +### Error acknowledgements + +As indicated above, the `abci.ResponseDeliverTx.Code` is deterministic. +Upon transaction execution errors, an error acknowledgement should be returned including the abci code. + +A test has been [written](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/host/types/ack_test.go#L41-#L82) to fail if the ABCI code is no longer deterministic. + +## Consequences + +> This section describes the consequences, after applying the decision. All consequences should be summarized here, not just the "positive" ones. + +### Positive + +- interchain account auth modules can act upon transaction results without requiring a query module +- transaction results align with those returned by execution of a normal SDK message. + +### Negative + +- the security assumptions of this decision rest on the inclusion of the ABCI error code and the Msg response in the ResponseDeliverTx hash created by Tendermint +- events are non-deterministic and cannot be included in the packet acknowledgement + +### Neutral + +No neutral consequences. diff --git a/docs/architecture/adr-004-ics29-lock-fee-module.md b/docs/architecture/adr-004-ics29-lock-fee-module.md new file mode 100644 index 0000000..472d5ad --- /dev/null +++ b/docs/architecture/adr-004-ics29-lock-fee-module.md @@ -0,0 +1,58 @@ +# ADR 004: Lock fee module upon escrow out of balance + +## Changelog + +- 03/03/2022: initial draft + +## Status + +Accepted + +## Context + +The fee module maintains an escrow account for all fees escrowed to incentivize packet relays. +It also tracks each packet fee escrowed separately from the escrow account. This is because the escrow account only maintains a total balance. It has no reference for which coins belonged to which packet fee. +In the presence of a severe bug, it is possible the escrow balance will become out of sync with the packet fees marked as escrowed. +The ICS29 module should be capable of elegantly handling such a scenario. + +## Decision + +We will allow for the ICS29 module to become "locked" if the escrow balance is determined to be out of sync with the packet fees marked as escrowed. +A "locked" fee module will not allow for packet escrows to occur nor will it distribute fees. All IBC callbacks will skip performing fee logic, similar to fee disabled channels. + +Manual intervention will be needed to unlock the fee module. + +### Sending side + +Special behaviour will have to be accounted for in `OnAcknowledgementPacket`. Since the counterparty will continue to send incentivized acknowledgements for fee enabled channels, the acknowledgement will still need to be unmarshalled into an incentivized acknowledgement before calling the underlying application `OnAcknowledgePacket` callback. + +When distributing fees, a cached context should be used. If the escrow account balance would become negative, the current state changes should be discarded and the fee module should be locked using the uncached context. This prevents fees from being partially distributed for a given packetID. + +### Receiving side + +`OnRecvPacket` should remain unaffected by the fee module becoming locked since escrow accounts only affect the sending side. + +## Consequences + +### Positive + +The fee module can be elegantly disabled in the presence of severe bugs. + +### Negative + +Extra logic is added to account for edge cases which are only possible in the presence of bugs. + +### Neutral + +## References + +Issues: + +- [#821](https://github.com/cosmos/ibc-go/issues/821) +- [#860](https://github.com/cosmos/ibc-go/issues/860) + +PR's: + +- [#1031](https://github.com/cosmos/ibc-go/pull/1031) +- [#1029](https://github.com/cosmos/ibc-go/pull/1029) +- [#1056](https://github.com/cosmos/ibc-go/pull/1056) diff --git a/docs/architecture/adr-005-consensus-height-events.md b/docs/architecture/adr-005-consensus-height-events.md new file mode 100644 index 0000000..30e8a34 --- /dev/null +++ b/docs/architecture/adr-005-consensus-height-events.md @@ -0,0 +1,92 @@ +# ADR 005: UpdateClient Events - ClientState Consensus Heights + +## Changelog + +- 25/04/2022: initial draft + +## Status + +Accepted + +## Context + +The `ibc-go` implementation leverages the [Cosmos-SDK's EventManager](https://github.com/cosmos/cosmos-sdk/blob/v0.45.4/docs/core/events.md#EventManager) to provide subscribers a method of reacting to application specific events. +Some IBC relayers depend on the [`consensus_height`](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/core/02-client/keeper/events.go#L33) attribute emitted as part of `UpdateClient` events in order to run `07-tendermint` misbehaviour detection by cross-checking the details of the *Header* emitted at a given consensus height against those of the *Header* from the originating chain. This includes such details as: + +- The `SignedHeader` containing the commitment root. +- The `ValidatorSet` that signed the *Header*. +- The `TrustedHeight` seen by the client at less than or equal to the height of *Header*. +- The last `TrustedValidatorSet` at the trusted height. + +Following the refactor of the `02-client` submodule and associated `ClientState` interfaces, it will now be possible for +light client implementations to perform such actions as batch updates, inserting `N` number of `ConsensusState`s into the application state tree with a single `UpdateClient` message. This flexibility is provided in `ibc-go` by the usage of the [Protobuf `Any`](https://developers.google.com/protocol-buffers/docs/proto3#any) field contained within the [`UpdateClient`](https://github.com/cosmos/ibc-go/blob/v3.0.0/proto/ibc/core/client/v1/tx.proto#L44) message. +For example, a batched client update message serialized as a Protobuf `Any` type for the `07-tendermint` lightclient implementation could be defined as follows: + +```protobuf +message BatchedHeaders { + repeated Header headers = 1; +} +``` + +To complement this flexibility, the `UpdateClient` handler will now support the submission of [client misbehaviour](https://github.com/cosmos/ibc/tree/master/spec/core/ics-002-client-semantics#misbehaviour) by consolidating the `Header` and `Misbehaviour` interfaces into a single `ClientMessage` interface type: + +```go +// ClientMessage is an interface used to update an IBC client. +// The update may be done by a single header, a batch of headers, misbehaviour, or any type which when verified produces +// a change to state of the IBC client +type ClientMessage interface { + proto.Message + + ClientType() string + ValidateBasic() error +} +``` + +To support this functionality the `GetHeight()` method has been omitted from the new `ClientMessage` interface. +Emission of standardised events from the `02-client` submodule now becomes problematic and is two-fold: + +1. The `02-client` submodule previously depended upon the `GetHeight()` method of `Header` types in order to [retrieve the updated consensus height](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/core/02-client/keeper/client.go#L90). +2. Emitting a single `consensus_height` event attribute is not sufficient in the case of a batched client update containing multiple *Headers*. + +## Decision + +The following decisions have been made in order to provide flexibility to consumers of `UpdateClient` events in a non-breaking fashion: + +1. Return a list of updated consensus heights `[]exported.Height` from the new `UpdateState` method of the `ClientState` interface. + +```go +// UpdateState updates and stores as necessary any associated information for an IBC client, such as the ClientState and corresponding ConsensusState. +// Upon successful update, a list of consensus heights is returned. It assumes the ClientMessage has already been verified. +UpdateState(sdk.Context, codec.BinaryCodec, sdk.KVStore, ClientMessage) []Height +``` + +2. Maintain the `consensus_height` event attribute emitted from the `02-client` update handler, but mark as deprecated for future removal. For example, with tendermint lightclients this will simply be `consensusHeights[0]` following a successful update using a single *Header*. + +3. Add an additional `consensus_heights` event attribute, containing a comma separated list of updated heights. This provides flexibility for emitting a single consensus height or multiple consensus heights in the example use-case of batched header updates. + +## Consequences + +### Positive + +- Subscribers of IBC core events can act upon `UpdateClient` events containing one or more consensus heights. +- Deprecation of the existing `consensus_height` attribute allows consumers to continue to process `UpdateClient` events as normal, with a path to upgrade to using the `consensus_heights` attribute moving forward. + +### Negative + +- Consumers of IBC core `UpdateClient` events are forced to make future code changes. + +### Neutral + +## References + +Discussions: + +- [#1208](https://github.com/cosmos/ibc-go/pull/1208#discussion_r839691927) + +Issues: + +- [#594](https://github.com/cosmos/ibc-go/issues/594) + +PRs: + +- [#1285](https://github.com/cosmos/ibc-go/pull/1285) diff --git a/docs/architecture/adr-006-02-client-refactor.md b/docs/architecture/adr-006-02-client-refactor.md new file mode 100644 index 0000000..16ec15a --- /dev/null +++ b/docs/architecture/adr-006-02-client-refactor.md @@ -0,0 +1,203 @@ +# ADR 006: ICS-02 client refactor + +## Changelog + +- 2022-08-01: Initial Draft + +## Status + +Accepted and applied in v7 of ibc-go + +## Context + +During the initial development of the 02-client submodule, each light client supported (06-solomachine, 07-tendermint, 09-localhost) was referenced through hardcoding. +Here is an example of the [code](https://github.com/cosmos/cosmos-sdk/commit/b93300288e3a04faef9c0774b75c13b24450ba1c#diff-c5f6b956947375f28d611f18d0e670cf28f8f305300a89c5a9b239b0eeec5064R83) that existed in the 02-client submodule: + +```go +func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, header exported.Header) (exported.ClientState, error) { + ... + + switch clientType { + case exported.Tendermint: + clientState, consensusState, err = tendermint.CheckValidityAndUpdateState( + clientState, header, ctx.BlockTime(), + ) + case exported.Localhost: + // override client state and update the block height + clientState = localhosttypes.NewClientState( + ctx.ChainID(), // use the chain ID from context since the client is from the running chain (i.e self). + ctx.BlockHeight(), + ) + default: + err = types.ErrInvalidClientType + } +``` + +To add additional light clients, code would need to be added directly to the 02-client submodule. +Evidently, this would likely become problematic as IBC scaled to many chains using consensus mechanisms beyond the initial supported light clients. +Issue [#6064](https://github.com/cosmos/cosmos-sdk/issues/6064) on the SDK addressed this problem by creating a more modular 02-client submodule. +The 02-client submodule would now interact with each light client via an interface. +While, this change was positive in development, increasing the flexibility and adoptability of IBC, it also opened the door to new problems. + +The difficulty of generalizing light clients became apparent once changes to those light clients were required. +Each light client represents a different consensus algorithm which may contain a host of complexity and nuances. +Here are some examples of issues which arose for light clients that are not applicable to all the light clients supported (06-solomachine, 07-tendermint, 09-localhost): + +### Tendermint non-zero height upgrades + +Before the launch of IBC, it was determined that the golang implementation of [tendermint](https://github.com/tendermint/tendermint) would not be capable of supporting non-zero height upgrades. +This implies that any upgrade would require changing of the chain ID and resetting the height to 0. +A chain is uniquely identified by its chain-id and validator set. +Two different chain ID's can be viewed as different chains and thus a normal update produced by a validator set cannot change the chain ID. +To work around the lack of support for non-zero height upgrades, an abstract height type was created along with an upgrade mechanism. +This type would indicate the revision number (the number of times the chain ID has been changed) and revision height (the current height of the blockchain). + +Refs: + +- Issue [#439](https://github.com/cosmos/ibc/issues/439) on IBC specification repository. +- Specification changes in [#447](https://github.com/cosmos/ibc/pull/447) +- Implementation changes for the abstract height type, [SDK#7211](https://github.com/cosmos/cosmos-sdk/pull/7211) + +### Tendermint requires misbehaviour detection during updates + +The initial release of the IBC module and the 07-tendermint light client implementation did not support misbehaviour detection during update nor did it prevent overwriting of previous updates. +Despite the fact that we designed the `ClientState` interface and developed the 07-tendermint client, we failed to detect even a duplicate update that constituted misbehaviour and thus should freeze the client. +This was fixed in PR [#141](https://github.com/cosmos/ibc-go/pull/141) which required light client implementations to be aware that they must handle duplicate updates and misbehaviour detection. +Misbehaviour detection during updates is not applicable to the solomachine nor localhost. +It is also not obvious that `CheckHeaderAndUpdateState` should be performing this functionality. + +### Localhost requires access to the entire client store + +The localhost has been broken since the initial version of the IBC module. +The localhost tried to be developed underneath the 02-client interfaces without special exception, but this proved to be impossible. +The issues were outlined in [#27](https://github.com/cosmos/ibc-go/issues/27) and further discussed in the attempted ADR in [#75](https://github.com/cosmos/ibc-go/pull/75). +Unlike all other clients, the localhost requires access to the entire IBC store and not just the prefixed client store. + +### Solomachine doesn't set consensus states + +The 06-solomachine does not set the consensus states within the prefixed client store. +It has a single consensus state that is stored within the client state. +This causes setting of the consensus state at the 02-client level to use unnecessary storage. +It also causes timeouts to fail with solo machines. +Previously, the timeout logic within IBC would obtain the consensus state at the height a timeout is being proved. +This is problematic for the solo machine as no consensus state is set. +See issue [#562](https://github.com/cosmos/ibc/issues/562) on the IBC specification repo. + +### New clients may want to do batch updates + +New light clients may not function in a similar fashion to 06-solomachine and 07-tendermint. +They may require setting many consensus states in a single update. +As @seunlanlege [states](https://github.com/cosmos/ibc-go/issues/284#issuecomment-1005583679): + +> I'm in support of these changes for 2 reasons: +> +> - This would allow light clients to handle batch header updates in CheckHeaderAndUpdateState, for the special case of 11-beefy proving the finality for a batch of headers is much more space and time efficient than the space/time complexity of proving each individual headers in that batch, combined. +> +> - This also allows for a single light client instance of 11-beefy be used to prove finality for every parachain connected to the relay chain (Polkadot/Kusama). We achieve this by setting the appropriate ConsensusState for individual parachain headers in CheckHeaderAndUpdateState + +## Decision + +### Require light clients to set client and consensus states + +The IBC specification states: + +> If the provided header was valid, the client MUST also mutate internal state to store now-finalised consensus roots and update any necessary signature authority tracking (e.g. changes to the validator set) for future calls to the validity predicate. + +The initial version of the IBC go SDK based module did not fulfill this requirement. +Instead, the 02-client submodule required each light client to return the client and consensus state which should be updated in the client prefixed store. +This decision lead to the issues "Solomachine doesn't set consensus states" and "New clients may want to do batch updates". + +Each light client should be required to set its own client and consensus states on any update necessary. +The go implementation should be changed to match the specification requirements. +This will allow more flexibility for light clients to manage their own internal storage and do batch updates. + +### Merge `Header`/`Misbehaviour` interface and rename to `ClientMessage` + +Remove `GetHeight()` from the header interface (as light clients now set the client/consensus states). +This results in the `Header`/`Misbehaviour` interfaces being the same. +To reduce complexity of the codebase, the `Header`/`Misbehaviour` interfaces should be merged into `ClientMessage`. +`ClientMessage` will provide the client with some authenticated information which may result in regular updates, misbehaviour detection, batch updates, or other custom functionality a light client requires. + +### Split `CheckHeaderAndUpdateState` into 4 functions + +See [#668](https://github.com/cosmos/ibc-go/issues/668). + +Split `CheckHeaderAndUpdateState` into 4 functions: + +- `VerifyClientMessage` +- `CheckForMisbehaviour` +- `UpdateStateOnMisbehaviour` +- `UpdateState` + +`VerifyClientMessage` checks the that the structure of a `ClientMessage` is correct and that all authentication data provided is valid. + +`CheckForMisbehaviour` checks to see if a `ClientMessage` is evidence of misbehaviour. + +`UpdateStateOnMisbehaviour` freezes the client and updates its state accordingly. + +`UpdateState` performs a regular update or a no-op on duplicate updates. + +The code roughly looks like: + +```go +func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, header exported.Header) error { + ... + + if err := clientState.VerifyClientMessage(clientMessage); err != nil { + return err + } + + foundMisbehaviour := clientState.CheckForMisbehaviour(clientMessage) + if foundMisbehaviour { + clientState.UpdateStateOnMisbehaviour(header) + // emit misbehaviour event + return + } + + clientState.UpdateState(clientMessage) // expects no-op on duplicate header + // emit update event + return +} +``` + +### Add `GetTimestampAtHeight` to the client state interface + +By adding `GetTimestampAtHeight` to the ClientState interface, we allow light clients which do non-traditional consensus state/timestamp storage to process timeouts correctly. +This fixes the issues outlined for the solo machine client. + +### Add generic verification functions + +As the complexity and the functionality grows, new verification functions will be required for additional paths. +This was explained in [#684](https://github.com/cosmos/ibc/issues/684) on the specification repo. +These generic verification functions would be immediately useful for the new paths added in connection/channel upgradability as well as for custom paths defined by IBC applications such as Interchain Queries. +The old verification functions (`VerifyClientState`, `VerifyConnection`, etc) should be removed in favor of the generic verification functions. + +## Consequences + +### Positive + +- Flexibility for light client implementations +- Well defined interfaces and their required functionality +- Generic verification functions +- Applies changes necessary for future client/connection/channel upgrabability features +- Timeout processing for solo machines +- Reduced code complexity + +### Negative + +- The refactor touches on sensitive areas of the ibc-go codebase +- Changing of established naming (`Header`/`Misbehaviour` to `ClientMessage`) + +### Neutral + +No notable consequences + +## References + +Issues: + +- [#284](https://github.com/cosmos/ibc-go/issues/284) + +PRs: + +- [#1871](https://github.com/cosmos/ibc-go/pull/1871) diff --git a/docs/architecture/adr-007-solomachine-signbytes.md b/docs/architecture/adr-007-solomachine-signbytes.md new file mode 100644 index 0000000..f2c7eef --- /dev/null +++ b/docs/architecture/adr-007-solomachine-signbytes.md @@ -0,0 +1,52 @@ +# ADR 007: Solo machine sign bytes + +## Changelog + +- 2022-08-02: Initial draft + +## Status + +Accepted, applied in v7 + +## Context + +The `06-solomachine` implementation up until ibc-go v7 constructed sign bytes using a `DataType` which described what type of data was being signed. +This design decision arose from a misunderstanding of the security implications. +It was noted that the proto definitions do not [provide uniqueness](https://github.com/cosmos/cosmos-sdk/pull/7237#discussion_r484264573) which is a necessity for ensuring two signatures over different data types can never be the same. +What was missed is that the uniqueness is not provided by the proto definition, but by the usage of the proto definition. +The path provided by core IBC will be unique and is already encoded into the signature data. +Thus two different paths with the same data values will encode differently which provides signature uniqueness. + +Furthermore, the current construction does not support the proposed changes in the spec repo to support [Generic Verification functions](https://github.com/cosmos/ibc/issues/684). +This is because in order to verify a new path, a new `DataType` must be added for that path. + +## Decision + +Remove `DataType` and change the `DataType` in the `SignBytes` and `SignatureAndData` to be `Path`. +The new `Path` field should be bytes. +Remove all `...Data` proto definitions except for `HeaderData` +These `...Data` definitions were created previously for each `DataType`. +The proto version of the solo machine proto definitions should be bumped to `v3`. + +This removes an extra layer of complexity from signature construction and allows for support of generic verification. + +## Consequences + +### Positive + +- Simplification of solo machine signature construction +- Support for generic verification + +### Negative + +- Breaks existing signature construction in a non-backwards compatible way +- Solo machines must update to handle the new format +- Migration required for solo machine client and consensus states + +### Neutral + +No notable consequences + +## References + +- [#1141](https://github.com/cosmos/ibc-go/issues/1141) diff --git a/docs/architecture/adr-008-app-caller-cbs.md b/docs/architecture/adr-008-app-caller-cbs.md new file mode 100644 index 0000000..8f423a4 --- /dev/null +++ b/docs/architecture/adr-008-app-caller-cbs.md @@ -0,0 +1,569 @@ +# ADR 008: Callback to IBC Actors + +## Changelog + +- 2022-08-10: Initial Draft +- 2023-03-22: Merged +- 2023-09-13: Updated with decisions made in implementation +- 2025-02-24: RecvPacket callback error now returns error acknowledgement + +## Status + +Accepted, middleware implemented + +## Context + +IBC was designed with callbacks between core IBC and IBC applications. IBC apps would send a packet to core IBC. When the result of the packet lifecycle eventually resolved into either an acknowledgement or a timeout, core IBC called a callback on the IBC application so that the IBC application could take action on the basis of the result (e.g. unescrow tokens for ICS-20). + +This setup worked well for off-chain users interacting with IBC applications. + +We are now seeing the desire for secondary applications (e.g. smart contracts, modules) to call into IBC apps as part of their state machine logic and then do some actions on the basis of the packet result. Or to receive a packet from IBC and do some logic upon receipt. + +Example Usecases: + +- Send an ICS-20 packet, and if it is successful, then send an ICA-packet to swap tokens on LP and return funds to sender +- Execute some logic upon receipt of token transfer to a smart contract address + +This requires a second layer of callbacks. The IBC application already gets the result of the packet from core IBC, but currently there is no standardized way to pass this information on to an actor module/smart contract. + +## Definitions + +- Actor: an actor is an on-chain module (this may be a hardcoded module in the chain binary or a smart contract) that wishes to execute custom logic whenever IBC receives a packet flow that it has either sent or received. It **must** be addressable by a string value. + +## Decision + +Create a middleware that can interface between IBC applications and smart contract VMs. The IBC applications and smart contract VMs will implement respective interfaces that will then be composed together by the callback middleware to allow a smart contract of any compatible VM to interact programmatically with an IBC application. + +## Data structures + +The `CallbackPacketData` struct will get constructed from custom callback data in the application packet. The `CallbackAddress` is the IBC Actor address on which the callback should be called on. The `SenderAddress` is also provided to optionally allow a VM to ensure that the sender is the same as the callback address. + +The struct also defines a `CommitGasLimit` which is the maximum gas a callback is allowed to use. If the callback exceeds this limit, the callback will panic and the tx will commit without the callback's state changes. + +The `ExecutionGasLimit` is the practical limit of the tx execution that is set in the context gas meter. It is the minimum of the `CommitGasLimit` and the gas left in the context gas meter which is determined by the relayer's choice of tx gas limit. If `ExecutionGasLimit < CommitGasLimit`, then an out-of-gas error will revert the entire transaction without committing anything, allowing for a different relayer to retry with a larger tx gas limit. + +Any middleware targeting this interface for callback handling should define a global limit that caps the gas that a callback is allowed to take (especially on AcknowledgePacket and TimeoutPacket) so that a custom callback does not prevent the packet lifecycle from completing. However, since this is a global cap it is likely to be very large. Thus, users may specify a smaller limit to cap the amount of fees a relayer must pay in order to complete the packet lifecycle on the user's behalf. + +```go +// Implemented by any packet data type that wants to support PacketActor callbacks +// PacketActor's will be unable to act on any packet data type that does not implement +// this interface. +type CallbackPacketData struct { + CallbackAddress: string + ExecutionGasLimit: uint64 + SenderAddress: string + CommitGasLimit: uint64 +} +``` + +IBC Apps or middleware can then call the IBCActor callbacks like so in their own callbacks: + +### Callback Middleware + +The CallbackMiddleware wraps an underlying IBC application along with a contractKeeper that delegates the callback to a virtual machine. This allows the Callback middleware to interface any compatible IBC application with any compatible VM (e.g. EVM, WASM) so long as the application implements the `CallbacksCompatibleModule` interface and the VM implements the `ContractKeeper` interface. + +```go +// IBCMiddleware implements the ICS26 callbacks for the ibc-callbacks middleware given +// the underlying application. +type IBCMiddleware struct { + app types.CallbacksCompatibleModule + ics4Wrapper porttypes.ICS4Wrapper + + contractKeeper types.ContractKeeper + + // maxCallbackGas defines the maximum amount of gas that a callback actor can ask the + // relayer to pay for. If a callback fails due to insufficient gas, the entire tx + // is reverted if the relayer hadn't provided the minimum(userDefinedGas, maxCallbackGas). + // If the actor hasn't defined a gas limit, then it is assumed to be the maxCallbackGas. + maxCallbackGas uint64 +} +``` + +### Callback-Compatible IBC Application + +The `CallbacksCompatibleModule` extends `porttypes.IBCModule` to include an `UnmarshalPacketData` function that allows the middleware to request that the underlying app unmarshal the packet data. This will then allow the middleware to retrieve the callback specific data from an arbitrary set of IBC application packets. + +```go +// CallbacksCompatibleModule is an interface that combines the IBCModule and PacketDataUnmarshaler +// interfaces to assert that the underlying application supports both. +type CallbacksCompatibleModule interface { + porttypes.IBCModule + porttypes.PacketDataUnmarshaler +} + +// PacketDataUnmarshaler defines an optional interface which allows a middleware to +// request the packet data to be unmarshaled by the base application. +type PacketDataUnmarshaler interface { + // UnmarshalPacketData unmarshals the packet data into a concrete type + // ctx, portID, channelID are provided as arguments, so that (if needed) + // the packet data can be unmarshaled based on the channel version. + // the version of the underlying app is also returned. + UnmarshalPacketData(ctx sdk.Context, portID, channelID string, bz []byte) (interface{}, string, error) +} +``` + +The application's packet data must additionally implement the following interfaces: + +```go +// PacketData defines an optional interface which an application's packet data structure may implement. +type PacketData interface { + // GetPacketSender returns the sender address of the packet data. + // If the packet sender is unknown or undefined, an empty string should be returned. + GetPacketSender(sourcePortID string) string +} + +// PacketDataProvider defines an optional interfaces for retrieving custom packet data stored on behalf of another application. +// An existing problem in the IBC middleware design is the inability for a middleware to define its own packet data type and insert packet sender provided information. +// A short term solution was introduced into several application's packet data to utilize a memo field to carry this information on behalf of another application. +// This interfaces standardizes that behaviour. Upon realization of the ability for middleware's to define their own packet data types, this interface will be deprecated and removed with time. +type PacketDataProvider interface { + // GetCustomPacketData returns the packet data held on behalf of another application. + // The name the information is stored under should be provided as the key. + // If no custom packet data exists for the key, nil should be returned. + GetCustomPacketData(key string) interface{} +} +``` + +The callback data can be embedded in an application packet by providing custom packet data for source and destination callback in the custom packet data under the appropriate key. + +```jsonc +// Custom Packet data embedded as a JSON object in the packet data + +// src callback custom data +{ + "src_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} + +// dest callback custom data +{ + "dest_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} + +// src and dest callback custom data embedded together +{ + "src_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + }, + "dest_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} +``` + +## ContractKeeper + +The `ContractKeeper` interface must be implemented by any VM that wants to support IBC callbacks. This allows for separation of concerns +between the middleware which is handling logic intended for all VMs (e.g. setting gas meter, extracting callback data, emitting events), +while the ContractKeeper can handle the specific details of calling into the VM in question. + +The `ContractKeeper` **may** impose additional checks such as ensuring that the contract address is the same as the packet sender in source callbacks. +It may also disable certain callback methods by simply performing a no-op. + +```go +// ContractKeeper defines the entry points exposed to the VM module which invokes a smart contract +type ContractKeeper interface { + // IBCSendPacketCallback is called in the source chain when a PacketSend is executed. The + // packetSenderAddress is determined by the underlying module, and may be empty if the sender is + // unknown or undefined. The contract is expected to handle the callback within the user defined + // gas limit, and handle any errors, or panics gracefully. + // This entry point is called with a cached context. If an error is returned, then the changes in + // this context will not be persisted, and the error will be propagated to the underlying IBC + // application, resulting in a packet send failure. + // + // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform + // validation on the origin of a given packet. It is recommended to perform the same validation + // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This + // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. + // + // The version provided is the base application version for the given packet send. This allows + // contracts to determine how to unmarshal the packetData. + IBCSendPacketCallback( + cachedCtx sdk.Context, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + packetData []byte, + contractAddress, + packetSenderAddress string, + version string, + ) error + // IBCOnAcknowledgementPacketCallback is called in the source chain when a packet acknowledgement + // is received. The packetSenderAddress is determined by the underlying module, and may be empty if + // the sender is unknown or undefined. The contract is expected to handle the callback within the + // user defined gas limit, and handle any errors, or panics gracefully. + // This entry point is called with a cached context. If an error is returned, then the changes in + // this context will not be persisted, but the packet lifecycle will not be blocked. + // + // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform + // validation on the origin of a given packet. It is recommended to perform the same validation + // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This + // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. + // + // The version provided is the base application version for the given packet send. This allows + // contracts to determine how to unmarshal the packetData. + IBCOnAcknowledgementPacketCallback( + cachedCtx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, + contractAddress, + packetSenderAddress string, + version string, + ) error + // IBCOnTimeoutPacketCallback is called in the source chain when a packet is not received before + // the timeout height. The packetSenderAddress is determined by the underlying module, and may be + // empty if the sender is unknown or undefined. The contract is expected to handle the callback + // within the user defined gas limit, and handle any error, out of gas, or panics gracefully. + // This entry point is called with a cached context. If an error is returned, then the changes in + // this context will not be persisted, but the packet lifecycle will not be blocked. + // + // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform + // validation on the origin of a given packet. It is recommended to perform the same validation + // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This + // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. + // + // The version provided is the base application version for the given packet send. This allows + // contracts to determine how to unmarshal the packetData. + IBCOnTimeoutPacketCallback( + cachedCtx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, + contractAddress, + packetSenderAddress string, + version string, + ) error + // IBCReceivePacketCallback is called in the destination chain when a packet acknowledgement is written. + // The contract is expected to handle the callback within the user defined gas limit. + // This entry point is called with a cached context. If an error is returned, then the error + // will be written as an error acknowledgement. This will cause the context changes made by the contract + // to be reverted along with any state changes made by the underlying application. + // The error acknowledgement will then be relayed to the sending application which can perform + // its error acknowledgement logic (e.g. refunding tokens back to user) + // + // The version provided is the base application version for the given packet send. This allows + // contracts to determine how to unmarshal the packetData. + IBCReceivePacketCallback( + cachedCtx sdk.Context, + packet ibcexported.PacketI, + ack ibcexported.Acknowledgement, + contractAddress string, + version string, + ) error +} +``` + +### PacketCallbacks + +The packet callbacks implemented in the middleware will first call the underlying application and then route to the IBC actor callback in the post-processing step. +It will extract the callback data from the application packet and set the callback gas meter depending on the global limit, the user limit, and the gas left in the transaction gas meter. +The callback will then be routed through the callback keeper which will either panic or return a result (success or failure). In the event of a (non-oog) panic or an error, the callback state changes +are discarded and the transaction is committed. + +If the relayer-defined gas limit is exceeded before the user-defined gas limit or global callback gas limit is exceeded, then the entire transaction is reverted to allow for resubmission. If the chain-defined or user-defined gas limit is reached, +the callback state changes are reverted and the transaction is committed. + +For the `SendPacket` callback, we will revert the entire transaction on any kind of error or panic. This is because the packet lifecycle has not yet started, so we can revert completely to avoid starting the packet lifecycle if the callback is not successful. + +```go +// SendPacket implements source callbacks for sending packets. +// It defers to the underlying application and then calls the contract callback. +// If the contract callback returns an error, panics, or runs out of gas, then +// the packet send is rejected. +func (im IBCMiddleware) SendPacket( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + data []byte, +) (uint64, error) { + // run underlying app logic first + // IBCActor logic will postprocess + seq, err := im.ics4Wrapper.SendPacket(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) + if err != nil { + return 0, err + } + + // use underlying app to get source callback information from packet data + callbackData, err := types.GetSourceCallbackData(im.app, data, sourcePort, ctx.GasMeter().GasRemaining(), im.maxCallbackGas) + // SendPacket is not blocked if the packet does not opt-in to callbacks + if err != nil { + return seq, nil + } + + callbackExecutor := func(cachedCtx sdk.Context) error { + return im.contractKeeper.IBCSendPacketCallback( + cachedCtx, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data, callbackData.CallbackAddress, callbackData.SenderAddress, + ) + } + + err = im.processCallback(ctx, types.CallbackTypeSendPacket, callbackData, callbackExecutor) + // contract keeper is allowed to reject the packet send. + if err != nil { + return 0, err + } + + types.EmitCallbackEvent(ctx, sourcePort, sourceChannel, seq, types.CallbackTypeSendPacket, callbackData, nil) + return seq, nil +} + +// WriteAcknowledgement implements the ReceivePacket destination callbacks for the ibc-callbacks middleware +// during asynchronous packet acknowledgement. +// It defers to the underlying application and then calls the contract callback. +// If the contract callback runs out of gas and may be retried with a higher gas limit then the state changes are +// reverted via a panic. +func (im IBCMiddleware) WriteAcknowledgement( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + packet ibcexported.PacketI, + ack ibcexported.Acknowledgement, +) error { + // run underlying app logic first + // IBCActor logic will postprocess + err := im.ics4Wrapper.WriteAcknowledgement(ctx, chanCap, packet, ack) + if err != nil { + return err + } + + // use underlying app to get destination callback information from packet data + callbackData, err := types.GetDestCallbackData( + im.app, packet.GetData(), packet.GetSourcePort(), ctx.GasMeter().GasRemaining(), im.maxCallbackGas, + ) + // WriteAcknowledgement is not blocked if the packet does not opt-in to callbacks + if err != nil { + return nil + } + + callbackExecutor := func(cachedCtx sdk.Context) error { + return im.contractKeeper.IBCReceivePacketCallback(cachedCtx, packet, ack, callbackData.CallbackAddress) + } + + // callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions + err = im.processCallback(ctx, types.CallbackTypeReceivePacket, callbackData, callbackExecutor) + // emit events + types.EmitCallbackEvent( + ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(), + types.CallbackTypeAcknowledgementPacket, callbackData, err, + ) + + return nil +} + +// Call the IBCActor recvPacket callback after processing the packet +// if the recvPacket callback exists. If the callback returns an error +// then return an error ack to revert all packet data processing. +func (im IBCMiddleware) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) (ack exported.Acknowledgement) { + // run underlying app logic first + // IBCActor logic will postprocess + ack := im.app.OnRecvPacket(ctx, packet, relayer) + // if ack is nil (asynchronous acknowledgements), then the callback will be handled in WriteAcknowledgement + // if ack is not successful, all state changes are reverted. If a packet cannot be received, then there is + // no need to execute a callback on the receiving chain. + if ack == nil || !ack.Success() { + return ack + } + + // use underlying app to get destination callback information from packet data + callbackData, err := types.GetDestCallbackData( + im.app, packet.GetData(), packet.GetSourcePort(), ctx.GasMeter().GasRemaining(), im.maxCallbackGas, + ) + // OnRecvPacket is not blocked if the packet does not opt-in to callbacks + if err != nil { + return ack + } + + callbackExecutor := func(cachedCtx sdk.Context) error { + return im.contractKeeper.IBCReceivePacketCallback(cachedCtx, packet, ack, callbackData.CallbackAddress) + } + + // callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions + err = im.processCallback(ctx, types.CallbackTypeReceivePacket, callbackData, callbackExecutor) + types.EmitCallbackEvent( + ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), + types.CallbackTypeReceivePacket, callbackData, err, + ) + if err != nil { + return channeltypes.NewErrorAcknowledgement(err) + } + + return ack +} + +// Call the IBCActor acknowledgementPacket callback after processing the packet +// if the ackPacket callback exists and returns an error +// DO NOT return the error upstream. The acknowledgement must complete for the packet +// lifecycle to end, so the custom callback cannot block completion. +// Instead we emit error events and set the error in state +// so that users and on-chain logic can handle this appropriately +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + // we first call the underlying app to handle the acknowledgement + // IBCActor logic will postprocess + err := im.app.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer) + if err != nil { + return err + } + + // use underlying app to get source callback information from packet data + callbackData, err := types.GetSourceCallbackData( + im.app, packet.GetData(), packet.GetSourcePort(), ctx.GasMeter().GasRemaining(), im.maxCallbackGas, + ) + // OnAcknowledgementPacket is not blocked if the packet does not opt-in to callbacks + if err != nil { + return nil + } + + callbackExecutor := func(cachedCtx sdk.Context) error { + return im.contractKeeper.IBCOnAcknowledgementPacketCallback( + cachedCtx, packet, acknowledgement, relayer, callbackData.CallbackAddress, callbackData.SenderAddress, + ) + } + + // callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions + err = im.processCallback(ctx, types.CallbackTypeAcknowledgementPacket, callbackData, callbackExecutor) + types.EmitCallbackEvent( + ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(), + types.CallbackTypeAcknowledgementPacket, callbackData, err, + ) + + return nil +} + +// Call the IBCActor timeoutPacket callback after processing the packet +// if the timeoutPacket callback exists and returns an error +// DO NOT return the error upstream. The timeout must complete for the packet +// lifecycle to end, so the custom callback cannot block completion. +// Instead we emit error events and set the error in state +// so that users and on-chain logic can handle this appropriately +func (im IBCModule) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + // application-specific onTimeoutPacket logic + err := im.app.OnTimeoutPacket(ctx, packet, relayer) + if err != nil { + return err + } + + // use underlying app to get source callback information from packet data + callbackData, err := types.GetSourceCallbackData( + im.app, packet.GetData(), packet.GetSourcePort(), ctx.GasMeter().GasRemaining(), im.maxCallbackGas, + ) + // OnTimeoutPacket is not blocked if the packet does not opt-in to callbacks + if err != nil { + return nil + } + + callbackExecutor := func(cachedCtx sdk.Context) error { + return im.contractKeeper.IBCOnTimeoutPacketCallback(cachedCtx, packet, relayer, callbackData.CallbackAddress, callbackData.SenderAddress) + } + + // callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions + err = im.processCallback(ctx, types.CallbackTypeTimeoutPacket, callbackData, callbackExecutor) + types.EmitCallbackEvent( + ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(), + types.CallbackTypeTimeoutPacket, callbackData, err, + ) + + return nil +} + +// processCallback executes the callbackExecutor and reverts contract changes if the callbackExecutor fails. +// +// Error Precedence and Returns: +// - oogErr: Takes the highest precedence. If the callback runs out of gas, an error wrapped with types.ErrCallbackOutOfGas is returned. +// - panicErr: Takes the second-highest precedence. If a panic occurs and it is not propagated, an error wrapped with types.ErrCallbackPanic is returned. +// - callbackErr: If the callbackExecutor returns an error, it is returned as-is. +// +// panics if +// - the contractExecutor panics for any reason, and the callbackType is SendPacket, or +// - the contractExecutor runs out of gas and the relayer has not reserved gas grater than or equal to +// CommitGasLimit. +func (IBCMiddleware) processCallback( + ctx sdk.Context, callbackType types.CallbackType, + callbackData types.CallbackData, callbackExecutor func(sdk.Context) error, +) (err error) { + cachedCtx, writeFn := ctx.CacheContext() + cachedCtx = cachedCtx.WithGasMeter(storetypes.NewGasMeter(callbackData.ExecutionGasLimit)) + + defer func() { + // consume the minimum of g.consumed and g.limit + ctx.GasMeter().ConsumeGas(cachedCtx.GasMeter().GasConsumedToLimit(), fmt.Sprintf("ibc %s callback", callbackType)) + + // recover from all panics except during SendPacket callbacks + if r := recover(); r != nil { + if callbackType == types.CallbackTypeSendPacket { + panic(r) + } + err = errorsmod.Wrapf(types.ErrCallbackPanic, "ibc %s callback panicked with: %v", callbackType, r) + } + + // if the callback ran out of gas and the relayer has not reserved enough gas, then revert the state + if cachedCtx.GasMeter().IsPastLimit() { + if callbackData.AllowRetry() { + panic(storetypes.ErrorOutOfGas{Descriptor: fmt.Sprintf("ibc %s callback out of gas; commitGasLimit: %d", callbackType, callbackData.CommitGasLimit)}) + } + err = errorsmod.Wrapf(types.ErrCallbackOutOfGas, "ibc %s callback out of gas", callbackType) + } + + // allow the transaction to be committed, continuing the packet lifecycle + }() + + err = callbackExecutor(cachedCtx) + if err == nil { + writeFn() + } + + return err +} +``` + +Chains are expected to specify a `maxCallbackGas` to ensure that callbacks do not consume an arbitrary amount of gas. Thus, it should always be possible for a relayer to complete the packet lifecycle even if the actor callbacks cannot run successfully. + +## Consequences + +### Positive + +- IBC Actors can now programmatically execute logic that involves sending a packet and then performing some additional logic once the packet lifecycle is complete +- Middleware implementing ADR-8 can be generally used for any application +- Leverages a similar callback architecture to the one used between core IBC and IBC applications + +### Negative + +- Callbacks may now have unbounded gas consumption since the actor may execute arbitrary logic. Chains implementing this feature should take care to place limitations on how much gas an actor callback can consume. +- The relayer pays for the callback gas instead of the IBCActor + +### Neutral + +- Application packets that want to support ADR-8 must additionally have their packet data implement `PacketDataProvider` and `PacketData` interfaces. +- Applications must implement `PacketDataUnmarshaler` interface +- Callback receiving module must implement the `ContractKeeper` interface + +## References + +- [Original issue](https://github.com/cosmos/ibc-go/issues/1660) +- [CallbackPacketData interface implementation](https://github.com/cosmos/ibc-go/pull/3287) +- [ICS 20, ICS 27 implementations of the CallbackPacketData interface](https://github.com/cosmos/ibc-go/pull/3287) diff --git a/docs/architecture/adr-009-v6-ics27-msgserver.md b/docs/architecture/adr-009-v6-ics27-msgserver.md new file mode 100644 index 0000000..e1fdd86 --- /dev/null +++ b/docs/architecture/adr-009-v6-ics27-msgserver.md @@ -0,0 +1,115 @@ +# ADR 009: ICS27 message server addition + +## Changelog + +- 2022/09/07: Initial draft + +## Status + +Accepted, implemented in v6 of ibc-go + +## Context + +ICS 27 (Interchain Accounts) brought a cross-chain account management protocol built upon IBC. +It enabled chains to programmatically create accounts on behalf of counterparty chains which may enable a variety of authentication methods for this interchain account. +The initial release of ICS 27 focused on enabling authentication schemes that may not require signing with a private key, such as via on-chain mechanisms like governance. + +Following the initial release of ICS 27 it became evident that: + +- a default authentication module would enable more usage of ICS 27 +- generic authentication modules should be capable of authenticating an interchain account registration +- application logic which wraps ICS 27 packet sends does not need to be associated with the authentication logic + +## Decision + +The controller module should be simplified to remove the correlation between the authentication logic for an interchain account and the application logic for an interchain account. +To minimize disruption to developers working on the original design of the ICS 27 controller module, all changes will be made in a backwards compatible fashion. + +### Msg server + +To achieve this, as stated by [@damiannolan](https://github.com/cosmos/ibc-go/issues/2026#issue-1341640594), it was proposed to: + +> Add a new `MsgServer` to `27-interchain-accounts` which exposes two distinct rpc endpoints: +> +> - `RegisterInterchainAccount` +> - `SendTx` + +This will enable any SDK (authentication) module to register interchain accounts and send transactions on their behalf. +Examples of existing SDK modules which would benefit from this change include: + +- x/auth +- x/gov +- x/group + +The existing go functions: `RegisterInterchainAccount()` and `SendTx()` will remain to operate as they did in previous release versions. + +This will be possible for SDK v0.46.x and above. + +### Allow `nil` underlying applications + +Authentication modules should interact with the controller module via the message server and should not be associated with application logic. +For now, it will be allowed to set a `nil` underlying application. +A future version may remove the underlying application entirely. + +See issue [#2040](https://github.com/cosmos/ibc-go/issues/2040) + +### Channel capability claiming + +The controller module will now claim the channel capability in `OnChanOpenInit`. +Underlying applications will be passed a `nil` capability in `OnChanOpenInit`. + +Channel capability migrations will be added in two steps: + +- Upgrade handler migration which modifies the channel capability owner from the underlying app to the controller module +- ICS 27 module automatic migration which asserts the upgrade handler channel capability migration has been performed successfully + +See issue [#2033](https://github.com/cosmos/ibc-go/issues/2033) + +### Middleware enabled channels + +In order to maintain backwards compatibility and avoid requiring underlying application developers to account for interchain accounts they did not register, a boolean mapping has been added to track the behaviour of how an account was created. + +If the account was created via the legacy API, then the underlying application callbacks will be executed. + +If the account was created with the new API (message server), then the underlying application callbacks will not be executed. + +See issue [#2145](https://github.com/cosmos/ibc-go/issues/2145) + +### Future considerations + +[ADR 008](https://github.com/cosmos/ibc-go/pull/1976) proposes the creation of a middleware which enables callers of an IBC packet send to perform application logic in conjunction with the IBC application. +The underlying application can be removed at the availability of such a middleware as that will be the preferred method for executing application logic upon a ICS 27 packet send. + +### Miscellaneous + +In order to avoid import cycles, the genesis types have been moved to their own directory. +A new protobuf package has been created for the genesis types. + +See PR [#2133](https://github.com/cosmos/ibc-go/pull/2133) + +An additional field has been added to the `ActiveChannel` type to store the `IsMiddlewareEnabled` field upon genesis import/export. + +See issue [#2165](https://github.com/cosmos/ibc-go/issues/2165) + +## Consequences + +### Positive + +- default authentication modules are provided (x/auth, x/group, x/gov) +- any SDK authentication module may now be used with ICS 27 +- separation of authentication from application logic in relation to ICS 27 +- minimized disruption to existing development around ICS 27 controller module +- underlying applications no longer have to handle capabilities +- removal of the underlying application upon the creation of ADR 008 may be done in a minimally disruptive fashion +- only underlying applications which registered the interchain account will perform application logic for that account (underlying applications do not need to be aware of accounts they did not register) + +### Negative + +- the security model has been reduced to that of the SDK. SDK modules may send packets for any interchain account. +- additional maintenance of the messages added and the middleware enabled flag +- underlying applications which will become ADR 008 modules are not required to be aware of accounts they did not register +- calling legacy API vs the new API results in different behaviour for ICS 27 application stacks which have an underlying application + +### Neutral + +- A major release is required diff --git a/docs/architecture/adr-010-light-clients-as-sdk-modules.md b/docs/architecture/adr-010-light-clients-as-sdk-modules.md new file mode 100644 index 0000000..dc8ade7 --- /dev/null +++ b/docs/architecture/adr-010-light-clients-as-sdk-modules.md @@ -0,0 +1,106 @@ +# ADR 010: IBC light clients as SDK modules + +## Changelog + +- 12/12/2022: initial draft + +## Status + +Proposed + +## Context + +ibc-go has 3 main consumers: + +- IBC light clients +- IBC applications +- relayers + +Relayers listen and respond to events emitted by ibc-go while IBC light clients and applications are invoked by core IBC. +Currently there exists two different approaches to callbacks being invoked by core IBC. + +IBC light clients currently are invoked by a `ClientState` and `ConsensusState` interface as defined by [core IBC](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L36). +The 02-client submodule will retrieve the `ClientState` or `ConsensusState` from the IBC store in order to perform callbacks to the light client. +This design requires all required information for the light client to function to be stored in the `ClientState` or `ConsensusState` or potentially under metadata keys for a specific client instance. +Additional information may be provided by core IBC via the defined interface arguments if that information is generic enough to be useful to all IBC light clients. +This constraint has proved problematic as pass through clients (such as wasm) cannot maintain easy access to a VM instance. +In addition, without increasing the size of the defined `ClientState` interface, light clients are unable to take advantage of basic built-in SDK functionality such as genesis import/export and migrations. + +The other approach used to perform callback logic is via registered SDK modules. +This approach is used by core IBC to interact with IBC applications. +IBC applications will register their callbacks on the IBC router at compile time. +When a packet comes in, core IBC will use the IBC router to lookup the registered callback functions for the provided packet. +The benefit of registered callbacks opposed to interface functions is that additional information may be accessed via external keepers. +Because the IBC applications are also SDK modules, they additionally get access to a host of functionality provided by the SDK. +This includes: genesis import/export, migrations, query/transaction CLI commands, type registration, gRPC query registration, and message server registration. + +As described in [ADR 006](./adr-006-02-client-refactor.md), generalizing light client behaviour is difficult. +IBC light clients will obtain greater flexibility and control via the registered SDK module approach. + +## Decision + +Instead of using two different approaches to invoking callbacks, IBC light clients should be invoked as SDK modules. +Over time and as necessary, core IBC should adjust its interactions with light clients such that they are SDK modules as opposed to interfaces. + +One immediate decision that has already been applied is to formalize light client type registration via the inclusion of an `AppModuleBasic` within the `ModuleManager` for a chain. +The [tendermint](https://github.com/cosmos/ibc-go/pull/2825) and [solo machine](https://github.com/cosmos/ibc-go/pull/2826) clients were refactored to include this `AppModuleBasic` implementation and core IBC will no longer include either type as registered by default. + +Longer term solutions include using internal module communication as described in [ADR 033](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-033-protobuf-inter-module-comm.md) on the SDK. +The following functions should become callbacks invoked via intermodule communication: + +- `Status` +- `GetTimestampAtHeight` +- `VerifyMembership` +- `VerifyNonMembership` +- `Initialize` +- `VerifyClientMessage` +- `CheckForMisbehaviour` +- `UpdateStateOnMisbehaviour` +- `UpdateState` +- `CheckSubstituteAndUpdateState` +- `VerifyUpgradeAndUpdateState` + +The ClientState interface should eventually be trimmed down to something along the lines of: + +```go +type ClientState interface { + proto.Message + + ClientType() string + GetLatestHeight() Height + Validate() error + + ZeroCustomFields() ClientState + + // ADDITION + Route() string // route used for intermodule communication +} +``` + +For the most part, any functions which require access to the client store should likely not be an interface function of the `ClientState`. + +`ExportMetadata` should eventually be replaced by a light client's ability to import/export it's own genesis information. + +### Intermodule communication + +To keep the transition from interface callbacks to SDK module callbacks as simple as possible, intermodule communication (when available) should be used to route to light client modules. +Without intermodule communication, a routing system would need to be developed/maintained to register callbacks. +This functionality of routing to another SDK module should and will be provided by the SDK. +Once it is possible to route to SDK modules, a `ClientState` type could expose the function `Route` which returns the callback route used to call the light client module. + +## Consequences + +### Positive + +- use a single approach for interacting with callbacks +- greater flexibility and control for IBC light clients +- does not require developing another routing system + +### Negative + +- requires breaking changes +- requires waiting for intermodule communication + +### Neutral + +N/A diff --git a/docs/architecture/adr-011-transfer-total-escrow-state-entry.md b/docs/architecture/adr-011-transfer-total-escrow-state-entry.md new file mode 100644 index 0000000..24e2f6d --- /dev/null +++ b/docs/architecture/adr-011-transfer-total-escrow-state-entry.md @@ -0,0 +1,145 @@ +# ADR 011: ICS-20 transfer state entry for total amount of tokens in escrow + +## Changelog + +- 2023-05-24: Initial draft + +## Status + +Accepted and applied in v7.1 of ibc-go + +## Context + +Every ICS-20 transfer channel has its own escrow bank account. This account is used to lock tokens that are transferred out of a chain that acts as the source of the tokens (i.e. when the tokens being transferred have not returned to the originating chain). This design makes it easy to query the balance of the escrow accounts and find out the total amount of tokens in escrow in a particular channel. However, there are use cases where it would be useful to determine the total escrowed amount of a given denomination across all channels where those tokens have been transferred out. + +For example: assuming that there are three channels between Cosmos Hub to Osmosis and 10 ATOM have been transferred from the Cosmos Hub to Osmosis on each of those channels, then we would like to know that 30 ATOM have been transferred (i.e. are locked in the escrow accounts of each channel) without needing to iterate over each escrow account to add up the balances of each. + +For a sample use case where this feature would be useful, please refer to Osmosis' rate limiting use case described in [#2664](https://github.com/cosmos/ibc-go/issues/2664). + +## Decision + +### State entry denom -> amount + +The total amount of tokens in escrow (across all transfer channels) for a given denomination is stored in state in an entry keyed by the denomination: `totalEscrowForDenom/{denom}`. + +### Panic if amount is negative + +If a negative amount is ever attempted to be stored, then the keeper function will panic: + +```go +if coin.Amount.IsNegative() { + panic(fmt.Sprintf("amount cannot be negative: %s", coin.Amount)) +} +``` + +### Delete state entry if amount is zero + +When setting the amount for a particular denomination, the value might be zero if all tokens that were transferred out of the chain have been transferred back. If this happens, then the state entry for this particular denomination will be deleted, since Cosmos SDK's `x/bank` module prunes any non-zero balances: + +```go +if coin.Amount.IsZero() { + store.Delete(key) // delete the key since Cosmos SDK x/bank module will prune any non-zero balances + return +} +``` + +### Bundle escrow/unescrow with setting state entry + +Two new functions are implemented that bundle together the operations of escrowing/unescrowing and setting the total escrow amount in state, since these operations need to be executed together. + +For escrowing tokens: + +```go +// escrowToken will send the given token from the provided sender to the escrow address. It will also +// update the total escrowed amount by adding the escrowed token to the current total escrow. +func (k Keeper) escrowToken(ctx sdk.Context, sender, escrowAddress sdk.AccAddress, token sdk.Coin) error { + if err := k.bankKeeper.SendCoins(ctx, sender, escrowAddress, sdk.NewCoins(token)); err != nil { + // failure is expected for insufficient balances + return err + } + + // track the total amount in escrow keyed by denomination to allow for efficient iteration + currentTotalEscrow := k.GetTotalEscrowForDenom(ctx, token.GetDenom()) + newTotalEscrow := currentTotalEscrow.Add(token) + k.SetTotalEscrowForDenom(ctx, newTotalEscrow) + + return nil +} +``` + +For unescrowing tokens: + +```go +// unescrowToken will send the given token from the escrow address to the provided receiver. It will also +// update the total escrow by deducting the unescrowed token from the current total escrow. +func (k Keeper) unescrowToken(ctx sdk.Context, escrowAddress, receiver sdk.AccAddress, token sdk.Coin) error { + if err := k.bankKeeper.SendCoins(ctx, escrowAddress, receiver, sdk.NewCoins(token)); err != nil { + // NOTE: this error is only expected to occur given an unexpected bug or a malicious + // counterparty module. The bug may occur in bank or any part of the code that allows + // the escrow address to be drained. A malicious counterparty module could drain the + // escrow address by allowing more tokens to be sent back then were escrowed. + return errorsmod.Wrap(err, "unable to unescrow tokens, this may be caused by a malicious counterparty module or a bug: please open an issue on counterparty module") + } + + // track the total amount in escrow keyed by denomination to allow for efficient iteration + currentTotalEscrow := k.GetTotalEscrowForDenom(ctx, token.GetDenom()) + newTotalEscrow := currentTotalEscrow.Sub(token) + k.SetTotalEscrowForDenom(ctx, newTotalEscrow) + + return nil +} +``` + +When tokens need to be escrowed in `sendTransfer`, then `escrowToken` is called; when tokens need to be unescrowed on execution of the `OnRecvPacket`, `OnAcknowledgementPacket` or `OnTimeoutPacket` callbacks, then `unescrowToken` is called. + +### gRPC query endpoint and CLI to retrieve amount + +A gRPC query endpoint is added so that it is possible to retrieve the total amount for a given denomination: + +```proto +// TotalEscrowForDenom returns the total amount of tokens in escrow based on the denom. +rpc TotalEscrowForDenom(QueryTotalEscrowForDenomRequest) returns (QueryTotalEscrowForDenomResponse) { + option (google.api.http).get = "/ibc/apps/transfer/v1/denoms/{denom=**}/total_escrow"; +} + +// QueryTotalEscrowForDenomRequest is the request type for TotalEscrowForDenom RPC method. +message QueryTotalEscrowForDenomRequest { + string denom = 1; +} + +// QueryTotalEscrowForDenomResponse is the response type for TotalEscrowForDenom RPC method. +message QueryTotalEscrowForDenomResponse { + cosmos.base.v1beta1.Coin amount = 1 [(gogoproto.nullable) = false]; +} +``` + +And a CLI query is also available to retrieve the total amount via the command line: + +```shell +query ibc-transfer total-escrow [denom] +``` + +## Consequences + +### Positive + +- Possibility to retrieve the total amount of a particular denomination in escrow across all transfer channels without iteration. + +### Negative + +No notable consequences + +### Neutral + +- A new entry is added to state for every denomination that is transferred out of the chain. + +## References + +Issues: + +- [#2664](https://github.com/cosmos/ibc-go/issues/2664) + +PRs: + +- [#3019](https://github.com/cosmos/ibc-go/pull/3019) +- [#3558](https://github.com/cosmos/ibc-go/pull/3558) diff --git a/docs/architecture/adr-015-ibc-packet-receiver.md b/docs/architecture/adr-015-ibc-packet-receiver.md new file mode 100644 index 0000000..37e5b79 --- /dev/null +++ b/docs/architecture/adr-015-ibc-packet-receiver.md @@ -0,0 +1,299 @@ +# ADR 015: IBC Packet Receiver + +## Changelog + +- 2019 Oct 22: Initial Draft + +## Context + +[ICS 26 - Routing Module](https://github.com/cosmos/ibc/tree/master/spec/core/ics-026-routing-module) defines a function [`handlePacketRecv`](https://github.com/cosmos/ibc/tree/master/spec/core/ics-026-routing-module#packet-relay). + +In ICS 26, the routing module is defined as a layer above each application module +which verifies and routes messages to the destination modules. It is possible to +implement it as a separate module, however, we already have the functionality to route +messages upon the destination identifiers in the baseapp. This ADR suggests +to utilize existing `baseapp.router` to route packets to application modules. + +Generally, routing module callbacks have two separate steps in them, +verification and execution. This corresponds to the `AnteHandler`-`Handler` +model inside the SDK. We can do the verification inside the `AnteHandler` +in order to increase developer ergonomics by reducing boilerplate +verification code. + +For atomic multi-message transaction, we want to keep the IBC related +state modification to be preserved even the application side state change +reverts. One of the example might be IBC token sending message following with +stake delegation which uses the tokens received by the previous packet message. +If the token receiving fails for any reason, we might not want to keep +executing the transaction, but we also don't want to abort the transaction +or the sequence and commitment will be reverted and the channel will be stuck. +This ADR suggests new `CodeType`, `CodeTxBreak`, to fix this problem. + +## Decision + +`PortKeeper` will have the capability key that is able to access only the +channels bound to the port. Entities that hold a `PortKeeper` will be +able to call the methods on it which are corresponding with the methods with +the same names on the `ChannelKeeper`, but only with the +allowed port. `ChannelKeeper.Port(string, ChannelChecker)` will be defined to +easily construct a capability-safe `PortKeeper`. This will be addressed in +another ADR and we will use insecure `ChannelKeeper` for now. + +`baseapp.runMsgs` will break the loop over the messages if one of the handlers +returns `!Result.IsOK()`. However, the outer logic will write the cached +store if `Result.IsOK() || Result.Code.IsBreak()`. `Result.Code.IsBreak()` if +`Result.Code == CodeTxBreak`. + +```go +func (app *BaseApp) runTx(tx Tx) (result Result) { + msgs := tx.GetMsgs() + + // AnteHandler + if app.anteHandler != nil { + anteCtx, msCache := app.cacheTxContext(ctx) + newCtx, err := app.anteHandler(anteCtx, tx) + if !newCtx.IsZero() { + ctx = newCtx.WithMultiStore(ms) + } + + if err != nil { + // error handling logic + return res + } + + msCache.Write() + } + + // Main Handler + runMsgCtx, msCache := app.cacheTxContext(ctx) + result = app.runMsgs(runMsgCtx, msgs) + // BEGIN modification made in this ADR + if result.IsOK() || result.IsBreak() { + // END + msCache.Write() + } + + return result +} +``` + +The Cosmos SDK will define an `AnteDecorator` for IBC packet receiving. The +`AnteDecorator` will iterate over the messages included in the transaction, type +`switch` to check whether the message contains an incoming IBC packet, and if so +verify the Merkle proof. + +```go +type ProofVerificationDecorator struct { + clientKeeper ClientKeeper + channelKeeper ChannelKeeper +} + +func (pvr ProofVerificationDecorator) AnteHandle(ctx Context, tx Tx, simulate bool, next AnteHandler) (Context, error) { + for _, msg := range tx.GetMsgs() { + var err error + switch msg := msg.(type) { + case client.MsgUpdateClient: + err = pvr.clientKeeper.UpdateClient(msg.ClientID, msg.Header) + case channel.MsgPacket: + err = pvr.channelKeeper.RecvPacket(msg.Packet, msg.Proofs, msg.ProofHeight) + case channel.MsgAcknowledgement: + err = pvr.channelKeeper.AcknowledgementPacket(msg.Acknowledgement, msg.Proof, msg.ProofHeight) + case channel.MsgTimeoutPacket: + err = pvr.channelKeeper.TimeoutPacket(msg.Packet, msg.Proof, msg.ProofHeight, msg.NextSequenceRecv) + case channel.MsgChannelOpenInit; + err = pvr.channelKeeper.CheckOpen(msg.PortID, msg.ChannelID, msg.Channel) + default: + continue + } + + if err != nil { + return ctx, err + } + } + + return next(ctx, tx, simulate) +} +``` + +Where `MsgUpdateClient`, `MsgPacket`, `MsgAcknowledgement`, `MsgTimeoutPacket` +are `sdk.Msg` types correspond to `handleUpdateClient`, `handleRecvPacket`, +`handleAcknowledgementPacket`, `handleTimeoutPacket` of the routing module, +respectively. + +The side effects of `RecvPacket`, `VerifyAcknowledgement`, +`VerifyTimeout` will be extracted out into separated functions, +`WriteAcknowledgement`, `DeleteCommitment`, `DeleteCommitmentTimeout`, respectively, +which will be called by the application handlers after the execution. + +`WriteAcknowledgement` writes the acknowledgement to the state that can be +verified by the counter-party chain and increments the sequence to prevent +double execution. `DeleteCommitment` will delete the commitment stored, +`DeleteCommitmentTimeout` will delete the commitment and close channel in case +of ordered channel. + +```go +func (keeper ChannelKeeper) WriteAcknowledgement(ctx Context, packet Packet, ack []byte) { + keeper.SetPacketAcknowledgement(ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), ack) + keeper.SetNextSequenceRecv(ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) +} + +func (keeper ChannelKeeper) DeleteCommitment(ctx Context, packet Packet) { + keeper.deletePacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) +} + +func (keeper ChannelKeeper) DeleteCommitmentTimeout(ctx Context, packet Packet) { + k.deletePacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + + if channel.Ordering == types.ORDERED [ + channel.State = types.CLOSED + k.SetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), channel) + } +} +``` + +Each application handler should call respective finalization methods on the `PortKeeper` +in order to increase sequence (in case of packet) or remove the commitment +(in case of acknowledgement and timeout). +Calling those functions implies that the application logic has successfully executed. +However, the handlers can return `Result` with `CodeTxBreak` after calling those methods +which will persist the state changes that has been already done but prevent any further +messages to be executed in case of semantically invalid packet. This will keep the sequence +increased in the previous IBC packets(thus preventing double execution) without +proceeding to the following messages. +In any case the application modules should never return state reverting result, +which will make the channel unable to proceed. + +`ChannelKeeper.CheckOpen` method will be introduced. This will replace `onChanOpen*` defined +under the routing module specification. Instead of define each channel handshake callback +functions, application modules can provide `ChannelChecker` function with the `AppModule` +which will be injected to `ChannelKeeper.Port()` at the top level application. +`CheckOpen` will find the correct `ChannelChecker` using the +`PortID` and call it, which will return an error if it is unacceptable by the application. + +The `ProofVerificationDecorator` will be inserted to the top level application. +It is not safe to make each module responsible to call proof verification +logic, whereas application can misbehave(in terms of IBC protocol) by +mistake. + +The `ProofVerificationDecorator` should come right after the default sybil attack +resistant layer from the current `auth.NewAnteHandler`: + +```go +// add IBC ProofVerificationDecorator to the Chain of +func NewAnteHandler( + ak keeper.AccountKeeper, supplyKeeper types.SupplyKeeper, ibcKeeper ibc.Keeper, + sigGasConsumer SignatureVerificationGasConsumer) sdk.AnteHandler { + return sdk.ChainAnteDecorators( + NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first + ... + NewIncrementSequenceDecorator(ak), + ibcante.ProofVerificationDecorator(ibcKeeper.ClientKeeper, ibcKeeper.ChannelKeeper), // innermost AnteDecorator + ) +} +``` + +The implementation of this ADR will also create a `Data` field of the `Packet` of type `[]byte`, which can be deserialised by the receiving module into its own private type. It is up to the application modules to do this according to their own interpretation, not by the IBC keeper. This is crucial for dynamic IBC. + +Example application-side usage: + +```go +type AppModule struct {} + +// CheckChannel will be provided to the ChannelKeeper as ChannelKeeper.Port(module.CheckChannel) +func (module AppModule) CheckChannel(portID, channelID string, channel Channel) error { + if channel.Ordering != UNORDERED { + return ErrUncompatibleOrdering() + } + if channel.CounterpartyPort != "bank" { + return ErrUncompatiblePort() + } + if channel.Version != "" { + return ErrUncompatibleVersion() + } + return nil +} + +func NewHandler(k Keeper) Handler { + return func(ctx Context, msg Msg) Result { + switch msg := msg.(type) { + case MsgTransfer: + return handleMsgTransfer(ctx, k, msg) + case ibc.MsgPacket: + var data PacketDataTransfer + if err := types.ModuleCodec.UnmarshalBinaryBare(msg.GetData(), &data); err != nil { + return err + } + return handlePacketDataTransfer(ctx, k, msg, data) + case ibc.MsgTimeoutPacket: + var data PacketDataTransfer + if err := types.ModuleCodec.UnmarshalBinaryBare(msg.GetData(), &data); err != nil { + return err + } + return handleTimeoutPacketDataTransfer(ctx, k, packet) + // interface { PortID() string; ChannelID() string; Channel() ibc.Channel } + // MsgChanInit, MsgChanTry implements ibc.MsgChannelOpen + case ibc.MsgChannelOpen: + return handleMsgChannelOpen(ctx, k, msg) + } + } +} + +func handleMsgTransfer(ctx Context, k Keeper, msg MsgTransfer) Result { + err := k.SendTransfer(ctx,msg.PortID, msg.ChannelID, msg.Amount, msg.Sender, msg.Receiver) + if err != nil { + return sdk.ResultFromError(err) + } + + return sdk.Result{} +} + +func handlePacketDataTransfer(ctx Context, k Keeper, packet Packet, data PacketDataTransfer) Result { + err := k.ReceiveTransfer(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetDestinationPort(), packet.GetDestinationChannel(), data) + if err != nil { + // TODO: Source chain sent invalid packet, shutdown channel + } + k.ChannelKeeper.WriteAcknowledgement([]byte{0x00}) // WriteAcknowledgement increases the sequence, preventing double spending + return sdk.Result{} +} + +func handleCustomTimeoutPacket(ctx Context, k Keeper, packet CustomPacket) Result { + err := k.RecoverTransfer(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetDestinationPort(), packet.GetDestinationChannel(), data) + if err != nil { + // This chain sent invalid packet or cannot recover the funds + panic(err) + } + k.ChannelKeeper.DeleteCommitmentTimeout(ctx, packet) + // packet timeout should not fail + return sdk.Result{} +} + +func handleMsgChannelOpen(sdk.Context, k Keeper, msg MsgOpenChannel) Result { + k.AllocateEscrowAddress(ctx, msg.ChannelID()) + return sdk.Result{} +} +``` + +## Status + +Proposed + +## Consequences + +### Positive + +- Intuitive interface for developers - IBC handlers do not need to care about IBC authentication +- State change commitment logic is embedded into `baseapp.runTx` logic + +### Negative + +- Cannot support dynamic ports, routing is tied to the baseapp router + +### Neutral + +- Introduces new `AnteHandler` decorator. +- Dynamic ports can be supported using hierarchical port identifier, see #5290 for detail + +## References + +- Relevant comment: [cosmos/ics#289](https://github.com/cosmos/ibc/issues/289#issuecomment-544533583) +- [ICS26 - Routing Module](https://github.com/cosmos/ibc/tree/master/spec/core/ics-026-routing-module) diff --git a/docs/architecture/adr-025-ibc-passive-channels.md b/docs/architecture/adr-025-ibc-passive-channels.md new file mode 100644 index 0000000..c8f921a --- /dev/null +++ b/docs/architecture/adr-025-ibc-passive-channels.md @@ -0,0 +1,141 @@ +# ADR 025: IBC Passive Channels + +## Changelog + +- 2021-04-23: Change status to "deprecated" +- 2020-05-23: Provide sample Go code and more details +- 2020-05-18: Initial Draft + +## Status + +*deprecated* + +## Context + +The current "naive" IBC Relayer strategy currently establishes a single predetermined IBC channel atop a single connection between two clients (each potentially of a different chain). This strategy then detects packets to be relayed by watching for `send_packet` and `recv_packet` events matching that channel, and sends the necessary transactions to relay those packets. + +We wish to expand this "naive" strategy to a "passive" one which detects and relays both channel handshake messages and packets on a given connection, without the need to know each channel in advance of relaying it. + +In order to accomplish this, we propose adding more comprehensive events to expose channel metadata for each transaction sent from the `x/ibc/core/04-channel/keeper/handshake.go` and `x/ibc/core/04-channel/keeper/packet.go` modules. + +Here is an example of what would be in `ChanOpenInit`: + +```go +const ( + EventTypeChannelMeta = "channel_meta" + AttributeKeyAction = "action" + AttributeKeyHops = "hops" + AttributeKeyOrder = "order" + AttributeKeySrcPort = "src_port" + AttributeKeySrcChannel = "src_channel" + AttributeKeySrcVersion = "src_version" + AttributeKeyDstPort = "dst_port" + AttributeKeyDstChannel = "dst_channel" + AttributeKeyDstVersion = "dst_version" +) +// ... +// Emit Event with Channel metadata for the relayer to pick up and +// relay to the other chain +// This appears immediately before the successful return statement. +ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeChannelMeta, + sdk.NewAttribute(types.AttributeKeyAction, "open_init"), + sdk.NewAttribute(types.AttributeKeySrcConnection, connectionHops[0]), + sdk.NewAttribute(types.AttributeKeyHops, strings.Join(connectionHops, ",")), + sdk.NewAttribute(types.AttributeKeyOrder, order.String()), + sdk.NewAttribute(types.AttributeKeySrcPort, portID), + sdk.NewAttribute(types.AttributeKeySrcChannel, channelID), + sdk.NewAttribute(types.AttributeKeySrcVersion, version), + sdk.NewAttribute(types.AttributeKeyDstPort, counterparty.GetPortID()), + sdk.NewAttribute(types.AttributeKeyDstChannel, counterparty.GetChannelID()), + // The destination version is not yet known, but a value is necessary to pad + // the event attribute offsets + sdk.NewAttribute(types.AttributeKeyDstVersion, ""), + ), +}) +``` + +These metadata events capture all the "header" information needed to route IBC channel handshake transactions without requiring the client to query any data except that of the connection ID that it is willing to relay. It is intended that `channel_meta.src_connection` is the only event key that needs to be indexed for a passive relayer to function. + +### Handling Channel Open Attempts + +In the case of the passive relayer, when one chain sends a `ChanOpenInit`, the relayer should inform the other chain of this open attempt and allow that chain to decide how (and if) it continues the handshake. Once both chains have actively approved the channel opening, then the rest of the handshake can happen as it does with the current "naive" relayer. + +To implement this behavior, we propose replacing the `cbs.OnChanOpenTry` callback with a new `cbs.OnAttemptChanOpenTry` callback which explicitly handles the `MsgChannelOpenTry`, usually by resulting in a call to `keeper.ChanOpenTry`. The typical implementation, in `x/ibc-transfer/module.go` would be compatible with the current "naive" relayer, as follows: + +```go +func (am AppModule) OnAttemptChanOpenTry( + ctx sdk.Context, + chanKeeper channel.Keeper, + portCap *capability.Capability, + msg channel.MsgChannelOpenTry, +) (*sdk.Result, error) { + // Require portID is the portID transfer module is bound to + boundPort := am.keeper.GetPort(ctx) + if boundPort != msg.PortID { + return nil, sdkerrors.Wrapf(porttypes.ErrInvalidPort, "invalid port: %s, expected %s", msg.PortID, boundPort) + } + + // BEGIN NEW CODE + // Assert our protocol version, overriding the relayer's suggestion. + msg.Version = types.Version + // Continue the ChanOpenTry. + res, chanCap, err := channel.HandleMsgChannelOpenTry(ctx, chanKeeper, portCap, msg) + if err != nil { + return nil, err + } + // END OF NEW CODE + + // ... the rest of the callback is similar to the existing OnChanOpenTry + // but uses msg.* directly. +``` + +Here is how this callback would be used, in the implementation of `x/ibc/handler.go`: + +```go +// ... +case channel.MsgChannelOpenTry: + // Lookup module by port capability + module, portCap, err := k.PortKeeper.LookupModuleByPort(ctx, msg.PortID) + if err != nil { + return nil, sdkerrors.Wrap(err, "could not retrieve module from port-id") + } + // Retrieve callbacks from router + cbs, ok := k.Router.GetRoute(module) + if !ok { + return nil, sdkerrors.Wrapf(port.ErrInvalidRoute, "route not found to module: %s", module) + } + // Delegate to the module's OnAttemptChanOpenTry. + return cbs.OnAttemptChanOpenTry(ctx, k.ChannelKeeper, portCap, msg) +``` + +The reason we do not have a more structured interaction between `x/ibc/handler.go` and the port's module (to explicitly negotiate versions, etc) is that we do not wish to constrain the app module to have to finish handling the `MsgChannelOpenTry` during this transaction or even this block. + +## Decision + +- Expose events to allow "passive" connection relayers. +- Enable application-initiated channels via such passive relayers. +- Allow port modules to control how to handle open-try messages. + +## Consequences + +### Positive + +Makes channels into a complete application-level abstraction. + +Applications have full control over initiating and accepting channels, rather than expecting a relayer to tell them when to do so. + +A passive relayer does not have to know what kind of channel (version string, ordering constraints, firewalling logic) the application supports. These are negotiated directly between applications. + +### Negative + +Increased event size for IBC messages. + +### Neutral + +More IBC events are exposed. + +## References + +- The Agoric VM's IBC handler currently [accommodates `attemptChanOpenTry`](https://github.com/Agoric/agoric-sdk/blob/904b3a0423222a1b32893453e44bbde598473960/packages/cosmic-swingset/lib/ag-solo/vats/ibc.js#L546) diff --git a/docs/architecture/adr-026-ibc-client-recovery-mechanisms.md b/docs/architecture/adr-026-ibc-client-recovery-mechanisms.md new file mode 100644 index 0000000..2ab6772 --- /dev/null +++ b/docs/architecture/adr-026-ibc-client-recovery-mechanisms.md @@ -0,0 +1,90 @@ +# ADR 026: IBC Client Recovery Mechanisms + +## Changelog + +- 2020/06/23: Initial version +- 2020/08/06: Revisions per review & to reference version +- 2021/01/15: Revision to support substitute clients for unfreezing +- 2021/05/20: Revision to simplify consensus state copying, remove initial height +- 2022/04/08: Revision to deprecate AllowUpdateAfterExpiry and AllowUpdateAfterMisbehaviour +- 2022/07/15: Revision to allow updating of TrustingPeriod +- 2023/09/05: Revision to migrate from gov v1beta1 to gov v1 + +## Status + +*Accepted* + +## Context + +### Summary + +At launch, IBC will be a novel protocol, without an experienced user-base. At the protocol layer, it is not possible to distinguish between client expiry or misbehaviour due to genuine faults (Byzantine behaviour) and client expiry or misbehaviour due to user mistakes (failing to update a client, or accidentally double-signing). In the base IBC protocol and ICS 20 fungible token transfer implementation, if a client can no longer be updated, funds in that channel will be permanently locked and can no longer be transferred. To the degree that it is safe to do so, it would be preferable to provide users with a recovery mechanism which can be utilised in these exceptional cases. + +### Exceptional cases + +The state of concern is where a client associated with connection(s) and channel(s) can no longer be updated. This can happen for several reasons: + +1. The chain which the client is following has halted and is no longer producing blocks/headers, so no updates can be made to the client +1. The chain which the client is following has continued to operate, but no relayer has submitted a new header within the unbonding period, and the client has expired + 1. This could be due to real misbehaviour (intentional Byzantine behaviour) or merely a mistake by validators, but the client cannot distinguish these two cases +1. The chain which the client is following has experienced a misbehaviour event, and the client has been frozen & thus can no longer be updated + +### Security model + +Two-thirds of the validator set (the quorum for governance, module participation) can already sign arbitrary data, so allowing governance to manually force-update a client with a new header after a delay period does not substantially alter the security model. + +## Decision + +We elect not to deal with chains which have actually halted, which is necessarily Byzantine behaviour and in which case token recovery is not likely possible anyways (in-flight packets cannot be timed-out, but the relative impact of that is minor). + +1. Require Tendermint light clients (ICS 07) to be created with the following additional flags + 1. `allow_update_after_expiry` (boolean, default true). Note that this flag has been deprecated, it remains to signal intent but checks against this value will not be enforced. +1. Require Tendermint light clients (ICS 07) to expose the following additional internal query functions + 1. `Expired() boolean`, which returns whether or not the client has passed the trusting period since the last update (in which case no headers can be validated) +1. Require Tendermint light clients (ICS 07) & solo machine clients (ICS 06) to be created with the following additional flags + 1. `allow_update_after_misbehaviour` (boolean, default true). Note that this flag has been deprecated, it remains to signal intent but checks against this value will not be enforced. +1. Require Tendermint light clients (ICS 07) to expose the following additional state mutation functions + 1. `Unfreeze()`, which unfreezes a light client after misbehaviour and clears any frozen height previously set +1. Add a new governance proposal with `MsgRecoverClient`. + 1. Create a new Msg with two client identifiers (`string`) and a signer. + 1. The first client identifier is the proposed client to be updated. This client must be either frozen or expired. + 1. The second client is a substitute client. It carries all the state for the client which may be updated. It must have identical client and chain parameters to the client which may be updated (except for latest height, frozen height, and chain-id). It should be continually updated during the voting period. + 1. If this governance proposal passes, the client on trial will be updated to the latest state of the substitute. + 1. The signer must be the authority set for the ibc module. + + Previously, `AllowUpdateAfterExpiry` and `AllowUpdateAfterMisbehaviour` were used to signal the recovery options for an expired or frozen client, and governance proposals were not allowed to overwrite the client if these parameters were set to false. However, this has now been deprecated because a code migration can overwrite the client and consensus states regardless of the value of these parameters. If governance would vote to overwrite a client or consensus state, it is likely that governance would also be willing to perform a code migration to do the same. + + In addition, `TrustingPeriod` was initially not allowed to be updated by a client upgrade proposal. However, due to the number of situations experienced in production where the `TrustingPeriod` of a client should be allowed to be updated because of ie: initial misconfiguration for a canonical channel, governance should be allowed to update this client parameter. + + In versions older than ibc-go v8, `MsgRecoverClient` was a governance proposal type `ClientUpdateProposal`. It has been removed and replaced by `MsgRecoverClient` in the migration from governance v1beta1 to governance v1. + + Note that this should NOT be lightly updated, as there may be a gap in time between when misbehaviour has occurred and when the evidence of misbehaviour is submitted. For example, if the `UnbondingPeriod` is 2 weeks and the `TrustingPeriod` has also been set to two weeks, a validator could wait until right before `UnbondingPeriod` finishes, submit false information, then unbond and exit without being slashed for misbehaviour. Therefore, we recommend that the trusting period for the 07-tendermint client be set to 2/3 of the `UnbondingPeriod`. + +Note that clients frozen due to misbehaviour must wait for the evidence to expire to avoid becoming refrozen. + +This ADR does not address planned upgrades, which are handled separately as per the [specification](https://github.com/cosmos/ibc/tree/master/spec/client/ics-007-tendermint-client#upgrades). + +## Consequences + +### Positive + +- Establishes a mechanism for client recovery in the case of expiry +- Establishes a mechanism for client recovery in the case of misbehaviour +- Constructing an ClientUpdate Proposal is as difficult as creating a new client + +### Negative + +- Additional complexity in client creation which must be understood by the user +- Coping state of the substitute adds complexity +- Governance participants must vote on a substitute client + +### Neutral + +No neutral consequences. + +## References + +- [Prior discussion](https://github.com/cosmos/ibc/issues/421) +- [Epoch number discussion](https://github.com/cosmos/ibc/issues/439) +- [Upgrade plan discussion](https://github.com/cosmos/ibc/issues/445) +- [Migration from gov v1beta1 to gov v1](https://github.com/cosmos/ibc-go/issues/3672) diff --git a/docs/architecture/adr-027-ibc-wasm.md b/docs/architecture/adr-027-ibc-wasm.md new file mode 100644 index 0000000..122c820 --- /dev/null +++ b/docs/architecture/adr-027-ibc-wasm.md @@ -0,0 +1,167 @@ +# ADR 27: Add support for Wasm based light client + +## Changelog + +- 26/11/2020: Initial Draft +- 26/05/2023: Update after 02-client refactor and re-implementation by Strangelove +- 13/12/2023: Update after upstreaming of module to ibc-go + +## Status + +*Accepted and applied in v0.1.0 of 08-wasm* + +## Abstract + +In the Cosmos SDK light clients are currently hardcoded in Go. This makes upgrading existing IBC light clients or +adding support for new light client a multi step process involving on-chain governance which is time-consuming. + +To remedy this, we are proposing a Wasm VM to host light client bytecode, which allows easier upgrading of +existing IBC light clients as well as adding support for new IBC light clients without requiring a code release and +corresponding hard-fork event. + +## Context + +Currently in ibc-go light clients are defined as part of the codebase and are implemented as modules under +`modules/light-clients`. Adding support for new light clients or updating an existing light client in the event +of a security issue or consensus update is a multi-step process which is both time-consuming and error-prone. +In order to enable new IBC light client implementations it is necessary to modify the codebase of ibc-go (if the light +client is part of its codebase), re-build chains' binaries, pass a governance proposal and validators upgrade their nodes. + +Another problem stemming from the above process is that if a chain wants to upgrade its own consensus, it will +need to convince every chain or hub connected to it to upgrade its light client in order to stay connected. Due +to the time-consuming process required to upgrade a light client, a chain with lots of connections needs to be +disconnected for quite some time after upgrading its consensus, which can be very expensive in terms of time and effort. + +We are proposing simplifying this workflow by integrating a Wasm light client module that makes adding support for +new light clients a simple governance-gated transaction. The light client bytecode, written in Wasm-compilable Rust, +runs inside a Wasm VM. The Wasm light client submodule exposes a proxy light client interface that routes incoming +messages to the appropriate handler function, inside the Wasm VM for execution. + +With the Wasm light client module, anybody can add new IBC light client in the form of Wasm bytecode (provided they are +able to submit the governance proposal transaction and that it passes) as well as instantiate clients using any created +client type. This allows any chain to update its own light client in other chains without going through the steps outlined above. + +## Decision + +We decided to implement the Wasm light client module as a light client proxy that will interface with the actual light client +uploaded as Wasm bytecode. To enable usage of the Wasm light client module, users need to add it to the list of allowed clients +by updating the `AllowedClients` parameter in the 02-client submodule of core IBC. + +```go +params := clientKeeper.GetParams(ctx) +params.AllowedClients = append(params.AllowedClients, exported.Wasm) +clientKeeper.SetParams(ctx, params) +``` + +Adding a new light client contract is governance-gated. To upload a new light client users need to submit +a [governance v1 proposal](https://docs.cosmos.network/main/modules/gov#proposals) that contains the `sdk.Msg` for storing +the Wasm contract's bytecode. The required message is `MsgStoreCode` and the bytecode is provided in the field `wasm_byte_code`: + +```proto +// MsgStoreCode defines the request type for the StoreCode rpc. +message MsgStoreCode { + // signer address + string signer = 1; + // wasm byte code of light client contract. It can be raw or gzip compressed + bytes wasm_byte_code = 2; +} +``` + +The RPC handler processing `MsgStoreCode` will make sure that the signer of the message matches the address of authority allowed to +submit this message (which is normally the address of the governance module). + +```go +// StoreCode defines a rpc handler method for MsgStoreCode +func (k Keeper) StoreCode(goCtx context.Context, msg *types.MsgStoreCode) (*types.MsgStoreCodeResponse, error) { + if k.GetAuthority() != msg.Signer { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "expected %s, got %s", k.GetAuthority(), msg.Signer) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + checksum, err := k.storeWasmCode(ctx, msg.WasmByteCode, ibcwasm.GetVM().StoreCode) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to store wasm bytecode") + } + + emitStoreWasmCodeEvent(ctx, checksum) + + return &types.MsgStoreCodeResponse{ + Checksum: checksum, + }, nil +} +``` + +The contract's bytecode is not stored in state (it is actually unnecessary and wasteful to store it, since +the Wasm VM already stores it and can be queried back, if needed). The checksum is simply the hash of the bytecode +of the contract and it is stored in state in an entry with key `checksums` that contains the checksums for the bytecodes that have been stored. + +### How light client proxy works? + +The light client proxy behind the scenes will call a CosmWasm smart contract instance with incoming arguments serialized +in JSON format with appropriate environment information. Data returned by the smart contract is deserialized and +returned to the caller. + +Consider the example of the `VerifyClientMessage` function of `ClientState` interface. Incoming arguments are +packaged inside a payload object that is then JSON serialized and passed to `queryContract`, which executes `WasmVm.Query` +and returns the slice of bytes returned by the smart contract. This data is deserialized and passed as return argument. + +```go +type QueryMsg struct { + Status *StatusMsg `json:"status,omitempty"` + ExportMetadata *ExportMetadataMsg `json:"export_metadata,omitempty"` + TimestampAtHeight *TimestampAtHeightMsg `json:"timestamp_at_height,omitempty"` + VerifyClientMessage *VerifyClientMessageMsg `json:"verify_client_message,omitempty"` + CheckForMisbehaviour *CheckForMisbehaviourMsg `json:"check_for_misbehaviour,omitempty"` +} + +type verifyClientMessageMsg struct { + ClientMessage *ClientMessage `json:"client_message"` +} + +// VerifyClientMessage must verify a ClientMessage. +// A ClientMessage could be a Header, Misbehaviour, or batch update. +// It must handle each type of ClientMessage appropriately. +// Calls to CheckForMisbehaviour, UpdateStaåte, and UpdateStateOnMisbehaviour +// will assume that the content of the ClientMessage has been verified +// and can be trusted. An error should be returned +// if the ClientMessage fails to verify. +func (cs ClientState) VerifyClientMessage( + ctx sdk.Context, + _ codec.BinaryCodec, + clientStore storetypes.KVStore, + clientMsg exported.ClientMessage +) error { + clientMessage, ok := clientMsg.(*ClientMessage) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected type: %T, got: %T", &ClientMessage{}, clientMsg) + } + + payload := QueryMsg{ + VerifyClientMessage: &VerifyClientMessageMsg{ClientMessage: clientMessage.Data}, + } + _, err := wasmQuery[EmptyResult](ctx, clientStore, &cs, payload) + return err +} +``` + +### Global Wasm VM variable + +The 08-wasm keeper structure keeps a reference to the Wasm VM instantiated in the keeper constructor function. The keeper uses +the Wasm VM to store the bytecode of light client contracts. However, the Wasm VM is also needed in the 08-wasm implementations of +some of the `ClientState` interface functions to initialise a contract, execute calls on the contract and query the contract. Since +the `ClientState` functions do not have access to the 08-wasm keeper, then it has been decided to keep a global pointer variable that +points to the same instance as the one in the 08-wasm keeper. This global pointer variable is then used in the implementations of +the `ClientState` functions. + +## Consequences + +### Positive + +- Adding support for new light client or upgrading existing light client is way easier than before and only requires single transaction instead of a hard-fork. +- Improves maintainability of ibc-go, since no change in codebase is required to support new client or upgrade it. +- The existence of support for Rust dependencies in light clients which may not exist in Go. + +### Negative + +- Light clients written in Rust need to be written in a subset of Rust which could compile in Wasm. +- Introspecting light client code is difficult as only compiled bytecode exists in the blockchain. diff --git a/docs/architecture/adr.template.md b/docs/architecture/adr.template.md new file mode 100644 index 0000000..4a9d2ef --- /dev/null +++ b/docs/architecture/adr.template.md @@ -0,0 +1,38 @@ +# ADR \{ADR-NUMBER\}: \{TITLE\} + +## Changelog + +- \{date\}: \{changelog\} + +## Status + +> A decision may be "proposed" if it hasn't been agreed upon yet, or "accepted" once it is agreed upon. If a later ADR changes or reverses a decision, it may be marked as "deprecated" or "superseded" with a reference to its replacement. + +\{Deprecated|Proposed|Accepted\} + +## Context + +> This section contains all the context one needs to understand the current state, and why there is a problem. It should be as succinct as possible and introduce the high-level idea behind the solution. + +## Decision + +> This section explains all of the details of the proposed solution, including implementation details. +It should also describe affects / corollary items that may need to be changed as a part of this. +If the proposed change will be large, please also indicate a way to do the change to maximize ease of review. +(e.g. the optimal split of things to do between separate PR's) + +## Consequences + +> This section describes the consequences, after applying the decision. All consequences should be summarized here, not just the "positive" ones. + +### Positive + +### Negative + +### Neutral + +## References + +> Are there any relevant PR comments, issues that led up to this, or articles referenced for why we made the given design choice? If so link them here! + +- \{reference link\} diff --git a/docs/audits/04-channel-upgrades/Atredis Partners - Interchain Foundation IBC-Go Channel Upgrade Feature Assessment - Report v1.1.pdf b/docs/audits/04-channel-upgrades/Atredis Partners - Interchain Foundation IBC-Go Channel Upgrade Feature Assessment - Report v1.1.pdf new file mode 100644 index 0000000..8af1352 Binary files /dev/null and b/docs/audits/04-channel-upgrades/Atredis Partners - Interchain Foundation IBC-Go Channel Upgrade Feature Assessment - Report v1.1.pdf differ diff --git a/docs/audits/08-wasm/Ethan Frey - Wasm Client Review.pdf b/docs/audits/08-wasm/Ethan Frey - Wasm Client Review.pdf new file mode 100644 index 0000000..dfbc276 Binary files /dev/null and b/docs/audits/08-wasm/Ethan Frey - Wasm Client Review.pdf differ diff --git a/docs/audits/08-wasm/Halborn audit report.pdf b/docs/audits/08-wasm/Halborn audit report.pdf new file mode 100644 index 0000000..7466448 Binary files /dev/null and b/docs/audits/08-wasm/Halborn audit report.pdf differ diff --git a/docs/audits/20-token-transfer/Atredis Partners - Interchain ICS20 v2 New Features Assessment - Report v1.0.pdf b/docs/audits/20-token-transfer/Atredis Partners - Interchain ICS20 v2 New Features Assessment - Report v1.0.pdf new file mode 100644 index 0000000..9d2dd00 Binary files /dev/null and b/docs/audits/20-token-transfer/Atredis Partners - Interchain ICS20 v2 New Features Assessment - Report v1.0.pdf differ diff --git a/docs/audits/27-interchain-accounts/Trail of Bits audit - Final Report.pdf b/docs/audits/27-interchain-accounts/Trail of Bits audit - Final Report.pdf new file mode 100644 index 0000000..5998d14 Binary files /dev/null and b/docs/audits/27-interchain-accounts/Trail of Bits audit - Final Report.pdf differ diff --git a/docs/audits/IBC-v2/IBC-v2-April-2025-Collaborative-Audit-Report.pdf b/docs/audits/IBC-v2/IBC-v2-April-2025-Collaborative-Audit-Report.pdf new file mode 100644 index 0000000..9f92121 Binary files /dev/null and b/docs/audits/IBC-v2/IBC-v2-April-2025-Collaborative-Audit-Report.pdf differ diff --git a/docs/babel.config.js b/docs/babel.config.js new file mode 100644 index 0000000..2650af9 --- /dev/null +++ b/docs/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: [require.resolve('@docusaurus/core/lib/babel/preset')], +}; diff --git a/docs/client/config.json b/docs/client/config.json new file mode 100644 index 0000000..2103635 --- /dev/null +++ b/docs/client/config.json @@ -0,0 +1,85 @@ +{ + "swagger": "2.0", + "info": { + "title": "IBC-GO - gRPC Gateway docs", + "description": "A REST interface for state queries", + "version": "1.0.0" + }, + "apis": [ + { + "url": "./tmp-swagger-gen/ibc/applications/transfer/v1/query.swagger.json", + "operationIds": { + "rename": { + "Params": "TransferParams" + } + } + }, + { + "url": "./tmp-swagger-gen/ibc/applications/interchain_accounts/controller/v1/query.swagger.json", + "operationIds": { + "rename": { + "Params": "InterchainAccountsControllerParams" + } + } + }, + { + "url": "./tmp-swagger-gen/ibc/applications/interchain_accounts/host/v1/query.swagger.json", + "operationIds": { + "rename": { + "Params": "InterchainAccountsHostParams" + } + } + }, + { + "url": "./tmp-swagger-gen/ibc/core/client/v1/query.swagger.json", + "operationIds": { + "rename": { + "Params": "ClientParams" + } + } + }, + { + "url": "./tmp-swagger-gen/ibc/core/connection/v1/query.swagger.json", + "operationIds": { + "rename": { + "Params": "ConnectionParams" + } + } + }, + { + "url": "./tmp-swagger-gen/ibc/core/channel/v1/query.swagger.json", + "operationIds": { + "rename": { + "Params": "ChannelParams" + } + } + }, + { + "url": "./tmp-swagger-gen/ibc/core/channel/v2/query.swagger.json", + "operationIds": { + "rename": { + "Params": "ChannelV2Params", + "Channel": "ChannelV2", + "ChannelClientState": "ChannelClientStateV2", + "ChannelConsensusState": "ChannelConsensusStateV2", + "NextSequenceSend": "NextSequenceSendV2", + "PacketAcknowledgement": "PacketAcknowledgementV2", + "PacketAcknowledgements": "PacketAcknowledgementsV2", + "PacketCommitment": "PacketCommitmentV2", + "PacketCommitments": "PacketCommitmentsV2", + "PacketReceipt": "PacketReceiptV2", + "UnreceivedAcks": "UnreceivedAcksV2", + "UnreceivedPackets": "UnreceivedPacketsV2", + } + } + }, + { + "url": "./tmp-swagger-gen/ibc/lightclients/wasm/v1/query.swagger.json", + "operationIds": { + "rename": { + "Params": "WasmParams" + } + } + } + ] +} diff --git a/docs/client/swagger-ui/swagger.yaml b/docs/client/swagger-ui/swagger.yaml new file mode 100644 index 0000000..6231202 --- /dev/null +++ b/docs/client/swagger-ui/swagger.yaml @@ -0,0 +1,19555 @@ +swagger: '2.0' +info: + title: IBC-GO - gRPC Gateway docs + description: A REST interface for state queries + version: 1.0.0 +paths: + /ibc/apps/transfer/v1/channels/{channel_id}/ports/{port_id}/escrow_address: + get: + summary: >- + EscrowAddress returns the escrow address for a particular port and + channel id. + operationId: EscrowAddress + responses: + '200': + description: A successful response. + schema: + type: object + properties: + escrow_address: + type: string + title: the escrow account address + description: >- + QueryEscrowAddressResponse is the response type of the + EscrowAddress RPC method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + value: + type: string + format: byte + parameters: + - name: channel_id + description: unique channel identifier + in: path + required: true + type: string + - name: port_id + description: unique port identifier + in: path + required: true + type: string + tags: + - Query + /ibc/apps/transfer/v1/denom_hashes/{trace}: + get: + summary: DenomHash queries a denomination hash information. + operationId: DenomHash + responses: + '200': + description: A successful response. + schema: + type: object + properties: + hash: + type: string + description: hash (in hex format) of the denomination trace information. + description: >- + QueryDenomHashResponse is the response type for the + Query/DenomHash RPC + + method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + value: + type: string + format: byte + parameters: + - name: trace + description: The denomination trace ([port_id]/[channel_id])+/[denom] + in: path + required: true + type: string + tags: + - Query + /ibc/apps/transfer/v1/denoms: + get: + summary: Denoms queries all denominations + operationId: Denoms + responses: + '200': + description: A successful response. + schema: + type: object + properties: + denoms: + type: array + items: + type: object + properties: + base: + type: string + title: the base token denomination + trace: + type: array + items: + type: object + properties: + port_id: + type: string + channel_id: + type: string + description: >- + Hop defines a port ID, channel ID pair specifying + where tokens must be forwarded + + next in a multihop transfer. + title: the trace of the token + description: >- + Denom holds the base denom of a Token and a trace of the + chains it was sent through. + description: denoms returns all denominations. + pagination: + description: pagination defines the pagination in the response. + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: |- + QueryDenomsResponse is the response type for the Query/Denoms RPC + method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + value: + type: string + format: byte + parameters: + - name: pagination.key + description: |- + key is a value returned in PageResponse.next_key to begin + querying the next page most efficiently. Only one of offset or key + should be set. + in: query + required: false + type: string + format: byte + - name: pagination.offset + description: >- + offset is a numeric offset that can be used when key is unavailable. + + It is less efficient than using key. Only one of offset or key + should + + be set. + in: query + required: false + type: string + format: uint64 + - name: pagination.limit + description: >- + limit is the total number of results to be returned in the result + page. + + If left empty it will default to a value to be set by each app. + in: query + required: false + type: string + format: uint64 + - name: pagination.count_total + description: >- + count_total is set to true to indicate that the result set should + include + + a count of the total number of items available for pagination in + UIs. + + count_total is only respected when offset is used. It is ignored + when key + + is set. + in: query + required: false + type: boolean + - name: pagination.reverse + description: >- + reverse is set to true if results are to be returned in the + descending order. + + + Since: cosmos-sdk 0.43 + in: query + required: false + type: boolean + tags: + - Query + /ibc/apps/transfer/v1/denoms/{denom}/total_escrow: + get: + summary: >- + TotalEscrowForDenom returns the total amount of tokens in escrow based + on the denom. + operationId: TotalEscrowForDenom + responses: + '200': + description: A successful response. + schema: + type: object + properties: + amount: + type: object + properties: + denom: + type: string + amount: + type: string + description: >- + Coin defines a token with a denomination and an amount. + + + NOTE: The amount field is an Int which implements the custom + method + + signatures required by gogoproto. + description: >- + QueryTotalEscrowForDenomResponse is the response type for + TotalEscrowForDenom RPC method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + value: + type: string + format: byte + parameters: + - name: denom + in: path + required: true + type: string + tags: + - Query + /ibc/apps/transfer/v1/denoms/{hash}: + get: + summary: Denom queries a denomination + operationId: Denom + responses: + '200': + description: A successful response. + schema: + type: object + properties: + denom: + type: object + properties: + base: + type: string + title: the base token denomination + trace: + type: array + items: + type: object + properties: + port_id: + type: string + channel_id: + type: string + description: >- + Hop defines a port ID, channel ID pair specifying where + tokens must be forwarded + + next in a multihop transfer. + title: the trace of the token + description: >- + Denom holds the base denom of a Token and a trace of the + chains it was sent through. + description: |- + QueryDenomResponse is the response type for the Query/Denom RPC + method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + value: + type: string + format: byte + parameters: + - name: hash + description: >- + hash (in hex format) or denom (full denom with ibc prefix) of the on + chain denomination. + in: path + required: true + type: string + tags: + - Query + /ibc/apps/transfer/v1/params: + get: + summary: Params queries all parameters of the ibc-transfer module. + operationId: TransferParams + responses: + '200': + description: A successful response. + schema: + type: object + properties: + params: + description: params defines the parameters of the module. + type: object + properties: + send_enabled: + type: boolean + description: >- + send_enabled enables or disables all cross-chain token + transfers from this + + chain. + receive_enabled: + type: boolean + description: >- + receive_enabled enables or disables all cross-chain token + transfers to this + + chain. + description: >- + QueryParamsResponse is the response type for the Query/Params RPC + method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + value: + type: string + format: byte + tags: + - Query + /ibc/apps/interchain_accounts/controller/v1/owners/{owner}/connections/{connection_id}: + get: + summary: >- + InterchainAccount returns the interchain account address for a given + owner address on a given connection + operationId: InterchainAccount + responses: + '200': + description: A successful response. + schema: + type: object + properties: + address: + type: string + description: >- + QueryInterchainAccountResponse the response type for the + Query/InterchainAccount RPC method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + value: + type: string + format: byte + parameters: + - name: owner + in: path + required: true + type: string + - name: connection_id + in: path + required: true + type: string + tags: + - Query + /ibc/apps/interchain_accounts/controller/v1/params: + get: + summary: Params queries all parameters of the ICA controller submodule. + operationId: InterchainAccountsControllerParams + responses: + '200': + description: A successful response. + schema: + type: object + properties: + params: + description: params defines the parameters of the module. + type: object + properties: + controller_enabled: + type: boolean + description: >- + controller_enabled enables or disables the controller + submodule. + description: >- + QueryParamsResponse is the response type for the Query/Params RPC + method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + value: + type: string + format: byte + tags: + - Query + /ibc/apps/interchain_accounts/host/v1/params: + get: + summary: Params queries all parameters of the ICA host submodule. + operationId: InterchainAccountsHostParams + responses: + '200': + description: A successful response. + schema: + type: object + properties: + params: + description: params defines the parameters of the module. + type: object + properties: + host_enabled: + type: boolean + description: host_enabled enables or disables the host submodule. + allow_messages: + type: array + items: + type: string + description: >- + allow_messages defines a list of sdk message typeURLs + allowed to be executed on a host chain. + description: >- + QueryParamsResponse is the response type for the Query/Params RPC + method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + value: + type: string + format: byte + tags: + - Query + /ibc/core/client/v1/client_states: + get: + summary: ClientStates queries all the IBC light clients of a chain. + operationId: ClientStates + responses: + '200': + description: A successful response. + schema: + type: object + properties: + client_states: + type: array + items: + type: object + properties: + client_id: + type: string + title: client identifier + client_state: + title: client state + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the + type of the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's + path must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be + in a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the + binary all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can + optionally set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results + based on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available + in the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty + scheme) might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the + above specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any + values in the form + + of utility functions or additional generated methods of + the Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and + the unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will + yield type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the + regular + + representation of the deserialized, embedded message, + with an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a + custom JSON + + representation, that representation will be embedded + adding a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message + [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + description: >- + IdentifiedClientState defines a client state with an + additional client + + identifier field. + description: list of stored ClientStates of the chain. + pagination: + title: pagination response + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: >- + PageResponse is to be embedded in gRPC response messages where + the + + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + description: >- + QueryClientStatesResponse is the response type for the + Query/ClientStates RPC + + method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: pagination.key + description: |- + key is a value returned in PageResponse.next_key to begin + querying the next page most efficiently. Only one of offset or key + should be set. + in: query + required: false + type: string + format: byte + - name: pagination.offset + description: >- + offset is a numeric offset that can be used when key is unavailable. + + It is less efficient than using key. Only one of offset or key + should + + be set. + in: query + required: false + type: string + format: uint64 + - name: pagination.limit + description: >- + limit is the total number of results to be returned in the result + page. + + If left empty it will default to a value to be set by each app. + in: query + required: false + type: string + format: uint64 + - name: pagination.count_total + description: >- + count_total is set to true to indicate that the result set should + include + + a count of the total number of items available for pagination in + UIs. + + count_total is only respected when offset is used. It is ignored + when key + + is set. + in: query + required: false + type: boolean + - name: pagination.reverse + description: >- + reverse is set to true if results are to be returned in the + descending order. + + + Since: cosmos-sdk 0.43 + in: query + required: false + type: boolean + tags: + - Query + /ibc/core/client/v1/client_states/{client_id}: + get: + summary: ClientState queries an IBC light client. + operationId: ClientState + responses: + '200': + description: A successful response. + schema: + type: object + properties: + client_state: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must + represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a + canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all + types that they + + expect it to use in the context of Any. However, for URLs + which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the + official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message + along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in + the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default + use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the last + '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding a + field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: client state associated with the request identifier + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + description: >- + QueryClientStateResponse is the response type for the + Query/ClientState RPC + + method. Besides the client state, it includes a proof and the + height from + + which the proof was retrieved. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: client_id + description: client state unique identifier + in: path + required: true + type: string + tags: + - Query + /ibc/core/client/v1/client_status/{client_id}: + get: + summary: Status queries the status of an IBC client. + operationId: ClientStatus + responses: + '200': + description: A successful response. + schema: + type: object + properties: + status: + type: string + description: >- + QueryClientStatusResponse is the response type for the + Query/ClientStatus RPC + + method. It returns the current status of the IBC client. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: client_id + description: client unique identifier + in: path + required: true + type: string + tags: + - Query + /ibc/core/client/v1/consensus_states/{client_id}: + get: + summary: |- + ConsensusStates queries all the consensus state associated with a given + client. + operationId: ConsensusStates + responses: + '200': + description: A successful response. + schema: + type: object + properties: + consensus_states: + type: array + items: + type: object + properties: + height: + title: consensus state height + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each + height while keeping + + RevisionNumber the same. However some consensus + algorithms may choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as + the RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + consensus_state: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the + type of the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's + path must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be + in a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the + binary all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can + optionally set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results + based on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available + in the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty + scheme) might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the + above specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any + values in the form + + of utility functions or additional generated methods of + the Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and + the unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will + yield type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the + regular + + representation of the deserialized, embedded message, + with an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a + custom JSON + + representation, that representation will be embedded + adding a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message + [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: consensus state + description: >- + ConsensusStateWithHeight defines a consensus state with an + additional height + + field. + title: consensus states associated with the identifier + pagination: + title: pagination response + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: >- + PageResponse is to be embedded in gRPC response messages where + the + + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + title: |- + QueryConsensusStatesResponse is the response type for the + Query/ConsensusStates RPC method + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: client_id + description: client identifier + in: path + required: true + type: string + - name: pagination.key + description: |- + key is a value returned in PageResponse.next_key to begin + querying the next page most efficiently. Only one of offset or key + should be set. + in: query + required: false + type: string + format: byte + - name: pagination.offset + description: >- + offset is a numeric offset that can be used when key is unavailable. + + It is less efficient than using key. Only one of offset or key + should + + be set. + in: query + required: false + type: string + format: uint64 + - name: pagination.limit + description: >- + limit is the total number of results to be returned in the result + page. + + If left empty it will default to a value to be set by each app. + in: query + required: false + type: string + format: uint64 + - name: pagination.count_total + description: >- + count_total is set to true to indicate that the result set should + include + + a count of the total number of items available for pagination in + UIs. + + count_total is only respected when offset is used. It is ignored + when key + + is set. + in: query + required: false + type: boolean + - name: pagination.reverse + description: >- + reverse is set to true if results are to be returned in the + descending order. + + + Since: cosmos-sdk 0.43 + in: query + required: false + type: boolean + tags: + - Query + /ibc/core/client/v1/consensus_states/{client_id}/heights: + get: + summary: >- + ConsensusStateHeights queries the height of every consensus states + associated with a given client. + operationId: ConsensusStateHeights + responses: + '200': + description: A successful response. + schema: + type: object + properties: + consensus_state_heights: + type: array + items: + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms + may choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + title: >- + Height is a monotonically increasing data type + + that can be compared against another Height for the purposes + of updating and + + freezing clients + title: consensus state heights + pagination: + title: pagination response + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: >- + PageResponse is to be embedded in gRPC response messages where + the + + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + title: |- + QueryConsensusStateHeightsResponse is the response type for the + Query/ConsensusStateHeights RPC method + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: client_id + description: client identifier + in: path + required: true + type: string + - name: pagination.key + description: |- + key is a value returned in PageResponse.next_key to begin + querying the next page most efficiently. Only one of offset or key + should be set. + in: query + required: false + type: string + format: byte + - name: pagination.offset + description: >- + offset is a numeric offset that can be used when key is unavailable. + + It is less efficient than using key. Only one of offset or key + should + + be set. + in: query + required: false + type: string + format: uint64 + - name: pagination.limit + description: >- + limit is the total number of results to be returned in the result + page. + + If left empty it will default to a value to be set by each app. + in: query + required: false + type: string + format: uint64 + - name: pagination.count_total + description: >- + count_total is set to true to indicate that the result set should + include + + a count of the total number of items available for pagination in + UIs. + + count_total is only respected when offset is used. It is ignored + when key + + is set. + in: query + required: false + type: boolean + - name: pagination.reverse + description: >- + reverse is set to true if results are to be returned in the + descending order. + + + Since: cosmos-sdk 0.43 + in: query + required: false + type: boolean + tags: + - Query + /ibc/core/client/v1/consensus_states/{client_id}/revision/{revision_number}/height/{revision_height}: + get: + summary: >- + ConsensusState queries a consensus state associated with a client state + at + + a given height. + operationId: ConsensusState + responses: + '200': + description: A successful response. + schema: + type: object + properties: + consensus_state: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must + represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a + canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all + types that they + + expect it to use in the context of Any. However, for URLs + which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the + official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message + along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in + the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default + use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the last + '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding a + field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: >- + consensus state associated with the client identifier at the + given height + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + title: >- + Height is a monotonically increasing data type + + that can be compared against another Height for the purposes + of updating and + + freezing clients + title: >- + QueryConsensusStateResponse is the response type for the + Query/ConsensusState + + RPC method + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: client_id + description: client identifier + in: path + required: true + type: string + - name: revision_number + description: consensus state revision number + in: path + required: true + type: string + format: uint64 + - name: revision_height + description: consensus state revision height + in: path + required: true + type: string + format: uint64 + - name: latest_height + description: >- + latest_height overrides the height field and queries the latest + stored + + ConsensusState. + in: query + required: false + type: boolean + tags: + - Query + /ibc/core/client/v1/params: + get: + summary: ClientParams queries all parameters of the ibc client submodule. + operationId: ClientParams + responses: + '200': + description: A successful response. + schema: + type: object + properties: + params: + description: params defines the parameters of the module. + type: object + properties: + allowed_clients: + type: array + items: + type: string + description: >- + allowed_clients defines the list of allowed client state + types which can be created + + and interacted with. If a client type is removed from the + allowed clients list, usage + + of this client will be disabled until it is added again to + the list. + description: >- + QueryClientParamsResponse is the response type for the + Query/ClientParams RPC + + method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + tags: + - Query + /ibc/core/client/v1/upgraded_client_states: + get: + summary: UpgradedClientState queries an Upgraded IBC light client. + operationId: UpgradedClientState + responses: + '200': + description: A successful response. + schema: + type: object + properties: + upgraded_client_state: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must + represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a + canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all + types that they + + expect it to use in the context of Any. However, for URLs + which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the + official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message + along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in + the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default + use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the last + '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding a + field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: client state associated with the request identifier + description: |- + QueryUpgradedClientStateResponse is the response type for the + Query/UpgradedClientState RPC method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + tags: + - Query + /ibc/core/client/v1/upgraded_consensus_states: + get: + summary: UpgradedConsensusState queries an Upgraded IBC consensus state. + operationId: UpgradedConsensusState + responses: + '200': + description: A successful response. + schema: + type: object + properties: + upgraded_consensus_state: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must + represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a + canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all + types that they + + expect it to use in the context of Any. However, for URLs + which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the + official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message + along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in + the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default + use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the last + '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding a + field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: Consensus state associated with the request identifier + description: |- + QueryUpgradedConsensusStateResponse is the response type for the + Query/UpgradedConsensusState RPC method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + tags: + - Query + /ibc/core/client/v1/verify_membership: + post: + summary: >- + VerifyMembership queries an IBC light client for proof verification of a + value at a given key path. + operationId: VerifyMembership + responses: + '200': + description: A successful response. + schema: + type: object + properties: + success: + type: boolean + description: boolean indicating success or failure of proof verification. + title: >- + QueryVerifyMembershipResponse is the response type for the + Query/VerifyMembership RPC method + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: body + in: body + required: true + schema: + type: object + properties: + client_id: + type: string + description: client unique identifier. + proof: + type: string + format: byte + description: the proof to be verified by the client. + proof_height: + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + title: >- + Height is a monotonically increasing data type + + that can be compared against another Height for the purposes + of updating and + + freezing clients + value: + type: string + format: byte + description: the value which is proven. + time_delay: + type: string + format: uint64 + title: optional time delay + block_delay: + type: string + format: uint64 + title: optional block delay + merkle_path: + description: the commitment key path. + type: object + properties: + key_path: + type: array + items: + type: string + format: byte + title: >- + QueryVerifyMembershipRequest is the request type for the + Query/VerifyMembership RPC method + tags: + - Query + /ibc/core/connection/v1/client_connections/{client_id}: + get: + summary: |- + ClientConnections queries the connection paths associated with a client + state. + operationId: ClientConnections + responses: + '200': + description: A successful response. + schema: + type: object + properties: + connection_paths: + type: array + items: + type: string + description: slice of all the connection paths associated with a client. + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was generated + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + title: |- + QueryClientConnectionsResponse is the response type for the + Query/ClientConnections RPC method + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: client_id + description: client identifier associated with a connection + in: path + required: true + type: string + tags: + - Query + /ibc/core/connection/v1/connections: + get: + summary: Connections queries all the IBC connections of a chain. + operationId: Connections + responses: + '200': + description: A successful response. + schema: + type: object + properties: + connections: + type: array + items: + type: object + properties: + id: + type: string + description: connection identifier. + client_id: + type: string + description: client associated with this connection. + versions: + type: array + items: + type: object + properties: + identifier: + type: string + title: unique version identifier + features: + type: array + items: + type: string + title: >- + list of features compatible with the specified + identifier + description: >- + Version defines the versioning scheme used to + negotiate the IBC version in + + the connection handshake. + title: >- + IBC version which can be utilised to determine encodings + or protocols for + + channels or packets utilising this connection + state: + description: current state of the connection end. + type: string + enum: + - STATE_UNINITIALIZED_UNSPECIFIED + - STATE_INIT + - STATE_TRYOPEN + - STATE_OPEN + default: STATE_UNINITIALIZED_UNSPECIFIED + counterparty: + description: counterparty chain associated with this connection. + type: object + properties: + client_id: + type: string + description: >- + identifies the client on the counterparty chain + associated with a given + + connection. + connection_id: + type: string + description: >- + identifies the connection end on the counterparty + chain associated with a + + given connection. + prefix: + description: commitment merkle prefix of the counterparty chain. + type: object + properties: + key_prefix: + type: string + format: byte + title: >- + MerklePrefix is merkle path prefixed to the key. + + The constructed key from the Path and the key will + be append(Path.KeyPath, + + append(Path.KeyPrefix, key...)) + delay_period: + type: string + format: uint64 + description: delay period associated with this connection. + description: >- + IdentifiedConnection defines a connection with additional + connection + + identifier field. + description: list of stored connections of the chain. + pagination: + title: pagination response + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: >- + PageResponse is to be embedded in gRPC response messages where + the + + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + height: + title: query block height + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + description: >- + QueryConnectionsResponse is the response type for the + Query/Connections RPC + + method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: pagination.key + description: |- + key is a value returned in PageResponse.next_key to begin + querying the next page most efficiently. Only one of offset or key + should be set. + in: query + required: false + type: string + format: byte + - name: pagination.offset + description: >- + offset is a numeric offset that can be used when key is unavailable. + + It is less efficient than using key. Only one of offset or key + should + + be set. + in: query + required: false + type: string + format: uint64 + - name: pagination.limit + description: >- + limit is the total number of results to be returned in the result + page. + + If left empty it will default to a value to be set by each app. + in: query + required: false + type: string + format: uint64 + - name: pagination.count_total + description: >- + count_total is set to true to indicate that the result set should + include + + a count of the total number of items available for pagination in + UIs. + + count_total is only respected when offset is used. It is ignored + when key + + is set. + in: query + required: false + type: boolean + - name: pagination.reverse + description: >- + reverse is set to true if results are to be returned in the + descending order. + + + Since: cosmos-sdk 0.43 + in: query + required: false + type: boolean + tags: + - Query + /ibc/core/connection/v1/connections/{connection_id}: + get: + summary: Connection queries an IBC connection end. + operationId: Connection + responses: + '200': + description: A successful response. + schema: + type: object + properties: + connection: + title: connection associated with the request identifier + type: object + properties: + client_id: + type: string + description: client associated with this connection. + versions: + type: array + items: + type: object + properties: + identifier: + type: string + title: unique version identifier + features: + type: array + items: + type: string + title: >- + list of features compatible with the specified + identifier + description: >- + Version defines the versioning scheme used to negotiate + the IBC version in + + the connection handshake. + description: >- + IBC version which can be utilised to determine encodings + or protocols for + + channels or packets utilising this connection. + state: + description: current state of the connection end. + type: string + enum: + - STATE_UNINITIALIZED_UNSPECIFIED + - STATE_INIT + - STATE_TRYOPEN + - STATE_OPEN + default: STATE_UNINITIALIZED_UNSPECIFIED + counterparty: + description: counterparty chain associated with this connection. + type: object + properties: + client_id: + type: string + description: >- + identifies the client on the counterparty chain + associated with a given + + connection. + connection_id: + type: string + description: >- + identifies the connection end on the counterparty + chain associated with a + + given connection. + prefix: + description: commitment merkle prefix of the counterparty chain. + type: object + properties: + key_prefix: + type: string + format: byte + title: >- + MerklePrefix is merkle path prefixed to the key. + + The constructed key from the Path and the key will be + append(Path.KeyPath, + + append(Path.KeyPrefix, key...)) + delay_period: + type: string + format: uint64 + description: >- + delay period that must pass before a consensus state can + be used for + + packet-verification NOTE: delay period logic is only + implemented by some + + clients. + description: >- + ConnectionEnd defines a stateful object on a chain connected + to another + + separate one. + + NOTE: there must only be 2 defined ConnectionEnds to establish + + a connection between two chains. + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + description: >- + QueryConnectionResponse is the response type for the + Query/Connection RPC + + method. Besides the connection end, it includes a proof and the + height from + + which the proof was retrieved. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: connection_id + description: connection unique identifier + in: path + required: true + type: string + tags: + - Query + /ibc/core/connection/v1/connections/{connection_id}/client_state: + get: + summary: |- + ConnectionClientState queries the client state associated with the + connection. + operationId: ConnectionClientState + responses: + '200': + description: A successful response. + schema: + type: object + properties: + identified_client_state: + title: client state associated with the channel + type: object + properties: + client_id: + type: string + title: client identifier + client_state: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type + of the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be + in a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can + optionally set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results + based on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty + scheme) might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the + above specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any + values in the form + + of utility functions or additional generated methods of + the Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and + the unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will + yield type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a + custom JSON + + representation, that representation will be embedded + adding a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: client state + description: >- + IdentifiedClientState defines a client state with an + additional client + + identifier field. + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + title: |- + QueryConnectionClientStateResponse is the response type for the + Query/ConnectionClientState RPC method + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: connection_id + description: connection identifier + in: path + required: true + type: string + tags: + - Query + /ibc/core/connection/v1/connections/{connection_id}/consensus_state/revision/{revision_number}/height/{revision_height}: + get: + summary: |- + ConnectionConsensusState queries the consensus state associated with the + connection. + operationId: ConnectionConsensusState + responses: + '200': + description: A successful response. + schema: + type: object + properties: + consensus_state: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must + represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a + canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all + types that they + + expect it to use in the context of Any. However, for URLs + which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the + official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message + along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in + the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default + use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the last + '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding a + field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: consensus state associated with the channel + client_id: + type: string + title: client ID associated with the consensus state + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + title: |- + QueryConnectionConsensusStateResponse is the response type for the + Query/ConnectionConsensusState RPC method + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: connection_id + description: connection identifier + in: path + required: true + type: string + - name: revision_number + in: path + required: true + type: string + format: uint64 + - name: revision_height + in: path + required: true + type: string + format: uint64 + tags: + - Query + /ibc/core/connection/v1/params: + get: + summary: ConnectionParams queries all parameters of the ibc connection submodule. + operationId: ConnectionParams + responses: + '200': + description: A successful response. + schema: + type: object + properties: + params: + description: params defines the parameters of the module. + type: object + properties: + max_expected_time_per_block: + type: string + format: uint64 + description: >- + maximum expected time per block (in nanoseconds), used to + enforce block delay. This parameter should reflect the + + largest amount of time that the chain might reasonably + take to produce the next block under normal operating + + conditions. A safe choice is 3-5x the expected time per + block. + description: >- + QueryConnectionParamsResponse is the response type for the + Query/ConnectionParams RPC method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + tags: + - Query + /ibc/core/channel/v1/channels: + get: + summary: Channels queries all the IBC channels of a chain. + operationId: Channels + responses: + '200': + description: A successful response. + schema: + type: object + properties: + channels: + type: array + items: + type: object + properties: + state: + title: current state of the channel end + type: string + enum: + - STATE_UNINITIALIZED_UNSPECIFIED + - STATE_INIT + - STATE_TRYOPEN + - STATE_OPEN + - STATE_CLOSED + default: STATE_UNINITIALIZED_UNSPECIFIED + description: >- + State defines if a channel is in one of the following + states: + + CLOSED, INIT, TRYOPEN, OPEN, or UNINITIALIZED. + + - STATE_UNINITIALIZED_UNSPECIFIED: Default State + - STATE_INIT: A channel has just started the opening handshake. + - STATE_TRYOPEN: A channel has acknowledged the handshake step on the counterparty chain. + - STATE_OPEN: A channel has completed the handshake. Open channels are + ready to send and receive packets. + - STATE_CLOSED: A channel has been closed and can no longer be used to send or receive + packets. + ordering: + title: whether the channel is ordered or unordered + type: string + enum: + - ORDER_NONE_UNSPECIFIED + - ORDER_UNORDERED + - ORDER_ORDERED + default: ORDER_NONE_UNSPECIFIED + description: >- + - ORDER_NONE_UNSPECIFIED: zero-value for channel + ordering + - ORDER_UNORDERED: packets can be delivered in any order, which may differ from the order in + which they were sent. + - ORDER_ORDERED: packets are delivered exactly in the order which they were sent + counterparty: + title: counterparty channel end + type: object + properties: + port_id: + type: string + description: >- + port on the counterparty chain which owns the other + end of the channel. + channel_id: + type: string + title: channel end on the counterparty chain + connection_hops: + type: array + items: + type: string + title: >- + list of connection identifiers, in order, along which + packets sent on + + this channel will travel + version: + type: string + title: >- + opaque channel version, which is agreed upon during the + handshake + port_id: + type: string + title: port identifier + channel_id: + type: string + title: channel identifier + description: >- + IdentifiedChannel defines a channel with additional port and + channel + + identifier fields. + description: list of stored channels of the chain. + pagination: + title: pagination response + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: >- + PageResponse is to be embedded in gRPC response messages where + the + + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + height: + title: query block height + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + description: >- + QueryChannelsResponse is the response type for the Query/Channels + RPC method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: pagination.key + description: |- + key is a value returned in PageResponse.next_key to begin + querying the next page most efficiently. Only one of offset or key + should be set. + in: query + required: false + type: string + format: byte + - name: pagination.offset + description: >- + offset is a numeric offset that can be used when key is unavailable. + + It is less efficient than using key. Only one of offset or key + should + + be set. + in: query + required: false + type: string + format: uint64 + - name: pagination.limit + description: >- + limit is the total number of results to be returned in the result + page. + + If left empty it will default to a value to be set by each app. + in: query + required: false + type: string + format: uint64 + - name: pagination.count_total + description: >- + count_total is set to true to indicate that the result set should + include + + a count of the total number of items available for pagination in + UIs. + + count_total is only respected when offset is used. It is ignored + when key + + is set. + in: query + required: false + type: boolean + - name: pagination.reverse + description: >- + reverse is set to true if results are to be returned in the + descending order. + + + Since: cosmos-sdk 0.43 + in: query + required: false + type: boolean + tags: + - Query + /ibc/core/channel/v1/channels/{channel_id}/ports/{port_id}: + get: + summary: Channel queries an IBC Channel. + operationId: Channel + responses: + '200': + description: A successful response. + schema: + type: object + properties: + channel: + title: channel associated with the request identifiers + type: object + properties: + state: + title: current state of the channel end + type: string + enum: + - STATE_UNINITIALIZED_UNSPECIFIED + - STATE_INIT + - STATE_TRYOPEN + - STATE_OPEN + - STATE_CLOSED + default: STATE_UNINITIALIZED_UNSPECIFIED + description: >- + State defines if a channel is in one of the following + states: + + CLOSED, INIT, TRYOPEN, OPEN, or UNINITIALIZED. + + - STATE_UNINITIALIZED_UNSPECIFIED: Default State + - STATE_INIT: A channel has just started the opening handshake. + - STATE_TRYOPEN: A channel has acknowledged the handshake step on the counterparty chain. + - STATE_OPEN: A channel has completed the handshake. Open channels are + ready to send and receive packets. + - STATE_CLOSED: A channel has been closed and can no longer be used to send or receive + packets. + ordering: + title: whether the channel is ordered or unordered + type: string + enum: + - ORDER_NONE_UNSPECIFIED + - ORDER_UNORDERED + - ORDER_ORDERED + default: ORDER_NONE_UNSPECIFIED + description: |- + - ORDER_NONE_UNSPECIFIED: zero-value for channel ordering + - ORDER_UNORDERED: packets can be delivered in any order, which may differ from the order in + which they were sent. + - ORDER_ORDERED: packets are delivered exactly in the order which they were sent + counterparty: + title: counterparty channel end + type: object + properties: + port_id: + type: string + description: >- + port on the counterparty chain which owns the other + end of the channel. + channel_id: + type: string + title: channel end on the counterparty chain + connection_hops: + type: array + items: + type: string + title: >- + list of connection identifiers, in order, along which + packets sent on + + this channel will travel + version: + type: string + title: >- + opaque channel version, which is agreed upon during the + handshake + description: >- + Channel defines pipeline for exactly-once packet delivery + between specific + + modules on separate blockchains, which has at least one end + capable of + + sending packets and one end capable of receiving packets. + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + description: >- + QueryChannelResponse is the response type for the Query/Channel + RPC method. + + Besides the Channel end, it includes a proof and the height from + which the + + proof was retrieved. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: channel_id + description: channel unique identifier + in: path + required: true + type: string + - name: port_id + description: port unique identifier + in: path + required: true + type: string + tags: + - Query + /ibc/core/channel/v1/channels/{channel_id}/ports/{port_id}/client_state: + get: + summary: >- + ChannelClientState queries for the client state for the channel + associated + + with the provided channel identifiers. + operationId: ChannelClientState + responses: + '200': + description: A successful response. + schema: + type: object + properties: + identified_client_state: + title: client state associated with the channel + type: object + properties: + client_id: + type: string + title: client identifier + client_state: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type + of the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be + in a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can + optionally set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results + based on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty + scheme) might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the + above specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any + values in the form + + of utility functions or additional generated methods of + the Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and + the unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will + yield type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a + custom JSON + + representation, that representation will be embedded + adding a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: client state + description: >- + IdentifiedClientState defines a client state with an + additional client + + identifier field. + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + title: |- + QueryChannelClientStateResponse is the Response type for the + Query/QueryChannelClientState RPC method + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: channel_id + description: channel unique identifier + in: path + required: true + type: string + - name: port_id + description: port unique identifier + in: path + required: true + type: string + tags: + - Query + /ibc/core/channel/v1/channels/{channel_id}/ports/{port_id}/consensus_state/revision/{revision_number}/height/{revision_height}: + get: + summary: |- + ChannelConsensusState queries for the consensus state for the channel + associated with the provided channel identifiers. + operationId: ChannelConsensusState + responses: + '200': + description: A successful response. + schema: + type: object + properties: + consensus_state: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must + represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a + canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all + types that they + + expect it to use in the context of Any. However, for URLs + which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the + official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message + along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in + the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default + use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the last + '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding a + field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: consensus state associated with the channel + client_id: + type: string + title: client ID associated with the consensus state + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + title: |- + QueryChannelClientStateResponse is the Response type for the + Query/QueryChannelClientState RPC method + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: channel_id + description: channel unique identifier + in: path + required: true + type: string + - name: port_id + description: port unique identifier + in: path + required: true + type: string + - name: revision_number + description: revision number of the consensus state + in: path + required: true + type: string + format: uint64 + - name: revision_height + description: revision height of the consensus state + in: path + required: true + type: string + format: uint64 + tags: + - Query + /ibc/core/channel/v1/channels/{channel_id}/ports/{port_id}/next_sequence: + get: + summary: >- + NextSequenceReceive returns the next receive sequence for a given + channel. + operationId: NextSequenceReceive + responses: + '200': + description: A successful response. + schema: + type: object + properties: + next_sequence_receive: + type: string + format: uint64 + title: next sequence receive number + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + title: |- + QuerySequenceResponse is the response type for the + Query/QueryNextSequenceReceiveResponse RPC method + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: channel_id + description: channel unique identifier + in: path + required: true + type: string + - name: port_id + description: port unique identifier + in: path + required: true + type: string + tags: + - Query + /ibc/core/channel/v1/channels/{channel_id}/ports/{port_id}/next_sequence_send: + get: + summary: NextSequenceSend returns the next send sequence for a given channel. + operationId: NextSequenceSend + responses: + '200': + description: A successful response. + schema: + type: object + properties: + next_sequence_send: + type: string + format: uint64 + title: next sequence send number + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + title: |- + QueryNextSequenceSendResponse is the request type for the + Query/QueryNextSequenceSend RPC method + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: channel_id + description: channel unique identifier + in: path + required: true + type: string + - name: port_id + description: port unique identifier + in: path + required: true + type: string + tags: + - Query + /ibc/core/channel/v1/channels/{channel_id}/ports/{port_id}/packet_acknowledgements: + get: + summary: >- + PacketAcknowledgements returns all the packet acknowledgements + associated + + with a channel. + operationId: PacketAcknowledgements + responses: + '200': + description: A successful response. + schema: + type: object + properties: + acknowledgements: + type: array + items: + type: object + properties: + port_id: + type: string + description: channel port identifier. + channel_id: + type: string + description: channel unique identifier. + sequence: + type: string + format: uint64 + description: packet sequence. + data: + type: string + format: byte + description: embedded data that represents packet state. + description: >- + PacketState defines the generic type necessary to retrieve + and store + + packet commitments, acknowledgements, and receipts. + + Caller is responsible for knowing the context necessary to + interpret this + + state as a commitment, acknowledgement, or a receipt. + pagination: + title: pagination response + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: >- + PageResponse is to be embedded in gRPC response messages where + the + + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + height: + title: query block height + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + title: |- + QueryPacketAcknowledgemetsResponse is the request type for the + Query/QueryPacketAcknowledgements RPC method + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: channel_id + description: channel unique identifier + in: path + required: true + type: string + - name: port_id + description: port unique identifier + in: path + required: true + type: string + - name: pagination.key + description: |- + key is a value returned in PageResponse.next_key to begin + querying the next page most efficiently. Only one of offset or key + should be set. + in: query + required: false + type: string + format: byte + - name: pagination.offset + description: >- + offset is a numeric offset that can be used when key is unavailable. + + It is less efficient than using key. Only one of offset or key + should + + be set. + in: query + required: false + type: string + format: uint64 + - name: pagination.limit + description: >- + limit is the total number of results to be returned in the result + page. + + If left empty it will default to a value to be set by each app. + in: query + required: false + type: string + format: uint64 + - name: pagination.count_total + description: >- + count_total is set to true to indicate that the result set should + include + + a count of the total number of items available for pagination in + UIs. + + count_total is only respected when offset is used. It is ignored + when key + + is set. + in: query + required: false + type: boolean + - name: pagination.reverse + description: >- + reverse is set to true if results are to be returned in the + descending order. + + + Since: cosmos-sdk 0.43 + in: query + required: false + type: boolean + - name: packet_commitment_sequences + description: list of packet sequences. + in: query + required: false + type: array + items: + type: string + format: uint64 + collectionFormat: multi + tags: + - Query + /ibc/core/channel/v1/channels/{channel_id}/ports/{port_id}/packet_acks/{sequence}: + get: + summary: PacketAcknowledgement queries a stored packet acknowledgement hash. + operationId: PacketAcknowledgement + responses: + '200': + description: A successful response. + schema: + type: object + properties: + acknowledgement: + type: string + format: byte + title: packet associated with the request fields + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + title: >- + QueryPacketAcknowledgementResponse defines the client query + response for a + + packet which also includes a proof and the height from which the + + proof was retrieved + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: channel_id + description: channel unique identifier + in: path + required: true + type: string + - name: port_id + description: port unique identifier + in: path + required: true + type: string + - name: sequence + description: packet sequence + in: path + required: true + type: string + format: uint64 + tags: + - Query + /ibc/core/channel/v1/channels/{channel_id}/ports/{port_id}/packet_commitments: + get: + summary: |- + PacketCommitments returns all the packet commitments hashes associated + with a channel. + operationId: PacketCommitments + responses: + '200': + description: A successful response. + schema: + type: object + properties: + commitments: + type: array + items: + type: object + properties: + port_id: + type: string + description: channel port identifier. + channel_id: + type: string + description: channel unique identifier. + sequence: + type: string + format: uint64 + description: packet sequence. + data: + type: string + format: byte + description: embedded data that represents packet state. + description: >- + PacketState defines the generic type necessary to retrieve + and store + + packet commitments, acknowledgements, and receipts. + + Caller is responsible for knowing the context necessary to + interpret this + + state as a commitment, acknowledgement, or a receipt. + pagination: + title: pagination response + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: >- + PageResponse is to be embedded in gRPC response messages where + the + + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + height: + title: query block height + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + title: |- + QueryPacketCommitmentsResponse is the request type for the + Query/QueryPacketCommitments RPC method + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: channel_id + description: channel unique identifier + in: path + required: true + type: string + - name: port_id + description: port unique identifier + in: path + required: true + type: string + - name: pagination.key + description: |- + key is a value returned in PageResponse.next_key to begin + querying the next page most efficiently. Only one of offset or key + should be set. + in: query + required: false + type: string + format: byte + - name: pagination.offset + description: >- + offset is a numeric offset that can be used when key is unavailable. + + It is less efficient than using key. Only one of offset or key + should + + be set. + in: query + required: false + type: string + format: uint64 + - name: pagination.limit + description: >- + limit is the total number of results to be returned in the result + page. + + If left empty it will default to a value to be set by each app. + in: query + required: false + type: string + format: uint64 + - name: pagination.count_total + description: >- + count_total is set to true to indicate that the result set should + include + + a count of the total number of items available for pagination in + UIs. + + count_total is only respected when offset is used. It is ignored + when key + + is set. + in: query + required: false + type: boolean + - name: pagination.reverse + description: >- + reverse is set to true if results are to be returned in the + descending order. + + + Since: cosmos-sdk 0.43 + in: query + required: false + type: boolean + tags: + - Query + /ibc/core/channel/v1/channels/{channel_id}/ports/{port_id}/packet_commitments/{packet_ack_sequences}/unreceived_acks: + get: + summary: >- + UnreceivedAcks returns all the unreceived IBC acknowledgements + associated + + with a channel and sequences. + operationId: UnreceivedAcks + responses: + '200': + description: A successful response. + schema: + type: object + properties: + sequences: + type: array + items: + type: string + format: uint64 + title: list of unreceived acknowledgement sequences + height: + title: query block height + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + title: |- + QueryUnreceivedAcksResponse is the response type for the + Query/UnreceivedAcks RPC method + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: channel_id + description: channel unique identifier + in: path + required: true + type: string + - name: port_id + description: port unique identifier + in: path + required: true + type: string + - name: packet_ack_sequences + description: list of acknowledgement sequences + in: path + required: true + type: array + items: + type: string + format: uint64 + collectionFormat: csv + minItems: 1 + tags: + - Query + /ibc/core/channel/v1/channels/{channel_id}/ports/{port_id}/packet_commitments/{packet_commitment_sequences}/unreceived_packets: + get: + summary: >- + UnreceivedPackets returns all the unreceived IBC packets associated with + a + + channel and sequences. + operationId: UnreceivedPackets + responses: + '200': + description: A successful response. + schema: + type: object + properties: + sequences: + type: array + items: + type: string + format: uint64 + title: list of unreceived packet sequences + height: + title: query block height + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + title: |- + QueryUnreceivedPacketsResponse is the response type for the + Query/UnreceivedPacketCommitments RPC method + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: channel_id + description: channel unique identifier + in: path + required: true + type: string + - name: port_id + description: port unique identifier + in: path + required: true + type: string + - name: packet_commitment_sequences + description: list of packet sequences + in: path + required: true + type: array + items: + type: string + format: uint64 + collectionFormat: csv + minItems: 1 + tags: + - Query + /ibc/core/channel/v1/channels/{channel_id}/ports/{port_id}/packet_commitments/{sequence}: + get: + summary: PacketCommitment queries a stored packet commitment hash. + operationId: PacketCommitment + responses: + '200': + description: A successful response. + schema: + type: object + properties: + commitment: + type: string + format: byte + title: packet associated with the request fields + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + title: >- + QueryPacketCommitmentResponse defines the client query response + for a packet + + which also includes a proof and the height from which the proof + was + + retrieved + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: channel_id + description: channel unique identifier + in: path + required: true + type: string + - name: port_id + description: port unique identifier + in: path + required: true + type: string + - name: sequence + description: packet sequence + in: path + required: true + type: string + format: uint64 + tags: + - Query + /ibc/core/channel/v1/channels/{channel_id}/ports/{port_id}/packet_receipts/{sequence}: + get: + summary: >- + PacketReceipt queries if a given packet sequence has been received on + the + + queried chain + operationId: PacketReceipt + responses: + '200': + description: A successful response. + schema: + type: object + properties: + received: + type: boolean + title: success flag for if receipt exists + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + title: >- + QueryPacketReceiptResponse defines the client query response for a + packet + + receipt which also includes a proof, and the height from which the + proof was + + retrieved + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: channel_id + description: channel unique identifier + in: path + required: true + type: string + - name: port_id + description: port unique identifier + in: path + required: true + type: string + - name: sequence + description: packet sequence + in: path + required: true + type: string + format: uint64 + tags: + - Query + /ibc/core/channel/v1/connections/{connection}/channels: + get: + summary: |- + ConnectionChannels queries all the channels associated with a connection + end. + operationId: ConnectionChannels + responses: + '200': + description: A successful response. + schema: + type: object + properties: + channels: + type: array + items: + type: object + properties: + state: + title: current state of the channel end + type: string + enum: + - STATE_UNINITIALIZED_UNSPECIFIED + - STATE_INIT + - STATE_TRYOPEN + - STATE_OPEN + - STATE_CLOSED + default: STATE_UNINITIALIZED_UNSPECIFIED + description: >- + State defines if a channel is in one of the following + states: + + CLOSED, INIT, TRYOPEN, OPEN, or UNINITIALIZED. + + - STATE_UNINITIALIZED_UNSPECIFIED: Default State + - STATE_INIT: A channel has just started the opening handshake. + - STATE_TRYOPEN: A channel has acknowledged the handshake step on the counterparty chain. + - STATE_OPEN: A channel has completed the handshake. Open channels are + ready to send and receive packets. + - STATE_CLOSED: A channel has been closed and can no longer be used to send or receive + packets. + ordering: + title: whether the channel is ordered or unordered + type: string + enum: + - ORDER_NONE_UNSPECIFIED + - ORDER_UNORDERED + - ORDER_ORDERED + default: ORDER_NONE_UNSPECIFIED + description: >- + - ORDER_NONE_UNSPECIFIED: zero-value for channel + ordering + - ORDER_UNORDERED: packets can be delivered in any order, which may differ from the order in + which they were sent. + - ORDER_ORDERED: packets are delivered exactly in the order which they were sent + counterparty: + title: counterparty channel end + type: object + properties: + port_id: + type: string + description: >- + port on the counterparty chain which owns the other + end of the channel. + channel_id: + type: string + title: channel end on the counterparty chain + connection_hops: + type: array + items: + type: string + title: >- + list of connection identifiers, in order, along which + packets sent on + + this channel will travel + version: + type: string + title: >- + opaque channel version, which is agreed upon during the + handshake + port_id: + type: string + title: port identifier + channel_id: + type: string + title: channel identifier + description: >- + IdentifiedChannel defines a channel with additional port and + channel + + identifier fields. + description: list of channels associated with a connection. + pagination: + title: pagination response + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: >- + PageResponse is to be embedded in gRPC response messages where + the + + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + height: + title: query block height + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + title: |- + QueryConnectionChannelsResponse is the Response type for the + Query/QueryConnectionChannels RPC method + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: connection + description: connection unique identifier + in: path + required: true + type: string + - name: pagination.key + description: |- + key is a value returned in PageResponse.next_key to begin + querying the next page most efficiently. Only one of offset or key + should be set. + in: query + required: false + type: string + format: byte + - name: pagination.offset + description: >- + offset is a numeric offset that can be used when key is unavailable. + + It is less efficient than using key. Only one of offset or key + should + + be set. + in: query + required: false + type: string + format: uint64 + - name: pagination.limit + description: >- + limit is the total number of results to be returned in the result + page. + + If left empty it will default to a value to be set by each app. + in: query + required: false + type: string + format: uint64 + - name: pagination.count_total + description: >- + count_total is set to true to indicate that the result set should + include + + a count of the total number of items available for pagination in + UIs. + + count_total is only respected when offset is used. It is ignored + when key + + is set. + in: query + required: false + type: boolean + - name: pagination.reverse + description: >- + reverse is set to true if results are to be returned in the + descending order. + + + Since: cosmos-sdk 0.43 + in: query + required: false + type: boolean + tags: + - Query + /ibc/core/channel/v2/clients/{client_id}/next_sequence_send: + get: + summary: NextSequenceSend returns the next send sequence for a given channel. + operationId: NextSequenceSendV2 + responses: + '200': + description: A successful response. + schema: + type: object + properties: + next_sequence_send: + type: string + format: uint64 + title: next sequence send number + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + title: >- + QueryNextSequenceSendResponse is the response type for the + Query/QueryNextSequenceSend RPC method + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: client_id + description: client unique identifier + in: path + required: true + type: string + tags: + - Query + /ibc/core/channel/v2/clients/{client_id}/packet_acknowledgements: + get: + summary: >- + PacketAcknowledgements returns all packet acknowledgements associated + with a channel. + operationId: PacketAcknowledgementsV2 + responses: + '200': + description: A successful response. + schema: + type: object + properties: + acknowledgements: + type: array + items: + type: object + properties: + client_id: + type: string + description: client unique identifier. + sequence: + type: string + format: uint64 + description: packet sequence. + data: + type: string + format: byte + description: embedded data that represents packet state. + description: >- + PacketState defines the generic type necessary to retrieve + and store + + packet commitments, acknowledgements, and receipts. + + Caller is responsible for knowing the context necessary to + interpret this + + state as a commitment, acknowledgement, or a receipt. + pagination: + title: pagination response + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: >- + PageResponse is to be embedded in gRPC response messages where + the + + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + height: + title: query block height + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + title: |- + QueryPacketAcknowledgemetsResponse is the request type for the + Query/QueryPacketAcknowledgements RPC method + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: client_id + description: client unique identifier + in: path + required: true + type: string + - name: pagination.key + description: |- + key is a value returned in PageResponse.next_key to begin + querying the next page most efficiently. Only one of offset or key + should be set. + in: query + required: false + type: string + format: byte + - name: pagination.offset + description: >- + offset is a numeric offset that can be used when key is unavailable. + + It is less efficient than using key. Only one of offset or key + should + + be set. + in: query + required: false + type: string + format: uint64 + - name: pagination.limit + description: >- + limit is the total number of results to be returned in the result + page. + + If left empty it will default to a value to be set by each app. + in: query + required: false + type: string + format: uint64 + - name: pagination.count_total + description: >- + count_total is set to true to indicate that the result set should + include + + a count of the total number of items available for pagination in + UIs. + + count_total is only respected when offset is used. It is ignored + when key + + is set. + in: query + required: false + type: boolean + - name: pagination.reverse + description: >- + reverse is set to true if results are to be returned in the + descending order. + + + Since: cosmos-sdk 0.43 + in: query + required: false + type: boolean + - name: packet_commitment_sequences + description: list of packet sequences. + in: query + required: false + type: array + items: + type: string + format: uint64 + collectionFormat: multi + tags: + - Query + /ibc/core/channel/v2/clients/{client_id}/packet_acks/{sequence}: + get: + summary: PacketAcknowledgement queries a stored acknowledgement commitment hash. + operationId: PacketAcknowledgementV2 + responses: + '200': + description: A successful response. + schema: + type: object + properties: + acknowledgement: + type: string + format: byte + title: acknowledgement associated with the request fields + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + description: >- + QueryPacketAcknowledgementResponse is the response type for the + Query/PacketAcknowledgement RPC method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: client_id + description: client unique identifier + in: path + required: true + type: string + - name: sequence + description: packet sequence + in: path + required: true + type: string + format: uint64 + tags: + - Query + /ibc/core/channel/v2/clients/{client_id}/packet_commitments: + get: + summary: PacketCommitments queries a stored packet commitment hash. + operationId: PacketCommitmentsV2 + responses: + '200': + description: A successful response. + schema: + type: object + properties: + commitments: + type: array + items: + type: object + properties: + client_id: + type: string + description: client unique identifier. + sequence: + type: string + format: uint64 + description: packet sequence. + data: + type: string + format: byte + description: embedded data that represents packet state. + description: >- + PacketState defines the generic type necessary to retrieve + and store + + packet commitments, acknowledgements, and receipts. + + Caller is responsible for knowing the context necessary to + interpret this + + state as a commitment, acknowledgement, or a receipt. + description: >- + collection of packet commitments for the requested channel + identifier. + pagination: + description: pagination response. + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + height: + description: query block height. + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + title: >- + Height is a monotonically increasing data type + + that can be compared against another Height for the purposes + of updating and + + freezing clients + description: >- + QueryPacketCommitmentResponse is the response type for the + Query/PacketCommitment RPC method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: client_id + description: client unique identifier + in: path + required: true + type: string + - name: pagination.key + description: |- + key is a value returned in PageResponse.next_key to begin + querying the next page most efficiently. Only one of offset or key + should be set. + in: query + required: false + type: string + format: byte + - name: pagination.offset + description: >- + offset is a numeric offset that can be used when key is unavailable. + + It is less efficient than using key. Only one of offset or key + should + + be set. + in: query + required: false + type: string + format: uint64 + - name: pagination.limit + description: >- + limit is the total number of results to be returned in the result + page. + + If left empty it will default to a value to be set by each app. + in: query + required: false + type: string + format: uint64 + - name: pagination.count_total + description: >- + count_total is set to true to indicate that the result set should + include + + a count of the total number of items available for pagination in + UIs. + + count_total is only respected when offset is used. It is ignored + when key + + is set. + in: query + required: false + type: boolean + - name: pagination.reverse + description: >- + reverse is set to true if results are to be returned in the + descending order. + + + Since: cosmos-sdk 0.43 + in: query + required: false + type: boolean + tags: + - Query + /ibc/core/channel/v2/clients/{client_id}/packet_commitments/{packet_ack_sequences}/unreceived_acks: + get: + summary: >- + UnreceivedAcks returns all the unreceived IBC acknowledgements + associated with a channel and sequences. + operationId: UnreceivedAcksV2 + responses: + '200': + description: A successful response. + schema: + type: object + properties: + sequences: + type: array + items: + type: string + format: uint64 + title: list of unreceived acknowledgement sequences + height: + title: query block height + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + title: |- + QueryUnreceivedAcksResponse is the response type for the + Query/UnreceivedAcks RPC method + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: client_id + description: client unique identifier + in: path + required: true + type: string + - name: packet_ack_sequences + description: list of acknowledgement sequences + in: path + required: true + type: array + items: + type: string + format: uint64 + collectionFormat: csv + minItems: 1 + tags: + - Query + /ibc/core/channel/v2/clients/{client_id}/packet_commitments/{sequences}/unreceived_packets: + get: + summary: >- + UnreceivedPackets returns all the unreceived IBC packets associated with + a channel and sequences. + operationId: UnreceivedPacketsV2 + responses: + '200': + description: A successful response. + schema: + type: object + properties: + sequences: + type: array + items: + type: string + format: uint64 + title: list of unreceived packet sequences + height: + title: query block height + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + title: >- + QueryUnreceivedPacketsResponse is the response type for the + Query/UnreceivedPacketCommitments RPC method + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: client_id + description: client unique identifier + in: path + required: true + type: string + - name: sequences + description: list of packet sequences + in: path + required: true + type: array + items: + type: string + format: uint64 + collectionFormat: csv + minItems: 1 + tags: + - Query + /ibc/core/channel/v2/clients/{client_id}/packet_commitments/{sequence}: + get: + summary: PacketCommitment queries a stored packet commitment hash. + operationId: PacketCommitmentV2 + responses: + '200': + description: A successful response. + schema: + type: object + properties: + commitment: + type: string + format: byte + title: packet associated with the request fields + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + description: >- + QueryPacketCommitmentResponse is the response type for the + Query/PacketCommitment RPC method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: client_id + description: client unique identifier + in: path + required: true + type: string + - name: sequence + description: packet sequence + in: path + required: true + type: string + format: uint64 + tags: + - Query + /ibc/core/channel/v2/clients/{client_id}/packet_receipts/{sequence}: + get: + summary: PacketReceipt queries a stored packet receipt. + operationId: PacketReceiptV2 + responses: + '200': + description: A successful response. + schema: + type: object + properties: + received: + type: boolean + title: success flag for if receipt exists + proof: + type: string + format: byte + title: merkle proof of existence or absence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height + while keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are + overridden to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero + values for both revision_number and revision_height. + description: >- + QueryPacketReceiptResponse is the response type for the + Query/PacketReceipt RPC method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of + the serialized + + protocol buffer message. This string must contain at + least + + one "/" character. The last segment of the URL's path + must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in + a canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary + all types that they + + expect it to use in the context of Any. However, for + URLs which use the + + scheme `http`, `https`, or no scheme, one can optionally + set up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based + on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in + the official + + protobuf release, and it is not used for type URLs + beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer + message along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values + in the form + + of utility functions or additional generated methods of the + Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by + default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the + last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield + type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with + an + + additional field `@type` which contains the type URL. + Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom + JSON + + representation, that representation will be embedded adding + a field + + `value` which holds the custom JSON in addition to the + `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + parameters: + - name: client_id + description: client unique identifier + in: path + required: true + type: string + - name: sequence + description: packet sequence + in: path + required: true + type: string + format: uint64 + tags: + - Query + /ibc/lightclients/wasm/v1/checksums: + get: + summary: Get all Wasm checksums + operationId: Checksums + responses: + '200': + description: A successful response. + schema: + type: object + properties: + checksums: + type: array + items: + type: string + description: >- + checksums is a list of the hex encoded checksums of all wasm + codes stored. + pagination: + description: pagination defines the pagination in the response. + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: >- + QueryChecksumsResponse is the response type for the + Query/Checksums RPC method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + value: + type: string + format: byte + parameters: + - name: pagination.key + description: |- + key is a value returned in PageResponse.next_key to begin + querying the next page most efficiently. Only one of offset or key + should be set. + in: query + required: false + type: string + format: byte + - name: pagination.offset + description: >- + offset is a numeric offset that can be used when key is unavailable. + + It is less efficient than using key. Only one of offset or key + should + + be set. + in: query + required: false + type: string + format: uint64 + - name: pagination.limit + description: >- + limit is the total number of results to be returned in the result + page. + + If left empty it will default to a value to be set by each app. + in: query + required: false + type: string + format: uint64 + - name: pagination.count_total + description: >- + count_total is set to true to indicate that the result set should + include + + a count of the total number of items available for pagination in + UIs. + + count_total is only respected when offset is used. It is ignored + when key + + is set. + in: query + required: false + type: boolean + - name: pagination.reverse + description: >- + reverse is set to true if results are to be returned in the + descending order. + + + Since: cosmos-sdk 0.43 + in: query + required: false + type: boolean + tags: + - Query + /ibc/lightclients/wasm/v1/checksums/{checksum}/code: + get: + summary: Get Wasm code for given checksum + operationId: Code + responses: + '200': + description: A successful response. + schema: + type: object + properties: + data: + type: string + format: byte + description: >- + QueryCodeResponse is the response type for the Query/Code RPC + method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + value: + type: string + format: byte + parameters: + - name: checksum + description: checksum is a hex encoded string of the code stored. + in: path + required: true + type: string + tags: + - Query +definitions: + cosmos.base.query.v1beta1.PageRequest: + type: object + properties: + key: + type: string + format: byte + description: |- + key is a value returned in PageResponse.next_key to begin + querying the next page most efficiently. Only one of offset or key + should be set. + offset: + type: string + format: uint64 + description: |- + offset is a numeric offset that can be used when key is unavailable. + It is less efficient than using key. Only one of offset or key should + be set. + limit: + type: string + format: uint64 + description: >- + limit is the total number of results to be returned in the result + page. + + If left empty it will default to a value to be set by each app. + count_total: + type: boolean + description: >- + count_total is set to true to indicate that the result set should + include + + a count of the total number of items available for pagination in UIs. + + count_total is only respected when offset is used. It is ignored when + key + + is set. + reverse: + type: boolean + description: >- + reverse is set to true if results are to be returned in the descending + order. + + + Since: cosmos-sdk 0.43 + description: |- + message SomeRequest { + Foo some_parameter = 1; + PageRequest pagination = 2; + } + title: |- + PageRequest is to be embedded in gRPC request messages for efficient + pagination. Ex: + cosmos.base.query.v1beta1.PageResponse: + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: |- + total is total number of results available if PageRequest.count_total + was set, its value is undefined otherwise + description: |- + PageResponse is to be embedded in gRPC response messages where the + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + cosmos.base.v1beta1.Coin: + type: object + properties: + denom: + type: string + amount: + type: string + description: |- + Coin defines a token with a denomination and an amount. + + NOTE: The amount field is an Int which implements the custom method + signatures required by gogoproto. + google.protobuf.Any: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of the + serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a canonical + form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all types that + they + + expect it to use in the context of Any. However, for URLs which use + the + + scheme `http`, `https`, or no scheme, one can optionally set up a type + + server that maps type URLs to message definitions as follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the official + + protobuf release, and it is not used for type URLs beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above specified + type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message along with + a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in the form + + of utility functions or additional generated methods of the Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default use + + 'type.googleapis.com/full.type.name' as the type URL and the unpack + + methods only use the fully qualified type name after the last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom JSON + + representation, that representation will be embedded adding a field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + grpc.gateway.runtime.Error: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of the + serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must + represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a + canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all types + that they + + expect it to use in the context of Any. However, for URLs which + use the + + scheme `http`, `https`, or no scheme, one can optionally set up + a type + + server that maps type URLs to message definitions as follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the + official + + protobuf release, and it is not used for type URLs beginning + with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) might + be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message along + with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in the + form + + of utility functions or additional generated methods of the Any + type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default use + + 'type.googleapis.com/full.type.name' as the type URL and the unpack + + methods only use the fully qualified type name after the last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom JSON + + representation, that representation will be embedded adding a field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + ibc.applications.transfer.v1.Denom: + type: object + properties: + base: + type: string + title: the base token denomination + trace: + type: array + items: + type: object + properties: + port_id: + type: string + channel_id: + type: string + description: >- + Hop defines a port ID, channel ID pair specifying where tokens must + be forwarded + + next in a multihop transfer. + title: the trace of the token + description: >- + Denom holds the base denom of a Token and a trace of the chains it was + sent through. + ibc.applications.transfer.v1.Hop: + type: object + properties: + port_id: + type: string + channel_id: + type: string + description: >- + Hop defines a port ID, channel ID pair specifying where tokens must be + forwarded + + next in a multihop transfer. + ibc.applications.transfer.v1.Params: + type: object + properties: + send_enabled: + type: boolean + description: >- + send_enabled enables or disables all cross-chain token transfers from + this + + chain. + receive_enabled: + type: boolean + description: >- + receive_enabled enables or disables all cross-chain token transfers to + this + + chain. + description: >- + Params defines the set of IBC transfer parameters. + + NOTE: To prevent a single token from being transferred, set the + + TransfersEnabled parameter to true and then set the bank module's + SendEnabled + + parameter for the denomination to false. + ibc.applications.transfer.v1.QueryDenomHashResponse: + type: object + properties: + hash: + type: string + description: hash (in hex format) of the denomination trace information. + description: |- + QueryDenomHashResponse is the response type for the Query/DenomHash RPC + method. + ibc.applications.transfer.v1.QueryDenomResponse: + type: object + properties: + denom: + type: object + properties: + base: + type: string + title: the base token denomination + trace: + type: array + items: + type: object + properties: + port_id: + type: string + channel_id: + type: string + description: >- + Hop defines a port ID, channel ID pair specifying where tokens + must be forwarded + + next in a multihop transfer. + title: the trace of the token + description: >- + Denom holds the base denom of a Token and a trace of the chains it was + sent through. + description: |- + QueryDenomResponse is the response type for the Query/Denom RPC + method. + ibc.applications.transfer.v1.QueryDenomsResponse: + type: object + properties: + denoms: + type: array + items: + type: object + properties: + base: + type: string + title: the base token denomination + trace: + type: array + items: + type: object + properties: + port_id: + type: string + channel_id: + type: string + description: >- + Hop defines a port ID, channel ID pair specifying where tokens + must be forwarded + + next in a multihop transfer. + title: the trace of the token + description: >- + Denom holds the base denom of a Token and a trace of the chains it + was sent through. + description: denoms returns all denominations. + pagination: + description: pagination defines the pagination in the response. + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: |- + QueryDenomsResponse is the response type for the Query/Denoms RPC + method. + ibc.applications.transfer.v1.QueryEscrowAddressResponse: + type: object + properties: + escrow_address: + type: string + title: the escrow account address + description: >- + QueryEscrowAddressResponse is the response type of the EscrowAddress RPC + method. + ibc.applications.transfer.v1.QueryParamsResponse: + type: object + properties: + params: + description: params defines the parameters of the module. + type: object + properties: + send_enabled: + type: boolean + description: >- + send_enabled enables or disables all cross-chain token transfers + from this + + chain. + receive_enabled: + type: boolean + description: >- + receive_enabled enables or disables all cross-chain token + transfers to this + + chain. + description: QueryParamsResponse is the response type for the Query/Params RPC method. + ibc.applications.transfer.v1.QueryTotalEscrowForDenomResponse: + type: object + properties: + amount: + type: object + properties: + denom: + type: string + amount: + type: string + description: |- + Coin defines a token with a denomination and an amount. + + NOTE: The amount field is an Int which implements the custom method + signatures required by gogoproto. + description: >- + QueryTotalEscrowForDenomResponse is the response type for + TotalEscrowForDenom RPC method. + ibc.applications.interchain_accounts.controller.v1.Params: + type: object + properties: + controller_enabled: + type: boolean + description: controller_enabled enables or disables the controller submodule. + description: |- + Params defines the set of on-chain interchain accounts parameters. + The following parameters may be used to disable the controller submodule. + ibc.applications.interchain_accounts.controller.v1.QueryInterchainAccountResponse: + type: object + properties: + address: + type: string + description: >- + QueryInterchainAccountResponse the response type for the + Query/InterchainAccount RPC method. + ibc.applications.interchain_accounts.controller.v1.QueryParamsResponse: + type: object + properties: + params: + description: params defines the parameters of the module. + type: object + properties: + controller_enabled: + type: boolean + description: controller_enabled enables or disables the controller submodule. + description: QueryParamsResponse is the response type for the Query/Params RPC method. + ibc.applications.interchain_accounts.host.v1.Params: + type: object + properties: + host_enabled: + type: boolean + description: host_enabled enables or disables the host submodule. + allow_messages: + type: array + items: + type: string + description: >- + allow_messages defines a list of sdk message typeURLs allowed to be + executed on a host chain. + description: |- + Params defines the set of on-chain interchain accounts parameters. + The following parameters may be used to disable the host submodule. + ibc.applications.interchain_accounts.host.v1.QueryParamsResponse: + type: object + properties: + params: + description: params defines the parameters of the module. + type: object + properties: + host_enabled: + type: boolean + description: host_enabled enables or disables the host submodule. + allow_messages: + type: array + items: + type: string + description: >- + allow_messages defines a list of sdk message typeURLs allowed to + be executed on a host chain. + description: QueryParamsResponse is the response type for the Query/Params RPC method. + ibc.core.client.v1.ConsensusStateWithHeight: + type: object + properties: + height: + title: consensus state height + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + consensus_state: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of the + serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must + represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a + canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all types + that they + + expect it to use in the context of Any. However, for URLs which + use the + + scheme `http`, `https`, or no scheme, one can optionally set up a + type + + server that maps type URLs to message definitions as follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the + official + + protobuf release, and it is not used for type URLs beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above specified + type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message along + with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in the + form + + of utility functions or additional generated methods of the Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default use + + 'type.googleapis.com/full.type.name' as the type URL and the unpack + + methods only use the fully qualified type name after the last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom JSON + + representation, that representation will be embedded adding a field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: consensus state + description: >- + ConsensusStateWithHeight defines a consensus state with an additional + height + + field. + ibc.core.client.v1.Height: + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while keeping + + RevisionNumber the same. However some consensus algorithms may choose to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so that + + height continues to be monitonically increasing even as the RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for both + revision_number and revision_height. + title: >- + Height is a monotonically increasing data type + + that can be compared against another Height for the purposes of updating + and + + freezing clients + ibc.core.client.v1.IdentifiedClientState: + type: object + properties: + client_id: + type: string + title: client identifier + client_state: + title: client state + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of the + serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must + represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a + canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all types + that they + + expect it to use in the context of Any. However, for URLs which + use the + + scheme `http`, `https`, or no scheme, one can optionally set up a + type + + server that maps type URLs to message definitions as follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the + official + + protobuf release, and it is not used for type URLs beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above specified + type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message along + with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in the + form + + of utility functions or additional generated methods of the Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default use + + 'type.googleapis.com/full.type.name' as the type URL and the unpack + + methods only use the fully qualified type name after the last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom JSON + + representation, that representation will be embedded adding a field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + description: |- + IdentifiedClientState defines a client state with an additional client + identifier field. + ibc.core.client.v1.Params: + type: object + properties: + allowed_clients: + type: array + items: + type: string + description: >- + allowed_clients defines the list of allowed client state types which + can be created + + and interacted with. If a client type is removed from the allowed + clients list, usage + + of this client will be disabled until it is added again to the list. + description: Params defines the set of IBC light client parameters. + ibc.core.client.v1.QueryClientParamsResponse: + type: object + properties: + params: + description: params defines the parameters of the module. + type: object + properties: + allowed_clients: + type: array + items: + type: string + description: >- + allowed_clients defines the list of allowed client state types + which can be created + + and interacted with. If a client type is removed from the allowed + clients list, usage + + of this client will be disabled until it is added again to the + list. + description: >- + QueryClientParamsResponse is the response type for the Query/ClientParams + RPC + + method. + ibc.core.client.v1.QueryClientStateResponse: + type: object + properties: + client_state: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of the + serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must + represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a + canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all types + that they + + expect it to use in the context of Any. However, for URLs which + use the + + scheme `http`, `https`, or no scheme, one can optionally set up a + type + + server that maps type URLs to message definitions as follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the + official + + protobuf release, and it is not used for type URLs beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above specified + type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message along + with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in the + form + + of utility functions or additional generated methods of the Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default use + + 'type.googleapis.com/full.type.name' as the type URL and the unpack + + methods only use the fully qualified type name after the last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom JSON + + representation, that representation will be embedded adding a field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: client state associated with the request identifier + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + description: >- + QueryClientStateResponse is the response type for the Query/ClientState + RPC + + method. Besides the client state, it includes a proof and the height from + + which the proof was retrieved. + ibc.core.client.v1.QueryClientStatesResponse: + type: object + properties: + client_states: + type: array + items: + type: object + properties: + client_id: + type: string + title: client identifier + client_state: + title: client state + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of the + serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must + represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a + canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all + types that they + + expect it to use in the context of Any. However, for URLs + which use the + + scheme `http`, `https`, or no scheme, one can optionally set + up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on + the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the + official + + protobuf release, and it is not used for type URLs beginning + with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message + along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in + the form + + of utility functions or additional generated methods of the Any + type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default + use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the last + '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom JSON + + representation, that representation will be embedded adding a + field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + description: >- + IdentifiedClientState defines a client state with an additional + client + + identifier field. + description: list of stored ClientStates of the chain. + pagination: + title: pagination response + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: |- + PageResponse is to be embedded in gRPC response messages where the + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + description: >- + QueryClientStatesResponse is the response type for the Query/ClientStates + RPC + + method. + ibc.core.client.v1.QueryClientStatusResponse: + type: object + properties: + status: + type: string + description: >- + QueryClientStatusResponse is the response type for the Query/ClientStatus + RPC + + method. It returns the current status of the IBC client. + ibc.core.client.v1.QueryConsensusStateHeightsResponse: + type: object + properties: + consensus_state_heights: + type: array + items: + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is incremented + so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + title: >- + Height is a monotonically increasing data type + + that can be compared against another Height for the purposes of + updating and + + freezing clients + title: consensus state heights + pagination: + title: pagination response + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: |- + PageResponse is to be embedded in gRPC response messages where the + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + title: |- + QueryConsensusStateHeightsResponse is the response type for the + Query/ConsensusStateHeights RPC method + ibc.core.client.v1.QueryConsensusStateResponse: + type: object + properties: + consensus_state: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of the + serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must + represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a + canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all types + that they + + expect it to use in the context of Any. However, for URLs which + use the + + scheme `http`, `https`, or no scheme, one can optionally set up a + type + + server that maps type URLs to message definitions as follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the + official + + protobuf release, and it is not used for type URLs beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above specified + type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message along + with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in the + form + + of utility functions or additional generated methods of the Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default use + + 'type.googleapis.com/full.type.name' as the type URL and the unpack + + methods only use the fully qualified type name after the last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom JSON + + representation, that representation will be embedded adding a field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: >- + consensus state associated with the client identifier at the given + height + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + title: >- + Height is a monotonically increasing data type + + that can be compared against another Height for the purposes of + updating and + + freezing clients + title: >- + QueryConsensusStateResponse is the response type for the + Query/ConsensusState + + RPC method + ibc.core.client.v1.QueryConsensusStatesResponse: + type: object + properties: + consensus_states: + type: array + items: + type: object + properties: + height: + title: consensus state height + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may + choose to + + reset the height in certain conditions e.g. hard forks, + state-machine + + breaking changes In these cases, the RevisionNumber is + incremented so that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden + to explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values + for both revision_number and revision_height. + consensus_state: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of the + serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must + represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a + canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all + types that they + + expect it to use in the context of Any. However, for URLs + which use the + + scheme `http`, `https`, or no scheme, one can optionally set + up a type + + server that maps type URLs to message definitions as + follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a + [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on + the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the + official + + protobuf release, and it is not used for type URLs beginning + with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) + might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message + along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in + the form + + of utility functions or additional generated methods of the Any + type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default + use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the last + '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom JSON + + representation, that representation will be embedded adding a + field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: consensus state + description: >- + ConsensusStateWithHeight defines a consensus state with an + additional height + + field. + title: consensus states associated with the identifier + pagination: + title: pagination response + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: |- + PageResponse is to be embedded in gRPC response messages where the + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + title: |- + QueryConsensusStatesResponse is the response type for the + Query/ConsensusStates RPC method + ibc.core.client.v1.QueryUpgradedClientStateResponse: + type: object + properties: + upgraded_client_state: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of the + serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must + represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a + canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all types + that they + + expect it to use in the context of Any. However, for URLs which + use the + + scheme `http`, `https`, or no scheme, one can optionally set up a + type + + server that maps type URLs to message definitions as follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the + official + + protobuf release, and it is not used for type URLs beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above specified + type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message along + with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in the + form + + of utility functions or additional generated methods of the Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default use + + 'type.googleapis.com/full.type.name' as the type URL and the unpack + + methods only use the fully qualified type name after the last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom JSON + + representation, that representation will be embedded adding a field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: client state associated with the request identifier + description: |- + QueryUpgradedClientStateResponse is the response type for the + Query/UpgradedClientState RPC method. + ibc.core.client.v1.QueryUpgradedConsensusStateResponse: + type: object + properties: + upgraded_consensus_state: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of the + serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must + represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a + canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all types + that they + + expect it to use in the context of Any. However, for URLs which + use the + + scheme `http`, `https`, or no scheme, one can optionally set up a + type + + server that maps type URLs to message definitions as follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the + official + + protobuf release, and it is not used for type URLs beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above specified + type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message along + with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in the + form + + of utility functions or additional generated methods of the Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default use + + 'type.googleapis.com/full.type.name' as the type URL and the unpack + + methods only use the fully qualified type name after the last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom JSON + + representation, that representation will be embedded adding a field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: Consensus state associated with the request identifier + description: |- + QueryUpgradedConsensusStateResponse is the response type for the + Query/UpgradedConsensusState RPC method. + ibc.core.client.v1.QueryVerifyMembershipRequest: + type: object + properties: + client_id: + type: string + description: client unique identifier. + proof: + type: string + format: byte + description: the proof to be verified by the client. + proof_height: + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + title: >- + Height is a monotonically increasing data type + + that can be compared against another Height for the purposes of + updating and + + freezing clients + value: + type: string + format: byte + description: the value which is proven. + time_delay: + type: string + format: uint64 + title: optional time delay + block_delay: + type: string + format: uint64 + title: optional block delay + merkle_path: + description: the commitment key path. + type: object + properties: + key_path: + type: array + items: + type: string + format: byte + title: >- + QueryVerifyMembershipRequest is the request type for the + Query/VerifyMembership RPC method + ibc.core.client.v1.QueryVerifyMembershipResponse: + type: object + properties: + success: + type: boolean + description: boolean indicating success or failure of proof verification. + title: >- + QueryVerifyMembershipResponse is the response type for the + Query/VerifyMembership RPC method + ibc.core.commitment.v2.MerklePath: + type: object + properties: + key_path: + type: array + items: + type: string + format: byte + description: >- + MerklePath is the path used to verify commitment proofs, which can be an + + arbitrary structured object (defined by a commitment type). + + ICS-23 verification supports membership proofs for nested merkle trees. + + The ICS-24 standard provable keys MUST be stored in the lowest level tree + with an optional prefix. + + The IC24 provable tree may then be stored in a higher level tree(s) that + hash up to the root hash + + stored in the consensus state of the client. + + Each element of the path represents the key of a merkle tree from the root + to the leaf. + + The elements of the path before the final element must be the path to the + tree that contains + + the ICS24 provable store. Thus, it should remain constant for all ICS24 + proofs. + + The final element of the path is the key of the leaf in the ICS24 provable + store, + + Thus IBC core will append the ICS24 path to the final element of the + MerklePath + + stored in the counterparty to create the full path to the leaf for proof + verification. + + Examples: + + Cosmos SDK: + + The Cosmos SDK commits to a multi-tree where each store is an IAVL tree + and all store hashes + + are hashed in a simple merkle tree to get the final root hash. Thus, the + MerklePath in the counterparty + + MerklePrefix has the following structure: ["ibc", ""] + + The core IBC handler will append the ICS24 path to the final element of + the MerklePath + + like so: ["ibc", "{packetCommitmentPath}"] which will then be used for + final verification. + + Ethereum: + + The Ethereum client commits to a single Patricia merkle trie. The ICS24 + provable store is managed + + by the smart contract state. Each smart contract has a specific prefix + reserved within the global trie. + + Thus the MerklePath in the counterparty is the prefix to the smart + contract state in the global trie. + + Since there is only one tree in the commitment structure of ethereum the + MerklePath in the counterparty + + MerklePrefix has the following structure: + ["IBCCoreContractAddressStoragePrefix"] + + The core IBC handler will append the ICS24 path to the final element of + the MerklePath + + like so: ["IBCCoreContractAddressStoragePrefix{packetCommitmentPath}"] + which will then be used for final + + verification. Thus the MerklePath in the counterparty MerklePrefix is the + nested key path from the root hash of the + + consensus state down to the ICS24 provable store. The IBC handler + retrieves the counterparty key path to the ICS24 + + provable store from the MerklePath and appends the ICS24 path to get the + final key path to the value being verified + + by the client against the root hash in the client's consensus state. + ibc.core.commitment.v1.MerklePrefix: + type: object + properties: + key_prefix: + type: string + format: byte + title: |- + MerklePrefix is merkle path prefixed to the key. + The constructed key from the Path and the key will be append(Path.KeyPath, + append(Path.KeyPrefix, key...)) + ibc.core.connection.v1.ConnectionEnd: + type: object + properties: + client_id: + type: string + description: client associated with this connection. + versions: + type: array + items: + type: object + properties: + identifier: + type: string + title: unique version identifier + features: + type: array + items: + type: string + title: list of features compatible with the specified identifier + description: >- + Version defines the versioning scheme used to negotiate the IBC + version in + + the connection handshake. + description: >- + IBC version which can be utilised to determine encodings or protocols + for + + channels or packets utilising this connection. + state: + description: current state of the connection end. + type: string + enum: + - STATE_UNINITIALIZED_UNSPECIFIED + - STATE_INIT + - STATE_TRYOPEN + - STATE_OPEN + default: STATE_UNINITIALIZED_UNSPECIFIED + counterparty: + description: counterparty chain associated with this connection. + type: object + properties: + client_id: + type: string + description: >- + identifies the client on the counterparty chain associated with a + given + + connection. + connection_id: + type: string + description: >- + identifies the connection end on the counterparty chain associated + with a + + given connection. + prefix: + description: commitment merkle prefix of the counterparty chain. + type: object + properties: + key_prefix: + type: string + format: byte + title: >- + MerklePrefix is merkle path prefixed to the key. + + The constructed key from the Path and the key will be + append(Path.KeyPath, + + append(Path.KeyPrefix, key...)) + delay_period: + type: string + format: uint64 + description: >- + delay period that must pass before a consensus state can be used for + + packet-verification NOTE: delay period logic is only implemented by + some + + clients. + description: |- + ConnectionEnd defines a stateful object on a chain connected to another + separate one. + NOTE: there must only be 2 defined ConnectionEnds to establish + a connection between two chains. + ibc.core.connection.v1.Counterparty: + type: object + properties: + client_id: + type: string + description: >- + identifies the client on the counterparty chain associated with a + given + + connection. + connection_id: + type: string + description: >- + identifies the connection end on the counterparty chain associated + with a + + given connection. + prefix: + description: commitment merkle prefix of the counterparty chain. + type: object + properties: + key_prefix: + type: string + format: byte + title: >- + MerklePrefix is merkle path prefixed to the key. + + The constructed key from the Path and the key will be + append(Path.KeyPath, + + append(Path.KeyPrefix, key...)) + description: >- + Counterparty defines the counterparty chain associated with a connection + end. + ibc.core.connection.v1.IdentifiedConnection: + type: object + properties: + id: + type: string + description: connection identifier. + client_id: + type: string + description: client associated with this connection. + versions: + type: array + items: + type: object + properties: + identifier: + type: string + title: unique version identifier + features: + type: array + items: + type: string + title: list of features compatible with the specified identifier + description: >- + Version defines the versioning scheme used to negotiate the IBC + version in + + the connection handshake. + title: >- + IBC version which can be utilised to determine encodings or protocols + for + + channels or packets utilising this connection + state: + description: current state of the connection end. + type: string + enum: + - STATE_UNINITIALIZED_UNSPECIFIED + - STATE_INIT + - STATE_TRYOPEN + - STATE_OPEN + default: STATE_UNINITIALIZED_UNSPECIFIED + counterparty: + description: counterparty chain associated with this connection. + type: object + properties: + client_id: + type: string + description: >- + identifies the client on the counterparty chain associated with a + given + + connection. + connection_id: + type: string + description: >- + identifies the connection end on the counterparty chain associated + with a + + given connection. + prefix: + description: commitment merkle prefix of the counterparty chain. + type: object + properties: + key_prefix: + type: string + format: byte + title: >- + MerklePrefix is merkle path prefixed to the key. + + The constructed key from the Path and the key will be + append(Path.KeyPath, + + append(Path.KeyPrefix, key...)) + delay_period: + type: string + format: uint64 + description: delay period associated with this connection. + description: |- + IdentifiedConnection defines a connection with additional connection + identifier field. + ibc.core.connection.v1.Params: + type: object + properties: + max_expected_time_per_block: + type: string + format: uint64 + description: >- + maximum expected time per block (in nanoseconds), used to enforce + block delay. This parameter should reflect the + + largest amount of time that the chain might reasonably take to produce + the next block under normal operating + + conditions. A safe choice is 3-5x the expected time per block. + description: Params defines the set of Connection parameters. + ibc.core.connection.v1.QueryClientConnectionsResponse: + type: object + properties: + connection_paths: + type: array + items: + type: string + description: slice of all the connection paths associated with a client. + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was generated + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + title: |- + QueryClientConnectionsResponse is the response type for the + Query/ClientConnections RPC method + ibc.core.connection.v1.QueryConnectionClientStateResponse: + type: object + properties: + identified_client_state: + title: client state associated with the channel + type: object + properties: + client_id: + type: string + title: client identifier + client_state: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of the + serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must + represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a + canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all + types that they + + expect it to use in the context of Any. However, for URLs + which use the + + scheme `http`, `https`, or no scheme, one can optionally set + up a type + + server that maps type URLs to message definitions as follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on + the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the + official + + protobuf release, and it is not used for type URLs beginning + with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) might + be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message + along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in the + form + + of utility functions or additional generated methods of the Any + type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom JSON + + representation, that representation will be embedded adding a + field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: client state + description: |- + IdentifiedClientState defines a client state with an additional client + identifier field. + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + title: |- + QueryConnectionClientStateResponse is the response type for the + Query/ConnectionClientState RPC method + ibc.core.connection.v1.QueryConnectionConsensusStateResponse: + type: object + properties: + consensus_state: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of the + serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must + represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a + canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all types + that they + + expect it to use in the context of Any. However, for URLs which + use the + + scheme `http`, `https`, or no scheme, one can optionally set up a + type + + server that maps type URLs to message definitions as follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the + official + + protobuf release, and it is not used for type URLs beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above specified + type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message along + with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in the + form + + of utility functions or additional generated methods of the Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default use + + 'type.googleapis.com/full.type.name' as the type URL and the unpack + + methods only use the fully qualified type name after the last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom JSON + + representation, that representation will be embedded adding a field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: consensus state associated with the channel + client_id: + type: string + title: client ID associated with the consensus state + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + title: |- + QueryConnectionConsensusStateResponse is the response type for the + Query/ConnectionConsensusState RPC method + ibc.core.connection.v1.QueryConnectionParamsResponse: + type: object + properties: + params: + description: params defines the parameters of the module. + type: object + properties: + max_expected_time_per_block: + type: string + format: uint64 + description: >- + maximum expected time per block (in nanoseconds), used to enforce + block delay. This parameter should reflect the + + largest amount of time that the chain might reasonably take to + produce the next block under normal operating + + conditions. A safe choice is 3-5x the expected time per block. + description: >- + QueryConnectionParamsResponse is the response type for the + Query/ConnectionParams RPC method. + ibc.core.connection.v1.QueryConnectionResponse: + type: object + properties: + connection: + title: connection associated with the request identifier + type: object + properties: + client_id: + type: string + description: client associated with this connection. + versions: + type: array + items: + type: object + properties: + identifier: + type: string + title: unique version identifier + features: + type: array + items: + type: string + title: list of features compatible with the specified identifier + description: >- + Version defines the versioning scheme used to negotiate the IBC + version in + + the connection handshake. + description: >- + IBC version which can be utilised to determine encodings or + protocols for + + channels or packets utilising this connection. + state: + description: current state of the connection end. + type: string + enum: + - STATE_UNINITIALIZED_UNSPECIFIED + - STATE_INIT + - STATE_TRYOPEN + - STATE_OPEN + default: STATE_UNINITIALIZED_UNSPECIFIED + counterparty: + description: counterparty chain associated with this connection. + type: object + properties: + client_id: + type: string + description: >- + identifies the client on the counterparty chain associated + with a given + + connection. + connection_id: + type: string + description: >- + identifies the connection end on the counterparty chain + associated with a + + given connection. + prefix: + description: commitment merkle prefix of the counterparty chain. + type: object + properties: + key_prefix: + type: string + format: byte + title: >- + MerklePrefix is merkle path prefixed to the key. + + The constructed key from the Path and the key will be + append(Path.KeyPath, + + append(Path.KeyPrefix, key...)) + delay_period: + type: string + format: uint64 + description: >- + delay period that must pass before a consensus state can be used + for + + packet-verification NOTE: delay period logic is only implemented + by some + + clients. + description: >- + ConnectionEnd defines a stateful object on a chain connected to + another + + separate one. + + NOTE: there must only be 2 defined ConnectionEnds to establish + + a connection between two chains. + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + description: >- + QueryConnectionResponse is the response type for the Query/Connection RPC + + method. Besides the connection end, it includes a proof and the height + from + + which the proof was retrieved. + ibc.core.connection.v1.QueryConnectionsResponse: + type: object + properties: + connections: + type: array + items: + type: object + properties: + id: + type: string + description: connection identifier. + client_id: + type: string + description: client associated with this connection. + versions: + type: array + items: + type: object + properties: + identifier: + type: string + title: unique version identifier + features: + type: array + items: + type: string + title: list of features compatible with the specified identifier + description: >- + Version defines the versioning scheme used to negotiate the + IBC version in + + the connection handshake. + title: >- + IBC version which can be utilised to determine encodings or + protocols for + + channels or packets utilising this connection + state: + description: current state of the connection end. + type: string + enum: + - STATE_UNINITIALIZED_UNSPECIFIED + - STATE_INIT + - STATE_TRYOPEN + - STATE_OPEN + default: STATE_UNINITIALIZED_UNSPECIFIED + counterparty: + description: counterparty chain associated with this connection. + type: object + properties: + client_id: + type: string + description: >- + identifies the client on the counterparty chain associated + with a given + + connection. + connection_id: + type: string + description: >- + identifies the connection end on the counterparty chain + associated with a + + given connection. + prefix: + description: commitment merkle prefix of the counterparty chain. + type: object + properties: + key_prefix: + type: string + format: byte + title: >- + MerklePrefix is merkle path prefixed to the key. + + The constructed key from the Path and the key will be + append(Path.KeyPath, + + append(Path.KeyPrefix, key...)) + delay_period: + type: string + format: uint64 + description: delay period associated with this connection. + description: |- + IdentifiedConnection defines a connection with additional connection + identifier field. + description: list of stored connections of the chain. + pagination: + title: pagination response + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: |- + PageResponse is to be embedded in gRPC response messages where the + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + height: + title: query block height + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + description: >- + QueryConnectionsResponse is the response type for the Query/Connections + RPC + + method. + ibc.core.connection.v1.State: + type: string + enum: + - STATE_UNINITIALIZED_UNSPECIFIED + - STATE_INIT + - STATE_TRYOPEN + - STATE_OPEN + default: STATE_UNINITIALIZED_UNSPECIFIED + description: |- + State defines if a connection is in one of the following states: + INIT, TRYOPEN, OPEN or UNINITIALIZED. + + - STATE_UNINITIALIZED_UNSPECIFIED: Default State + - STATE_INIT: A connection end has just started the opening handshake. + - STATE_TRYOPEN: A connection end has acknowledged the handshake step on the counterparty + chain. + - STATE_OPEN: A connection end has completed the handshake. + ibc.core.connection.v1.Version: + type: object + properties: + identifier: + type: string + title: unique version identifier + features: + type: array + items: + type: string + title: list of features compatible with the specified identifier + description: |- + Version defines the versioning scheme used to negotiate the IBC version in + the connection handshake. + ibc.core.channel.v1.Channel: + type: object + properties: + state: + title: current state of the channel end + type: string + enum: + - STATE_UNINITIALIZED_UNSPECIFIED + - STATE_INIT + - STATE_TRYOPEN + - STATE_OPEN + - STATE_CLOSED + default: STATE_UNINITIALIZED_UNSPECIFIED + description: |- + State defines if a channel is in one of the following states: + CLOSED, INIT, TRYOPEN, OPEN, or UNINITIALIZED. + + - STATE_UNINITIALIZED_UNSPECIFIED: Default State + - STATE_INIT: A channel has just started the opening handshake. + - STATE_TRYOPEN: A channel has acknowledged the handshake step on the counterparty chain. + - STATE_OPEN: A channel has completed the handshake. Open channels are + ready to send and receive packets. + - STATE_CLOSED: A channel has been closed and can no longer be used to send or receive + packets. + ordering: + title: whether the channel is ordered or unordered + type: string + enum: + - ORDER_NONE_UNSPECIFIED + - ORDER_UNORDERED + - ORDER_ORDERED + default: ORDER_NONE_UNSPECIFIED + description: |- + - ORDER_NONE_UNSPECIFIED: zero-value for channel ordering + - ORDER_UNORDERED: packets can be delivered in any order, which may differ from the order in + which they were sent. + - ORDER_ORDERED: packets are delivered exactly in the order which they were sent + counterparty: + title: counterparty channel end + type: object + properties: + port_id: + type: string + description: >- + port on the counterparty chain which owns the other end of the + channel. + channel_id: + type: string + title: channel end on the counterparty chain + connection_hops: + type: array + items: + type: string + title: |- + list of connection identifiers, in order, along which packets sent on + this channel will travel + version: + type: string + title: opaque channel version, which is agreed upon during the handshake + description: |- + Channel defines pipeline for exactly-once packet delivery between specific + modules on separate blockchains, which has at least one end capable of + sending packets and one end capable of receiving packets. + ibc.core.channel.v1.Counterparty: + type: object + properties: + port_id: + type: string + description: >- + port on the counterparty chain which owns the other end of the + channel. + channel_id: + type: string + title: channel end on the counterparty chain + title: Counterparty defines a channel end counterparty + ibc.core.channel.v1.IdentifiedChannel: + type: object + properties: + state: + title: current state of the channel end + type: string + enum: + - STATE_UNINITIALIZED_UNSPECIFIED + - STATE_INIT + - STATE_TRYOPEN + - STATE_OPEN + - STATE_CLOSED + default: STATE_UNINITIALIZED_UNSPECIFIED + description: |- + State defines if a channel is in one of the following states: + CLOSED, INIT, TRYOPEN, OPEN, or UNINITIALIZED. + + - STATE_UNINITIALIZED_UNSPECIFIED: Default State + - STATE_INIT: A channel has just started the opening handshake. + - STATE_TRYOPEN: A channel has acknowledged the handshake step on the counterparty chain. + - STATE_OPEN: A channel has completed the handshake. Open channels are + ready to send and receive packets. + - STATE_CLOSED: A channel has been closed and can no longer be used to send or receive + packets. + ordering: + title: whether the channel is ordered or unordered + type: string + enum: + - ORDER_NONE_UNSPECIFIED + - ORDER_UNORDERED + - ORDER_ORDERED + default: ORDER_NONE_UNSPECIFIED + description: |- + - ORDER_NONE_UNSPECIFIED: zero-value for channel ordering + - ORDER_UNORDERED: packets can be delivered in any order, which may differ from the order in + which they were sent. + - ORDER_ORDERED: packets are delivered exactly in the order which they were sent + counterparty: + title: counterparty channel end + type: object + properties: + port_id: + type: string + description: >- + port on the counterparty chain which owns the other end of the + channel. + channel_id: + type: string + title: channel end on the counterparty chain + connection_hops: + type: array + items: + type: string + title: |- + list of connection identifiers, in order, along which packets sent on + this channel will travel + version: + type: string + title: opaque channel version, which is agreed upon during the handshake + port_id: + type: string + title: port identifier + channel_id: + type: string + title: channel identifier + description: |- + IdentifiedChannel defines a channel with additional port and channel + identifier fields. + ibc.core.channel.v1.Order: + type: string + enum: + - ORDER_NONE_UNSPECIFIED + - ORDER_UNORDERED + - ORDER_ORDERED + default: ORDER_NONE_UNSPECIFIED + description: |- + - ORDER_NONE_UNSPECIFIED: zero-value for channel ordering + - ORDER_UNORDERED: packets can be delivered in any order, which may differ from the order in + which they were sent. + - ORDER_ORDERED: packets are delivered exactly in the order which they were sent + title: Order defines if a channel is ORDERED or UNORDERED + ibc.core.channel.v1.PacketState: + type: object + properties: + port_id: + type: string + description: channel port identifier. + channel_id: + type: string + description: channel unique identifier. + sequence: + type: string + format: uint64 + description: packet sequence. + data: + type: string + format: byte + description: embedded data that represents packet state. + description: |- + PacketState defines the generic type necessary to retrieve and store + packet commitments, acknowledgements, and receipts. + Caller is responsible for knowing the context necessary to interpret this + state as a commitment, acknowledgement, or a receipt. + ibc.core.channel.v1.QueryChannelClientStateResponse: + type: object + properties: + identified_client_state: + title: client state associated with the channel + type: object + properties: + client_id: + type: string + title: client identifier + client_state: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of the + serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must + represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a + canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all + types that they + + expect it to use in the context of Any. However, for URLs + which use the + + scheme `http`, `https`, or no scheme, one can optionally set + up a type + + server that maps type URLs to message definitions as follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on + the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the + official + + protobuf release, and it is not used for type URLs beginning + with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) might + be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above + specified type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message + along with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in the + form + + of utility functions or additional generated methods of the Any + type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default use + + 'type.googleapis.com/full.type.name' as the type URL and the + unpack + + methods only use the fully qualified type name after the last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom JSON + + representation, that representation will be embedded adding a + field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: client state + description: |- + IdentifiedClientState defines a client state with an additional client + identifier field. + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + title: |- + QueryChannelClientStateResponse is the Response type for the + Query/QueryChannelClientState RPC method + ibc.core.channel.v1.QueryChannelConsensusStateResponse: + type: object + properties: + consensus_state: + type: object + properties: + type_url: + type: string + description: >- + A URL/resource name that uniquely identifies the type of the + serialized + + protocol buffer message. This string must contain at least + + one "/" character. The last segment of the URL's path must + represent + + the fully qualified name of the type (as in + + `path/google.protobuf.Duration`). The name should be in a + canonical form + + (e.g., leading "." is not accepted). + + + In practice, teams usually precompile into the binary all types + that they + + expect it to use in the context of Any. However, for URLs which + use the + + scheme `http`, `https`, or no scheme, one can optionally set up a + type + + server that maps type URLs to message definitions as follows: + + + * If no scheme is provided, `https` is assumed. + + * An HTTP GET on the URL must yield a [google.protobuf.Type][] + value in binary format, or produce an error. + * Applications are allowed to cache lookup results based on the + URL, or have them precompiled into a binary to avoid any + lookup. Therefore, binary compatibility needs to be preserved + on changes to types. (Use versioned type names to manage + breaking changes.) + + Note: this functionality is not currently available in the + official + + protobuf release, and it is not used for type URLs beginning with + + type.googleapis.com. + + + Schemes other than `http`, `https` (or the empty scheme) might be + + used with implementation specific semantics. + value: + type: string + format: byte + description: >- + Must be a valid serialized protocol buffer of the above specified + type. + description: >- + `Any` contains an arbitrary serialized protocol buffer message along + with a + + URL that describes the type of the serialized message. + + + Protobuf library provides support to pack/unpack Any values in the + form + + of utility functions or additional generated methods of the Any type. + + + Example 1: Pack and unpack a message in C++. + + Foo foo = ...; + Any any; + any.PackFrom(foo); + ... + if (any.UnpackTo(&foo)) { + ... + } + + Example 2: Pack and unpack a message in Java. + + Foo foo = ...; + Any any = Any.pack(foo); + ... + if (any.is(Foo.class)) { + foo = any.unpack(Foo.class); + } + // or ... + if (any.isSameTypeAs(Foo.getDefaultInstance())) { + foo = any.unpack(Foo.getDefaultInstance()); + } + + Example 3: Pack and unpack a message in Python. + + foo = Foo(...) + any = Any() + any.Pack(foo) + ... + if any.Is(Foo.DESCRIPTOR): + any.Unpack(foo) + ... + + Example 4: Pack and unpack a message in Go + + foo := &pb.Foo{...} + any, err := anypb.New(foo) + if err != nil { + ... + } + ... + foo := &pb.Foo{} + if err := any.UnmarshalTo(foo); err != nil { + ... + } + + The pack methods provided by protobuf library will by default use + + 'type.googleapis.com/full.type.name' as the type URL and the unpack + + methods only use the fully qualified type name after the last '/' + + in the type URL, for example "foo.bar.com/x/y.z" will yield type + + name "y.z". + + + JSON + + + The JSON representation of an `Any` value uses the regular + + representation of the deserialized, embedded message, with an + + additional field `@type` which contains the type URL. Example: + + package google.profile; + message Person { + string first_name = 1; + string last_name = 2; + } + + { + "@type": "type.googleapis.com/google.profile.Person", + "firstName": , + "lastName": + } + + If the embedded message type is well-known and has a custom JSON + + representation, that representation will be embedded adding a field + + `value` which holds the custom JSON in addition to the `@type` + + field. Example (for message [google.protobuf.Duration][]): + + { + "@type": "type.googleapis.com/google.protobuf.Duration", + "value": "1.212s" + } + title: consensus state associated with the channel + client_id: + type: string + title: client ID associated with the consensus state + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + title: |- + QueryChannelClientStateResponse is the Response type for the + Query/QueryChannelClientState RPC method + ibc.core.channel.v1.QueryChannelResponse: + type: object + properties: + channel: + title: channel associated with the request identifiers + type: object + properties: + state: + title: current state of the channel end + type: string + enum: + - STATE_UNINITIALIZED_UNSPECIFIED + - STATE_INIT + - STATE_TRYOPEN + - STATE_OPEN + - STATE_CLOSED + default: STATE_UNINITIALIZED_UNSPECIFIED + description: |- + State defines if a channel is in one of the following states: + CLOSED, INIT, TRYOPEN, OPEN, or UNINITIALIZED. + + - STATE_UNINITIALIZED_UNSPECIFIED: Default State + - STATE_INIT: A channel has just started the opening handshake. + - STATE_TRYOPEN: A channel has acknowledged the handshake step on the counterparty chain. + - STATE_OPEN: A channel has completed the handshake. Open channels are + ready to send and receive packets. + - STATE_CLOSED: A channel has been closed and can no longer be used to send or receive + packets. + ordering: + title: whether the channel is ordered or unordered + type: string + enum: + - ORDER_NONE_UNSPECIFIED + - ORDER_UNORDERED + - ORDER_ORDERED + default: ORDER_NONE_UNSPECIFIED + description: |- + - ORDER_NONE_UNSPECIFIED: zero-value for channel ordering + - ORDER_UNORDERED: packets can be delivered in any order, which may differ from the order in + which they were sent. + - ORDER_ORDERED: packets are delivered exactly in the order which they were sent + counterparty: + title: counterparty channel end + type: object + properties: + port_id: + type: string + description: >- + port on the counterparty chain which owns the other end of the + channel. + channel_id: + type: string + title: channel end on the counterparty chain + connection_hops: + type: array + items: + type: string + title: >- + list of connection identifiers, in order, along which packets sent + on + + this channel will travel + version: + type: string + title: opaque channel version, which is agreed upon during the handshake + description: >- + Channel defines pipeline for exactly-once packet delivery between + specific + + modules on separate blockchains, which has at least one end capable of + + sending packets and one end capable of receiving packets. + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + description: >- + QueryChannelResponse is the response type for the Query/Channel RPC + method. + + Besides the Channel end, it includes a proof and the height from which the + + proof was retrieved. + ibc.core.channel.v1.QueryChannelsResponse: + type: object + properties: + channels: + type: array + items: + type: object + properties: + state: + title: current state of the channel end + type: string + enum: + - STATE_UNINITIALIZED_UNSPECIFIED + - STATE_INIT + - STATE_TRYOPEN + - STATE_OPEN + - STATE_CLOSED + default: STATE_UNINITIALIZED_UNSPECIFIED + description: |- + State defines if a channel is in one of the following states: + CLOSED, INIT, TRYOPEN, OPEN, or UNINITIALIZED. + + - STATE_UNINITIALIZED_UNSPECIFIED: Default State + - STATE_INIT: A channel has just started the opening handshake. + - STATE_TRYOPEN: A channel has acknowledged the handshake step on the counterparty chain. + - STATE_OPEN: A channel has completed the handshake. Open channels are + ready to send and receive packets. + - STATE_CLOSED: A channel has been closed and can no longer be used to send or receive + packets. + ordering: + title: whether the channel is ordered or unordered + type: string + enum: + - ORDER_NONE_UNSPECIFIED + - ORDER_UNORDERED + - ORDER_ORDERED + default: ORDER_NONE_UNSPECIFIED + description: |- + - ORDER_NONE_UNSPECIFIED: zero-value for channel ordering + - ORDER_UNORDERED: packets can be delivered in any order, which may differ from the order in + which they were sent. + - ORDER_ORDERED: packets are delivered exactly in the order which they were sent + counterparty: + title: counterparty channel end + type: object + properties: + port_id: + type: string + description: >- + port on the counterparty chain which owns the other end of + the channel. + channel_id: + type: string + title: channel end on the counterparty chain + connection_hops: + type: array + items: + type: string + title: >- + list of connection identifiers, in order, along which packets + sent on + + this channel will travel + version: + type: string + title: >- + opaque channel version, which is agreed upon during the + handshake + port_id: + type: string + title: port identifier + channel_id: + type: string + title: channel identifier + description: |- + IdentifiedChannel defines a channel with additional port and channel + identifier fields. + description: list of stored channels of the chain. + pagination: + title: pagination response + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: |- + PageResponse is to be embedded in gRPC response messages where the + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + height: + title: query block height + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + description: >- + QueryChannelsResponse is the response type for the Query/Channels RPC + method. + ibc.core.channel.v1.QueryConnectionChannelsResponse: + type: object + properties: + channels: + type: array + items: + type: object + properties: + state: + title: current state of the channel end + type: string + enum: + - STATE_UNINITIALIZED_UNSPECIFIED + - STATE_INIT + - STATE_TRYOPEN + - STATE_OPEN + - STATE_CLOSED + default: STATE_UNINITIALIZED_UNSPECIFIED + description: |- + State defines if a channel is in one of the following states: + CLOSED, INIT, TRYOPEN, OPEN, or UNINITIALIZED. + + - STATE_UNINITIALIZED_UNSPECIFIED: Default State + - STATE_INIT: A channel has just started the opening handshake. + - STATE_TRYOPEN: A channel has acknowledged the handshake step on the counterparty chain. + - STATE_OPEN: A channel has completed the handshake. Open channels are + ready to send and receive packets. + - STATE_CLOSED: A channel has been closed and can no longer be used to send or receive + packets. + ordering: + title: whether the channel is ordered or unordered + type: string + enum: + - ORDER_NONE_UNSPECIFIED + - ORDER_UNORDERED + - ORDER_ORDERED + default: ORDER_NONE_UNSPECIFIED + description: |- + - ORDER_NONE_UNSPECIFIED: zero-value for channel ordering + - ORDER_UNORDERED: packets can be delivered in any order, which may differ from the order in + which they were sent. + - ORDER_ORDERED: packets are delivered exactly in the order which they were sent + counterparty: + title: counterparty channel end + type: object + properties: + port_id: + type: string + description: >- + port on the counterparty chain which owns the other end of + the channel. + channel_id: + type: string + title: channel end on the counterparty chain + connection_hops: + type: array + items: + type: string + title: >- + list of connection identifiers, in order, along which packets + sent on + + this channel will travel + version: + type: string + title: >- + opaque channel version, which is agreed upon during the + handshake + port_id: + type: string + title: port identifier + channel_id: + type: string + title: channel identifier + description: |- + IdentifiedChannel defines a channel with additional port and channel + identifier fields. + description: list of channels associated with a connection. + pagination: + title: pagination response + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: |- + PageResponse is to be embedded in gRPC response messages where the + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + height: + title: query block height + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + title: |- + QueryConnectionChannelsResponse is the Response type for the + Query/QueryConnectionChannels RPC method + ibc.core.channel.v1.QueryNextSequenceReceiveResponse: + type: object + properties: + next_sequence_receive: + type: string + format: uint64 + title: next sequence receive number + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + title: |- + QuerySequenceResponse is the response type for the + Query/QueryNextSequenceReceiveResponse RPC method + ibc.core.channel.v1.QueryNextSequenceSendResponse: + type: object + properties: + next_sequence_send: + type: string + format: uint64 + title: next sequence send number + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + title: |- + QueryNextSequenceSendResponse is the request type for the + Query/QueryNextSequenceSend RPC method + ibc.core.channel.v1.QueryPacketAcknowledgementResponse: + type: object + properties: + acknowledgement: + type: string + format: byte + title: packet associated with the request fields + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + title: |- + QueryPacketAcknowledgementResponse defines the client query response for a + packet which also includes a proof and the height from which the + proof was retrieved + ibc.core.channel.v1.QueryPacketAcknowledgementsResponse: + type: object + properties: + acknowledgements: + type: array + items: + type: object + properties: + port_id: + type: string + description: channel port identifier. + channel_id: + type: string + description: channel unique identifier. + sequence: + type: string + format: uint64 + description: packet sequence. + data: + type: string + format: byte + description: embedded data that represents packet state. + description: >- + PacketState defines the generic type necessary to retrieve and store + + packet commitments, acknowledgements, and receipts. + + Caller is responsible for knowing the context necessary to interpret + this + + state as a commitment, acknowledgement, or a receipt. + pagination: + title: pagination response + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: |- + PageResponse is to be embedded in gRPC response messages where the + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + height: + title: query block height + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + title: |- + QueryPacketAcknowledgemetsResponse is the request type for the + Query/QueryPacketAcknowledgements RPC method + ibc.core.channel.v1.QueryPacketCommitmentResponse: + type: object + properties: + commitment: + type: string + format: byte + title: packet associated with the request fields + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + title: >- + QueryPacketCommitmentResponse defines the client query response for a + packet + + which also includes a proof and the height from which the proof was + + retrieved + ibc.core.channel.v1.QueryPacketCommitmentsResponse: + type: object + properties: + commitments: + type: array + items: + type: object + properties: + port_id: + type: string + description: channel port identifier. + channel_id: + type: string + description: channel unique identifier. + sequence: + type: string + format: uint64 + description: packet sequence. + data: + type: string + format: byte + description: embedded data that represents packet state. + description: >- + PacketState defines the generic type necessary to retrieve and store + + packet commitments, acknowledgements, and receipts. + + Caller is responsible for knowing the context necessary to interpret + this + + state as a commitment, acknowledgement, or a receipt. + pagination: + title: pagination response + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: |- + PageResponse is to be embedded in gRPC response messages where the + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + height: + title: query block height + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + title: |- + QueryPacketCommitmentsResponse is the request type for the + Query/QueryPacketCommitments RPC method + ibc.core.channel.v1.QueryPacketReceiptResponse: + type: object + properties: + received: + type: boolean + title: success flag for if receipt exists + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + title: >- + QueryPacketReceiptResponse defines the client query response for a packet + + receipt which also includes a proof, and the height from which the proof + was + + retrieved + ibc.core.channel.v1.QueryUnreceivedAcksResponse: + type: object + properties: + sequences: + type: array + items: + type: string + format: uint64 + title: list of unreceived acknowledgement sequences + height: + title: query block height + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + title: |- + QueryUnreceivedAcksResponse is the response type for the + Query/UnreceivedAcks RPC method + ibc.core.channel.v1.QueryUnreceivedPacketsResponse: + type: object + properties: + sequences: + type: array + items: + type: string + format: uint64 + title: list of unreceived packet sequences + height: + title: query block height + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + title: |- + QueryUnreceivedPacketsResponse is the response type for the + Query/UnreceivedPacketCommitments RPC method + ibc.core.channel.v1.State: + type: string + enum: + - STATE_UNINITIALIZED_UNSPECIFIED + - STATE_INIT + - STATE_TRYOPEN + - STATE_OPEN + - STATE_CLOSED + default: STATE_UNINITIALIZED_UNSPECIFIED + description: |- + State defines if a channel is in one of the following states: + CLOSED, INIT, TRYOPEN, OPEN, or UNINITIALIZED. + + - STATE_UNINITIALIZED_UNSPECIFIED: Default State + - STATE_INIT: A channel has just started the opening handshake. + - STATE_TRYOPEN: A channel has acknowledged the handshake step on the counterparty chain. + - STATE_OPEN: A channel has completed the handshake. Open channels are + ready to send and receive packets. + - STATE_CLOSED: A channel has been closed and can no longer be used to send or receive + packets. + ibc.core.channel.v2.PacketState: + type: object + properties: + client_id: + type: string + description: client unique identifier. + sequence: + type: string + format: uint64 + description: packet sequence. + data: + type: string + format: byte + description: embedded data that represents packet state. + description: |- + PacketState defines the generic type necessary to retrieve and store + packet commitments, acknowledgements, and receipts. + Caller is responsible for knowing the context necessary to interpret this + state as a commitment, acknowledgement, or a receipt. + ibc.core.channel.v2.QueryNextSequenceSendResponse: + type: object + properties: + next_sequence_send: + type: string + format: uint64 + title: next sequence send number + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + title: >- + QueryNextSequenceSendResponse is the response type for the + Query/QueryNextSequenceSend RPC method + ibc.core.channel.v2.QueryPacketAcknowledgementResponse: + type: object + properties: + acknowledgement: + type: string + format: byte + title: acknowledgement associated with the request fields + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + description: >- + QueryPacketAcknowledgementResponse is the response type for the + Query/PacketAcknowledgement RPC method. + ibc.core.channel.v2.QueryPacketAcknowledgementsResponse: + type: object + properties: + acknowledgements: + type: array + items: + type: object + properties: + client_id: + type: string + description: client unique identifier. + sequence: + type: string + format: uint64 + description: packet sequence. + data: + type: string + format: byte + description: embedded data that represents packet state. + description: >- + PacketState defines the generic type necessary to retrieve and store + + packet commitments, acknowledgements, and receipts. + + Caller is responsible for knowing the context necessary to interpret + this + + state as a commitment, acknowledgement, or a receipt. + pagination: + title: pagination response + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: |- + PageResponse is to be embedded in gRPC response messages where the + corresponding request message has used PageRequest. + + message SomeResponse { + repeated Bar results = 1; + PageResponse page = 2; + } + height: + title: query block height + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + title: |- + QueryPacketAcknowledgemetsResponse is the request type for the + Query/QueryPacketAcknowledgements RPC method + ibc.core.channel.v2.QueryPacketCommitmentResponse: + type: object + properties: + commitment: + type: string + format: byte + title: packet associated with the request fields + proof: + type: string + format: byte + title: merkle proof of existence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + description: >- + QueryPacketCommitmentResponse is the response type for the + Query/PacketCommitment RPC method. + ibc.core.channel.v2.QueryPacketCommitmentsResponse: + type: object + properties: + commitments: + type: array + items: + type: object + properties: + client_id: + type: string + description: client unique identifier. + sequence: + type: string + format: uint64 + description: packet sequence. + data: + type: string + format: byte + description: embedded data that represents packet state. + description: >- + PacketState defines the generic type necessary to retrieve and store + + packet commitments, acknowledgements, and receipts. + + Caller is responsible for knowing the context necessary to interpret + this + + state as a commitment, acknowledgement, or a receipt. + description: collection of packet commitments for the requested channel identifier. + pagination: + description: pagination response. + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + height: + description: query block height. + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + title: >- + Height is a monotonically increasing data type + + that can be compared against another Height for the purposes of + updating and + + freezing clients + description: >- + QueryPacketCommitmentResponse is the response type for the + Query/PacketCommitment RPC method. + ibc.core.channel.v2.QueryPacketReceiptResponse: + type: object + properties: + received: + type: boolean + title: success flag for if receipt exists + proof: + type: string + format: byte + title: merkle proof of existence or absence + proof_height: + title: height at which the proof was retrieved + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + description: >- + QueryPacketReceiptResponse is the response type for the + Query/PacketReceipt RPC method. + ibc.core.channel.v2.QueryUnreceivedAcksResponse: + type: object + properties: + sequences: + type: array + items: + type: string + format: uint64 + title: list of unreceived acknowledgement sequences + height: + title: query block height + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + title: |- + QueryUnreceivedAcksResponse is the response type for the + Query/UnreceivedAcks RPC method + ibc.core.channel.v2.QueryUnreceivedPacketsResponse: + type: object + properties: + sequences: + type: array + items: + type: string + format: uint64 + title: list of unreceived packet sequences + height: + title: query block height + type: object + properties: + revision_number: + type: string + format: uint64 + title: the revision that the client is currently on + revision_height: + type: string + format: uint64 + title: the height within the given revision + description: >- + Normally the RevisionHeight is incremented at each height while + keeping + + RevisionNumber the same. However some consensus algorithms may choose + to + + reset the height in certain conditions e.g. hard forks, state-machine + + breaking changes In these cases, the RevisionNumber is incremented so + that + + height continues to be monitonically increasing even as the + RevisionHeight + + gets reset + + + Please note that json tags for generated Go code are overridden to + explicitly exclude the omitempty jsontag. + + This enforces the Go json marshaller to always emit zero values for + both revision_number and revision_height. + title: >- + QueryUnreceivedPacketsResponse is the response type for the + Query/UnreceivedPacketCommitments RPC method + ibc.lightclients.wasm.v1.QueryChecksumsResponse: + type: object + properties: + checksums: + type: array + items: + type: string + description: >- + checksums is a list of the hex encoded checksums of all wasm codes + stored. + pagination: + description: pagination defines the pagination in the response. + type: object + properties: + next_key: + type: string + format: byte + description: |- + next_key is the key to be passed to PageRequest.key to + query the next page most efficiently. It will be empty if + there are no more results. + total: + type: string + format: uint64 + title: >- + total is total number of results available if + PageRequest.count_total + + was set, its value is undefined otherwise + description: >- + QueryChecksumsResponse is the response type for the Query/Checksums RPC + method. + ibc.lightclients.wasm.v1.QueryCodeResponse: + type: object + properties: + data: + type: string + format: byte + description: QueryCodeResponse is the response type for the Query/Code RPC method. diff --git a/docs/dev/development-setup.md b/docs/dev/development-setup.md new file mode 100644 index 0000000..7d26484 --- /dev/null +++ b/docs/dev/development-setup.md @@ -0,0 +1,60 @@ +# Development setup + +## Dependencies + +We use [Go 1.16 Modules](https://go.dev/wiki/Modules) to manage dependency versions. + +The main branch of every Cosmos repository should just build with `go get`, which means they should be kept up-to-date with their dependencies, so we can get away with telling people they can just `go get` our software. + +Since some dependencies are not under our control, a third party may break our build, in which case we can fall back on `go mod tidy -v`. + +Other helpful commands: + +- `go get` to add a new go module (including if the existing go module is being semantic version bumped, i.e. my/module/v1 -> my/module/v2). +- `go get -u` to update an existing dependency. +- `go mod tidy` to update dependencies in `go.sum`. + +## Protobuf + +We use [Protocol Buffers](https://developers.google.com/protocol-buffers) along with [buf](https://docs.buf.build/introduction) and [gogoproto](https://github.com/gogo/protobuf) to generate code for use in ibc-go. + +For deterministic behavior around protobuf tooling, everything is containerized using Docker. Make sure to have Docker installed on your machine, or head to [Docker's website](https://docs.docker.com/get-docker/) to install it. + +For formatting code in `.proto` files, you can run the `make proto-format` command. + +For linting and checking breaking changes, we also use [buf](https://buf.build/). You can use the commands `make proto-lint` and `make proto-check-breaking` to respectively lint your proto files and check for breaking changes. + +To generate the protobuf stubs, you can run `make proto-gen`. + +We also added the `make proto-all` command to run the above commands (`proto-format`, `proto-lint` and `proto-gen`) sequentially. + +To update third-party protobuf dependencies, you can run `make proto-update-deps`. This requires `buf` to be installed in the local development environment (see [`buf`s installation documentation](https://docs.buf.build/installation) for more details). + +For generating or updating the swagger file that documents the URLs of the RESTful API that exposes the gRPC endpoints over HTTP, you can run the `proto-swagger-gen` command. + +It reads protobuf service definitions and generates a reverse-proxy server which translates a RESTful HTTP API into gRPC. + +## Developing and testing + +- The latest state of development is on `main`. +- Build the `simd` test chain binary with `make build`. +- `main` must never fail `make test`. +- No `--force` onto `main` (except when reverting a broken commit, which should seldom happen). +- Create a development branch either on `github.com/cosmos/ibc-go`, or your fork (using `git remote add fork`). +- Before submitting a pull request, begin `git rebase` on top of `main`. +- Ensure you are using the pre-commit hooks by running `make setup-pre-commit`. + +All Go tests in ibc-go can be ran by running `make test`. + +Please make sure to run `make format` before every commit - the easiest way to do this is have your editor run it for you upon saving a file. Additionally please ensure that your code is lint compliant by running `make lint-fix` (requires `golangci-lint`). + +When testing a function under a variety of different inputs, we prefer to use [table driven tests](https://github.com/golang/go/wiki/TableDrivenTests). + +All unit tests should use the testing package. Please see the testing package [README](../../testing/README.md) for more information. + +## Documentation + +- If you open a PR on ibc-go, it is mandatory to update the relevant documentation in `/docs`. +- We lint the markdown files for documentation with [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli). Please run `make docs-lint` before pushing changes in the markdown files (you will need to have `markdownlint-cli` installed, so please follow the [installation instructions](https://github.com/igorshubovych/markdownlint-cli#installation)). +- Generate the folder `docs/.vuepress/dist` with all the static files for the documentation site with `make build-docs`. +- Run the documentation site locally with `make view-docs`. diff --git a/docs/dev/go-style-guide.md b/docs/dev/go-style-guide.md new file mode 100644 index 0000000..3605eb2 --- /dev/null +++ b/docs/dev/go-style-guide.md @@ -0,0 +1,119 @@ + +# Go style guide + +In order to keep our code looking good with lots of programmers working on it, it helps to have a "style guide", so all the code generally looks quite similar. This doesn't mean there is only one "right way" to write code, or even that this standard is better than your style. But if we agree to a number of stylistic practices, it makes it much easier to read and modify new code. Please feel free to make suggestions if there's something you would like to add or modify. + +We expect all contributors to be familiar with [Effective Go](https://golang.org/doc/effective_go.html) (and it's recommended reading for all Go programmers anyways). Additionally, we generally agree with the suggestions in [Uber's style guide](https://github.com/uber-go/guide/blob/master/style.md) and use that as a starting point. + +## Code Structure + +Perhaps more key for code readability than good commenting is having the right structure. As a rule of thumb, try to write in a logical order of importance, taking a little time to think how to order and divide the code such that someone could scroll down and understand the functionality of it just as well as you do. A loose example of such order would be: + +- Constants, global and package-level variables. +- Main struct definition. +- Options (only if they are seen as critical to the struct else they should be placed in another file). +- Initialization/start and stop of the service functions. +- Public functions (in order of most important). +- Private/helper functions. +- Auxiliary structs and function (can also be above private functions or in a separate file). + +## General + +- Use `gofumpt` to format all code upon saving it (or run `make format`). +- Think about documentation, and try to leave godoc comments, when it will help new developers. +- Every package should have a high level doc.go file to describe the purpose of that package, its main functions, and any other relevant information. +- Applications (e.g. clis/servers) should panic on unexpected unrecoverable errors and print a stack trace. + +## Comments + +- Use a space after the comment deliminter (ex. `// your comment`). +- Many comments are not sentences. These should begin with a lower case letter and end without a period. +- Conversely, sentences in comments should be sentenced-cased and end with a period. +- Comments should explain *why* something is being done rather than *what* the code is doing. For example: + + The comments in + +```go +// assign a variable foo +f := foo +// assign f to b +b := f +``` + + have little value, but the following is more useful: + +```go +f := foo +// we copy the variable f because we want to preserve the state at time of initialization +b := f +``` + +## Linting + +- Run `make lint-fix` to fix any linting errors. + +## Various + +- Functions that return functions should have the suffix `Fn`. +- Names should not [stutter](https://blog.golang.org/package-names). For example, a struct generally shouldn’t have a field named after itself; e.g., this shouldn't occur: + +```go +type middleware struct { + middleware Middleware +} +``` + +- Acronyms are all capitalized, like "RPC", "gRPC", "API". "MyID", rather than "MyId". +- Whenever it is safe to use Go's built-in `error` instantiation functions (as opposed to Cosmos SDK's error instantiation functions), prefer `errors.New()` instead of `fmt.Errorf()` unless you're actually using the format feature with arguments. + +## Importing libraries + +- Use [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports). +- Separate imports into blocks. For example: + +```go +import ( + // standard library imports + "fmt" + "testing" + + // external library imports + "github.com/stretchr/testify/require" + + // Cosmos-SDK imports + abci "github.com/cometbft/cometbft/abci/types" + + // ibc-go library imports + "github.com/cosmos/ibc-go/modules/core/23-commitment/types" +) +``` + +Run `make lint-fix` to get the imports ordered and grouped automatically. + +## Dependencies + +- Dependencies should be pinned by a release tag, or specific commit, to avoid breaking `go get` when external dependencies are updated. +- Refer to the [contributing](./development-setup.md#dependencies) document for more details. + +## Testing + +- Make use of table driven testing where possible and not-cumbersome. Read [this blog post](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go) for more information. See the [tests](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/transfer/keeper/msg_server_test.go#L11) for [`Transfer`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/transfer/keeper/msg_server.go#L15) for an example. +- Make use of Testify [assert](https://godoc.org/github.com/stretchr/testify/assert) and [require](https://godoc.org/github.com/stretchr/testify/require). +- When using mocks, it is recommended to use Testify [mock](https://pkg.go.dev/github.com/stretchr/testify/mock) along with [Mockery](https://github.com/vektra/mockery) for autogeneration. + +## Errors + +- Ensure that errors are concise, clear and traceable. +- Depending on the context, use either `cosmossdk.io/errors` or `stdlib` error packages. +- For wrapping errors, use `fmt.Errorf()` with `%w`. +- Panic is appropriate when an internal invariant of a system is broken, while all other cases (in particular, incorrect or invalid usage) should return errors. +- Error messages should be formatted as following: + +```go +sdkerrors.Wrapf( + , + "expected %s, got %s", + , + +) +``` diff --git a/docs/dev/project-structure.md b/docs/dev/project-structure.md new file mode 100644 index 0000000..df70521 --- /dev/null +++ b/docs/dev/project-structure.md @@ -0,0 +1,48 @@ +# Project structure + +If you're not familiar with the overall module structure from the SDK modules, please check this [document](https://docs.cosmos.network/main/build/building-modules/intro) as prerequisite reading. + +Every Interchain Standard (ICS) has been developed in its own package. The development team separated the IBC TAO (Transport, Authentication, Ordering) ICS specifications from the IBC application level specification. The following sections describe the architecture of the most relevant directories that comprise this repository. + +## `modules` + +This folder contains implementations for the IBC TAO (`core`), IBC applications (`apps`) and light clients (`light-clients`). + +### `core` + +- `02-client`: This package is an implementation for Cosmos SDK-based chains of [ICS 02](https://github.com/cosmos/ibc/tree/main/spec/core/ics-002-client-semantics). This implementation defines the types and methods needed to operate light clients tracking other chain's consensus state. +- `03-connection`: This package is an implementation for Cosmos SDK-based chains of [ICS 03](https://github.com/cosmos/ibc/tree/main/spec/core/ics-003-connection-semantics). This implementation defines the types and methods necessary to perform connection handshake between two chains. +- `04-channel`: This package is an implementation for Cosmos SDK-based chains of [ICS 04](https://github.com/cosmos/ibc/tree/main/spec/core/ics-004-channel-and-packet-semantics). This implementation defines the types and methods necessary to perform channel handshake between two chains and ensure correct packet sending flow. +- `05-port`: This package is an implementation for Cosmos SDK-based chains of [ICS 05](https://github.com/cosmos/ibc/tree/main/spec/core/ics-005-port-allocation). This implements the port allocation system by which modules can bind to uniquely named ports. +- `23-commitment`: This package is an implementation for Cosmos SDK-based chains of [ICS 23](https://github.com/cosmos/ibc/tree/main/spec/core/ics-023-vector-commitments). This implementation defines the functions required to prove inclusion or non-inclusion of particular values at particular paths in state. +- `24-host`: This package is an implementation for Cosmos SDK-based chains of [ICS 24](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements). + +### `apps` + +- `transfer`: This is the Cosmos SDK implementation of the [ICS 20](https://github.com/cosmos/ibc/tree/main/spec/app/ics-020-fungible-token-transfer) protocol, which enables cross-chain fungible token transfers. For more information, read the [module's docs](../docs/02-apps/01-transfer/01-overview.md) +- `27-interchain-accounts`: This is the Cosmos SDK implementation of the [ICS 27](https://github.com/cosmos/ibc/tree/main/spec/app/ics-027-interchain-accounts) protocol, which enables cross-chain account management built upon IBC. For more information, read the [module's documentation](../docs/02-apps/02-interchain-accounts/01-overview.md). +- `callbacks`: This is an implementation of [ADR 008](../architecture/adr-008-app-caller-cbs.md) that allows for secondary applications (e.g. smart contracts, modules) to call into IBC apps as part of their state machine logic and then do some actions on packet lifecycle events. For more information, read the [module's documentation](../docs/04-middleware/01-callbacks/01-overview.md). + +### `light-clients` + +- `06-solomachine`: This package implements the types for the Solo Machine light client specified in [ICS 06](https://github.com/cosmos/ibc/tree/main/spec/client/ics-006-solo-machine-client). +- `07-tendermint`: This package implements the types for the Tendermint consensus light client as specified in [ICS 07](https://github.com/cosmos/ibc/tree/main/spec/client/ics-007-tendermint-client). +- `08-wasm`: This package implements a proxy light client module that routes requests to the actual light clients uploaded as Wasm byte code, as specified in [ICS 08](https://github.com/cosmos/ibc/tree/main/spec/client/ics-008-wasm-client). +- `09-localhost`: This package implements a localhost loopback client with the ability to send and receive IBC packets to and from the same state machine, as specified in [ICS 09](https://github.com/cosmos/ibc/tree/main/spec/client/ics-009-loopback-cilent). + +## `proto` + +This folder contains all the Protobuf files used for + +- common message type definitions, +- message type definitions related to genesis state, +- `Query` service and related message type definitions, +- `Msg` service and related message type definitions. + +## `testing` + +This package contains the implementation of the testing package used in unit and integration tests. Please read the [package's documentation](../../testing/README.md) for more information. + +## `e2e` + +This folder contains all the e2e tests of ibc-go. Please read the [module's documentation](../../e2e/README.md) for more information. diff --git a/docs/dev/pull-requests.md b/docs/dev/pull-requests.md new file mode 100644 index 0000000..d544b1c --- /dev/null +++ b/docs/dev/pull-requests.md @@ -0,0 +1,68 @@ +# Pull request guidelines + +> To accommodate the review process we suggest that PRs are categorically broken up. Ideally each PR addresses only a single issue and does not introduce unrelated changes. Additionally, as much as possible code refactoring and cleanup should be submitted as separate PRs from bug fixes and feature additions. + +If the PR is the result of a related GitHub issue, please include `closes: #` in the PR’s description in order to auto-close the related issue once the PR is merged. This will also link the issue and the PR together so that if anyone looks at either in the future, they won’t have any problem trying to find the corresponding issue/PR as it will be recorded in the sidebar. + +If the PR is not the result of an existing issue and it fixes a bug, please provide a detailed description of the bug. For feature additions, we recommend opening an issue first and have it discussed and agreed upon, before working on it and opening a PR. + +If possible, [tick the "Allow edits from maintainers" box](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork) when opening your PR from your fork of ibc-go. This allows us to directly make minor edits / refactors and speeds up the merging process. + +If you open a PR on ibc-go, it is mandatory to update the relevant documentation in `/docs`. + +## Pull request targeting + +Ensure that you base and target your PR on the either the `main` branch or the corresponding feature branch where a large body of work is being implemented. Please make sure that the PR is made from a branch different than either `main` or the corresponding feature branch. + +All development should be then targeted against `main` or the feature branch. Bug fixes which are required for outstanding releases should be backported if the CODEOWNERS decide it is applicable. + +## Commit Messages + +Commit messages should follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/). + +When opening a PR, include the proposed commit message in the PR description. + +The commit message type should be one of: + +- `feat` / `feature` for feature work. +- `bug` / `fix` for bug fixes. +- `imp` / `improvements` for improvements. +- `doc` / `docs` / `documentation` for any documentation changes. +- `test` / `e2e` for addition or improvements of unit, integration and e2e tests or their corresponding infrastructure. +- `deprecated` for deprecation changes. +- `deps` / `build` for changes to dependencies. +- `chore` / `misc` / `nit` for any miscellaneous changes that don't fit into another category. + +**Note**: If any change is breaking, the following format must be used: + +- `type` + `(api)!` for api breaking changes, e.g. `fix(api)!: api breaking fix` +- `type` + `(statemachine)!` for state machine breaking changes, e.g. `fix(statemachine)!: state machine breaking fix` + +**`api` breaking changes take precedence over `statemachine` breaking changes.** + +## Pull request review process + +All PRs require an approval from at least one CODEOWNER before merge. PRs which cause significant changes require two approvals from CODEOWNERS. When reviewing PRs please use the following review guidelines: + +- `Approval` through the GitHub UI with the following comments: + - `Concept ACK` means that you agree with the overall proposed concept, but have neither reviewed the code nor tested it. + - `LGTM` means the above and besides you have superficially reviewed the code without considering how logic affects other parts the codebase. + - `utACK` (aka. `Untested ACK`) means the above and besides have thoroughly reviewed the code and considered the safety of logic changes, but have not tested it. + - `Tested ACK` means the above and besides you have tested the code. +- If you are only making "surface level" reviews, submit any notes as `Comments` without submitting an approval. + +A thorough review means that: + +- You understand the code and make sure that documentation is updated in the right places. +- You must also think through anything which ought to be included but is not. +- You must think through whether any added code could be partially combined (DRYed) with existing code. +- You must think through any potential security issues or incentive-compatibility flaws introduced by the changes. +- Naming must be consistent with conventions and the rest of the codebase. +- Code must live in a reasonable location, considering dependency structures (e.g. not importing testing modules in production code, or including example code modules in production code). + +## Pull request merge procedure + +- Ensure pull request branch is rebased on target branch. +- Ensure all GitHub requirements pass. +- Set the changelog entry in the commit message for the pull request. +- Squash and merge pull request. diff --git a/docs/dev/release-management.md b/docs/dev/release-management.md new file mode 100644 index 0000000..5048cef --- /dev/null +++ b/docs/dev/release-management.md @@ -0,0 +1,86 @@ +# Tagging a release + +Before tagging a new release, please run the [compatibility e2e test suite](https://github.com/cosmos/ibc-go/actions/workflows/e2e-compatibility.yaml) for the corresponding release line. + +## New major release branch + +Pre-requisites for creating a release branch for a new major version: + +1. Bump [Go package version](https://github.com/cosmos/ibc-go/blob/main/go.mod#L3). +2. Change all imports. For example: if the next major version is `v3`, then change all imports starting with `github.com/cosmos/ibc-go/v2` to `github.com/cosmos/ibc-go/v3`). + +Once the above pre-requisites are satisfied: + +1. Start on `main`. +2. Create the release branch (`release/vX.XX.X`). For example: `release/v3.0.x`. + +## New minor release branch + +1. Start on the latest release branch in the same major release line. For example: the latest release branch in the `v3` release line is `v3.2.x`. +2. Create branch from the release branch. For example: create branch `release/v3.3.x` from `v3.2.x`. + +Post-requisites for both new major and minor release branches: + +1. Add branch protection rules to new release branch. +2. Add backport task to [`mergify.yml`](https://github.com/cosmos/ibc-go/blob/main/.github/mergify.yml). +3. Create label for backport (e.g.`backport-to-v3.0.x`). + +## Point release procedure + +In order to alleviate the burden for a single person to have to cherry-pick and handle merge conflicts of all desired backporting PRs to a point release, we instead maintain a living backport branch, where all desired features and bug fixes are merged into as separate PRs. + +### Example + +Current release is `v1.0.2`. We then maintain a (living) branch `release/v1.0.x`, given `x` as the next patch release number (currently `v1.0.3`) for the `v1.0` release series. As bugs are fixed and PRs are merged into `main`, if a contributor wishes the PR to be released into the `v1.0.x` point release, the contributor must: + +1. Add the `backport-to-v1.0.x` label to the PR. +2. Once the PR is merged, the Mergify GitHub application will automatically copy the changes into another branch and open a new PR against the desired `release/v1.0.x` branch. +3. If the following has not been discussed in the original PR, then update the backport PR's description and ensure it contains the following information: + +- **[Impact]** explanation of how the bug affects users or developers. +- **[Test Case]** section with detailed instructions on how to reproduce the bug. +- **[Regression Potential]** section with a discussion how regressions are most likely to manifest, or might manifest even if it's unlikely, as a result of the change. **It is assumed that any backport PR is well-tested before it is merged in and has an overall low risk of regression**. This section should discuss the potential for state breaking changes to occur such as through out-of-gas errors. + +It is the PR's author's responsibility to fix merge conflicts, update changelog entries, and ensure CI passes. If a PR originates from an external contributor, it may be a core team member's responsibility to perform this process instead of the original author. Lastly, it is core team's responsibility to ensure that the PR meets all the backport criteria. + +Finally, when a point release is ready to be made: + +1. Checkout the release branch (e.g. `release/v1.0.x`). +2. In `CHANGELOG.md`: + +- Ensure changelog entries are verified. +- Remove any sections of the changelog that do not have any entries (e.g. if the release does not have any bug fixes, then remove the section). +- Remove the `[Unreleased]` title. +- Add release version and date of release. + +3. Create release in GitHub: + +- Select the correct target branch (e.g. `release/v1.0.x`). +- Choose a tag (e.g. `v1.0.3`). +- Write release notes. +- Check the `This is a pre-release` checkbox if needed (this applies for alpha, beta and release candidates). + +### Post-release procedure + +- Update [`CHANGELOG.md`](../../CHANGELOG.md) in `main` (remove from the `[Unreleased]` section any items that are part of the release).` +- Put back the `[Unreleased]` section in the release branch (e.g. `release/v1.0.x`) with clean sections for each of the types of changelog entries, so that entries will be added for the PRs that are backported for the next release. +- Update [version matrix](../../RELEASES.md#version-matrix) in `RELEASES.md`: add the new release and remove any tags that might not be recommended anymore. + +Additionally, for the first point release of a new major or minor release branch: + +- Update the table of supported release lines (and End of Life dates) in [`RELEASES.md`](../../RELEASES.md): add the new release line and remove any release lines that might have become discontinued. +- Update the [list of supported release lines in README.md](../../RELEASES.md#releases), if necessary. +- Update the manual [e2e `simd`](https://github.com/cosmos/ibc-go/blob/main/.github/workflows/e2e-manual-simd.yaml) test workflow: + - Remove any tags that might not be recommended anymore. +- Update docs site: + - If the release is occurring on the main branch, on the latest version, then run `npm run docusaurus docs:version vX.Y.Z` in the `docs/` directory. (where `X.Y.Z` is the new version number) + - If the release is occurring on an older release branch, then make a PR to the main branch called `docs: new release vX.Y.Z` doing the following: + - Update the content of the docs found in `docs/versioned_docs/version-vx.y.z` if needed. (where `x.y.z` is the previous version number) + - Update the version number of the older release branch by changing the version number of the older release branch in: + - In `docs/versions.json`. + - Rename `docs/versioned_sidebars/version-vx.y.z-sidebars.json` + - Rename `docs/versioned_docs/version-vx.y.z` +- After changes to docs site are deployed, check [ibc.cosmos.network](https://ibc.cosmos.network) is updated. +- Open issue in [SDK tutorials repo](https://github.com/cosmos/sdk-tutorials) to update tutorials to the released version of ibc-go. + +See [this PR](https://github.com/cosmos/ibc-go/pull/2919) for an example of the involved changes. diff --git a/docs/docs/00-intro.md b/docs/docs/00-intro.md new file mode 100644 index 0000000..ae4706d --- /dev/null +++ b/docs/docs/00-intro.md @@ -0,0 +1,42 @@ +--- +slug: / +sidebar_position: 0 +--- + +# IBC-Go Documentation + +Welcome to the documentation for IBC-Go, the Golang implementation of the Inter-Blockchain Communication Protocol! + +The Inter-Blockchain Communication Protocol (IBC) is a protocol that allows blockchains to talk to each other. Chains that speak IBC can share any type of data as long as it's encoded in bytes, enabling the industry’s most feature-rich cross-chain interactions. IBC can be used to build a wide range of cross-chain applications that include token transfers, atomic swaps, multi-chain smart contracts (with or without mutually comprehensible VMs), and cross-chain account control. IBC is secure and permissionless. + +The protocol realizes this interoperability by specifying a set of data structures, abstractions, and semantics that can be implemented by any distributed ledger that satisfies a small set of requirements. + +:::note Notice +Since ibc-go v10, there are two versions of the protocol in the same release: IBC classic and IBC v2. The protocols are seperate - a connection uses either IBC classic or IBC v2 +::: + +## High-level overview of IBC v2 + +For a high level overview of IBC v2, please refer to [this blog post.](https://ibcprotocol.dev/blog/ibc-v2-announcement) For a more detailed understanding of the IBC v2 protocol, please refer to the [IBC v2 protocol specification.](https://github.com/cosmos/ibc/tree/main/spec/IBC_V2) + +If you are interested in using the cannonical deployment of IBC v2, connecting Cosmos chains and Ethereum, take a look at the [IBC Eureka](https://docs.skip.build/go/eureka/eureka-overview) documentation to get started. + +## High-level overview of IBC Classic + +The following diagram shows how IBC works at a high level: + +![Light Mode IBC Overview](./images/ibcoverview-light.svg#gh-light-mode-only)![Dark Mode IBC Overview](./images/ibcoverview-dark.svg#gh-dark-mode-only) + +The transport layer (TAO) provides the necessary infrastructure to establish secure connections and authenticate data packets between chains. The application layer builds on top of the transport layer and defines exactly how data packets should be packaged and interpreted by the sending and receiving chains. + +IBC provides a reliable, permissionless, and generic base layer (allowing for the secure relaying of data packets), while allowing for composability and modularity with separation of concerns by moving application designs (interpreting and acting upon the packet data) to a higher-level layer. This separation is reflected in the categories: + +- **IBC/TAO** comprises the Transport, Authentication, and Ordering of packets, i.e. the infrastructure layer. +- **IBC/APP** consists of the application handlers for the data packets being passed over the transport layer. These include but are not limited to fungible token transfers (ICS-20), NFT transfers (ICS-721), and interchain accounts (ICS-27). +- **Application module:** groups any application, middleware or smart contract that may wrap downstream application handlers to provide enhanced functionality. + +Note three crucial elements in the diagram: + +- The chains depend on relayers to communicate. [Relayers](https://github.com/cosmos/ibc/blob/main/spec/relayer/ics-018-relayer-algorithms/README.md) are the "physical" connection layer of IBC: off-chain processes responsible for relaying data between two chains running the IBC protocol by scanning the state of each chain, constructing appropriate datagrams, and executing them on the opposite chain as is allowed by the protocol. +- Many relayers can serve one or more channels to send messages between the chains. +- Each side of the connection uses the light client of the other chain to quickly verify incoming messages. diff --git a/docs/docs/01-ibc/01-overview.md b/docs/docs/01-ibc/01-overview.md new file mode 100644 index 0000000..2f3608a --- /dev/null +++ b/docs/docs/01-ibc/01-overview.md @@ -0,0 +1,293 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /ibc/overview +--- + +# Overview + +:::note Synopsis +Learn about IBC, its components, and its use cases. +::: + +## What is the Inter-Blockchain Communication Protocol (IBC)? + +This document serves as a guide for developers who want to write their own Inter-Blockchain +Communication Protocol (IBC) applications for custom use cases. + +> IBC applications must be written as self-contained modules. + +Due to the modular design of the IBC Protocol, IBC +application developers do not need to be concerned with the low-level details of clients, +connections, and proof verification. + +This brief explanation of the lower levels of the +stack gives application developers a broad understanding of the IBC +Protocol. Abstraction layer details for channels and ports are most relevant for application developers and describe how to define custom packets and `IBCModule` callbacks. + +The requirements to have your module interact over IBC are: + +- Bind to a port or ports. +- Define your packet data. +- Use the default acknowledgment struct provided by core IBC or optionally define a custom acknowledgment struct. +- Standardize an encoding of the packet data. +- Implement the `IBCModule` interface. +- Implement the `UpgradableModule` interface (optional). + +Read on for a detailed explanation of how to write a self-contained IBC application module. + +## Components overview + +### [Clients](https://github.com/cosmos/ibc-go/blob/main/modules/core/02-client) + +IBC clients are on-chain light clients. Each light client is identified by a unique client ID. +IBC clients track the consensus states of other blockchains, along with the proof spec necessary to +properly verify proofs against the client's consensus state. A client can be associated with any number +of connections to the counterparty chain. The client identifier is auto generated using the client type +and the global client counter appended in the format: `{client-type}-{N}`. + +A `ClientState` should contain chain specific and light client specific information necessary for verifying updates +and upgrades to the IBC client. The `ClientState` may contain information such as chain ID, latest height, proof specs, +unbonding periods or the status of the light client. The `ClientState` should not contain information that +is specific to a given block at a certain height, this is the function of the `ConsensusState`. Each `ConsensusState` +should be associated with a unique block and should be referenced using a height. IBC clients are given a +client identifier prefixed store to store their associated client state and consensus states along with +any metadata associated with the consensus states. Consensus states are stored using their associated height. + +The supported IBC clients are: + +- [Solo Machine light client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/06-solomachine): Devices such as phones, browsers, or laptops. +- [Tendermint light client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/07-tendermint): The default for Cosmos SDK-based chains. +- [Wasm client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/08-wasm): Proxy client useful for running light clients written in a Wasm-compilable language. +- [Localhost (loopback) client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/09-localhost): Useful for testing, simulation, and relaying packets to modules on the same application. + +### IBC client heights + +IBC Client Heights are represented by the struct: + +```go +type Height struct { + RevisionNumber uint64 + RevisionHeight uint64 +} +``` + +The `RevisionNumber` represents the revision of the chain that the height is representing. +A revision typically represents a continuous, monotonically increasing range of block-heights. +The `RevisionHeight` represents the height of the chain within the given revision. + +On any reset of the `RevisionHeight`—for example, when hard-forking a Tendermint chain, +the `RevisionNumber` will get incremented. This allows IBC clients to distinguish between a +block height `n` of a previous revision of the chain (at revision `p`) and block-height `n` of the current +revision of the chain (at revision `e`). + +`Height`s that share the same revision number can be compared by simply comparing their respective `RevisionHeight`s. +`Height`s that do not share the same revision number will only be compared using their respective `RevisionNumber`s. +Thus a height `h` with revision number `e+1` will always be greater than a height `g` with revision number `e`, +**REGARDLESS** of the difference in revision heights. + +For example: + +```go +Height{RevisionNumber: 3, RevisionHeight: 0} > Height{RevisionNumber: 2, RevisionHeight: 100000000000} +``` + +When a Tendermint chain is running a particular revision, relayers can simply submit headers and proofs with the revision number +given by the chain's `chainID`, and the revision height given by the Tendermint block height. When a chain updates using a hard-fork +and resets its block-height, it is responsible for updating its `chainID` to increment the revision number. +IBC Tendermint clients then verifies the revision number against their `chainID` and treat the `RevisionHeight` as the Tendermint block-height. + +Tendermint chains wishing to use revisions to maintain persistent IBC connections even across height-resetting upgrades must format their `chainID`s +in the following manner: `{chainID}-{revision_number}`. On any height-resetting upgrade, the `chainID` **MUST** be updated with a higher revision number +than the previous value. + +For example: + +- Before upgrade `chainID`: `gaiamainnet-3` +- After upgrade `chainID`: `gaiamainnet-4` + +Clients that do not require revisions, such as the `06-solomachine` client, can simply hardcode `0` into the revision number whenever they +need to return an IBC height when implementing IBC interfaces and use the `RevisionHeight` exclusively. + +Other client types can implement their own logic to verify the IBC heights that relayers provide in their `Update`, `Misbehavior`, and +`Verify` functions respectively. + +The IBC interfaces expect an `ibcexported.Height` interface, however all clients must use the concrete implementation provided in +`02-client/types` and reproduced above. + +### [Connections](https://github.com/cosmos/ibc-go/blob/main/modules/core/03-connection) + +Connections encapsulate two [`ConnectionEnd`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/connection/v1/connection.proto#L17) +objects on two separate blockchains. Each `ConnectionEnd` is associated with a client of the +other blockchain (for example, the counterparty blockchain). The connection handshake is responsible +for verifying that the light clients on each chain are correct for their respective counterparties. +Connections, once established, are responsible for facilitating all cross-chain verifications of IBC state. +A connection can be associated with any number of channels. + +The connection handshake is a 4-step handshake. Briefly, if a given chain A wants to open a connection with +chain B using already established light clients on both chains: + +1. chain A sends a `ConnectionOpenInit` message to signal a connection initialization attempt with chain B. +2. chain B sends a `ConnectionOpenTry` message to try opening the connection on chain A. +3. chain A sends a `ConnectionOpenAck` message to mark its connection end state as open. +4. chain B sends a `ConnectionOpenConfirm` message to mark its connection end state as open. + +#### Time delayed connections + +Connections can be opened with a time delay by setting the `delay_period` field (in nanoseconds) in the [`MsgConnectionOpenInit`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/connection/v1/tx.proto#L45). +The time delay is used to require that the underlying light clients have been updated to a certain height before commitment verification can be performed. + +`delayPeriod` is used in conjunction with the [`max_expected_time_per_block`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/connection/v1/connection.proto#L113) parameter of the connection submodule to determine the `blockDelay`, which is number of blocks that the connection must be delayed by. + +When commitment verification is performed, the connection submodule will pass `delayPeriod` and `blockDelay` to the light client. It is up to the light client to determine whether the light client has been updated to the required height. Only the following light clients in `ibc-go` support time delayed connections: + +- `07-tendermint` +- `08-wasm` (passed to the contact) + +### [Proofs](https://github.com/cosmos/ibc-go/blob/main/modules/core/23-commitment) and [paths](https://github.com/cosmos/ibc-go/blob/main/modules/core/24-host) + +In IBC, blockchains do not directly pass messages to each other over the network. Instead, to +communicate, a blockchain commits some state to a specifically defined path that is reserved for a +specific message type and a specific counterparty. For example, for storing a specific connectionEnd as part +of a handshake or a packet intended to be relayed to a module on the counterparty chain. A relayer +process monitors for updates to these paths and relays messages by submitting the data stored +under the path and a proof to the counterparty chain. + +Proofs are passed from core IBC to light clients as bytes. It is up to light client implementations to interpret these bytes appropriately. + +- The paths that all IBC implementations must use for committing IBC messages is defined in +[ICS-24 Host State Machine Requirements](https://github.com/cosmos/ibc/tree/master/spec/core/ics-024-host-requirements). +- The proof format that all implementations must be able to produce and verify is defined in [ICS-23 Proofs](https://github.com/cosmos/ics23) implementation. + +### [Ports](https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port) + +An IBC module can bind to any number of ports. Each port must be identified by a unique `portID`. +Since IBC is designed to be secure with mutually distrusted modules operating on the same ledger, +binding a port returns a dynamic object capability. In order to take action on a particular port +(for example, an open channel with its port ID), a module must provide the dynamic object capability to the IBC +handler. This requirement prevents a malicious module from opening channels with ports it does not own. Thus, +IBC modules are responsible for claiming the capability that is returned on `BindPort`. + +### [Channels](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +An IBC channel can be established between two IBC ports. Currently, a port is exclusively owned by a +single module. IBC packets are sent over channels. Just as IP packets contain the destination IP +address and IP port, and the source IP address and source IP port, IBC packets contain +the destination port ID and channel ID, and the source port ID and channel ID. This packet structure enables IBC to +correctly route packets to the destination module while allowing modules receiving packets to +know the sender module. + +A channel can be `ORDERED`, where packets from a sending module must be processed by the +receiving module in the order they were sent. Or a channel can be `UNORDERED`, where packets +from a sending module are processed in the order they arrive (might be in a different order than they were sent). + +Modules can choose which channels they wish to communicate over with, thus IBC expects modules to +implement callbacks that are called during the channel handshake. These callbacks can do custom +channel initialization logic. If any callback returns an error, the channel handshake fails. Thus, by +returning errors on callbacks, modules can programmatically reject and accept channels. + +The channel handshake is a 4-step handshake. Briefly, if a given chain A wants to open a channel with +chain B using an already established connection: + +1. chain A sends a `ChanOpenInit` message to signal a channel initialization attempt with chain B. +2. chain B sends a `ChanOpenTry` message to try opening the channel on chain A. +3. chain A sends a `ChanOpenAck` message to mark its channel end status as open. +4. chain B sends a `ChanOpenConfirm` message to mark its channel end status as open. + +If all handshake steps are successful, the channel is opened on both sides. At each step in the handshake, the module +associated with the `ChannelEnd` executes its callback. So +on `ChanOpenInit`, the module on chain A executes its callback `OnChanOpenInit`. + +The channel identifier is auto derived in the format: `channel-{N}` where `N` is the next sequence to be used. + +#### Closing channels + +Closing a channel occurs in 2 handshake steps as defined in [ICS 04](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics). +Once a channel is closed, it cannot be reopened. The channel handshake steps are: + +**`ChanCloseInit`** closes a channel on the executing chain if + +- the channel exists and it is not already closed, +- the connection it exists upon is `OPEN`, +- the [IBC module callback `OnChanCloseInit`](./03-apps/02-ibcmodule.md#channel-closing-callbacks) returns `nil`. + +`ChanCloseInit` can be initiated by any user by submitting a `MsgChannelCloseInit` transaction. +Note that channels are automatically closed when a packet times out on an `ORDERED` channel. +A timeout on an `ORDERED` channel skips the `ChanCloseInit` step and immediately closes the channel. + +**`ChanCloseConfirm`** is a response to a counterparty channel executing `ChanCloseInit`. The channel +on the executing chain closes if + +- the channel exists and is not already closed, +- the connection the channel exists upon is `OPEN`, +- the executing chain successfully verifies that the counterparty channel has been closed +- the [IBC module callback `OnChanCloseConfirm`](./03-apps/02-ibcmodule.md#channel-closing-callbacks) returns `nil`. + +Currently, none of the IBC applications provided in ibc-go support `ChanCloseInit`. + +### [Packets](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +Modules communicate with each other by sending packets over IBC channels. All +IBC packets contain the destination `portID` and `channelID` along with the source `portID` and +`channelID`. This packet structure allows modules to know the sender module of a given packet. IBC packets +contain a sequence to optionally enforce ordering. + +IBC packets also contain a `TimeoutHeight` and a `TimeoutTimestamp` that determine the deadline before the receiving module must process a packet. + +Modules send custom application data to each other inside the `Data` `[]byte` field of the IBC packet. +Thus, packet data is opaque to IBC handlers. It is incumbent on a sender module to encode +their application-specific packet information into the `Data` field of packets. The receiver +module must decode that `Data` back to the original application data. + +### [Receipts and timeouts](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +Since IBC works over a distributed network and relies on potentially faulty relayers to relay messages between ledgers, +IBC must handle the case where a packet does not get sent to its destination in a timely manner or at all. Packets must +specify a non-zero value for timeout height (`TimeoutHeight`) or timeout timestamp (`TimeoutTimestamp` ) after which a packet can no longer be successfully received on the destination chain. + +- The `timeoutHeight` indicates a consensus height on the destination chain after which the packet is no longer to be processed, and instead counts as having timed-out. +- The `timeoutTimestamp` indicates a timestamp on the destination chain after which the packet is no longer to be processed, and instead counts as having timed-out. + +If the timeout passes without the packet being successfully received, the packet can no longer be +received on the destination chain. The sending module can timeout the packet and take appropriate actions. + +If the timeout is reached, then a proof of packet timeout can be submitted to the original chain. The original chain can then perform +application-specific logic to timeout the packet, perhaps by rolling back the packet send changes (refunding senders any locked funds, etc). + +- In `ORDERED` channels, a timeout of a single packet in the channel causes the channel to close. + + - If packet sequence `n` times out, then a packet at sequence `k > n` cannot be received without violating the contract of `ORDERED` channels that packets are processed in the order that they are sent. + - Since `ORDERED` channels enforce this invariant, a proof that sequence `n` has not been received on the destination chain by the specified timeout of packet `n` is sufficient to timeout packet `n` and close the channel. + +- In `UNORDERED` channels, the application-specific timeout logic for that packet is applied and the channel is not closed. + + - Packets can be received in any order. + - IBC writes a packet receipt for each sequence received in the `UNORDERED` channel. This receipt does not contain information; it is simply a marker intended to signify that the `UNORDERED` channel has received a packet at the specified sequence. + - To timeout a packet on an `UNORDERED` channel, a proof is required that a packet receipt **does not exist** for the packet's sequence by the specified timeout. + +For this reason, most modules should use `UNORDERED` channels as they require fewer liveness guarantees to function effectively for users of that channel. + +### [Acknowledgments](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +Modules can also choose to write application-specific acknowledgments upon processing a packet. Acknowledgments can be done: + +- Synchronously on `OnRecvPacket` if the module processes packets as soon as they are received from IBC module. +- Asynchronously if module processes packets at some later point after receiving the packet. + +This acknowledgment data is opaque to IBC much like the packet `Data` and is treated by IBC as a simple byte string `[]byte`. Receiver modules must encode their acknowledgment so that the sender module can decode it correctly. The encoding must be negotiated between the two parties during version negotiation in the channel handshake. + +The acknowledgment can encode whether the packet processing succeeded or failed, along with additional information that allows the sender module to take appropriate action. + +After the acknowledgment has been written by the receiving chain, a relayer relays the acknowledgment back to the original sender module. + +The original sender module then executes application-specific acknowledgment logic using the contents of the acknowledgment. + +- After an acknowledgement fails, packet-send changes can be rolled back (for example, refunding senders in ICS 20). +- After an acknowledgment is received successfully on the original sender on the chain, the corresponding packet commitment is deleted since it is no longer needed. + +## Further readings and specs + +If you want to learn more about IBC, check the following specifications: + +- [IBC specification overview](https://github.com/cosmos/ibc/blob/master/README.md) diff --git a/docs/docs/01-ibc/02-integration.md b/docs/docs/01-ibc/02-integration.md new file mode 100644 index 0000000..3e34f49 --- /dev/null +++ b/docs/docs/01-ibc/02-integration.md @@ -0,0 +1,359 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /ibc/integration +--- + +# Integration + +:::note Synopsis +Learn how to integrate IBC to your application +::: + +This document outlines the required steps to integrate and configure the [IBC +module](https://github.com/cosmos/ibc-go/tree/main/modules/core) to your Cosmos SDK application and enable sending fungible token transfers to other chains. An [example app using ibc-go v10 is linked](https://github.com/gjermundgaraba/probe/tree/ibc/v10). + +## Integrating the IBC module + +Integrating the IBC module to your SDK-based application is straightforward. The general changes can be summarized in the following steps: + +- [Define additional `Keeper` fields for the new modules on the `App` type](#add-application-fields-to-app). +- [Add the module's `StoreKey`s and initialize their `Keeper`s](#configure-the-keepers). +- [Create Application Stacks with Middleware](#create-application-stacks-with-middleware) +- [Set up IBC router and add route for the `transfer` module](#register-module-routes-in-the-ibc-router). +- [Grant permissions to `transfer`'s `ModuleAccount`](#module-account-permissions). +- [Add the modules to the module `Manager`](#module-manager-and-simulationmanager). +- [Update the module `SimulationManager` to enable simulations](#module-manager-and-simulationmanager). +- [Integrate light client modules (e.g. `07-tendermint`)](#integrating-light-clients). +- [Add modules to `Begin/EndBlockers` and `InitGenesis`](#application-abci-ordering). + +### Add application fields to `App` + +We need to register the core `ibc` and `transfer` `Keeper`s. To support the use of IBC v2, `transferv2` and `callbacksv2` must also be registered as follows: + +```go title="app.go" +import ( + // other imports + // ... + ibckeeper "github.com/cosmos/ibc-go/v10/modules/core/keeper" + ibctransferkeeper "github.com/cosmos/ibc-go/v10/modules/apps/transfer/keeper" + // ibc v2 imports + transferv2 "github.com/cosmos/ibc-go/v10/modules/apps/transfer/v2" + ibccallbacksv2 "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/v2" +) + +type App struct { + // baseapp, keys and subspaces definitions + + // other keepers + // ... + IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly + TransferKeeper ibctransferkeeper.Keeper // for cross-chain fungible token transfers + + // ... + // module and simulation manager definitions +} +``` + +### Configure the `Keeper`s + +Initialize the IBC `Keeper`s (for core `ibc` and `transfer` modules), and any additional modules you want to include. + +:::note Notice +The capability module has been removed in ibc-go v10, therefore the `ScopedKeeper` has also been removed +::: + +```go +import ( + // other imports + // ... + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibckeeper "github.com/cosmos/ibc-go/v10/modules/core/keeper" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer" + ibctransfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" +) + +func NewApp(...args) *App { + // define codecs and baseapp + + // ... other module keepers + + // Create IBC Keeper + app.IBCKeeper = ibckeeper.NewKeeper( + appCodec, + runtime.NewKVStoreService(keys[ibcexported.StoreKey]), + app.GetSubspace(ibcexported.ModuleName), + app.UpgradeKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // Create Transfer Keeper + app.TransferKeeper = ibctransferkeeper.NewKeeper( + appCodec, + runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]), + app.GetSubspace(ibctransfertypes.ModuleName), + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeper, + app.MsgServiceRouter(), + app.AccountKeeper, + app.BankKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // ... continues +} +``` + +### Create Application Stacks with Middleware + +Middleware stacks in IBC allow you to wrap an `IBCModule` with additional logic for packets and acknowledgements. This is a chain of handlers that execute in order. The transfer stack below shows how to wire up transfer to use packet forward middleware, and the callbacks middleware. Note that the order is important. + +```go +// Create Transfer Stack for IBC Classic +maxCallbackGas := uint64(10_000_000) +wasmStackIBCHandler := wasm.NewIBCHandler(app.WasmKeeper, app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper) + +var transferStack porttypes.IBCModule +transferStack = transfer.NewIBCModule(app.TransferKeeper) +// callbacks wraps the transfer stack as its base app, and uses PacketForwardKeeper as the ICS4Wrapper +// i.e. packet-forward-middleware is higher on the stack and sits between callbacks and the ibc channel keeper +// Since this is the lowest level middleware of the transfer stack, it should be the first entrypoint for transfer keeper's +// WriteAcknowledgement. +cbStack := ibccallbacks.NewIBCMiddleware(transferStack, app.PacketForwardKeeper, wasmStackIBCHandler, maxCallbackGas) +transferStack = packetforward.NewIBCMiddleware( + cbStack, + app.PacketForwardKeeper, + 0, // retries on timeout + packetforwardkeeper.DefaultForwardTransferPacketTimeoutTimestamp, +) +``` + +#### IBC v2 Application Stack + +For IBC v2, an example transfer stack is shown below. In this case the transfer stack is using the callbacks middleware. + +```go +// Create IBC v2 transfer middleware stack +// the callbacks gas limit is recommended to be 10M for use with wasm contracts +maxCallbackGas := uint64(10_000_000) +wasmStackIBCHandler := wasm.NewIBCHandler(app.WasmKeeper, app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper) + +var ibcv2TransferStack ibcapi.IBCModule + ibcv2TransferStack = transferv2.NewIBCModule(app.TransferKeeper) + ibcv2TransferStack = ibccallbacksv2.NewIBCMiddleware(transferv2.NewIBCModule(app.TransferKeeper), app.IBCKeeper.ChannelKeeperV2, wasmStackIBCHandler, app.IBCKeeper.ChannelKeeperV2, maxCallbackGas) +``` + +### Register module routes in the IBC `Router` + +IBC needs to know which module is bound to which port so that it can route packets to the +appropriate module and call the appropriate callbacks. The port to module name mapping is handled by +IBC's port `Keeper`. However, the mapping from module name to the relevant callbacks is accomplished +by the port +[`Router`](https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port/types/router.go) on the +`ibc` module. + +Adding the module routes allows the IBC handler to call the appropriate callback when processing a channel handshake or a packet. + +Currently, a `Router` is static so it must be initialized and set correctly on app initialization. +Once the `Router` has been set, no new routes can be added. + +```go title="app.go" +import ( + // other imports + // ... + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + ibctransfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +) + +func NewApp(...args) *App { + // .. continuation from above + + // Create static IBC router, add transfer module route, then set and seal it + ibcRouter := porttypes.NewRouter() + ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) + // Setting Router will finalize all routes by sealing router + // No more routes can be added + app.IBCKeeper.SetRouter(ibcRouter) + + // ... continues +``` + +#### IBC v2 Router + +With IBC v2, there is a new [router](https://github.com/cosmos/ibc-go/blob/main/modules/core/api/router.go) that needs to register the routes for a portID to a given IBCModule. It supports two kinds of routes: direct routes and prefix-based routes. The direct routes match one specific port ID to a module, while the prefix-based routes match any port ID with a specific prefix to a module. +For example, if a direct route named `someModule` exists, only messages addressed to exactly that port ID will be passed to the corresponding module. +However, if instead, `someModule` is a prefix-based route, port IDs like `someModuleRandomPort1`, `someModuleRandomPort2`, etc., will be passed to the module. +Note that the router will panic when you add a route that conflicts with an already existing route. This is also the case if you add a prefix-based route that conflicts with an existing direct route or vice versa. + +```go +// IBC v2 router creation + ibcRouterV2 := ibcapi.NewRouter() + ibcRouterV2.AddRoute(ibctransfertypes.PortID, ibcv2TransferStack) + // Setting Router will finalize all routes by sealing router + // No more routes can be added + app.IBCKeeper.SetRouterV2(ibcRouterV2) +``` + +### Module `Manager` and `SimulationManager` + +In order to use IBC, we need to add the new modules to the module `Manager` and to the `SimulationManager`, in case your application supports [simulations](https://docs.cosmos.network/main/learn/advanced/simulation). + +```go title="app.go" +import ( + // other imports + // ... + "github.com/cosmos/cosmos-sdk/types/module" + + ibc "github.com/cosmos/ibc-go/v10/modules/core" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer" +) + +func NewApp(...args) *App { + // ... continuation from above + + app.ModuleManager = module.NewManager( + // other modules + // ... + // highlight-start ++ ibc.NewAppModule(app.IBCKeeper), ++ transfer.NewAppModule(app.TransferKeeper), + // highlight-end + ) + + // ... + + app.simulationManager = module.NewSimulationManagerFromAppModules( + // other modules + // ... + app.ModuleManager.Modules, + map[string]module.AppModuleSimulation{}, + ) + + // ... continues +``` + +### Module account permissions + +After that, we need to grant `Minter` and `Burner` permissions to +the `transfer` `ModuleAccount` to mint and burn relayed tokens. + +```go title="app.go" +import ( + // other imports + // ... + "github.com/cosmos/cosmos-sdk/types/module" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + + // highlight-next-line ++ ibctransfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +) + +// app.go +var ( + // module account permissions + maccPerms = map[string][]string{ + // other module accounts permissions + // ... + ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + } +) +``` + +### Integrating light clients + +> Note that from v10 onwards, all light clients are expected to implement the [`LightClientInterface` interface](../03-light-clients/01-developer-guide/02-light-client-module.md#implementing-the-lightclientmodule-interface) defined by core IBC, and have to be explicitly registered in a chain's app.go. This is in contrast to earlier versions of ibc-go when `07-tendermint` and `06-solomachine` were added out of the box. Follow the steps below to integrate the `07-tendermint` light client. + +All light clients must be registered with `module.Manager` in a chain's app.go file. The following code example shows how to instantiate `07-tendermint` light client module and register its `ibctm.AppModule`. + +```go title="app.go" +import ( + // other imports + // ... + "github.com/cosmos/cosmos-sdk/types/module" + // highlight-next-line ++ ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" +) + +// app.go +// after sealing the IBC router +clientKeeper := app.IBCKeeper.ClientKeeper +storeProvider := app.IBCKeeper.ClientKeeper.GetStoreProvider() + +tmLightClientModule := ibctm.NewLightClientModule(appCodec, storeProvider) +clientKeeper.AddRoute(ibctm.ModuleName, &tmLightClientModule) +// ... +app.ModuleManager = module.NewManager( + // ... + ibc.NewAppModule(app.IBCKeeper), + transfer.NewAppModule(app.TransferKeeper), // i.e ibc-transfer module + + // register light clients on IBC + // highlight-next-line ++ ibctm.NewAppModule(tmLightClientModule), +) +``` + +#### Allowed Clients Params + +The allowed clients parameter defines an allow list of client types supported by the chain. The +default value is a single-element list containing the [`AllowedClients`](https://github.com/cosmos/ibc-go/blob/main/modules/core/02-client/types/client.pb.go#L248-L253) wildcard (`"*"`). Alternatively, the parameter +may be set with a list of client types (e.g. `"06-solomachine","07-tendermint","09-localhost"`). +A client type that is not registered on this list will fail upon creation or on genesis validation. +Note that, since the client type is an arbitrary string, chains must not register two light clients +which return the same value for the `ClientType()` function, otherwise the allow list check can be +bypassed. + +### Application ABCI ordering + +One addition from IBC is the concept of `HistoricalInfo` which is stored in the Cosmos SDK `x/staking` module. The number of records stored by `x/staking` is controlled by the `HistoricalEntries` parameter which stores `HistoricalInfo` on a per-height basis. +Each entry contains the historical information for the `Header` and `ValidatorSet` of this chain which is stored +at each height during the `BeginBlock` call. The `HistoricalInfo` is required to introspect a blockchain's prior state at a given height in order to verify the light client `ConsensusState` during the +connection handshake. + +```go title="app.go" +import ( + // other imports + // ... + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibckeeper "github.com/cosmos/ibc-go/v10/modules/core/keeper" + ibctransfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +) + +func NewApp(...args) *App { + // ... continuation from above + + // add x/staking, ibc and transfer modules to BeginBlockers + app.ModuleManager.SetOrderBeginBlockers( + // other modules ... + stakingtypes.ModuleName, + ibcexported.ModuleName, + ibctransfertypes.ModuleName, + ) + app.ModuleManager.SetOrderEndBlockers( + // other modules ... + stakingtypes.ModuleName, + ibcexported.ModuleName, + ibctransfertypes.ModuleName, + ) + + // ... + + genesisModuleOrder := []string{ + // other modules + // ... + ibcexported.ModuleName, + ibctransfertypes.ModuleName, + } + app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...) + + // ... continues +``` + +That's it! You have now wired up the IBC module and the `transfer` module, and are now able to send fungible tokens across +different chains. If you want to have a broader view of the changes take a look into the SDK's +[`SimApp`](https://github.com/cosmos/ibc-go/blob/main/testing/simapp/app.go). diff --git a/docs/docs/01-ibc/03-apps/00-ibcv2apps.md b/docs/docs/01-ibc/03-apps/00-ibcv2apps.md new file mode 100644 index 0000000..f68bc39 --- /dev/null +++ b/docs/docs/01-ibc/03-apps/00-ibcv2apps.md @@ -0,0 +1,316 @@ +--- +title: IBC v2 Applications +sidebar_label: IBC v2 Applications +sidebar_position: 0 +slug: /ibc/apps/ibcv2apps +--- +:::note Synopsis +Learn how to implement IBC v2 applications +::: + +To build an IBC v2 application the following steps are required: + +1. [Implement the `IBCModule` interface](#implement-the-ibcmodule-interface) +2. [Bind Ports](#bind-ports) +3. [Implement the IBCModule Keeper](#implement-the-ibcmodule-keeper) +4. [Implement application payload and success acknowledgement](#packets-and-payloads) +5. [Set and Seal the IBC Router](#routing) + +Highlighted improvements for app developers with IBC v2: + +- No need to support channel handshake callbacks +- Flexibility on upgrading application versioning, no need to use channel upgradability to renegotiate an application version, simply support the application version on both sides of the connection. +- Flexibility to choose your desired encoding type. + +## Implement the `IBCModule` interface + +The Cosmos SDK expects all IBC modules to implement the [`IBCModule` +interface](https://github.com/cosmos/ibc-go/blob/main/modules/core/api/module.go#L9-L53). This interface contains all of the callbacks IBC expects modules to implement. Note that for IBC v2, an application developer no longer needs to implement callbacks for the channel handshake. Note that this interface is distinct from the [porttypes.IBCModule interface][porttypes.IBCModule] used for IBC Classic. + +```go +// IBCModule implements the application interface given the keeper. +// The implementation of the IBCModule interface could for example be in a file called ibc_module.go, +// but ultimately file structure is up to the developer +type IBCModule struct { + keeper keeper.Keeper +} +``` + +Additionally, in the `module.go` file, add the following line: + +```go +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + // Add this line + _ porttypes.IBCModule = IBCModule{} +) +``` + +### Packet callbacks + +IBC expects modules to implement callbacks for handling the packet lifecycle, as defined in the `IBCModule` interface. + +With IBC v2, modules are not directly connected. Instead a pair of clients are connected and register the counterparty clientID. Packets are routed to the relevant application module by the portID registered in the Router. Relayers send packets between the routers/packet handlers on each chain. + +![IBC packet flow diagram](./images/packet_flow_v2.png) + +Briefly, a successful packet flow works as follows: + +1. A user sends a message to the IBC packet handler +2. The IBC packet handler validates the message, creates the packet and stores the commitment and returns the packet sequence number. The [`Payload`](https://github.com/cosmos/ibc-go/blob/fe25b216359fab71b3228461b05dbcdb1a554158/proto/ibc/core/channel/v2/packet.proto#L26-L38), which contains application specific data, is routed to the relevant application. +3. If the counterparty writes an acknowledgement of the packet then the sending chain will process the acknowledgement. +4. If the packet is not successfully received before the timeout, then the sending chain processes the packet's timeout. + +#### Sending packets + +[`MsgSendPacket`](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel/v2/types/tx.pb.go#L69-L75) is sent by a user to the [channel v2 message server](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel/v2/keeper/msg_server.go), which calls `ChannelKeeperV2.SendPacket`. This validates the message, creates the packet, stores the commitment and returns the packet sequence number. The application must specify its own payload which is used by the application and sent with `MsgSendPacket`. + +An application developer needs to implement the custom logic the application executes when a packet is sent. + +```go + +// OnSendPacket logic +func (im *IBCModule) OnSendPacket( + ctx sdk.Context, + sourceChannel string, + destinationChannel string, + sequence uint64, + payload channeltypesv2.Payload, + signer sdk.AccAddress) error { + +// implement any validation + +// implement payload decoding and validation + +// call the relevant keeper method for state changes as a result of application logic + +// emit events or telemetry data + +return nil +} + +``` + +#### Receiving packets + +To handle receiving packets, the module must implement the `OnRecvPacket` callback. An application module should validate and confirm support for the given version and encoding method used as there is greater flexibility in IBC v2 to support a range of versions and encoding methods. +The `OnRecvPacket` callback is invoked by the IBC module after the packet has been proven to be valid and correctly processed by the IBC +keepers. +Thus, the `OnRecvPacket` callback only needs to worry about making the appropriate state +changes given the packet data without worrying about whether the packet is valid or not. + +Modules may return to the IBC handler an acknowledgement which implements the `Acknowledgement` interface. +The IBC handler will then commit this acknowledgement of the packet so that a relayer may relay the +acknowledgement back to the sender module. + +The state changes that occurr during this callback could be: + +- the packet processing was successful as indicated by the `PacketStatus_Success` and an `Acknowledgement()` will be written +- if the packet processing was unsuccessful as indicated by the `PacketStatus_Failure` and an `ackErr` will be written + +Note that with IBC v2 the error acknowledgements are standardised and cannot be customised. + +```go +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, payload channeltypesv2.Payload, relayer sdk.AccAddress) channeltypesv2.RecvPacketResult { + + // do application state changes based on payload and return the result + // state changes should be written via the `RecvPacketResult` + + return recvResult +} +``` + +#### Acknowledging packets + +After a module writes an acknowledgement, a relayer can relay back the acknowledgement to the sender module. The sender module can +then process the acknowledgement using the `OnAcknowledgementPacket` callback. The contents of the +acknowledgement is entirely up to the application developer. + +IBC will pass in the acknowledgements as `[]byte` to this callback. The callback +is responsible for decoding the acknowledgement and processing it. The acknowledgement is serialised and deserialised using JSON. + +```go +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, acknowledgement []byte, payload channeltypesv2.Payload, relayer sdk.AccAddress) error { + + // check the type of the acknowledgement + + // if not ackErr, unmarshal the JSON acknowledgement and unmarshal packet payload + + // perform any application specific logic for processing acknowledgement + + // emit events + + return nil +} +``` + +#### Timeout packets + +If the timeout for a packet is reached before the packet is successfully received or the receiving +chain can no longer process the packet the sending chain must process the timeout using +`OnTimeoutPacket`. Again the IBC module will verify that the timeout is +valid, so our module only needs to implement the state machine logic for what to do once a +timeout is reached and the packet can no longer be received. + +```go +func (im IBCModule) OnTimeoutPacket( + ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, payload channeltypesv2.Payload, relayer sdk.AccAddress) error { + + // unmarshal packet data + + // do custom timeout logic, e.g. refund tokens for transfer +} +``` + +#### PacketDataUnmarshaler + +The `PacketDataUnmarshaler` interface is required for IBC v2 applications to implement because the encoding type is specified by the `Payload` and multiple encoding types are supported. + +```go +type PacketDataUnmarshaler interface { + // UnmarshalPacketData unmarshals the packet data into a concrete type + // the payload is provided and the packet data interface is returned + UnmarshalPacketData(payload channeltypesv2.Payload) (interface{}, error) +} +``` + +## Bind Ports + +Currently, ports must be bound on app initialization. In order to bind modules to their respective ports on initialization, the following needs to be implemented: + +> Note that `portID` does not refer to a certain numerical ID, like `localhost:8080` with a `portID` 8080. Rather it refers to the application module the port binds. For IBC Modules built with the Cosmos SDK, it defaults to the module's name and for Cosmwasm contracts it defaults to the contract address. + +Add port ID to the `GenesisState` proto definition: + +```protobuf +message GenesisState { + string port_id = 1; + // other fields +} +``` + +You can see an example for transfer [here](https://github.com/cosmos/ibc-go/blob/main/proto/ibc/applications/transfer/v1/genesis.proto). + +Add port ID as a key to the module store: + +```go +// x//types/keys.go +const ( + // ModuleName defines the IBC Module name + ModuleName = "moduleName" + + // PortID is the default port id that module binds to + PortID = "portID" + + // ... +) +``` + +Note that with IBC v2, the version does not need to be added as a key (as required with IBC classic) because versioning of applications is now contained within the [packet Payload](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel/v2/types/packet.go#L23-L32). + +Add port ID to `x//types/genesis.go`: + +```go +// in x//types/genesis.go + +// DefaultGenesisState returns a GenesisState +// with the portID defined in keys.go +func DefaultGenesisState() *GenesisState { + return &GenesisState{ + PortId: PortID, + // additional k-v fields + } +} + +// Validate performs basic genesis state validation +// returning an error upon any failure. +func (gs GenesisState) Validate() error { + if err := host.PortIdentifierValidator(gs.PortId); err != nil { + return err + } + //additional validations + + return gs.Params.Validate() +} +``` + +Set the port in the module keeper's for `InitGenesis`: + +```go +// SetPort sets the portID for the transfer module. Used in InitGenesis +func (k Keeper) SetPort(ctx sdk.Context, portID string) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Set(types.PortKey, []byte(portID)); err != nil { + panic(err) + } +} + + // Initialize any other module state, like params with SetParams. +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { + store := k.storeService.OpenKVStore(ctx) + bz := k.cdc.MustMarshal(¶ms) + if err := store.Set([]byte(types.ParamsKey), bz); err != nil { + panic(err) + } +} + // ... + +``` + +The module is set to the desired port. The setting and sealing happens during creation of the IBC router. + +## Implement the IBCModule Keeper + +More information on implementing the IBCModule Keepers can be found in the [keepers section](04-keeper.md) + +## Packets and Payloads + +Applications developers need to define the `Payload` contained within an [IBC packet](https://github.com/cosmos/ibc-go/blob/fe25b216359fab71b3228461b05dbcdb1a554158/proto/ibc/core/channel/v2/packet.proto#L11-L24). Note that in IBC v2 the `timeoutHeight` has been removed and only `timeoutTimestamp` is used. A packet can contain multiple payloads in a list. Each Payload includes: + +```go +// Payload contains the source and destination ports and payload for the application (version, encoding, raw bytes) +message Payload { + // specifies the source port of the packet. + string source_port = 1; + // specifies the destination port of the packet. + string destination_port = 2; + // version of the specified application. + string version = 3; + // the encoding used for the provided value. + string encoding = 4; + // the raw bytes for the payload. + bytes value = 5; +} +``` + +Note that compared to IBC classic, where the applications version and encoding is negotiated during the channel handshake, IBC v2 provides enhanced flexibility. The application version and encoding used by the Payload is defined in the Payload. An example Payload is illustrated below: + +```go +type MyAppPayloadData struct { + Field1 string + Field2 uint64 +} + +// Marshal your payload to bytes using your encoding +bz, err := json.Marshal(MyAppPayloadData{Field1: "example", Field2: 7}) + +// Wrap it in a channel v2 Payload +payload := channeltypesv2.NewPayload( + sourcePort, + destPort, + "my-app-v1", // App version + channeltypesv2.EncodingJSON, // Encoding type, e.g. JSON, protobuf or ABI + bz, // Encoded data +) +``` + +It is also possible to define your own custom success acknowledgement which will be returned to the sender if the packet is successfully recieved and is returned in the `RecvPacketResult`. Note that if the packet processing fails, it is not possible to define a custom error acknowledgment, a constant ackErr is returned. + +## Routing + +More information on implementing the IBC Router can be found in the [routing section](../03-apps/06-routing.md). + +[porttypes.IBCModule]: https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port/types/module.go diff --git a/docs/docs/01-ibc/03-apps/01-apps.md b/docs/docs/01-ibc/03-apps/01-apps.md new file mode 100644 index 0000000..dae4179 --- /dev/null +++ b/docs/docs/01-ibc/03-apps/01-apps.md @@ -0,0 +1,446 @@ +--- +title: IBC Applications +sidebar_label: IBC Applications +sidebar_position: 1 +slug: /ibc/apps/apps +--- + +# IBC Applications + +:::warning +This page is relevant for IBC Classic, naviagate to the IBC v2 applications page for information on v2 apps +::: + +Learn how to configure your application to use IBC and send data packets to other chains. + +This document serves as a guide for developers who want to write their own Inter-blockchain +Communication Protocol (IBC) applications for custom use cases. + +Due to the modular design of the IBC protocol, IBC +application developers do not need to concern themselves with the low-level details of clients, +connections, and proof verification, however a brief explaination is given. Then the document goes into detail on the abstraction layer most relevant for application +developers (channels and ports), and describes how to define your own custom packets, and +`IBCModule` callbacks. + +To have your module interact over IBC you must: bind to a port(s), define your own packet data and acknowledgement structs as well as how to encode/decode them, and implement the +`IBCModule` interface. Below is a more detailed explanation of how to write an IBC application +module correctly. + +:::note + +## Pre-requisites Readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: + +## Create a custom IBC application module + +### Implement `IBCModule` Interface and callbacks + +The Cosmos SDK expects all IBC modules to implement the [`IBCModule` +interface](https://github.com/cosmos/ibc-go/tree/main/modules/core/05-port/types/module.go). This +interface contains all of the callbacks IBC expects modules to implement. This section will describe +the callbacks that are called during channel handshake execution. + +Here are the channel handshake callbacks that modules are expected to implement: + +```go +// Called by IBC Handler on MsgOpenInit +func (k Keeper) OnChanOpenInit(ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + counterparty channeltypes.Counterparty, + version string, +) error { + + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + // Examples: Abort if order == UNORDERED, + // Abort if version is unsupported + err := checkArguments(args) + return err +} + +// Called by IBC Handler on MsgOpenTry +OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + if err := checkArguments(args); err != nil { + return err + } + + // Construct application version + // IBC applications must return the appropriate application version + // This can be a simple string or it can be a complex version constructed + // from the counterpartyVersion and other arguments. + // The version returned will be the channel version used for both channel ends. + appVersion := negotiateAppVersion(counterpartyVersion, args) + + return appVersion, nil +} + +// Called by IBC Handler on MsgOpenAck +OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyVersion string, +) error { + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} + +// Called by IBC Handler on MsgOpenConfirm +OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} +``` + +The channel closing handshake will also invoke module callbacks that can return errors to abort the +closing handshake. Closing a channel is a 2-step handshake, the initiating chain calls +`ChanCloseInit` and the finalizing chain calls `ChanCloseConfirm`. + +```go +// Called by IBC Handler on MsgCloseInit +OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + // ... do custom finalization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} + +// Called by IBC Handler on MsgCloseConfirm +OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // ... do custom finalization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} +``` + +#### Channel Handshake Version Negotiation + +Application modules are expected to verify versioning used during the channel handshake procedure. + +- `ChanOpenInit` callback should verify that the `MsgChanOpenInit.Version` is valid +- `ChanOpenTry` callback should construct the application version used for both channel ends. If no application version can be constructed, it must return an error. +- `ChanOpenAck` callback should verify that the `MsgChanOpenAck.CounterpartyVersion` is valid and supported. + +IBC expects application modules to perform application version negotiation in `OnChanOpenTry`. The negotiated version +must be returned to core IBC. If the version cannot be negotiated, an error should be returned. + +Versions must be strings but can implement any versioning structure. If your application plans to +have linear releases then semantic versioning is recommended. If your application plans to release +various features in between major releases then it is advised to use the same versioning scheme +as IBC. This versioning scheme specifies a version identifier and compatible feature set with +that identifier. Valid version selection includes selecting a compatible version identifier with +a subset of features supported by your application for that version. The struct is used for this +scheme can be found in `03-connection/types`. + +Since the version type is a string, applications have the ability to do simple version verification +via string matching or they can use the already implemented versioning system and pass the proto +encoded version into each handhshake call as necessary. + +ICS20 currently implements basic string matching with a single supported version. + +### Custom Packets + +Modules connected by a channel must agree on what application data they are sending over the +channel, as well as how they will encode/decode it. This process is not specified by IBC as it is up +to each application module to determine how to implement this agreement. However, for most +applications this will happen as a version negotiation during the channel handshake. While more +complex version negotiation is possible to implement inside the channel opening handshake, a very +simple version negotiation is implemented in the [ibc-transfer module](https://github.com/cosmos/ibc-go/tree/main/modules/apps/transfer/module.go). + +Thus, a module must define its custom packet data structure, along with a well-defined way to +encode and decode it to and from `[]byte`. + +```go +// Custom packet data defined in application module +type CustomPacketData struct { + // Custom fields ... +} + +EncodePacketData(packetData CustomPacketData) []byte { + // encode packetData to bytes +} + +DecodePacketData(encoded []byte) (CustomPacketData) { + // decode from bytes to packet data +} +``` + +Then a module must encode its packet data before sending it through IBC. + +```go +// Sending custom application packet data +data := EncodePacketData(customPacketData) +packet.Data = data +// Send packet to IBC, authenticating with channelCap +sequence, err := IBCChannelKeeper.SendPacket( + ctx, + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + data, +) +``` + +A module receiving a packet must decode the `PacketData` into a structure it expects so that it can +act on it. + +```go +// Receiving custom application packet data (in OnRecvPacket) +packetData := DecodePacketData(packet.Data) +// handle received custom packet data +``` + +#### Packet Flow Handling + +Just as IBC expected modules to implement callbacks for channel handshakes, IBC also expects modules +to implement callbacks for handling the packet flow through a channel. + +Once a module A and module B are connected to each other, relayers can start relaying packets and +acknowledgements back and forth on the channel. + +![IBC packet flow diagram](https://media.githubusercontent.com/media/cosmos/ibc/old/spec/ics-004-channel-and-packet-semantics/channel-state-machine.png) + +Briefly, a successful packet flow works as follows: + +1. module A sends a packet through the IBC module +2. the packet is received by module B +3. if module B writes an acknowledgement of the packet then module A will process the + acknowledgement +4. if the packet is not successfully received before the timeout, then module A processes the + packet's timeout. + +##### Sending Packets + +Modules do not send packets through callbacks, since the modules initiate the action of sending +packets to the IBC module, as opposed to other parts of the packet flow where msgs sent to the IBC +module must trigger execution on the port-bound module through the use of callbacks. Thus, to send a +packet a module simply needs to call `SendPacket` on the `IBCChannelKeeper`. + +```go +// Sending custom application packet data +data := EncodePacketData(customPacketData) +// Send packet to IBC, authenticating with channelCap +sequence, err := IBCChannelKeeper.SendPacket( + ctx, + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + data, +) +``` + +##### Receiving Packets + +To handle receiving packets, the module must implement the `OnRecvPacket` callback. This gets +invoked by the IBC module after the packet has been proved valid and correctly processed by the IBC +keepers. Thus, the `OnRecvPacket` callback only needs to worry about making the appropriate state +changes given the packet data without worrying about whether the packet is valid or not. + +Modules may return to the IBC handler an acknowledgement which implements the Acknowledgement interface. +The IBC handler will then commit this acknowledgement of the packet so that a relayer may relay the +acknowledgement back to the sender module. + +The state changes that occurred during this callback will only be written if: + +- the acknowledgement was successful as indicated by the `Success()` function of the acknowledgement +- if the acknowledgement returned is nil indicating that an asynchronous process is occurring + +NOTE: Applications which process asynchronous acknowledgements must handle reverting state changes +when appropriate. Any state changes that occurred during the `OnRecvPacket` callback will be written +for asynchronous acknowledgements. + +```go +OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, +) ibcexported.Acknowledgement { + // Decode the packet data + packetData := DecodePacketData(packet.Data) + + // do application state changes based on packet data and return the acknowledgement + // NOTE: The acknowledgement will indicate to the IBC handler if the application + // state changes should be written via the `Success()` function. Application state + // changes are only written if the acknowledgement is successful or the acknowledgement + // returned is nil indicating that an asynchronous acknowledgement will occur. + ack := processPacket(ctx, packet, packetData) + + return ack +} +``` + +The Acknowledgement interface: + +```go +// Acknowledgement defines the interface used to return +// acknowledgements in the OnRecvPacket callback. +type Acknowledgement interface { + Success() bool + Acknowledgement() []byte +} +``` + +### Acknowledgements + +Modules may commit an acknowledgement upon receiving and processing a packet in the case of synchronous packet processing. +In the case where a packet is processed at some later point after the packet has been received (asynchronous execution), the acknowledgement +will be written once the packet has been processed by the application which may be well after the packet receipt. + +NOTE: Most blockchain modules will want to use the synchronous execution model in which the module processes and writes the acknowledgement +for a packet as soon as it has been received from the IBC module. + +This acknowledgement can then be relayed back to the original sender chain, which can take action +depending on the contents of the acknowledgement. + +Just as packet data was opaque to IBC, acknowledgements are similarly opaque. Modules must pass and +receive acknowledegments with the IBC modules as byte strings. + +Thus, modules must agree on how to encode/decode acknowledgements. The process of creating an +acknowledgement struct along with encoding and decoding it, is very similar to the packet data +example above. [ICS 04](https://github.com/cosmos/ibc/blob/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope) +specifies a recommended format for acknowledgements. This acknowledgement type can be imported from +[channel types](https://github.com/cosmos/ibc-go/tree/main/modules/core/04-channel/types). + +While modules may choose arbitrary acknowledgement structs, a default acknowledgement types is provided by IBC [here](https://github.com/cosmos/ibc-go/blob/main/proto/ibc/core/channel/v1/channel.proto): + +```proto +// Acknowledgement is the recommended acknowledgement format to be used by +// app-specific protocols. +// NOTE: The field numbers 21 and 22 were explicitly chosen to avoid accidental +// conflicts with other protobuf message formats used for acknowledgements. +// The first byte of any message with this format will be the non-ASCII values +// `0xaa` (result) or `0xb2` (error). Implemented as defined by ICS: +// https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope +message Acknowledgement { + // response contains either a result or an error and must be non-empty + oneof response { + bytes result = 21; + string error = 22; + } +} +``` + +#### Acknowledging Packets + +After a module writes an acknowledgement, a relayer can relay back the acknowledgement to the sender module. The sender module can +then process the acknowledgement using the `OnAcknowledgementPacket` callback. The contents of the +acknowledgement is entirely up to the modules on the channel (just like the packet data); however, it +may often contain information on whether the packet was successfully processed along +with some additional data that could be useful for remediation if the packet processing failed. + +Since the modules are responsible for agreeing on an encoding/decoding standard for packet data and +acknowledgements, IBC will pass in the acknowledgements as `[]byte` to this callback. The callback +is responsible for decoding the acknowledgement and processing it. + +```go +OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, +) (*sdk.Result, error) { + // Decode acknowledgement + ack := DecodeAcknowledgement(acknowledgement) + + // process ack + res, err := processAck(ack) + return res, err +} +``` + +#### Timeout Packets + +If the timeout for a packet is reached before the packet is successfully received or the +counterparty channel end is closed before the packet is successfully received, then the receiving +chain can no longer process it. Thus, the sending chain must process the timeout using +`OnTimeoutPacket` to handle this situation. Again the IBC module will verify that the timeout is +indeed valid, so our module only needs to implement the state machine logic for what to do once a +timeout is reached and the packet can no longer be received. + +```go +OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, +) (*sdk.Result, error) { + // do custom timeout logic +} +``` + +### Routing + +As mentioned above, modules must implement the IBC module interface (which contains both channel +handshake callbacks and packet handling callbacks). The concrete implementation of this interface +must be registered with the module name as a route on the IBC `Router`. + +```go +// app.go +func NewApp(...args) *App { +// ... + +// Create static IBC router, add module routes, then set and seal it +ibcRouter := port.NewRouter() + +ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferModule) +// Note: moduleCallbacks must implement IBCModule interface +ibcRouter.AddRoute(moduleName, moduleCallbacks) + +// Setting Router will finalize all routes by sealing router +// No more routes can be added +app.IBCKeeper.SetRouter(ibcRouter) +``` + +## Working Example + +For a real working example of an IBC application, you can look through the `ibc-transfer` module +which implements everything discussed above. + +Here are the useful parts of the module to look at: + +[Binding to transfer +port](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/genesis.go) + +[Sending transfer +packets](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/relay.go) + +[Implementing IBC +callbacks](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/ibc_module.go) diff --git a/docs/docs/01-ibc/03-apps/02-ibcmodule.md b/docs/docs/01-ibc/03-apps/02-ibcmodule.md new file mode 100644 index 0000000..ca3477d --- /dev/null +++ b/docs/docs/01-ibc/03-apps/02-ibcmodule.md @@ -0,0 +1,367 @@ +--- +title: Implement IBCModule interface and callbacks +sidebar_label: Implement IBCModule interface and callbacks +sidebar_position: 2 +slug: /ibc/apps/ibcmodule +--- + +# Implement `IBCModule` interface and callbacks + +:::note Synopsis +Learn how to implement the `IBCModule` interface and all of the callbacks it requires. +::: + +The Cosmos SDK expects all IBC modules to implement the [`IBCModule` +interface](https://github.com/cosmos/ibc-go/tree/main/modules/core/05-port/types/module.go). This interface contains all of the callbacks IBC expects modules to implement. They include callbacks related to channel handshake, closing and packet callbacks (`OnRecvPacket`, `OnAcknowledgementPacket` and `OnTimeoutPacket`). + +```go +// IBCModule implements the ICS26 interface for given the keeper. +// The implementation of the IBCModule interface could for example be in a file called ibc_module.go, +// but ultimately file structure is up to the developer +type IBCModule struct { + keeper keeper.Keeper +} +``` + +Additionally, in the `module.go` file, add the following line: + +```go +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + // Add this line + _ porttypes.IBCModule = IBCModule{} +) +``` + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: + +## Channel handshake callbacks + +This section will describe the callbacks that are called during channel handshake execution. + +Here are the channel handshake callbacks that modules are expected to implement: + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `checkArguments` and `negotiateAppVersion` functions. + +```go +// Called by IBC Handler on MsgOpenInit +func (im IBCModule) OnChanOpenInit(ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + // Examples: + // - Abort if order == UNORDERED, + // - Abort if version is unsupported + if err := checkArguments(args); err != nil { + return "", err + } + + + return version, nil +} + +// Called by IBC Handler on MsgOpenTry +func (im IBCModule) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + if err := checkArguments(args); err != nil { + return "", err + } + + // Construct application version + // IBC applications must return the appropriate application version + // This can be a simple string or it can be a complex version constructed + // from the counterpartyVersion and other arguments. + // The version returned will be the channel version used for both channel ends. + appVersion := negotiateAppVersion(counterpartyVersion, args) + + return appVersion, nil +} + +// Called by IBC Handler on MsgOpenAck +func (im IBCModule) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyVersion string, +) error { + if counterpartyVersion != types.Version { + return sdkerrors.Wrapf(types.ErrInvalidVersion, "invalid counterparty version: %s, expected %s", counterpartyVersion, types.Version) + } + + // do custom logic + + return nil +} + +// Called by IBC Handler on MsgOpenConfirm +func (im IBCModule) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // do custom logic + + return nil +} +``` + +### Channel closing callbacks + +The channel closing handshake will also invoke module callbacks that can return errors to abort the closing handshake. Closing a channel is a 2-step handshake, the initiating chain calls `ChanCloseInit` and the finalizing chain calls `ChanCloseConfirm`. + +Currently, all IBC modules in this repository return an error for `OnChanCloseInit` to prevent the channels from closing. This is because any user can call `ChanCloseInit` by submitting a `MsgChannelCloseInit` transaction. + +```go +// Called by IBC Handler on MsgCloseInit +func (im IBCModule) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + // ... do custom finalization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} + +// Called by IBC Handler on MsgCloseConfirm +func (im IBCModule) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // ... do custom finalization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} +``` + +### Channel handshake version negotiation + +Application modules are expected to verify versioning used during the channel handshake procedure. + +- `OnChanOpenInit` will verify that the relayer-chosen parameters + are valid and perform any custom `INIT` logic. + It may return an error if the chosen parameters are invalid + in which case the handshake is aborted. + If the provided version string is non-empty, `OnChanOpenInit` should return + the version string if valid or an error if the provided version is invalid. + **If the version string is empty, `OnChanOpenInit` is expected to + return a default version string representing the version(s) + it supports.** + If there is no default version string for the application, + it should return an error if the provided version is an empty string. +- `OnChanOpenTry` will verify the relayer-chosen parameters along with the + counterparty-chosen version string and perform custom `TRY` logic. + If the relayer-chosen parameters + are invalid, the callback must return an error to abort the handshake. + If the counterparty-chosen version is not compatible with this module's + supported versions, the callback must return an error to abort the handshake. + If the versions are compatible, the try callback must select the final version + string and return it to core IBC. + `OnChanOpenTry` may also perform custom initialization logic. +- `OnChanOpenAck` will error if the counterparty selected version string + is invalid and abort the handshake. It may also perform custom ACK logic. + +Versions must be strings but can implement any versioning structure. If your application plans to +have linear releases then semantic versioning is recommended. If your application plans to release +various features in between major releases then it is advised to use the same versioning scheme +as IBC. This versioning scheme specifies a version identifier and compatible feature set with +that identifier. Valid version selection includes selecting a compatible version identifier with +a subset of features supported by your application for that version. The struct used for this +scheme can be found in [03-connection/types](https://github.com/cosmos/ibc-go/blob/main/modules/core/03-connection/types/version.go#L16). + +Since the version type is a string, applications have the ability to do simple version verification +via string matching or they can use the already implemented versioning system and pass the proto +encoded version into each handhshake call as necessary. + +ICS20 currently implements basic string matching with a single supported version. + +## Packet callbacks + +Just as IBC expects modules to implement callbacks for channel handshakes, it also expects modules to implement callbacks for handling the packet flow through a channel, as defined in the `IBCModule` interface. + +Once a module A and module B are connected to each other, relayers can start relaying packets and acknowledgements back and forth on the channel. + +![IBC packet flow diagram](./images/packet_flow.png) + +Briefly, a successful packet flow works as follows: + +1. Module A sends a packet through the IBC module +2. The packet is received by module B +3. If module B writes an acknowledgement of the packet then module A will process the acknowledgement +4. If the packet is not successfully received before the timeout, then module A processes the packet's timeout. + +### Sending packets + +Modules **do not send packets through callbacks**, since the modules initiate the action of sending packets to the IBC module, as opposed to other parts of the packet flow where messages sent to the IBC +module must trigger execution on the port-bound module through the use of callbacks. Thus, to send a packet a module simply needs to call `SendPacket` on the `IBCChannelKeeper`. + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `EncodePacketData(customPacketData)` function. + +```go +// Sending custom application packet data +data := EncodePacketData(customPacketData) +// Send packet to IBC, authenticating with channelCap +sequence, err := IBCChannelKeeper.SendPacket( + ctx, + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + data, +) +``` + +### Receiving packets + +To handle receiving packets, the module must implement the `OnRecvPacket` callback. This gets +invoked by the IBC module after the packet has been proved valid and correctly processed by the IBC +keepers. Thus, the `OnRecvPacket` callback only needs to worry about making the appropriate state +changes given the packet data without worrying about whether the packet is valid or not. + +Modules may return to the IBC handler an acknowledgement which implements the `Acknowledgement` interface. +The IBC handler will then commit this acknowledgement of the packet so that a relayer may relay the +acknowledgement back to the sender module. + +The state changes that occurred during this callback will only be written if: + +- the acknowledgement was successful as indicated by the `Success()` function of the acknowledgement +- if the acknowledgement returned is nil indicating that an asynchronous process is occurring + +NOTE: Applications which process asynchronous acknowledgements must handle reverting state changes +when appropriate. Any state changes that occurred during the `OnRecvPacket` callback will be written +for asynchronous acknowledgements. + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `DecodePacketData(packet.Data)` function. + +```go +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, +) ibcexported.Acknowledgement { + // Decode the packet data + packetData := DecodePacketData(packet.Data) + + // do application state changes based on packet data and return the acknowledgement + // NOTE: The acknowledgement will indicate to the IBC handler if the application + // state changes should be written via the `Success()` function. Application state + // changes are only written if the acknowledgement is successful or the acknowledgement + // returned is nil indicating that an asynchronous acknowledgement will occur. + ack := processPacket(ctx, packet, packetData) + + return ack +} +``` + +Reminder, the `Acknowledgement` interface: + +```go +// Acknowledgement defines the interface used to return +// acknowledgements in the OnRecvPacket callback. +type Acknowledgement interface { + Success() bool + Acknowledgement() []byte +} +``` + +### Acknowledging packets + +After a module writes an acknowledgement, a relayer can relay back the acknowledgement to the sender module. The sender module can +then process the acknowledgement using the `OnAcknowledgementPacket` callback. The contents of the +acknowledgement is entirely up to the modules on the channel (just like the packet data); however, it +may often contain information on whether the packet was successfully processed along +with some additional data that could be useful for remediation if the packet processing failed. + +Since the modules are responsible for agreeing on an encoding/decoding standard for packet data and +acknowledgements, IBC will pass in the acknowledgements as `[]byte` to this callback. The callback +is responsible for decoding the acknowledgement and processing it. + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `DecodeAcknowledgement(acknowledgments)` and `processAck(ack)` functions. + +```go +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, +) (*sdk.Result, error) { + // Decode acknowledgement + ack := DecodeAcknowledgement(acknowledgement) + + // process ack + res, err := processAck(ack) + return res, err +} +``` + +### Timeout packets + +If the timeout for a packet is reached before the packet is successfully received or the +counterparty channel end is closed before the packet is successfully received, then the receiving +chain can no longer process it. Thus, the sending chain must process the timeout using +`OnTimeoutPacket` to handle this situation. Again the IBC module will verify that the timeout is +indeed valid, so our module only needs to implement the state machine logic for what to do once a +timeout is reached and the packet can no longer be received. + +```go +func (im IBCModule) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, +) (*sdk.Result, error) { + // do custom timeout logic +} +``` + +### Optional interfaces + +The following interface are optional and MAY be implemented by an IBCModule. + +#### PacketDataUnmarshaler + +The `PacketDataUnmarshaler` interface is defined as follows: + +```go +// PacketDataUnmarshaler defines an optional interface which allows a middleware to +// request the packet data to be unmarshaled by the base application. +type PacketDataUnmarshaler interface { + // UnmarshalPacketData unmarshals the packet data into a concrete type + // ctx, portID, channelID are provided as arguments, so that (if needed) + // the packet data can be unmarshaled based on the channel version. + // The version of the underlying app is also returned. + UnmarshalPacketData(ctx sdk.Context, portID, channelID string, bz []byte) (interface{}, string, error) +} +``` + +The implementation of `UnmarshalPacketData` should unmarshal the bytes into the packet data type defined for an IBC stack. +The base application of an IBC stack should unmarshal the bytes into its packet data type, while a middleware may simply defer the call to the underlying application. + +This interface allows middlewares to unmarshal a packet data in order to make use of interfaces the packet data type implements. +For example, the callbacks middleware makes use of this function to access packet data types which implement the `PacketData` and `PacketDataProvider` interfaces. diff --git a/docs/docs/01-ibc/03-apps/03-bindports.md b/docs/docs/01-ibc/03-apps/03-bindports.md new file mode 100644 index 0000000..6eb8568 --- /dev/null +++ b/docs/docs/01-ibc/03-apps/03-bindports.md @@ -0,0 +1,106 @@ +--- +title: Bind ports +sidebar_label: Bind ports +sidebar_position: 3 +slug: /ibc/apps/bindports +--- + +# Bind ports + +:::note Synopsis +Learn what changes to make to bind modules to their ports on initialization. +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: +Currently, ports must be bound on app initialization. In order to bind modules to their respective ports on initialization, the following needs to be implemented: + +> Note that `portID` does not refer to a certain numerical ID, like `localhost:8080` with a `portID` 8080. Rather it refers to the application module the port binds. For IBC Modules built with the Cosmos SDK, it defaults to the module's name and for Cosmwasm contracts it defaults to the contract address. + +1. Add port ID to the `GenesisState` proto definition: + +```protobuf +message GenesisState { + string port_id = 1; + // other fields +} +``` + +2. Add port ID as a key to the module store: + +```go +// x//types/keys.go +const ( + // ModuleName defines the IBC Module name + ModuleName = "moduleName" + + // Version defines the current version the IBC + // module supports + Version = "moduleVersion-1" + + // PortID is the default port id that module binds to + PortID = "portID" + + // ... +) +``` + +3. Add port ID to `x//types/genesis.go`: + +```go +// in x//types/genesis.go + +// DefaultGenesisState returns a GenesisState with "portID" as the default PortID. +func DefaultGenesisState() *GenesisState { + return &GenesisState{ + PortId: PortID, + // additional k-v fields + } +} + +// Validate performs basic genesis state validation returning an error upon any +// failure. +func (gs GenesisState) Validate() error { + if err := host.PortIdentifierValidator(gs.PortId); err != nil { + return err + } + //additional validations + + return gs.Params.Validate() +} +``` + +4. Set the port in the module keeper's for `InitGenesis`: + +:::note +The capability module has been removed so port binding has also changed +::: + +```go +// SetPort sets the portID for the transfer module. Used in InitGenesis +func (k Keeper) SetPort(ctx sdk.Context, portID string) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Set(types.PortKey, []byte(portID)); err != nil { + panic(err) + } +} + + // Initialize any other module state, like params with SetParams. +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { + store := k.storeService.OpenKVStore(ctx) + bz := k.cdc.MustMarshal(¶ms) + if err := store.Set([]byte(types.ParamsKey), bz); err != nil { + panic(err) + } +} + // ... + +``` + +The module is set to the desired port. The setting and sealing happens during creation of the IBC router. diff --git a/docs/docs/01-ibc/03-apps/04-keeper.md b/docs/docs/01-ibc/03-apps/04-keeper.md new file mode 100644 index 0000000..4c0d124 --- /dev/null +++ b/docs/docs/01-ibc/03-apps/04-keeper.md @@ -0,0 +1,70 @@ +--- +title: Keeper +sidebar_label: Keeper +sidebar_position: 4 +slug: /ibc/apps/keeper +--- + +# Keeper + +:::note Synopsis +Learn how to implement the IBC Module keeper. Relevant for IBC classic and v2 +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: +In the previous sections, on channel handshake callbacks and port binding in `InitGenesis`, a reference was made to keeper methods that need to be implemented when creating a custom IBC module. Below is an overview of how to define an IBC module's keeper. + +> Note that some code has been left out for clarity, to get a full code overview, please refer to [the transfer module's keeper in the ibc-go repo](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/keeper.go). + +```go +// Keeper defines the IBC app module keeper +type Keeper struct { + storeKey sdk.StoreKey + cdc codec.BinaryCodec + paramSpace paramtypes.Subspace + + channelKeeper types.ChannelKeeper + portKeeper types.PortKeeper + + // ... additional according to custom logic +} + +// NewKeeper creates a new IBC app module Keeper instance +func NewKeeper( + // args +) Keeper { + // ... + + return Keeper{ + cdc: cdc, + storeKey: key, + paramSpace: paramSpace, + + channelKeeper: channelKeeper, + portKeeper: portKeeper, + + // ... additional according to custom logic + } +} + +// GetPort returns the portID for the IBC app module. Used in ExportGenesis +func (k Keeper) GetPort(ctx sdk.Context) string { + store := ctx.KVStore(k.storeKey) + return string(store.Get(types.PortKey)) +} + +// SetPort sets the portID for the IBC app module. Used in InitGenesis +func (k Keeper) SetPort(ctx sdk.Context, portID string) { + store := ctx.KVStore(k.storeKey) + store.Set(types.PortKey, []byte(portID)) +} + +// ... additional according to custom logic +``` diff --git a/docs/docs/01-ibc/03-apps/05-packets_acks.md b/docs/docs/01-ibc/03-apps/05-packets_acks.md new file mode 100644 index 0000000..de96b87 --- /dev/null +++ b/docs/docs/01-ibc/03-apps/05-packets_acks.md @@ -0,0 +1,163 @@ +--- +title: Define packets and acks +sidebar_label: Define packets and acks +sidebar_position: 5 +slug: /ibc/apps/packets_acks +--- + +# Define packets and acks + +:::note Synopsis +Learn how to define custom packet and acknowledgement structs and how to encode and decode them. +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: + +## Custom packets + +Modules connected by a channel must agree on what application data they are sending over the +channel, as well as how they will encode/decode it. This process is not specified by IBC as it is up +to each application module to determine how to implement this agreement. However, for most +applications this will happen as a version negotiation during the channel handshake. While more +complex version negotiation is possible to implement inside the channel opening handshake, a very +simple version negotiation is implemented in the [ibc-transfer module](https://github.com/cosmos/ibc-go/tree/main/modules/apps/transfer/module.go). + +Thus, a module must define its custom packet data structure, along with a well-defined way to +encode and decode it to and from `[]byte`. + +```go +// Custom packet data defined in application module +type CustomPacketData struct { + // Custom fields ... +} + +EncodePacketData(packetData CustomPacketData) []byte { + // encode packetData to bytes +} + +DecodePacketData(encoded []byte) (CustomPacketData) { + // decode from bytes to packet data +} +``` + +> Note that the `CustomPacketData` struct is defined in the proto definition and then compiled by the protobuf compiler. + +Then a module must encode its packet data before sending it through IBC. + +```go +// Sending custom application packet data +data := EncodePacketData(customPacketData) +// Send packet to IBC, authenticating with channelCap +sequence, err := IBCChannelKeeper.SendPacket( + ctx, + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + data, +) +``` + +A module receiving a packet must decode the `PacketData` into a structure it expects so that it can +act on it. + +```go +// Receiving custom application packet data (in OnRecvPacket) +packetData := DecodePacketData(packet.Data) +// handle received custom packet data +``` + +### Optional interfaces + +The following interfaces are optional and MAY be implemented by a custom packet type. +They allow middlewares such as callbacks to access information stored within the packet data. + +#### PacketData interface + +The `PacketData` interface is defined as follows: + +```go +// PacketData defines an optional interface which an application's packet data structure may implement. +type PacketData interface { + // GetPacketSender returns the sender address of the packet data. + // If the packet sender is unknown or undefined, an empty string should be returned. + GetPacketSender(sourcePortID string) string +} +``` + +The implementation of `GetPacketSender` should return the sender of the packet data. +If the packet sender is unknown or undefined, an empty string should be returned. + +This interface is intended to give IBC middlewares access to the packet sender of a packet data type. + +#### PacketDataProvider interface + +The `PacketDataProvider` interface is defined as follows: + +```go +// PacketDataProvider defines an optional interfaces for retrieving custom packet data stored on behalf of another application. +// An existing problem in the IBC middleware design is the inability for a middleware to define its own packet data type and insert packet sender provided information. +// A short term solution was introduced into several application's packet data to utilize a memo field to carry this information on behalf of another application. +// This interfaces standardizes that behaviour. Upon realization of the ability for middleware's to define their own packet data types, this interface will be deprecated and removed with time. +type PacketDataProvider interface { + // GetCustomPacketData returns the packet data held on behalf of another application. + // The name the information is stored under should be provided as the key. + // If no custom packet data exists for the key, nil should be returned. + GetCustomPacketData(key string) interface{} +} +``` + +The implementation of `GetCustomPacketData` should return packet data held on behalf of another application (if present and supported). +If this functionality is not supported, it should return nil. Otherwise it should return the packet data associated with the provided key. + +This interface gives IBC applications access to the packet data information embedded into the base packet data type. +Within transfer and interchain accounts, the embedded packet data is stored within the Memo field. + +Once all IBC applications within an IBC stack are capable of creating/maintaining their own packet data type's, this interface function will be deprecated and removed. + +## Acknowledgements + +Modules may commit an acknowledgement upon receiving and processing a packet in the case of synchronous packet processing. +In the case where a packet is processed at some later point after the packet has been received (asynchronous execution), the acknowledgement +will be written once the packet has been processed by the application which may be well after the packet receipt. + +NOTE: Most blockchain modules will want to use the synchronous execution model in which the module processes and writes the acknowledgement +for a packet as soon as it has been received from the IBC module. + +This acknowledgement can then be relayed back to the original sender chain, which can take action +depending on the contents of the acknowledgement. + +Just as packet data was opaque to IBC, acknowledgements are similarly opaque. Modules must pass and +receive acknowledegments with the IBC modules as byte strings. + +Thus, modules must agree on how to encode/decode acknowledgements. The process of creating an +acknowledgement struct along with encoding and decoding it, is very similar to the packet data +example above. [ICS 04](https://github.com/cosmos/ibc/blob/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope) +specifies a recommended format for acknowledgements. This acknowledgement type can be imported from +[channel types](https://github.com/cosmos/ibc-go/tree/main/modules/core/04-channel/types). + +While modules may choose arbitrary acknowledgement structs, a default acknowledgement types is provided by IBC [here](https://github.com/cosmos/ibc-go/blob/main/proto/ibc/core/channel/v1/channel.proto): + +```protobuf +// Acknowledgement is the recommended acknowledgement format to be used by +// app-specific protocols. +// NOTE: The field numbers 21 and 22 were explicitly chosen to avoid accidental +// conflicts with other protobuf message formats used for acknowledgements. +// The first byte of any message with this format will be the non-ASCII values +// `0xaa` (result) or `0xb2` (error). Implemented as defined by ICS: +// https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope +message Acknowledgement { + // response contains either a result or an error and must be non-empty + oneof response { + bytes result = 21; + string error = 22; + } +} +``` diff --git a/docs/docs/01-ibc/03-apps/06-routing.md b/docs/docs/01-ibc/03-apps/06-routing.md new file mode 100644 index 0000000..e84c2d3 --- /dev/null +++ b/docs/docs/01-ibc/03-apps/06-routing.md @@ -0,0 +1,44 @@ +--- +title: Routing +sidebar_label: Routing +sidebar_position: 6 +slug: /ibc/apps/routing +--- + +# Routing + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: +:::note Synopsis +Learn how to hook a route to the IBC router for the custom IBC module. +::: + +As mentioned above, modules must implement the `IBCModule` interface (which contains both channel +handshake callbacks for IBC classic only, and packet handling callbacks for IBC classic and v2). The concrete implementation of this interface +must be registered with the module name as a route on the IBC `Router`. + +```go +// app.go +func NewApp(...args) *App { + // ... + + // Create static IBC router, add module routes, then set and seal it + ibcRouter := port.NewRouter() + + ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferModule) + // Note: moduleCallbacks must implement IBCModule interface + ibcRouter.AddRoute(moduleName, moduleCallbacks) + + // Setting Router will finalize all routes by sealing router + // No more routes can be added + app.IBCKeeper.SetRouter(ibcRouter) + + // ... +} +``` diff --git a/docs/docs/01-ibc/03-apps/_category_.json b/docs/docs/01-ibc/03-apps/_category_.json new file mode 100644 index 0000000..1c34da9 --- /dev/null +++ b/docs/docs/01-ibc/03-apps/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Applications", + "position": 3, + "link": null +} \ No newline at end of file diff --git a/docs/docs/01-ibc/03-apps/images/packet_flow.png b/docs/docs/01-ibc/03-apps/images/packet_flow.png new file mode 100644 index 0000000..e5bae3f Binary files /dev/null and b/docs/docs/01-ibc/03-apps/images/packet_flow.png differ diff --git a/docs/docs/01-ibc/03-apps/images/packet_flow_v2.png b/docs/docs/01-ibc/03-apps/images/packet_flow_v2.png new file mode 100644 index 0000000..7e3d673 Binary files /dev/null and b/docs/docs/01-ibc/03-apps/images/packet_flow_v2.png differ diff --git a/docs/docs/01-ibc/04-middleware/01-overview.md b/docs/docs/01-ibc/04-middleware/01-overview.md new file mode 100644 index 0000000..d26c73b --- /dev/null +++ b/docs/docs/01-ibc/04-middleware/01-overview.md @@ -0,0 +1,55 @@ +--- +title: IBC middleware +sidebar_label: IBC middleware +sidebar_position: 1 +slug: /ibc/middleware/overview +--- + +# IBC middleware + +:::note Synopsis +Learn how to write your own custom middleware to wrap an IBC application, and understand how to hook different middleware to IBC base applications to form different IBC application stacks +::: + +This documentation serves as a guide for middleware developers who want to write their own middleware and for chain developers who want to use IBC middleware on their chains. + +After going through the overview they can consult respectively: + +- [documentation on developing custom middleware](02-develop.md) +- [documentation on integrating middleware into a stack on a chain](03-integration.md) + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC Integration](../02-integration.md) +- [IBC Application Developer Guide](../03-apps/01-apps.md) + +::: + +## Why middleware? + +IBC applications are designed to be self-contained modules that implement their own application-specific logic through a set of interfaces with the core IBC handlers. These core IBC handlers, in turn, are designed to enforce the correctness properties of IBC (transport, authentication, ordering) while delegating all application-specific handling to the IBC application modules. **However, there are cases where some functionality may be desired by many applications, yet not appropriate to place in core IBC.** + +Middleware allows developers to define the extensions as separate modules that can wrap over the base application. This middleware can thus perform its own custom logic, and pass data into the application so that it may run its logic without being aware of the middleware's existence. This allows both the application and the middleware to implement its own isolated logic while still being able to run as part of a single packet flow. + +## Definitions + +`Middleware`: A self-contained module that sits between core IBC and an underlying IBC application during packet execution. All messages between core IBC and underlying application must flow through middleware, which may perform its own custom logic. + +`Underlying Application`: An underlying application is the application that is directly connected to the middleware in question. This underlying application may itself be middleware that is chained to a base application. + +`Base Application`: A base application is an IBC application that does not contain any middleware. It may be nested by 0 or multiple middleware to form an application stack. + +`Application Stack (or stack)`: A stack is the complete set of application logic (middleware(s) + base application) that gets connected to core IBC. A stack may be just a base application, or it may be a series of middlewares that nest a base application. + +The diagram below gives an overview of a middleware stack consisting of two middleware (one stateless, the other stateful). + +![middleware-stack.png](./images/middleware-stack.png) + +Keep in mind that: + +- **The order of the middleware matters** (more on how to correctly define your stack in the code will follow in the [integration section](03-integration.md)). +- Depending on the type of message, it will either be passed on from the base application up the middleware stack to core IBC or down the stack in the reverse situation (handshake and packet callbacks). +- IBC middleware will wrap over an underlying IBC application and sits between core IBC and the application. It has complete control in modifying any message coming from IBC to the application, and any message coming from the application to core IBC. **Middleware must be completely trusted by chain developers who wish to integrate them**, as this gives them complete flexibility in modifying the application(s) they wrap. diff --git a/docs/docs/01-ibc/04-middleware/02-develop.md b/docs/docs/01-ibc/04-middleware/02-develop.md new file mode 100644 index 0000000..1ed72e4 --- /dev/null +++ b/docs/docs/01-ibc/04-middleware/02-develop.md @@ -0,0 +1,478 @@ +--- +title: Create a custom IBC middleware +sidebar_label: Create a custom IBC middleware +sidebar_position: 3 +slug: /ibc/middleware/develop +--- + + +# Create a custom IBC middleware + +IBC middleware will wrap over an underlying IBC application (a base application or downstream middleware) and sits between core IBC and the base application. + +:::warning +middleware developers must use the same serialization and deserialization method as in ibc-go's codec: transfertypes.ModuleCdc.[Must]MarshalJSON +::: + +For middleware builders this means: + +```go +import transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +transfertypes.ModuleCdc.[Must]MarshalJSON +func MarshalAsIBCDoes(ack channeltypes.Acknowledgement) ([]byte, error) { + return transfertypes.ModuleCdc.MarshalJSON(&ack) +} +``` + +The interfaces a middleware must implement are found [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/05-port/types/module.go). + +```go +// Middleware implements the ICS26 Module interface +type Middleware interface { + IBCModule // middleware has access to an underlying application which may be wrapped by more middleware + ICS4Wrapper // middleware has access to ICS4Wrapper which may be core IBC Channel Handler or a higher-level middleware that wraps this middleware. +} +``` + +An `IBCMiddleware` struct implementing the `Middleware` interface, can be defined with its constructor as follows: + +```go +// @ x/module_name/ibc_middleware.go + +// IBCMiddleware implements the ICS26 callbacks and ICS4Wrapper for the fee middleware given the +// fee keeper and the underlying application. +type IBCMiddleware struct { + app porttypes.IBCModule + keeper keeper.Keeper +} + +// NewIBCMiddleware creates a new IBCMiddleware given the keeper and underlying application +func NewIBCMiddleware(app porttypes.IBCModule, k keeper.Keeper) IBCMiddleware { + return IBCMiddleware{ + app: app, + keeper: k, + } +} +``` + +## Implement `IBCModule` interface + +`IBCMiddleware` is a struct that implements the [ICS-26 `IBCModule` interface (`porttypes.IBCModule`)](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/05-port/types/module.go#L14-L107). It is recommended to separate these callbacks into a separate file `ibc_middleware.go`. + +> Note how this is analogous to implementing the same interfaces for IBC applications that act as base applications. + +As will be mentioned in the [integration section](03-integration.md), this struct should be different than the struct that implements `AppModule` in case the middleware maintains its own internal state and processes separate SDK messages. + +The middleware must have access to the underlying application, and be called before it during all ICS-26 callbacks. It may execute custom logic during these callbacks, and then call the underlying application's callback. + +> Middleware **may** choose not to call the underlying application's callback at all. Though these should generally be limited to error cases. + +The `IBCModule` interface consists of the channel handshake callbacks and packet callbacks. Most of the custom logic will be performed in the packet callbacks, in the case of the channel handshake callbacks, introducing the middleware requires consideration to the version negotiation. + +### Channel handshake callbacks + +#### Version negotiation + +In the case where the IBC middleware expects to speak to a compatible IBC middleware on the counterparty chain, they must use the channel handshake to negotiate the middleware version without interfering in the version negotiation of the underlying application. + +Middleware accomplishes this by formatting the version in a JSON-encoded string containing the middleware version and the application version. The application version may as well be a JSON-encoded string, possibly including further middleware and app versions, if the application stack consists of multiple milddlewares wrapping a base application. The format of the version is specified in ICS-30 as the following: + +```json +{ + "": "", + "app_version": "" +} +``` + +The `` key in the JSON struct should be replaced by the actual name of the key for the corresponding middleware (e.g. `fee_version`). + +During the handshake callbacks, the middleware can unmarshal the version string and retrieve the middleware and application versions. It can do its negotiation logic on ``, and pass the `` to the underlying application. + +> **NOTE**: Middleware that does not need to negotiate with a counterparty middleware on the remote stack will not implement the version unmarshalling and negotiation, and will simply perform its own custom logic on the callbacks without relying on the counterparty behaving similarly. + +#### `OnChanOpenInit` + +```go +func (im IBCMiddleware) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + if version != "" { + // try to unmarshal JSON-encoded version string and pass + // the app-specific version to app callback. + // otherwise, pass version directly to app callback. + metadata, err := Unmarshal(version) + if err != nil { + // Since it is valid for fee version to not be specified, + // the above middleware version may be for another middleware. + // Pass the entire version string onto the underlying application. + return im.app.OnChanOpenInit( + ctx, + order, + connectionHops, + portID, + channelID, + counterparty, + version, + ) + } + else { + metadata = { + // set middleware version to default value + MiddlewareVersion: defaultMiddlewareVersion, + // allow application to return its default version + AppVersion: "", + } + } + } + + doCustomLogic() + + // if the version string is empty, OnChanOpenInit is expected to return + // a default version string representing the version(s) it supports + appVersion, err := im.app.OnChanOpenInit( + ctx, + order, + connectionHops, + portID, + channelID, + counterparty, + metadata.AppVersion, // note we only pass app version here + ) + if err != nil { + return "", err + } + + version := constructVersion(metadata.MiddlewareVersion, appVersion) + + return version, nil +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L36-L83) an example implementation of this callback for the ICS-29 Fee Middleware module. + +#### `OnChanOpenTry` + +```go +func (im IBCMiddleware) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + // try to unmarshal JSON-encoded version string and pass + // the app-specific version to app callback. + // otherwise, pass version directly to app callback. + cpMetadata, err := Unmarshal(counterpartyVersion) + if err != nil { + return app.OnChanOpenTry( + ctx, + order, + connectionHops, + portID, + channelID, + counterparty, + counterpartyVersion, + ) + } + + doCustomLogic() + + // Call the underlying application's OnChanOpenTry callback. + // The try callback must select the final app-specific version string and return it. + appVersion, err := app.OnChanOpenTry( + ctx, + order, + connectionHops, + portID, + channelID, + counterparty, + cpMetadata.AppVersion, // note we only pass counterparty app version here + ) + if err != nil { + return "", err + } + + // negotiate final middleware version + middlewareVersion := negotiateMiddlewareVersion(cpMetadata.MiddlewareVersion) + version := constructVersion(middlewareVersion, appVersion) + + return version, nil +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L88-L125) an example implementation of this callback for the ICS-29 Fee Middleware module. + +#### `OnChanOpenAck` + +```go +func (im IBCMiddleware) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyChannelID string, + counterpartyVersion string, +) error { + // try to unmarshal JSON-encoded version string and pass + // the app-specific version to app callback. + // otherwise, pass version directly to app callback. + cpMetadata, err = UnmarshalJSON(counterpartyVersion) + if err != nil { + return app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) + } + + if !isCompatible(cpMetadata.MiddlewareVersion) { + return error + } + doCustomLogic() + + // call the underlying application's OnChanOpenTry callback + return app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, cpMetadata.AppVersion) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L128-L153)) an example implementation of this callback for the ICS-29 Fee Middleware module. + +#### `OnChanOpenConfirm` + +```go +func OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + doCustomLogic() + + return app.OnChanOpenConfirm(ctx, portID, channelID) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L156-L163) an example implementation of this callback for the ICS-29 Fee Middleware module. + +#### `OnChanCloseInit` + +```go +func OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + doCustomLogic() + + return app.OnChanCloseInit(ctx, portID, channelID) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L166-L188) an example implementation of this callback for the ICS-29 Fee Middleware module. + +#### `OnChanCloseConfirm` + +```go +func OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + doCustomLogic() + + return app.OnChanCloseConfirm(ctx, portID, channelID) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L191-L213) an example implementation of this callback for the ICS-29 Fee Middleware module. + +### Packet callbacks + +The packet callbacks just like the handshake callbacks wrap the application's packet callbacks. The packet callbacks are where the middleware performs most of its custom logic. The middleware may read the packet flow data and perform some additional packet handling, or it may modify the incoming data before it reaches the underlying application. This enables a wide degree of usecases, as a simple base application like token-transfer can be transformed for a variety of usecases by combining it with custom middleware. + +#### `OnRecvPacket` + +```go +func (im IBCMiddleware) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) ibcexported.Acknowledgement { + doCustomLogic(packet) + + ack := app.OnRecvPacket(ctx, packet, relayer) + + doCustomLogic(ack) // middleware may modify outgoing ack + + return ack +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L217-L238) an example implementation of this callback for the ICS-29 Fee Middleware module. + +#### `OnAcknowledgementPacket` + +```go +func (im IBCMiddleware) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + doCustomLogic(packet, ack) + + return app.OnAcknowledgementPacket(ctx, packet, ack, relayer) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L242-L293) an example implementation of this callback for the ICS-29 Fee Middleware module. + +#### `OnTimeoutPacket` + +```go +func (im IBCMiddleware) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + doCustomLogic(packet) + + return app.OnTimeoutPacket(ctx, packet, relayer) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L297-L335) an example implementation of this callback for the ICS-29 Fee Middleware module. + +## ICS-04 wrappers + +Middleware must also wrap ICS-04 so that any communication from the application to the `channelKeeper` goes through the middleware first. Similar to the packet callbacks, the middleware may modify outgoing acknowledgements and packets in any way it wishes. + +To ensure optimal generalisability, the `ICS4Wrapper` abstraction serves to abstract away whether a middleware is the topmost middleware (and thus directly calling into the ICS-04 `channelKeeper`) or itself being wrapped by another middleware. + +Remember that middleware can be stateful or stateless. When defining the stateful middleware's keeper, the `ics4Wrapper` field is included. Then the appropriate keeper can be passed when instantiating the middleware's keeper in `app.go` + +```go +type Keeper struct { + storeKey storetypes.StoreKey + cdc codec.BinaryCodec + + ics4Wrapper porttypes.ICS4Wrapper + channelKeeper types.ChannelKeeper + portKeeper types.PortKeeper + ... +} +``` + +For stateless middleware, the `ics4Wrapper` can be passed on directly without having to instantiate a keeper struct for the middleware. + +[The interface](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/05-port/types/module.go#L110-L133) looks as follows: + +```go +// This is implemented by ICS4 and all middleware that are wrapping base application. +// The base application will call `sendPacket` or `writeAcknowledgement` of the middleware directly above them +// which will call the next middleware until it reaches the core IBC handler. +type ICS4Wrapper interface { + SendPacket( + ctx sdk.Context, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + data []byte, + ) (sequence uint64, err error) + + WriteAcknowledgement( + ctx sdk.Context, + packet exported.PacketI, + ack exported.Acknowledgement, + ) error + + GetAppVersion( + ctx sdk.Context, + portID, + channelID string, + ) (string, bool) +} +``` + +:warning: In the following paragraphs, the methods are presented in pseudo code which has been kept general, not stating whether the middleware is stateful or stateless. Remember that when the middleware is stateful, `ics4Wrapper` can be accessed through the keeper. + +Check out the references provided for an actual implementation to clarify, where the `ics4Wrapper` methods in `ibc_middleware.go` simply call the equivalent keeper methods where the actual logic resides. + +### `SendPacket` + +```go +func SendPacket( + ctx sdk.Context, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + appData []byte, +) (uint64, error) { + // middleware may modify data + data = doCustomLogic(appData) + + return ics4Wrapper.SendPacket( + ctx, + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + data, + ) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/keeper/relay.go#L17-L27) an example implementation of this function for the ICS-29 Fee Middleware module. + +### `WriteAcknowledgement` + +```go +// only called for async acks +func WriteAcknowledgement( + ctx sdk.Context, + packet exported.PacketI, + ack exported.Acknowledgement, +) error { + // middleware may modify acknowledgement + ack_bytes = doCustomLogic(ack) + + return ics4Wrapper.WriteAcknowledgement(packet, ack_bytes) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/keeper/relay.go#L31-L55) an example implementation of this function for the ICS-29 Fee Middleware module. + +### `GetAppVersion` + +```go +// middleware must return the underlying application version +func GetAppVersion( + ctx sdk.Context, + portID, + channelID string, +) (string, bool) { + version, found := ics4Wrapper.GetAppVersion(ctx, portID, channelID) + if !found { + return "", false + } + + if !MiddlewareEnabled { + return version, true + } + + // unwrap channel version + metadata, err := Unmarshal(version) + if err != nil { + panic(fmt.Errof("unable to unmarshal version: %w", err)) + } + + return metadata.AppVersion, true +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/keeper/relay.go#L58-L74) an example implementation of this function for the ICS-29 Fee Middleware module. diff --git a/docs/docs/01-ibc/04-middleware/02-developIBCv2.md b/docs/docs/01-ibc/04-middleware/02-developIBCv2.md new file mode 100644 index 0000000..94e2e44 --- /dev/null +++ b/docs/docs/01-ibc/04-middleware/02-developIBCv2.md @@ -0,0 +1,260 @@ +--- +title: Create and integrate IBC v2 middleware +sidebar_label: Create and integrate IBC v2 middleware +sidebar_position: 2 +slug: /ibc/middleware/developIBCv2 +--- + +# Quick Navigation + +1. [Create a custom IBC v2 middleware](#create-a-custom-ibc-v2-middleware) +2. [Implement `IBCModule` interface](#implement-ibcmodule-interface) +3. [WriteAckWrapper](#writeackwrapper) +4. [Integrate IBC v2 Middleware](#integrate-ibc-v2-middleware) +5. [Security Model](#security-model) +6. [Design Principles](#design-principles) + +## Create a custom IBC v2 middleware + +IBC middleware will wrap over an underlying IBC application (a base application or downstream middleware) and sits between core IBC and the base application. + +:::warning +middleware developers must use the same serialization and deserialization method as in ibc-go's codec: transfertypes.ModuleCdc.[Must]MarshalJSON +::: + +For middleware builders this means: + +```go +import transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +transfertypes.ModuleCdc.[Must]MarshalJSON +func MarshalAsIBCDoes(ack channeltypes.Acknowledgement) ([]byte, error) { + return transfertypes.ModuleCdc.MarshalJSON(&ack) +} +``` + +The interfaces a middleware must implement are found in [core/api](https://github.com/cosmos/ibc-go/blob/main/modules/core/api/module.go#L11). Note that this interface has changed from IBC classic. + +An `IBCMiddleware` struct implementing the `Middleware` interface, can be defined with its constructor as follows: + +```go +// @ x/module_name/ibc_middleware.go + +// IBCMiddleware implements the IBCv2 middleware interface +type IBCMiddleware struct { + app api.IBCModule // underlying app or middleware + writeAckWrapper api. WriteAcknowledgementWrapper // writes acknowledgement for an async acknowledgement + PacketDataUnmarshaler api.PacketDataUnmarshaler // optional interface + keeper types.Keeper // required for stateful middleware + // Keeper may include middleware specific keeper and the ChannelKeeperV2 + + // additional middleware specific fields +} + +// NewIBCMiddleware creates a new IBCMiddleware given the keeper and underlying application +func NewIBCMiddleware(app api.IBCModule, +writeAckWrapper api.WriteAcknowledgementWrapper, +k types.Keeper +) IBCMiddleware { + return IBCMiddleware{ + app: app, + writeAckWrapper: writeAckWrapper, + keeper: k, + } +} +``` + +:::note +The ICS4Wrapper has been removed in IBC v2 and there are no channel handshake callbacks, a writeAckWrapper has been added to the interface +::: + +## Implement `IBCModule` interface + +`IBCMiddleware` is a struct that implements the [`IBCModule` interface (`api.IBCModule`)](https://github.com/cosmos/ibc-go/blob/main/modules/core/api/module.go#L11-L53). It is recommended to separate these callbacks into a separate file `ibc_middleware.go`. + +> Note how this is analogous to implementing the same interfaces for IBC applications that act as base applications. + +The middleware must have access to the underlying application, and be called before it during all ICS-26 callbacks. It may execute custom logic during these callbacks, and then call the underlying application's callback. + +> Middleware **may** choose not to call the underlying application's callback at all. Though these should generally be limited to error cases. + +The `IBCModule` interface consists of the packet callbacks where cutom logic is performed. + +### Packet callbacks + +The packet callbacks are where the middleware performs most of its custom logic. The middleware may read the packet flow data and perform some additional packet handling, or it may modify the incoming data before it reaches the underlying application. This enables a wide degree of usecases, as a simple base application like token-transfer can be transformed for a variety of usecases by combining it with custom middleware, for example acting as a filter for which tokens can be sent and recieved. + +#### `OnRecvPacket` + +```go +func (im IBCMiddleware) OnRecvPacket( + ctx sdk.Context, + sourceClient string, + destinationClient string, + sequence uint64, + payload channeltypesv2.Payload, + relayer sdk.AccAddress, +) channeltypesv2.RecvPacketResult { + // Middleware may choose to do custom preprocessing logic before calling the underlying app OnRecvPacket + // Middleware may choose to error early and return a RecvPacketResult Failure + // Middleware may choose to modify the payload before passing on to OnRecvPacket though this + // should only be done to support very advanced custom behavior + // Middleware MUST NOT modify client identifiers and sequence + doCustomPreProcessLogic() + + // call underlying app OnRecvPacket + recvResult := im.app.OnRecvPacket(ctx, sourceClient, destinationClient, sequence, payload, relayer) + if recvResult.Status == PACKET_STATUS_FAILURE { + return recvResult + } + + doCustomPostProcessLogic(recvResult) // middleware may modify recvResult + + return recvResult +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/v2/ibc_middleware.go#L161-L230) an example implementation of this callback for the Callbacks Middleware module. + +#### `OnAcknowledgementPacket` + +```go +func (im IBCMiddleware) OnAcknowledgementPacket( + ctx sdk.Context, + sourceClient string, + destinationClient string, + sequence uint64, + acknowledgement []byte, + payload channeltypesv2.Payload, + relayer sdk.AccAddress, +) error { + // preprocessing logic may modify the acknowledgement before passing to + // the underlying app though this should only be done in advanced cases + // Middleware may return error early + // it MUST NOT change the identifiers of the clients or the sequence + doCustomPreProcessLogic(payload, acknowledgement) + + // call underlying app OnAcknowledgementPacket + err = im.app.OnAcknowledgementPacket( + sourceClient, destinationClient, sequence, + acknowledgement, payload, relayer + ) + if err != nil { + return err + } + + // may perform some post acknowledgement logic and return error here + return doCustomPostProcessLogic() +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/v2/ibc_middleware.go#L236-L302) an example implementation of this callback for the Callbacks Middleware module. + +#### `OnTimeoutPacket` + +```go +func (im IBCMiddleware) OnTimeoutPacket( + ctx sdk.Context, + sourceClient string, + destinationClient string, + sequence uint64, + payload channeltypesv2.Payload, + relayer sdk.AccAddress, +) error { + // Middleware may choose to do custom preprocessing logic before calling the underlying app OnTimeoutPacket + // Middleware may return error early + doCustomPreProcessLogic(payload) + + // call underlying app OnTimeoutPacket + err = im.app.OnTimeoutPacket( + sourceClient, destinationClient, sequence, + payload, relayer + ) + if err != nil { + return err + } + + // may perform some post timeout logic and return error here + return doCustomPostProcessLogic() +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/v2/ibc_middleware.go#L309-L367) an example implementation of this callback for the Callbacks Middleware module. + +### WriteAckWrapper + +Middleware must also wrap the `WriteAcknowledgement` interface so that any acknowledgement written by the application passes through the middleware first. This allows middleware to modify or delay writing an acknowledgment before committed to the IBC store. + +```go +// WithWriteAckWrapper sets the WriteAcknowledgementWrapper for the middleware. +func (im *IBCMiddleware) WithWriteAckWrapper(writeAckWrapper api.WriteAcknowledgementWrapper) { + im.writeAckWrapper = writeAckWrapper +} + +// GetWriteAckWrapper returns the WriteAckWrapper +func (im *IBCMiddleware) GetWriteAckWrapper() api.WriteAcknowledgementWrapper { + return im.writeAckWrapper +} +``` + +### `WriteAcknowledgement` + +This is where the middleware acknowledgement handling is finalised. An example is shown in the [callbacks middleware](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/v2/ibc_middleware.go#L369-L454) + +```go +// WriteAcknowledgement facilitates acknowledgment being written asynchronously +// The call stack flows from the IBC application to the IBC core handler +// Thus this function is called by the IBC app or a lower-level middleware +func (im IBCMiddleware) WriteAcknowledgement( + ctx sdk.Context, + clientID string, + sequence uint64, + ack channeltypesv2.Acknowledgement, +) error { + doCustomPreProcessLogic() // may modify acknowledgement + + return im.writeAckWrapper.WriteAcknowledgement( + ctx, clientId, sequence, ack, + ) +} +``` + +## Integrate IBC v2 Middleware + +Middleware should be registered within the module manager in `app.go`. + +The order of middleware **matters**, function calls from IBC to the application travel from top-level middleware to the bottom middleware and then to the application. Function calls from the application to IBC goes through the bottom middleware in order to the top middleware and then to core IBC handlers. Thus the same set of middleware put in different orders may produce different effects. + +### Example Integration + +The example integration is detailed for an IBC v2 stack using transfer and the callbacks middleware. + +```go +// Middleware Stacks +// initialising callbacks middleware + maxCallbackGas := uint64(10_000_000) + wasmStackIBCHandler := wasm.NewIBCHandler(app.WasmKeeper, app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper) + +// Create the transferv2 stack with transfer and callbacks middleware + var ibcv2TransferStack ibcapi.IBCModule + ibcv2TransferStack = transferv2.NewIBCModule(app.TransferKeeper) + ibcv2TransferStack = ibccallbacksv2.NewIBCMiddleware(transferv2.NewIBCModule(app.TransferKeeper), app.IBCKeeper.ChannelKeeperV2, wasmStackIBCHandler, app.IBCKeeper.ChannelKeeperV2, maxCallbackGas) + +// Create static IBC v2 router, add app routes, then set and seal it + ibcRouterV2 := ibcapi.NewRouter() + ibcRouterV2.AddRoute(ibctransfertypes.PortID, ibcv2TransferStack) + app.IBCKeeper.SetRouterV2(ibcRouterV2) +``` + +## Security Model + +IBC Middleware completely wraps all communication between IBC core and the application that it is wired with. Thus, the IBC Middleware has complete control to modify any packets and acknowledgements the underlying application receives or sends. Thus, if a chain chooses to wrap an application with a given middleware, that middleware is **completely trusted** and part of the application's security model. **Do not use middlewares that are untrusted.** + +## Design Principles + +The middleware follows a decorator pattern that wraps an underlying application's connection to the IBC core handlers. Thus, when implementing a middleware for a specific purpose, it is recommended to be as **unintrusive** as possible in the middleware design while still accomplishing the intended behavior. + +The least intrusive middleware is stateless. They simply read the ICS26 callback arguments before calling the underlying app's callback and error if the arguments are not acceptable (e.g. whitelisting packets). Stateful middleware that are used solely for erroring are also very simple to build, an example of this would be a rate-limiting middleware that prevents transfer outflows from getting too high within a certain time frame. + +Middleware that directly interfere with the payload or acknowledgement before passing control to the underlying app are way more intrusive to the underyling app processing. This makes such middleware more error-prone when implementing as incorrect handling can cause the underlying app to break or worse execute unexpected behavior. Moreover, such middleware typically needs to be built for a specific underlying app rather than being generic. An example of this is the packet-forwarding middleware which modifies the payload and is specifically built for transfer. + +Middleware that modifies the payload or acknowledgement such that it is no longer readable by the underlying application is the most complicated middleware. Since it is not readable by the underlying apps, if these middleware write additional state into payloads and acknowledgements that get committed to IBC core provable state, there MUST be an equivalent counterparty middleware that is able to parse and intepret this additional state while also converting the payload and acknowledgment back to a readable form for the underlying application on its side. Thus, such middleware requires deployment on both sides of an IBC connection or the packet processing will break. This is the hardest type of middleware to implement, integrate and deploy. Thus, it is not recommended unless absolutely necessary to fulfill the given use case. diff --git a/docs/docs/01-ibc/04-middleware/03-integration.md b/docs/docs/01-ibc/04-middleware/03-integration.md new file mode 100644 index 0000000..5ceff37 --- /dev/null +++ b/docs/docs/01-ibc/04-middleware/03-integration.md @@ -0,0 +1,72 @@ +--- +title: Integrating IBC middleware into a chain +sidebar_label: Integrating IBC middleware into a chain +sidebar_position: 4 +slug: /ibc/middleware/integration +--- + + +# Integrating IBC middleware into a chain + +Learn how to integrate IBC middleware(s) with a base application to your chain. The following document only applies for Cosmos SDK chains. + +If the middleware is maintaining its own state and/or processing SDK messages, then it should create and register its SDK module with the module manager in `app.go`. + +All middleware must be connected to the IBC router and wrap over an underlying base IBC application. An IBC application may be wrapped by many layers of middleware, only the top layer middleware should be hooked to the IBC router, with all underlying middlewares and application getting wrapped by it. + +The order of middleware **matters**, function calls from IBC to the application travel from top-level middleware to the bottom middleware and then to the application. Function calls from the application to IBC goes through the bottom middleware in order to the top middleware and then to core IBC handlers. Thus the same set of middleware put in different orders may produce different effects. + +## Example integration + +```go +// app.go pseudocode + + + +// middleware 1 and middleware 3 are stateful middleware, +// perhaps implementing separate sdk.Msg and Handlers +mw1Keeper := mw1.NewKeeper(storeKey1, ..., ics4Wrapper: channelKeeper, ...) // in stack 1 & 3 +// middleware 2 is stateless +mw3Keeper1 := mw3.NewKeeper(storeKey3,..., ics4Wrapper: mw1Keeper, ...) // in stack 1 +mw3Keeper2 := mw3.NewKeeper(storeKey3,..., ics4Wrapper: channelKeeper, ...) // in stack 2 + +// Only create App Module **once** and register in app module +// if the module maintains independent state and/or processes sdk.Msgs +app.moduleManager = module.NewManager( + ... + mw1.NewAppModule(mw1Keeper), + mw3.NewAppModule(mw3Keeper1), + mw3.NewAppModule(mw3Keeper2), + transfer.NewAppModule(transferKeeper), + custom.NewAppModule(customKeeper) +) + +// NOTE: IBC Modules may be initialized any number of times provided they use a separate +// Keeper and underlying port. + +customKeeper1 := custom.NewKeeper(..., KeeperCustom1, ...) +customKeeper2 := custom.NewKeeper(..., KeeperCustom2, ...) + +// initialize base IBC applications +// if you want to create two different stacks with the same base application, +// they must be given different Keepers and assigned different ports. +transferIBCModule := transfer.NewIBCModule(transferKeeper) +customIBCModule1 := custom.NewIBCModule(customKeeper1, "portCustom1") +customIBCModule2 := custom.NewIBCModule(customKeeper2, "portCustom2") + +// create IBC stacks by combining middleware with base application +// NOTE: since middleware2 is stateless it does not require a Keeper +// stack 1 contains mw1 -> mw3 -> transfer +stack1 := mw1.NewIBCMiddleware(mw3.NewIBCMiddleware(transferIBCModule, mw3Keeper1), mw1Keeper) +// stack 2 contains mw3 -> mw2 -> custom1 +stack2 := mw3.NewIBCMiddleware(mw2.NewIBCMiddleware(customIBCModule1), mw3Keeper2) +// stack 3 contains mw2 -> mw1 -> custom2 +stack3 := mw2.NewIBCMiddleware(mw1.NewIBCMiddleware(customIBCModule2, mw1Keeper)) + +// associate each stack with the moduleName provided by the underlying Keeper +ibcRouter := porttypes.NewRouter() +ibcRouter.AddRoute("transfer", stack1) +ibcRouter.AddRoute("custom1", stack2) +ibcRouter.AddRoute("custom2", stack3) +app.IBCKeeper.SetRouter(ibcRouter) +``` diff --git a/docs/docs/01-ibc/04-middleware/_category_.json b/docs/docs/01-ibc/04-middleware/_category_.json new file mode 100644 index 0000000..ec27d4e --- /dev/null +++ b/docs/docs/01-ibc/04-middleware/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Middleware", + "position": 4, + "link": null +} \ No newline at end of file diff --git a/docs/docs/01-ibc/04-middleware/images/middleware-stack.png b/docs/docs/01-ibc/04-middleware/images/middleware-stack.png new file mode 100644 index 0000000..ed5852f Binary files /dev/null and b/docs/docs/01-ibc/04-middleware/images/middleware-stack.png differ diff --git a/docs/docs/01-ibc/05-upgrades/00-intro.md b/docs/docs/01-ibc/05-upgrades/00-intro.md new file mode 100644 index 0000000..d7bb473 --- /dev/null +++ b/docs/docs/01-ibc/05-upgrades/00-intro.md @@ -0,0 +1,15 @@ +--- +title: Upgrading IBC Chains Overview +sidebar_label: Overview +sidebar_position: 0 +slug: /ibc/upgrades/intro +--- + +# Upgrading IBC Chains Overview + +This directory contains information on how to upgrade an IBC chain without breaking counterparty clients and connections. + +IBC-connected chains must be able to upgrade without breaking connections to other chains. Otherwise there would be a massive disincentive towards upgrading and disrupting high-value IBC connections, thus preventing chains in the IBC ecosystem from evolving and improving. Many chain upgrades may be irrelevant to IBC, however some upgrades could potentially break counterparty clients if not handled correctly. Thus, any IBC chain that wishes to perform an IBC-client-breaking upgrade must perform an IBC upgrade in order to allow counterparty clients to securely upgrade to the new light client. + +1. The [quick-guide](./01-quick-guide.md) describes how IBC-connected chains can perform client-breaking upgrades and how relayers can securely upgrade counterparty clients using the SDK. +2. The [developer-guide](./02-developer-guide.md) is a guide for developers intending to develop IBC client implementations with upgrade functionality. diff --git a/docs/docs/01-ibc/05-upgrades/01-quick-guide.md b/docs/docs/01-ibc/05-upgrades/01-quick-guide.md new file mode 100644 index 0000000..23448f7 --- /dev/null +++ b/docs/docs/01-ibc/05-upgrades/01-quick-guide.md @@ -0,0 +1,60 @@ +--- +title: How to Upgrade IBC Chains and their Clients +sidebar_label: How to Upgrade IBC Chains and their Clients +sidebar_position: 1 +slug: /ibc/upgrades/quick-guide +--- + + +# How to Upgrade IBC Chains and their Clients + +:::note Synopsis +Learn how to upgrade your chain and counterparty clients. +::: + +The information in this doc for upgrading chains is relevant to SDK chains. However, the guide for counterparty clients is relevant to any Tendermint client that enables upgrades. + +## IBC Client Breaking Upgrades + +IBC-connected chains must perform an IBC upgrade if their upgrade will break counterparty IBC clients. The current IBC protocol supports upgrading tendermint chains for a specific subset of IBC-client-breaking upgrades. Here is the exhaustive list of IBC client-breaking upgrades and whether the IBC protocol currently supports such upgrades. + +IBC currently does **NOT** support unplanned upgrades. All of the following upgrades must be planned and committed to in advance by the upgrading chain, in order for counterparty clients to maintain their connections securely. + +Note: Since upgrades are only implemented for Tendermint clients, this doc only discusses upgrades on Tendermint chains that would break counterparty IBC Tendermint Clients. + +1. Changing the Chain-ID: **Supported** +2. Changing the UnbondingPeriod: **Partially Supported**, chains may increase the unbonding period with no issues. However, decreasing the unbonding period may irreversibly break some counterparty clients. Thus, it is **not recommended** that chains reduce the unbonding period. +3. Changing the height (resetting to 0): **Supported**, so long as chains remember to increment the revision number in their chain-id. +4. Changing the ProofSpecs: **Supported**, this should be changed if the proof structure needed to verify IBC proofs is changed across the upgrade. Ex: Switching from an IAVL store, to a SimpleTree Store +5. Changing the UpgradePath: **Supported**, this might involve changing the key under which upgraded clients and consensus states are stored in the upgrade store, or even migrating the upgrade store itself. +6. Migrating the IBC store: **Unsupported**, as the IBC store location is negotiated by the connection. +7. Upgrading to a backwards compatible version of IBC: Supported +8. Upgrading to a non-backwards compatible version of IBC: **Unsupported**, as IBC version is negotiated on connection handshake. +9. Changing the Tendermint LightClient algorithm: **Partially Supported**. Changes to the light client algorithm that do not change the ClientState or ConsensusState struct may be supported, provided that the counterparty is also upgraded to support the new light client algorithm. Changes that require updating the ClientState and ConsensusState structs themselves are theoretically possible by providing a path to translate an older ClientState struct into the new ClientState struct; however this is not currently implemented. + +## Step-by-Step Upgrade Process for SDK chains + +If the IBC-connected chain is conducting an upgrade that will break counterparty clients, it must ensure that the upgrade is first supported by IBC using the list above and then execute the upgrade process described below in order to prevent counterparty clients from breaking. + +1. Create a governance proposal with the [`MsgIBCSoftwareUpgrade`](https://buf.build/cosmos/ibc/docs/main:ibc.core.client.v1#ibc.core.client.v1.MsgIBCSoftwareUpgrade) message which contains an `UpgradePlan` and a new IBC `ClientState` in the `UpgradedClientState` field. Note that the `UpgradePlan` must specify an upgrade height **only** (no upgrade time), and the `ClientState` should only include the fields common to all valid clients (chain-specified parameters) and zero out any client-customizable fields (such as `TrustingPeriod`). +2. Vote on and pass the governance proposal. + +Upon passing the governance proposal, the upgrade module will commit the `UpgradedClient` under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedClient`. On the block right before the upgrade height, the upgrade module will also commit an initial consensus state for the next chain under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedConsState`. + +Once the chain reaches the upgrade height and halts, a relayer can upgrade the counterparty clients to the last block of the old chain. They can then submit the proofs of the `UpgradedClient` and `UpgradedConsensusState` against this last block and upgrade the counterparty client. + +## Step-by-Step Upgrade Process for Relayers Upgrading Counterparty Clients + +Once the upgrading chain has committed to upgrading, relayers must wait till the chain halts at the upgrade height before upgrading counterparty clients. This is because chains may reschedule or cancel upgrade plans before they occur. Thus, relayers must wait till the chain reaches the upgrade height and halts before they can be sure the upgrade will take place. + +Thus, the upgrade process for relayers trying to upgrade the counterparty clients is as follows: + +1. Wait for the upgrading chain to reach the upgrade height and halt +2. Query a full node for the proofs of `UpgradedClient` and `UpgradedConsensusState` at the last height of the old chain. +3. Update the counterparty client to the last height of the old chain using the `UpdateClient` msg. +4. Submit an `UpgradeClient` msg to the counterparty chain with the `UpgradedClient`, `UpgradedConsensusState` and their respective proofs. +5. Submit an `UpdateClient` msg to the counterparty chain with a header from the new upgraded chain. + +The Tendermint client on the counterparty chain will verify that the upgrading chain did indeed commit to the upgraded client and upgraded consensus state at the upgrade height (since the upgrade height is included in the key). If the proofs are verified against the upgrade height, then the client will upgrade to the new client while retaining all of its client-customized fields. Thus, it will retain its old TrustingPeriod, TrustLevel, MaxClockDrift, etc; while adopting the new chain-specified fields such as UnbondingPeriod, ChainId, UpgradePath, etc. Note, this can lead to an invalid client since the old client-chosen fields may no longer be valid given the new chain-chosen fields. Upgrading chains should try to avoid these situations by not altering parameters that can break old clients. For an example, see the UnbondingPeriod example in the supported upgrades section. + +The upgraded consensus state will serve purely as a basis of trust for future `UpdateClientMsgs` and will not contain a consensus root to perform proof verification against. Thus, relayers must submit an `UpdateClientMsg` with a header from the new chain so that the connection can be used for proof verification again. diff --git a/docs/docs/01-ibc/05-upgrades/02-developer-guide.md b/docs/docs/01-ibc/05-upgrades/02-developer-guide.md new file mode 100644 index 0000000..c55b698 --- /dev/null +++ b/docs/docs/01-ibc/05-upgrades/02-developer-guide.md @@ -0,0 +1,14 @@ +--- +title: IBC Client Developer Guide to Upgrades +sidebar_label: IBC Client Developer Guide to Upgrades +sidebar_position: 2 +slug: /ibc/upgrades/developer-guide +--- + +# IBC Client Developer Guide to Upgrades + +:::note Synopsis +Learn how to implement upgrade functionality for your custom IBC client. +::: + +Please see the section [Handling upgrades](../../03-light-clients/01-developer-guide/06-upgrades.md) from the light client developer guide for more information. diff --git a/docs/docs/01-ibc/05-upgrades/03-genesis-restart.md b/docs/docs/01-ibc/05-upgrades/03-genesis-restart.md new file mode 100644 index 0000000..8f1d612 --- /dev/null +++ b/docs/docs/01-ibc/05-upgrades/03-genesis-restart.md @@ -0,0 +1,52 @@ +--- +title: Genesis Restart Upgrades +sidebar_label: Genesis Restart Upgrades +sidebar_position: 3 +slug: /ibc/upgrades/genesis-restart +--- + + +# Genesis Restart Upgrades + +:::note Synopsis +Learn how to upgrade your chain and counterparty clients using genesis restarts. +::: + +**NOTE**: Regular genesis restarts are currently unsupported by relayers! + +## IBC Client Breaking Upgrades + +IBC client breaking upgrades are possible using genesis restarts. +It is highly recommended to use the in-place migrations instead of a genesis restart. +Genesis restarts should be used sparingly and as backup plans. + +Genesis restarts still require the usage of an IBC upgrade proposal in order to correctly upgrade counterparty clients. + +### Step-by-Step Upgrade Process for SDK Chains + +If the IBC-connected chain is conducting an upgrade that will break counterparty clients, it must ensure that the upgrade is first supported by IBC using the [IBC Client Breaking Upgrade List](./01-quick-guide.md#ibc-client-breaking-upgrades) and then execute the upgrade process described below in order to prevent counterparty clients from breaking. + +1. Create a governance proposal with the [`MsgIBCSoftwareUpgrade`](https://buf.build/cosmos/ibc/docs/main:ibc.core.client.v1#ibc.core.client.v1.MsgIBCSoftwareUpgrade) which contains an `UpgradePlan` and a new IBC `ClientState` in the `UpgradedClientState` field. Note that the `UpgradePlan` must specify an upgrade height **only** (no upgrade time), and the `ClientState` should only include the fields common to all valid clients and zero out any client-customizable fields (such as `TrustingPeriod`). +2. Vote on and pass the governance proposal. +3. Halt the node after successful upgrade. +4. Export the genesis file. +5. Swap to the new binary. +6. Run migrations on the genesis file. +7. Remove the upgrade plan set by the governance proposal from the genesis file. This may be done by migrations. +8. Change desired chain-specific fields (chain id, unbonding period, etc). This may be done by migrations. +8. Reset the node's data. +9. Start the chain. + +Upon passing the governance proposal, the upgrade module will commit the `UpgradedClient` under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedClient`. On the block right before the upgrade height, the upgrade module will also commit an initial consensus state for the next chain under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedConsState`. + +Once the chain reaches the upgrade height and halts, a relayer can upgrade the counterparty clients to the last block of the old chain. They can then submit the proofs of the `UpgradedClient` and `UpgradedConsensusState` against this last block and upgrade the counterparty client. + +### Step-by-Step Upgrade Process for Relayers Upgrading Counterparty Clients + +These steps are identical to the regular [IBC client breaking upgrade process](./01-quick-guide.md#step-by-step-upgrade-process-for-relayers-upgrading-counterparty-clients). + +## Non-IBC Client Breaking Upgrades + +While ibc-go supports genesis restarts which do not break IBC clients, relayers do not support this upgrade path. +Here is a tracking issue on [Hermes](https://github.com/informalsystems/ibc-rs/issues/1152). +Please do not attempt a regular genesis restarts unless you have a tool to update counterparty clients correctly. diff --git a/docs/docs/01-ibc/05-upgrades/_category_.json b/docs/docs/01-ibc/05-upgrades/_category_.json new file mode 100644 index 0000000..c2db1ee --- /dev/null +++ b/docs/docs/01-ibc/05-upgrades/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Upgrades", + "position": 5, + "link": { "type": "doc", "id": "intro" } +} diff --git a/docs/docs/01-ibc/07-relayer.md b/docs/docs/01-ibc/07-relayer.md new file mode 100644 index 0000000..035934c --- /dev/null +++ b/docs/docs/01-ibc/07-relayer.md @@ -0,0 +1,53 @@ +--- +title: Relayer +sidebar_label: Relayer +sidebar_position: 7 +slug: /ibc/relayer +--- + +# Relayer + +:::note + +## Pre-requisite readings + +- [IBC Overview](01-overview.md) +- [Events](https://docs.cosmos.network/v0.47/learn/advanced/events) + +::: + +## Events + +Events are emitted for every transaction processed by the base application to indicate the execution +of some logic clients may want to be aware of. This is extremely useful when relaying IBC packets. +Any message that uses IBC will emit events for the corresponding TAO logic executed as defined in +the [IBC events document](/events/events). + +In the SDK, it can be assumed that for every message there is an event emitted with the type `message`, +attribute key `action`, and an attribute value representing the type of message sent +(`channel_open_init` would be the attribute value for `MsgChannelOpenInit`). If a relayer queries +for transaction events, it can split message events using this event Type/Attribute Key pair. + +The Event Type `message` with the Attribute Key `module` may be emitted multiple times for a single +message due to application callbacks. It can be assumed that any TAO logic executed will result in +a module event emission with the attribute value `ibc_` (02-client emits `ibc_client`). + +### Subscribing with Tendermint + +Calling the Tendermint RPC method `Subscribe` via Tendermint's Websocket will return events using +Tendermint's internal representation of them. Instead of receiving back a list of events as they +were emitted, Tendermint will return the type `map[string][]string` which maps a string in the +form `.` to `attribute_value`. This causes extraction of the event +ordering to be non-trivial, but still possible. + +A relayer should use the `message.action` key to extract the number of messages in the transaction +and the type of IBC transactions sent. For every IBC transaction within the string array for +`message.action`, the necessary information should be extracted from the other event fields. If +`send_packet` appears at index 2 in the value for `message.action`, a relayer will need to use the +value at index 2 of the key `send_packet.packet_sequence`. This process should be repeated for each +piece of information needed to relay a packet. + +## Example Implementations + +- [Golang Relayer](https://github.com/cosmos/relayer) +- [Hermes](https://github.com/informalsystems/hermes) diff --git a/docs/docs/01-ibc/08-best-practices.md b/docs/docs/01-ibc/08-best-practices.md new file mode 100644 index 0000000..77cba9b --- /dev/null +++ b/docs/docs/01-ibc/08-best-practices.md @@ -0,0 +1,25 @@ +--- +title: Best Practices +sidebar_label: Best Practices +sidebar_position: 8 +slug: /ibc/best-practices +--- + +# Best practices + +## Identifying legitimate channels + +Identifying which channel to use can be difficult as it requires verifying information about the chains you want to connect to. +Channels are based on a light client. A chain can be uniquely identified by its chain ID, validator set pairing. It is unsafe to rely only on the chain ID. +Any user can create a client with any chain ID, but only the chain with correct validator set and chain ID can produce headers which would update that client. + +Which channel to use is based on social consensus. The desired channel should have the following properties: + +- based on a valid client (can only be updated by the chain it connects to) +- has sizable activity +- the underlying client is active + +To verify if a client is valid. You will need to obtain a header from the chain you want to connect to. This can be done by running a full node for that chain or relying on a trusted rpc address. +Then you should query the light client you want to verify and obtain its latest consensus state. All consensus state fields must match the header queried for at same height as the consensus state (root, timestamp, next validator set hash). + +Explorers and wallets are highly encouraged to follow this practice. It is unsafe to algorithmically add new channels without following this process. diff --git a/docs/docs/01-ibc/09-permissioning.md b/docs/docs/01-ibc/09-permissioning.md new file mode 100644 index 0000000..edfbdfc --- /dev/null +++ b/docs/docs/01-ibc/09-permissioning.md @@ -0,0 +1,32 @@ +--- +title: Permissioning +sidebar_label: Permissioning +sidebar_position: 9 +slug: /ibc/permissioning +--- + +# Permissioning + +IBC is designed at its base level to be a permissionless protocol. This does not mean that chains cannot add in permissioning on top of IBC. In ibc-go this can be accomplished by implementing and wiring an ante-decorator that checks if the IBC message is signed by a permissioned authority. If the signer address check passes, the tx can go through; otherwise it is rejected from the mempool. + +The antehandler runs before message-processing so it acts as a customizable filter that can reject messages before they get included in the block. The Cosmos SDK allows developers to write ante-decorators that can be stacked with others to add multiple independent customizable filters that run in sequence. Thus, chain developers that want to permission IBC messages are advised to implement their own custom permissioned IBC ante-decorator to add to the standard ante-decorator stack. + +## Best practices + +`MsgCreateClient`: permissioning the client creation is the most important for permissioned IBC. This will prevent malicious relayers from creating clients to fake chains. If a chain wants to control which chains are connected to it directly over IBC, the best way to do this is by controlling which clients get created. The permissioned authority can create clients only of counterparties that the chain approves of. The permissioned authority can be the governance account, however `MsgCreateClient` contains a consensus state that can be expired by the time governance passes the proposal to execute the message. Thus, if the voting period is longer than the unbonding period of the counterparty, it is advised to use a permissioned authority that can immediately execute the transaction (e.g. a trusted multisig). + +`MsgConnectionOpenInit`: permissioning this message will give the chain control over the connections that are opened and also will control which connection identifier is associated with which counterparty. + +`MsgConnectionOpenTry`: permissioning this message through a permissioned address check is ill-advised because it will prevent relayers from easily completing the handshake that was initialized on the counterparty. However, if the chain does want strict control of exactly which connections are opened, it can permission this message. Be aware, if two chains with strict permissions try to open a connection it may take much longer than expected. + +`MsgChannelOpenInit`: permissioning this message will give the chain control over the channels that are opened and also will control which channel identifier is associated with which counterparty. + +`MsgChannelOpenTry`: permissioning this message through a permissioned address check is ill-advised because it will prevent relayers from easily completing the handshake that was initialized on the counterparty. However, if the chain does want strict control of exactly which channels are opened, it can permission this message. Be aware, if two chains with strict permissions try to open a channel it may take much longer than expected. + +It is not advised to permission any other message from ibc-go. Permissionless relayers should still be allowed to complete handshakes that were authorized by permissioned parties, and to relay user packets on channels that were also authorized by permissioned parties. This provides the maximum liveness provided by a permissionless relayer network with the safety guarantees provided by permissioned client, connection, and channel creation. + +## Genesis setup + +Chains that are starting up from genesis have the option of initializing authorized clients, connections and channels from genesis. This allows chains to automatically connect to desired chains with a desired identifier. + +Note: The chain must be launched soon after the genesis file is created so that the client creation does not occur with an expired consensus state. The connections and channels must also simply have their `INIT` messages executed so that relayers can complete the rest of the handshake. diff --git a/docs/docs/01-ibc/_category_.json b/docs/docs/01-ibc/_category_.json new file mode 100644 index 0000000..afc6d18 --- /dev/null +++ b/docs/docs/01-ibc/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Using IBC-Go", + "position": 1, + "link": null +} \ No newline at end of file diff --git a/docs/docs/02-apps/01-transfer/01-overview.md b/docs/docs/02-apps/01-transfer/01-overview.md new file mode 100644 index 0000000..b731368 --- /dev/null +++ b/docs/docs/02-apps/01-transfer/01-overview.md @@ -0,0 +1,142 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /apps/transfer/ics20-v1/overview +--- + +# Overview + +:::note Synopsis +Learn about what the token Transfer module is +::: + +## What is the Transfer module? + +Transfer is the Cosmos SDK implementation of the [ICS-20](https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer) protocol, which enables cross-chain fungible token transfers. + +## Concepts + +### Acknowledgements + +ICS20 uses the recommended acknowledgement format as specified by [ICS 04](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope). + +A successful receive of a transfer packet will result in a Result Acknowledgement being written +with the value `[]byte{byte(1)}` in the `Response` field. + +An unsuccessful receive of a transfer packet will result in an Error Acknowledgement being written +with the error message in the `Response` field. + +### Denomination trace + +The denomination trace corresponds to the information that allows a token to be traced back to its +origin chain. It contains a sequence of port and channel identifiers ordered from the most recent to +the oldest in the timeline of transfers. + +:::tip +When using transfer with IBC v2 connecting to e.g. Ethereum, the source channel identifier will be the source client identifier instead. +::: + +This information is included on the token's base denomination field in the form of a hash to prevent an +unbounded denomination length. For example, the token `transfer/channelToA/uatom` will be displayed +as `ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2`. The human readable denomination +is stored using `x/bank` module's [denom metadata](https://docs.cosmos.network/main/build/modules/bank#denom-metadata) +feature. You may display the human readable denominations by querying balances with the `--resolve-denom` flag, as in: + +```shell +simd query bank balances [address] --resolve-denom +``` + +Each send to any chain other than the one it was previously received from is a movement forwards in +the token's timeline. This causes trace to be added to the token's history and the destination port +and destination channel to be prefixed to the denomination. In these instances the sender chain is +acting as the "source zone". When the token is sent back to the chain it previously received from, the +prefix is removed. This is a backwards movement in the token's timeline and the sender chain is +acting as the "sink zone". + +It is strongly recommended to read the full details of [ADR 001: Coin Source Tracing](/architecture/adr-001-coin-source-tracing) to understand the implications and context of the IBC token representations. + +## UX suggestions for clients + +For clients (wallets, exchanges, applications, block explorers, etc) that want to display the source of the token, it is recommended to use the following alternatives for each of the cases below: + +### Direct connection + +If the denomination trace contains a single identifier prefix pair (as in the example above), then +the easiest way to retrieve the chain and light client identifier is to map the trace information +directly. In summary, this requires querying the channel from the denomination trace identifiers, +and then the counterparty client state using the counterparty port and channel identifiers from the +retrieved channel. + +A general pseudo algorithm would look like the following: + +1. Query the full denomination trace. +2. Query the channel with the `portID/channelID` pair, which corresponds to the first destination of the + token. +3. Query the client state using the identifiers pair. Note that this query will return a `"Not +Found"` response if the current chain is not connected to this channel. +4. Retrieve the client identifier or chain identifier from the client state (eg: on + Tendermint clients) and store it locally. + +Using the gRPC gateway client service the steps above would be, with a given IBC token `ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2` stored on `chainB`: + +1. `GET /ibc/apps/transfer/v1/denom_traces/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2` -> `{"path": "transfer/channelToA", "base_denom": "uatom"}` +2. `GET /ibc/apps/transfer/v1/channels/channelToA/ports/transfer/client_state"` -> `{"client_id": "clientA", "chain-id": "chainA", ...}` +3. `GET /ibc/apps/transfer/v1/channels/channelToA/ports/transfer"` -> `{"channel_id": "channelToA", port_id": "transfer", counterparty: {"channel_id": "channelToB", port_id": "transfer"}, ...}` +4. `GET /ibc/apps/transfer/v1/channels/channelToB/ports/transfer/client_state" -> {"client_id": "clientB", "chain-id": "chainB", ...}` + +Then, the token transfer chain path for the `uatom` denomination would be: `chainA` -> `chainB`. + +### Multiple hops + +The multiple channel hops case applies when the token has passed through multiple chains between the original source and final destination chains. + +The IBC protocol doesn't know the topology of the overall network (i.e connections between chains and identifier names between them). For this reason, in the multiple hops case, a particular chain in the timeline of the individual transfers can't query the chain and client identifiers of the other chains. + +Take for example the following sequence of transfers `A -> B -> C` for an IBC token, with a final prefix path (trace info) of `transfer/channelChainC/transfer/channelChainB`. What the paragraph above means is that even in the case that chain `C` is directly connected to chain `A`, querying the port and channel identifiers that chain `B` uses to connect to chain `A` (eg: `transfer/channelChainA`) can be completely different from the one that chain `C` uses to connect to chain `A` (eg: `transfer/channelToChainA`). + +Thus the proposed solution for clients that the IBC team recommends are the following: + +- **Connect to all chains**: Connecting to all the chains in the timeline would allow clients to + perform the queries outlined in the [direct connection](#direct-connection) section to each + relevant chain. By repeatedly following the port and channel denomination trace transfer timeline, + clients should always be able to find all the relevant identifiers. This comes at the tradeoff + that the client must connect to nodes on each of the chains in order to perform the queries. +- **Relayer as a Service (RaaS)**: A longer term solution is to use/create a relayer service that + could map the denomination trace to the chain path timeline for each token (i.e `origin chain -> +chain #1 -> ... -> chain #(n-1) -> final chain`). These services could provide merkle proofs in + order to allow clients to optionally verify the path timeline correctness for themselves by + running light clients. If the proofs are not verified, they should be considered as trusted third + parties services. Additionally, client would be advised in the future to use RaaS that support the + largest number of connections between chains in the ecosystem. Unfortunately, none of the existing + public relayers (in [Golang](https://github.com/cosmos/relayer) and + [Rust](https://github.com/informalsystems/ibc-rs)), provide this service to clients. + +:::tip +The only viable alternative for clients (at the time of writing) to tokens with multiple connection hops, is to connect to all chains directly and perform relevant queries to each of them in the sequence. +::: + +## Locked funds + +In some [exceptional cases](/architecture/adr-026-ibc-client-recovery-mechanisms#exceptional-cases), a client state associated with a given channel cannot be updated. This causes that funds from fungible tokens in that channel will be permanently locked and thus can no longer be transferred. + +To mitigate this, a client update governance proposal can be submitted to update the frozen client +with a new valid header. Once the proposal passes the client state will be unfrozen and the funds +from the associated channels will then be unlocked. This mechanism only applies to clients that +allow updates via governance, such as Tendermint clients. + +In addition to this, it's important to mention that a token must be sent back along the exact route +that it took originally in order to return it to its original form on the source chain (eg: the +Cosmos Hub for the `uatom`). Sending a token back to the same chain across a different channel will +**not** move the token back across its timeline. If a channel in the chain history closes before the +token can be sent back across that channel, then the token will not be returnable to its original +form. + +## Security considerations + +For safety, no other module must be capable of minting tokens with the `ibc/` prefix. The IBC +transfer module needs a subset of the denomination space that only it can create tokens in. + +## Channel Closure + +The IBC transfer module does not support channel closure. diff --git a/docs/docs/02-apps/01-transfer/02-state.md b/docs/docs/02-apps/01-transfer/02-state.md new file mode 100644 index 0000000..7239b01 --- /dev/null +++ b/docs/docs/02-apps/01-transfer/02-state.md @@ -0,0 +1,14 @@ +--- +title: State +sidebar_label: State +sidebar_position: 2 +slug: /apps/transfer/ics20-v1/state +--- + +# State + +The IBC transfer application module keeps state of the port to which the module is binded and the denomination trace information. + +- `PortKey`: `0x01 -> ProtocolBuffer(string)` +- `DenomTraceKey`: `0x02 | []bytes(traceHash) -> ProtocolBuffer(Denom)` +- `DenomKey` : `0x03 | []bytes(traceHash) -> ProtocolBuffer(Denom)` diff --git a/docs/docs/02-apps/01-transfer/03-state-transitions.md b/docs/docs/02-apps/01-transfer/03-state-transitions.md new file mode 100644 index 0000000..85b8733 --- /dev/null +++ b/docs/docs/02-apps/01-transfer/03-state-transitions.md @@ -0,0 +1,37 @@ +--- +title: State Transitions +sidebar_label: State Transitions +sidebar_position: 3 +slug: /apps/transfer/ics20-v1/state-transitions +--- + +# State transitions + +## Send fungible tokens + +A successful fungible token send has two state transitions depending if the transfer is a movement forward or backwards in the token's timeline: + +1. Sender chain is the source chain, *i.e* a transfer to any chain other than the one it was previously received from is a movement forwards in the token's timeline. This results in the following state transitions: + + - The coins are transferred to an escrow address (i.e locked) on the sender chain. + - The coins are transferred to the receiving chain through IBC TAO logic. + +2. Sender chain is the sink chain, *i.e* the token is sent back to the chain it previously received from. This is a backwards movement in the token's timeline. This results in the following state transitions: + + - The coins (vouchers) are burned on the sender chain. + - The coins are transferred to the receiving chain through IBC TAO logic. + +## Receive fungible tokens + +A successful fungible token receive has two state transitions depending if the transfer is a movement forward or backwards in the token's timeline: + +1. Receiver chain is the source chain. This is a backwards movement in the token's timeline. This results in the following state transitions: + + - The leftmost port and channel identifier pair is removed from the token denomination prefix. + - The tokens are unescrowed and sent to the receiving address. + +2. Receiver chain is the sink chain. This is a movement forwards in the token's timeline. This results in the following state transitions: + + - Token vouchers are minted by prefixing the destination port and channel identifiers to the trace information. + - The receiving chain stores the new trace information in the store (if not set already). + - The vouchers are sent to the receiving address. diff --git a/docs/docs/02-apps/01-transfer/04-messages.md b/docs/docs/02-apps/01-transfer/04-messages.md new file mode 100644 index 0000000..feeeedb --- /dev/null +++ b/docs/docs/02-apps/01-transfer/04-messages.md @@ -0,0 +1,76 @@ +--- +title: Messages +sidebar_label: Messages +sidebar_position: 4 +slug: /apps/transfer/ics20-v1/messages +--- + +# Messages + +## `MsgTransfer` + +A fungible token cross chain transfer is achieved by using the `MsgTransfer`: + +```go +type MsgTransfer struct { + SourcePort string + // with IBC v2 SourceChannel will be a client ID + SourceChannel string + Token sdk.Coin + Sender string + Receiver string + // If you are sending with IBC v1 protocol, either timeout_height or timeout_timestamp must be set. + // If you are sending with IBC v2 protocol, timeout_timestamp must be set, and timeout_height must be omitted. + TimeoutHeight ibcexported.Height + // Timeout timestamp in absolute nanoseconds since unix epoch. + TimeoutTimestamp uint64 + // optional Memo field + Memo string + // optional Encoding field + Encoding string +} +``` + +This message is expected to fail if: + +- `SourcePort` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators). +- `SourceChannel` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +- `Token` is invalid: + - `Amount` is not positive. + - `Denom` is not a valid IBC denomination as per [ADR 001 - Coin Source Tracing](/architecture/adr-001-coin-source-tracing). +- `Sender` is empty. +- `Receiver` is empty or contains more than 2048 bytes. +- `Memo` contains more than 32768 bytes. +- `TimeoutHeight` and `TimeoutTimestamp` are both zero for IBC Classic. + - Note that `TimeoutHeight` as a concept is removed in IBC v2, hence this must always be emitted and only `TimeoutTimestamp` used. + +This message will send a fungible token to the counterparty chain represented by the counterparty Channel End connected to the Channel End with the identifiers `SourcePort` and `SourceChannel`. Note that in IBC v2 a pair of clients are connected and the `SourceChannel` is referring to the source `ClientID`. + +The denomination provided for transfer should correspond to the same denomination represented on this chain. The prefixes will be added as necessary upon by the receiving chain. + +If the `Amount` is set to the maximum value for a 256-bit unsigned integer (i.e. 2^256 - 1), then the whole balance of the corresponding denomination will be transferred. The helper function `UnboundedSpendLimit` in the `types` package of the `transfer` module provides the sentinel value that can be used. + +### Memo + +The memo field was added to allow applications and users to attach metadata to transfer packets. The field is optional and may be left empty. When it is used to attach metadata for a particular middleware, the memo field should be represented as a json object where different middlewares use different json keys. + +For example, the following memo field is used by the callbacks middleware to attach a source callback to a transfer packet: + +```jsonc +{ + "src_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} +``` + +You can find more information about other applications that use the memo field in the [chain registry](https://github.com/cosmos/chain-registry/blob/master/_memo_keys/ICS20_memo_keys.json). + +### Encoding + +In IBC v2, the encoding method used by an application has more flexibility as it is specified within a `Payload`, rather than negotiated and fixed during an IBC classic channel handshake. Certain encoding types may be more suited to specific blockchains, e.g. ABI encoding is more gas efficient to decode in an EVM than JSON or Protobuf. + +Within ibc-go, JSON, protobuf and ABI encoding are supported and can be used, see the [transfer packet types](https://github.com/cosmos/ibc-go/blob/14bc17e26ad12cee6bdb99157a05296fcf58b762/modules/apps/transfer/types/packet.go#L36-L40). + \ No newline at end of file diff --git a/docs/docs/02-apps/01-transfer/05-events.md b/docs/docs/02-apps/01-transfer/05-events.md new file mode 100644 index 0000000..c53192b --- /dev/null +++ b/docs/docs/02-apps/01-transfer/05-events.md @@ -0,0 +1,59 @@ +--- +title: Events +sidebar_label: Events +sidebar_position: 5 +slug: /apps/transfer/ics20-v1/events +--- + +# Events + +## `MsgTransfer` + +| Type | Attribute Key | Attribute Value | +|--------------|-----------------|-----------------| +| ibc_transfer | sender | \{sender\} | +| ibc_transfer | receiver | \{receiver\} | +| ibc_transfer | denom | \{denom\} | +| ibc_transfer | denom_hash | \{denom_hash\} | +| ibc_transfer | amount | \{amount\} | +| ibc_transfer | memo | \{memo\} | +| message | module | transfer | + +## `OnRecvPacket` callback + +| Type | Attribute Key | Attribute Value | +|-----------------------|-----------------|-----------------| +| fungible_token_packet | sender | \{sender\} | +| fungible_token_packet | receiver | \{receiver\} | +| fungible_token_packet | denom | \{denom\} | +| fungible_token_packet | denom_hash | \{denom_hash\} | +| fungible_token_packet | amount | \{amount\} | +| fungible_token_packet | memo | \{memo\} | +| fungible_token_packet | success | \{ackSuccess\} | +| fungible_token_packet | error | \{ackError\} | +| message | module | transfer | + +## `OnAcknowledgePacket` callback + +| Type | Attribute Key | Attribute Value | +|-----------------------|-----------------|------------------| +| fungible_token_packet | sender | \{sender\} | +| fungible_token_packet | receiver | \{receiver\} | +| fungible_token_packet | denom | \{denom\} | +| fungible_token_packet | denom_hash | \{denom_hash\} | +| fungible_token_packet | amount | \{amount\} | +| fungible_token_packet | memo | \{memo\} | +| fungible_token_packet | acknowledgement | \{ack.String()\} | +| fungible_token_packet | success / error | \{ack.Response\} | +| message | module | transfer | + +## `OnTimeoutPacket` callback + +| Type | Attribute Key | Attribute Value | +|---------|-----------------|-----------------| +| timeout | refund_receiver | \{receiver\} | +| timeout | refund_tokens | \{jsonTokens\} | +| timeout | denom | \{denom\} | +| timeout | denom_hash | \{denom_hash\} | +| timeout | memo | \{memo\} | +| message | module | transfer | diff --git a/docs/docs/02-apps/01-transfer/06-metrics.md b/docs/docs/02-apps/01-transfer/06-metrics.md new file mode 100644 index 0000000..de85e0b --- /dev/null +++ b/docs/docs/02-apps/01-transfer/06-metrics.md @@ -0,0 +1,17 @@ +--- +title: Metrics +sidebar_label: Metrics +sidebar_position: 6 +slug: /apps/transfer/ics20-v1/metrics +--- + +# Metrics + +The IBC transfer application module exposes the following set of [metrics](https://docs.cosmos.network/main/learn/advanced/telemetry). + +| Metric | Description | Unit | Type | +|:--------------------------------|:------------------------------------------------------------------------------------------|:----------------|:--------| +| `tx_msg_ibc_transfer` | The total amount of tokens transferred via IBC in a `MsgTransfer` (source or sink chain) | token | gauge | +| `ibc_transfer_packet_receive` | The total amount of tokens received in a `FungibleTokenPacketData` (source or sink chain) | token | gauge | +| `ibc_transfer_send` | Total number of IBC transfers sent from a chain (source or sink) | transfer | counter | +| `ibc_transfer_receive` | Total number of IBC transfers received to a chain (source or sink) | transfer | counter | diff --git a/docs/docs/02-apps/01-transfer/07-params.md b/docs/docs/02-apps/01-transfer/07-params.md new file mode 100644 index 0000000..efa4e0b --- /dev/null +++ b/docs/docs/02-apps/01-transfer/07-params.md @@ -0,0 +1,93 @@ +--- +title: Params +sidebar_label: Params +sidebar_position: 7 +slug: /apps/transfer/ics20-v1/params +--- + +# Parameters + +The IBC transfer application module contains the following parameters: + +| Name | Type | Default Value | +| ---------------- | ---- | ------------- | +| `SendEnabled` | bool | `true` | +| `ReceiveEnabled` | bool | `true` | + +The IBC transfer module stores its parameters under its `StoreKey` + +## `SendEnabled` + +The `SendEnabled` parameter controls send cross-chain transfer capabilities for all fungible tokens. + +To prevent a single token from being transferred from the chain, set the `SendEnabled` parameter to `true` and then, depending on the Cosmos SDK version, do one of the following: + +- For Cosmos SDK v0.46.x or earlier, set the bank module's [`SendEnabled` parameter](https://github.com/cosmos/cosmos-sdk/blob/release/v0.46.x/x/bank/spec/05_params.md#sendenabled) for the denomination to `false`. +- For Cosmos SDK versions above v0.46.x, set the bank module's `SendEnabled` entry for the denomination to `false` using `MsgSetSendEnabled` as a governance proposal. + +:::warning +Doing so will prevent the token from being transferred between any accounts in the blockchain. +::: + +## `ReceiveEnabled` + +The transfers enabled parameter controls receive cross-chain transfer capabilities for all fungible tokens. + +To prevent a single token from being transferred to the chain, set the `ReceiveEnabled` parameter to `true` and then, depending on the Cosmos SDK version, do one of the following: + +- For Cosmos SDK v0.46.x or earlier, set the bank module's [`SendEnabled` parameter](https://github.com/cosmos/cosmos-sdk/blob/release/v0.46.x/x/bank/spec/05_params.md#sendenabled) for the denomination to `false`. +- For Cosmos SDK versions above v0.46.x, set the bank module's `SendEnabled` entry for the denomination to `false` using `MsgSetSendEnabled` as a governance proposal. + +:::warning +Doing so will prevent the token from being transferred between any accounts in the blockchain. +::: + +## Queries + +Current parameter values can be queried via a query message. + + + +```protobuf +// proto/ibc/applications/transfer/v1/query.proto + +// QueryParamsRequest is the request type for the Query/Params RPC method. +message QueryParamsRequest {} + +// QueryParamsResponse is the response type for the Query/Params RPC method. +message QueryParamsResponse { + // params defines the parameters of the module. + Params params = 1; +} +``` + +To execute the query in `simd`, you use the following command: + +```bash +simd query ibc-transfer params +``` + +## Changing Parameters + +To change the parameter values, you must make a governance proposal that executes the `MsgUpdateParams` message. + + + +```protobuf +// proto/ibc/applications/transfer/v1/tx.proto + +// MsgUpdateParams is the Msg/UpdateParams request type. +message MsgUpdateParams { + // signer address (it may be the address that controls the module, which defaults to x/gov unless overwritten). + string signer = 1; + + // params defines the transfer parameters to update. + // + // NOTE: All parameters must be supplied. + Params params = 2 [(gogoproto.nullable) = false]; +} + +// MsgUpdateParamsResponse defines the response structure for executing a +// MsgUpdateParams message. +message MsgUpdateParamsResponse {} +``` diff --git a/docs/docs/02-apps/01-transfer/08-authorizations.md b/docs/docs/02-apps/01-transfer/08-authorizations.md new file mode 100644 index 0000000..be4b5e4 --- /dev/null +++ b/docs/docs/02-apps/01-transfer/08-authorizations.md @@ -0,0 +1,56 @@ +--- +title: Authorizations +sidebar_label: Authorizations +sidebar_position: 8 +slug: /apps/transfer/ics20-v1/authorizations +--- + +# `TransferAuthorization` + +`TransferAuthorization` implements the `Authorization` interface for `ibc.applications.transfer.v1.MsgTransfer`. It allows a granter to grant a grantee the privilege to submit `MsgTransfer` on its behalf. Please see the [Cosmos SDK docs](https://docs.cosmos.network/v0.47/modules/authz) for more details on granting privileges via the `x/authz` module. + +More specifically, the granter allows the grantee to transfer funds that belong to the granter over a specified channel. + +For the specified channel, the granter must be able to specify a spend limit of a specific denomination they wish to allow the grantee to be able to transfer. + +The granter may be able to specify the list of addresses that they allow to receive funds. If empty, then all addresses are allowed. + +It takes: + +- a `SourcePort` and a `SourceChannel` which together comprise the unique transfer channel identifier over which authorized funds can be transferred. +- a `SpendLimit` that specifies the maximum amount of tokens the grantee can transfer. The `SpendLimit` is updated as the tokens are transferred, unless the sentinel value of the maximum value for a 256-bit unsigned integer (i.e. 2^256 - 1) is used for the amount, in which case the `SpendLimit` will not be updated (please be aware that using this sentinel value will grant the grantee the privilege to transfer **all** the tokens of a given denomination available at the granter's account). The helper function `UnboundedSpendLimit` in the `types` package of the `transfer` module provides the sentinel value that can be used. This `SpendLimit` may also be updated to increase or decrease the limit as the granter wishes. +- an `AllowList` list that specifies the list of addresses that are allowed to receive funds. If this list is empty, then all addresses are allowed to receive funds from the `TransferAuthorization`. +- an `AllowedPacketData` list that specifies the list of memo strings that are allowed to be included in the memo field of the packet. If this list is empty, then only an empty memo is allowed (a `memo` field with non-empty content will be denied). If this list includes a single element equal to `"*"`, then any content in `memo` field will be allowed. + +Setting a `TransferAuthorization` is expected to fail if: + +- the spend limit is nil +- the denomination of the spend limit is an invalid coin type +- the source port ID is invalid +- the source channel ID is invalid +- there are duplicate entries in the `AllowList` +- the `memo` field is not allowed by `AllowedPacketData` + +Below is the `TransferAuthorization` message: + +```go +func NewTransferAuthorization(allocations ...Allocation) *TransferAuthorization { + return &TransferAuthorization{ + Allocations: allocations, + } +} + +type Allocation struct { + // the port on which the packet will be sent + SourcePort string + // the channel by which the packet will be sent + SourceChannel string + // spend limitation on the channel + SpendLimit sdk.Coins + // allow list of receivers, an empty allow list permits any receiver address + AllowList []string + // allow list of memo strings, an empty list prohibits all memo strings; + // a list only with "*" permits any memo string + AllowedPacketData []string +} +``` diff --git a/docs/docs/02-apps/01-transfer/09-client.md b/docs/docs/02-apps/01-transfer/09-client.md new file mode 100644 index 0000000..a26807b --- /dev/null +++ b/docs/docs/02-apps/01-transfer/09-client.md @@ -0,0 +1,94 @@ +--- +title: Client +sidebar_label: Client +sidebar_position: 9 +slug: /apps/transfer/ics20-v1/client +--- + +# Client + +## CLI + +A user can query and interact with the `transfer` module using the CLI. Use the `--help` flag to discover the available commands: + +### Query + +The `query` commands allow users to query `transfer` state. + +```shell +simd query ibc-transfer --help +``` + +#### Transactions + +The `tx` commands allow users to interact with the controller submodule. + +```shell +simd tx ibc-transfer --help +``` + +#### `transfer` + +The `transfer` command allows users to execute cross-chain token transfers from the source port ID and channel ID on the sending chain. + +```shell +simd tx ibc-transfer transfer [src-port] [src-channel] [receiver] [coins] [flags] +``` + +The `coins` parameter accepts the amount and denomination (e.g. `100uatom`) of the tokens to be transferred. + +The additional flags that can be used with the command are: + +- `--packet-timeout-height` to specify the timeout block height in the format `{revision}-{height}`. The default value is `0-0`, which effectively disables the timeout. Timeout height can only be absolute, therefore this option must be used in combination with `--absolute-timeouts` set to true. On IBC v1 protocol, either `--packet-timeout-height` or `--packet-timeout-timestamp` must be set. On IBC v2 protocol `--packet-timeout-timestamp` must be set. +- `--packet-timeout-timestamp` to specify the timeout timestamp in nanoseconds. The timeout can be either relative (from the current UTC time) or absolute. The default value is 10 minutes (and thus relative). On IBC v1 protocol, either `--packet-timeout-height` or `--packet-timeout-timestamp` must be set. On IBC v2 protocol `--packet-timeout-timestamp` must be set. +- `--absolute-timeouts` to interpret the timeout timestamp as an absolute value (when set to true). The default value is false (and thus the timeout is considered relative to current UTC time). +- `--memo` to specify the memo string to be sent along with the transfer packet. If forwarding is used, then the memo string will be carried through the intermediary chains to the final destination. + +#### `total-escrow` + +The `total-escrow` command allows users to query the total amount in escrow for a particular coin denomination regardless of the transfer channel from where the coins were sent out. + +```shell +simd query ibc-transfer total-escrow [denom] [flags] +``` + +Example: + +```shell +simd query ibc-transfer total-escrow samoleans +``` + +Example Output: + +```shell +amount: "100" +``` + +## gRPC + +A user can query the `transfer` module using gRPC endpoints. + +### `TotalEscrowForDenom` + +The `TotalEscrowForDenom` endpoint allows users to query the total amount in escrow for a particular coin denomination regardless of the transfer channel from where the coins were sent out. + +```shell +ibc.applications.transfer.v1.Query/TotalEscrowForDenom +``` + +Example: + +```shell +grpcurl -plaintext \ + -d '{"denom":"samoleans"}' \ + localhost:9090 \ + ibc.applications.transfer.v1.Query/TotalEscrowForDenom +``` + +Example output: + +```shell +{ + "amount": "100" +} +``` diff --git a/docs/docs/02-apps/01-transfer/10-IBCv2-transfer.md b/docs/docs/02-apps/01-transfer/10-IBCv2-transfer.md new file mode 100644 index 0000000..bf4512c --- /dev/null +++ b/docs/docs/02-apps/01-transfer/10-IBCv2-transfer.md @@ -0,0 +1,65 @@ +--- +title: IBC v2 Transfer +sidebar_label: IBC v2 Transfer +sidebar_position: 10 +slug: /apps/transfer/ics20-v1/ibcv2transfer +--- + +# IBC v2 Transfer + +Much of the core business logic of sending and recieving tokens between chains is unchanged between IBC Classic and IBC v2. Some of the key differences to pay attention to are detailed below. + +## No Channel Handshakes, New Packet Format and Encoding Support + +- IBC v2 does not establish connection between applications with a channel handshake. Channel identifiers represent Client IDs and are included in the `Payload` + - The source and destination port must be `"transfer"` + - The channel IDs [must be valid client IDs](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/v2/ibc_module.go#L46-L47) of the format `{clientID}-{sequence}`, e.g. 08-wasm-007 +- The [`Payload`](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel/v2/types/packet.pb.go#L146-L158) contains the [`FungibleTokenPacketData`](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/types/packet.pb.go#L28-L39) for a token transfer. + +The code snippet shows the `Payload` struct. + +```go +// Payload contains the source and destination ports and payload for the application (version, encoding, raw bytes) +type Payload struct { + // specifies the source port of the packet, e.g. transfer + SourcePort string `protobuf:"bytes,1,opt,name=source_port,json=sourcePort,proto3" json:"source_port,omitempty"` + // specifies the destination port of the packet, e.g. trasnfer + DestinationPort string `protobuf:"bytes,2,opt,name=destination_port,json=destinationPort,proto3" json:"destination_port,omitempty"` + // version of the specified application + Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` + // the encoding used for the provided value, for transfer this could be JSON, protobuf or ABI + Encoding string `protobuf:"bytes,4,opt,name=encoding,proto3" json:"encoding,omitempty"` + // the raw bytes for the payload. + Value []byte `protobuf:"bytes,5,opt,name=value,proto3" json:"value,omitempty"` +} +``` + +The code snippet shows the structure of the `Payload` bytes for token transfer + +```go +// FungibleTokenPacketData defines a struct for the packet payload +// See FungibleTokenPacketData spec: +// https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#data-structures +type FungibleTokenPacketData struct { + // the token denomination to be transferred + Denom string `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom,omitempty"` + // the token amount to be transferred + Amount string `protobuf:"bytes,2,opt,name=amount,proto3" json:"amount,omitempty"` + // the sender address + Sender string `protobuf:"bytes,3,opt,name=sender,proto3" json:"sender,omitempty"` + // the recipient address on the destination chain + Receiver string `protobuf:"bytes,4,opt,name=receiver,proto3" json:"receiver,omitempty"` + // optional memo + Memo string `protobuf:"bytes,5,opt,name=memo,proto3" json:"memo,omitempty"` +} +``` + +## Base Denoms cannot contain slashes + +With the new [`Denom`](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/types/token.pb.go#L81-L87) struct, the base denom, i.e. uatom, is seperated from the trace - the path the token has travelled. The trace is presented as an array of [`Hop`](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/types/token.pb.go#L136-L140)s. + +Because IBC v2 no longer uses channels, it is no longer possible to rely on a fixed format for an identifier so using a base denom that contains a "/" is dissallowed. + +## Changes to the application module interface + +Instead of implementing token transfer for `port.IBCModule`, IBC v2 uses the new application interface `api.IBCModule`. More information on the interface differences can be found in the [application section](../../01-ibc/03-apps/00-ibcv2apps.md). diff --git a/docs/docs/02-apps/01-transfer/_category_.json b/docs/docs/02-apps/01-transfer/_category_.json new file mode 100644 index 0000000..a803e10 --- /dev/null +++ b/docs/docs/02-apps/01-transfer/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Token Transfer", + "position": 1, + "link": null +} diff --git a/docs/docs/02-apps/01-transfer/images/forwarding-3-chains-dark.png b/docs/docs/02-apps/01-transfer/images/forwarding-3-chains-dark.png new file mode 100644 index 0000000..3defdd2 Binary files /dev/null and b/docs/docs/02-apps/01-transfer/images/forwarding-3-chains-dark.png differ diff --git a/docs/docs/02-apps/01-transfer/images/forwarding-3-chains-light.png b/docs/docs/02-apps/01-transfer/images/forwarding-3-chains-light.png new file mode 100644 index 0000000..5eb2954 Binary files /dev/null and b/docs/docs/02-apps/01-transfer/images/forwarding-3-chains-light.png differ diff --git a/docs/docs/02-apps/02-interchain-accounts/01-overview.md b/docs/docs/02-apps/02-interchain-accounts/01-overview.md new file mode 100644 index 0000000..5a0aafa --- /dev/null +++ b/docs/docs/02-apps/02-interchain-accounts/01-overview.md @@ -0,0 +1,51 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /apps/interchain-accounts/overview +--- + +:::warning +Interchain Accounts is only compatible with IBC Classic, not IBC v2 +::: + +# Overview + +:::note Synopsis +Learn about what the Interchain Accounts module is +::: + +## What is the Interchain Accounts module? + +Interchain Accounts is the Cosmos SDK implementation of the ICS-27 protocol, which enables cross-chain account management built upon IBC. + +- How does an interchain account differ from a regular account? + +Regular accounts use a private key to sign transactions. Interchain Accounts are instead controlled programmatically by counterparty chains via IBC packets. + +## Concepts + +`Host Chain`: The chain where the interchain account is registered. The host chain listens for IBC packets from a controller chain which should contain instructions (e.g. Cosmos SDK messages) for which the interchain account will execute. + +`Controller Chain`: The chain registering and controlling an account on a host chain. The controller chain sends IBC packets to the host chain to control the account. + +`Interchain Account`: An account on a host chain created using the ICS-27 protocol. An interchain account has all the capabilities of a normal account. However, rather than signing transactions with a private key, a controller chain will send IBC packets to the host chain which signals what transactions the interchain account should execute. + +`Authentication Module`: A custom application module on the controller chain that uses the Interchain Accounts module to build custom logic for the creation & management of interchain accounts. It can be either an IBC application module using the [legacy API](10-legacy/03-keeper-api.md), or a regular Cosmos SDK application module sending messages to the controller submodule's `MsgServer` (this is the recommended approach from ibc-go v6 if access to packet callbacks is not needed). Please note that the legacy API will eventually be removed and IBC applications will not be able to use them in later releases. + +## SDK security model + +SDK modules on a chain are assumed to be trustworthy. For example, there are no checks to prevent an untrustworthy module from accessing the bank keeper. + +The implementation of ICS-27 in ibc-go uses this assumption in its security considerations. + +The implementation assumes other IBC application modules will not bind to ports within the ICS-27 namespace. + +## Channel Closure + +The provided interchain account host and controller implementations do not support `ChanCloseInit`. However, they do support `ChanCloseConfirm`. +This means that the host and controller modules cannot close channels, but they will confirm channel closures initiated by other implementations of ICS-27. + +In the event of a channel closing (due to a packet timeout in an ordered channel, for example), the interchain account associated with that channel can become accessible again if a new channel is created with a (JSON-formatted) version string that encodes the exact same `Metadata` information of the previous channel. The channel can be reopened using either [`MsgRegisterInterchainAccount`](./05-messages.md#msgregisterinterchainaccount) or `MsgChannelOpenInit`. If `MsgRegisterInterchainAccount` is used, then it is possible to leave the `version` field of the message empty, since it will be filled in by the controller submodule. If `MsgChannelOpenInit` is used, then the `version` field must be provided with the correct JSON-encoded `Metadata` string. See section [Understanding Active Channels](./09-active-channels.md#understanding-active-channels) for more information. + +When reopening a channel with the default controller submodule, the ordering of the channel cannot be changed. diff --git a/docs/docs/02-apps/02-interchain-accounts/02-development.md b/docs/docs/02-apps/02-interchain-accounts/02-development.md new file mode 100644 index 0000000..f949a92 --- /dev/null +++ b/docs/docs/02-apps/02-interchain-accounts/02-development.md @@ -0,0 +1,40 @@ +--- +title: Development Use Cases +sidebar_label: Development Use Cases +sidebar_position: 2 +slug: /apps/interchain-accounts/development +--- + + +# Development use cases + +The initial version of Interchain Accounts allowed for the controller submodule to be extended by providing it with an underlying application which would handle all packet callbacks. +That functionality is now being deprecated in favor of alternative approaches. +This document will outline potential use cases and redirect each use case to the appropriate documentation. + +## Custom authentication + +Interchain accounts may be associated with alternative types of authentication relative to the traditional public/private key signing. +If you wish to develop or use Interchain Accounts with a custom authentication module and do not need to execute custom logic on the packet callbacks, we recommend you use ibc-go v6 or greater and that your custom authentication module interacts with the controller submodule via the [`MsgServer`](./05-messages.md). + +If you wish to consume and execute custom logic in the packet callbacks, then please read the section [Packet callbacks](#packet-callbacks) below. + +## Redirection to a smart contract + +It may be desirable to allow smart contracts to control an interchain account. +To facilitate such an action, the controller submodule may be provided an underlying application which redirects to smart contract callers. +An improved design has been suggested in [ADR 008](https://github.com/cosmos/ibc-go/pull/1976) which performs this action via middleware. + +Implementers of this use case are recommended to follow the ADR 008 approach. +The underlying application may continue to be used as a short term solution for ADR 008 and the [legacy API](./10-legacy/01-auth-modules.md) should continue to be utilized in such situations. + +## Packet callbacks + +If a developer requires access to packet callbacks for their use case, then they have the following options: + +1. Write a smart contract which is connected via an ADR 008 or equivalent IBC application (recommended). +2. Use the controller's underlying application to implement packet callback logic. + +In the first case, the smart contract should use the [`MsgServer`](./05-messages.md). + +In the second case, the underlying application should use the [legacy API](./10-legacy/03-keeper-api.md). diff --git a/docs/docs/02-apps/02-interchain-accounts/03-auth-modules.md b/docs/docs/02-apps/02-interchain-accounts/03-auth-modules.md new file mode 100644 index 0000000..bac035e --- /dev/null +++ b/docs/docs/02-apps/02-interchain-accounts/03-auth-modules.md @@ -0,0 +1,27 @@ +--- +title: Authentication Modules +sidebar_label: Authentication Modules +sidebar_position: 3 +slug: /apps/interchain-accounts/auth-modules +--- + + +# Building an authentication module + +:::note Synopsis +Authentication modules enable application developers to perform custom logic when interacting with the Interchain Accounts controller sumbmodule's `MsgServer`. +::: + +The controller submodule is used for account registration and packet sending. It executes only logic required of all controllers of interchain accounts. The type of authentication used to manage the interchain accounts remains unspecified. There may exist many different types of authentication which are desirable for different use cases. Thus the purpose of the authentication module is to wrap the controller submodule with custom authentication logic. + +In ibc-go, authentication modules can communicate with the controller submodule by passing messages through `baseapp`'s `MsgServiceRouter`. To implement an authentication module, the `IBCModule` interface need not be fulfilled; it is only required to fulfill Cosmos SDK's `AppModuleBasic` interface, just like any regular Cosmos SDK application module. + +The authentication module must: + +- Authenticate interchain account owners. +- Track the associated interchain account address for an owner. +- Send packets on behalf of an owner (after authentication). + +## Integration into `app.go` file + +To integrate the authentication module into your chain, please follow the steps outlined in [`app.go` integration](04-integration.md#example-integration). diff --git a/docs/docs/02-apps/02-interchain-accounts/04-integration.md b/docs/docs/02-apps/02-interchain-accounts/04-integration.md new file mode 100644 index 0000000..aa72536 --- /dev/null +++ b/docs/docs/02-apps/02-interchain-accounts/04-integration.md @@ -0,0 +1,195 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 4 +slug: /apps/interchain-accounts/integration +--- + + +# Integration + +:::note Synopsis +Learn how to integrate Interchain Accounts host and controller functionality to your chain. The following document only applies for Cosmos SDK chains. +::: + +The Interchain Accounts module contains two submodules. Each submodule has its own IBC application. The Interchain Accounts module should be registered as an `AppModule` in the same way all SDK modules are registered on a chain, but each submodule should create its own `IBCModule` as necessary. A route should be added to the IBC router for each submodule which will be used. + +Chains who wish to support ICS-27 may elect to act as a host chain, a controller chain or both. Disabling host or controller functionality may be done statically by excluding the host or controller submodule entirely from the `app.go` file or it may be done dynamically by taking advantage of the on-chain parameters which enable or disable the host or controller submodules. + +Interchain Account authentication modules (both custom or generic, such as the `x/gov`, `x/group` or `x/auth` Cosmos SDK modules) can send messages to the controller submodule's [`MsgServer`](05-messages.md) to register interchain accounts and send packets to the interchain account. To accomplish this, the authentication module needs to be composed with `baseapp`'s `MsgServiceRouter`. + +![ica-v6.png](./images/ica-v6.png) + +## Example integration + +```go +// app.go + +// Register the AppModule for the Interchain Accounts module and the authentication module +// Note: No `icaauth` exists, this must be substituted with an actual Interchain Accounts authentication module +ModuleBasics = module.NewBasicManager( + ... + ica.AppModuleBasic{}, + icaauth.AppModuleBasic{}, + ... +) + +... + +// Add module account permissions for the Interchain Accounts module +// Only necessary for host chain functionality +// Each Interchain Account created on the host chain is derived from the module account created +maccPerms = map[string][]string{ + ... + icatypes.ModuleName: nil, +} + +... + +// Add Interchain Accounts Keepers for each submodule used and the authentication module +// If a submodule is being statically disabled, the associated Keeper does not need to be added. +type App struct { + ... + + ICAControllerKeeper icacontrollerkeeper.Keeper + ICAHostKeeper icahostkeeper.Keeper + ICAAuthKeeper icaauthkeeper.Keeper + + ... +} + +... + +// Create store keys for each submodule Keeper and the authentication module +keys := sdk.NewKVStoreKeys( + ... + icacontrollertypes.StoreKey, + icahosttypes.StoreKey, + icaauthtypes.StoreKey, + ... +) + +... + + +// Create the Keeper for each submodule +app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, app.IBCKeeper.PortKeeper, + app.MsgServiceRouter(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), +) +app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, app.IBCKeeper.PortKeeper, app.AccountKeeper, + app.MsgServiceRouter(), app.GRPCQueryRouter(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), +) + +// Create Interchain Accounts AppModule +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper) + +// Create your Interchain Accounts authentication module +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.MsgServiceRouter()) + +// ICA auth AppModule +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) + +// Create controller IBC application stack and host IBC module as desired +icaControllerStack := icacontroller.NewIBCMiddleware(app.ICAControllerKeeper) +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host and authentication routes +ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icahosttypes.SubModuleName, icaHostIBCModule) +... + +// Register Interchain Accounts and authentication module AppModule's +app.moduleManager = module.NewManager( + ... + icaModule, + icaAuthModule, +) + +... + +// Add Interchain Accounts to begin blocker logic +app.moduleManager.SetOrderBeginBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add Interchain Accounts to end blocker logic +app.moduleManager.SetOrderEndBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add Interchain Accounts module InitGenesis logic +app.moduleManager.SetOrderInitGenesis( + ... + icatypes.ModuleName, + ... +) + +// initParamsKeeper init params keeper and its subspaces +func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey sdk.StoreKey) paramskeeper.Keeper { + ... + paramsKeeper.Subspace(icahosttypes.SubModuleName) + paramsKeeper.Subspace(icacontrollertypes.SubModuleName) + ... +} +``` + +If no custom athentication module is needed and a generic Cosmos SDK authentication module can be used, then from the sample integration code above all references to `ICAAuthKeeper` and `icaAuthModule` can be removed. That's it, the following code would not be needed: + +```go +// Create your Interchain Accounts authentication module +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.MsgServiceRouter()) + +// ICA auth AppModule +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) +``` + +### Using submodules exclusively + +As described above, the Interchain Accounts application module is structured to support the ability of exclusively enabling controller or host functionality. +This can be achieved by simply omitting either controller or host `Keeper` from the Interchain Accounts `NewAppModule` constructor function, and mounting only the desired submodule via the `IBCRouter`. +Alternatively, submodules can be enabled and disabled dynamically using [on-chain parameters](06-parameters.md). + +The following snippets show basic examples of statically disabling submodules using `app.go`. + +#### Disabling controller chain functionality + +```go +// Create Interchain Accounts AppModule omitting the controller keeper +icaModule := ica.NewAppModule(nil, &app.ICAHostKeeper) + +// Create host IBC Module +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host route +ibcRouter.AddRoute(icahosttypes.SubModuleName, icaHostIBCModule) +``` + +#### Disabling host chain functionality + +```go +// Create Interchain Accounts AppModule omitting the host keeper +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, nil) + + +// Optionally instantiate your custom authentication module if needed, or not otherwise +... + +// Create controller IBC application stack +icaControllerStack := icacontroller.NewIBCMiddleware(app.ICAControllerKeeper) + +// Register controller route +ibcRouter.AddRoute(icacontrollertypes.SubModuleName, icaControllerStack) +``` diff --git a/docs/docs/02-apps/02-interchain-accounts/05-messages.md b/docs/docs/02-apps/02-interchain-accounts/05-messages.md new file mode 100644 index 0000000..cbdb98a --- /dev/null +++ b/docs/docs/02-apps/02-interchain-accounts/05-messages.md @@ -0,0 +1,153 @@ +--- +title: Messages +sidebar_label: Messages +sidebar_position: 5 +slug: /apps/interchain-accounts/messages +--- + + +# Messages + +## `MsgRegisterInterchainAccount` + +An Interchain Accounts channel handshake can be initiated using `MsgRegisterInterchainAccount`: + +```go +type MsgRegisterInterchainAccount struct { + Owner string + ConnectionID string + Version string + Ordering channeltypes.Order +} +``` + +This message is expected to fail if: + +- `Owner` is an empty string or contains more than 2048 bytes. +- `ConnectionID` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). + +This message will construct a new `MsgChannelOpenInit` on chain and route it to the core IBC message server to initiate the opening step of the channel handshake. + +The controller submodule will generate a new port identifier. The caller is expected to provide an appropriate application version string. For example, this may be an ICS-27 JSON encoded [`Metadata`](https://github.com/cosmos/ibc-go/blob/v6.0.0/proto/ibc/applications/interchain_accounts/v1/metadata.proto#L11) type or an ICS-29 JSON encoded [`Metadata`](https://github.com/cosmos/ibc-go/blob/v6.0.0/proto/ibc/applications/fee/v1/metadata.proto#L11) type with a nested application version. +If the `Version` string is omitted, the controller submodule will construct a default version string in the `OnChanOpenInit` handshake callback. + +```go +type MsgRegisterInterchainAccountResponse struct { + ChannelID string + PortId string +} +``` + +The `ChannelID` and `PortID` are returned in the message response. + +## `MsgSendTx` + +An Interchain Accounts transaction can be executed on a remote host chain by sending a `MsgSendTx` from the corresponding controller chain: + +```go +type MsgSendTx struct { + Owner string + ConnectionID string + PacketData InterchainAccountPacketData + RelativeTimeout uint64 +} +``` + +This message is expected to fail if: + +- `Owner` is an empty string or contains more than 2048 bytes. +- `ConnectionID` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +- `PacketData` contains an `UNSPECIFIED` type enum, the length of `Data` bytes is zero or the `Memo` field exceeds 256 characters in length. +- `RelativeTimeout` is zero. + +This message will create a new IBC packet with the provided `PacketData` and send it via the channel associated with the `Owner` and `ConnectionID`. +The `PacketData` is expected to contain a list of serialized `[]sdk.Msg` in the form of `CosmosTx`. Please note the signer field of each `sdk.Msg` must be the interchain account address. +When the packet is relayed to the host chain, the `PacketData` is unmarshalled and the messages are authenticated and executed. + +```go +type MsgSendTxResponse struct { + Sequence uint64 +} +``` + +The packet `Sequence` is returned in the message response. + +### Queries + +It is possible to use [`MsgModuleQuerySafe`](https://github.com/cosmos/ibc-go/blob/eecfa5c09a4c38a5c9f2cc2a322d2286f45911da/proto/ibc/applications/interchain_accounts/host/v1/tx.proto#L41-L51) to execute a list of queries on the host chain. This message can be included in the list of encoded `sdk.Msg`s of `InterchainPacketData`. The host chain will return on the acknowledgment the responses for all the queries. Please note that only module safe queries can be executed ([deterministic queries that are safe to be called from within the state machine](https://docs.cosmos.network/main/build/building-modules/query-services#calling-queries-from-the-state-machine)). + +The queries available from Cosmos SDK are: + +```plaintext +/cosmos.auth.v1beta1.Query/Accounts +/cosmos.auth.v1beta1.Query/Account +/cosmos.auth.v1beta1.Query/AccountAddressByID +/cosmos.auth.v1beta1.Query/Params +/cosmos.auth.v1beta1.Query/ModuleAccounts +/cosmos.auth.v1beta1.Query/ModuleAccountByName +/cosmos.auth.v1beta1.Query/AccountInfo +/cosmos.bank.v1beta1.Query/Balance +/cosmos.bank.v1beta1.Query/AllBalances +/cosmos.bank.v1beta1.Query/SpendableBalances +/cosmos.bank.v1beta1.Query/SpendableBalanceByDenom +/cosmos.bank.v1beta1.Query/TotalSupply +/cosmos.bank.v1beta1.Query/SupplyOf +/cosmos.bank.v1beta1.Query/Params +/cosmos.bank.v1beta1.Query/DenomMetadata +/cosmos.bank.v1beta1.Query/DenomMetadataByQueryString +/cosmos.bank.v1beta1.Query/DenomsMetadata +/cosmos.bank.v1beta1.Query/DenomOwners +/cosmos.bank.v1beta1.Query/SendEnabled +/cosmos.circuit.v1.Query/Account +/cosmos.circuit.v1.Query/Accounts +/cosmos.circuit.v1.Query/DisabledList +/cosmos.staking.v1beta1.Query/Validators +/cosmos.staking.v1beta1.Query/Validator +/cosmos.staking.v1beta1.Query/ValidatorDelegations +/cosmos.staking.v1beta1.Query/ValidatorUnbondingDelegations +/cosmos.staking.v1beta1.Query/Delegation +/cosmos.staking.v1beta1.Query/UnbondingDelegation +/cosmos.staking.v1beta1.Query/DelegatorDelegations +/cosmos.staking.v1beta1.Query/DelegatorUnbondingDelegations +/cosmos.staking.v1beta1.Query/Redelegations +/cosmos.staking.v1beta1.Query/DelegatorValidators +/cosmos.staking.v1beta1.Query/DelegatorValidator +/cosmos.staking.v1beta1.Query/HistoricalInfo +/cosmos.staking.v1beta1.Query/Pool +/cosmos.staking.v1beta1.Query/Params +``` + +And the query available from ibc-go is: + +```plaintext +/ibc.core.client.v1.Query/VerifyMembership +``` + +The following code block shows an example of how `MsgModuleQuerySafe` can be used to query the account balance of an account on the host chain. The resulting packet data variable is used to set the `PacketData` of `MsgSendTx`. + +```go +balanceQuery := banktypes.NewQueryBalanceRequest("cosmos1...", "uatom") +queryBz, err := balanceQuery.Marshal() + +// signer of message must be the interchain account on the host +queryMsg := icahosttypes.NewMsgModuleQuerySafe("cosmos2...", []icahosttypes.QueryRequest{ + { + Path: "/cosmos.bank.v1beta1.Query/Balance", + Data: queryBz, + }, +}) + +bz, err := icatypes.SerializeCosmosTx(cdc, []proto.Message{queryMsg}, icatypes.EncodingProtobuf) + +packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: bz, + Memo: "", +} +``` + +## Atomicity + +As the Interchain Accounts module supports the execution of multiple transactions using the Cosmos SDK `Msg` interface, it provides the same atomicity guarantees as Cosmos SDK-based applications, leveraging the [`CacheMultiStore`](https://docs.cosmos.network/main/learn/advanced/store#cachemultistore) architecture provided by the [`Context`](https://docs.cosmos.network/main/learn/advanced/context.html) type. + +This provides atomic execution of transactions when using Interchain Accounts, where state changes are only committed if all `Msg`s succeed. diff --git a/docs/docs/02-apps/02-interchain-accounts/06-parameters.md b/docs/docs/02-apps/02-interchain-accounts/06-parameters.md new file mode 100644 index 0000000..5004118 --- /dev/null +++ b/docs/docs/02-apps/02-interchain-accounts/06-parameters.md @@ -0,0 +1,65 @@ +--- +title: Parameters +sidebar_label: Parameters +sidebar_position: 6 +slug: /apps/interchain-accounts/parameters +--- + + +# Parameters + +The Interchain Accounts module contains the following on-chain parameters, logically separated for each distinct submodule: + +## Controller Submodule Parameters + +| Name | Type | Default Value | +|------------------------|------|---------------| +| `ControllerEnabled` | bool | `true` | + +### ControllerEnabled + +The `ControllerEnabled` parameter controls a chains ability to service ICS-27 controller specific logic. This includes the sending of Interchain Accounts packet data as well as the following ICS-26 callback handlers: + +- `OnChanOpenInit` +- `OnChanOpenAck` +- `OnChanCloseConfirm` +- `OnAcknowledgementPacket` +- `OnTimeoutPacket` + +## Host Submodule Parameters + +| Name | Type | Default Value | +|------------------------|----------|---------------| +| `HostEnabled` | bool | `true` | +| `AllowMessages` | []string | `["*"]` | + +### HostEnabled + +The `HostEnabled` parameter controls a chains ability to service ICS-27 host specific logic. This includes the following ICS-26 callback handlers: + +- `OnChanOpenTry` +- `OnChanOpenConfirm` +- `OnChanCloseConfirm` +- `OnRecvPacket` + +### AllowMessages + +The `AllowMessages` parameter provides the ability for a chain to limit the types of messages or transactions that hosted interchain accounts are authorized to execute by defining an allowlist using the Protobuf message type URL format. + +For example, a Cosmos SDK-based chain that elects to provide hosted Interchain Accounts with the ability of governance voting and staking delegations will define its parameters as follows: + +```json +"params": { + "host_enabled": true, + "allow_messages": ["/cosmos.staking.v1beta1.MsgDelegate", "/cosmos.gov.v1beta1.MsgVote"] +} +``` + +There is also a special wildcard `"*"` value which allows any type of message to be executed by the interchain account. This must be the only value in the `allow_messages` array. + +```json +"params": { + "host_enabled": true, + "allow_messages": ["*"] +} +``` diff --git a/docs/docs/02-apps/02-interchain-accounts/07-tx-encoding.md b/docs/docs/02-apps/02-interchain-accounts/07-tx-encoding.md new file mode 100644 index 0000000..c30ee07 --- /dev/null +++ b/docs/docs/02-apps/02-interchain-accounts/07-tx-encoding.md @@ -0,0 +1,58 @@ +--- +title: Transaction Encoding +sidebar_label: Transaction Encoding +sidebar_position: 7 +slug: /apps/interchain-accounts/tx-encoding +--- + +# Transaction Encoding + +When orchestrating an interchain account transaction, which comprises multiple `sdk.Msg` objects represented as `Any` types, the transactions must be encoded as bytes within [`InterchainAccountPacketData`](https://github.com/cosmos/ibc-go/blob/v7.2.0/proto/ibc/applications/interchain_accounts/v1/packet.proto#L21-L26). + +```protobuf +// InterchainAccountPacketData is comprised of a raw transaction, type of transaction and optional memo field. +message InterchainAccountPacketData { + Type type = 1; + bytes data = 2; + string memo = 3; +} +``` + +The `data` field must be encoded as a [`CosmosTx`](https://github.com/cosmos/ibc-go/blob/v7.2.0/proto/ibc/applications/interchain_accounts/v1/packet.proto#L28-L31). + +```protobuf +// CosmosTx contains a list of sdk.Msg's. It should be used when sending transactions to an SDK host chain. +message CosmosTx { + repeated google.protobuf.Any messages = 1; +} +``` + +The encoding method for `CosmosTx` is determined during the channel handshake process. If the channel version [metadata's `encoding` field](https://github.com/cosmos/ibc-go/blob/v7.2.0/proto/ibc/applications/interchain_accounts/v1/metadata.proto#L22) is marked as `proto3`, then `CosmosTx` undergoes protobuf encoding. Conversely, if the field is set to `proto3json`, then [proto3 json](https://protobuf.dev/programming-guides/proto3/#json) encoding takes place, which generates a JSON representation of the protobuf message. + +## Protobuf Encoding + +Protobuf encoding serves as the standard encoding process for `CosmosTx`. This occurs if the channel handshake initiates with an empty channel version metadata or if the `encoding` field explicitly denotes `proto3`. In Golang, the protobuf encoding procedure utilizes the `proto.Marshal` function. Every protobuf autogenerated Golang type comes equipped with a `Marshal` method that can be employed to encode the message. + +## (Protobuf) JSON Encoding + +The proto3 JSON encoding presents an alternative encoding technique for `CosmosTx`. It is selected if the channel handshake begins with the channel version metadata `encoding` field labeled as `proto3json`. In Golang, the Proto3 canonical encoding in JSON is implemented by the `"github.com/cosmos/gogoproto/jsonpb"` package. Within Cosmos SDK, the `ProtoCodec` structure implements the `JSONCodec` interface, leveraging the `jsonpb` package. This method generates a JSON format as follows: + +```json +{ + "messages": [ + { + "@type": "/cosmos.bank.v1beta1.MsgSend", + "from_address": "cosmos1...", + "to_address": "cosmos1...", + "amount": [ + { + "denom": "uatom", + "amount": "1000000" + } + ] + } + ] +} +``` + +Here, the `"messages"` array is populated with transactions. Each transaction is represented as a JSON object with the `@type` field denoting the transaction type and the remaining fields representing the transaction's attributes. diff --git a/docs/docs/02-apps/02-interchain-accounts/08-client.md b/docs/docs/02-apps/02-interchain-accounts/08-client.md new file mode 100644 index 0000000..079a97a --- /dev/null +++ b/docs/docs/02-apps/02-interchain-accounts/08-client.md @@ -0,0 +1,202 @@ +--- +title: Client +sidebar_label: Client +sidebar_position: 8 +slug: /apps/interchain-accounts/client +--- + +# Client + +## CLI + +A user can query and interact with the Interchain Accounts module using the CLI. Use the `--help` flag to discover the available commands: + +```shell +simd query interchain-accounts --help +``` + +> Please not that this section does not document all the available commands, but only the ones that deserved extra documentation that was not possible to fit in the command line documentation. + +### Controller + +A user can query and interact with the controller submodule. + +#### Query + +The `query` commands allow users to query the controller submodule. + +```shell +simd query interchain-accounts controller --help +``` + +#### Transactions + +The `tx` commands allow users to interact with the controller submodule. + +```shell +simd tx interchain-accounts controller --help +``` + +#### `register` + +The `register` command allows users to register an interchain account on a host chain on the provided connection. + +```shell +simd tx interchain-accounts controller register [connection-id] [flags] +``` + +During registration a new channel is set up between controller and host. There are two flags available that influence the channel that is created: + +- `--version` to specify the (JSON-formatted) version string of the channel. For example: `{\"version\":\"ics27-1\",\"encoding\":\"proto3\",\"tx_type\":\"sdk_multi_msg\",\"controller_connection_id\":\"connection-0\",\"host_connection_id\":\"connection-0\"}`. Passing a custom version string is useful if you want to specify, for example, the encoding format of the interchain accounts packet data (either `proto3` or `proto3json`). If not specified the controller submodule will generate a default version string. +- `--ordering` to specify the ordering of the channel. Available options are `order_ordered` and `order_unordered` (default if not specified). + +Example: + +```shell +simd tx interchain-accounts controller register connection-0 --ordering order_ordered --from cosmos1.. +``` + +#### `send-tx` + +The `send-tx` command allows users to send a transaction on the provided connection to be executed using an interchain account on the host chain. + +```shell +simd tx interchain-accounts controller send-tx [connection-id] [path/to/packet_msg.json] +``` + +Example: + +```shell +simd tx interchain-accounts controller send-tx connection-0 packet-data.json --from cosmos1.. +``` + +See below for example contents of `packet-data.json`. The CLI handler will unmarshal the following into `InterchainAccountPacketData` appropriately. + +```json +{ + "type":"TYPE_EXECUTE_TX", + "data":"CqIBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEoEBCkFjb3Ntb3MxNWNjc2hobXAwZ3N4MjlxcHFxNmc0em1sdG5udmdteXU5dWV1YWRoOXkybmM1emowc3psczVndGRkehItY29zbW9zMTBoOXN0YzV2Nm50Z2V5Z2Y1eGY5NDVuanFxNWgzMnI1M3VxdXZ3Gg0KBXN0YWtlEgQxMDAw", + "memo":"" +} +``` + +Note the `data` field is a base64 encoded byte string as per the tx encoding agreed upon during the channel handshake. + +A helper CLI is provided in the host submodule which can be used to generate the packet data JSON using the counterparty chain's binary. See the [`generate-packet-data` command](#generate-packet-data) for an example. + +### Host + +A user can query and interact with the host submodule. + +#### Query + +The `query` commands allow users to query the host submodule. + +```shell +simd query interchain-accounts host --help +``` + +#### Transactions + +The `tx` commands allow users to interact with the controller submodule. + +```shell +simd tx interchain-accounts host --help +``` + +##### `generate-packet-data` + +The `generate-packet-data` command allows users to generate protobuf or proto3 JSON encoded interchain accounts packet data for input message(s). The packet data can then be used with the controller submodule's [`send-tx` command](#send-tx). The `--encoding` flag can be used to specify the encoding format (value must be either `proto3` or `proto3json`); if not specified, the default will be `proto3`. The `--memo` flag can be used to include a memo string in the interchain accounts packet data. + +```shell +simd tx interchain-accounts host generate-packet-data [message] +``` + +Example: + +```shell +simd tx interchain-accounts host generate-packet-data '[{ + "@type":"/cosmos.bank.v1beta1.MsgSend", + "from_address":"cosmos15ccshhmp0gsx29qpqq6g4zmltnnvgmyu9ueuadh9y2nc5zj0szls5gtddz", + "to_address":"cosmos10h9stc5v6ntgeygf5xf945njqq5h32r53uquvw", + "amount": [ + { + "denom": "stake", + "amount": "1000" + } + ] +}]' --memo memo +``` + +The command accepts a single `sdk.Msg` or a list of `sdk.Msg`s that will be encoded into the outputs `data` field. + +Example output: + +```json +{ + "type":"TYPE_EXECUTE_TX", + "data":"CqIBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEoEBCkFjb3Ntb3MxNWNjc2hobXAwZ3N4MjlxcHFxNmc0em1sdG5udmdteXU5dWV1YWRoOXkybmM1emowc3psczVndGRkehItY29zbW9zMTBoOXN0YzV2Nm50Z2V5Z2Y1eGY5NDVuanFxNWgzMnI1M3VxdXZ3Gg0KBXN0YWtlEgQxMDAw", + "memo":"memo" +} +``` + +## gRPC + +A user can query the interchain account module using gRPC endpoints. + +### Controller + +A user can query the controller submodule using gRPC endpoints. + +#### `InterchainAccount` + +The `InterchainAccount` endpoint allows users to query the controller submodule for the interchain account address for a given owner on a particular connection. + +```shell +ibc.applications.interchain_accounts.controller.v1.Query/InterchainAccount +``` + +Example: + +```shell +grpcurl -plaintext \ + -d '{"owner":"cosmos1..","connection_id":"connection-0"}' \ + localhost:9090 \ + ibc.applications.interchain_accounts.controller.v1.Query/InterchainAccount +``` + +#### `Params` + +The `Params` endpoint users to query the current controller submodule parameters. + +```shell +ibc.applications.interchain_accounts.controller.v1.Query/Params +``` + +Example: + +```shell +grpcurl -plaintext \ + localhost:9090 \ + ibc.applications.interchain_accounts.controller.v1.Query/Params +``` + +### Host + +A user can query the host submodule using gRPC endpoints. + +#### `Params` + +The `Params` endpoint users to query the current host submodule parameters. + +```shell +ibc.applications.interchain_accounts.host.v1.Query/Params +``` + +Example: + +```shell +grpcurl -plaintext \ + localhost:9090 \ + ibc.applications.interchain_accounts.host.v1.Query/Params +``` diff --git a/docs/docs/02-apps/02-interchain-accounts/09-active-channels.md b/docs/docs/02-apps/02-interchain-accounts/09-active-channels.md new file mode 100644 index 0000000..862082e --- /dev/null +++ b/docs/docs/02-apps/02-interchain-accounts/09-active-channels.md @@ -0,0 +1,45 @@ +--- +title: Active Channels +sidebar_label: Active Channels +sidebar_position: 9 +slug: /apps/interchain-accounts/active-channels +--- + +# Understanding Active Channels + +The Interchain Accounts module uses either [ORDERED or UNORDERED](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#ordering) channels. + +When using `ORDERED` channels, the order of transactions when sending packets from a controller to a host chain is maintained. + +When using `UNORDERED` channels, there is no guarantee that the order of transactions when sending packets from the controller to the host chain is maintained. If no ordering is specified in `MsgRegisterInterchainAccount`, then the default ordering for new ICA channels is `UNORDERED`. + +> A limitation when using ORDERED channels is that when a packet times out the channel will be closed. + +In the case of a channel closing, a controller chain needs to be able to regain access to the interchain account registered on this channel. `Active Channels` enable this functionality. + +When an Interchain Account is registered using `MsgRegisterInterchainAccount`, a new channel is created on a particular port. During the `OnChanOpenAck` and `OnChanOpenConfirm` steps (on controller & host chain respectively) the `Active Channel` for this interchain account is stored in state. + +It is possible to create a new channel using the same controller chain portID if the previously set `Active Channel` is now in a `CLOSED` state. This channel creation can be initialized programmatically by sending a new `MsgChannelOpenInit` message like so: + +```go +msg := channeltypes.NewMsgChannelOpenInit(portID, string(versionBytes), channeltypes.ORDERED, []string{connectionID}, icatypes.HostPortID, authtypes.NewModuleAddress(icatypes.ModuleName).String()) +handler := keeper.msgRouter.Handler(msg) +res, err := handler(ctx, msg) +if err != nil { + return err +} +``` + +Alternatively, any relayer operator may initiate a new channel handshake for this interchain account once the previously set `Active Channel` is in a `CLOSED` state. This is done by initiating the channel handshake on the controller chain using the same portID associated with the interchain account in question. + +It is important to note that once a channel has been opened for a given interchain account, new channels can not be opened for this account until the currently set `Active Channel` is set to `CLOSED`. + +## Future improvements + +Future versions of the ICS-27 protocol and the Interchain Accounts module will likely use a new channel type that provides ordering of packets without the channel closing in the event of a packet timing out, thus removing the need for `Active Channels` entirely. +The following is a list of issues which will provide the infrastructure to make this possible: + +- [IBC Channel Upgrades](https://github.com/cosmos/ibc-go/issues/1599) +- [Implement ORDERED_ALLOW_TIMEOUT logic in 04-channel](https://github.com/cosmos/ibc-go/issues/1661) +- [Add ORDERED_ALLOW_TIMEOUT as supported ordering in 03-connection](https://github.com/cosmos/ibc-go/issues/1662) +- [Allow ICA channels to be opened as ORDERED_ALLOW_TIMEOUT](https://github.com/cosmos/ibc-go/issues/1663) diff --git a/docs/docs/02-apps/02-interchain-accounts/10-legacy/01-auth-modules.md b/docs/docs/02-apps/02-interchain-accounts/10-legacy/01-auth-modules.md new file mode 100644 index 0000000..332e05d --- /dev/null +++ b/docs/docs/02-apps/02-interchain-accounts/10-legacy/01-auth-modules.md @@ -0,0 +1,268 @@ +--- +title: Authentication Modules +sidebar_label: Authentication Modules +sidebar_position: 1 +slug: /apps/interchain-accounts/legacy/auth-modules +--- + + +# Building an authentication module + +## Deprecation Notice + +**This document is deprecated and will be removed in future releases**. + +:::note Synopsis +Authentication modules play the role of the `Base Application` as described in [ICS-30 IBC Middleware](https://github.com/cosmos/ibc/tree/master/spec/app/ics-030-middleware), and enable application developers to perform custom logic when working with the Interchain Accounts controller API. +::: + +The controller submodule is used for account registration and packet sending. It executes only logic required of all controllers of interchain accounts. The type of authentication used to manage the interchain accounts remains unspecified. There may exist many different types of authentication which are desirable for different use cases. Thus the purpose of the authentication module is to wrap the controller submodule with custom authentication logic. + +In ibc-go, authentication modules are connected to the controller chain via a middleware stack. The controller submodule is implemented as [middleware](https://github.com/cosmos/ibc/tree/master/spec/app/ics-030-middleware) and the authentication module is connected to the controller submodule as the base application of the middleware stack. To implement an authentication module, the `IBCModule` interface must be fulfilled. By implementing the controller submodule as middleware, any amount of authentication modules can be created and connected to the controller submodule without writing redundant code. + +The authentication module must: + +- Authenticate interchain account owners. +- Track the associated interchain account address for an owner. +- Send packets on behalf of an owner (after authentication). + +## `IBCModule` implementation + +The following `IBCModule` callbacks must be implemented with appropriate custom logic: + +```go +// OnChanOpenInit implements the IBCModule interface +func (im IBCModule) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + // perform custom logic + + return version, nil +} + +// OnChanOpenAck implements the IBCModule interface +func (im IBCModule) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyVersion string, +) error { + // perform custom logic + + return nil +} + +// OnChanCloseConfirm implements the IBCModule interface +func (im IBCModule) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // perform custom logic + + return nil +} + +// OnAcknowledgementPacket implements the IBCModule interface +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + // perform custom logic + + return nil +} + +// OnTimeoutPacket implements the IBCModule interface. +func (im IBCModule) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + // perform custom logic + + return nil +} +``` + +The following functions must be defined to fulfill the `IBCModule` interface, but they will never be called by the controller submodule so they may error or panic. That is because in Interchain Accounts, the channel handshake is always initiated on the controller chain and packets are always sent to the host chain and never to the controller chain. + +```go +// OnChanOpenTry implements the IBCModule interface +func (im IBCModule) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + panic("UNIMPLEMENTED") +} + +// OnChanOpenConfirm implements the IBCModule interface +func (im IBCModule) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + panic("UNIMPLEMENTED") +} + +// OnChanCloseInit implements the IBCModule interface +func (im IBCModule) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + panic("UNIMPLEMENTED") +} + +// OnRecvPacket implements the IBCModule interface. A successful acknowledgement +// is returned if the packet data is successfully decoded and the receive application +// logic returns without error. +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) ibcexported.Acknowledgement { + panic("UNIMPLEMENTED") +} +``` + +## `OnAcknowledgementPacket` + +Controller chains will be able to access the acknowledgement written into the host chain state once a relayer relays the acknowledgement. +The acknowledgement bytes contain either the response of the execution of the message(s) on the host chain or an error. They will be passed to the auth module via the `OnAcknowledgementPacket` callback. Auth modules are expected to know how to decode the acknowledgement. + +If the controller chain is connected to a host chain using the host module on ibc-go, it may interpret the acknowledgement bytes as follows: + +Begin by unmarshaling the acknowledgement into `sdk.TxMsgData`: + +```go +var ack channeltypes.Acknowledgement +if err := channeltypes.SubModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { + return err +} + +txMsgData := &sdk.TxMsgData{} +if err := proto.Unmarshal(ack.GetResult(), txMsgData); err != nil { + return err +} +``` + +If the `txMsgData.Data` field is non nil, the host chain is using SDK version \<\= v0.45. +The auth module should interpret the `txMsgData.Data` as follows: + +```go +switch len(txMsgData.Data) { +case 0: + // see documentation below for SDK 0.46.x or greater +default: + for _, msgData := range txMsgData.Data { + if err := handler(msgData); err != nil { + return err + } + } +... +} +``` + +A handler will be needed to interpret what actions to perform based on the message type sent. +A router could be used, or more simply a switch statement. + +```go +func handler(msgData sdk.MsgData) error { +switch msgData.MsgType { +case sdk.MsgTypeURL(&banktypes.MsgSend{}): + msgResponse := &banktypes.MsgSendResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleBankSendMsg(msgResponse) + +case sdk.MsgTypeURL(&stakingtypes.MsgDelegate{}): + msgResponse := &stakingtypes.MsgDelegateResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleStakingDelegateMsg(msgResponse) + +case sdk.MsgTypeURL(&transfertypes.MsgTransfer{}): + msgResponse := &transfertypes.MsgTransferResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleIBCTransferMsg(msgResponse) + +default: + return +} +``` + +If the `txMsgData.Data` is empty, the host chain is using SDK version > v0.45. +The auth module should interpret the `txMsgData.Responses` as follows: + +```go +... +// switch statement from above +case 0: + for _, any := range txMsgData.MsgResponses { + if err := handleAny(any); err != nil { + return err + } + } +} +``` + +A handler will be needed to interpret what actions to perform based on the type URL of the Any. +A router could be used, or more simply a switch statement. +It may be possible to deduplicate logic between `handler` and `handleAny`. + +```go +func handleAny(any *codectypes.Any) error { +switch any.TypeURL { +case banktypes.MsgSend: + msgResponse, err := unpackBankMsgSendResponse(any) + if err != nil { + return err + } + + handleBankSendMsg(msgResponse) + +case stakingtypes.MsgDelegate: + msgResponse, err := unpackStakingDelegateResponse(any) + if err != nil { + return err + } + + handleStakingDelegateMsg(msgResponse) + + case transfertypes.MsgTransfer: + msgResponse, err := unpackIBCTransferMsgResponse(any) + if err != nil { + return err + } + + handleIBCTransferMsg(msgResponse) + +default: + return +} +``` + +## Integration into `app.go` file + +To integrate the authentication module into your chain, please follow the steps outlined in [`app.go` integration](02-integration.md#example-integration). diff --git a/docs/docs/02-apps/02-interchain-accounts/10-legacy/02-integration.md b/docs/docs/02-apps/02-interchain-accounts/10-legacy/02-integration.md new file mode 100644 index 0000000..4f71f7d --- /dev/null +++ b/docs/docs/02-apps/02-interchain-accounts/10-legacy/02-integration.md @@ -0,0 +1,196 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /apps/interchain-accounts/legacy/integration +--- + + +# Integration + +## Deprecation Notice + +**This document is deprecated and will be removed in future releases**. + +:::note Synopsis +Learn how to integrate Interchain Accounts host and controller functionality to your chain. The following document only applies for Cosmos SDK chains. +::: + +The Interchain Accounts module contains two submodules. Each submodule has its own IBC application. The Interchain Accounts module should be registered as an `AppModule` in the same way all SDK modules are registered on a chain, but each submodule should create its own `IBCModule` as necessary. A route should be added to the IBC router for each submodule which will be used. + +Chains who wish to support ICS-27 may elect to act as a host chain, a controller chain or both. Disabling host or controller functionality may be done statically by excluding the host or controller module entirely from the `app.go` file or it may be done dynamically by taking advantage of the on-chain parameters which enable or disable the host or controller submodules. + +Interchain Account authentication modules are the base application of a middleware stack. The controller submodule is the middleware in this stack. + +![ica-pre-v6.png](./images/ica-pre-v6.png) + +## Example integration + +```go +// app.go + +// Register the AppModule for the Interchain Accounts module and the authentication module +// Note: No `icaauth` exists, this must be substituted with an actual Interchain Accounts authentication module +ModuleBasics = module.NewBasicManager( + ... + ica.AppModuleBasic{}, + icaauth.AppModuleBasic{}, + ... +) + +... + +// Add module account permissions for the Interchain Accounts module +// Only necessary for host chain functionality +// Each Interchain Account created on the host chain is derived from the module account created +maccPerms = map[string][]string{ + ... + icatypes.ModuleName: nil, +} + +... + +// Add Interchain Accounts Keepers for each submodule used and the authentication module +// If a submodule is being statically disabled, the associated Keeper does not need to be added. +type App struct { + ... + + ICAControllerKeeper icacontrollerkeeper.Keeper + ICAHostKeeper icahostkeeper.Keeper + ICAAuthKeeper icaauthkeeper.Keeper + + ... +} + +... + +// Create store keys for each submodule Keeper and the authentication module +keys := sdk.NewKVStoreKeys( + ... + icacontrollertypes.StoreKey, + icahosttypes.StoreKey, + icaauthtypes.StoreKey, + ... +) + +... + + +... + +// Create the Keeper for each submodule +app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.MsgServiceRouter(), +) +app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.AccountKeeper, app.MsgServiceRouter(), +) + +// Create Interchain Accounts AppModule +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper) + +// Create your Interchain Accounts authentication module +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.ICAControllerKeeper) + +// ICA auth AppModule +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) + +// ICA auth IBC Module +icaAuthIBCModule := icaauth.NewIBCModule(app.ICAAuthKeeper) + +// Create controller IBC application stack and host IBC module as desired +icaControllerStack := icacontroller.NewIBCMiddlewareWithAuth(icaAuthIBCModule, app.ICAControllerKeeper) +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host and authentication routes +ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icahosttypes.SubModuleName, icaHostIBCModule). + AddRoute(icaauthtypes.ModuleName, icaControllerStack) // Note, the authentication module is routed to the top level of the middleware stack + +... + +// Register Interchain Accounts and authentication module AppModule's +app.moduleManager = module.NewManager( + ... + icaModule, + icaAuthModule, +) + +... + +// Add fee middleware to begin blocker logic +app.moduleManager.SetOrderBeginBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add fee middleware to end blocker logic +app.moduleManager.SetOrderEndBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add Interchain Accounts module InitGenesis logic +app.moduleManager.SetOrderInitGenesis( + ... + icatypes.ModuleName, + ... +) + +// initParamsKeeper init params keeper and its subspaces +func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey sdk.StoreKey) paramskeeper.Keeper { + ... + paramsKeeper.Subspace(icahosttypes.SubModuleName) + paramsKeeper.Subspace(icacontrollertypes.SubModuleName) + ... +``` + +## Using submodules exclusively + +As described above, the Interchain Accounts application module is structured to support the ability of exclusively enabling controller or host functionality. +This can be achieved by simply omitting either controller or host `Keeper` from the Interchain Accounts `NewAppModule` constructor function, and mounting only the desired submodule via the `IBCRouter`. +Alternatively, submodules can be enabled and disabled dynamically using [on-chain parameters](../06-parameters.md). + +The following snippets show basic examples of statically disabling submodules using `app.go`. + +### Disabling controller chain functionality + +```go +// Create Interchain Accounts AppModule omitting the controller keeper +icaModule := ica.NewAppModule(nil, &app.ICAHostKeeper) + +// Create host IBC Module +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host route +ibcRouter.AddRoute(icahosttypes.SubModuleName, icaHostIBCModule) +``` + +### Disabling host chain functionality + +```go +// Create Interchain Accounts AppModule omitting the host keeper +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, nil) + +// Create your Interchain Accounts authentication module, setting up the Keeper, AppModule and IBCModule appropriately +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.ICAControllerKeeper) +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) +icaAuthIBCModule := icaauth.NewIBCModule(app.ICAAuthKeeper) + +// Create controller IBC application stack +icaControllerStack := icacontroller.NewIBCMiddlewareWithAuth(icaAuthIBCModule, app.ICAControllerKeeper) + +// Register controller and authentication routes +ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icaauthtypes.ModuleName, icaControllerStack) // Note, the authentication module is routed to the top level of the middleware stack +``` diff --git a/docs/docs/02-apps/02-interchain-accounts/10-legacy/03-keeper-api.md b/docs/docs/02-apps/02-interchain-accounts/10-legacy/03-keeper-api.md new file mode 100644 index 0000000..8209477 --- /dev/null +++ b/docs/docs/02-apps/02-interchain-accounts/10-legacy/03-keeper-api.md @@ -0,0 +1,123 @@ +--- +title: Keeper API +sidebar_label: Keeper API +sidebar_position: 3 +slug: /apps/interchain-accounts/legacy/keeper-api +--- + + +# Keeper API + +## Deprecation Notice + +**This document is deprecated and will be removed in future releases**. + +The controller submodule keeper exposes two legacy functions that allow respectively for custom authentication modules to register interchain accounts and send packets to the interchain account. + +## `RegisterInterchainAccount` + +The authentication module can begin registering interchain accounts by calling `RegisterInterchainAccount`: + +```go +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, connectionID, owner.String(), version, channeltypes.UNORDERED); err != nil { + return err +} + +return nil +``` + +The `version` argument is used to support ICS-29 fee middleware for relayer incentivization of ICS-27 packets. The `ordering` argument allows to specify the ordering of the channel that is created; if `NONE` is passed, then the default ordering will be `UNORDERED`. Consumers of the `RegisterInterchainAccount` are expected to build the appropriate JSON encoded version string themselves and pass it accordingly. If an empty string is passed in the `version` argument, then the version will be initialized to a default value in the `OnChanOpenInit` callback of the controller's handler, so that channel handshake can proceed. + +The following code snippet illustrates how to construct an appropriate interchain accounts `Metadata` and encode it as a JSON bytestring: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, controllerConnectionID, owner.String(), string(appVersion), channeltypes.UNORDERED); err != nil { + return err +} +``` + +Similarly, if the application stack is configured to route through ICS-29 fee middleware and a fee enabled channel is desired, construct the appropriate ICS-29 `Metadata` type: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +feeMetadata := feetypes.Metadata{ + AppVersion: string(appVersion), + FeeVersion: feetypes.Version, +} + +feeEnabledVersion, err := feetypes.ModuleCdc.MarshalJSON(&feeMetadata) +if err != nil { + return err +} + +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, controllerConnectionID, owner.String(), string(feeEnabledVersion), channeltypes.UNORDERED); err != nil { + return err +} +``` + +## `SendTx` + +The authentication module can attempt to send a packet by calling `SendTx`: + +```go +// Authenticate owner +// perform custom logic + +// Construct controller portID based on interchain account owner address +portID, err := icatypes.NewControllerPortID(owner.String()) +if err != nil { + return err +} + +// Obtain data to be sent to the host chain. +// In this example, the owner of the interchain account would like to send a bank MsgSend to the host chain. +// The appropriate serialization function should be called. The host chain must be able to deserialize the transaction. +// If the host chain is using the ibc-go host module, `SerializeCosmosTx` should be used. +msg := &banktypes.MsgSend{FromAddress: fromAddr, ToAddress: toAddr, Amount: amt} +data, err := icatypes.SerializeCosmosTx(keeper.cdc, []proto.Message{msg}) +if err != nil { + return err +} + +// Construct packet data +packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, +} + +// Obtain timeout timestamp +// An appropriate timeout timestamp must be determined based on the usage of the interchain account. +// If the packet times out, the channel will be closed requiring a new channel to be created. +timeoutTimestamp := obtainTimeoutTimestamp() + +// Send the interchain accounts packet, returning the packet sequence +seq, err = keeper.icaControllerKeeper.SendTx(ctx, portID, packetData, timeoutTimestamp) +``` + +The data within an `InterchainAccountPacketData` must be serialized using a format supported by the host chain. +If the host chain is using the ibc-go host chain submodule, `SerializeCosmosTx` should be used. If the `InterchainAccountPacketData.Data` is serialized using a format not supported by the host chain, the packet will not be successfully received. diff --git a/docs/docs/02-apps/02-interchain-accounts/10-legacy/_category_.json b/docs/docs/02-apps/02-interchain-accounts/10-legacy/_category_.json new file mode 100644 index 0000000..da34288 --- /dev/null +++ b/docs/docs/02-apps/02-interchain-accounts/10-legacy/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Legacy", + "position": 10, + "link": null +} diff --git a/docs/docs/02-apps/02-interchain-accounts/10-legacy/images/ica-pre-v6.png b/docs/docs/02-apps/02-interchain-accounts/10-legacy/images/ica-pre-v6.png new file mode 100644 index 0000000..4529b23 Binary files /dev/null and b/docs/docs/02-apps/02-interchain-accounts/10-legacy/images/ica-pre-v6.png differ diff --git a/docs/docs/02-apps/02-interchain-accounts/_category_.json b/docs/docs/02-apps/02-interchain-accounts/_category_.json new file mode 100644 index 0000000..41e3ac2 --- /dev/null +++ b/docs/docs/02-apps/02-interchain-accounts/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Interchain Accounts", + "position": 2, + "link": null +} \ No newline at end of file diff --git a/docs/docs/02-apps/02-interchain-accounts/images/ica-v6.png b/docs/docs/02-apps/02-interchain-accounts/images/ica-v6.png new file mode 100644 index 0000000..abe3eba Binary files /dev/null and b/docs/docs/02-apps/02-interchain-accounts/images/ica-v6.png differ diff --git a/docs/docs/02-apps/_category_.json b/docs/docs/02-apps/_category_.json new file mode 100644 index 0000000..83a389b --- /dev/null +++ b/docs/docs/02-apps/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "IBC Application Modules", + "position": 2, + "link": null +} \ No newline at end of file diff --git a/docs/docs/03-light-clients/01-developer-guide/01-overview.md b/docs/docs/03-light-clients/01-developer-guide/01-overview.md new file mode 100644 index 0000000..8545d22 --- /dev/null +++ b/docs/docs/03-light-clients/01-developer-guide/01-overview.md @@ -0,0 +1,95 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /ibc/light-clients/overview +--- + +# Overview + +:::note Synopsis +Learn how to build IBC light client modules and fulfill the interfaces required to integrate with core IBC. +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../../01-ibc/01-overview.md) +- [IBC Transport, Authentication, and Ordering Layer - Clients](https://tutorials.cosmos.network/academy/3-ibc/4-clients.html) +- [ICS-002 Client Semantics](https://github.com/cosmos/ibc/tree/main/spec/core/ics-002-client-semantics) + +::: + +IBC uses light clients in order to provide trust-minimized interoperability between sovereign blockchains. Light clients operate under a strict set of rules which provide security guarantees for state updates and facilitate the ability to verify the state of a remote blockchain using merkle proofs. + +The following aims to provide a high level IBC light client module developer guide. Access to IBC light clients are gated by the core IBC `MsgServer` which utilizes the abstractions set by the `02-client` submodule to call into a light client module. A light client module developer is only required to implement a set of interfaces as defined in the `modules/core/exported` package of ibc-go. + +A light client module developer should be concerned with three main interfaces: + +- [`LightClientModule`](#lightclientmodule) a module which manages many light client instances of a certain type. +- [`ClientState`](#clientstate) encapsulates the light client implementation and its semantics. +- [`ConsensusState`](#consensusstate) tracks consensus data used for verification of client updates, misbehaviour detection and proof verification of counterparty state. +- [`ClientMessage`](#clientmessage) used for submitting block headers for client updates and submission of misbehaviour evidence using conflicting headers. + +Throughout this guide the `07-tendermint` light client module may be referred to as a reference example. + +## Concepts and vocabulary + +### `LightClientModule` + +`LightClientModule` is an interface defined by core IBC which allows for modular light client implementations. All light client implementations *must* implement the [`LightClientModule` interface](https://github.com/cosmos/ibc-go/blob/501a8462345da099144efe91d495bfcfa18d760d/modules/core/exported/client.go#L51) so that core IBC may redirect calls to the light client module. + +For example a light client module may need to: + +- create clients +- update clients +- recover and upgrade clients +- verify membership and non-membership + +The methods which make up this interface are detailed at a more granular level in the [`LightClientModule` section of this guide](02-light-client-module.md). + +Please refer to the `07-tendermint`'s [`LightClientModule` definition](https://github.com/cosmos/ibc-go/blob/501a8462345da099144efe91d495bfcfa18d760d/modules/light-clients/07-tendermint/light_client_module.go#L17) for more information. + +### `ClientState` + +`ClientState` is a term used to define the data structure which encapsulates opaque light client state. The `ClientState` contains all the information needed to verify a `ClientMessage` and perform membership and non-membership proof verification of counterparty state. This includes properties that refer to the remote state machine, the light client type and the specific light client instance. + +For example: + +- Constraints used for client updates. +- Constraints used for misbehaviour detection. +- Constraints used for state verification. +- Constraints used for client upgrades. + +The `ClientState` type maintained within the light client module *must* implement the [`ClientState`](https://github.com/cosmos/ibc-go/tree/02-client-refactor-beta1/modules/core/exported/client.go#L36) interface defined in `core/modules/exported/client.go`. +The methods which make up this interface are detailed at a more granular level in the [`ClientState` section of this guide](03-client-state.md). + +Please refer to the `07-tendermint` light client module's [`ClientState` definition](https://github.com/cosmos/ibc-go/tree/02-client-refactor-beta1/proto/ibc/lightclients/tendermint/v1/tendermint.proto#L18) containing information such as chain ID, status, latest height, unbonding period and proof specifications. + +### `ConsensusState` + +`ConsensusState` is a term used to define the data structure which encapsulates consensus data at a particular point in time, i.e. a unique height or sequence number of a state machine. There must exist a single trusted `ConsensusState` for each height. `ConsensusState` generally contains a trusted root, validator set information and timestamp. + +For example, the `ConsensusState` of the `07-tendermint` light client module defines a trusted root which is used by the `ClientState` to perform verification of membership and non-membership commitment proofs, as well as the next validator set hash used for verifying headers can be trusted in client updates. + +The `ConsensusState` type maintained within the light client module *must* implement the [`ConsensusState`](https://github.com/cosmos/ibc-go/tree/02-client-refactor-beta1/modules/core/exported/client.go#L134) interface defined in `modules/core/exported/client.go`. +The methods which make up this interface are detailed at a more granular level in the [`ConsensusState` section of this guide](04-consensus-state.md). + +### `Height` + +`Height` defines a monotonically increasing sequence number which provides ordering of consensus state data persisted through client updates. +IBC light client module developers are expected to use the [concrete type](https://github.com/cosmos/ibc-go/tree/02-client-refactor-beta1/proto/ibc/core/client/v1/client.proto#L89) provided by the `02-client` submodule. This implements the expectations required by the [`Height`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L156) interface defined in `modules/core/exported/client.go`. + +### `ClientMessage` + +`ClientMessage` refers to the interface type [`ClientMessage`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L147) used for performing updates to a `ClientState` stored on chain. +This may be any concrete type which produces a change in state to the IBC client when verified. + +The following are considered as valid update scenarios: + +- A block header which when verified inserts a new `ConsensusState` at a unique height. +- A batch of block headers which when verified inserts `N` `ConsensusState` instances for `N` unique heights. +- Evidence of misbehaviour provided by two conflicting block headers. + +Learn more in the [Handling update and misbehaviour](05-updates-and-misbehaviour.md) section. diff --git a/docs/docs/03-light-clients/01-developer-guide/02-light-client-module.md b/docs/docs/03-light-clients/01-developer-guide/02-light-client-module.md new file mode 100644 index 0000000..41f53dd --- /dev/null +++ b/docs/docs/03-light-clients/01-developer-guide/02-light-client-module.md @@ -0,0 +1,76 @@ +--- +title: Light Client Module interface +sidebar_label: Light Client Module interface +sidebar_position: 2 +slug: /ibc/light-clients/light-client-module +--- + + +# Implementing the `LightClientModule` interface + +## `Status` method + +`Status` must return the status of the client. + +- An `Active` status indicates that clients are allowed to process packets. +- A `Frozen` status indicates that misbehaviour was detected in the counterparty chain and the client is not allowed to be used. +- An `Expired` status indicates that a client is not allowed to be used because it was not updated for longer than the trusting period. +- An `Unknown` status indicates that there was an error in determining the status of a client. + +All possible `Status` types can be found [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L22-L32). + +This field is returned in the response of the gRPC [`ibc.core.client.v1.Query/ClientStatus`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/types/query.pb.go#L665) endpoint. + +## `TimestampAtHeight` method + +`TimestampAtHeight` must return the timestamp for the consensus state associated with the provided height. +This value is used to facilitate timeouts by checking the packet timeout timestamp against the returned value. + +## `LatestHeight` method + +`LatestHeight` should return the latest block height that the client state represents. + +## `Initialize` method + +Clients must validate the initial consensus state, and set the initial client state and consensus state in the provided client store. +Clients may also store any necessary client-specific metadata. + +`Initialize` is called when a [client is created](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/keeper/client.go#L30). + +## `UpdateState` method + +`UpdateState` updates and stores as necessary any associated information for an IBC client, such as the `ClientState` and corresponding `ConsensusState`. See section [`UpdateState`](05-updates-and-misbehaviour.md#updatestate) for more information. + +## `UpdateStateOnMisbehaviour` method + +`UpdateStateOnMisbehaviour` should perform appropriate state changes on a client state given that misbehaviour has been detected and verified. See section [`UpdateStateOnMisbehaviour`](05-updates-and-misbehaviour.md#updatestateonmisbehaviour) for more information. + +## `VerifyMembership` method + +`VerifyMembership` must verify the existence of a value at a given commitment path at the specified height. For more information about membership proofs +see the [Existence and non-existence proofs section](07-proofs.md). + +## `VerifyNonMembership` method + +`VerifyNonMembership` must verify the absence of a value at a given commitment path at a specified height. For more information about non-membership proofs +see the [Existence and non-existence proofs section](07-proofs.md). + +## `VerifyClientMessage` method + +`VerifyClientMessage` must verify a `ClientMessage`. A `ClientMessage` could be a `Header`, `Misbehaviour`, or batch update. +It must handle each type of `ClientMessage` appropriately. Calls to `CheckForMisbehaviour`, `UpdateState`, and `UpdateStateOnMisbehaviour` +will assume that the content of the `ClientMessage` has been verified and can be trusted. An error should be returned +if the ClientMessage fails to verify. See section [`VerifyClientMessage`](05-updates-and-misbehaviour.md#verifyclientmessage) for more information. + +## `CheckForMisbehaviour` method + +Checks for evidence of a misbehaviour in `Header` or `Misbehaviour` type. It assumes the `ClientMessage` +has already been verified. See section [`CheckForMisbehaviour`](05-updates-and-misbehaviour.md#checkformisbehaviour) for more information. + +## `RecoverClient` method + +`RecoverClient` is used to recover an expired or frozen client by updating the client with the state of a substitute client. The method must verify that the provided substitute may be used to update the subject client. See section [Implementing `RecoverClient`](./08-proposals.md#implementing-recoverclient) for more information. + +## `VerifyUpgradeAndUpdateState` method + +`VerifyUpgradeAndUpdateState` provides a path to upgrading clients given an upgraded `ClientState`, upgraded `ConsensusState` and proofs for each. See section [Implementing `VerifyUpgradeAndUpdateState`](./06-upgrades.md#implementing-verifyupgradeandupdatestate) for more information. diff --git a/docs/docs/03-light-clients/01-developer-guide/03-client-state.md b/docs/docs/03-light-clients/01-developer-guide/03-client-state.md new file mode 100644 index 0000000..f35102e --- /dev/null +++ b/docs/docs/03-light-clients/01-developer-guide/03-client-state.md @@ -0,0 +1,21 @@ +--- +title: Client State interface +sidebar_label: Client State interface +sidebar_position: 3 +slug: /ibc/light-clients/client-state +--- + + +# Implementing the `ClientState` interface + +Learn how to implement the [`ClientState`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L36) interface. + +## `ClientType` method + +`ClientType` should return a unique string identifier of the light client. This will be used when generating a client identifier. +The format is created as follows: `{client-type}-{N}` where `{N}` is the unique global nonce associated with a specific client (e.g `07-tendermint-0`). + +## `Validate` method + +`Validate` should validate every client state field and should return an error if any value is invalid. The light client +implementer is in charge of determining which checks are required. See the [Tendermint light client implementation](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/client_state.go#L111) as a reference. diff --git a/docs/docs/03-light-clients/01-developer-guide/04-consensus-state.md b/docs/docs/03-light-clients/01-developer-guide/04-consensus-state.md new file mode 100644 index 0000000..c470cd3 --- /dev/null +++ b/docs/docs/03-light-clients/01-developer-guide/04-consensus-state.md @@ -0,0 +1,27 @@ +--- +title: Consensus State interface +sidebar_label: Consensus State interface +sidebar_position: 4 +slug: /ibc/light-clients/consensus-state +--- + + +# Implementing the `ConsensusState` interface + +A `ConsensusState` is the snapshot of the counterparty chain, that an IBC client uses to verify proofs (e.g. a block). + +The further development of multiple types of IBC light clients and the difficulties presented by this generalization problem (see [ADR-006](https://github.com/cosmos/ibc-go/blob/main/docs/architecture/adr-006-02-client-refactor.md) for more information about this historical context) led to the design decision of each client keeping track of and set its own `ClientState` and `ConsensusState`, as well as the simplification of client `ConsensusState` updates through the generalized `ClientMessage` interface. + +The below [`ConsensusState`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L133) interface is a generalized interface for the types of information a `ConsensusState` could contain. For a reference `ConsensusState` implementation, please see the [Tendermint light client `ConsensusState`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/consensus_state.go). + +## `ClientType` method + +This is the type of client consensus. It should be the same as the `ClientType` return value for the [corresponding `ClientState` implementation](03-client-state.md). + +## `GetTimestamp` method + +`GetTimestamp` should return the timestamp (in nanoseconds) of the consensus state snapshot. This function has been deprecated and will be removed in a future release. + +## `ValidateBasic` method + +`ValidateBasic` should validate every consensus state field and should return an error if any value is invalid. The light client implementer is in charge of determining which checks are required. diff --git a/docs/docs/03-light-clients/01-developer-guide/05-updates-and-misbehaviour.md b/docs/docs/03-light-clients/01-developer-guide/05-updates-and-misbehaviour.md new file mode 100644 index 0000000..fb25e37 --- /dev/null +++ b/docs/docs/03-light-clients/01-developer-guide/05-updates-and-misbehaviour.md @@ -0,0 +1,104 @@ +--- +title: Handling Updates and Misbehaviour +sidebar_label: Handling Updates and Misbehaviour +sidebar_position: 5 +slug: /ibc/light-clients/updates-and-misbehaviour +--- + + +# Handling `ClientMessage`s: updates and misbehaviour + +As mentioned before in the documentation about [implementing the `ConsensusState` interface](04-consensus-state.md), [`ClientMessage`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L147) is an interface used to update an IBC client. This update may be performed by: + +- a single header, +- a batch of headers, +- evidence of misbehaviour, +- or any type which when verified produces a change to the consensus state of the IBC client. + +This interface has been purposefully kept generic in order to give the maximum amount of flexibility to the light client implementer. + +## Implementing the `ClientMessage` interface + +Find the `ClientMessage` interface in `modules/core/exported`: + +```go +type ClientMessage interface { + proto.Message + + ClientType() string + ValidateBasic() error +} +``` + +The `ClientMessage` will be passed to the client to be used in [`UpdateClient`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/keeper/client.go#L48), which retrieves the `LightClientModule` by client type (parsed from the client ID available in `MsgUpdateClient`). This `LightClientModule` implements the [`LightClientModule` interface](02-light-client-module.md) for its specific consenus type (e.g. Tendermint). + +`UpdateClient` will then handle a number of cases including misbehaviour and/or updating the consensus state, utilizing the specific methods defined in the relevant `LightClientModule`. + +```go +VerifyClientMessage(ctx sdk.Context, clientID string, clientMsg ClientMessage) error +CheckForMisbehaviour(ctx sdk.Context, clientID string, clientMsg ClientMessage) bool +UpdateStateOnMisbehaviour(ctx sdk.Context, clientID string, clientMsg ClientMessage) +UpdateState(ctx sdk.Context, clientID string, clientMsg ClientMessage) []Height +``` + +## Handling updates and misbehaviour + +The functions for handling updates to a light client and evidence of misbehaviour are all found in the [`LightClientModule`](https://github.com/cosmos/ibc-go/blob/501a8462345da099144efe91d495bfcfa18d760d/modules/core/exported/client.go#L51) interface, and will be discussed below. + +> It is important to note that `Misbehaviour` in this particular context is referring to misbehaviour on the chain level intended to fool the light client. This will be defined by each light client. + +## `VerifyClientMessage` + +`VerifyClientMessage` must verify a `ClientMessage`. A `ClientMessage` could be a `Header`, `Misbehaviour`, or batch update. To understand how to implement a `ClientMessage`, please refer to the [Implementing the `ClientMessage` interface](#implementing-the-clientmessage-interface) section. + +It must handle each type of `ClientMessage` appropriately. Calls to `CheckForMisbehaviour`, `UpdateState`, and `UpdateStateOnMisbehaviour` will assume that the content of the `ClientMessage` has been verified and can be trusted. An error should be returned if the `ClientMessage` fails to verify. + +For an example of a `VerifyClientMessage` implementation, please check the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/76730ff030b52a351096ee941b7e4da44af9f059/modules/light-clients/07-tendermint/update.go#L23). + +## `CheckForMisbehaviour` + +Checks for evidence of a misbehaviour in `Header` or `Misbehaviour` type. It assumes the `ClientMessage` has already been verified. + +For an example of a `CheckForMisbehaviour` implementation, please check the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/76730ff030b52a351096ee941b7e4da44af9f059/modules/light-clients/07-tendermint/misbehaviour_handle.go#L22). + +> The Tendermint light client [defines `Misbehaviour`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/misbehaviour.go) as two different types of situations: a situation where two conflicting `Header`s with the same height have been submitted to update a client's `ConsensusState` within the same trusting period, or that the two conflicting `Header`s have been submitted at different heights but the consensus states are not in the correct monotonic time ordering (BFT time violation). More explicitly, updating to a new height must have a timestamp greater than the previous consensus state, or, if inserting a consensus at a past height, then time must be less than those heights which come after and greater than heights which come before. + +## `UpdateStateOnMisbehaviour` + +`UpdateStateOnMisbehaviour` should perform appropriate state changes on a client state given that misbehaviour has been detected and verified. This method should only be called when misbehaviour is detected, as it does not perform any misbehaviour checks. Notably, it should freeze the client so that calling the `Status` function on the associated client state no longer returns `Active`. + +For an example of a `UpdateStateOnMisbehaviour` implementation, please check the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/76730ff030b52a351096ee941b7e4da44af9f059/modules/light-clients/07-tendermint/update.go#L202). + +## `UpdateState` + +`UpdateState` updates and stores as necessary any associated information for an IBC client, such as the `ClientState` and corresponding `ConsensusState`. It should perform a no-op on duplicate updates. + +It assumes the `ClientMessage` has already been verified. + +For an example of a `UpdateState` implementation, please check the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/76730ff030b52a351096ee941b7e4da44af9f059/modules/light-clients/07-tendermint/update.go#L134). + +## Putting it all together + +The `02-client` `Keeper` module in ibc-go offers a reference as to how these functions will be used to [update the client](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/keeper/client.go#L48). + +```go +clientModule, found := k.router.GetRoute(clientID) +if !found { + return errorsmod.Wrap(types.ErrRouteNotFound, clientID) +} + +if err := clientModule.VerifyClientMessage(ctx, clientID, clientMsg); err != nil { + return err +} + +foundMisbehaviour := clientModule.CheckForMisbehaviour(ctx, clientID, clientMsg) +if foundMisbehaviour { + clientModule.UpdateStateOnMisbehaviour(ctx, clientID, clientMsg) + // emit misbehaviour event + return +} + +clientModule.UpdateState(ctx, clientID, clientMsg) // expects no-op on duplicate header +// emit update event +return +``` diff --git a/docs/docs/03-light-clients/01-developer-guide/06-upgrades.md b/docs/docs/03-light-clients/01-developer-guide/06-upgrades.md new file mode 100644 index 0000000..7816509 --- /dev/null +++ b/docs/docs/03-light-clients/01-developer-guide/06-upgrades.md @@ -0,0 +1,62 @@ +--- +title: Handling Upgrades +sidebar_label: Handling Upgrades +sidebar_position: 6 +slug: /ibc/light-clients/upgrades +--- + + +# Handling upgrades + +It is vital that high-value IBC clients can upgrade along with their underlying chains to avoid disruption to the IBC ecosystem. Thus, IBC client developers will want to implement upgrade functionality to enable clients to maintain connections and channels even across chain upgrades. + +## Implementing `VerifyUpgradeAndUpdateState` + +The IBC protocol allows client implementations to provide a path to upgrading clients given the upgraded `ClientState`, upgraded `ConsensusState` and proofs for each. This path is provided in the `VerifyUpgradeAndUpdateState` method: + +```go +// NOTE: proof heights are not included as upgrade to a new revision is expected to pass only on the last +// height committed by the current revision. Clients are responsible for ensuring that the planned last +// height of the current revision is somehow encoded in the proof verification process. +// This is to ensure that no premature upgrades occur, since upgrade plans committed to by the counterparty +// may be cancelled or modified before the last planned height. +// If the upgrade is verified, the upgraded client and consensus states must be set in the client store. +func (l LightClientModule) VerifyUpgradeAndUpdateState( + ctx sdk.Context, + clientID string, + newClient []byte, + newConsState []byte, + upgradeClientProof, + upgradeConsensusStateProof []byte, +) error +``` + +> Please refer to the [Tendermint light client implementation](https://github.com/cosmos/ibc-go/blob/47162061bcbfe74df791161059715a635e31c604/modules/light-clients/07-tendermint/light_client_module.go#L257) as an example for implementation. + +It is important to note that light clients **must** handle all management of client and consensus states including the setting of updated `ClientState` and `ConsensusState` in the client store. This can include verifying that the submitted upgraded `ClientState` is of a valid `ClientState` type, that the height of the upgraded client is not greater than the height of the current client (in order to preserve BFT monotonic time), or that certain parameters which should not be changed have not been altered in the upgraded `ClientState`. + +Developers must ensure that the `MsgUpgradeClient` does not pass until the last height of the old chain has been committed, and after the chain upgrades, the `MsgUpgradeClient` should pass once and only once on all counterparty clients. + +### Upgrade path + +Clients should have **prior knowledge of the merkle path** that the upgraded client and upgraded consensus states will use. The height at which the upgrade has occurred should also be encoded in the proof. + +> The Tendermint client implementation accomplishes this by including an `UpgradePath` in the `ClientState` itself, which is used along with the upgrade height to construct the merkle path under which the client state and consensus state are committed. + +## Chain specific vs client specific client parameters + +Developers should maintain the distinction between client parameters that are uniform across every valid light client of a chain (chain-chosen parameters), and client parameters that are customizable by each individual client (client-chosen parameters). + +When upgrading a client, developers must ensure that the new client adopts all of the new client parameters that must be uniform across every valid light client of a chain (chain-chosen parameters), while maintaining the client parameters that are customizable by each individual client (client-chosen parameters) from the previous version of the client. + +## Security + +Upgrades must adhere to the IBC Security Model. IBC does not rely on the assumption of honest relayers for correctness. Thus users should not have to rely on relayers to maintain client correctness and security (though honest relayers must exist to maintain relayer liveness). While relayers may choose any set of client parameters while creating a new `ClientState`, this still holds under the security model since users can always choose a relayer-created client that suits their security and correctness needs or create a client with their desired parameters if no such client exists. + +However, when upgrading an existing client, one must keep in mind that there are already many users who depend on this client's particular parameters. **We cannot give the upgrading relayer free choice over these parameters once they have already been chosen. This would violate the security model** since users who rely on the client would have to rely on the upgrading relayer to maintain the same level of security. + +Thus, developers must make sure that their upgrade mechanism allows clients to upgrade the chain-specified parameters whenever a chain upgrade changes these parameters (examples in the Tendermint client include `UnbondingPeriod`, `TrustingPeriod`, `ChainID`, `UpgradePath`, etc), while ensuring that the relayer submitting the `MsgUpgradeClient` cannot alter the client-chosen parameters that the users are relying upon (examples in Tendermint client include `TrustLevel`, `MaxClockDrift`, etc). + +### Document potential client parameter conflicts during upgrades + +Counterparty clients can upgrade securely by using all of the chain-chosen parameters from the chain-committed `UpgradedClient` and preserving all of the old client-chosen parameters. This enables chains to securely upgrade without relying on an honest relayer, however it can in some cases lead to an invalid final `ClientState` if the new chain-chosen parameters clash with the old client-chosen parameter. This can happen in the Tendermint client case if the upgrading chain lowers the `UnbondingPeriod` (chain-chosen) to a duration below that of a counterparty client's `TrustingPeriod` (client-chosen). Such cases should be clearly documented by developers, so that chains know which upgrades should be avoided to prevent this problem. The final upgraded client should also be validated in `VerifyUpgradeAndUpdateState` before returning to ensure that the client does not upgrade to an invalid `ClientState`. diff --git a/docs/docs/03-light-clients/01-developer-guide/07-proofs.md b/docs/docs/03-light-clients/01-developer-guide/07-proofs.md new file mode 100644 index 0000000..60fcc53 --- /dev/null +++ b/docs/docs/03-light-clients/01-developer-guide/07-proofs.md @@ -0,0 +1,71 @@ +--- +title: Existence/Non-Existence Proofs +sidebar_label: Existence/Non-Existence Proofs +sidebar_position: 7 +slug: /ibc/light-clients/proofs +--- + + +# Existence and non-existence proofs + +IBC uses merkle proofs in order to verify the state of a remote counterparty state machine given a trusted root, and [ICS-23](https://github.com/cosmos/ics23/tree/master/go) is a general approach for verifying merkle trees which is used in ibc-go. + +Currently, all Cosmos SDK modules contain their own stores, which maintain the state of the application module in an IAVL (immutable AVL) binary merkle tree format. Specifically with regard to IBC, core IBC maintains its own IAVL store, and IBC apps (e.g. transfer) maintain their own dedicated stores. The Cosmos SDK multistore therefore creates a simple merkle tree of all of these IAVL trees, and from each of these individual IAVL tree root hashes it derives a root hash for the application state tree as a whole (the `AppHash`). + +For the purposes of ibc-go, there are two types of proofs which are important: existence and non-existence proofs, terms which have been used interchangeably with membership and non-membership proofs. For the purposes of this guide, we will stick with "existence" and "non-existence". + +## Existence proofs + +Existence proofs are used in IBC transactions which involve verification of counterparty state for transactions which will result in the writing of provable state. For example, this includes verification of IBC store state for handshakes and packets. + +Put simply, existence proofs prove that a particular key and value exists in the tree. Under the hood, an IBC existence proof is comprised of two proofs: an IAVL proof that the key exists in IBC store/IBC root hash, and a proof that the IBC root hash exists in the multistore root hash. + +## Non-existence proofs + +Non-existence proofs verify the absence of data stored within counterparty state and are used to prove that a key does NOT exist in state. As stated above, these types of proofs can be used to timeout packets by proving that the counterparty has not written a packet receipt into the store, meaning that a token transfer has NOT successfully occurred. + +Some trees (e.g. SMT) may have a sentinel empty child for non-existent keys. In this case, the ICS-23 proof spec should include this `EmptyChild` so that ICS-23 handles the non-existence proof correctly. + +In some cases, there is a necessity to "mock" non-existence proofs if the counterparty does not have ability to prove absence. Since the verification method is designed to give complete control to client implementations, clients can support chains that do not provide absence proofs by verifying the existence of a non-empty sentinel `ABSENCE` value. In these special cases, the proof provided will be an ICS-23 `Existence` proof, and the client will verify that the `ABSENCE` value is stored under the given path for the given height. + +## State verification methods: `VerifyMembership` and `VerifyNonMembership` + +The state verification functions for all IBC data types have been consolidated into two generic methods, `VerifyMembership` and `VerifyNonMembership`. + +From the [`LightClientModule` interface definition](https://github.com/cosmos/ibc-go/blob/main/modules/core/exported/client.go#L56), we find: + +```go +// VerifyMembership is a generic proof verification method which verifies +// a proof of the existence of a value at a given CommitmentPath at the +// specified height. The caller is expected to construct the full CommitmentPath +// from a CommitmentPrefix and a standardized path (as defined in ICS 24). +VerifyMembership( + ctx sdk.Context, + clientID string, + height Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path Path, + value []byte, +) error + +// VerifyNonMembership is a generic proof verification method which verifies +// the absence of a given CommitmentPath at a specified height. The caller is +// expected to construct the full CommitmentPath from a CommitmentPrefix and +// a standardized path (as defined in ICS 24). +VerifyNonMembership( + ctx sdk.Context, + clientStore sdk.KVStore, + cdc codec.BinaryCodec, + height Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path Path, +) error +``` + +Both are expected to be provided with a standardised key path, `exported.Path`, as defined in [ICS-24 host requirements](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements). Membership verification requires callers to provide the value marshalled as `[]byte`. Delay period values should be zero for non-packet processing verification. A zero proof height is now allowed by core IBC and may be passed into `VerifyMembership` and `VerifyNonMembership`. Light clients are responsible for returning an error if a zero proof height is invalid behaviour. + +Please refer to the [ICS-23 implementation](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/23-commitment/types/merkle.go#L131-L205) for a concrete example. diff --git a/docs/docs/03-light-clients/01-developer-guide/08-proposals.md b/docs/docs/03-light-clients/01-developer-guide/08-proposals.md new file mode 100644 index 0000000..38bdf1e --- /dev/null +++ b/docs/docs/03-light-clients/01-developer-guide/08-proposals.md @@ -0,0 +1,36 @@ +--- +title: Handling Proposals +sidebar_label: Handling Proposals +sidebar_position: 8 +slug: /ibc/light-clients/proposals +--- + + +# Handling proposals + +It is possible to update the client with the state of the substitute client through a governance proposal. This type of governance proposal is typically used to recover an expired or frozen client, as it can recover the entire state and therefore all existing channels built on top of the client. `RecoverClient` should be implemented to handle the proposal. + +## Implementing `RecoverClient` + +In the [`LightClientModule` interface](https://github.com/cosmos/ibc-go/blob/501a8462345da099144efe91d495bfcfa18d760d/modules/core/exported/client.go#L51), we find: + +```go +// RecoverClient must verify that the provided substitute +// may be used to update the subject client. The light client +// must set the updated client and consensus states within +// the clientStore for the subject client. +RecoverClient( + ctx sdk.Context, + clientID, + substituteClientID string, +) error +``` + +Prior to updating, this function must verify that: + +- the substitute client is the same type as the subject client. For a reference implementation, please see the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/47162061bcbfe74df791161059715a635e31c604/modules/light-clients/07-tendermint/proposal_handle.go#L34). +- the provided substitute may be used to update the subject client. This may mean that certain parameters must remain unaltered. For example, a [valid substitute Tendermint light client](https://github.com/cosmos/ibc-go/blob/47162061bcbfe74df791161059715a635e31c604/modules/light-clients/07-tendermint/proposal_handle.go#L86) must NOT change the chain ID, trust level, max clock drift, unbonding period, proof specs or upgrade path. Please note that `AllowUpdateAfterMisbehaviour` and `AllowUpdateAfterExpiry` have been deprecated (see ADR 026 for more information). + +After these checks are performed, the function must [set the updated client and consensus states](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/proposal_handle.go#L77) within the client store for the subject client. + +Please refer to the [Tendermint light client implementation](https://github.com/cosmos/ibc-go/blob/47162061bcbfe74df791161059715a635e31c604/modules/light-clients/07-tendermint/proposal_handle.go#L79) for reference. diff --git a/docs/docs/03-light-clients/01-developer-guide/09-setup.md b/docs/docs/03-light-clients/01-developer-guide/09-setup.md new file mode 100644 index 0000000..61e5e74 --- /dev/null +++ b/docs/docs/03-light-clients/01-developer-guide/09-setup.md @@ -0,0 +1,135 @@ +--- +title: Setup +sidebar_label: Setup +sidebar_position: 9 +slug: /ibc/light-clients/setup +--- + + +# Setup + +:::note Synopsis +Learn how to configure light client modules and create clients using core IBC and the `02-client` submodule. +::: + +A last step to finish the development of the light client, is to implement the `AppModuleBasic` interface to allow it to be added to the chain's `app.go` alongside other light client types the chain enables. + +Finally, a succinct rundown is given of the remaining steps to make the light client operational, getting the light client type passed through governance and creating the clients. + +## Configuring a light client module + +An IBC light client module must implement the [`AppModuleBasic`](https://github.com/cosmos/cosmos-sdk/blob/main/types/module/module.go#L50) interface in order to register its concrete types against the core IBC interfaces defined in `modules/core/exported`. This is accomplished via the `RegisterInterfaces` method which provides the light client module with the opportunity to register codec types using the chain's `InterfaceRegistry`. Please refer to the [`07-tendermint` codec registration](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/codec.go#L11). + +The `AppModuleBasic` interface may also be leveraged to install custom CLI handlers for light client module users. Light client modules can safely no-op for interface methods which it does not wish to implement. + +Please refer to the [core IBC documentation](../../01-ibc/02-integration.md#integrating-light-clients) for how to configure additional light client modules alongside `07-tendermint` in `app.go`. + +See below for an example of the `07-tendermint` implementation of `AppModuleBasic`. + +```go +var _ module.AppModuleBasic = AppModuleBasic{} + +// AppModuleBasic defines the basic application module used by the tendermint light client. +// Only the RegisterInterfaces function needs to be implemented. All other function perform +// a no-op. +type AppModuleBasic struct{} + +// Name returns the tendermint module name. +func (AppModuleBasic) Name() string { + return ModuleName +} + +// RegisterLegacyAminoCodec performs a no-op. The Tendermint client does not support amino. +func (AppModuleBasic) RegisterLegacyAminoCodec(*codec.LegacyAmino) {} + +// RegisterInterfaces registers module concrete types into protobuf Any. This allows core IBC +// to unmarshal tendermint light client types. +func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) { + RegisterInterfaces(registry) +} + +// DefaultGenesis performs a no-op. Genesis is not supported for the tendermint light client. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return nil +} + +// ValidateGenesis performs a no-op. Genesis is not supported for the tendermint light client. +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { + return nil +} + +// RegisterGRPCGatewayRoutes performs a no-op. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {} + +// GetTxCmd performs a no-op. Please see the 02-client cli commands. +func (AppModuleBasic) GetTxCmd() *cobra.Command { + return nil +} + +// GetQueryCmd performs a no-op. Please see the 02-client cli commands. +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return nil +} +``` + +## Creating clients + +A client is created by executing a new `MsgCreateClient` transaction composed with a valid `ClientState` and initial `ConsensusState` encoded as protobuf `Any`s. +Generally, this is performed by an off-chain process known as an [IBC relayer](https://github.com/cosmos/ibc/tree/main/spec/relayer/ics-018-relayer-algorithms) however, this is not a strict requirement. + +See below for a list of IBC relayer implementations: + +- [cosmos/relayer](https://github.com/cosmos/relayer) +- [informalsystems/hermes](https://github.com/informalsystems/hermes) +- [confio/ts-relayer](https://github.com/confio/ts-relayer) + +Stateless checks are performed within the [`ValidateBasic`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/types/msgs.go#L48) method of `MsgCreateClient`. + +```protobuf +// MsgCreateClient defines a message to create an IBC client +message MsgCreateClient { + option (gogoproto.goproto_getters) = false; + + // light client state + google.protobuf.Any client_state = 1 [(gogoproto.moretags) = "yaml:\"client_state\""]; + // consensus state associated with the client that corresponds to a given + // height. + google.protobuf.Any consensus_state = 2 [(gogoproto.moretags) = "yaml:\"consensus_state\""]; + // signer address + string signer = 3; +} +``` + +Leveraging protobuf `Any` encoding allows core IBC to [unpack](https://github.com/cosmos/ibc-go/blob/47162061bcbfe74df791161059715a635e31c604/modules/core/keeper/msg_server.go#L38) the `ClientState` into its respective interface type registered previously using the light client module's `RegisterInterfaces` method. + +Within the `02-client` submodule, the [`ClientState` is then initialized](https://github.com/cosmos/ibc-go/blob/47162061bcbfe74df791161059715a635e31c604/modules/core/02-client/keeper/client.go#L40-L42) with its own isolated key-value store, namespaced using a unique client identifier. + +In order to successfully create an IBC client using a new client type, it [must be supported](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/keeper/client.go#L19-L25). Light client support in IBC is gated by on-chain governance. The allow list may be updated by submitting a new governance proposal to update the `02-client` parameter `AllowedClients`. + +See below for example: + +```shell +%s tx gov submit-proposal --from +``` + +where `proposal.json` contains: + +```json +{ + "title": "IBC Clients Param Change", + "summary": "Update allowed clients", + "messages": [ + { + "@type": "/ibc.core.client.v1.MsgUpdateParams", + "signer": "cosmos1...", // The gov module account address + "params": { + "allowed_clients": ["06-solomachine", "07-tendermint", "0x-new-client"] + } + } + ], + "metadata": "AQ==", + "deposit": "100stake" +} +``` + +If the `AllowedClients` list contains a single element that is equal to the wildcard `"*"`, then all client types are allowed and it is thus not necessary to submit a governance proposal to update the parameter. diff --git a/docs/docs/03-light-clients/01-developer-guide/_category_.json b/docs/docs/03-light-clients/01-developer-guide/_category_.json new file mode 100644 index 0000000..e1c4f32 --- /dev/null +++ b/docs/docs/03-light-clients/01-developer-guide/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Developer Guide", + "position": 1, + "link": null +} \ No newline at end of file diff --git a/docs/docs/03-light-clients/02-localhost/01-overview.md b/docs/docs/03-light-clients/02-localhost/01-overview.md new file mode 100644 index 0000000..bae284d --- /dev/null +++ b/docs/docs/03-light-clients/02-localhost/01-overview.md @@ -0,0 +1,56 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /ibc/light-clients/localhost/overview +--- + + +# `09-localhost` + +## Overview + +:::note Synopsis +Learn about the 09-localhost light client module. +::: + +The 09-localhost light client module implements a stateless localhost loopback client with the ability to send and +receive IBC packets to and from the same state machine. + +### Context + +In a multichain environment, application developers will be used to developing cross-chain applications through IBC. +From their point of view, whether or not they are interacting with multiple modules on the same chain or on different +chains should not matter. The localhost client module enables a unified interface to interact with different +applications on a single chain, using the familiar IBC application layer semantics. + +### Implementation + +There exists a localhost light client module which can be invoked with the client identifier `09-localhost`. The light +client is stateless, so the `ClientState` is constructed on demand when required. + +To supplement this, a [sentinel `ConnectionEnd` is stored in core IBC](04-connection.md) state with the connection +identifier `connection-localhost`. This enables IBC applications to create channels directly on top of the sentinel +connection which leverage the 09-localhost loopback functionality. + +[State verification](05-state-verification.md) for channel state in handshakes or processing packets is reduced in +complexity, the `09-localhost` client can simply compare bytes stored under the standardized key paths. + +### Localhost vs *regular* client + +The localhost client aims to provide a unified approach to interacting with applications on a single chain, as the IBC +application layer provides for cross-chain interactions. To achieve this unified interface though, there are a number of +differences under the hood compared to a 'regular' IBC client (excluding `06-solomachine` and `09-localhost` itself). + +The table below lists some important differences: + +| | Regular client | Localhost | +|----------------------------------------------|-----------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------| +| Number of clients | Many instances of a client *type* corresponding to different counterparties | A single sentinel client with the client identifier `09-localhost` | +| Client creation | Relayer (permissionless) | Implicitly made available by the 02-client submodule in core IBC | +| Client updates | Relayer submits headers using `MsgUpdateClient` | No client updates are required as the localhost implementation is stateless | +| Number of connections | Many connections, 1 (or more) per client | A single sentinel connection with the connection identifier `connection-localhost` | +| Connection creation | Connection handshake, provided underlying client | Sentinel `ConnectionEnd` is created and set in store in the `InitGenesis` handler of the 03-connection submodule in core IBC | +| Counterparty | Underlying client, representing another chain | Client with identifier `09-localhost` in same chain | +| `VerifyMembership` and `VerifyNonMembership` | Performs proof verification using consensus state roots | Performs state verification using key-value lookups in the core IBC store | +| `ClientState` storage | `ClientState` stored and directly provable with `VerifyMembership` | Stateless, so `ClientState` is not provable directly with `VerifyMembership` | diff --git a/docs/docs/03-light-clients/02-localhost/02-integration.md b/docs/docs/03-light-clients/02-localhost/02-integration.md new file mode 100644 index 0000000..56d284a --- /dev/null +++ b/docs/docs/03-light-clients/02-localhost/02-integration.md @@ -0,0 +1,19 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /ibc/light-clients/localhost/integration +--- + + +# Integration + +The 09-localhost light client module registers codec types within the core IBC module. This differs from other light client module implementations which are expected to register codec types using the `AppModuleBasic` interface. + +The localhost client is implicitly enabled by using the `AllowAllClients` wildcard (`"*"`) in the 02-client submodule default value for param [`allowed_clients`](https://github.com/cosmos/ibc-go/blob/v7.0.0/proto/ibc/core/client/v1/client.proto#L102). + +```go +// DefaultAllowedClients are the default clients for the AllowedClients parameter. +// By default it allows all client types. +var DefaultAllowedClients = []string{AllowAllClients} +``` diff --git a/docs/docs/03-light-clients/02-localhost/03-client-state.md b/docs/docs/03-light-clients/02-localhost/03-client-state.md new file mode 100644 index 0000000..18b0daf --- /dev/null +++ b/docs/docs/03-light-clients/02-localhost/03-client-state.md @@ -0,0 +1,10 @@ +--- +title: ClientState +sidebar_label: ClientState +sidebar_position: 3 +slug: /ibc/light-clients/localhost/client-state +--- + +# `ClientState` + +The 09-localhost client is stateless and has no types. diff --git a/docs/docs/03-light-clients/02-localhost/04-connection.md b/docs/docs/03-light-clients/02-localhost/04-connection.md new file mode 100644 index 0000000..480e63d --- /dev/null +++ b/docs/docs/03-light-clients/02-localhost/04-connection.md @@ -0,0 +1,29 @@ +--- +title: Connection +sidebar_label: Connection +sidebar_position: 4 +slug: /ibc/light-clients/localhost/connection +--- + + +# Localhost connections + +The 09-localhost light client module integrates with core IBC through a single sentinel localhost connection. +The sentinel `ConnectionEnd` is stored by default in the core IBC store. + +This enables channel handshakes to be initiated out of the box by supplying the localhost connection identifier (`connection-localhost`) in the `connectionHops` parameter of `MsgChannelOpenInit`. + +The `ConnectionEnd` is created and set in store via the `InitGenesis` handler of the 03-connection submodule in core IBC. +The `ConnectionEnd` and its `Counterparty` both reference the `09-localhost` client identifier, and share the localhost connection identifier `connection-localhost`. + +```go +// CreateSentinelLocalhostConnection creates and sets the sentinel localhost connection end in the IBC store. +func (k Keeper) CreateSentinelLocalhostConnection(ctx sdk.Context) { + counterparty := types.NewCounterparty(exported.LocalhostClientID, exported.LocalhostConnectionID, commitmenttypes.NewMerklePrefix(k.GetCommitmentPrefix().Bytes())) + connectionEnd := types.NewConnectionEnd(types.OPEN, exported.LocalhostClientID, counterparty, types.GetCompatibleVersions(), 0) + + k.SetConnection(ctx, exported.LocalhostConnectionID, connectionEnd) +} +``` + +Note that connection handshakes are disallowed when using the `09-localhost` client type. diff --git a/docs/docs/03-light-clients/02-localhost/05-state-verification.md b/docs/docs/03-light-clients/02-localhost/05-state-verification.md new file mode 100644 index 0000000..2c68983 --- /dev/null +++ b/docs/docs/03-light-clients/02-localhost/05-state-verification.md @@ -0,0 +1,23 @@ +--- +title: State Verification +sidebar_label: State Verification +sidebar_position: 5 +slug: /ibc/light-clients/localhost/state-verification +--- + +# State verification + +The localhost client handles state verification through the `LightClientModule` interface methods `VerifyMembership` and `VerifyNonMembership` by performing read-only operations directly on the core IBC store. + +When verifying channel state in handshakes or processing packets the `09-localhost` client can simply compare bytes stored under the standardized key paths defined by [ICS-24](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements). + +For existence proofs via `VerifyMembership` the 09-localhost client will retrieve the value stored under the provided key path and compare it against the value provided by the caller. In contrast, non-existence proofs via `VerifyNonMembership` assert the absence of a value at the provided key path. + +Relayers are expected to provide a sentinel proof when sending IBC messages. Submission of nil or empty proofs is disallowed in core IBC messaging. +The 09-localhost light client module defines a `SentinelProof` as a single byte. Localhost client state verification will fail if the sentinel proof value is not provided. + +```go +var SentinelProof = []byte{0x01} +``` + +The `ClientState` of `09-localhost` is stateless, so it is not directly provable with `VerifyMembership` or `VerifyNonMembership`. diff --git a/docs/docs/03-light-clients/02-localhost/_category_.json b/docs/docs/03-light-clients/02-localhost/_category_.json new file mode 100644 index 0000000..f2e9bd7 --- /dev/null +++ b/docs/docs/03-light-clients/02-localhost/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Localhost", + "position": 2, + "link": null +} \ No newline at end of file diff --git a/docs/docs/03-light-clients/03-solomachine/01-solomachine.md b/docs/docs/03-light-clients/03-solomachine/01-solomachine.md new file mode 100644 index 0000000..91c41b1 --- /dev/null +++ b/docs/docs/03-light-clients/03-solomachine/01-solomachine.md @@ -0,0 +1,26 @@ +--- +title: Solomachine +sidebar_label: Solomachine +sidebar_position: 1 +slug: /ibc/light-clients/solomachine/solomachine +--- + + +# `solomachine` + +## Abstract + +This paper defines the implementation of the ICS06 protocol on the Cosmos SDK. For the general +specification please refer to the [ICS06 Specification](https://github.com/cosmos/ibc/tree/master/spec/client/ics-006-solo-machine-client). + +This implementation of a solo machine light client supports single and multi-signature public +keys. The client is capable of handling public key updates by header and governance proposals. +The light client is capable of processing client misbehaviour. Proofs of the counterparty state +are generated by the solo machine client by signing over the desired state with a certain sequence, +diversifier, and timestamp. + +## Contents + +1. **[Concepts](02-concepts.md)** +2. **[State](03-state.md)** +3. **[State Transitions](04-state_transitions.md)** diff --git a/docs/docs/03-light-clients/03-solomachine/02-concepts.md b/docs/docs/03-light-clients/03-solomachine/02-concepts.md new file mode 100644 index 0000000..f6c7ff7 --- /dev/null +++ b/docs/docs/03-light-clients/03-solomachine/02-concepts.md @@ -0,0 +1,168 @@ +--- +title: Concepts +sidebar_label: Concepts +sidebar_position: 2 +slug: /ibc/light-clients/solomachine/concepts +--- + + +# Concepts + +## Client State + +The `ClientState` for a solo machine light client stores the latest sequence, the frozen sequence, +the latest consensus state, and client flag indicating if the client should be allowed to be updated +after a governance proposal. + +If the client is not frozen then the frozen sequence is 0. + +## Consensus State + +The consensus states stores the public key, diversifier, and timestamp of the solo machine light client. + +The diversifier is used to prevent accidental misbehaviour if the same public key is used across +different chains with the same client identifier. It should be unique to the chain the light client +is used on. + +## Public Key + +The public key can be a single public key or a multi-signature public key. The public key type used +must fulfill the tendermint public key interface (this will become the SDK public key interface in the +near future). The public key must be registered on the application codec otherwise encoding/decoding +errors will arise. The public key stored in the consensus state is represented as a protobuf `Any`. +This allows for flexibility in what other public key types can be supported in the future. + +## Counterparty Verification + +The solo machine light client can verify counterparty client state, consensus state, connection state, +channel state, packet commitments, packet acknowledgements, packet receipt absence, +and the next sequence receive. At the end of each successful verification call the light +client sequence number will be incremented. + +Successful verification requires the current public key to sign over the proof. + +## Proofs + +A solo machine proof should verify that the solomachine public key signed +over some specified data. The format for generating marshaled proofs for +the SDK's implementation of solo machine is as follows: + +1. Construct the data using the associated protobuf definition and marshal it. + +For example: + +```go +data := &ClientStateData{ + Path: []byte(path.String()), + ClientState: protoAny, +} + +dataBz, err := cdc.Marshal(data) +``` + +The helper functions `...DataBytes()` in [proof.go](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/06-solomachine/proof.go) handle this +functionality. + +2. Construct the `SignBytes` and marshal it. + +For example: + +```go +signBytes := &SignBytes{ + Sequence: sequence, + Timestamp: timestamp, + Diversifier: diversifier, + DataType: CLIENT, + Data: dataBz, +} + +signBz, err := cdc.Marshal(signBytes) +``` + +The helper functions `...SignBytes()` in [proof.go](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/06-solomachine/proof.go) handle this functionality. +The `DataType` field is used to disambiguate what type of data was signed to prevent potential +proto encoding overlap. + +3. Sign the sign bytes. Embed the signatures into either `SingleSignatureData` or `MultiSignatureData`. +Convert the `SignatureData` to proto and marshal it. + +For example: + +```go +sig, err := key.Sign(signBz) +sigData := &signing.SingleSignatureData{ + Signature: sig, +} + +protoSigData := signing.SignatureDataToProto(sigData) +bz, err := cdc.Marshal(protoSigData) +``` + +4. Construct a `TimestampedSignatureData` and marshal it. The marshaled result can be passed in +as the proof parameter to the verification functions. + +For example: + +```go +timestampedSignatureData := &solomachine.TimestampedSignatureData{ + SignatureData: sigData, + Timestamp: solomachine.Time, +} + +proof, err := cdc.Marshal(timestampedSignatureData) +``` + +NOTE: At the end of this process, the sequence associated with the key needs to be updated. +The sequence must be incremented each time proof is generated. + +## Updates By Header + +An update by a header will only succeed if: + +- the header provided is parseable to solo machine header +- the header sequence matches the current sequence +- the header timestamp is greater than or equal to the consensus state timestamp +- the currently registered public key generated the proof + +If the update is successful: + +- the public key is updated +- the diversifier is updated +- the timestamp is updated +- the sequence is incremented by 1 +- the new consensus state is set in the client state + +## Updates By Proposal + +An update by a governance proposal will only succeed if: + +- the substitute provided is parseable to solo machine client state +- the new consensus state public key does not equal the current consensus state public key + +If the update is successful: + +- the subject client state is updated to the substitute client state +- the subject consensus state is updated to the substitute consensus state +- the client is unfrozen (if it was previously frozen) + +NOTE: Previously, `AllowUpdateAfterProposal` was used to signal the update/recovery options for the solo machine client. However, this has now been deprecated because a code migration can overwrite the client and consensus states regardless of the value of this parameter. If governance would vote to overwrite a client or consensus state, it is likely that governance would also be willing to perform a code migration to do the same. + +## Misbehaviour + +Misbehaviour handling will only succeed if: + +- the misbehaviour provided is parseable to solo machine misbehaviour +- the client is not already frozen +- the current public key signed over two unique data messages at the same sequence and diversifier. + +If the misbehaviour is successfully processed: + +- the client is frozen by setting the frozen sequence to the misbehaviour sequence + +NOTE: Misbehaviour processing is data processing order dependent. A misbehaving solo machine +could update to a new public key to prevent being frozen before misbehaviour is submitted. + +## Upgrades + +Upgrades to solo machine light clients are not supported since an entirely different type of +public key can be set using normal client updates. diff --git a/docs/docs/03-light-clients/03-solomachine/03-state.md b/docs/docs/03-light-clients/03-solomachine/03-state.md new file mode 100644 index 0000000..bdb3102 --- /dev/null +++ b/docs/docs/03-light-clients/03-solomachine/03-state.md @@ -0,0 +1,12 @@ +--- +title: State +sidebar_label: State +sidebar_position: 3 +slug: /ibc/light-clients/solomachine/state +--- + + +# State + +The solo machine light client will only store consensus states for each update by a header +or a governance proposal. The latest client state is also maintained in the store. diff --git a/docs/docs/03-light-clients/03-solomachine/04-state_transitions.md b/docs/docs/03-light-clients/03-solomachine/04-state_transitions.md new file mode 100644 index 0000000..aabaa58 --- /dev/null +++ b/docs/docs/03-light-clients/03-solomachine/04-state_transitions.md @@ -0,0 +1,43 @@ +--- +title: State Transitions +sidebar_label: State Transitions +sidebar_position: 4 +slug: /ibc/light-clients/solomachine/state_transitions +--- + + +# State Transitions + +## Client State Verification Functions + +Successful state verification by a solo machine light client will result in: + +- the sequence being incremented by 1. + +## Update By Header + +A successful update of a solo machine light client by a header will result in: + +- the public key being updated to the new public key provided by the header. +- the diversifier being updated to the new diviersifier provided by the header. +- the timestamp being updated to the new timestamp provided by the header. +- the sequence being incremented by 1 +- the consensus state being updated (consensus state stores the public key, diversifier, and timestamp) + +## Update By Governance Proposal + +A successful update of a solo machine light client by a governance proposal will result in: + +- the client state being updated to the substitute client state +- the consensus state being updated to the substitute consensus state (consensus state stores the public key, diversifier, and timestamp) +- the frozen sequence being set to zero (client is unfrozen if it was previously frozen). + +## Upgrade + +Client udgrades are not supported for the solo machine light client. No state transition occurs. + +## Misbehaviour + +Successful misbehaviour processing of a solo machine light client will result in: + +- the frozen sequence being set to the sequence the misbehaviour occurred at diff --git a/docs/docs/03-light-clients/03-solomachine/_category_.json b/docs/docs/03-light-clients/03-solomachine/_category_.json new file mode 100644 index 0000000..3bfa67e --- /dev/null +++ b/docs/docs/03-light-clients/03-solomachine/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Solomachine", + "position": 3, + "link": null +} \ No newline at end of file diff --git a/docs/docs/03-light-clients/04-wasm/01-overview.md b/docs/docs/03-light-clients/04-wasm/01-overview.md new file mode 100644 index 0000000..79e26a2 --- /dev/null +++ b/docs/docs/03-light-clients/04-wasm/01-overview.md @@ -0,0 +1,26 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /ibc/light-clients/wasm/overview +--- + +# `08-wasm` + +## Overview + +Learn about the `08-wasm` light client proxy module. + +### Context + +Traditionally, light clients used by ibc-go have been implemented only in Go, and since ibc-go v7 (with the release of the 02-client refactor), they are [first-class Cosmos SDK modules](/architecture/adr-010-light-clients-as-sdk-modules). This means that updating existing light client implementations or adding support for new light clients is a multi-step, time-consuming process involving on-chain governance: it is necessary to modify the codebase of ibc-go (if the light client is part of its codebase), re-build chains' binaries, pass a governance proposal and have validators upgrade their nodes. + +### Motivation + +To break the limitation of being able to write light client implementations only in Go, the `08-wasm` adds support to run light clients written in a Wasm-compilable language. The light client byte code implements the entry points of a [CosmWasm](https://docs.cosmwasm.com/docs/) smart contract, and runs inside a Wasm VM. The `08-wasm` module exposes a proxy light client interface that routes incoming messages to the appropriate handler function, inside the Wasm VM, for execution. + +Adding a new light client to a chain is just as simple as submitting a governance proposal with the message that stores the byte code of the light client contract. No coordinated upgrade is needed. When the governance proposal passes and the message is executed, the contract is ready to be instantiated upon receiving a relayer-submitted `MsgCreateClient`. The process of creating a Wasm light client is the same as with a regular light client implemented in Go. + +### Use cases + +- Development of light clients for non-Cosmos ecosystem chains: state machines in other ecosystems are, in many cases, implemented in Rust, and thus there are probably libraries used in their light client implementations for which there is no equivalent in Go. This makes the development of a light client in Go very difficult, but relatively simple to do it in Rust. Therefore, writing a CosmWasm smart contract in Rust that implements the light client algorithm becomes a lower effort. diff --git a/docs/docs/03-light-clients/04-wasm/02-concepts.md b/docs/docs/03-light-clients/04-wasm/02-concepts.md new file mode 100644 index 0000000..4d42c96 --- /dev/null +++ b/docs/docs/03-light-clients/04-wasm/02-concepts.md @@ -0,0 +1,90 @@ +--- +title: Concepts +sidebar_label: Concepts +sidebar_position: 2 +slug: /ibc/light-clients/wasm/concepts +--- + +# Concepts + +Learn about the differences between a proxy light client and a Wasm light client. + +## Proxy light client + +The `08-wasm` module is not a regular light client in the same sense as, for example, the 07-tendermint light client. `08-wasm` is instead a *proxy* light client module, and this means that the module acts a proxy to the actual implementations of light clients. The module will act as a wrapper for the actual light clients uploaded as Wasm byte code and will delegate all operations to them (i.e. `08-wasm` just passes through the requests to the Wasm light clients). Still, the `08-wasm` module implements all the required interfaces necessary to integrate with core IBC, so that 02-client can call into it as it would for any other light client module. These interfaces are `LightClientModule`, `ClientState`, `ConsensusState` and `ClientMessage`, and we will describe them in the context of `08-wasm` in the following sections. For more information about this set of interfaces, please read section [Overview of the light client module developer guide](../01-developer-guide/01-overview.md#overview). + +### `LightClientModule` + +The `08-wasm`'s `LightClientModule` data structure contains two fields: + +- `keeper` is the `08-wasm` module keeper. +- `storeProvider` encapsulates the IBC core store key and provides access to isolated prefix stores for each client so they can read/write in separate namespaces. + +```go +type LightClientModule struct { + keeper wasmkeeper.Keeper + storeProvider exported.ClientStoreProvider +} +``` + +See section [`LightClientModule` of the light client module developer guide](../01-developer-guide/01-overview.md#lightclientmodule) for more information about the `LightClientModule` interface. + +### `ClientState` + +The `08-wasm`'s `ClientState` data structure contains three fields: + +- `Data` contains the bytes of the Protobuf-encoded client state of the underlying light client implemented as a Wasm contract. For example, if the Wasm light client contract implements the GRANDPA light client algorithm, then `Data` will contain the bytes for a [GRANDPA client state](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L35-L60). +- `Checksum` is the sha256 hash of the Wasm contract's byte code. This hash is used as an identifier to call the right contract. +- `LatestHeight` is the latest height of the counterparty state machine (i.e. the height of the blockchain), whose consensus state the light client tracks. + +```go +type ClientState struct { + // bytes encoding the client state of the underlying + // light client implemented as a Wasm contract + Data []byte + // sha256 hash of Wasm contract byte code + Checksum []byte + // latest height of the counterparty ledger + LatestHeight types.Height +} +``` + +See section [`ClientState` of the light client module developer guide](../01-developer-guide/01-overview.md#clientstate) for more information about the `ClientState` interface. + +### `ConsensusState` + +The `08-wasm`'s `ConsensusState` data structure maintains one field: + +- `Data` contains the bytes of the Protobuf-encoded consensus state of the underlying light client implemented as a Wasm contract. For example, if the Wasm light client contract implements the GRANDPA light client algorithm, then `Data` will contain the bytes for a [GRANDPA consensus state](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L87-L94). + +```go +type ConsensusState struct { + // bytes encoding the consensus state of the underlying light client + // implemented as a Wasm contract. + Data []byte +} +``` + +See section [`ConsensusState` of the light client module developer guide](../01-developer-guide/01-overview.md#consensusstate) for more information about the `ConsensusState` interface. + +### `ClientMessage` + +`ClientMessage` is used for performing updates to a `ClientState` stored on chain. The `08-wasm`'s `ClientMessage` data structure maintains one field: + +- `Data` contains the bytes of the Protobuf-encoded header(s) or misbehaviour for the underlying light client implemented as a Wasm contract. For example, if the Wasm light client contract implements the GRANDPA light client algorithm, then `Data` will contain the bytes of either [header](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L96-L104) or [misbehaviour](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L106-L112) for a GRANDPA light client. + +```go +type ClientMessage struct { + // bytes encoding the header(s) or misbehaviour for the underlying light client + // implemented as a Wasm contract. + Data []byte +} +``` + +See section [`ClientMessage` of the light client module developer guide](../01-developer-guide/01-overview.md#clientmessage) for more information about the `ClientMessage` interface. + +## Wasm light client + +The actual light client can be implemented in any language that compiles to Wasm and implements the interfaces of a [CosmWasm](https://docs.cosmwasm.com/docs/) contract. Even though in theory other languages could be used, in practice (at least for the time being) the most suitable language to use would be Rust, since there is already good support for it for developing CosmWasm smart contracts. + +At the moment of writing there are two contracts available: one for [Tendermint](https://github.com/ComposableFi/composable-ibc/tree/master/light-clients/ics07-tendermint-cw) and one [GRANDPA](https://github.com/ComposableFi/composable-ibc/tree/master/light-clients/ics10-grandpa-cw) (which is being used in production in [Composable Finance's Centauri bridge](https://github.com/ComposableFi/composable-ibc)). And there are others in development (e.g. for Near). diff --git a/docs/docs/03-light-clients/04-wasm/03-integration.md b/docs/docs/03-light-clients/04-wasm/03-integration.md new file mode 100644 index 0000000..3895bde --- /dev/null +++ b/docs/docs/03-light-clients/04-wasm/03-integration.md @@ -0,0 +1,398 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 3 +slug: /ibc/light-clients/wasm/integration +--- + +# Integration + +Learn how to integrate the `08-wasm` module in a chain binary and about the recommended approaches depending on whether the [`x/wasm` module](https://github.com/CosmWasm/wasmd/tree/main/x/wasm) is already used in the chain. The following document only applies for Cosmos SDK chains. + +## Importing the `08-wasm` module + +`08-wasm` has no stable releases yet. To use it, you need to import the git commit that contains the module with the compatible versions of `ibc-go` and `wasmvm`. To do so, run the following command with the desired git commit in your project: + +```sh +go get github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10 +``` + +## `app.go` setup + +The sample code below shows the relevant integration points in `app.go` required to set up the `08-wasm` module in a chain binary. Since `08-wasm` is a light client module itself, please check out as well the section [Integrating light clients](../../01-ibc/02-integration.md#integrating-light-clients) for more information: + +```go +// app.go +import ( + ... + "github.com/cosmos/cosmos-sdk/runtime" + + cmtos "github.com/cometbft/cometbft/libs/os" + + ibcwasm "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10" + ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/keeper" + ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + ... +) + +... + +// Register the AppModule for the 08-wasm module +ModuleBasics = module.NewBasicManager( + ... + ibcwasm.AppModuleBasic{}, + ... +) + +// Add 08-wasm Keeper +type SimApp struct { + ... + WasmClientKeeper ibcwasmkeeper.Keeper + ... +} + +func NewSimApp( + logger log.Logger, + db dbm.DB, + traceStore io.Writer, + loadLatest bool, + appOpts servertypes.AppOptions, + baseAppOptions ...func(*baseapp.BaseApp), +) *SimApp { + ... + keys := sdk.NewKVStoreKeys( + ... + ibcwasmtypes.StoreKey, + ) + + // Instantiate 08-wasm's keeper + // This sample code uses a constructor function that + // accepts a pointer to an existing instance of Wasm VM. + // This is the recommended approach when the chain + // also uses `x/wasm`, and then the Wasm VM instance + // can be shared. + app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM( + appCodec, + runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmVM, + app.GRPCQueryRouter(), + ) + + wasmLightClientModule := wasm.NewLightClientModule(app.WasmClientKeeper) + app.IBCKeeper.ClientKeeper.AddRoute(ibcwasmtypes.ModuleName, &wasmLightClientModule) + + app.ModuleManager = module.NewManager( + // SDK app modules + ... + ibcwasm.NewAppModule(app.WasmClientKeeper), + ) + app.ModuleManager.SetOrderBeginBlockers( + ... + ibcwasmtypes.ModuleName, + ... + ) + app.ModuleManager.SetOrderEndBlockers( + ... + ibcwasmtypes.ModuleName, + ... + ) + genesisModuleOrder := []string{ + ... + ibcwasmtypes.ModuleName, + ... + } + app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...) + app.ModuleManager.SetOrderExportGenesis(genesisModuleOrder...) + ... + + // initialize BaseApp + app.SetInitChainer(app.InitChainer) + ... + + // must be before Loading version + if manager := app.SnapshotManager(); manager != nil { + err := manager.RegisterExtensions( + ibcwasmkeeper.NewWasmSnapshotter(app.CommitMultiStore(), &app.WasmClientKeeper), + ) + if err != nil { + panic(fmt.Errorf("failed to register snapshot extension: %s", err)) + } + } + ... + + if loadLatest { + ... + + ctx := app.BaseApp.NewUncachedContext(true, cmtproto.Header{}) + + // Initialize pinned codes in wasmvm as they are not persisted there + if err := app.WasmClientKeeper.InitializePinnedCodes(ctx); err != nil { + cmtos.Exit(fmt.Sprintf("failed initialize pinned codes %s", err)) + } + } +} +``` + +## Keeper instantiation + +When it comes to instantiating `08-wasm`'s keeper, there are two recommended ways of doing it. Choosing one or the other will depend on whether the chain already integrates [`x/wasm`](https://github.com/CosmWasm/wasmd/tree/main/x/wasm) or not. + +### If `x/wasm` is present + +If the chain where the module is integrated uses `x/wasm` then we recommend that both `08-wasm` and `x/wasm` share the same Wasm VM instance. Having two separate Wasm VM instances is still possible, but care should be taken to make sure that both instances do not share the directory when the VM stores blobs and various caches, otherwise unexpected behaviour is likely to happen (from `x/wasm` v0.51 and `08-wasm` v0.2.0+ibc-go-v8.3-wasmvm-v2.0 this will be forbidden anyway, since wasmvm v2.0.0 and above will not allow two different Wasm VM instances to shared the same data folder). + +In order to share the Wasm VM instance, please follow the guideline below. Please note that this requires `x/wasm` v0.41 or above. + +- Instantiate the Wasm VM in `app.go` with the parameters of your choice. +- [Create an `Option` with this Wasm VM instance](https://github.com/CosmWasm/wasmd/blob/db93d7b6c7bb6f4a340d74b96a02cec885729b59/x/wasm/keeper/options.go#L21-L25). +- Add the option created in the previous step to a slice and [pass it to the `x/wasm NewKeeper` constructor function](https://github.com/CosmWasm/wasmd/blob/db93d7b6c7bb6f4a340d74b96a02cec885729b59/x/wasm/keeper/keeper_cgo.go#L36). +- Pass the pointer to the Wasm VM instance to `08-wasm` [`NewKeeperWithVM` constructor function](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/keeper/keeper.go#L39-L47). + +The code to set this up would look something like this: + +```go +// app.go +import ( + ... + "github.com/cosmos/cosmos-sdk/runtime" + + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + + ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/keeper" + ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + ... +) + +... + +// instantiate the Wasm VM with the chosen parameters +wasmer, err := wasmvm.NewVM( + dataDir, + availableCapabilities, + contractMemoryLimit, // default of 32 + contractDebugMode, + memoryCacheSize, +) +if err != nil { + panic(err) +} + +// create an Option slice (or append to an existing one) +// with the option to use a custom Wasm VM instance +wasmOpts = []wasmkeeper.Option{ + wasmkeeper.WithWasmEngine(wasmer), +} + +// the keeper will use the provided Wasm VM instance, +// instead of instantiating a new one +app.WasmKeeper = wasmkeeper.NewKeeper( + appCodec, + keys[wasmtypes.StoreKey], + app.AccountKeeper, + app.BankKeeper, + app.StakingKeeper, + distrkeeper.NewQuerier(app.DistrKeeper), + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeper, + &app.IBCKeeper.PortKeeper, + scopedWasmKeeper, + app.TransferKeeper, + app.MsgServiceRouter(), + app.GRPCQueryRouter(), + wasmDir, + wasmConfig, + availableCapabilities, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmOpts..., +) + +app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM( + appCodec, + runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmer, // pass the Wasm VM instance to `08-wasm` keeper constructor + app.GRPCQueryRouter(), +) +... +``` + +### If `x/wasm` is not present + +If the chain does not use [`x/wasm`](https://github.com/CosmWasm/wasmd/tree/main/x/wasm), even though it is still possible to use the method above from the previous section +(e.g. instantiating a Wasm VM in app.go an pass it to 08-wasm's [`NewKeeperWithVM` constructor function](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/keeper/keeper.go#L39-L47), since there would be no need in this case to share the Wasm VM instance with another module, you can use the [`NewKeeperWithConfig` constructor function](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/keeper/keeper.go#L88-L96) and provide the Wasm VM configuration parameters of your choice instead. A Wasm VM instance will be created in `NewKeeperWithConfig`. The parameters that can set are: + +- `DataDir` is the [directory for Wasm blobs and various caches](https://github.com/CosmWasm/wasmvm/blob/v2.0.0/lib.go#L25). As an example, in `wasmd` this is set to the [`wasm` folder under the home directory](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/app/app.go#L578). In the code snippet below we set this field to the `ibc_08-wasm_client_data` folder under the home directory. +- `SupportedCapabilities` is a [list of capabilities supported by the chain](https://github.com/CosmWasm/wasmvm/blob/v2.0.0/lib.go#L26). [`wasmd` sets this to all the available capabilities](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/app/app.go#L586), but 08-wasm only requires `iterator`. +- `MemoryCacheSize` sets [the size in MiB of an in-memory cache for e.g. module caching](https://github.com/CosmWasm/wasmvm/blob/v2.0.0/lib.go#L29C16-L29C104). It is not consensus-critical and should be defined on a per-node basis, often in the range 100 to 1000 MB. [`wasmd` reads this value of](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/app/app.go#L579). Default value is 256. +- `ContractDebugMode` is a [flag to enable/disable printing debug logs from the contract to STDOUT](https://github.com/CosmWasm/wasmvm/blob/v2.0.0/lib.go#L28). This should be false in production environments. Default value is false. + +Another configuration parameter of the Wasm VM is the contract memory limit (in MiB), which is [set to 32](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/types/config.go#L8), [following the example of `wasmd`](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/x/wasm/keeper/keeper.go#L32-L34). This parameter is not configurable by users of `08-wasm`. + +The following sample code shows how the keeper would be constructed using this method: + +```go +// app.go +import ( + ... + "github.com/cosmos/cosmos-sdk/runtime" + + ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/keeper" + ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + ... +) + +... + +// homePath is the path to the directory where the data +// directory for Wasm blobs and caches will be created +wasmConfig := ibcwasmtypes.WasmConfig{ + DataDir: filepath.Join(homePath, "ibc_08-wasm_client_data"), + SupportedCapabilities: []string{"iterator"}, + ContractDebugMode: false, +} +app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithConfig( + appCodec, + runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmConfig, + app.GRPCQueryRouter(), +) +``` + +Check out also the [`WasmConfig` type definition](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/types/config.go#L21-L31) for more information on each of the configurable parameters. Some parameters allow node-level configurations. There is additionally the function [`DefaultWasmConfig`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/types/config.go#L36-L42) available that returns a configuration with the default values. + +### Options + +The `08-wasm` module comes with an options API inspired by the one in `x/wasm`. +Currently the only option available is the `WithQueryPlugins` option, which allows registration of custom query plugins for the `08-wasm` module. The use of this API is optional and it is only required if the chain wants to register custom query plugins for the `08-wasm` module. + +#### `WithQueryPlugins` + +By default, the `08-wasm` module does not configure any querier options for light client contracts. However, it is possible to register custom query plugins for [`QueryRequest::Custom`](https://github.com/CosmWasm/cosmwasm/blob/v2.0.1/packages/std/src/query/mod.rs#L48) and [`QueryRequest::Stargate`](https://github.com/CosmWasm/cosmwasm/blob/v2.0.1/packages/std/src/query/mod.rs#L57-L65). + +Assuming that the keeper is not yet instantiated, the following sample code shows how to register query plugins for the `08-wasm` module. + +We first construct a [`QueryPlugins`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/types/querier.go#L78-L87) object with the desired query plugins: + +```go +queryPlugins := ibcwasmtypes.QueryPlugins { + Custom: MyCustomQueryPlugin(), + // `myAcceptList` is a `[]string` containing the list of gRPC query paths that the chain wants to allow for the `08-wasm` module to query. + // These queries must be registered in the chain's gRPC query router, be deterministic, and track their gas usage. + // The `AcceptListStargateQuerier` function will return a query plugin that will only allow queries for the paths in the `myAcceptList`. + // The query responses are encoded in protobuf unlike the implementation in `x/wasm`. + Stargate: ibcwasmtypes.AcceptListStargateQuerier(myAcceptList), +} +``` + +Note that the `Stargate` querier appends the user defined accept list of query routes to a default list defined by the `08-wasm` module. +The `defaultAcceptList` defines a single query route: `"/ibc.core.client.v1.Query/VerifyMembership"`. This allows for light client smart contracts to delegate parts of their workflow to other light clients for auxiliary proof verification. For example, proof of inclusion of block and tx data by a data availability provider. + +```go +// defaultAcceptList defines a set of default allowed queries made available to the Querier. +var defaultAcceptList = []string{ + "/ibc.core.client.v1.Query/VerifyMembership", +} +``` + +You may leave any of the fields in the `QueryPlugins` object as `nil` if you do not want to register a query plugin for that query type. + +Then, we pass the `QueryPlugins` object to the `WithQueryPlugins` option: + +```go +querierOption := ibcwasmkeeper.WithQueryPlugins(&queryPlugins) +``` + +Finally, we pass the option to the `NewKeeperWithConfig` or `NewKeeperWithVM` constructor function during [Keeper instantiation](#keeper-instantiation): + +```diff +app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithConfig( + appCodec, + runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmConfig, + app.GRPCQueryRouter(), ++ querierOption, +) +``` + +```diff +app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM( + appCodec, + runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmer, // pass the Wasm VM instance to `08-wasm` keeper constructor + app.GRPCQueryRouter(), ++ querierOption, +) +``` + +## Updating `AllowedClients` + +If the chain's 02-client submodule parameter `AllowedClients` contains the single wildcard `"*"` element, then it is not necessary to do anything in order to allow the creation of `08-wasm` clients. However, if the parameter contains a list of client types (e.g. `["06-solomachine", "07-tendermint"]`), then in order to use the `08-wasm` module chains must update the [`AllowedClients` parameter](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/client/v1/client.proto#L64) of core IBC. This can be configured directly in the application upgrade handler with the sample code below: + +```go +import ( + ... + ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + ... +) + +... + +func CreateWasmUpgradeHandler( + mm *module.Manager, + configurator module.Configurator, + clientKeeper clientkeeper.Keeper, +) upgradetypes.UpgradeHandler { + return func(goCtx context.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + // explicitly update the IBC 02-client params, adding the wasm client type + params := clientKeeper.GetParams(ctx) + params.AllowedClients = append(params.AllowedClients, ibcwasmtypes.Wasm) + clientKeeper.SetParams(ctx, params) + + return mm.RunMigrations(goCtx, configurator, vm) + } +} +``` + +Or alternatively the parameter can be updated via a governance proposal (see at the bottom of section [`Creating clients`](../01-developer-guide/09-setup.md#creating-clients) for an example of how to do this). + +## Adding the module to the store + +As part of the upgrade migration you must also add the module to the upgrades store. + +```go +func (app SimApp) RegisterUpgradeHandlers() { + + ... + + if upgradeInfo.Name == UpgradeName && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { + storeUpgrades := storetypes.StoreUpgrades{ + Added: []string{ + ibcwasmtypes.ModuleName, + }, + } + + // configure store loader that checks if version == upgradeHeight and applies store upgrades + app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) + } +} +``` + +## Adding snapshot support + +In order to use the `08-wasm` module chains are required to register the `WasmSnapshotter` extension in the snapshot manager. This snapshotter takes care of persisting the external state, in the form of contract code, of the Wasm VM instance to disk when the chain is snapshotted. [This code](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/testing/simapp/app.go#L775-L782) should be placed in `NewSimApp` function in `app.go`. + +## Pin byte codes at start + +Wasm byte codes should be pinned to the WasmVM cache on every application start, therefore [this code](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/testing/simapp/app.go#L825-L830) should be placed in `NewSimApp` function in `app.go`. diff --git a/docs/docs/03-light-clients/04-wasm/04-messages.md b/docs/docs/03-light-clients/04-wasm/04-messages.md new file mode 100644 index 0000000..e27513b --- /dev/null +++ b/docs/docs/03-light-clients/04-wasm/04-messages.md @@ -0,0 +1,75 @@ +--- +title: Messages +sidebar_label: Messages +sidebar_position: 4 +slug: /ibc/light-clients/wasm/messages +--- + +# Messages + +## `MsgStoreCode` + +Uploading the Wasm light client contract to the Wasm VM storage is achieved by means of `MsgStoreCode`: + +```go +type MsgStoreCode struct { + // signer address + Signer string + // wasm byte code of light client contract. It can be raw or gzip compressed + WasmByteCode []byte +} +``` + +This message is expected to fail if: + +- `Signer` is an invalid Bech32 address, or it does not match the designated authority address. +- `WasmByteCode` is empty or it exceeds the maximum size, currently set to 3MB. + +Only light client contracts stored using `MsgStoreCode` are allowed to be instantiated. An attempt to create a light client from contracts uploaded via other means (e.g. through `x/wasm` if the module shares the same Wasm VM instance with 08-wasm) will fail. Due to the idempotent nature of the Wasm VM's `StoreCode` function, it is possible to store the same byte code multiple times. + +When execution of `MsgStoreCode` succeeds, the checksum of the contract (i.e. the sha256 hash of the contract's byte code) is stored in an allow list. When a relayer submits [`MsgCreateClient`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/client/v1/tx.proto#L25-L37) with 08-wasm's `ClientState`, the client state includes the checksum of the Wasm byte code that should be called. Then 02-client calls [08-wasm's implementation of `Initialize` function](https://github.com/cosmos/ibc-go/blob/06fd8eb5ee1697e3b43be7528a6e42f5e4a4613c/modules/core/02-client/keeper/client.go#L40) (which is an interface function part of `LightClientModule`), and it will check that the checksum in the client state matches one of the checksums in the allow list. If a match is found, the light client is initialized; otherwise, the transaction is aborted. + +## `MsgMigrateContract` + +Migrating a contract to a new Wasm byte code is achieved by means of `MsgMigrateContract`: + +```go +type MsgMigrateContract struct { + // signer address + Signer string + // the client id of the contract + ClientId string + // the SHA-256 hash of the new wasm byte code for the contract + Checksum []byte + // the json-encoded migrate msg to be passed to the contract on migration + Msg []byte +} +``` + +This message is expected to fail if: + +- `Signer` is an invalid Bech32 address, or it does not match the designated authority address. +- `ClientId` is not a valid identifier prefixed by `08-wasm`. +- `Checksum` is not exactly 32 bytes long or it is not found in the list of allowed checksums (a new checksum is added to the list when executing `MsgStoreCode`), or it matches the current checksum of the contract. + +When a Wasm light client contract is migrated to a new Wasm byte code the checksum for the contract will be updated with the new checksum. + +## `MsgRemoveChecksum` + +Removing a checksum from the list of allowed checksums is achieved by means of `MsgRemoveChecksum`: + +```go +type MsgRemoveChecksum struct { + // signer address + Signer string + // Wasm byte code checksum to be removed from the store + Checksum []byte +} +``` + +This message is expected to fail if: + +- `Signer` is an invalid Bech32 address, or it does not match the designated authority address. +- `Checksum` is not exactly 32 bytes long or it is not found in the list of allowed checksums (a new checksum is added to the list when executing `MsgStoreCode`). + +When a checksum is removed from the list of allowed checksums, then the corresponding Wasm byte code will not be available for instantiation in [08-wasm's implementation of `Initialize` function](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/core/02-client/keeper/client.go#L36). diff --git a/docs/docs/03-light-clients/04-wasm/05-governance.md b/docs/docs/03-light-clients/04-wasm/05-governance.md new file mode 100644 index 0000000..2993c5d --- /dev/null +++ b/docs/docs/03-light-clients/04-wasm/05-governance.md @@ -0,0 +1,126 @@ +--- +title: Governance +sidebar_label: Governance +sidebar_position: 5 +slug: /ibc/light-clients/wasm/governance +--- + +# Governance + +Learn how to upload Wasm light client byte code on a chain, and how to migrate an existing Wasm light client contract. + +## Setting an authority + +Both the storage of Wasm light client byte code as well as the migration of an existing Wasm light client contract are permissioned (i.e. only allowed to an authority such as governance). The designated authority is specified when instantiating `08-wasm`'s keeper: both [`NewKeeperWithVM`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/keeper/keeper.go#L39-L47) and [`NewKeeperWithConfig`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/keeper/keeper.go#L88-L96) constructor functions accept an `authority` argument that must be the address of the authorized actor. For example, in `app.go`, when instantiating the keeper, you can pass the address of the governance module: + +```go +// app.go +import ( + ... + "github.com/cosmos/cosmos-sdk/runtime" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/keeper" + ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + ... +) + +// app.go +app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM( + appCodec, + runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), // authority + wasmVM, + app.GRPCQueryRouter(), +) +``` + +## Storing new Wasm light client byte code + + If governance is the allowed authority, the governance v1 proposal that needs to be submitted to upload a new light client contract should contain the message [`MsgStoreCode`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/proto/ibc/lightclients/wasm/v1/tx.proto#L23-L30) with the base64-encoded byte code of the Wasm contract. Use the following CLI command and JSON as an example: + +```shell +simd tx gov submit-proposal --from +``` + +where `proposal.json` contains: + +```json +{ + "title": "Upload IBC Wasm light client", + "summary": "Upload wasm client", + "messages": [ + { + "@type": "/ibc.lightclients.wasm.v1.MsgStoreCode", + "signer": "cosmos1...", // the authority address (e.g. the gov module account address) + "wasm_byte_code": "YWJ...PUB+" // standard base64 encoding of the Wasm contract byte code + } + ], + "metadata": "AQ==", + "deposit": "100stake" +} +``` + +To learn more about the `submit-proposal` CLI command, please check out [the relevant section in Cosmos SDK documentation](https://docs.cosmos.network/main/modules/gov#submit-proposal). + +Alternatively, the process of submitting the proposal may be simpler if you use the CLI command `store-code`. This CLI command accepts as argument the file of the Wasm light client contract and takes care of constructing the proposal message with `MsgStoreCode` and broadcasting it. See section [`store-code`](./08-client.md#store-code) for more information. + +## Migrating an existing Wasm light client contract + +If governance is the allowed authority, the governance v1 proposal that needs to be submitted to migrate an existing new Wasm light client contract should contain the message [`MsgMigrateContract`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/proto/ibc/lightclients/wasm/v1/tx.proto#L52-L63) with the checksum of the Wasm byte code to migrate to. Use the following CLI command and JSON as an example: + +```shell +simd tx gov submit-proposal --from +``` + +where `proposal.json` contains: + +```json +{ + "title": "Migrate IBC Wasm light client", + "summary": "Migrate wasm client", + "messages": [ + { + "@type": "/ibc.lightclients.wasm.v1.MsgMigrateContract", + "signer": "cosmos1...", // the authority address (e.g. the gov module account address) + "client_id": "08-wasm-1", // client identifier of the Wasm light client contract that will be migrated + "checksum": "a8ad...4dc0", // SHA-256 hash of the Wasm byte code to migrate to, previously stored with MsgStoreCode + "msg": "{}" // JSON-encoded message to be passed to the contract on migration + } + ], + "metadata": "AQ==", + "deposit": "100stake" +} +``` + +To learn more about the `submit-proposal` CLI command, please check out [the relevant section in Cosmos SDK documentation](https://docs.cosmos.network/main/modules/gov#submit-proposal). + +## Removing an existing checksum + +If governance is the allowed authority, the governance v1 proposal that needs to be submitted to remove a specific checksum from the list of allowed checksums should contain the message [`MsgRemoveChecksum`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/proto/ibc/lightclients/wasm/v1/tx.proto#L39-L46) with the checksum (of a corresponding Wasm byte code). Use the following CLI command and JSON as an example: + +```shell +simd tx gov submit-proposal --from +``` + +where `proposal.json` contains: + +```json +{ + "title": "Remove checksum of Wasm light client byte code", + "summary": "Remove checksum", + "messages": [ + { + "@type": "/ibc.lightclients.wasm.v1.MsgRemoveChecksum", + "signer": "cosmos1...", // the authority address (e.g. the gov module account address) + "checksum": "a8ad...4dc0", // SHA-256 hash of the Wasm byte code that should be removed from the list of allowed checksums + } + ], + "metadata": "AQ==", + "deposit": "100stake" +} +``` + +To learn more about the `submit-proposal` CLI command, please check out [the relevant section in Cosmos SDK documentation](https://docs.cosmos.network/main/modules/gov#submit-proposal). diff --git a/docs/docs/03-light-clients/04-wasm/06-events.md b/docs/docs/03-light-clients/04-wasm/06-events.md new file mode 100644 index 0000000..5d68ce9 --- /dev/null +++ b/docs/docs/03-light-clients/04-wasm/06-events.md @@ -0,0 +1,26 @@ +--- +title: Events +sidebar_label: Events +sidebar_position: 6 +slug: /ibc/light-clients/wasm/events +--- + +# Events + +The `08-wasm` module emits the following events: + +## `MsgStoreCode` + +| Type | Attribute Key | Attribute Value | +|------------------|----------------|--------------------------| +| store_wasm_code | wasm_checksum | \{hex.Encode(checksum)\} | +| message | module | 08-wasm | + +## `MsgMigrateContract` + +| Type | Attribute Key | Attribute Value | +|------------------|----------------|-----------------------------| +| migrate_contract | client_id | \{clientId\} | +| migrate_contract | wasm_checksum | \{hex.Encode(checksum)\} | +| migrate_contract | new_checksum | \{hex.Encode(newChecksum)\} | +| message | module | 08-wasm | diff --git a/docs/docs/03-light-clients/04-wasm/07-contracts.md b/docs/docs/03-light-clients/04-wasm/07-contracts.md new file mode 100644 index 0000000..48d8bf9 --- /dev/null +++ b/docs/docs/03-light-clients/04-wasm/07-contracts.md @@ -0,0 +1,110 @@ +--- +title: Contracts +sidebar_label: Contracts +sidebar_position: 7 +slug: /ibc/light-clients/wasm/contracts +--- + +# Contracts + +Learn about the expected behaviour of Wasm light client contracts and the between with `08-wasm`. + +## API + +The `08-wasm` light client proxy performs calls to the Wasm light client via the Wasm VM. The calls require as input JSON-encoded payload messages that fall in the three categories described in the next sections. + +## `InstantiateMessage` + +This is the message sent to the contract's `instantiate` entry point. It contains the bytes of the protobuf-encoded client and consensus states of the underlying light client, both provided in [`MsgCreateClient`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/client/v1/tx.proto#L40-L52). Please note that the bytes contained within the JSON message are represented as base64-encoded strings. + +```go +type InstantiateMessage struct { + ClientState []byte `json:"client_state"` + ConsensusState []byte `json:"consensus_state"` + Checksum []byte `json:"checksum" +} +``` + +The Wasm light client contract is expected to store the client and consensus state in the corresponding keys of the client-prefixed store. + +## `QueryMsg` + +`QueryMsg` acts as a discriminated union type that is used to encode the messages that are sent to the contract's `query` entry point. Only one of the fields of the type should be set at a time, so that the other fields are omitted in the encoded JSON and the payload can be correctly translated to the corresponding element of the enumeration in Rust. + +```go +type QueryMsg struct { + Status *StatusMsg `json:"status,omitempty"` + TimestampAtHeight *TimestampAtHeightMsg `json:"timestamp_at_height,omitempty"` + VerifyClientMessage *VerifyClientMessageMsg `json:"verify_client_message,omitempty"` + CheckForMisbehaviour *CheckForMisbehaviourMsg `json:"check_for_misbehaviour,omitempty"` +} +``` + +```rust +#[cw_serde] +pub enum QueryMsg { + Status(StatusMsg), + TimestampAtHeight(TimestampAtHeightMsg), + VerifyClientMessage(VerifyClientMessageRaw), + CheckForMisbehaviour(CheckForMisbehaviourMsgRaw), +} +``` + +To learn what it is expected from the Wasm light client contract when processing each message, please read the corresponding section of the [Light client developer guide](../01-developer-guide/01-overview.md): + +- For `StatusMsg`, see the section [`Status` method](../01-developer-guide/03-client-state.md#status-method). +- For `TimestampAtHeightMsg`, see the section [`GetTimestampAtHeight` method](../01-developer-guide/03-client-state.md#gettimestampatheight-method). +- For `VerifyClientMessageMsg`, see the section [`VerifyClientMessage`](../01-developer-guide/05-updates-and-misbehaviour.md#verifyclientmessage). +- For `CheckForMisbehaviourMsg`, see the section [`CheckForMisbehaviour` method](../01-developer-guide/03-client-state.md#checkformisbehaviour-method). + +## `SudoMsg` + +`SudoMsg` acts as a discriminated union type that is used to encode the messages that are sent to the contract's `sudo` entry point. Only one of the fields of the type should be set at a time, so that the other fields are omitted in the encoded JSON and the payload can be correctly translated to the corresponding element of the enumeration in Rust. + +The `sudo` entry point is able to perform state-changing writes in the client-prefixed store. + +```go +type SudoMsg struct { + UpdateState *UpdateStateMsg `json:"update_state,omitempty"` + UpdateStateOnMisbehaviour *UpdateStateOnMisbehaviourMsg `json:"update_state_on_misbehaviour,omitempty"` + VerifyUpgradeAndUpdateState *VerifyUpgradeAndUpdateStateMsg `json:"verify_upgrade_and_update_state,omitempty"` + VerifyMembership *VerifyMembershipMsg `json:"verify_membership,omitempty"` + VerifyNonMembership *VerifyNonMembershipMsg `json:"verify_non_membership,omitempty"` + MigrateClientStore *MigrateClientStoreMsg `json:"migrate_client_store,omitempty"` +} +``` + +```rust +#[cw_serde] +pub enum SudoMsg { + UpdateState(UpdateStateMsgRaw), + UpdateStateOnMisbehaviour(UpdateStateOnMisbehaviourMsgRaw), + VerifyUpgradeAndUpdateState(VerifyUpgradeAndUpdateStateMsgRaw), + VerifyMembership(VerifyMembershipMsgRaw), + VerifyNonMembership(VerifyNonMembershipMsgRaw), + MigrateClientStore(MigrateClientStoreMsgRaw), +} +``` + +To learn what it is expected from the Wasm light client contract when processing each message, please read the corresponding section of the [Light client developer guide](../01-developer-guide/01-overview.md): + +- For `UpdateStateMsg`, see the section [`UpdateState`](../01-developer-guide/05-updates-and-misbehaviour.md#updatestate). +- For `UpdateStateOnMisbehaviourMsg`, see the section [`UpdateStateOnMisbehaviour`](../01-developer-guide/05-updates-and-misbehaviour.md#updatestateonmisbehaviour). +- For `VerifyUpgradeAndUpdateStateMsg`, see the section [`GetTimestampAtHeight` method](../01-developer-guide/06-upgrades.md#implementing-verifyupgradeandupdatestate). +- For `VerifyMembershipMsg`, see the section [`VerifyMembership` method](../01-developer-guide/03-client-state.md#verifymembership-method). +- For `VerifyNonMembershipMsg`, see the section [`VerifyNonMembership` method](../01-developer-guide/03-client-state.md#verifynonmembership-method). +- For `MigrateClientStoreMsg`, see the section [Implementing `CheckSubstituteAndUpdateState`](../01-developer-guide/08-proposals.md#implementing-checksubstituteandupdatestate). + +### Migration + +The `08-wasm` proxy light client exposes the `MigrateContract` RPC endpoint that can be used to migrate a given Wasm light client contract (specified by the client identifier) to a new Wasm byte code (specified by the hash of the byte code). The expected use case for this RPC endpoint is to enable contracts to migrate to new byte code in case the current byte code is found to have a bug or vulnerability. The Wasm byte code that contracts are migrated have to be uploaded beforehand using `MsgStoreCode` and must implement the `migrate` entry point. See section[`MsgMigrateContract`](./04-messages.md#msgmigratecontract) for information about the request message for this RPC endpoint. + +## Expected behaviour + +The `08-wasm` proxy light client modules expects the following behaviour from the Wasm light client contracts when executing messages that perform state-changing writes: + +- The contract must not delete the client state from the store. +- The contract must not change the client state to a client state of another type. +- The contract must not change the checksum in the client state. + +Any violation of these rules will result in an error returned from `08-wasm` that will abort the transaction. diff --git a/docs/docs/03-light-clients/04-wasm/08-client.md b/docs/docs/03-light-clients/04-wasm/08-client.md new file mode 100644 index 0000000..9f39830 --- /dev/null +++ b/docs/docs/03-light-clients/04-wasm/08-client.md @@ -0,0 +1,151 @@ +--- +title: Client +sidebar_label: Client +sidebar_position: 7 +slug: /ibc/light-clients/wasm/client +--- + +# Client + +## CLI + +A user can query and interact with the `08-wasm` module using the CLI. Use the `--help` flag to discover the available commands: + +### Transactions + +The `tx` commands allow users to interact with the `08-wasm` submodule. + +```shell +simd tx ibc-wasm --help +``` + +#### `store-code` + +The `store-code` command allows users to submit a governance proposal with a `MsgStoreCode` to store the byte code of a Wasm light client contract. + +```shell +simd tx ibc-wasm store-code [path/to/wasm-file] [flags] +``` + +`path/to/wasm-file` is the path to the `.wasm` or `.wasm.gz` file. + +#### `migrate-contract` + +The `migrate-contract` command allows users to broadcast a transaction with a `MsgMigrateContract` to migrate the contract for a given light client to a new byte code denoted by the given checksum. + +```shell +simd tx ibc-wasm migrate-contract [client-id] [checksum] [migrate-msg] +``` + +The migrate message must not be emptied and is expected to be a JSON-encoded string. + +### Query + +The `query` commands allow users to query `08-wasm` state. + +```shell +simd query ibc-wasm --help +``` + +#### `checksums` + +The `checksums` command allows users to query the list of checksums of Wasm light client contracts stored in the Wasm VM via the `MsgStoreCode`. The checksums are hex-encoded. + +```shell +simd query ibc-wasm checksums [flags] +``` + +Example: + +```shell +simd query ibc-wasm checksums +``` + +Example Output: + +```shell +checksums: +- c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64 +pagination: + next_key: null + total: "1" +``` + +#### `code` + +The `code` command allows users to query the Wasm byte code of a light client contract given the provided input checksum. + +```shell +./simd q ibc-wasm code +``` + +Example: + +```shell +simd query ibc-wasm code c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64 +``` + +Example Output: + +```shell +code: AGFzb...AqBBE= +``` + +## gRPC + +A user can query the `08-wasm` module using gRPC endpoints. + +### `Checksums` + +The `Checksums` endpoint allows users to query the list of checksums of Wasm light client contracts stored in the Wasm VM via the `MsgStoreCode`. + +```shell +ibc.lightclients.wasm.v1.Query/Checksums +``` + +Example: + +```shell +grpcurl -plaintext \ + -d '{}' \ + localhost:9090 \ + ibc.lightclients.wasm.v1.Query/Checksums +``` + +Example output: + +```shell +{ + "checksums": [ + "c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64" + ], + "pagination": { + "total": "1" + } +} +``` + +### `Code` + +The `Code` endpoint allows users to query the Wasm byte code of a light client contract given the provided input checksum. + +```shell +ibc.lightclients.wasm.v1.Query/Code +``` + +Example: + +```shell +grpcurl -plaintext \ + -d '{"checksum":"c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64"}' \ + localhost:9090 \ + ibc.lightclients.wasm.v1.Query/Code +``` + +Example output: + +```shell +{ + "code": AGFzb...AqBBE= +} +``` diff --git a/docs/docs/03-light-clients/04-wasm/09-migrations.md b/docs/docs/03-light-clients/04-wasm/09-migrations.md new file mode 100644 index 0000000..b70f90e --- /dev/null +++ b/docs/docs/03-light-clients/04-wasm/09-migrations.md @@ -0,0 +1,239 @@ +--- +title: Migrations +sidebar_label: Migrations +sidebar_position: 9 +slug: /ibc/light-clients/wasm/migrations +--- + +# Migrations + +This guide provides instructions for migrating 08-wasm versions. + +Please note that the following releases are retracted. Please refer to the appropriate migrations section for upgrading. + +```bash +v0.3.1-0.20240717085919-bb71eef0f3bf => v0.3.0+ibc-go-v8.3-wasmvm-v2.0 +v0.2.1-0.20240717085554-570d057959e3 => v0.2.0+ibc-go-v7.6-wasmvm-v1.5 +v0.2.1-0.20240523101951-4b45d1822fb6 => v0.2.0+ibc-go-v8.3-wasmvm-v2.0 +v0.1.2-0.20240412103620-7ee2a2452b79 => v0.1.1+ibc-go-v7.3-wasmvm-v1.5 +v0.1.1-0.20231213092650-57fcdb9a9a9d => v0.1.0+ibc-go-v8.0-wasmvm-v1.5 +v0.1.1-0.20231213092633-b306e7a706e1 => v0.1.0+ibc-go-v7.3-wasmvm-v1.5 +``` + +## From ibc-go v8.4.x to ibc-go v9.0.x + +### Chains + +- The `Initialize`, `Status`, `GetTimestampAtHeight`, `GetLatestHeight`, `VerifyMembership`, `VerifyNonMembership`, `VerifyClientMessage`, `UpdateState` and `UpdateStateOnMisbehaviour` functions in `ClientState` have been removed and all their logic has been moved to functions of the `LightClientModule`. +- The `MigrateContract` function has been removed from `ClientState`. +- The `VerifyMembershipMsg` and `VerifyNonMembershipMsg` payloads for `SudoMsg` have been modified. The `Path` field of both structs has been updated from `v1.MerklePath` to `v2.MerklePath`. The new `v2.MerklePath` field contains a `KeyPath` of `[][]byte` as opposed to `[]string`. This supports proving values stored under keys which contain non-utf8 encoded symbols. As a result, the JSON field `path` containing `key_path` of both messages will marshal elements as a base64 encoded bytestrings. This is a breaking change for 08-wasm client contracts and they should be migrated to correctly support deserialisation of the `v2.MerklePath` field. +- The `ExportMetadataMsg` struct has been removed and is no longer required for contracts to implement. Core IBC will handle exporting all key/value's written to the store by a light client contract. +- The `ZeroCustomFields` interface function has been removed from the `ClientState` interface. Core IBC only used this function to set tendermint client states when scheduling an IBC software upgrade. The interface function has been replaced by a type assertion. +- The `MaxWasmByteSize` function has been removed in favor of the `MaxWasmSize` constant. +- The `HasChecksum`, `GetAllChecksums` and `Logger` functions have been moved from the `types` package to a method on the `Keeper` type in the `keeper` package. +- The `InitializePinnedCodes` function has been moved to a method on the `Keeper` type in the `keeper` package. +- The `CustomQuerier`, `StargateQuerier` and `QueryPlugins` types have been moved from the `types` package to the `keeper` package. +- The `NewDefaultQueryPlugins`, `AcceptListStargateQuerier` and `RejectCustomQuerier` functions has been moved from the `types` package to the `keeper` package. +- The `NewDefaultQueryPlugins` function signature has changed to take an argument: `queryRouter ibcwasm.QueryRouter`. +- The `AcceptListStargateQuerier` function signature has changed to take an additional argument: `queryRouter ibcwasm.QueryRouter`. +- The `WithQueryPlugins` function signature has changed to take in the `QueryPlugins` type from the `keeper` package (previously from the `types` package). +- The `VMGasRegister` variable has been moved from the `types` package to the `keeper` package. + +## From v0.3.0+ibc-go-v8.3-wasmvm-v2.0 to v0.4.1-ibc-go-v8.4-wasmvm-v2.0 + +### Contract developers + +Contract developers are required to update their JSON API message structure for the `SudoMsg` payloads `VerifyMembershipMsg` and `VerifyNonMembershipMsg`. +The `path` field on both JSON API messages has been renamed to `merkle_path`. + +A migration is required for existing 08-wasm client contracts in order to correctly handle the deserialisation of these fields. + +## From v0.2.0+ibc-go-v7.3-wasmvm-v1.5 to v0.3.1-ibc-go-v7.4-wasmvm-v1.5 + +### Contract developers + +Contract developers are required to update their JSON API message structure for the `SudoMsg` payloads `VerifyMembershipMsg` and `VerifyNonMembershipMsg`. +The `path` field on both JSON API messages has been renamed to `merkle_path`. + +A migration is required for existing 08-wasm client contracts in order to correctly handle the deserialisation of these fields. + +## From v0.2.0+ibc-go-v8.3-wasmvm-v2.0 to v0.3.0-ibc-go-v8.3-wasmvm-v2.0 + +### Contract developers + +The `v0.3.0` release of 08-wasm for ibc-go `v8.3.x` and above introduces a breaking change for client contract developers. + +The contract API `SudoMsg` payloads `VerifyMembershipMsg` and `VerifyNonMembershipMsg` have been modified. +The encoding of the `Path` field of both structs has been updated from `v1.MerklePath` to `v2.MerklePath` to support proving values stored under keys which contain non-utf8 encoded symbols. + +As a result, the `Path` field now contains a `MerklePath` composed of `key_path` of `[][]byte` as opposed to `[]string`. The JSON field `path` containing `key_path` of both `VerifyMembershipMsg` and `VerifyNonMembershipMsg` structs will now marshal elements as base64 encoded bytestrings. See below for example JSON diff. + +```diff +{ + "verify_membership": { + "height": { + "revision_height": 1 + }, + "delay_time_period": 0, + "delay_block_period": 0, + "proof":"dmFsaWQgcHJvb2Y=", + "path": { ++ "key_path":["L2liYw==","L2tleS9wYXRo"] +- "key_path":["/ibc","/key/path"] + }, + "value":"dmFsdWU=" + } +} +``` + +A migration is required for existing 08-wasm client contracts in order to correctly handle the deserialisation of `key_path` from `[]string` to `[][]byte`. +Contract developers should familiarise themselves with the migration path offered by 08-wasm [here](./05-governance.md#migrating-an-existing-wasm-light-client-contract). + +An example of the required changes in a client contract may look like: + +```diff +#[cw_serde] +pub struct MerklePath { ++ pub key_path: Vec, +- pub key_path: Vec, +} +``` + +Please refer to the [`cosmwasm_std`](https://docs.rs/cosmwasm-std/2.0.4/cosmwasm_std/struct.Binary.html) documentation for more information. + +## From v0.1.1+ibc-go-v7.3-wasmvm-v1.5 to v0.2.0-ibc-go-v7.3-wasmvm-v1.5 + +### Contract developers + +The `v0.2.0` release of 08-wasm for ibc-go `v7.6.x` and above introduces a breaking change for client contract developers. + +The contract API `SudoMsg` payloads `VerifyMembershipMsg` and `VerifyNonMembershipMsg` have been modified. +The encoding of the `Path` field of both structs has been updated from `v1.MerklePath` to `v2.MerklePath` to support proving values stored under keys which contain non-utf8 encoded symbols. + +As a result, the `Path` field now contains a `MerklePath` composed of `key_path` of `[][]byte` as opposed to `[]string`. The JSON field `path` containing `key_path` of both `VerifyMembershipMsg` and `VerifyNonMembershipMsg` structs will now marshal elements as base64 encoded bytestrings. See below for example JSON diff. + +```diff +{ + "verify_membership": { + "height": { + "revision_height": 1 + }, + "delay_time_period": 0, + "delay_block_period": 0, + "proof":"dmFsaWQgcHJvb2Y=", + "path": { ++ "key_path":["L2liYw==","L2tleS9wYXRo"] +- "key_path":["/ibc","/key/path"] + }, + "value":"dmFsdWU=" + } +} +``` + +A migration is required for existing 08-wasm client contracts in order to correctly handle the deserialisation of `key_path` from `[]string` to `[][]byte`. +Contract developers should familiarise themselves with the migration path offered by 08-wasm [here](./05-governance.md#migrating-an-existing-wasm-light-client-contract). + +An example of the required changes in a client contract may look like: + +```diff +#[cw_serde] +pub struct MerklePath { ++ pub key_path: Vec, +- pub key_path: Vec, +} +``` + +Please refer to the [`cosmwasm_std`](https://docs.rs/cosmwasm-std/2.0.4/cosmwasm_std/struct.Binary.html) documentation for more information. + +## From ibc-go v7.3.x to ibc-go v8.0.x + +### Chains + +In the 08-wasm versions compatible with ibc-go v7.3.x and above from the v7 release line, the checksums of the uploaded Wasm bytecodes are all stored under a single key. From ibc-go v8.0.x the checksums are stored using [`collections.KeySet`](https://docs.cosmos.network/v0.50/build/packages/collections#keyset), whose full functionality became available in Cosmos SDK v0.50. There is therefore an [automatic migration handler](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/module.go#L115-L118) configured in the 08-wasm module to migrate the stored checksums to `collections.KeySet`. + +## From v0.1.0+ibc-go-v8.0-wasmvm-v1.5 to v0.2.0-ibc-go-v8.3-wasmvm-v2.0 + +The `WasmEngine` interface has been updated to reflect changes in the function signatures of Wasm VM: + +```diff +type WasmEngine interface { +- StoreCode(code wasmvm.WasmCode) (wasmvm.Checksum, error) ++ StoreCode(code wasmvm.WasmCode, gasLimit uint64) (wasmvmtypes.Checksum, uint64, error) + + StoreCodeUnchecked(code wasmvm.WasmCode) (wasmvm.Checksum, error) + + Instantiate( + checksum wasmvm.Checksum, + env wasmvmtypes.Env, + info wasmvmtypes.MessageInfo, + initMsg []byte, + store wasmvm.KVStore, + goapi wasmvm.GoAPI, + querier wasmvm.Querier, + gasMeter wasmvm.GasMeter, + gasLimit uint64, + deserCost wasmvmtypes.UFraction, +- ) (*wasmvmtypes.Response, uint64, error) ++ ) (*wasmvmtypes.ContractResult, uint64, error) + + Query( + checksum wasmvm.Checksum, + env wasmvmtypes.Env, + queryMsg []byte, + store wasmvm.KVStore, + goapi wasmvm.GoAPI, + querier wasmvm.Querier, + gasMeter wasmvm.GasMeter, + gasLimit uint64, + deserCost wasmvmtypes.UFraction, +- ) ([]byte, uint64, error) ++ ) (*wasmvmtypes.QueryResult, uint64, error) + + Migrate( + checksum wasmvm.Checksum, + env wasmvmtypes.Env, + migrateMsg []byte, + store wasmvm.KVStore, + goapi wasmvm.GoAPI, + querier wasmvm.Querier, + gasMeter wasmvm.GasMeter, + gasLimit uint64, + deserCost wasmvmtypes.UFraction, +- ) (*wasmvmtypes.Response, uint64, error) ++ ) (*wasmvmtypes.ContractResult, uint64, error) + + Sudo( + checksum wasmvm.Checksum, + env wasmvmtypes.Env, + sudoMsg []byte, + store wasmvm.KVStore, + goapi wasmvm.GoAPI, + querier wasmvm.Querier, + gasMeter wasmvm.GasMeter, + gasLimit uint64, + deserCost wasmvmtypes.UFraction, +- ) (*wasmvmtypes.Response, uint64, error) ++ ) (*wasmvmtypes.ContractResult, uint64, error) + + GetCode(checksum wasmvm.Checksum) (wasmvm.WasmCode, error) + + Pin(checksum wasmvm.Checksum) error + + Unpin(checksum wasmvm.Checksum) error +} +``` + +Similar changes were required in the functions of `MockWasmEngine` interface. + +### Chains + +The `SupportedCapabilities` field of `WasmConfig` is now of type `[]string`: + +```diff +type WasmConfig struct { + DataDir string +- SupportedCapabilities string ++ SupportedCapabilities []string + ContractDebugMode bool +} +``` diff --git a/docs/docs/03-light-clients/04-wasm/_category_.json b/docs/docs/03-light-clients/04-wasm/_category_.json new file mode 100644 index 0000000..51c4eb7 --- /dev/null +++ b/docs/docs/03-light-clients/04-wasm/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Wasm", + "position": 4, + "link": null +} \ No newline at end of file diff --git a/docs/docs/03-light-clients/05-tendermint/01-overview.md b/docs/docs/03-light-clients/05-tendermint/01-overview.md new file mode 100644 index 0000000..1971b4c --- /dev/null +++ b/docs/docs/03-light-clients/05-tendermint/01-overview.md @@ -0,0 +1,167 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /ibc/light-clients/tendermint/overview +--- + +# `07-tendermint` + +## Overview + +:::note Synopsis +Learn about the 07-tendermint light client module. +::: + +The Tendermint client is the first and most deployed light client in IBC. It implements the IBC [light client module interface](https://github.com/cosmos/ibc-go/blob/v9.0.0-beta.1/modules/core/exported/client.go#L41-L123) to track a counterparty running [CometBFT](https://github.com/cometbft/cometbft) consensus. + +:::note +Tendermint is the old name of CometBFT which has been retained in IBC to avoid expensive migration costs. +::: + +The Tendermint client consists of two important structs that keep track of the state of the counterparty chain and allow for future updates. The `ClientState` struct contains all the parameters necessary for CometBFT header verification. The `ConsensusState`, on the other hand, is a compressed view of a particular header of the counterparty chain. Unlike off chain light clients, IBC does not store full header. Instead it stores only the information it needs to prove verification of key/value pairs in the counterparty state (i.e. the header `AppHash`), and the information necessary to use the consensus state as the next root of trust to add a new consensus state to the client (i.e. the header `NextValidatorsHash` and `Timestamp`). The relayer provides the full trusted header on `UpdateClient`, which will get checked against the compressed root-of-trust consensus state. If the trusted header matches a previous consensus state, and the trusted header and new header pass the CometBFT light client update algorithm, then the new header is compressed into a consensus state and added to the IBC client. + +Each Tendermint Client is composed of a single `ClientState` keyed on the client ID, and multiple consensus states which are keyed on both the clientID and header height. Relayers can use the consensus states to verify merkle proofs of packet commitments, acknowledgements, and receipts against the `AppHash` of the counterparty chain in order to enable verified packet flow. + +If a counterparty chain violates the CometBFT protocol in a way that is detectable to off-chain light clients, this misbehaviour can also be submitted to an IBC client by any off-chain actor. Upon verification of this misbehaviour, the Tendermint IBC Client will freeze, preventing any further packet flow from this malicious chain from occurring. Governance or some other out-of-band protocol may then be used to unwind any damage that has already occurred. + +## Initialization + +The Tendermint light client is initialized with a `ClientState` that contains parameters necessary for CometBFT header verification along with a latest height and `ConsensusState` that encapsulates the application state root of a trusted header that will serve to verify future incoming headers from the counterparty. + +```proto +message ClientState { + // human readable chain-id that will be included in header + // and signed over by the validator set + string chain_id = 1; + // trust level is the fraction of the trusted validator set + // that must sign over a new untrusted header before it is accepted + // it can be a minimum of 1/3 and a maximum of 2/3 + // Note these are the bounds of liveness. 1/3 is the minimum + // honest stake needed to maintain liveness on a chain, + // requiring more than 2/3 to sign over the new header would + // break the BFT threshold of allowing 1/3 malicious validators + Fraction trust_level = 2; + // duration of the period since the LatestTimestamp during which the + // submitted headers are valid for update + google.protobuf.Duration trusting_period = 3; + // duration of the staking unbonding period + google.protobuf.Duration unbonding_period = 4; + // defines how much new (untrusted) header's Time can drift + // into the future relative to our local clock. + google.protobuf.Duration max_clock_drift = 5; + + // Block height when the client was frozen due to a misbehaviour + ibc.core.client.v1.Height frozen_height = 6; + // Latest height the client was updated to + ibc.core.client.v1.Height latest_height = 7; + + // Proof specifications used in verifying counterparty state + repeated cosmos.ics23.v1.ProofSpec proof_specs = 8; + + // Path at which next upgraded client will be committed. + // Each element corresponds to the key for a single CommitmentProof in the + // chained proof. NOTE: ClientState must stored under + // `{upgradePath}/{upgradeHeight}/clientState` ConsensusState must be stored + // under `{upgradepath}/{upgradeHeight}/consensusState` For SDK chains using + // the default upgrade module, upgrade_path should be []string{"upgrade", + // "upgradedIBCState"}` + repeated string upgrade_path = 9; +} +``` + +```proto +message ConsensusState { + // timestamp that corresponds to the block height in which the ConsensusState + // was stored. + google.protobuf.Timestamp timestamp = 1; + // commitment root (i.e app hash) that will be used + // to verify proofs of packet flow messages + ibc.core.commitment.v1.MerkleRoot root = 2; + // hash of the next validator set that will be used as + // a new updated source of trust to verify future updates + bytes next_validators_hash = 3; +} +``` + +## Updates + +Once the initial client state and consensus state are submitted, future consensus states can be added to the client by submitting IBC [headers](https://github.com/cosmos/ibc-go/blob/v9.0.0-beta.1/proto/ibc/lightclients/tendermint/v1/tendermint.proto#L76-L94). These headers contain all necessary information to run the CometBFT light client protocol. + +```proto +message Header { + // this is the new signed header that we want to add + // as a new consensus state to the ibc client. + // the signed header contains the commit signatures of the `validator_set` below + .tendermint.types.SignedHeader signed_header = 1; + + // the validator set which signed the new header + .tendermint.types.ValidatorSet validator_set = 2; + // the trusted height of the consensus state which we are updating from + ibc.core.client.v1.Height trusted_height = 3; + // the trusted validator set, the hash of the trusted validators must be equal to + // `next_validators_hash` of the current consensus state + .tendermint.types.ValidatorSet trusted_validators = 4; +} +``` + +For detailed information on the CometBFT light client protocol and its safety properties please refer to the [original Tendermint whitepaper](https://arxiv.org/abs/1807.04938). + +## Proofs + +As consensus states are added to the client, they can be used for proof verification by relayers wishing to prove packet flow messages against a particular height on the counterparty. This uses the `VerifyMembership` and `VerifyNonMembership` methods on the Tendermint client. + +```go +// VerifyMembership is a generic proof verification method +//which verifies a proof of the existence of a value at a +// given CommitmentPath at the specified height. The caller +// is expected to construct the full CommitmentPath from a +// CommitmentPrefix and a standardized path (as defined in ICS 24). +VerifyMembership( + ctx sdk.Context, + clientID string, + height Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path Path, + value []byte, +) error + +// VerifyNonMembership is a generic proof verification method +// which verifies the absence of a given CommitmentPath at a +// specified height. The caller is expected to construct the +// full CommitmentPath from a CommitmentPrefix and a standardized +// path (as defined in ICS 24). +VerifyNonMembership( + ctx sdk.Context, + clientID string, + height Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path Path, +) error +``` + +The Tendermint client is initialized with an ICS23 proof spec. This allows the Tendermint implementation to support many different merkle tree structures so long as they can be represented in an [`ics23.ProofSpec`](https://github.com/cosmos/ics23/blob/go/v0.10.0/proto/cosmos/ics23/v1/proofs.proto#L145-L170). + +## Misbehaviour + +The Tendermint light client directly tracks consensus of a CometBFT counterparty chain. So long as the counterparty is Byzantine Fault Tolerant, that is to say, the malicious subset of the bonded validators does not exceed the trust level of the client, then the client is secure. + +In case the malicious subset of the validators exceeds the trust level of the client, then the client can be deceived into accepting invalid blocks and the connection is no longer secure. + +The Tendermint client has some mitigations in place to prevent this. If there are two valid blocks signed by the counterparty validator set at the same height [e.g. a valid block signed by an honest subset and an invalid block signed by a malicious one], then these conflicting headers can be submitted to the client as [misbehaviour](https://github.com/cosmos/ibc-go/blob/v9.0.0-beta.1/proto/ibc/lightclients/tendermint/v1/tendermint.proto#L65-L74). The client will verify the headers and freeze the client; preventing any future updates and proof verification from succeeding. This effectively halts communication with the compromised counterparty while out-of-band social consensus can unwind any damage done. + +Similarly, if the timestamps of the headers are not monotonically increasing, this can also be evidence of malicious behaviour and cause the client to freeze. + +Thus, any consensus faults that are detectable by a light client are part of the misbehaviour protocol and can be used to minimize the damage caused by a compromised counterparty chain. + +### Security model + +It is important to note that IBC is not a completely trustless protocol; it is **trust-minimized**. This means that the safety property of bilateral IBC communication between two chains is dependent on the safety properties of the two chains in question. If one of the chains is compromised completely, then the IBC connection to the other chain is liable to receive invalid packets from the malicious chain. For example, if a malicious validator set has taken over more than 2/3 of the validator power on a chain; that malicious validator set can create a single chain of blocks with arbitrary commitment roots and arbitrary commitments to the next validator set. This would seize complete control of the chain and prevent the honest subset from even being able to create a competing honest block. + +In this case, there is no ability for the IBC Tendermint client solely tracking CometBFT consensus to detect the misbehaviour and freeze the client. The IBC protocol would require out-of-band mechanisms to detect and fix such an egregious safety fault on the counterparty chain. Since the Tendermint light client is only tracking consensus and not also verifying the validity of state transitions, malicious behaviour from a validator set that is beyond the BFT fault threshold is an accepted risk of this light client implementation. + +The IBC protocol has principles of fault isolation (e.g. all tokens are prefixed by their channel, so tokens from different chains are not mutually fungible) and fault mitigation (e.g. ability to freeze the client if misbehaviour can be detected before complete malicious takeover) that make this risk as minimal as possible. diff --git a/docs/docs/03-light-clients/05-tendermint/_category_.json b/docs/docs/03-light-clients/05-tendermint/_category_.json new file mode 100644 index 0000000..e45e833 --- /dev/null +++ b/docs/docs/03-light-clients/05-tendermint/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Tendermint", + "position": 5, + "link": null + } diff --git a/docs/docs/03-light-clients/06-proposals.md b/docs/docs/03-light-clients/06-proposals.md new file mode 100644 index 0000000..85c74b7 --- /dev/null +++ b/docs/docs/03-light-clients/06-proposals.md @@ -0,0 +1,120 @@ +--- +title: Governance Proposals +sidebar_label: Light Client Recovery +sidebar_position: 6 +slug: /ibc/proposals +--- + +# Light Client Recovery + +In uncommon situations, a highly valued client may become frozen or expire due to uncontrollable +circumstances. A highly valued client might have hundreds of channels being actively used. +Some of those channels might have a significant amount of locked tokens used for ICS 20. + +## Frozen Light Clients + +If the one third of the validator set of the chain the client represents decides to collude, +they can sign off on two valid but conflicting headers each signed by the other one third +of the honest validator set. The light client can now be updated with two valid, but conflicting +headers at the same height. The light client cannot know which header is trustworthy and therefore +evidence of such misbehaviour is likely to be submitted resulting in a frozen light client. + +Frozen light clients cannot be updated under any circumstance except via a governance proposal. +Since a quorum of validators can sign arbitrary state roots which may not be valid executions +of the state machine, a governance proposal has been added to ease the complexity of unfreezing +or updating clients which have become "stuck". Without this mechanism, validator sets would need +to construct a state root to unfreeze the client. Unfreezing clients, re-enables all of the channels +built upon that client. This may result in recovery of otherwise lost funds. + +## Expired Light Clients + +Tendermint light clients may become expired if the trusting period has passed since their +last update. This may occur if relayers stop submitting headers to update the clients. + +An unplanned upgrade by the counterparty chain may also result in expired clients. If the counterparty +chain undergoes an unplanned upgrade, there may be no commitment to that upgrade signed by the validator +set before the chain ID changes. In this situation, the validator set of the last valid update for the +light client is never expected to produce another valid header since the chain ID has changed, which will +ultimately lead the on-chain light client to become expired. + +# How to recover an expired client with a governance proposal + +> **Who is this information for?** +> Although technically anyone can submit the governance proposal to recover an expired client, often it will be **relayer operators** (at least coordinating the submission). + +In the case that a highly valued light client is frozen, expired, or rendered non-updateable, a +governance proposal may be submitted to update this client, known as the subject client. The +proposal includes the client identifier for the subject and the client identifier for a substitute +client. Light client implementations may implement custom updating logic, but in most cases, +the subject will be updated to the latest consensus state of the substitute client, if the proposal passes. +The substitute client is used as a "stand in" while the subject is on trial. It is best practice to create +a substitute client *after* the subject has become frozen to avoid the substitute from also becoming frozen. +An active substitute client allows headers to be submitted during the voting period to prevent accidental expiry +once the proposal passes. + +See also the relevant documentation: [ADR-026, IBC client recovery mechanisms](/architecture/adr-026-ibc-client-recovery-mechanisms) + +## Preconditions + +- There exists an active client (with a known client identifier) for the same counterparty chain as the expired client. +- The governance deposit. + +## Steps + +### Step 1 + +Check if the client is attached to the expected `chain_id`. For example, for an expired Tendermint client representing the Akash chain the client state looks like this on querying the client state: + +```text +{ + client_id: 07-tendermint-146 + client_state: + '@type': /ibc.lightclients.tendermint.v1.ClientState + allow_update_after_expiry: true + allow_update_after_misbehaviour: true + chain_id: akashnet-2 +} +``` + +The client is attached to the expected Akash `chain_id`. Note that although the parameters (`allow_update_after_expiry` and `allow_update_after_misbehaviour`) exist to signal intent, these parameters have been deprecated and will not enforce any checks on the revival of client. See ADR-026 for more context on this deprecation. + +### Step 2 + +Anyone can submit the governance proposal to recover the client by executing the following via CLI. +If the chain is on an ibc-go version older than v8, please see the [relevant documentation](https://ibc.cosmos.network/v7/ibc/proposals). + +- From ibc-go v8 onwards + + ```shell + tx gov submit-proposal [path-to-proposal-json] + ``` + + where `proposal.json` contains: + + ```json + { + "messages": [ + { + "@type": "/ibc.core.client.v1.MsgRecoverClient", + "subject_client_id": "", + "substitute_client_id": "", + "signer": "" + } + ], + "metadata": "", + "deposit": "10stake" + "title": "My proposal", + "summary": "A short summary of my proposal", + "expedited": false + } + ``` + +The `` identifier is the proposed client to be updated. This client must be either frozen or expired. + +The `` represents a substitute client. It carries all the state for the client which may be updated. It must have identical client and chain parameters to the client which may be updated (except for latest height, frozen height, and chain ID). It should be continually updated during the voting period. + +After this, all that remains is deciding who funds the governance deposit and ensuring the governance proposal passes. If it does, the client on trial will be updated to the latest state of the substitute. + +## Important considerations + +Please note that if the counterparty client is also expired, that client will also need to update. This process updates only one client. diff --git a/docs/docs/03-light-clients/_category_.json b/docs/docs/03-light-clients/_category_.json new file mode 100644 index 0000000..e42f1b9 --- /dev/null +++ b/docs/docs/03-light-clients/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "IBC Light Clients", + "position": 3, + "link": null +} \ No newline at end of file diff --git a/docs/docs/04-middleware/01-callbacks/01-overview.md b/docs/docs/04-middleware/01-callbacks/01-overview.md new file mode 100644 index 0000000..a5abfd3 --- /dev/null +++ b/docs/docs/04-middleware/01-callbacks/01-overview.md @@ -0,0 +1,51 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /middleware/callbacks/overview +--- + +# Overview + +Learn about what the Callbacks Middleware is, and how to build custom modules that utilize the Callbacks Middleware functionality + +## What is the Callbacks Middleware? + +IBC was designed with callbacks between core IBC and IBC applications. IBC apps would send a packet to core IBC, and receive a callback on every step of that packet's lifecycle. This allows IBC applications to be built on top of core IBC, and to be able to execute custom logic on packet lifecycle events (e.g. unescrow tokens for ICS-20). + +This setup worked well for off-chain users interacting with IBC applications. However, we are now seeing the desire for secondary applications (e.g. smart contracts, modules) to call into IBC apps as part of their state machine logic and then do some actions on packet lifecycle events. + +The Callbacks Middleware allows for this functionality by allowing the packets of the underlying IBC applications to register callbacks to secondary applications for lifecycle events. These callbacks are then executed by the Callbacks Middleware when the corresponding packet lifecycle event occurs. + +After much discussion, the design was expanded to [an ADR](/architecture/adr-008-app-caller-cbs), and the Callbacks Middleware is an implementation of that ADR. + +## Concepts + +Callbacks Middleware was built with smart contracts in mind, but can be used by any secondary application that wants to allow IBC packets to call into it. Think of the Callbacks Middleware as a bridge between core IBC and a secondary application. + +We have the following definitions: + +- `Underlying IBC application`: The IBC application that is wrapped by the Callbacks Middleware. This is the IBC application that is actually sending and receiving packet lifecycle events from core IBC. For example, the transfer module, or the ICA controller submodule. +- `IBC Actor`: IBC Actor is an on-chain or off-chain entity that can initiate a packet on the underlying IBC application. For example, a smart contract, an off-chain user, or a module that sends a transfer packet are all IBC Actors. +- `Secondary application`: The application that is being called into by the Callbacks Middleware for packet lifecycle events. This is the application that is receiving the callback directly from the Callbacks Middleware module. For example, the `x/wasm` module. +- `Callback Actor`: The on-chain smart contract or module that is registered to receive callbacks from the secondary application. For example, a Wasm smart contract (gatekeeped by the `x/wasm` module). Note that the Callback Actor is not necessarily the same as the IBC Actor. For example, an off-chain user can initiate a packet on the underlying IBC application, but the Callback Actor could be a smart contract. The secondary application may want to check that the IBC Actor is allowed to call into the Callback Actor, for example, by checking that the IBC Actor is the same as the Callback Actor. +- `Callback Address`: Address of the Callback Actor. This is the address that the secondary application will call into when a packet lifecycle event occurs. For example, the address of the Wasm smart contract. +- `Maximum gas limit`: The maximum amount of gas that the Callbacks Middleware will allow the secondary application to use when it executes its custom logic. +- `User defined gas limit`: The amount of gas that the IBC Actor wants to allow the secondary application to use when it executes its custom logic. This is the gas limit that the IBC Actor specifies when it sends a packet to the underlying IBC application. This cannot be greater than the maximum gas limit. + +Think of the secondary application as a bridge between the Callbacks Middleware and the Callback Actor. The secondary application is responsible for executing the custom logic of the Callback Actor when a packet lifecycle event occurs. The secondary application is also responsible for checking that the IBC Actor is allowed to call into the Callback Actor. + +Note that it is possible that the IBC Actor, Secondary Application, and Callback Actor are all the same entity. In which case, the Callback Address should be the secondary application's module address. + +The following diagram shows how a typical `RecvPacket`, `AcknowledgementPacket`, and `TimeoutPacket` execution flow would look like: +![callbacks-middleware](./images/callbackflow.svg) + +And the following diagram shows how a typical `SendPacket` and `WriteAcknowledgement` execution flow would look like: +![callbacks-middleware](./images/ics4-callbackflow.svg) + +## Known Limitations + +- Callbacks are always executed after the underlying IBC application has executed its logic. +- Maximum gas limit is hardcoded manually during wiring. It requires a coordinated upgrade to change the maximum gas limit. +- The receive packet callback does not pass the relayer address to the secondary application. This is so that we can use the same callback for both synchronous and asynchronous acknowledgements. +- The receive packet callback does not pass IBC Actor's address, this is because the IBC Actor lives in the counterparty chain and cannot be trusted. diff --git a/docs/docs/04-middleware/01-callbacks/02-integration.md b/docs/docs/04-middleware/01-callbacks/02-integration.md new file mode 100644 index 0000000..332a5e4 --- /dev/null +++ b/docs/docs/04-middleware/01-callbacks/02-integration.md @@ -0,0 +1,81 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /middleware/callbacks/integration +--- + +# Integration + +Learn how to integrate the callbacks middleware with IBC applications. The following document is intended for developers building on top of the Cosmos SDK and only applies for Cosmos SDK chains. + +:::tip +An example integration for an IBC v2 transfer stack using the callbacks middleware can be found in the [ibc-go module integration](../../01-ibc/02-integration.md) section +::: + +The callbacks middleware is a minimal and stateless implementation of the IBC middleware interface. It does not have a keeper, nor does it store any state. It simply routes IBC middleware messages to the appropriate callback function, which is implemented by the secondary application. Therefore, it doesn't need to be registered as a module, nor does it need to be added to the module manager. It only needs to be added to the IBC application stack. + +## Pre-requisite Readings + +- [IBC middleware development](../../01-ibc/04-middleware/02-develop.md) +- [IBC middleware integration](../../01-ibc/04-middleware/03-integration.md) + +The callbacks middleware, as the name suggests, plays the role of an IBC middleware and as such must be configured by chain developers to route and handle IBC messages correctly. +For Cosmos SDK chains this setup is done via the `app/app.go` file, where modules are constructed and configured in order to bootstrap the blockchain application. + +## Configuring an application stack with the callbacks middleware + +As mentioned in [IBC middleware development](../../01-ibc/04-middleware/02-develop.md) an application stack may be composed of many or no middlewares that nest a base application. +These layers form the complete set of application logic that enable developers to build composable and flexible IBC application stacks. +For example, an application stack may just be a single base application like `transfer`, however, the same application stack composed with `packet-forward-middleware` and `callbacks` will nest the `transfer` base application twice by wrapping it with the callbacks module and then packet forward middleware. + +The callbacks middleware also **requires** a secondary application that will receive the callbacks to implement the [`ContractKeeper`](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/types/expected_keepers.go#L12-L100). The wasmd contract keeper has been implemented [here](https://github.com/CosmWasm/wasmd/tree/main/x/wasm/keeper) and is referenced as the `WasmKeeper`. + +### Transfer + +See below for an example of how to create an application stack using `transfer`, `packet-forward-middleware`, and `callbacks`. Feel free to omit the `packet-forward-middleware` if you do not want to use it. +The following `transferStack` is configured in `app/app.go` and added to the IBC `Router`. +The in-line comments describe the execution flow of packets between the application stack and IBC core. + +```go +// Create Transfer Stack +// SendPacket, since it is originating from the application to core IBC: +// transferKeeper.SendPacket -> callbacks.SendPacket -> feeKeeper.SendPacket -> channel.SendPacket + +// RecvPacket, message that originates from core IBC and goes down to app, the flow is the other way +// channel.RecvPacket -> fee.OnRecvPacket -> callbacks.OnRecvPacket -> transfer.OnRecvPacket + +// transfer stack contains (from top to bottom): +// - IBC Packet Forward Middleware +// - IBC Callbacks Middleware +// - Transfer + +// initialise the gas limit for callbacks, recommended to be 10M for use with cosmwasm contracts +maxCallbackGas := uint64(10_000_000) + +// the keepers for the callbacks middleware +wasmStackIBCHandler := wasm.NewIBCHandler(app.WasmKeeper, app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper) + +// create IBC module from bottom to top of stack +// Create Transfer Stack + var transferStack porttypes.IBCModule + transferStack = transfer.NewIBCModule(app.TransferKeeper) +// callbacks wraps the transfer stack as its base app, and uses PacketForwardKeeper as the ICS4Wrapper +// i.e. packet-forward-middleware is higher on the stack and sits between callbacks and the ibc channel keeper +// Since this is the lowest level middleware of the transfer stack, it should be the first entrypoint for transfer keeper's +// WriteAcknowledgement. + cbStack := ibccallbacks.NewIBCMiddleware(transferStack, app.PacketForwardKeeper, wasmStackIBCHandler, maxCallbackGas) + transferStack = packetforward.NewIBCMiddleware( + cbStack, + app.PacketForwardKeeper, + 0, + packetforwardkeeper.DefaultForwardTransferPacketTimeoutTimestamp, + ) + app.TransferKeeper.WithICS4Wrapper(cbStack) + +// Create static IBC router, add app routes, then set and seal it + ibcRouter := porttypes.NewRouter() + ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) + ibcRouter.AddRoute(wasmtypes.ModuleName, wasmStackIBCHandler) + app.IBCKeeper.SetRouter(ibcRouter) +``` diff --git a/docs/docs/04-middleware/01-callbacks/03-interfaces.md b/docs/docs/04-middleware/01-callbacks/03-interfaces.md new file mode 100644 index 0000000..9830f3b --- /dev/null +++ b/docs/docs/04-middleware/01-callbacks/03-interfaces.md @@ -0,0 +1,170 @@ +--- +title: Interfaces +sidebar_label: Interfaces +sidebar_position: 3 +slug: /middleware/callbacks/interfaces +--- + +# Interfaces + +The callbacks middleware requires certain interfaces to be implemented by the underlying IBC applications and the secondary application. If you're simply wiring up the callbacks middleware to an existing IBC application stack and a secondary application such as `icacontroller` and `x/wasm`, you can skip this section. + +## Interfaces for developing the Underlying IBC Application + +### `PacketDataUnmarshaler` + +```go +// PacketDataUnmarshaler defines an optional interface which allows a middleware to +// request the packet data to be unmarshaled by the base application. +type PacketDataUnmarshaler interface { + // UnmarshalPacketData unmarshals the packet data into a concrete type + // ctx, portID, channelID are provided as arguments, so that (if needed) + // the packet data can be unmarshaled based on the channel version. + // The version of the underlying app is also returned. + UnmarshalPacketData(ctx sdk.Context, portID, channelID string, bz []byte) (interface{}, string, error) +} +``` + +The callbacks middleware **requires** the underlying ibc application to implement the [`PacketDataUnmarshaler`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/core/05-port/types/module.go#L142-L147) interface so that it can unmarshal the packet data bytes into the appropriate packet data type. This allows usage of interface functions implemented by the packet data type. The packet data type is expected to implement the `PacketDataProvider` interface (see section below), which is used to parse the callback data that is currently stored in the packet memo field for `transfer` and `ica` packets as a JSON string. See its implementation in the [`transfer`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/transfer/ibc_module.go#L303-L313) and [`icacontroller`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/27-interchain-accounts/controller/ibc_middleware.go#L258-L268) modules for reference. + +If the underlying application is a middleware itself, then it can implement this interface by simply passing the function call to its underlying application. See its implementation in the [`fee middleware`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/29-fee/ibc_middleware.go#L368-L378) for reference. + +### `PacketDataProvider` + +```go +// PacketDataProvider defines an optional interfaces for retrieving custom packet data stored on behalf of another application. +// An existing problem in the IBC middleware design is the inability for a middleware to define its own packet data type and insert packet sender provided information. +// A short term solution was introduced into several application's packet data to utilize a memo field to carry this information on behalf of another application. +// This interfaces standardizes that behaviour. Upon realization of the ability for middleware's to define their own packet data types, this interface will be deprecated and removed with time. +type PacketDataProvider interface { + // GetCustomPacketData returns the packet data held on behalf of another application. + // The name the information is stored under should be provided as the key. + // If no custom packet data exists for the key, nil should be returned. + GetCustomPacketData(key string) interface{} +} +``` + +The callbacks middleware also **requires** the underlying ibc application's packet data type to implement the [`PacketDataProvider`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/core/exported/packet.go#L43-L52) interface. This interface is used to retrieve the callback data from the packet data (using the memo field in the case of `transfer` and `ica`). For example, see its implementation in the [`transfer`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/transfer/types/packet.go#L85-L105) module. + +Since middlewares do not have packet types, they do not need to implement this interface. + +### `PacketData` + +```go +// PacketData defines an optional interface which an application's packet data structure may implement. +type PacketData interface { + // GetPacketSender returns the sender address of the packet data. + // If the packet sender is unknown or undefined, an empty string should be returned. + GetPacketSender(sourcePortID string) string +} +``` + +[`PacketData`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/core/exported/packet.go#L36-L41) is an optional interface that can be implemented by the underlying ibc application's packet data type. It is used to retrieve the packet sender address from the packet data. The callbacks middleware uses this interface to retrieve the packet sender address and pass it to the callback function during a source callback. If this interface is not implemented, then the callbacks middleware passes and empty string as the sender address. For example, see its implementation in the [`transfer`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/transfer/types/packet.go#L74-L83) and [`ica`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/27-interchain-accounts/types/packet.go#L78-L92) module. + +This interface was added so that secondary applications can retrieve the packet sender address to perform custom authorization logic if needed. + +Since middlewares do not have packet types, they do not need to implement this interface. + +## Interfaces for developing the Secondary Application + +### `ContractKeeper` + +The callbacks middleware requires the secondary application to implement the [`ContractKeeper`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/callbacks/types/expected_keepers.go#L11-L83) interface. The contract keeper will be invoked at each step of the packet lifecycle. When a packet is sent, if callback information is provided, the contract keeper will be invoked via the `IBCSendPacketCallback`. This allows the contract keeper to prevent packet sends when callback information is provided, for example if the sender is unauthorized to perform callbacks on the given information. If the packet send is successful, the contract keeper on the destination (if present) will be invoked when a packet has been received and the acknowledgement is written, this will occur via `IBCReceivePacketCallback`. At the end of the packet lifecycle, when processing acknowledgements or timeouts, the source contract keeper will be invoked either via `IBCOnAcknowledgementPacket` or `IBCOnTimeoutPacket`. Once a packet has been sent, each step of the packet lifecycle can be processed given that a relayer sets the gas limit to be more than or equal to the required `CommitGasLimit`. State changes performed in the callback will only be committed upon successful execution. + +```go +// ContractKeeper defines the entry points exposed to the VM module which invokes a smart contract +type ContractKeeper interface { + // IBCSendPacketCallback is called in the source chain when a PacketSend is executed. The + // packetSenderAddress is determined by the underlying module, and may be empty if the sender is + // unknown or undefined. The contract is expected to handle the callback within the user defined + // gas limit, and handle any errors, or panics gracefully. + // This entry point is called with a cached context. If an error is returned, then the changes in + // this context will not be persisted, and the error will be propagated to the underlying IBC + // application, resulting in a packet send failure. + // + // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform + // validation on the origin of a given packet. It is recommended to perform the same validation + // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This + // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. + // + // The version provided is the base application version for the given packet send. This allows + // contracts to determine how to unmarshal the packetData. + IBCSendPacketCallback( + cachedCtx sdk.Context, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + packetData []byte, + contractAddress, + packetSenderAddress string, + version string, + ) error + // IBCOnAcknowledgementPacketCallback is called in the source chain when a packet acknowledgement + // is received. The packetSenderAddress is determined by the underlying module, and may be empty if + // the sender is unknown or undefined. The contract is expected to handle the callback within the + // user defined gas limit, and handle any errors, or panics gracefully. + // This entry point is called with a cached context. If an error is returned, then the changes in + // this context will not be persisted, but the packet lifecycle will not be blocked. + // + // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform + // validation on the origin of a given packet. It is recommended to perform the same validation + // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This + // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. + // + // The version provided is the base application version for the given packet send. This allows + // contracts to determine how to unmarshal the packetData. + IBCOnAcknowledgementPacketCallback( + cachedCtx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, + contractAddress, + packetSenderAddress string, + version string, + ) error + // IBCOnTimeoutPacketCallback is called in the source chain when a packet is not received before + // the timeout height. The packetSenderAddress is determined by the underlying module, and may be + // empty if the sender is unknown or undefined. The contract is expected to handle the callback + // within the user defined gas limit, and handle any error, out of gas, or panics gracefully. + // This entry point is called with a cached context. If an error is returned, then the changes in + // this context will not be persisted, but the packet lifecycle will not be blocked. + // + // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform + // validation on the origin of a given packet. It is recommended to perform the same validation + // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This + // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. + // + // The version provided is the base application version for the given packet send. This allows + // contracts to determine how to unmarshal the packetData. + IBCOnTimeoutPacketCallback( + cachedCtx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, + contractAddress, + packetSenderAddress string, + version string, + ) error + // IBCReceivePacketCallback is called in the destination chain when a packet acknowledgement is written. + // The contract is expected to handle the callback within the user defined gas limit, and handle any errors, + // out of gas, or panics gracefully. + // This entry point is called with a cached context. If an error is returned, then the changes in + // this context will not be persisted, but the packet lifecycle will not be blocked. + // + // The version provided is the base application version for the given packet send. This allows + // contracts to determine how to unmarshal the packetData. + IBCReceivePacketCallback( + cachedCtx sdk.Context, + packet ibcexported.PacketI, + ack ibcexported.Acknowledgement, + contractAddress string, + version string, + ) error +} +``` + +These are the callback entry points exposed to the secondary application. The secondary application is expected to execute its custom logic within these entry points. The callbacks middleware will handle the execution of these callbacks and revert the state if needed. + +:::tip +Note that the source callback entry points are provided with the `packetSenderAddress` and MAY choose to use this to perform validation on the origin of a given packet. It is recommended to perform the same validation on all source chain callbacks (SendPacket, AcknowledgePacket, TimeoutPacket). This defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. +::: diff --git a/docs/docs/04-middleware/01-callbacks/04-events.md b/docs/docs/04-middleware/01-callbacks/04-events.md new file mode 100644 index 0000000..448a56f --- /dev/null +++ b/docs/docs/04-middleware/01-callbacks/04-events.md @@ -0,0 +1,39 @@ +--- +title: Events +sidebar_label: Events +sidebar_position: 4 +slug: /middleware/callbacks/events +--- + +# Events + +An overview of all events related to the callbacks middleware. There are two types of events, `"ibc_src_callback"` and `"ibc_dest_callback"`. + +## Shared Attributes + +Both of these event types share the following attributes: + +| **Attribute Key** | **Attribute Values** | **Optional** | +|:-------------------------:|:---------------------------------------------------------------------------------------:|:------------------:| +| module | "ibccallbacks" | | +| callback_type | **One of**: "send_packet", "acknowledgement_packet", "timeout_packet", "receive_packet" | | +| callback_address | string | | +| callback_exec_gas_limit | string (parsed from uint64) | | +| callback_commit_gas_limit | string (parsed from uint64) | | +| packet_sequence | string (parsed from uint64) | | +| callback_result | **One of**: "success", "failure" | | +| callback_error | string (parsed from callback err) | Yes, if err != nil | + +## `ibc_src_callback` Attributes + +| **Attribute Key** | **Attribute Values** | +|:------------------:|:------------------------:| +| packet_src_port | string (sourcePortID) | +| packet_src_channel | string (sourceChannelID) | + +## `ibc_dest_callback` Attributes + +| **Attribute Key** | **Attribute Values** | +|:-------------------:|:------------------------:| +| packet_dest_port | string (destPortID) | +| packet_dest_channel | string (destChannelID) | diff --git a/docs/docs/04-middleware/01-callbacks/05-end-users.md b/docs/docs/04-middleware/01-callbacks/05-end-users.md new file mode 100644 index 0000000..2142168 --- /dev/null +++ b/docs/docs/04-middleware/01-callbacks/05-end-users.md @@ -0,0 +1,96 @@ +--- +title: End Users +sidebar_label: End Users +sidebar_position: 5 +slug: /middleware/callbacks/end-users +--- + +# Usage + +This section explains how to use the callbacks middleware from the perspective of an IBC Actor. Callbacks middleware provides two types of callbacks: + +- Source callbacks: + - `SendPacket` callback + - `OnAcknowledgementPacket` callback + - `OnTimeoutPacket` callback +- Destination callbacks: + - `ReceivePacket` callback + +For a given channel, the source callbacks are supported if the source chain has the callbacks middleware wired up in the channel's IBC stack. Similarly, the destination callbacks are supported if the destination chain has the callbacks middleware wired up in the channel's IBC stack. + +:::tip +Callbacks are always executed after the packet has been processed by the underlying IBC module. +::: + +:::warning +If the underlying application module is doing an asynchronous acknowledgement on packet receive (for example, if the [packet forward middleware](https://github.com/cosmos/ibc-apps/tree/main/middleware/packet-forward-middleware) is in the stack, and is being used by this packet), then the callbacks middleware will execute the `ReceivePacket` callback after the acknowledgement has been received. +::: + +## Source Callbacks + +Source callbacks are natively supported in the following ibc modules (if they are wrapped by the callbacks middleware): + +- `transfer` +- `icacontroller` + +To have your source callbacks be processed by the callbacks middleware, you must set the memo in the application's packet data to the following format: + +```jsonc +{ + "src_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} +``` + +## Destination Callbacks + +Destination callbacks are natively only supported in the transfer module. Note that wrapping icahost is not supported. This is because icahost should be able to execute an arbitrary transaction anyway, and can call contracts or modules directly. + +To have your destination callbacks processed by the callbacks middleware, you must set the memo in the application's packet data to the following format: + +```jsonc +{ + "dest_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} +``` + +Note that a packet can have both a source and destination callback. + +```jsonc +{ + "src_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + }, + "dest_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} +``` + +# User Defined Gas Limit + +User defined gas limit was added for the following reasons: + +- To prevent callbacks from blocking packet lifecycle. +- To prevent relayers from being able to DOS the callback execution by sending a packet with a low amount of gas. + +:::tip +There is a chain wide parameter that sets the maximum gas limit that a user can set for a callback. This is to prevent a user from setting a gas limit that is too high for relayers. If the `"gas_limit"` is not set in the packet memo, then the maximum gas limit is used. +::: + +These goals are achieved by creating a minimum gas amount required for callback execution. If the relayer provides at least the minimum gas limit for the callback execution, then the packet lifecycle will not be blocked if the callback runs out of gas during execution, and the callback cannot be retried. If the relayer does not provided the minimum amount of gas and the callback executions runs out of gas, the entire tx is reverted and it may be executed again. + +:::tip +`SendPacket` callback is always reverted if the callback execution fails or returns an error for any reason. This is so that the packet is not sent if the callback execution fails. +::: diff --git a/docs/docs/04-middleware/01-callbacks/06-gas.md b/docs/docs/04-middleware/01-callbacks/06-gas.md new file mode 100644 index 0000000..869d220 --- /dev/null +++ b/docs/docs/04-middleware/01-callbacks/06-gas.md @@ -0,0 +1,74 @@ +--- +title: Gas Management +sidebar_label: Gas Management +sidebar_position: 6 +slug: /middleware/callbacks/gas +--- + +# Gas Management + +## Overview + +Executing arbitrary code on a chain can be arbitrarily expensive. In general, a callback may consume infinite gas (think of a callback that loops forever). This is problematic for a few reasons: + +- It can block the packet lifecycle. +- It can be used to consume all of the relayer's funds and gas. +- A relayer can DOS the callback execution by sending a packet with a low amount of gas. + +To prevent these, the callbacks middleware introduces two gas limits: a chain wide gas limit (`maxCallbackGas`) and a user defined gas limit. + +### Chain Wide Gas Limit + +Since the callbacks middleware does not have a keeper, it does not use a governance parameter to set the chain wide gas limit. Instead, the chain wide gas limit is passed in as a parameter to the callbacks middleware during initialization. + +```go +// app.go + +maxCallbackGas := uint64(10_000_000) + +var transferStack porttypes.IBCModule +transferStack = transfer.NewIBCModule(app.TransferKeeper) +transferStack = ibccallbacks.NewIBCMiddleware(transferStack, app.MockContractKeeper, maxCallbackGas) + +// Add transfer stack to IBC Router +ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) +``` + +### User Defined Gas Limit + +The user defined gas limit is set by the IBC Actor during packet creation. The user defined gas limit is set in the packet memo. If the user defined gas limit is not set or if the user defined gas limit is greater than the chain wide gas limit, then the chain wide gas limit is used as the user defined gas limit. + +```jsonc +{ + "src_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + }, + "dest_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} +``` + +## Gas Limit Enforcement + +During a callback execution, there are three types of gas limits that are enforced: + +- User defined gas limit +- Chain wide gas limit +- Context gas limit (amount of gas that the relayer has left for this execution) + +Chain wide gas limit is used as a maximum to the user defined gas limit as explained in the [previous section](#user-defined-gas-limit). It may also be used as a default value if no user gas limit is provided. Therefore, we can ignore the chain wide gas limit for the rest of this section and work with the minimum of the chain wide gas limit and user defined gas limit. This minimum is called the commit gas limit. + +The gas limit enforcement is done by executing the callback inside a cached context with a new gas meter. The gas meter is initialized with the minimum of the commit gas limit and the context gas limit. This minimum is called the execution gas limit. We say that retries are allowed if `context gas limit < commit gas limit`. Otherwise, we say that retries are not allowed. + +If the callback execution fails due to an out of gas error, then the middleware checks if retries are allowed. If retries are not allowed, then it recovers from the out of gas error, consumes execution gas limit from the original context, and continues with the packet life cycle. If retries are allowed, then it panics with an out of gas error to revert the entire tx. The packet can then be submitted again with a higher gas limit. The out of gas panic descriptor is shown below. + +```go +fmt.Sprintf("ibc %s callback out of gas; commitGasLimit: %d", callbackType, callbackData.CommitGasLimit)} +``` + +If the callback execution does not fail due to an out of gas error then the callbacks middleware does not block the packet life cycle regardless of whether retries are allowed or not. diff --git a/docs/docs/04-middleware/01-callbacks/07-callbacks-IBCv2.md b/docs/docs/04-middleware/01-callbacks/07-callbacks-IBCv2.md new file mode 100644 index 0000000..74d88b2 --- /dev/null +++ b/docs/docs/04-middleware/01-callbacks/07-callbacks-IBCv2.md @@ -0,0 +1,29 @@ +--- +title: Callbacks with IBC v2 +sidebar_label: Callbacks with IBC v2 +sidebar_position: 7 +slug: /middleware/callbacks/callbacks-with-v2 +--- + +# Use with IBC v2 + +This page highlights some of the differences between IBC v2 and IBC classic relevant for the callbacks middleware and how to use the module with IBC v2. More details on middleware for IBC v2 can be found in the [middleware section](../../01-ibc/04-middleware/02-developIBCv2.md). + +## Interfaces + +Some of the interface differences are: + +- The callbacks middleware for IBC v2 requires the [`Underlying Application`](../01-callbacks/01-overview.md) to implement the new [`CallbacksCompatibleModuleV2`](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/types/callbacks.go#L53-L58) interface. +- `channeltypesv2.Payload` is now used instead of `channeltypes.Packet` +- With IBC classic, the `OnRecvPacket` callback returns the `ack`, whereas v2 returns the `recvResult` which is the [status of the packet](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel/v2/types/packet.pb.go#L26-L38): unspecified, success, failue or asynchronous +- `api.WriteAcknowledgementWrapper` is used instead of `ICS4Wrapper.WriteAcknowledgement`. It is only needed if the lower level application is going to write an asynchronous acknowledgement. + +## Contract Developers + +The wasmd contract keeper enables cosmwasm developers to use the callbacks middleware. The [cosmwasm documentation](https://cosmwasm.cosmos.network/ibc/extensions/callbacks) provides information for contract developers. The IBC v2 callbacks implementation uses a `Payload` but reconstructs an IBC classic `Packet` to preserve the cosmwasm contract keeper interface. Additionally contracts must now handle the IBC v2 `ErrorAcknowledgement` sentinel value in the case of a failure. + +The callbacks middleware can be used for transfer + action workflows, for example a transfer and swap on recieve. These workflows require knowledge of the ibc denom that has been recieved. To assist with parsing the ics20 packet, [helper functions](https://github.com/cosmos/solidity-ibc-eureka/blob/a8870b023e58622fb7b3f733572c684851f8e5ee/packages/cosmwasm/ibc-callbacks-helpers/src/ics20.rs#L7-L41) can be found in the solidity-ibc-eureka repository. + +## Integration + +An example integration of the callbacks middleware in a transfer stack that is using IBC v2 can be found in the [ibc-go integration section](../../01-ibc/02-integration.md) diff --git a/docs/docs/04-middleware/01-callbacks/_category_.json b/docs/docs/04-middleware/01-callbacks/_category_.json new file mode 100644 index 0000000..4c2654b --- /dev/null +++ b/docs/docs/04-middleware/01-callbacks/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Callbacks Middleware", + "position": 2, + "link": null +} diff --git a/docs/docs/04-middleware/01-callbacks/images/callbackflow.svg b/docs/docs/04-middleware/01-callbacks/images/callbackflow.svg new file mode 100644 index 0000000..df58e93 --- /dev/null +++ b/docs/docs/04-middleware/01-callbacks/images/callbackflow.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/docs/04-middleware/01-callbacks/images/ics4-callbackflow.svg b/docs/docs/04-middleware/01-callbacks/images/ics4-callbackflow.svg new file mode 100644 index 0000000..4b42440 --- /dev/null +++ b/docs/docs/04-middleware/01-callbacks/images/ics4-callbackflow.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/docs/04-middleware/_category_.json b/docs/docs/04-middleware/_category_.json new file mode 100644 index 0000000..cc8fa26 --- /dev/null +++ b/docs/docs/04-middleware/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "IBC Middleware Modules", + "position": 4, + "link": null +} \ No newline at end of file diff --git a/docs/docs/05-migrations/01-support-denoms-with-slashes.md b/docs/docs/05-migrations/01-support-denoms-with-slashes.md new file mode 100644 index 0000000..ac1481d --- /dev/null +++ b/docs/docs/05-migrations/01-support-denoms-with-slashes.md @@ -0,0 +1,87 @@ +--- +title: Support transfer of coins whose base denom contains slashes +sidebar_label: Support transfer of coins whose base denom contains slashes +sidebar_position: 1 +slug: /migrations/support-denoms-with-slashes +--- +# Migrating from not supporting base denoms with slashes to supporting base denoms with slashes + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +This document is necessary when chains are upgrading from a version that does not support base denoms with slashes (e.g. v3.0.0) to a version that does (e.g. v3.2.0). All versions of ibc-go smaller than v1.5.0 for the v1.x release line, v2.3.0 for the v2.x release line, and v3.1.0 for the v3.x release line do **NOT** support IBC token transfers of coins whose base denoms contain slashes. Therefore the in-place of genesis migration described in this document are required when upgrading. + +If a chain receives coins of a base denom with slashes before it upgrades to supporting it, the receive may pass however the trace information will be incorrect. + +E.g. If a base denom of `testcoin/testcoin/testcoin` is sent to a chain that does not support slashes in the base denom, the receive will be successful. However, the trace information stored on the receiving chain will be: `Trace: "transfer/{channel-id}/testcoin/testcoin", BaseDenom: "testcoin"`. + +This incorrect trace information must be corrected when the chain does upgrade to fully supporting denominations with slashes. + +To do so, chain binaries should include a migration script that will run when the chain upgrades from not supporting base denominations with slashes to supporting base denominations with slashes. + +## Chains + +### ICS20 - Transfer + +The transfer module will now support slashes in base denoms, so we must iterate over current traces to check if any of them are incorrectly formed and correct the trace information. + +### Upgrade Proposal + +```go +app.UpgradeKeeper.SetUpgradeHandler("MigrateTraces", + func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + // transfer module consensus version has been bumped to 2 + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) +``` + +This is only necessary if there are denom traces in the store with incorrect trace information from previously received coins that had a slash in the base denom. However, it is recommended that any chain upgrading to support base denominations with slashes runs this code for safety. + +For a more detailed sample, please check out the code changes in [this pull request](https://github.com/cosmos/ibc-go/pull/1680). + +### Genesis Migration + +If the chain chooses to add support for slashes in base denoms via genesis export, then the trace information must be corrected during genesis migration. + +The migration code required may look like: + +```go +func migrateGenesisSlashedDenomsUpgrade(appState genutiltypes.AppMap, clientCtx client.Context, genDoc *tmtypes.GenesisDoc) (genutiltypes.AppMap, error) { + if appState[ibctransfertypes.ModuleName] != nil { + transferGenState := &ibctransfertypes.GenesisState{} + clientCtx.Codec.MustUnmarshalJSON(appState[ibctransfertypes.ModuleName], transferGenState) + + substituteTraces := make([]ibctransfertypes.DenomTrace, len(transferGenState.DenomTraces)) + for i, dt := range transferGenState.DenomTraces { + // replace all previous traces with the latest trace if validation passes + // note most traces will have same value + newTrace := ibctransfertypes.ParseDenomTrace(dt.GetFullDenomPath()) + + if err := newTrace.Validate(); err != nil { + substituteTraces[i] = dt + } else { + substituteTraces[i] = newTrace + } + } + + transferGenState.DenomTraces = substituteTraces + + // delete old genesis state + delete(appState, ibctransfertypes.ModuleName) + + // set new ibc transfer genesis state + appState[ibctransfertypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(transferGenState) + } + + return appState, nil +} +``` + +For a more detailed sample, please check out the code changes in [this pull request](https://github.com/cosmos/ibc-go/pull/1528). diff --git a/docs/docs/05-migrations/02-sdk-to-v1.md b/docs/docs/05-migrations/02-sdk-to-v1.md new file mode 100644 index 0000000..93e7f58 --- /dev/null +++ b/docs/docs/05-migrations/02-sdk-to-v1.md @@ -0,0 +1,195 @@ +--- +title: SDK v0.43 to IBC-Go v1 +sidebar_label: SDK v0.43 to IBC-Go v1 +sidebar_position: 2 +slug: /migrations/sdk-to-v1 +--- + +# Migrating to ibc-go + +This file contains information on how to migrate from the IBC module contained in the SDK 0.41.x and 0.42.x lines to the IBC module in the ibc-go repository based on the 0.44 SDK version. + +## Import Changes + +The most obvious changes is import name changes. We need to change: + +- applications -> apps +- cosmos-sdk/x/ibc -> ibc-go + +On my GNU/Linux based machine I used the following commands, executed in order: + +```shell +grep -RiIl 'cosmos-sdk\/x\/ibc\/applications' | xargs sed -i 's/cosmos-sdk\/x\/ibc\/applications/ibc-go\/modules\/apps/g' +``` + +```shell +grep -RiIl 'cosmos-sdk\/x\/ibc' | xargs sed -i 's/cosmos-sdk\/x\/ibc/ibc-go\/modules/g' +``` + +ref: [explanation of the above commands](https://www.internalpointers.com/post/linux-find-and-replace-text-multiple-files) + +Executing these commands out of order will cause issues. + +Feel free to use your own method for modifying import names. + +NOTE: Updating to the `v0.44.0` SDK release and then running `go mod tidy` will cause a downgrade to `v0.42.0` in order to support the old IBC import paths. +Update the import paths before running `go mod tidy`. + +## Chain Upgrades + +Chains may choose to upgrade via an upgrade proposal or genesis upgrades. Both in-place store migrations and genesis migrations are supported. + +**WARNING**: Please read at least the quick guide for [IBC client upgrades](../01-ibc/05-upgrades/01-quick-guide.md) before upgrading your chain. It is highly recommended you do not change the chain-ID during an upgrade, otherwise you must follow the IBC client upgrade instructions. + +Both in-place store migrations and genesis migrations will: + +- migrate the solo machine client state from v1 to v2 protobuf definitions +- prune all solo machine consensus states +- prune all expired tendermint consensus states + +Chains must set a new connection parameter during either in place store migrations or genesis migration. The new parameter, max expected block time, is used to enforce packet processing delays on the receiving end of an IBC packet flow. Checkout the [docs](https://github.com/cosmos/ibc-go/blob/release/v1.0.x/docs/ibc/proto-docs.md#params-2) for more information. + +### In-Place Store Migrations + +The new chain binary will need to run migrations in the upgrade handler. The fromVM (previous module version) for the IBC module should be 1. This will allow migrations to be run for IBC updating the version from 1 to 2. + +Ex: + +```go +app.UpgradeKeeper.SetUpgradeHandler("my-upgrade-proposal", + func(ctx sdk.Context, _ upgradetypes.Plan, _ module.VersionMap) (module.VersionMap, error) { + // set max expected block time parameter. Replace the default with your expected value + // https://github.com/cosmos/ibc-go/blob/release/v1.0.x/docs/ibc/proto-docs.md#params-2 + app.IBCKeeper.ConnectionKeeper.SetParams(ctx, ibcconnectiontypes.DefaultParams()) + + fromVM := map[string]uint64{ + ... // other modules + "ibc": 1, + ... + } + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) + +``` + +### Genesis Migrations + +To perform genesis migrations, the following code must be added to your existing migration code. + +```go +// add imports as necessary +import ( + ibcv100 "github.com/cosmos/ibc-go/modules/core/legacy/v100" + ibchost "github.com/cosmos/ibc-go/modules/core/24-host" +) + +... + +// add in migrate cmd function +// expectedTimePerBlock is a new connection parameter +// https://github.com/cosmos/ibc-go/blob/release/v1.0.x/docs/ibc/proto-docs.md#params-2 +newGenState, err = ibcv100.MigrateGenesis(newGenState, clientCtx, *genDoc, expectedTimePerBlock) +if err != nil { + return err +} +``` + +**NOTE:** The genesis chain-id, time and height MUST be updated before migrating IBC, otherwise the tendermint consensus state will not be pruned. + +## IBC Keeper Changes + +The IBC Keeper now takes in the Upgrade Keeper. Please add the chains' Upgrade Keeper after the Staking Keeper: + +```diff +// Create IBC Keeper +app.IBCKeeper = ibckeeper.NewKeeper( +- appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, scopedIBCKeeper, ++ appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, +) +``` + +## Proposals + +### UpdateClientProposal + +The `UpdateClient` has been modified to take in two client-identifiers and one initial height. + +### UpgradeProposal + +A new IBC proposal type has been added, `UpgradeProposal`. This handles an IBC (breaking) Upgrade. +The previous `UpgradedClientState` field in an Upgrade `Plan` has been deprecated in favor of this new proposal type. + +### Proposal Handler Registration + +The `ClientUpdateProposalHandler` has been renamed to `ClientProposalHandler`. +It handles both `UpdateClientProposal`s and `UpgradeProposal`s. + +Add this import: + +```diff ++ ibcclienttypes "github.com/cosmos/ibc-go/modules/core/02-client/types" +``` + +Please ensure the governance module adds the correct route: + +```diff +- AddRoute(ibchost.RouterKey, ibcclient.NewClientUpdateProposalHandler(app.IBCKeeper.ClientKeeper)) ++ AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)) +``` + +NOTE: Simapp registration was incorrect in the 0.41.x releases. The `UpdateClient` proposal handler should be registered with the router key belonging to `ibc-go/core/02-client/types` +as shown in the diffs above. + +### Proposal CLI Registration + +Please ensure both proposal type CLI commands are registered on the governance module by adding the following arguments to `gov.NewAppModuleBasic()`: + +Add the following import: + +```diff ++ ibcclientclient "github.com/cosmos/ibc-go/modules/core/02-client/client" +``` + +Register the cli commands: + +```diff +gov.NewAppModuleBasic( + paramsclient.ProposalHandler, distrclient.ProposalHandler, upgradeclient.ProposalHandler, upgradeclient.CancelProposalHandler, ++ ibcclientclient.UpdateClientProposalHandler, ibcclientclient.UpgradeProposalHandler, +), +``` + +REST routes are not supported for these proposals. + +## Proto file changes + +The gRPC querier service endpoints have changed slightly. The previous files used `v1beta1` gRPC route, this has been updated to `v1`. + +The solo machine has replaced the FrozenSequence uint64 field with a IsFrozen boolean field. The package has been bumped from `v1` to `v2` + +## IBC callback changes + +### OnRecvPacket + +Application developers need to update their `OnRecvPacket` callback logic. + +The `OnRecvPacket` callback has been modified to only return the acknowledgement. The acknowledgement returned must implement the `Acknowledgement` interface. The acknowledgement should indicate if it represents a successful processing of a packet by returning true on `Success()` and false in all other cases. A return value of false on `Success()` will result in all state changes which occurred in the callback being discarded. More information can be found in the [documentation](../01-ibc/03-apps/01-apps.md#receiving-packets). + +The `OnRecvPacket`, `OnAcknowledgementPacket`, and `OnTimeoutPacket` callbacks are now passed the `sdk.AccAddress` of the relayer who relayed the IBC packet. Applications may use or ignore this information. + +## IBC Event changes + +The `packet_data` attribute has been deprecated in favor of `packet_data_hex`, in order to provide standardized encoding/decoding of packet data in events. While the `packet_data` event still exists, all relayers and IBC Event consumers are strongly encouraged to switch over to using `packet_data_hex` as soon as possible. + +The `packet_ack` attribute has also been deprecated in favor of `packet_ack_hex` for the same reason stated above. All relayers and IBC Event consumers are strongly encouraged to switch over to using `packet_ack_hex` as soon as possible. + +The `consensus_height` attribute has been removed in the Misbehaviour event emitted. IBC clients no longer have a frozen height and misbehaviour does not necessarily have an associated height. + +## Relevant SDK changes + +- (codec) [\#9226](https://github.com/cosmos/cosmos-sdk/pull/9226) Rename codec interfaces and methods, to follow a general Go interfaces: + - `codec.Marshaler` → `codec.Codec` (this defines objects which serialize other objects) + - `codec.BinaryMarshaler` → `codec.BinaryCodec` + - `codec.JSONMarshaler` → `codec.JSONCodec` + - Removed `BinaryBare` suffix from `BinaryCodec` methods (`MarshalBinaryBare`, `UnmarshalBinaryBare`, ...) + - Removed `Binary` infix from `BinaryCodec` methods (`MarshalBinaryLengthPrefixed`, `UnmarshalBinaryLengthPrefixed`, ...) diff --git a/docs/docs/05-migrations/03-v1-to-v2.md b/docs/docs/05-migrations/03-v1-to-v2.md new file mode 100644 index 0000000..13afccd --- /dev/null +++ b/docs/docs/05-migrations/03-v1-to-v2.md @@ -0,0 +1,59 @@ +--- +title: IBC-Go v1 to v2 +sidebar_label: IBC-Go v1 to v2 +sidebar_position: 3 +slug: /migrations/v1-to-v2 +--- +# Migrating from ibc-go v1 to v2 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go -> github.com/cosmos/ibc-go/v2 +``` + +## Chains + +- No relevant changes were made in this release. + +## IBC Apps + +A new function has been added to the app module interface: + +```go +// NegotiateAppVersion performs application version negotiation given the provided channel ordering, connectionID, portID, counterparty and proposed version. +// An error is returned if version negotiation cannot be performed. For example, an application module implementing this interface +// may decide to return an error in the event of the proposed version being incompatible with it's own +NegotiateAppVersion( + ctx sdk.Context, + order channeltypes.Order, + connectionID string, + portID string, + counterparty channeltypes.Counterparty, + proposedVersion string, +) (version string, err error) +``` + +This function should perform application version negotiation and return the negotiated version. If the version cannot be negotiated, an error should be returned. This function is only used on the client side. + +### `sdk.Result` removed + +`sdk.Result` has been removed as a return value in the application callbacks. Previously it was being discarded by core IBC and was thus unused. + +## Relayers + +A new gRPC has been added to 05-port, `AppVersion`. It returns the negotiated app version. This function should be used for the `ChanOpenTry` channel handshake step to decide upon the application version which should be set in the channel. + +## IBC Light Clients + +- No relevant changes were made in this release. diff --git a/docs/docs/05-migrations/04-v2-to-v3.md b/docs/docs/05-migrations/04-v2-to-v3.md new file mode 100644 index 0000000..2db12c5 --- /dev/null +++ b/docs/docs/05-migrations/04-v2-to-v3.md @@ -0,0 +1,186 @@ +--- +title: IBC-Go v2 to v3 +sidebar_label: IBC-Go v2 to v3 +sidebar_position: 4 +slug: /migrations/v2-to-v3 +--- + +# Migrating from ibc-go v2 to v3 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go/v2 -> github.com/cosmos/ibc-go/v3 +``` + +No genesis or in-place migrations are required when upgrading from v1 or v2 of ibc-go. + +## Chains + +### ICS20 + +The `transferkeeper.NewKeeper(...)` now takes in an ICS4Wrapper. +The ICS4Wrapper should be the IBC Channel Keeper unless ICS 20 is being connected to a middleware application. + +### ICS27 + +ICS27 Interchain Accounts has been added as a supported IBC application of ibc-go. +Please see the [ICS27 documentation](../02-apps/02-interchain-accounts/01-overview.md) for more information. + +### Upgrade Proposal + +If the chain will adopt ICS27, it must set the appropriate params during the execution of the upgrade handler in `app.go`: + +```go +app.UpgradeKeeper.SetUpgradeHandler("v3", + func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + // set the ICS27 consensus version so InitGenesis is not run + fromVM[icatypes.ModuleName] = icamodule.ConsensusVersion() + + // create ICS27 Controller submodule params + controllerParams := icacontrollertypes.Params{ + ControllerEnabled: true, + } + + // create ICS27 Host submodule params + hostParams := icahosttypes.Params{ + HostEnabled: true, + AllowMessages: []string{"/cosmos.bank.v1beta1.MsgSend", ...}, + } + + // initialize ICS27 module + icamodule.InitModule(ctx, controllerParams, hostParams) + + ... + + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) +``` + +The host and controller submodule params only need to be set if the chain integrates those submodules. +For example, if a chain chooses not to integrate a controller submodule, it may pass empty params into `InitModule`. + +#### Add `StoreUpgrades` for ICS27 module + +For ICS27 it is also necessary to [manually add store upgrades](https://docs.cosmos.network/main/learn/advanced/upgrade#add-storeupgrades-for-new-modules) for the new ICS27 module and then configure the store loader to apply those upgrades in `app.go`: + +```go +if upgradeInfo.Name == "v3" && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { + storeUpgrades := store.StoreUpgrades{ + Added: []string{icacontrollertypes.StoreKey, icahosttypes.StoreKey}, + } + + app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) +} +``` + +This ensures that the new module's stores are added to the multistore before the migrations begin. +The host and controller submodule keys only need to be added if the chain integrates those submodules. +For example, if a chain chooses not to integrate a controller submodule, it does not need to add the controller key to the `Added` field. + +### Genesis migrations + +If the chain will adopt ICS27 and chooses to upgrade via a genesis export, then the ICS27 parameters must be set during genesis migration. + +The migration code required may look like: + +```go + controllerGenesisState := icatypes.DefaultControllerGenesis() + // overwrite parameters as desired + controllerGenesisState.Params = icacontrollertypes.Params{ + ControllerEnabled: true, + } + + hostGenesisState := icatypes.DefaultHostGenesis() + // overwrite parameters as desired + hostGenesisState.Params = icahosttypes.Params{ + HostEnabled: true, + AllowMessages: []string{"/cosmos.bank.v1beta1.MsgSend", ...}, + } + + icaGenesisState := icatypes.NewGenesisState(controllerGenesisState, hostGenesisState) + + // set new ics27 genesis state + appState[icatypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(icaGenesisState) +``` + +### Ante decorator + +The field of type `channelkeeper.Keeper` in the `AnteDecorator` structure has been replaced with a field of type `*keeper.Keeper`: + +```diff +type AnteDecorator struct { +- k channelkeeper.Keeper ++ k *keeper.Keeper +} + +- func NewAnteDecorator(k channelkeeper.Keeper) AnteDecorator { ++ func NewAnteDecorator(k *keeper.Keeper) AnteDecorator { + return AnteDecorator{k: k} +} +``` + +## IBC Apps + +### `OnChanOpenTry` must return negotiated application version + +The `OnChanOpenTry` application callback has been modified. +The return signature now includes the application version. +IBC applications must perform application version negotiation in `OnChanOpenTry` using the counterparty version. +The negotiated application version then must be returned in `OnChanOpenTry` to core IBC. +Core IBC will set this version in the TRYOPEN channel. + +### `OnChanOpenAck` will take additional `counterpartyChannelID` argument + +The `OnChanOpenAck` application callback has been modified. +The arguments now include the counterparty channel id. + +### `NegotiateAppVersion` removed from `IBCModule` interface + +Previously this logic was handled by the `NegotiateAppVersion` function. +Relayers would query this function before calling `ChanOpenTry`. +Applications would then need to verify that the passed in version was correct. +Now applications will perform this version negotiation during the channel handshake, thus removing the need for `NegotiateAppVersion`. + +### Channel state will not be set before application callback + +The channel handshake logic has been reorganized within core IBC. +Channel state will not be set in state after the application callback is performed. +Applications must rely only on the passed in channel parameters instead of querying the channel keeper for channel state. + +### IBC application callbacks moved from `AppModule` to `IBCModule` + +Previously, IBC module callbacks were apart of the `AppModule` type. +The recommended approach is to create an `IBCModule` type and move the IBC module callbacks from `AppModule` to `IBCModule` in a separate file `ibc_module.go`. + +The mock module go API has been broken in this release by applying the above format. +The IBC module callbacks have been moved from the mock modules `AppModule` into a new type `IBCModule`. + +As apart of this release, the mock module now supports middleware testing. Please see the [README](https://github.com/cosmos/ibc-go/blob/v3.0.0/testing/README.md#middleware-testing) for more information. + +Please review the [mock](https://github.com/cosmos/ibc-go/blob/v3.0.0/testing/mock/ibc_module.go) and [transfer](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/transfer/ibc_module.go) modules as examples. Additionally, [simapp](https://github.com/cosmos/ibc-go/blob/v3.0.0/testing/simapp/app.go) provides an example of how `IBCModule` types should now be added to the IBC router in favour of `AppModule`. + +### IBC testing package + +`TestChain`s are now created with chainID's beginning from an index of 1. Any calls to `GetChainID(0)` will now fail. Please increment all calls to `GetChainID` by 1. + +## Relayers + +`AppVersion` gRPC has been removed. +The `version` string in `MsgChanOpenTry` has been deprecated and will be ignored by core IBC. +Relayers no longer need to determine the version to use on the `ChanOpenTry` step. +IBC applications will determine the correct version using the counterparty version. + +## IBC Light Clients + +The `GetProofSpecs` function has been removed from the `ClientState` interface. This function was previously unused by core IBC. Light clients which don't use this function may remove it. diff --git a/docs/docs/05-migrations/05-v3-to-v4.md b/docs/docs/05-migrations/05-v3-to-v4.md new file mode 100644 index 0000000..cf2c2c3 --- /dev/null +++ b/docs/docs/05-migrations/05-v3-to-v4.md @@ -0,0 +1,159 @@ +--- +title: IBC-Go v3 to v4 +sidebar_label: IBC-Go v3 to v4 +sidebar_position: 5 +slug: /migrations/v3-to-v4 +--- +# Migrating from ibc-go v3 to v4 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go/v3 -> github.com/cosmos/ibc-go/v4 +``` + +No genesis or in-place migrations required when upgrading from v1 or v2 of ibc-go. + +## Chains + +### ICS27 - Interchain Accounts + +The controller submodule implements now the 05-port `Middleware` interface instead of the 05-port `IBCModule` interface. Chains that integrate the controller submodule, need to create it with the `NewIBCMiddleware` constructor function. For example: + +```diff +- icacontroller.NewIBCModule(app.ICAControllerKeeper, icaAuthIBCModule) ++ icacontroller.NewIBCMiddleware(icaAuthIBCModule, app.ICAControllerKeeper) +``` + +where `icaAuthIBCModule` is the Interchain Accounts authentication IBC Module. + +### ICS29 - Fee Middleware + +The Fee Middleware module, as the name suggests, plays the role of an IBC middleware and as such must be configured by chain developers to route and handle IBC messages correctly. + +Please read the Fee Middleware integration documentation for an in depth guide on how to configure the module correctly in order to incentivize IBC packets. + +Take a look at the following diff for an [example setup](https://github.com/cosmos/ibc-go/pull/1432/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08aL366) of how to incentivize ics27 channels. + +### Migration to fix support for base denoms with slashes + +As part of [v1.5.0](https://github.com/cosmos/ibc-go/releases/tag/v1.5.0), [v2.3.0](https://github.com/cosmos/ibc-go/releases/tag/v2.3.0) and [v3.1.0](https://github.com/cosmos/ibc-go/releases/tag/v3.1.0) some [migration handler code sample was documented](./01-support-denoms-with-slashes.md#upgrade-proposal) that needs to run in order to correct the trace information of coins transferred using ICS20 whose base denom contains slashes. + +Based on feedback from the community we add now an improved solution to run the same migration that does not require copying a large piece of code over from the migration document, but instead requires only adding a one-line upgrade handler. + +If the chain will migrate to supporting base denoms with slashes, it must set the appropriate params during the execution of the upgrade handler in `app.go`: + +```go +app.UpgradeKeeper.SetUpgradeHandler("MigrateTraces", + func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + // transfer module consensus version has been bumped to 2 + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) +``` + +If a chain receives coins of a base denom with slashes before it upgrades to supporting it, the receive may pass however the trace information will be incorrect. + +E.g. If a base denom of `testcoin/testcoin/testcoin` is sent to a chain that does not support slashes in the base denom, the receive will be successful. However, the trace information stored on the receiving chain will be: `Trace: "transfer/{channel-id}/testcoin/testcoin", BaseDenom: "testcoin"`. + +This incorrect trace information must be corrected when the chain does upgrade to fully supporting denominations with slashes. + +## IBC Apps + +### ICS03 - Connection + +Crossing hellos have been removed from 03-connection handshake negotiation. +`PreviousConnectionId` in `MsgConnectionOpenTry` has been deprecated and is no longer used by core IBC. + +`NewMsgConnectionOpenTry` no longer takes in the `PreviousConnectionId` as crossing hellos are no longer supported. A non-empty `PreviousConnectionId` will fail basic validation for this message. + +### ICS04 - Channel + +The `WriteAcknowledgement` API now takes the `exported.Acknowledgement` type instead of passing in the acknowledgement byte array directly. +This is an API breaking change and as such IBC application developers will have to update any calls to `WriteAcknowledgement`. + +The `OnChanOpenInit` application callback has been modified. +The return signature now includes the application version as detailed in the latest IBC [spec changes](https://github.com/cosmos/ibc/pull/629). + +The `NewErrorAcknowledgement` method signature has changed. +It now accepts an `error` rather than a `string`. This was done in order to prevent accidental state changes. +All error acknowledgements now contain a deterministic ABCI code and error message. It is the responsibility of the application developer to emit error details in events. + +Crossing hellos have been removed from 04-channel handshake negotiation. +IBC Applications no longer need to account from already claimed capabilities in the `OnChanOpenTry` callback. The capability provided by core IBC must be able to be claimed with error. +`PreviousChannelId` in `MsgChannelOpenTry` has been deprecated and is no longer used by core IBC. + +`NewMsgChannelOpenTry` no longer takes in the `PreviousChannelId` as crossing hellos are no longer supported. A non-empty `PreviousChannelId` will fail basic validation for this message. + +### ICS27 - Interchain Accounts + +The `RegisterInterchainAccount` API has been modified to include an additional `version` argument. This change has been made in order to support ICS29 fee middleware, for relayer incentivization of ICS27 packets. +Consumers of the `RegisterInterchainAccount` are now expected to build the appropriate JSON encoded version string themselves and pass it accordingly. +This should be constructed within the interchain accounts authentication module which leverages the APIs exposed via the interchain accounts `controllerKeeper`. If an empty string is passed in the `version` argument, then the version will be initialized to a default value in the `OnChanOpenInit` callback of the controller's handler, so that channel handshake can proceed. + +The following code snippet illustrates how to construct an appropriate interchain accounts `Metadata` and encode it as a JSON bytestring: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +if err := k.icaControllerKeeper.RegisterInterchainAccount(ctx, msg.ConnectionId, msg.Owner, string(appVersion)); err != nil { + return err +} +``` + +Similarly, if the application stack is configured to route through ICS29 fee middleware and a fee enabled channel is desired, construct the appropriate ICS29 `Metadata` type: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +feeMetadata := feetypes.Metadata{ + AppVersion: string(appVersion), + FeeVersion: feetypes.Version, +} + +feeEnabledVersion, err := feetypes.ModuleCdc.MarshalJSON(&feeMetadata) +if err != nil { + return err +} + +if err := k.icaControllerKeeper.RegisterInterchainAccount(ctx, msg.ConnectionId, msg.Owner, string(feeEnabledVersion)); err != nil { + return err +} +``` + +## Relayers + +When using the `DenomTrace` gRPC, the full IBC denomination with the `ibc/` prefix may now be passed in. + +Crossing hellos are no longer supported by core IBC for 03-connection and 04-channel. The handshake should be completed in the logical 4 step process (INIT, TRY, ACK, CONFIRM). diff --git a/docs/docs/05-migrations/06-v4-to-v5.md b/docs/docs/05-migrations/06-v4-to-v5.md new file mode 100644 index 0000000..d3fdafb --- /dev/null +++ b/docs/docs/05-migrations/06-v4-to-v5.md @@ -0,0 +1,441 @@ +--- +title: IBC-Go v4 to v5 +sidebar_label: IBC-Go v4 to v5 +sidebar_position: 6 +slug: /migrations/v4-to-v5 +--- + +# Migrating from v4 to v5 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- [Chains](#chains) +- [IBC Apps](#ibc-apps) +- [Relayers](#relayers) +- [IBC Light Clients](#ibc-light-clients) + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go/v4 -> github.com/cosmos/ibc-go/v5 +``` + +## Chains + +### Ante decorator + +The `AnteDecorator` type in `core/ante` has been renamed to `RedundantRelayDecorator` (and the corresponding constructor function to `NewRedundantRelayDecorator`). Therefore in the function that creates the instance of the `sdk.AnteHandler` type (e.g. `NewAnteHandler`) the change would be like this: + +```diff +func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { + // parameter validation + + anteDecorators := []sdk.AnteDecorator{ + // other ante decorators +- ibcante.NewAnteDecorator(opts.IBCkeeper), ++ ibcante.NewRedundantRelayDecorator(options.IBCKeeper), + } + + return sdk.ChainAnteDecorators(anteDecorators...), nil +} +``` + +The `AnteDecorator` was actually renamed twice, but in [this PR](https://github.com/cosmos/ibc-go/pull/1820) you can see the changes made for the final rename. + +## IBC Apps + +### Core + +The `key` parameter of the `NewKeeper` function in `modules/core/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + stakingKeeper clienttypes.StakingKeeper, + upgradeKeeper clienttypes.UpgradeKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +) *Keeper +``` + +The `RegisterRESTRoutes` function in `modules/core` has been removed. + +### ICS03 - Connection + +The `key` parameter of the `NewKeeper` function in `modules/core/03-connection/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + ck types.ClientKeeper +) Keeper +``` + +### ICS04 - Channel + +The function `NewPacketId` in `modules/core/04-channel/types` has been renamed to `NewPacketID`: + +```diff +- func NewPacketId( ++ func NewPacketID( + portID, + channelID string, + seq uint64 +) PacketId +``` + +The `key` parameter of the `NewKeeper` function in `modules/core/04-channel/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + clientKeeper types.ClientKeeper, + connectionKeeper types.ConnectionKeeper, + portKeeper types.PortKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +) Keeper +``` + +### ICS20 - Transfer + +The `key` parameter of the `NewKeeper` function in `modules/apps/transfer/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + ics4Wrapper types.ICS4Wrapper, + channelKeeper types.ChannelKeeper, + portKeeper types.PortKeeper, + authKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +) Keeper +``` + +The `amount` parameter of function `GetTransferCoin` in `modules/apps/transfer/types` is now of type `math.Int` (`"cosmossdk.io/math"`): + +```diff +func GetTransferCoin( + portID, channelID, baseDenom string, +- amount sdk.Int ++ amount math.Int +) sdk.Coin +``` + +The `RegisterRESTRoutes` function in `modules/apps/transfer` has been removed. + +### ICS27 - Interchain Accounts + +The `key` and `msgRouter` parameters of the `NewKeeper` functions in + +- `modules/apps/27-interchain-accounts/controller/keeper` +- and `modules/apps/27-interchain-accounts/host/keeper` + +have changed type. The `key` parameter is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`), and the `msgRouter` parameter is now of type `*icatypes.MessageRouter` (where `icatypes` is an import alias for `"github.com/cosmos/ibc-go/v5/modules/apps/27-interchain-accounts/types"`): + +```diff +// NewKeeper creates a new interchain accounts controller Keeper instance +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + ics4Wrapper icatypes.ICS4Wrapper, + channelKeeper icatypes.ChannelKeeper, + portKeeper icatypes.PortKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +- msgRouter *baseapp.MsgServiceRouter, ++ msgRouter *icatypes.MessageRouter, +) Keeper +``` + +```diff +// NewKeeper creates a new interchain accounts host Keeper instance +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + channelKeeper icatypes.ChannelKeeper, + portKeeper icatypes.PortKeeper, + accountKeeper icatypes.AccountKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +- msgRouter *baseapp.MsgServiceRouter, ++ msgRouter *icatypes.MessageRouter, +) Keeper +``` + +The new `MessageRouter` interface is defined as: + +```go +type MessageRouter interface { + Handler(msg sdk.Msg) baseapp.MsgServiceHandler +} +``` + +The `RegisterRESTRoutes` function in `modules/apps/27-interchain-accounts` has been removed. + +An additional parameter, `ics4Wrapper` has been added to the `host` submodule `NewKeeper` function in `modules/apps/27-interchain-accounts/host/keeper`. +This allows the `host` submodule to correctly unwrap the channel version for channel reopening handshakes in the `OnChanOpenTry` callback. + +```diff +func NewKeeper( + cdc codec.BinaryCodec, + key storetypes.StoreKey, + paramSpace paramtypes.Subspace, ++ ics4Wrapper icatypes.ICS4Wrapper, + channelKeeper icatypes.ChannelKeeper, + portKeeper icatypes.PortKeeper, + accountKeeper icatypes.AccountKeeper, + scopedKeeper icatypes.ScopedKeeper, + msgRouter icatypes.MessageRouter, +) Keeper +``` + +#### Cosmos SDK message handler responses in packet acknowledgement + +The construction of the transaction response of a message execution on the host chain has changed. The `Data` field in the `sdk.TxMsgData` has been deprecated and since Cosmos SDK 0.46 the `MsgResponses` field contains the message handler responses packed into `Any`s. + +For chains on Cosmos SDK 0.45 and below, the message response was constructed like this: + +```go +txMsgData := &sdk.TxMsgData{ + Data: make([]*sdk.MsgData, len(msgs)), +} + +for i, msg := range msgs { + // message validation + + msgResponse, err := k.executeMsg(cacheCtx, msg) + // return if err != nil + + txMsgData.Data[i] = &sdk.MsgData{ + MsgType: sdk.MsgTypeURL(msg), + Data: msgResponse, + } +} + +// emit events + +txResponse, err := proto.Marshal(txMsgData) +// return if err != nil + +return txResponse, nil +``` + +And for chains on Cosmos SDK 0.46 and above, it is now done like this: + +```go +txMsgData := &sdk.TxMsgData{ + MsgResponses: make([]*codectypes.Any, len(msgs)), +} + +for i, msg := range msgs { + // message validation + + protoAny, err := k.executeMsg(cacheCtx, msg) + // return if err != nil + + txMsgData.MsgResponses[i] = protoAny +} + +// emit events + +txResponse, err := proto.Marshal(txMsgData) +// return if err != nil + +return txResponse, nil +``` + +When handling the acknowledgement in the `OnAcknowledgementPacket` callback of a custom ICA controller module, then depending on whether `txMsgData.Data` is empty or not, the logic to handle the message handler response will be different. **Only controller chains on Cosmos SDK 0.46 or above will be able to write the logic needed to handle the response from a host chain on Cosmos SDK 0.46 or above.** + +```go +var ack channeltypes.Acknowledgement +if err := channeltypes.SubModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { + return err +} + +var txMsgData sdk.TxMsgData +if err := proto.Unmarshal(ack.GetResult(), txMsgData); err != nil { + return err +} + +switch len(txMsgData.Data) { +case 0: // for SDK 0.46 and above + for _, msgResponse := range txMsgData.MsgResponses { + // unmarshall msgResponse and execute logic based on the response + } + return nil +default: // for SDK 0.45 and below + for _, msgData := range txMsgData.Data { + // unmarshall msgData and execute logic based on the response + } +} +``` + +See [ADR-03](/architecture/adr-003-ics27-acknowledgement#next-major-version-format) for more information or the [corresponding documentation about authentication modules](../02-apps/02-interchain-accounts/03-auth-modules.md#onacknowledgementpacket). + +### ICS29 - Fee Middleware + +The `key` parameter of the `NewKeeper` function in `modules/apps/29-fee` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + ics4Wrapper types.ICS4Wrapper, + channelKeeper types.ChannelKeeper, + portKeeper types.PortKeeper, + authKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, +) Keeper +``` + +The `RegisterRESTRoutes` function in `modules/apps/29-fee` has been removed. + +### IBC testing package + +The `MockIBCApp` type has been renamed to `IBCApp` (and the corresponding constructor function to `NewIBCApp`). This has resulted therefore in: + +- The `IBCApp` field of the `*IBCModule` in `testing/mock` to change its type as well to `*IBCApp`: + +```diff +type IBCModule struct { + appModule *AppModule +- IBCApp *MockIBCApp // base application of an IBC middleware stack ++ IBCApp *IBCApp // base application of an IBC middleware stack +} +``` + +- The `app` parameter to `*NewIBCModule` in `testing/mock` to change its type as well to `*IBCApp`: + +```diff +func NewIBCModule( + appModule *AppModule, +- app *MockIBCApp ++ app *IBCApp +) IBCModule +``` + +The `MockEmptyAcknowledgement` type has been renamed to `EmptyAcknowledgement` (and the corresponding constructor function to `NewEmptyAcknowledgement`). + +The `TestingApp` interface in `testing` has gone through some modifications: + +- The return type of the function `GetStakingKeeper` is not the concrete type `stakingkeeper.Keeper` anymore (where `stakingkeeper` is an import alias for `"github.com/cosmos/cosmos-sdk/x/staking/keeper"`), but it has been changed to the interface `ibctestingtypes.StakingKeeper` (where `ibctestingtypes` is an import alias for `""github.com/cosmos/ibc-go/v5/testing/types"`). See this [PR](https://github.com/cosmos/ibc-go/pull/2028) for more details. The `StakingKeeper` interface is defined as: + +```go +type StakingKeeper interface { + GetHistoricalInfo(ctx sdk.Context, height int64) (stakingtypes.HistoricalInfo, bool) +} +``` + +- The return type of the function `LastCommitID` has changed to `storetypes.CommitID` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`). + +See the following `git diff` for more details: + +```diff +type TestingApp interface { + abci.Application + + // ibc-go additions + GetBaseApp() *baseapp.BaseApp +- GetStakingKeeper() stakingkeeper.Keeper ++ GetStakingKeeper() ibctestingtypes.StakingKeeper + GetIBCKeeper() *keeper.Keeper + GetScopedIBCKeeper() capabilitykeeper.ScopedKeeper + GetTxConfig() client.TxConfig + + // Implemented by SimApp + AppCodec() codec.Codec + + // Implemented by BaseApp +- LastCommitID() sdk.CommitID ++ LastCommitID() storetypes.CommitID + LastBlockHeight() int64 +} +``` + +The `powerReduction` parameter of the function `SetupWithGenesisValSet` in `testing` is now of type `math.Int` (`"cosmossdk.io/math"`): + +```diff +func SetupWithGenesisValSet( + t *testing.T, + valSet *tmtypes.ValidatorSet, + genAccs []authtypes.GenesisAccount, + chainID string, +- powerReduction sdk.Int, ++ powerReduction math.Int, + balances ...banktypes.Balance +) TestingApp +``` + +The `accAmt` parameter of the functions + +- `AddTestAddrsFromPubKeys` , +- `AddTestAddrs` +- and `AddTestAddrsIncremental` + +in `testing/simapp` are now of type `math.Int` (`"cosmossdk.io/math"`): + +```diff +func AddTestAddrsFromPubKeys( + app *SimApp, + ctx sdk.Context, + pubKeys []cryptotypes.PubKey, +- accAmt sdk.Int, ++ accAmt math.Int +) +func addTestAddrs( + app *SimApp, + ctx sdk.Context, + accNum int, +- accAmt sdk.Int, ++ accAmt math.Int, + strategy GenerateAccountStrategy +) []sdk.AccAddress +func AddTestAddrsIncremental( + app *SimApp, + ctx sdk.Context, + accNum int, +- accAmt sdk.Int, ++ accAmt math.Int +) []sdk.AccAddress +``` + +The `RegisterRESTRoutes` function in `testing/mock` has been removed. + +## Relayers + +- No relevant changes were made in this release. + +## IBC Light Clients + +### ICS02 - Client + +The `key` parameter of the `NewKeeper` function in `modules/core/02-client/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + sk types.StakingKeeper, + uk types.UpgradeKeeper +) Keeper +``` diff --git a/docs/docs/05-migrations/07-v5-to-v6.md b/docs/docs/05-migrations/07-v5-to-v6.md new file mode 100644 index 0000000..4fe56a7 --- /dev/null +++ b/docs/docs/05-migrations/07-v5-to-v6.md @@ -0,0 +1,299 @@ +--- +title: IBC-Go v5 to v6 +sidebar_label: IBC-Go v5 to v6 +sidebar_position: 7 +slug: /migrations/v5-to-v6 +--- + +# Migrating from ibc-go v5 to v6 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +## Chains + +The `ibc-go/v6` release introduces a new set of migrations for `27-interchain-accounts`. Ownership of ICS27 channel capabilities is transferred from ICS27 authentication modules and will now reside with the ICS27 controller submodule moving forward. + +For chains which contain a custom authentication module using the ICS27 controller submodule this requires a migration function to be included in the chain upgrade handler. A subsequent migration handler is run automatically, asserting the ownership of ICS27 channel capabilities has been transferred successfully. + +This migration is not required for chains which *do not* contain a custom authentication module using the ICS27 controller submodule. + +This migration facilitates the addition of the ICS27 controller submodule `MsgServer` which provides a standardised approach to integrating existing forms of authentication such as `x/gov` and `x/group` provided by the Cosmos SDK. + +For more information please refer to [ADR 009](/architecture/adr-009-v6-ics27-msgserver). + +### Upgrade proposal + +Please refer to [PR #2383](https://github.com/cosmos/ibc-go/pull/2383) for integrating the ICS27 channel capability migration logic or follow the steps outlined below: + +1. Add the upgrade migration logic to chain distribution. This may be, for example, maintained under a package `app/upgrades/v6`. + +```go +package v6 + +import ( + "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + + v6 "github.com/cosmos/ibc-go/v6/modules/apps/27-interchain-accounts/controller/migrations/v6" +) + +const ( + UpgradeName = "v6" +) + +func CreateUpgradeHandler( + mm *module.Manager, + configurator module.Configurator, + cdc codec.BinaryCodec, + capabilityStoreKey *storetypes.KVStoreKey, + capabilityKeeper *capabilitykeeper.Keeper, + moduleName string, +) upgradetypes.UpgradeHandler { + return func(ctx sdk.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + if err := v6.MigrateICS27ChannelCapability(ctx, cdc, capabilityStoreKey, capabilityKeeper, moduleName); err != nil { + return nil, err + } + + return mm.RunMigrations(ctx, configurator, vm) + } +} +``` + +2. Set the upgrade handler in `app.go`. The `moduleName` parameter refers to the authentication module's `ScopedKeeper` name. This is the name provided upon instantiation in `app.go` via the [`x/capability` keeper `ScopeToModule(moduleName string)`](https://github.com/cosmos/cosmos-sdk/blob/v0.46.1/x/capability/keeper/keeper.go#L70) method. [See here for an example in `simapp`](https://github.com/cosmos/ibc-go/blob/v5.0.0/testing/simapp/app.go#L304). + +```go +app.UpgradeKeeper.SetUpgradeHandler( + v6.UpgradeName, + v6.CreateUpgradeHandler( + app.mm, + app.configurator, + app.appCodec, + app.keys[capabilitytypes.ModuleName], + app.CapabilityKeeper, + >>>> moduleName <<<<, + ), +) +``` + +## IBC Apps + +### ICS27 - Interchain Accounts + +#### Controller APIs + +In previous releases of ibc-go, chain developers integrating the ICS27 interchain accounts controller functionality were expected to create a custom `Base Application` referred to as an authentication module, see the section [Building an authentication module](../02-apps/02-interchain-accounts/03-auth-modules.md) from the documentation. + +The `Base Application` was intended to be composed with the ICS27 controller submodule `Keeper` and facilitate many forms of message authentication depending on a chain's particular use case. + +Prior to ibc-go v6 the controller submodule exposed only these two functions (to which we will refer as the legacy APIs): + +- [`RegisterInterchainAccount`](https://github.com/cosmos/ibc-go/blob/v5.0.0/modules/apps/27-interchain-accounts/controller/keeper/account.go#L19) +- [`SendTx`](https://github.com/cosmos/ibc-go/blob/v5.0.0/modules/apps/27-interchain-accounts/controller/keeper/relay.go#L18) + +However, these functions have now been deprecated in favour of the new controller submodule `MsgServer` and will be removed in later releases. + +Both APIs remain functional and maintain backwards compatibility in ibc-go v6, however consumers of these APIs are now recommended to follow the message passing paradigm outlined in Cosmos SDK [ADR 031](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-031-msg-service.md) and [ADR 033](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-033-protobuf-inter-module-comm.md). This is facilitated by the Cosmos SDK [`MsgServiceRouter`](https://github.com/cosmos/cosmos-sdk/blob/main/baseapp/msg_service_router.go#L17) and chain developers creating custom application logic can now omit the ICS27 controller submodule `Keeper` from their module and instead depend on message routing. + +Depending on the use case, developers of custom authentication modules face one of three scenarios: + +![auth-module-decision-tree.png](./images/auth-module-decision-tree.png) + +**My authentication module needs to access IBC packet callbacks** + +Application developers that wish to consume IBC packet callbacks and react upon packet acknowledgements **must** continue using the controller submodule's legacy APIs. The authentication modules will not need a `ScopedKeeper` anymore, though, because the channel capability will be claimed by the controller submodule. For example, given an Interchain Accounts authentication module keeper `ICAAuthKeeper`, the authentication module's `ScopedKeeper` (`scopedICAAuthKeeper`) is not needed anymore and can be removed for the argument list of the keeper constructor function, as shown here: + +```diff +app.ICAAuthKeeper = icaauthkeeper.NewKeeper( + appCodec, + keys[icaauthtypes.StoreKey], + app.ICAControllerKeeper, +- scopedICAAuthKeeper, +) +``` + +Please note that the authentication module's `ScopedKeeper` name is still needed as part of the channel capability migration described in section [Upgrade proposal](#upgrade-proposal) above. Therefore the authentication module's `ScopedKeeper` cannot be completely removed from the chain code until the migration has run. + +In the future, the use of the legacy APIs for accessing packet callbacks will be replaced by IBC Actor Callbacks (see [ADR 008](https://github.com/cosmos/ibc-go/pull/1976) for more details) and it will also be possible to access them with the `MsgServiceRouter`. + +**My authentication module does not need access to IBC packet callbacks** + +The authentication module can migrate from using the legacy APIs and it can be composed instead with the `MsgServiceRouter`, so that the authentication module is able to pass messages to the controller submodule's `MsgServer` to register interchain accounts and send packets to the interchain account. For example, given an Interchain Accounts authentication module keeper `ICAAuthKeeper`, the ICS27 controller submodule keeper (`ICAControllerKeeper`) and authentication module scoped keeper (`scopedICAAuthKeeper`) are not needed anymore and can be replaced with the `MsgServiceRouter`, as shown here: + +```diff +app.ICAAuthKeeper = icaauthkeeper.NewKeeper( + appCodec, + keys[icaauthtypes.StoreKey], +- app.ICAControllerKeeper, +- scopedICAAuthKeeper, ++ app.MsgServiceRouter(), +) +``` + +In your authentication module you can route messages to the controller submodule's `MsgServer` instead of using the legacy APIs. For example, for registering an interchain account: + +```diff +- if err := keeper.icaControllerKeeper.RegisterInterchainAccount( +- ctx, +- connectionID, +- owner.String(), +- version, +- ); err != nil { +- return err +- } ++ msg := controllertypes.NewMsgRegisterInterchainAccount( ++ connectionID, ++ owner.String(), ++ version, ++ ) ++ handler := keeper.msgRouter.Handler(msg) ++ res, err := handler(ctx, msg) ++ if err != nil { ++ return err ++ } +``` + +where `controllertypes` is an import alias for `"github.com/cosmos/ibc-go/v6/modules/apps/27-interchain-accounts/controller/types"`. + +In addition, in this use case the authentication module does not need to implement the `IBCModule` interface anymore. + +**I do not need a custom authentication module anymore** + +If your authentication module does not have any extra functionality compared to the default authentication module added in ibc-go v6 (the `MsgServer`), or if you can use a generic authentication module, such as the `x/auth`, `x/gov` or `x/group` modules from the Cosmos SDK (v0.46 and later), then you can remove your authentication module completely and use instead the gRPC endpoints of the `MsgServer` or the CLI added in ibc-go v6. + +Please remember that the authentication module's `ScopedKeeper` name is still needed as part of the channel capability migration described in section [Upgrade proposal](#upgrade-proposal) above. + +#### Host params + +The ICS27 host submodule default params have been updated to include the `AllowAllHostMsgs` wildcard `*`. +This enables execution of any `sdk.Msg` type for ICS27 registered on the host chain `InterfaceRegistry`. + +```diff +// AllowAllHostMsgs holds the string key that allows all message types on interchain accounts host module +const AllowAllHostMsgs = "*" + +... + +// DefaultParams is the default parameter configuration for the host submodule +func DefaultParams() Params { +- return NewParams(DefaultHostEnabled, nil) ++ return NewParams(DefaultHostEnabled, []string{AllowAllHostMsgs}) +} +``` + +#### API breaking changes + +`SerializeCosmosTx` takes in a `[]proto.Message` instead of `[]sdk.Message`. This allows for the serialization of proto messages without requiring the fulfillment of the `sdk.Msg` interface. + +The `27-interchain-accounts` genesis types have been moved to their own package: `modules/apps/27-interchain-accounts/genesis/types`. +This change facilitates the addition of the ICS27 controller submodule `MsgServer` and avoids cyclic imports. This should have minimal disruption to chain developers integrating `27-interchain-accounts`. + +The ICS27 host submodule `NewKeeper` function in `modules/apps/27-interchain-accounts/host/keeper` now includes an additional parameter of type `ICS4Wrapper`. +This provides the host submodule with the ability to correctly unwrap channel versions in the event of a channel reopening handshake. + +```diff +func NewKeeper( + cdc codec.BinaryCodec, key storetypes.StoreKey, paramSpace paramtypes.Subspace, +- channelKeeper icatypes.ChannelKeeper, portKeeper icatypes.PortKeeper, ++ ics4Wrapper icatypes.ICS4Wrapper, channelKeeper icatypes.ChannelKeeper, portKeeper icatypes.PortKeeper, + accountKeeper icatypes.AccountKeeper, scopedKeeper icatypes.ScopedKeeper, msgRouter icatypes.MessageRouter, +) Keeper +``` + +### ICS29 - `NewKeeper` API change + +The `NewKeeper` function of ICS29 has been updated to remove the `paramSpace` parameter as it was unused. + +```diff +func NewKeeper( +- cdc codec.BinaryCodec, key storetypes.StoreKey, paramSpace paramtypes.Subspace, +- ics4Wrapper types.ICS4Wrapper, channelKeeper types.ChannelKeeper, portKeeper types.PortKeeper, authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, ++ cdc codec.BinaryCodec, key storetypes.StoreKey, ++ ics4Wrapper types.ICS4Wrapper, channelKeeper types.ChannelKeeper, ++ portKeeper types.PortKeeper, authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, +) Keeper { +``` + +### ICS20 - `SendTransfer` is no longer exported + +The `SendTransfer` function of ICS20 has been removed. IBC transfers should now be initiated with `MsgTransfer` and routed to the ICS20 `MsgServer`. + +See below for example: + +```go +if handler := msgRouter.Handler(msgTransfer); handler != nil { + if err := msgTransfer.ValidateBasic(); err != nil { + return nil, err + } + + res, err := handler(ctx, msgTransfer) + if err != nil { + return nil, err + } +} +``` + +### ICS04 - `SendPacket` API change + +The `SendPacket` API has been simplified: + +```diff +// SendPacket is called by a module in order to send an IBC packet on a channel +func (k Keeper) SendPacket( + ctx sdk.Context, + channelCap *capabilitytypes.Capability, +- packet exported.PacketI, +-) error { ++ sourcePort string, ++ sourceChannel string, ++ timeoutHeight clienttypes.Height, ++ timeoutTimestamp uint64, ++ data []byte, ++) (uint64, error) { +``` + +Callers no longer need to pass in a pre-constructed packet. +The destination port/channel identifiers and the packet sequence will be determined by core IBC. +`SendPacket` will return the packet sequence. + +### IBC testing package + +The `SendPacket` API has been simplified: + +```diff +// SendPacket is called by a module in order to send an IBC packet on a channel +func (k Keeper) SendPacket( + ctx sdk.Context, + channelCap *capabilitytypes.Capability, +- packet exported.PacketI, +-) error { ++ sourcePort string, ++ sourceChannel string, ++ timeoutHeight clienttypes.Height, ++ timeoutTimestamp uint64, ++ data []byte, ++) (uint64, error) { +``` + +Callers no longer need to pass in a pre-constructed packet. `SendPacket` will return the packet sequence. + +## Relayers + +- No relevant changes were made in this release. + +## IBC Light Clients + +- No relevant changes were made in this release. diff --git a/docs/docs/05-migrations/08-v6-to-v7.md b/docs/docs/05-migrations/08-v6-to-v7.md new file mode 100644 index 0000000..e2214cf --- /dev/null +++ b/docs/docs/05-migrations/08-v6-to-v7.md @@ -0,0 +1,357 @@ +--- +title: IBC-Go v6 to v7 +sidebar_label: IBC-Go v6 to v7 +sidebar_position: 8 +slug: /migrations/v6-to-v7 +--- +# Migrating from ibc-go v6 to v7 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +## Chains + +Chains will perform automatic migrations to remove existing localhost clients and to migrate the solomachine to v3 of the protobuf definition. + +An optional upgrade handler has been added to prune expired tendermint consensus states. It may be used during any upgrade (from v7 onwards). +Add the following to the function call to the upgrade handler in `app/app.go`, to perform the optional state pruning. + +```go +import ( + // ... + ibctmmigrations "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint/migrations" +) + +// ... + +app.UpgradeKeeper.SetUpgradeHandler( + upgradeName, + func(ctx sdk.Context, _ upgradetypes.Plan, _ module.VersionMap) (module.VersionMap, error) { + // prune expired tendermint consensus states to save storage space + _, err := ibctmmigrations.PruneExpiredConsensusStates(ctx, app.Codec, app.IBCKeeper.ClientKeeper) + if err != nil { + return nil, err + } + + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }, +) +``` + +Checkout the logs to see how many consensus states are pruned. + +### Light client registration + +Chains must explicitly register the types of any light client modules it wishes to integrate. + +#### Tendermint registration + +To register the tendermint client, modify the `app.go` file to include the tendermint `AppModuleBasic`: + +```diff +import ( + // ... ++ ibctm "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" +) + +// ... + +ModuleBasics = module.NewBasicManager( + ... + ibc.AppModuleBasic{}, ++ ibctm.AppModuleBasic{}, + ... +) +``` + +It may be useful to reference the [PR](https://github.com/cosmos/ibc-go/pull/2825) which added the `AppModuleBasic` for the tendermint client. + +#### Solo machine registration + +To register the solo machine client, modify the `app.go` file to include the solo machine `AppModuleBasic`: + +```diff +import ( + // ... ++ solomachine "github.com/cosmos/ibc-go/v7/modules/light-clients/06-solomachine" +) + +// ... + +ModuleBasics = module.NewBasicManager( + ... + ibc.AppModuleBasic{}, ++ solomachine.AppModuleBasic{}, + ... +) +``` + +It may be useful to reference the [PR](https://github.com/cosmos/ibc-go/pull/2826) which added the `AppModuleBasic` for the solo machine client. + +### Testing package API + +The `SetChannelClosed` utility method in `testing/endpoint.go` has been updated to `SetChannelState`, which will take a `channeltypes.State` argument so that the `ChannelState` can be set to any of the possible channel states. + +## IBC Apps + +- No relevant changes were made in this release. + +## Relayers + +- No relevant changes were made in this release. + +## IBC Light Clients + +### `ClientState` interface changes + +The `VerifyUpgradeAndUpdateState` function has been modified. The client state and consensus state return values have been removed. + +Light clients **must** handle all management of client and consensus states including the setting of updated client state and consensus state in the client store. + +The `Initialize` method is now expected to set the initial client state, consensus state and any client-specific metadata in the provided store upon client creation. + +The `CheckHeaderAndUpdateState` method has been split into 4 new methods: + +- `VerifyClientMessage` verifies a `ClientMessage`. A `ClientMessage` could be a `Header`, `Misbehaviour`, or batch update. Calls to `CheckForMisbehaviour`, `UpdateState`, and `UpdateStateOnMisbehaviour` will assume that the content of the `ClientMessage` has been verified and can be trusted. An error should be returned if the `ClientMessage` fails to verify. + +- `CheckForMisbehaviour` checks for evidence of a misbehaviour in `Header` or `Misbehaviour` types. + +- `UpdateStateOnMisbehaviour` performs appropriate state changes on a `ClientState` given that misbehaviour has been detected and verified. + +- `UpdateState` updates and stores as necessary any associated information for an IBC client, such as the `ClientState` and corresponding `ConsensusState`. An error is returned if `ClientMessage` is of type `Misbehaviour`. Upon successful update, a list containing the updated consensus state height is returned. + +The `CheckMisbehaviourAndUpdateState` function has been removed from `ClientState` interface. This functionality is now encapsulated by the usage of `VerifyClientMessage`, `CheckForMisbehaviour`, `UpdateStateOnMisbehaviour`. + +The function `GetTimestampAtHeight` has been added to the `ClientState` interface. It should return the timestamp for a consensus state associated with the provided height. + +Prior to ibc-go/v7 the `ClientState` interface defined a method for each data type which was being verified in the counterparty state store. +The state verification functions for all IBC data types have been consolidated into two generic methods, `VerifyMembership` and `VerifyNonMembership`. +Both are expected to be provided with a standardised key path, `exported.Path`, as defined in [ICS 24 host requirements](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements). Membership verification requires callers to provide the marshalled value `[]byte`. Delay period values should be zero for non-packet processing verification. A zero proof height is now allowed by core IBC and may be passed into `VerifyMembership` and `VerifyNonMembership`. Light clients are responsible for returning an error if a zero proof height is invalid behaviour. + +See below for an example of how ibc-go now performs channel state verification. + +```go +merklePath := commitmenttypes.NewMerklePath(host.ChannelPath(portID, channelID)) +merklePath, err := commitmenttypes.ApplyPrefix(connection.GetCounterparty().GetPrefix(), merklePath) +if err != nil { + return err +} + +channelEnd, ok := channel.(channeltypes.Channel) +if !ok { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "invalid channel type %T", channel) +} + +bz, err := k.cdc.Marshal(&channelEnd) +if err != nil { + return err +} + +if err := clientState.VerifyMembership( + ctx, clientStore, k.cdc, height, + 0, 0, // skip delay period checks for non-packet processing verification + proof, merklePath, bz, +); err != nil { + return sdkerrors.Wrapf(err, "failed channel state verification for client (%s)", clientID) +} +``` + +### `Header` and `Misbehaviour` + +`exported.Header` and `exported.Misbehaviour` interface types have been merged and renamed to `ClientMessage` interface. + +`GetHeight` function has been removed from `exported.Header` and thus is not included in the `ClientMessage` interface + +### `ConsensusState` + +The `GetRoot` function has been removed from consensus state interface since it was not used by core IBC. + +### Client keeper + +Keeper function `CheckMisbehaviourAndUpdateState` has been removed since function `UpdateClient` can now handle updating `ClientState` on `ClientMessage` type which can be any `Misbehaviour` implementations. + +### SDK message + +`MsgSubmitMisbehaviour` is deprecated since `MsgUpdateClient` can now submit a `ClientMessage` type which can be any `Misbehaviour` implementations. + +The field `header` in `MsgUpdateClient` has been renamed to `client_message`. + +## Solomachine + +The `06-solomachine` client implementation has been simplified in ibc-go/v7. In-place store migrations have been added to migrate solomachine clients from `v2` to `v3`. + +### `ClientState` + +The `ClientState` protobuf message definition has been updated to remove the deprecated `bool` field `allow_update_after_proposal`. + +```diff +message ClientState { + option (gogoproto.goproto_getters) = false; + + uint64 sequence = 1; + bool is_frozen = 2 [(gogoproto.moretags) = "yaml:\"is_frozen\""]; + ConsensusState consensus_state = 3 [(gogoproto.moretags) = "yaml:\"consensus_state\""]; +- bool allow_update_after_proposal = 4 [(gogoproto.moretags) = "yaml:\"allow_update_after_proposal\""]; +} +``` + +### `Header` and `Misbehaviour` + +The `06-solomachine` protobuf message `Header` has been updated to remove the `sequence` field. This field was seen as redundant as the implementation can safely rely on the `sequence` value maintained within the `ClientState`. + +```diff +message Header { + option (gogoproto.goproto_getters) = false; + +- uint64 sequence = 1; +- uint64 timestamp = 2; +- bytes signature = 3; +- google.protobuf.Any new_public_key = 4 [(gogoproto.moretags) = "yaml:\"new_public_key\""]; +- string new_diversifier = 5 [(gogoproto.moretags) = "yaml:\"new_diversifier\""]; ++ uint64 timestamp = 1; ++ bytes signature = 2; ++ google.protobuf.Any new_public_key = 3 [(gogoproto.moretags) = "yaml:\"new_public_key\""]; ++ string new_diversifier = 4 [(gogoproto.moretags) = "yaml:\"new_diversifier\""]; +} +``` + +Similarly, the `Misbehaviour` protobuf message has been updated to remove the `client_id` field. + +```diff +message Misbehaviour { + option (gogoproto.goproto_getters) = false; + +- string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""]; +- uint64 sequence = 2; +- SignatureAndData signature_one = 3 [(gogoproto.moretags) = "yaml:\"signature_one\""]; +- SignatureAndData signature_two = 4 [(gogoproto.moretags) = "yaml:\"signature_two\""]; ++ uint64 sequence = 1; ++ SignatureAndData signature_one = 2 [(gogoproto.moretags) = "yaml:\"signature_one\""]; ++ SignatureAndData signature_two = 3 [(gogoproto.moretags) = "yaml:\"signature_two\""]; +} +``` + +### `SignBytes` + +Most notably, the `SignBytes` protobuf definition has been modified to replace the `data_type` field with a new field, `path`. The `path` field is defined as `bytes` and represents a serialized [ICS-24](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements) standardized key path under which the `data` is stored. + +```diff +message SignBytes { + option (gogoproto.goproto_getters) = false; + + uint64 sequence = 1; + uint64 timestamp = 2; + string diversifier = 3; +- DataType data_type = 4 [(gogoproto.moretags) = "yaml:\"data_type\""]; ++ bytes path = 4; + bytes data = 5; +} +``` + +The `DataType` enum and all associated data types have been removed, greatly reducing the number of message definitions and complexity in constructing the `SignBytes` message type. Likewise, solomachine implementations must now use the serialized `path` value when constructing `SignatureAndData` for signature verification of `SignBytes` data. + +```diff +message SignatureAndData { + option (gogoproto.goproto_getters) = false; + + bytes signature = 1; +- DataType data_type = 2 [(gogoproto.moretags) = "yaml:\"data_type\""]; ++ bytes path = 2; + bytes data = 3; + uint64 timestamp = 4; +} +``` + +For more information, please refer to [ADR-007](https://github.com/cosmos/ibc-go/blob/02-client-refactor-beta1/docs/architecture/adr-007-solomachine-signbytes.md). + +### IBC module constants + +IBC module constants have been moved from the `host` package to the `exported` package. Any usages will need to be updated. + +```diff +import ( + // ... +- host "github.com/cosmos/ibc-go/v7/modules/core/24-host" ++ ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" + // ... +) + +- host.ModuleName ++ ibcexported.ModuleName + +- host.StoreKey ++ ibcexported.StoreKey + +- host.QuerierRoute ++ ibcexported.QuerierRoute + +- host.RouterKey ++ ibcexported.RouterKey +``` + +## Upgrading to Cosmos SDK 0.47 + +The following should be considered as complementary to [Cosmos SDK v0.47 UPGRADING.md](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc2/UPGRADING.md). + +### Protobuf + +Protobuf code generation, linting and formatting have been updated to leverage the `ghcr.io/cosmos/proto-builder:0.11.5` docker container. IBC protobuf definitions are now packaged and published to [buf.build/cosmos/ibc](https://buf.build/cosmos/ibc) via CI workflows. The `third_party/proto` directory has been removed in favour of dependency management using [buf.build](https://docs.buf.build/introduction). + +### App modules + +Legacy APIs of the `AppModule` interface have been removed from ibc-go modules. For example, for + +```diff +- // Route implements the AppModule interface +- func (am AppModule) Route() sdk.Route { +- return sdk.Route{} +- } +- +- // QuerierRoute implements the AppModule interface +- func (AppModule) QuerierRoute() string { +- return types.QuerierRoute +- } +- +- // LegacyQuerierHandler implements the AppModule interface +- func (am AppModule) LegacyQuerierHandler(*codec.LegacyAmino) sdk.Querier { +- return nil +- } +- +- // ProposalContents doesn't return any content functions for governance proposals. +- func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalContent { +- return nil +- } +``` + +### Imports + +Imports for ics23 have been updated as the repository have been migrated from confio to cosmos. + +```diff +import ( + // ... +- ics23 "github.com/confio/ics23/go" ++ ics23 "github.com/cosmos/ics23/go" + // ... +) +``` + +Imports for gogoproto have been updated. + +```diff +import ( + // ... +- "github.com/gogo/protobuf/proto" ++ "github.com/cosmos/gogoproto/proto" + // ... +) +``` diff --git a/docs/docs/05-migrations/09-v7-to-v7_1.md b/docs/docs/05-migrations/09-v7-to-v7_1.md new file mode 100644 index 0000000..04b7b30 --- /dev/null +++ b/docs/docs/05-migrations/09-v7-to-v7_1.md @@ -0,0 +1,66 @@ +--- +title: IBC-Go v7 to v7.1 +sidebar_label: IBC-Go v7 to v7.1 +sidebar_position: 9 +slug: /migrations/v7-to-v7_1 +--- + +# Migrating from v7 to v7.1 + +This guide provides instructions for migrating to version `v7.1.0` of ibc-go. + +There are four sections based on the four potential user groups of this document: + +- [Migrating from v7 to v7.1](#migrating-from-v7-to-v71) + - [Chains](#chains) + - [IBC Apps](#ibc-apps) + - [Relayers](#relayers) + - [IBC Light Clients](#ibc-light-clients) + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. + +## Chains + +In the previous release of ibc-go, the localhost `v1` light client module was deprecated and removed. The ibc-go `v7.1.0` release introduces `v2` of the 09-localhost light client module. + +An [automatic migration handler](https://github.com/cosmos/ibc-go/blob/v7.2.0/modules/core/module.go#L127-L145) is configured in the core IBC module to set the localhost `ClientState` and sentinel `ConnectionEnd` in state. + +In order to use the 09-localhost client chains must update the `AllowedClients` parameter in the 02-client submodule of core IBC. This can be configured directly in the application upgrade handler or alternatively updated via the legacy governance parameter change proposal. +We **strongly** recommend chains to perform this action so that intra-ledger communication can be carried out using the familiar IBC interfaces. + +See the upgrade handler code sample provided below or [follow this link](https://github.com/cosmos/ibc-go/blob/v7.2.0/testing/simapp/upgrades/upgrades.go#L85) for the upgrade handler used by the ibc-go simapp. + +```go +func CreateV7LocalhostUpgradeHandler( + mm *module.Manager, + configurator module.Configurator, + clientKeeper clientkeeper.Keeper, +) upgradetypes.UpgradeHandler { + return func(ctx sdk.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + // explicitly update the IBC 02-client params, adding the localhost client type + params := clientKeeper.GetParams(ctx) + params.AllowedClients = append(params.AllowedClients, exported.Localhost) + clientKeeper.SetParams(ctx, params) + + return mm.RunMigrations(ctx, configurator, vm) + } +} +``` + +### Transfer migration + +An [automatic migration handler](https://github.com/cosmos/ibc-go/blob/v7.2.0/modules/apps/transfer/module.go#L111-L113) is configured in the transfer module to set the total amount in escrow for all denominations of coins that have been sent out. For each denomination a state entry is added with the total amount of coins in escrow regardless of the channel from which they were transferred. + +## IBC Apps + +- No relevant changes were made in this release. + +## Relayers + +The event attribute `packet_connection` (`connectiontypes.AttributeKeyConnection`) has been deprecated. +Please use the `connection_id` attribute (`connectiontypes.AttributeKeyConnectionID`) which is emitted by all channel events. +Only send packet, receive packet, write acknowledgement, and acknowledge packet events used `packet_connection` previously. + +## IBC Light Clients + +- No relevant changes were made in this release. diff --git a/docs/docs/05-migrations/10-v7_2-to-v7_3.md b/docs/docs/05-migrations/10-v7_2-to-v7_3.md new file mode 100644 index 0000000..762bf2b --- /dev/null +++ b/docs/docs/05-migrations/10-v7_2-to-v7_3.md @@ -0,0 +1,50 @@ +--- +title: IBC-Go v7.2 to v7.3 +sidebar_label: IBC-Go v7.2 to v7.3 +sidebar_position: 10 +slug: /migrations/v7_2-to-v7_3 +--- + +# Migrating from v7.2 to v7.3 + +This guide provides instructions for migrating to version `v7.3.0` of ibc-go. + +There are four sections based on the four potential user groups of this document: + +- [Migrating from v7.2 to v7.3](#migrating-from-v72-to-v73) + - [Chains](#chains) + - [IBC Apps](#ibc-apps) + - [Relayers](#relayers) + - [IBC Light Clients](#ibc-light-clients) + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. + +## Chains + +- No relevant changes were made in this release. + +## IBC Apps + +A set of interfaces have been added that IBC applications may optionally implement. Developers interested in integrating their applications with the [callbacks middleware](../04-middleware/01-callbacks/01-overview.md) should implement these interfaces so that the callbacks middleware can retrieve the desired callback addresses on the source and destination chains and execute actions on packet lifecycle events. The interfaces are [`PacketDataUnmarshaler`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/core/05-port/types/module.go#L142-L147), [`PacketDataProvider`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/core/exported/packet.go#L43-L52) and [`PacketData`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/core/exported/packet.go#L36-L41). + +Sample implementations are available for reference. For `transfer`: + +- [`PacketDataUnmarshaler`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/transfer/ibc_module.go#L303-L313), +- [`PacketDataProvider`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/transfer/types/packet.go#L85-L105) +- and [`PacketData`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/transfer/types/packet.go#L74-L83). + +For `27-interchain-accounts`: + +- [`PacketDataUnmarshaler`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/27-interchain-accounts/controller/ibc_middleware.go#L258-L268), +- [`PacketDataProvider`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/27-interchain-accounts/types/packet.go#L94-L114) +- and [`PacketData`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/27-interchain-accounts/types/packet.go#L78-L92). + +## Relayers + +- No relevant changes were made in this release. + +## IBC Light Clients + +### 06-solomachine + +Solo machines are now expected to sign data on a path that 1) does not include a connection prefix (e.g `ibc`) and 2) does not escape any characters. See PR [#4429](https://github.com/cosmos/ibc-go/pull/4429) for more details. We recommend **NOT** using the solo machine light client of versions lower than v7.3.0. diff --git a/docs/docs/05-migrations/11-v7-to-v8.md b/docs/docs/05-migrations/11-v7-to-v8.md new file mode 100644 index 0000000..b9814ff --- /dev/null +++ b/docs/docs/05-migrations/11-v7-to-v8.md @@ -0,0 +1,221 @@ +--- +title: IBC-Go v7 to v8 +sidebar_label: IBC-Go v7 to v8 +sidebar_position: 11 +slug: /migrations/v7-to-v8 +--- + +# Migrating from v7 to v8 + +This guide provides instructions for migrating to version `v8.0.0` of ibc-go. + +There are four sections based on the four potential user groups of this document: + +- [Migrating from v7 to v8](#migrating-from-v7-to-v8) + - [Chains](#chains) + - [Cosmos SDK v0.50 upgrade](#cosmos-sdk-v050-upgrade) + - [Authority](#authority) + - [Testing package](#testing-package) + - [Params migration](#params-migration) + - [Governance V1 migration](#governance-v1-migration) + - [Transfer migration](#transfer-migration) + - [IBC Apps](#ibc-apps) + - [ICS20 - Transfer](#ics20---transfer) + - [ICS27 - Interchain Accounts](#ics27---interchain-accounts) + - [Relayers](#relayers) + - [IBC Light Clients](#ibc-light-clients) + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. + +## Chains + +The type of the `PortKeeper` field of the IBC keeper have been changed to `*portkeeper.Keeper`: + +```diff +// Keeper defines each ICS keeper for IBC +type Keeper struct { + // implements gRPC QueryServer interface + types.QueryServer + + cdc codec.BinaryCodec + + ClientKeeper clientkeeper.Keeper + ConnectionKeeper connectionkeeper.Keeper + ChannelKeeper channelkeeper.Keeper +- PortKeeper portkeeper.Keeper ++ PortKeeper *portkeeper.Keeper + Router *porttypes.Router + + authority string +} +``` + +See [this PR](https://github.com/cosmos/ibc-go/pull/4703/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08a) for the changes required in `app.go`. + +An extra parameter `totalEscrowed` of type `sdk.Coins` has been added to transfer module's [`NewGenesisState` function](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/transfer/types/genesis.go#L10). This parameter specifies the total amount of tokens that are in the module's escrow accounts. + +### Cosmos SDK v0.50 upgrade + +Version `v8.0.0` of ibc-go upgrades to Cosmos SDK v0.50. Please follow the [Cosmos SDK v0.50 upgrading guide](https://github.com/cosmos/cosmos-sdk/blob/v0.50.1/UPGRADING.md) to account for its API breaking changes. + +### Authority + +An authority identifier (e.g. an address) needs to be passed in the `NewKeeper` functions of the following keepers: + +- You must pass the `authority` to the ica/host keeper (implemented in [#3520](https://github.com/cosmos/ibc-go/pull/3520)). See [diff](https://github.com/cosmos/ibc-go/pull/3520/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08a): + +```diff +// app.go + +// ICA Host keeper +app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), + app.IBCFeeKeeper, // use ics29 fee as ics4Wrapper in middleware stack + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.AccountKeeper, scopedICAHostKeeper, app.MsgServiceRouter(), ++ authtypes.NewModuleAddress(govtypes.ModuleName).String(), +) +``` + +- You must pass the `authority` to the ica/controller keeper (implemented in [#3590](https://github.com/cosmos/ibc-go/pull/3590)). See [diff](https://github.com/cosmos/ibc-go/pull/3590/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08a): + +```diff +// app.go + +// ICA Controller keeper +app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName), + app.IBCFeeKeeper, // use ics29 fee as ics4Wrapper in middleware stack + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + scopedICAControllerKeeper, app.MsgServiceRouter(), ++ authtypes.NewModuleAddress(govtypes.ModuleName).String(), +) +``` + +- You must pass the `authority` to the ibctransfer keeper (implemented in [#3553](https://github.com/cosmos/ibc-go/pull/3553)). See [diff](https://github.com/cosmos/ibc-go/pull/3553/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08a): + +```diff +// app.go + +// Create Transfer Keeper and pass IBCFeeKeeper as expected Channel and PortKeeper +// since fee middleware will wrap the IBCKeeper for underlying application. +app.TransferKeeper = ibctransferkeeper.NewKeeper( + appCodec, keys[ibctransfertypes.StoreKey], app.GetSubspace(ibctransfertypes.ModuleName), + app.IBCFeeKeeper, // ISC4 Wrapper: fee IBC middleware + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.AccountKeeper, app.BankKeeper, scopedTransferKeeper, ++ authtypes.NewModuleAddress(govtypes.ModuleName).String(), +) +``` + +- You should pass the `authority` to the IBC keeper (implemented in [#3640](https://github.com/cosmos/ibc-go/pull/3640) and [#3650](https://github.com/cosmos/ibc-go/pull/3650)). See [diff](https://github.com/cosmos/ibc-go/pull/3640/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08a): + +```diff +// app.go + +// IBC Keepers +app.IBCKeeper = ibckeeper.NewKeeper( + appCodec, + keys[ibcexported.StoreKey], + app.GetSubspace(ibcexported.ModuleName), + app.StakingKeeper, + app.UpgradeKeeper, + scopedIBCKeeper, ++ authtypes.NewModuleAddress(govtypes.ModuleName).String(), +) +``` + +The authority determines the transaction signer allowed to execute certain messages (e.g. `MsgUpdateParams`). + +### Testing package + +- The function `SetupWithGenesisAccounts` has been removed. +- The function [`RelayPacketWithResults`](https://github.com/cosmos/ibc-go/blob/v8.0.0/testing/path.go#L66) has been added. This function returns the result of the packet receive transaction, the acknowledgement written on the receiving chain, an error if a relay step fails or the packet commitment does not exist on either chain. + +### Params migration + +Params are now self managed in the following submodules: + +- ica/controller [#3590](https://github.com/cosmos/ibc-go/pull/3590) +- ica/host [#3520](https://github.com/cosmos/ibc-go/pull/3520) +- ibc/connection [#3650](https://github.com/cosmos/ibc-go/pull/3650) +- ibc/client [#3640](https://github.com/cosmos/ibc-go/pull/3640) +- ibc/transfer [#3553](https://github.com/cosmos/ibc-go/pull/3553) + +Each module has a corresponding `MsgUpdateParams` message with a `Params` which can be specified in full to update the modules' `Params`. + +Legacy params subspaces must still be initialised in app.go in order to successfully migrate from `x/params`` to the new self-contained approach. See reference [this](https://github.com/cosmos/ibc-go/blob/v8.0.0/testing/simapp/app.go#L1007-L1012) for reference. + +For new chains which do not rely on migration of parameters from `x/params`, an expected interface has been added for each module. This allows chain developers to provide `nil` as the `legacySubspace` argument to `NewKeeper` functions. + +### Governance V1 migration + +Proposals have been migrated to [gov v1 messages](https://docs.cosmos.network/v0.50/modules/gov#messages) (see [#4620](https://github.com/cosmos/ibc-go/pull/4620)). The proposal `ClientUpdateProposal` has been deprecated and [`MsgRecoverClient`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/client/v1/tx.proto#L121-L134) should be used instead. Likewise, the proposal `UpgradeProposal` has been deprecated and [`MsgIBCSoftwareUpgrade`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/client/v1/tx.proto#L139-L154) should be used instead. Both proposals will be removed in the next major release. + +`MsgRecoverClient` and `MsgIBCSoftwareUpgrade` will only be allowed to be executed if the signer is the authority designated at the time of instantiating the IBC keeper. So please make sure that the correct authority is provided to the IBC keeper. + +Remove the `UpgradeProposalHandler` and `UpdateClientProposalHandler` from the `BasicModuleManager`: + +```diff +app.BasicModuleManager = module.NewBasicManagerFromManager( + app.ModuleManager, + map[string]module.AppModuleBasic{ + genutiltypes.ModuleName: genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator), + govtypes.ModuleName: gov.NewAppModuleBasic( + []govclient.ProposalHandler{ + paramsclient.ProposalHandler, +- ibcclientclient.UpdateClientProposalHandler, +- ibcclientclient.UpgradeProposalHandler, + }, + ), +}) +``` + +Support for in-flight legacy recover client proposals (i.e. `ClientUpdateProposal`) will be made for v8, but chains should use `MsgRecoverClient` only afterwards to avoid in-flight client recovery failing when upgrading to v9. See [this issue](https://github.com/cosmos/ibc-go/issues/4721) for more information. + +Please note that ibc-go offers facilities to test an ibc-go upgrade: + +- All e2e tests of the repository can be [run with custom Docker chain images](https://github.com/cosmos/ibc-go/blob/c5bac5e03a0eae449b9efe0d312258115c1a1e85/e2e/README.md#running-tests-with-custom-images). +- An [importable workflow](https://github.com/cosmos/ibc-go/blob/c5bac5e03a0eae449b9efe0d312258115c1a1e85/e2e/README.md#importable-workflow) that can be used from any other repository to test chain upgrades. + +### Transfer migration + +An [automatic migration handler](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/transfer/module.go#L136) is configured in the transfer module to set the [denomination metadata](https://github.com/cosmos/cosmos-sdk/blob/v0.50.1/proto/cosmos/bank/v1beta1/bank.proto#L96-L125) for the IBC denominations of all vouchers minted by the transfer module. + +## IBC Apps + +### ICS20 - Transfer + +- The function `IsBound` has been renamed to [`hasCapability`](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/transfer/keeper/keeper.go#L98) and made unexported. + +### ICS27 - Interchain Accounts + +- Functions [`SerializeCosmosTx`](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/27-interchain-accounts/types/codec.go#L32) and [`DeserializeCosmosTx`](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/27-interchain-accounts/types/codec.go#L76) now accept an extra parameter `encoding` of type `string` that specifies the format in which the transaction messages are marshaled. Both [protobuf and proto3 JSON formats](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/27-interchain-accounts/types/metadata.go#L14-L17) are supported. +- The function `IsBound` of controller submodule has been renamed to [`hasCapability`](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/27-interchain-accounts/controller/keeper/keeper.go#L111) and made unexported. +- The function `IsBound` of host submodule has been renamed to [`hasCapability`](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/27-interchain-accounts/host/keeper/keeper.go#L94) and made unexported. + +## Relayers + +- Getter functions in `MsgChannelOpenInitResponse`, `MsgChannelOpenTryResponse`, `MsgTransferResponse`, `MsgRegisterInterchainAccountResponse` and `MsgSendTxResponse` have been removed. The fields can be accessed directly. +- `channeltypes.EventTypeTimeoutPacketOnClose` (where `channeltypes` is an import alias for `"github.com/cosmos/ibc-go/v8/modules/core/04-channel"`) has been removed, since core IBC does not emit any event with this key. +- Attribute with key `counterparty_connection_id` has been removed from event with key `connectiontypes.EventTypeConnectionOpenInit` (where `connectiontypes` is an import alias for `"github.com/cosmos/ibc-go/v8/modules/core/03-connection/types"`) and attribute with key `counterparty_channel_id` has been removed from event with key `channeltypes.EventTypeChannelOpenInit` (where `channeltypes` is an import alias for `"github.com/cosmos/ibc-go/v8/modules/core/04-channel"`) since both (counterparty connection ID and counterparty channel ID) are empty on `ConnectionOpenInit` and `ChannelOpenInit` respectively. +- As part of the migration to [governance V1 messages](#governance-v1-migration) the following changes in events have been made: + +```diff +// IBC client events vars +var ( + EventTypeCreateClient = "create_client" + EventTypeUpdateClient = "update_client" + EventTypeUpgradeClient = "upgrade_client" + EventTypeSubmitMisbehaviour = "client_misbehaviour" +- EventTypeUpdateClientProposal = "update_client_proposal" +- EventTypeUpgradeClientProposal = "upgrade_client_proposal" ++ EventTypeRecoverClient = "recover_client" ++ EventTypeScheduleIBCSoftwareUpgrade = "schedule_ibc_software_upgrade" + EventTypeUpgradeChain = "upgrade_chain" +) +``` + +## IBC Light Clients + +- Functions `Pretty` and `String` of type `MerklePath` have been [removed](https://github.com/cosmos/ibc-go/pull/4459/files#diff-dd94ec1dde9b047c0cdfba204e30dad74a81de202e3b09ac5b42f493153811af). diff --git a/docs/docs/05-migrations/12-v8-to-v8_1.md b/docs/docs/05-migrations/12-v8-to-v8_1.md new file mode 100644 index 0000000..7808e81 --- /dev/null +++ b/docs/docs/05-migrations/12-v8-to-v8_1.md @@ -0,0 +1,42 @@ +--- +title: IBC-Go v8 to v8.1 +sidebar_label: IBC-Go v8 to v8.1 +sidebar_position: 12 +slug: /migrations/v8-to-v8_1 +--- + +# Migrating from v8 to v8.1 + +This guide provides instructions for migrating to version `v8.1.0` of ibc-go. + +There are four sections based on the four potential user groups of this document: + +- [Migrating from v8 to v8.1](#migrating-from-v8-to-v81) + - [Chains](#chains) + - [IBC apps](#ibc-apps) + - [Relayers](#relayers) + - [IBC light clients](#ibc-light-clients) + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. + +## Chains + +### `04-channel` params migration + +Self-managed [params](https://github.com/cosmos/ibc-go/blob/v8.1.0/proto/ibc/core/channel/v1/channel.proto#L183-L187) have been added for `04-channel` module. The params include the `upgrade_timeout` that is used in channel upgradability to specify the interval of time during which the counterparty chain must flush all in-flight packets on its end and move to `FLUSH_COMPLETE` state). An [automatic migration handler](https://github.com/cosmos/ibc-go/blob/v8.1.0/modules/core/module.go#L162-L166) is configured in the `04-channel` module that sets the default params (with a default upgrade timeout of 10 minutes). The module has a corresponding [`MsgUpdateParams` message](https://github.com/cosmos/ibc-go/blob/v8.1.0/proto/ibc/core/channel/v1/tx.proto#L435-L447) with a `Params` field which can be specified in full to update the module's `Params`. + +### Fee migration + +In ibc-go v8.1.0 an improved, more efficient escrow calculation of fees for packet incentivisation has been introduced (see [this issue](https://github.com/cosmos/ibc-go/issues/5509) for more information). Before v8.1.0 the amount escrowed was `(ReckFee + AckFee + TimeoutFee)`; from ibc-go v8.1.0, the calculation is changed to `Max(RecvFee + AckFee, TimeoutFee)`. In order to guarantee that the correct amount of fees are refunded for packets that are in-flight during the upgrade to ibc-go v8.1.0, an [automatic migration handler](https://github.com/cosmos/ibc-go/blob/v8.1.0/modules/apps/29-fee/module.go#L113-L115) is configured in the `29-fee` module to refund the leftover fees (i.e `(ReckFee + AckFee + TimeoutFee) - Max(RecvFee + AckFee, TimeoutFee)`) that otherwise would not be refunded when the packet lifecycle completes and the new calculation is used. + +## IBC apps + +- No relevant changes were made in this release. + +## Relayers + +- No relevant changes were made in this release. + +## IBC light clients + +- No relevant changes were made in this release. diff --git a/docs/docs/05-migrations/13-v8_1-to-v10.md b/docs/docs/05-migrations/13-v8_1-to-v10.md new file mode 100644 index 0000000..60cfcaf --- /dev/null +++ b/docs/docs/05-migrations/13-v8_1-to-v10.md @@ -0,0 +1,289 @@ +--- +title: IBC-Go v8.1 to v10 +sidebar_label: IBC-Go v8.1 to v10 +sidebar_position: 13 +slug: /migrations/v8_1-to-v10 +--- + +# Migrating from v8.1 to v10 + +This guide provides instructions for migrating to a new version of ibc-go. + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. In addition, for this release, the 08-wasm module has been released as v10, and the callbacks middleware has been moved into the ibc-go module itself. + +Diff examples are shown after the list of overall changes: + +- To add support for IBC v2, Chains will need to wire up a new IBC v2 Transfer stack +- Chains will need to wire up the new light client modules +- Chains will need to update Keeper construction calls to comply with the new signatures +- Chains will need to remove the route for the legacy proposal handler for 02-client from their `app/app.go` +- Chains will need to remove the capability keeper and all related setup, including the scoped keepers from their `app/app.go` +- Chains will need to remove ibc fee middleware (29-fee) +- Chains will need, if using this module, to update their imports and usage of `github.com/cosmos/ibc-go/modules/light-clients/08-wasm/` to `github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10` +- Chains will need, if using this module, to update their imports and usage of `github.com/cosmos/ibc-go/modules/apps/callbacks` to `github.com/cosmos/ibc-go/v10/modules/apps/callbacks` + +To add IBC v2 support, wire up a new transfer stack. Example below showing wired up with IBC Callbacks module: + +```diff ++ var ibcv2TransferStack ibcapi.IBCModule ++ ibcv2TransferStack = transferv2.NewIBCModule(app.TransferKeeper) ++ ibcv2TransferStack = ibccallbacksv2.NewIBCMiddleware( ++ transferv2.NewIBCModule(app.TransferKeeper), ++ app.IBCKeeper.ChannelKeeperV2, ++ wasmStackIBCHandler, ++ app.IBCKeeper.ChannelKeeperV2, ++ maxCallbackGas, ++ ) +``` + +Wire up each light client as a separate module and add them to the client keeper router. Example below for 07-tendermint and 08-wasm: + +```diff ++ // Light client modules ++ clientKeeper := app.IBCKeeper.ClientKeeper ++ storeProvider := app.IBCKeeper.ClientKeeper.GetStoreProvider() ++ ++ tmLightClientModule := ibctm.NewLightClientModule(appCodec, storeProvider) ++ clientKeeper.AddRoute(ibctm.ModuleName, &tmLightClientModule) ++ ++ wasmLightClientModule := ibcwasm.NewLightClientModule(app.WasmClientKeeper, storeProvider) ++ clientKeeper.AddRoute(ibcwasmtypes.ModuleName, &wasmLightClientModule) +``` + +Remove ibc fee module name (if used) from module account permissions: + +```diff + // app.go + ... + // module account permissions + var maccPerms = map[string][]string{ + ... +- ibcfeetypes.ModuleName: nil, + ... + } +``` + +Remove `CapabilityKeeper`, `IBCFeeKeeper` and all `capabilitykeeper.ScopedKeeper` Scoped keepers from the App struct: + +```diff + // ChainApp extended ABCI application + type ChainApp struct { + ... +- CapabilityKeeper *capabilitykeeper.Keeper + ... +- IBCFeeKeeper ibcfeekeeper.Keeper + ... +- ScopedIBCKeeper capabilitykeeper.ScopedKeeper +- ScopedICAHostKeeper capabilitykeeper.ScopedKeeper +- ScopedICAControllerKeeper capabilitykeeper.ScopedKeeper +- ScopedTransferKeeper capabilitykeeper.ScopedKeeper +- ScopedIBCFeeKeeper capabilitykeeper.ScopedKeeper + ... + } + ... +- app.ScopedIBCKeeper = scopedIBCKeeper +- app.ScopedTransferKeeper = scopedTransferKeeper +- app.ScopedWasmKeeper = scopedWasmKeeper +- app.ScopedICAHostKeeper = scopedICAHostKeeper +- app.ScopedICAControllerKeeper = scopedICAControllerKeeper +``` + +Remove capability and ibc fee middleware store keys from the `NewKVStoreKeys` call: + +```diff +... + keys := storetypes.NewKVStoreKeys( + ... +- capabilitytypes.StoreKey, +- ibcfeetypes.StoreKey, + ... + } +``` + +Remove the in-memory store keys previously used by the capability module: + +```diff +- memKeys := storetypes.NewMemoryStoreKeys(capabilitytypes.MemStoreKey) +... +- app.MountMemoryStores(memKeys) +``` + +Remove creation of the capability keeper: + +```diff +- // add capability keeper and ScopeToModule for ibc module +- app.CapabilityKeeper = capabilitykeeper.NewKeeper( +- appCodec, +- keys[capabilitytypes.StoreKey], +- memKeys[capabilitytypes.MemStoreKey], +- ) + +- scopedIBCKeeper := app.CapabilityKeeper.ScopeToModule(ibcexported.ModuleName) +- scopedICAHostKeeper := app.CapabilityKeeper.ScopeToModule(icahosttypes.SubModuleName) +- scopedICAControllerKeeper := app.CapabilityKeeper.ScopeToModule(icacontrollertypes.SubModuleName) +- scopedTransferKeeper := app.CapabilityKeeper.ScopeToModule(ibctransfertypes.ModuleName) +- scopedWasmKeeper := app.CapabilityKeeper.ScopeToModule(wasmtypes.ModuleName) +- app.CapabilityKeeper.Seal() +``` + +Remove the legacy route for the client keeper: + +```diff +... + govRouter.AddRoute(govtypes.RouterKey, govv1beta1.ProposalHandler). +- AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(app.ParamsKeeper)). +- AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)) ++ AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(app.ParamsKeeper)) +``` + +Update Core IBC Keeper constructor: + +```diff + app.IBCKeeper = ibckeeper.NewKeeper( + appCodec, +- keys[ibcexported.StoreKey], ++ runtime.NewKVStoreService(keys[ibcexported.StoreKey]), + app.GetSubspace(ibcexported.ModuleName), +- app.StakingKeeper, + app.UpgradeKeeper, +- scopedIBCKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) +``` + +Update IBC Transfer keeper constructor: + +```diff + app.TransferKeeper = ibctransferkeeper.NewKeeper( + appCodec, +- keys[ibctransfertypes.StoreKey], ++ runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]), + app.GetSubspace(ibctransfertypes.ModuleName), + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeper, +- app.IBCKeeper.PortKeeper, ++ app.MsgServiceRouter(), + app.AccountKeeper, + app.BankKeeper, +- scopedTransferKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) +``` + +Update ICA Host keeper constructor, notice the removal of the `WithQueryRouter` call in particular: + +```diff + app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, +- keys[icahosttypes.StoreKey], ++ runtime.NewKVStoreService(keys[icahosttypes.StoreKey]), + app.GetSubspace(icahosttypes.SubModuleName), +- app.IBCFeeKeeper, // use ics29 fee as ics4Wrapper in middleware stack + app.IBCKeeper.ChannelKeeper, +- app.IBCKeeper.PortKeeper, ++ app.IBCKeeper.ChannelKeeper, + app.AccountKeeper, +- scopedICAHostKeeper, + app.MsgServiceRouter(), ++ app.GRPCQueryRouter(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) +- app.ICAHostKeeper.WithQueryRouter(app.GRPCQueryRouter()) +``` + +Remove IBC Fee Module keeper: + +```diff +- app.IBCFeeKeeper = ibcfeekeeper.NewKeeper( +- appCodec, keys[ibcfeetypes.StoreKey], +- app.IBCKeeper.ChannelKeeper, // may be replaced with IBC middleware +- app.IBCKeeper.ChannelKeeper, +- app.IBCKeeper.PortKeeper, app.AccountKeeper, app.BankKeeper, +- ) +``` + +Update Transfer stack to remove the fee middleware. The example below shows the correct way to wire up a middleware stack with the IBC callbacks middleware: + +```diff + // Create Transfer Stack + var transferStack porttypes.IBCModule + transferStack = transfer.NewIBCModule(app.TransferKeeper) +- transferStack = ibccallbacks.NewIBCMiddleware(transferStack, app.IBCFeeKeeper, wasmStackIBCHandler, maxCallbackGas) +- transferStack = ibcfee.NewIBCMiddleware(transferStack, app.IBCFeeKeeper) ++ // callbacks wraps the transfer stack as its base app, and uses PacketForwardKeeper as the ICS4Wrapper ++ // i.e. packet-forward-middleware is higher on the stack and sits between callbacks and the ibc channel keeper ++ // Since this is the lowest level middleware of the transfer stack, it should be the first entrypoint for transfer keeper's ++ // WriteAcknowledgement. ++ cbStack := ibccallbacks.NewIBCMiddleware(transferStack, app.PacketForwardKeeper, wasmStackIBCHandler, maxCallbackGas) +transferStack = packetforward.NewIBCMiddleware( +- transferStack, ++ cbStack, + app.PacketForwardKeeper, + 0, + packetforwardkeeper.DefaultForwardTransferPacketTimeoutTimestamp, + ) ++ app.TransferKeeper.WithICS4Wrapper(cbStack) +``` + +Remove ibc fee middleware and any empty IBCModule (often dubbed `noAuthzModule`) from the ICA Controller stack creation: + +```diff +- var noAuthzModule porttypes.IBCModule +- icaControllerStack = icacontroller.NewIBCMiddleware(noAuthzModule, app.ICAControllerKeeper) +- icaControllerStack = ibcfee.NewIBCMiddleware(icaControllerStack, app.IBCFeeKeeper) ++ icaControllerStack = icacontroller.NewIBCMiddleware(app.ICAControllerKeeper) +``` + +Remove ibc fee middleware from ICA Host stack creation: + +```diff + icaHostStack = icahost.NewIBCModule(app.ICAHostKeeper) +- icaHostStack = ibcfee.NewIBCMiddleware(icaHostStack, app.IBCFeeKeeper) +``` + +Update the module manager creation by removing the capability module, fee module and updating the tendermint app module constructor: + +```diff + app.ModuleManager = module.NewManager( + ... +- capability.NewAppModule(appCodec, *app.CapabilityKeeper, false), + ... +- ibcfee.NewAppModule(app.IBCFeeKeeper), + ... +- ibctm.NewAppModule(), ++ ibctm.NewAppModule(tmLightClientModule), + ... + ) +``` + +Remove the capability module and ibc fee middleware from `SetOrderBeginBlockers`, `SetOrderEndBlockers`, `SetOrderInitGenesis` and `SetOrderExportGenesis`: + +```diff +- capabilitytypes.ModuleName, +- ibcfeetypes.ModuleName, +``` + +If you use 08-wasm, you will need to update the go module that is used for `QueryPlugins` and `AcceptListStargateQuerier`. + +```diff +- wasmLightClientQuerier := ibcwasmtypes.QueryPlugins{ ++ wasmLightClientQuerier := ibcwasmkeeper.QueryPlugins{ +- Stargate: ibcwasmtypes.AcceptListStargateQuerier([]string{ ++ Stargate: ibcwasmkeeper.AcceptListStargateQuerier([]string{ + "/ibc.core.client.v1.Query/ClientState", + "/ibc.core.client.v1.Query/ConsensusState", + "/ibc.core.connection.v1.Query/Connection", +- }), ++ }, app.GRPCQueryRouter()), + } +``` + +If you use 08-wasm, you will need to use the wasm client keeper rather than the go module to initialize pinned codes: + +```diff +- if err := ibcwasmkeeper.InitializePinnedCodes(ctx); err != nil { +- panic(fmt.Sprintf("ibcwasmkeeper failed initialize pinned codes %s", err)) ++ if err := app.WasmClientKeeper.InitializePinnedCodes(ctx); err != nil { ++ panic(fmt.Sprintf("WasmClientKeeper failed initialize pinned codes %s", err)) ++ } +``` diff --git a/docs/docs/05-migrations/_category_.json b/docs/docs/05-migrations/_category_.json new file mode 100644 index 0000000..354a84e --- /dev/null +++ b/docs/docs/05-migrations/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Migrations", + "position": 5, + "link": null +} \ No newline at end of file diff --git a/docs/docs/05-migrations/images/auth-module-decision-tree.png b/docs/docs/05-migrations/images/auth-module-decision-tree.png new file mode 100644 index 0000000..1122ddb Binary files /dev/null and b/docs/docs/05-migrations/images/auth-module-decision-tree.png differ diff --git a/docs/docs/05-migrations/migration.template.md b/docs/docs/05-migrations/migration.template.md new file mode 100644 index 0000000..182686e --- /dev/null +++ b/docs/docs/05-migrations/migration.template.md @@ -0,0 +1,28 @@ +# Migrating from \ to \ + +This guide provides instructions for migrating to a new version of ibc-go. + +There are four sections based on the four potential user groups of this document: + +- [Chains](#chains) +- [IBC Apps](#ibc-apps) +- [Relayers](#relayers) +- [IBC Light Clients](#ibc-light-clients) + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. + +## Chains + +- No relevant changes were made in this release. + +## IBC Apps + +- No relevant changes were made in this release. + +## Relayers + +- No relevant changes were made in this release. + +## IBC Light Clients + +- No relevant changes were made in this release. diff --git a/docs/docs/images/ibcoverview-dark.svg b/docs/docs/images/ibcoverview-dark.svg new file mode 100644 index 0000000..d8c12ed --- /dev/null +++ b/docs/docs/images/ibcoverview-dark.svg @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/docs/images/ibcoverview-light.svg b/docs/docs/images/ibcoverview-light.svg new file mode 100644 index 0000000..12e38b8 --- /dev/null +++ b/docs/docs/images/ibcoverview-light.svg @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js new file mode 100644 index 0000000..28fa5b3 --- /dev/null +++ b/docs/docusaurus.config.js @@ -0,0 +1,357 @@ +// @ts-check +// Note: type annotations allow type checking and IDEs autocompletion + +import { Highlight, themes } from "prism-react-renderer"; + +const lightCodeTheme = themes.github; +const darkCodeTheme = themes.dracula; + +/** @type {import('@docusaurus/types').Config} */ +const config = { + title: "IBC-Go", + tagline: "Documentation for IBC-Go", + favicon: "img/white-cosmos-icon.svg", + + // Set the production url of your site here + // for local production tests, set to http://localhost:3000/ + url: "https://ibc.cosmos.network", + // Set the // pathname under which your site is served + // For GitHub pages deployment, it is often '//' + baseUrl: "/", + + // GitHub pages deployment config. + // If you aren't using GitHub pages, you don't need these. + organizationName: "cosmos", // Usually your GitHub org/user name. + projectName: "ibc-go", // Usually your repo name. + deploymentBranch: "gh-pages", + trailingSlash: true, + + onBrokenLinks: "log", + onBrokenMarkdownLinks: "log", + + // Even if you don't use internalization, you can use this field to set useful + // metadata like html lang. For example, if your site is Chinese, you may want + // to replace "en" with "zh-Hans". + i18n: { + defaultLocale: "en", + locales: ["en"], + }, + + presets: [ + [ + "classic", + /** @type {import('@docusaurus/preset-classic').Options} */ + ({ + docs: { + sidebarPath: require.resolve("./sidebars.js"), + // Routed the docs to the root path + routeBasePath: "/", + // Exclude template markdown files from the docs + exclude: ["**/*.template.md"], + // Select the latest version + lastVersion: "v10.1.x", + // Assign banners to specific versions + versions: { + current: { + path: "main", + banner: "unreleased", + }, + "v10.1.x": { + path: "v10", + banner: "none", + }, + "v8.5.x": { + path: "v8", + banner: "none", + }, + "v7.8.x": { + path: "v7", + banner: "none", + }, + "v6.3.x": { + path: "v6", + banner: "none", + }, + "v5.4.x": { + path: "v5", + banner: "none", + }, + "v4.6.x": { + path: "v4", + banner: "none", + }, + }, + }, + theme: { + customCss: require.resolve("./src/css/custom.css"), + }, + gtag: { + trackingID: "G-HP8ZXWVLJG", + anonymizeIP: true, + }, + sitemap: { + changefreq: "weekly", + priority: 0.5, + filename: "sitemap.xml", + }, + }), + ], + ], + + themeConfig: + /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ + ({ + image: "img/ibc-go-docs-social-card.png", + navbar: { + logo: { + alt: "IBC Logo", + src: "img/black-ibc-logo.svg", + srcDark: "img/white-ibc-logo.svg", + href: "/", + }, + items: [ + { + type: "docSidebar", + sidebarId: "defaultSidebar", + position: "left", + label: "Documentation", + }, + { + type: "doc", + position: "left", + docId: "README", + docsPluginId: "adrs", + label: "Architecture Decision Records", + }, + { + type: "docsVersionDropdown", + position: "right", + dropdownActiveClassDisabled: true, + }, + { + href: "https://github.com/cosmos/ibc-go", + html: ` + + + `, + position: "right", + }, + ], + }, + footer: { + links: [ + { + items: [ + { + html: `Cosmos Logo`, + }, + ], + }, + { + title: "Documentation", + items: [ + { + label: "Hermes Relayer", + href: "https://hermes.informal.systems/", + }, + { + label: "Cosmos Hub", + href: "https://hub.cosmos.network", + }, + { + label: "CometBFT", + href: "https://docs.cometbft.com", + }, + ], + }, + { + title: "Community", + items: [ + { + label: "Discord", + href: "https://discord.com/invite/interchain", + }, + { + label: "Twitter", + href: "https://twitter.com/interchain_io", + }, + { + label: "YouTube", + href: "https://www.youtube.com/@interchain_io", + }, + ], + }, + { + title: "Other Tools", + items: [ + { + label: "Go Relayer", + href: "https://github.com/cosmos/relayer", + }, + { + label: "ibc-rs", + href: "https://github.com/cosmos/ibc-rs", + }, + { + label: "interchaintest", + href: "https://github.com/cosmos/interchaintest", + }, + { + label: "CosmWasm", + href: "https://cosmwasm.com/", + }, + ], + }, + { + title: "More", + items: [ + { + label: "GitHub", + href: "https://github.com/cosmos/ibc-go", + }, + { + label: "IBC Protocol Website", + href: "https://www.ibcprotocol.dev/", + }, + { + label: "Privacy Policy", + href: "https://v1.cosmos.network/privacy", + }, + ], + }, + ], + logo: { + alt: "Large IBC Logo", + src: "img/black-large-ibc-logo.svg", + srcDark: "img/white-large-ibc-logo.svg", + width: 275, + }, + copyright: `

The development of IBC-Go is led primarily by Interchain GmbH. Funding for this development comes primarily from the Interchain Foundation, a Swiss non-profit.

`, + }, + prism: { + theme: lightCodeTheme, + darkTheme: darkCodeTheme, + additionalLanguages: ["protobuf", "go", "go-module", "yaml", "toml", "diff"], + magicComments: [ + // Remember to extend the default highlight class name as well! + { + className: 'theme-code-block-highlighted-line', + line: 'highlight-next-line', + block: { start: 'highlight-start', end: 'highlight-end' }, + }, + { + className: 'code-block-minus-diff-line', + line: 'minus-diff-line', + block: { start: 'minus-diff-start', end: 'minus-diff-end' }, + }, + { + className: 'code-block-plus-diff-line', + line: 'plus-diff-line', + block: { start: 'plus-diff-start', end: 'plus-diff-end' }, + }, + ], + }, + }), + themes: ["docusaurus-theme-github-codeblock"], + plugins: [ + [ + 'docusaurus-pushfeedback', { + project: '3mpmaho4fa', + buttonPosition: 'center-right', + modalPosition: 'sidebar-right', + buttonStyle: 'dark', + } + ], + [ + "@docusaurus/plugin-content-docs", + { + id: "adrs", + path: "architecture", + routeBasePath: "architecture", + sidebarPath: require.resolve("./sidebars.js"), + exclude: ["**/*.template.md"], + }, + ], + [ + "@docusaurus/plugin-content-docs", + { + id: "events", + path: "events", + routeBasePath: "events", + sidebarPath: false, + exclude: ["**/*.template.md"], + }, + ], + [ + "@docusaurus/plugin-content-docs", + { + id: "params", + path: "params", + routeBasePath: "params", + sidebarPath: false, + exclude: ["**/*.template.md"], + }, + ], + [ + "@docusaurus/plugin-client-redirects", + { + // makes the default page next in production + redirects: [ + { + from: ["/master", "/next"], + to: "/main/", + }, + { + from: ["/", "/docs"], + to: "/v10/", + } + ], + }, + ], + [ + "@gracefullight/docusaurus-plugin-microsoft-clarity", + { projectId: "idk9udvhuu" }, + ], + [ + require.resolve("@easyops-cn/docusaurus-search-local"), + { + indexBlog: false, + docsRouteBasePath: ["/", "architecture"], + highlightSearchTermsOnTargetPage: true, + }, + ], + async function myPlugin(context, options) { + return { + name: "docusaurus-tailwindcss", + configurePostCss(postcssOptions) { + postcssOptions.plugins.push(require("postcss-import")); + postcssOptions.plugins.push(require("tailwindcss/nesting")); + postcssOptions.plugins.push(require("tailwindcss")); + postcssOptions.plugins.push(require("autoprefixer")); + return postcssOptions; + }, + }; + }, + ], + scripts: [ + { + src: "https://widget.kapa.ai/kapa-widget.bundle.js", + "data-website-id": "33cd677e-ea9e-437e-a3c1-dbacbe03afc1", + "data-project-name": "Interchain", + "data-user-analytics-fingerprint-enabled": "true", + "data-project-color": "#1878FF", + "data-modal-title": "IBC Docs AI", + "data-modal-disclaimer": "This is a custom LLM for the Interchain Stack with access to developer documentation and resources across the core Cosmos components. Please note that answers are generated by an AI so please use your best judgement before implementing.", + "data-modal-ask-ai-input-placeholder": "Ask me a question about IBC...", + "data-modal-disclaimer-text-color": "#000000", + "data-modal-disclaimer-font-size": "14px", + "data-modal-image": + "/img/black-ibc-logo-400x400.svg", + "data-project-logo": + "/img/spirograph-white.svg", + async: true, + }, + ], +}; + +module.exports = config; diff --git a/docs/events/events.md b/docs/events/events.md new file mode 100644 index 0000000..0135a20 --- /dev/null +++ b/docs/events/events.md @@ -0,0 +1,250 @@ +# Events + +:::danger +This document is unmaintained and may be out of date! +::: + +The IBC module emits the following events. It can be expected that the type `message`, +with an attribute key of `action` will represent the first event for each message +being processed as emitted by the SDK's baseapp. Each IBC TAO message will +also emit its module name in the format 'ibc_sub-modulename'. + +All the events for the Channel handshakes, `SendPacket`, `RecvPacket`, `AcknowledgePacket`, +`TimeoutPacket` and `TimeoutOnClose` will emit additional events not specified here due to +callbacks to IBC applications. + +## ICS 02 - Client + +### MsgCreateClient + +| Type | Attribute Key | Attribute Value | +| ------------- | ---------------- | ----------------- | +| create_client | client_id | \{clientId\} | +| create_client | client_type | \{clientType\} | +| create_client | consensus_height | \{consensusHeight\} | +| message | module | ibc_client | + +### MsgUpdateClient + +| Type | Attribute Key | Attribute Value | Status | +| ------------- | ----------------- | ------------------------------- | ---------- | +| update_client | client_id | \{clientId\} | | +| update_client | client_type | \{clientType\} | | +| update_client | consensus_height | \{consensusHeight\} | Deprecated | +| update_client | consensus_heights | \{join(consensusHeights, ",")\} | | +| message | module | ibc_client | | + +### MsgSubmitMisbehaviour + +| Type | Attribute Key | Attribute Value | +| ------------------- | ---------------- | ------------------- | +| client_misbehaviour | client_id | \{clientId\} | +| client_misbehaviour | client_type | \{clientType\} | +| client_misbehaviour | consensus_height | \{consensusHeight\} | +| message | module | evidence | +| message | sender | \{senderAddress\} | +| submit_evidence | evidence_hash | \{evidenceHash\} | + +### UpdateClientProposal + +| Type | Attribute Key | Attribute Value | +| ---------------------- | ---------------- | ----------------- | +| update_client_proposal | client_id | \{clientId\} | +| update_client_proposal | client_type | \{clientType\} | +| update_client_proposal | consensus_height | \{consensusHeight\} | + +### IBCSoftwareUpgrade + +| Type | Attribute Key | Attribute Value | +|-------------------------------|---------------------|---------------------------------| +| schedule_ibc_software_upgrade | title | \{title\} | +| schedule_ibc_software_upgrade | upgrade_plan_height | \{plan.height\} | + +## ICS 03 - Connection + +### MsgConnectionOpenInit + +| Type | Attribute Key | Attribute Value | +| -------------------- | ---------------------- | ----------------------- | +| connection_open_init | connection_id | \{connectionId\} | +| connection_open_init | client_id | \{clientId\} | +| connection_open_init | counterparty_client_id | \{counterparty.clientId\} | +| message | module | ibc_connection | + +### MsgConnectionOpenTry + +| Type | Attribute Key | Attribute Value | +| ------------------- | -------------------------- | --------------------------- | +| connection_open_try | connection_id | \{connectionId\} | +| connection_open_try | client_id | \{clientId\} | +| connection_open_try | counterparty_client_id | \{counterparty.clientId | +| connection_open_try | counterparty_connection_id | \{counterparty.connectionId\} | +| message | module | ibc_connection | + +### MsgConnectionOpenAck + +| Type | Attribute Key | Attribute Value | +| ------------------- | -------------------------- | --------------------------- | +| connection_open_ack | connection_id | \{connectionId\} | +| connection_open_ack | client_id | \{clientId\} | +| connection_open_ack | counterparty_client_id | \{counterparty.clientId\} | +| connection_open_ack | counterparty_connection_id | \{counterparty.connectionId\} | +| message | module | ibc_connection | + +### MsgConnectionOpenConfirm + +| Type | Attribute Key | Attribute Value | +| ----------------------- | -------------------------- | --------------------------- | +| connection_open_confirm | connection_id | \{connectionId\} | +| connection_open_confirm | client_id | \{clientId\} | +| connection_open_confirm | counterparty_client_id | \{counterparty.clientId\} | +| connection_open_confirm | counterparty_connection_id | \{counterparty.connectionId\} | +| message | module | ibc_connection | + +## ICS 04 - Channel + +### MsgChannelOpenInit + +| Type | Attribute Key | Attribute Value | +| ----------------- | -------------------- | ----------------------------- | +| channel_open_init | port_id | \{portId\} | +| channel_open_init | channel_id | \{channelId\} | +| channel_open_init | counterparty_port_id | \{channel.counterparty.portId\} | +| channel_open_init | connection_id | \{channel.connectionHops\} | +| message | module | ibc_channel | + +### MsgChannelOpenTry + +| Type | Attribute Key | Attribute Value | +| ---------------- | ----------------------- | -------------------------------- | +| channel_open_try | port_id | \{portId\} | +| channel_open_try | channel_id | \{channelId\} | +| channel_open_try | counterparty_port_id | \{channel.counterparty.portId\} | +| channel_open_try | counterparty_channel_id | \{channel.counterparty.channelId\} | +| channel_open_try | connection_id | \{channel.connectionHops\} | +| message | module | ibc_channel | + +### MsgChannelOpenAck + +| Type | Attribute Key | Attribute Value | +| ---------------- | ----------------------- | -------------------------------- | +| channel_open_ack | port_id | \{portId\} | +| channel_open_ack | channel_id | \{channelId\} | +| channel_open_ack | counterparty_port_id | \{channel.counterparty.portId\} | +| channel_open_ack | counterparty_channel_id | \{channel.counterparty.channelId\} | +| channel_open_ack | connection_id | \{channel.connectionHops\} | +| message | module | ibc_channel | + +### MsgChannelOpenConfirm + +| Type | Attribute Key | Attribute Value | +| -------------------- | ----------------------- | -------------------------------- | +| channel_open_confirm | port_id | \{portId\} | +| channel_open_confirm | channel_id | \{channelId\} | +| channel_open_confirm | counterparty_port_id | \{channel.counterparty.portId\} | +| channel_open_confirm | counterparty_channel_id | \{channel.counterparty.channelId\} | +| channel_open_confirm | connection_id | \{channel.connectionHops\} | +| message | module | ibc_channel | + +### MsgChannelCloseInit + +| Type | Attribute Key | Attribute Value | +| ------------------ | ----------------------- | -------------------------------- | +| channel_close_init | port_id | \{portId\} | +| channel_close_init | channel_id | \{channelId\} | +| channel_close_init | counterparty_port_id | \{channel.counterparty.portId\} | +| channel_close_init | counterparty_channel_id | \{channel.counterparty.channelId\} | +| channel_close_init | connection_id | \{channel.connectionHops\} | +| message | module | ibc_channel | + +### MsgChannelCloseConfirm + +| Type | Attribute Key | Attribute Value | +| --------------------- | ----------------------- | -------------------------------- | +| channel_close_confirm | port_id | \{portId\} | +| channel_close_confirm | channel_id | \{channelId\} | +| channel_close_confirm | counterparty_port_id | \{channel.counterparty.portId\} | +| channel_close_confirm | counterparty_channel_id | \{channel.counterparty.channelId\} | +| channel_close_confirm | connection_id | \{channel.connectionHops\} | +| message | module | ibc_channel | + +### SendPacket (application module call) + +| Type | Attribute Key | Attribute Value | Status | +| ----------- | ------------------------ | -------------------------------- | ---------- | +| send_packet | packet_data_hex | \{hex.Encode(data)\} | | +| send_packet | packet_timeout_height | \{timeoutHeight\} | | +| send_packet | packet_timeout_timestamp | \{timeoutTimestamp\} | | +| send_packet | packet_sequence | \{sequence\} | | +| send_packet | packet_src_port | \{sourcePort\} | | +| send_packet | packet_src_channel | \{sourceChannel\} | | +| send_packet | packet_dst_port | \{destinationPort\} | | +| send_packet | packet_dst_channel | \{destinationChannel\} | | +| send_packet | packet_channel_ordering | \{channel.Ordering\} | | +| send_packet | packet_connection | \{channel.ConnectionHops[0]\} | Deprecated | +| send_packet | connection_id | \{channel.ConnectionHops[0]\} | | +| message | module | ibc_channel | | + +### MsgRecvPacket + +| Type | Attribute Key | Attribute Value | Status | +| ----------- | ------------------------ | --------------------------- | ---------- | +| recv_packet | packet_data_hex | \{hex.Encode(data)\} | | +| recv_packet | packet_timeout_height | \{timeoutHeight\} | | +| recv_packet | packet_timeout_timestamp | \{timeoutTimestamp\} | | +| recv_packet | packet_sequence | \{sequence\} | | +| recv_packet | packet_src_port | \{sourcePort\} | | +| recv_packet | packet_src_channel | \{sourceChannel\} | | +| recv_packet | packet_dst_port | \{destinationPort\} | | +| recv_packet | packet_dst_channel | \{destinationChannel\} | | +| recv_packet | packet_channel_ordering | \{channel.Ordering\} | | +| recv_packet | packet_connection | \{channel.ConnectionHops[0]\} | Deprecated | +| recv_packet | connection_id | \{channel.ConnectionHops[0]\} | | +| message | module | ibc_channel | | + +| Type | Attribute Key | Attribute Value | Status | +| --------------------- | ------------------------ | --------------------------- | ---------- | +| write_acknowledgement | packet_data_hex | \{hex.Encode(data)\} | | +| write_acknowledgement | packet_timeout_height | \{timeoutHeight\} | | +| write_acknowledgement | packet_timeout_timestamp | \{timeoutTimestamp\} | | +| write_acknowledgement | packet_sequence | \{sequence\} | | +| write_acknowledgement | packet_src_port | \{sourcePort\} | | +| write_acknowledgement | packet_src_channel | \{sourceChannel\} | | +| write_acknowledgement | packet_dst_port | \{destinationPort\} | | +| write_acknowledgement | packet_dst_channel | \{destinationChannel\} | | +| write_acknowledgement | packet_ack_hex | \{hex.Encode(ack)\} | | +| write_acknowledgement | packet_channel_ordering | \{channel.Ordering\} | | +| write_acknowledgement | packet_connection | \{channel.ConnectionHops[0]\} | Deprecated | +| write_acknowledgement | connection_id | \{channel.ConnectionHops[0]\} | | +| message | module | ibc_channel | | + +### MsgAcknowledgePacket + +| Type | Attribute Key | Attribute Value | Status | +| ------------------ | ------------------------ | --------------------------- | ---------- | +| acknowledge_packet | packet_timeout_height | \{timeoutHeight\} | | +| acknowledge_packet | packet_timeout_timestamp | \{timeoutTimestamp\} | | +| acknowledge_packet | packet_sequence | \{sequence\} | | +| acknowledge_packet | packet_src_port | \{sourcePort\} | | +| acknowledge_packet | packet_src_channel | \{sourceChannel\} | | +| acknowledge_packet | packet_dst_port | \{destinationPort\} | | +| acknowledge_packet | packet_dst_channel | \{destinationChannel\} | | +| acknowledge_packet | packet_channel_ordering | \{channel.Ordering\} | | +| acknowledge_packet | packet_connection | \{channel.ConnectionHops[0]\} | Deprecated | +| acknowledge_packet | connection_id | \{channel.ConnectionHops[0]\} | | +| message | module | ibc_channel | | + +### MsgTimeoutPacket & MsgTimeoutOnClose + +| Type | Attribute Key | Attribute Value | +| -------------- | ------------------------ | --------------------------- | +| timeout_packet | packet_timeout_height | \{timeoutHeight\} | +| timeout_packet | packet_timeout_timestamp | \{timeoutTimestamp\} | +| timeout_packet | packet_sequence | \{sequence\} | +| timeout_packet | packet_src_port | \{sourcePort\} | +| timeout_packet | packet_src_channel | \{sourceChannel\} | +| timeout_packet | packet_dst_port | \{destinationPort\} | +| timeout_packet | packet_dst_channel | \{destinationChannel\} | +| timeout_packet | packet_channel_ordering | \{channel.Ordering\} | +| timeout_packet | connection_id | \{channel.ConnectionHops[0]\} | +| message | module | ibc_channel | diff --git a/docs/package-lock.json b/docs/package-lock.json new file mode 100644 index 0000000..7d6f1c4 --- /dev/null +++ b/docs/package-lock.json @@ -0,0 +1,15565 @@ +{ + "name": "docs", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "docs", + "version": "0.0.0", + "dependencies": { + "@docusaurus/core": "^3.4.0", + "@docusaurus/plugin-client-redirects": "^3.4.0", + "@docusaurus/plugin-content-docs": "^3.4.0", + "@docusaurus/plugin-google-gtag": "^3.4.0", + "@docusaurus/plugin-sitemap": "^3.4.0", + "@docusaurus/preset-classic": "^3.4.0", + "@easyops-cn/docusaurus-search-local": "^0.40.1", + "@gracefullight/docusaurus-plugin-microsoft-clarity": "^1.0.0", + "@mdx-js/react": "^3.0.0", + "autoprefixer": "^10.4.16", + "clsx": "^2.1.0", + "docusaurus-pushfeedback": "^1.0.0", + "docusaurus-theme-github-codeblock": "^2.0.1", + "postcss": "^8.4.32", + "postcss-import": "^16.0.0", + "prism-react-renderer": "^2.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "tailwindcss": "^3.4.0" + }, + "devDependencies": { + "@docusaurus/module-type-aliases": "^3.3.2" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", + "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", + "@algolia/autocomplete-shared": "1.9.3" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", + "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.9.3" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", + "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.9.3" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", + "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/cache-browser-local-storage": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.23.3.tgz", + "integrity": "sha512-vRHXYCpPlTDE7i6UOy2xE03zHF2C8MEFjPN2v7fRbqVpcOvAUQK81x3Kc21xyb5aSIpYCjWCZbYZuz8Glyzyyg==", + "license": "MIT", + "dependencies": { + "@algolia/cache-common": "4.23.3" + } + }, + "node_modules/@algolia/cache-common": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.23.3.tgz", + "integrity": "sha512-h9XcNI6lxYStaw32pHpB1TMm0RuxphF+Ik4o7tcQiodEdpKK+wKufY6QXtba7t3k8eseirEMVB83uFFF3Nu54A==", + "license": "MIT" + }, + "node_modules/@algolia/cache-in-memory": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.23.3.tgz", + "integrity": "sha512-yvpbuUXg/+0rbcagxNT7un0eo3czx2Uf0y4eiR4z4SD7SiptwYTpbuS0IHxcLHG3lq22ukx1T6Kjtk/rT+mqNg==", + "license": "MIT", + "dependencies": { + "@algolia/cache-common": "4.23.3" + } + }, + "node_modules/@algolia/client-account": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.23.3.tgz", + "integrity": "sha512-hpa6S5d7iQmretHHF40QGq6hz0anWEHGlULcTIT9tbUssWUriN9AUXIFQ8Ei4w9azD0hc1rUok9/DeQQobhQMA==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.23.3", + "@algolia/client-search": "4.23.3", + "@algolia/transporter": "4.23.3" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.23.3.tgz", + "integrity": "sha512-LBsEARGS9cj8VkTAVEZphjxTjMVCci+zIIiRhpFun9jGDUlS1XmhCW7CTrnaWeIuCQS/2iPyRqSy1nXPjcBLRA==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.23.3", + "@algolia/client-search": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/transporter": "4.23.3" + } + }, + "node_modules/@algolia/client-common": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.23.3.tgz", + "integrity": "sha512-l6EiPxdAlg8CYhroqS5ybfIczsGUIAC47slLPOMDeKSVXYG1n0qGiz4RjAHLw2aD0xzh2EXZ7aRguPfz7UKDKw==", + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.23.3", + "@algolia/transporter": "4.23.3" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.23.3.tgz", + "integrity": "sha512-3E3yF3Ocr1tB/xOZiuC3doHQBQ2zu2MPTYZ0d4lpfWads2WTKG7ZzmGnsHmm63RflvDeLK/UVx7j2b3QuwKQ2g==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/transporter": "4.23.3" + } + }, + "node_modules/@algolia/client-search": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.23.3.tgz", + "integrity": "sha512-P4VAKFHqU0wx9O+q29Q8YVuaowaZ5EM77rxfmGnkHUJggh28useXQdopokgwMeYw2XUht49WX5RcTQ40rZIabw==", + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/transporter": "4.23.3" + } + }, + "node_modules/@algolia/events": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz", + "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==", + "license": "MIT" + }, + "node_modules/@algolia/logger-common": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.23.3.tgz", + "integrity": "sha512-y9kBtmJwiZ9ZZ+1Ek66P0M68mHQzKRxkW5kAAXYN/rdzgDN0d2COsViEFufxJ0pb45K4FRcfC7+33YB4BLrZ+g==", + "license": "MIT" + }, + "node_modules/@algolia/logger-console": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.23.3.tgz", + "integrity": "sha512-8xoiseoWDKuCVnWP8jHthgaeobDLolh00KJAdMe9XPrWPuf1by732jSpgy2BlsLTaT9m32pHI8CRfrOqQzHv3A==", + "license": "MIT", + "dependencies": { + "@algolia/logger-common": "4.23.3" + } + }, + "node_modules/@algolia/recommend": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.23.3.tgz", + "integrity": "sha512-9fK4nXZF0bFkdcLBRDexsnGzVmu4TSYZqxdpgBW2tEyfuSSY54D4qSRkLmNkrrz4YFvdh2GM1gA8vSsnZPR73w==", + "license": "MIT", + "dependencies": { + "@algolia/cache-browser-local-storage": "4.23.3", + "@algolia/cache-common": "4.23.3", + "@algolia/cache-in-memory": "4.23.3", + "@algolia/client-common": "4.23.3", + "@algolia/client-search": "4.23.3", + "@algolia/logger-common": "4.23.3", + "@algolia/logger-console": "4.23.3", + "@algolia/requester-browser-xhr": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/requester-node-http": "4.23.3", + "@algolia/transporter": "4.23.3" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.23.3.tgz", + "integrity": "sha512-jDWGIQ96BhXbmONAQsasIpTYWslyjkiGu0Quydjlowe+ciqySpiDUrJHERIRfELE5+wFc7hc1Q5hqjGoV7yghw==", + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.23.3" + } + }, + "node_modules/@algolia/requester-common": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.23.3.tgz", + "integrity": "sha512-xloIdr/bedtYEGcXCiF2muajyvRhwop4cMZo+K2qzNht0CMzlRkm8YsDdj5IaBhshqfgmBb3rTg4sL4/PpvLYw==", + "license": "MIT" + }, + "node_modules/@algolia/requester-node-http": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.23.3.tgz", + "integrity": "sha512-zgu++8Uj03IWDEJM3fuNl34s746JnZOWn1Uz5taV1dFyJhVM/kTNw9Ik7YJWiUNHJQXcaD8IXD1eCb0nq/aByA==", + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.23.3" + } + }, + "node_modules/@algolia/transporter": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.23.3.tgz", + "integrity": "sha512-Wjl5gttqnf/gQKJA+dafnD0Y6Yw97yvfY8R9h0dQltX1GXTgNs1zWgvtWW0tHl1EgMdhAyw189uWiZMnL3QebQ==", + "license": "MIT", + "dependencies": { + "@algolia/cache-common": "4.23.3", + "@algolia/logger-common": "4.23.3", + "@algolia/requester-common": "4.23.3" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz", + "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.7", + "@babel/parser": "^7.23.6", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "dependencies": { + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.7.tgz", + "integrity": "sha512-xCoqR/8+BoNnXOY7RVSgv6X+o7pmT5q1d+gGcRlXYkI+9B31glE4jeejhKVpA04O1AtzOt7OSQ6VYKP5FcRl9g==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz", + "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "dependencies": { + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", + "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", + "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", + "dependencies": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.15", + "@babel/types": "^7.22.19" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.7.tgz", + "integrity": "sha512-6AMnjCoC8wjqBzDHkuqpa7jAKwvMo4dC+lr/TFBz+ucfulO1XMpDnwWPGBNwClOKZ8h6xn5N81W/R5OrcKtCbQ==", + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", + "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz", + "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", + "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz", + "integrity": "sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", + "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", + "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", + "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", + "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", + "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.7.tgz", + "integrity": "sha512-PdxEpL71bJp1byMG0va5gwQcXHxuEYC/BgI/e88mGTtohbZN28O5Yit0Plkkm/dBzCF/BxmbNcses1RH1T+urA==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.23.3.tgz", + "integrity": "sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==", + "dependencies": { + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", + "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", + "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", + "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", + "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz", + "integrity": "sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", + "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", + "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", + "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", + "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", + "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", + "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", + "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz", + "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", + "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", + "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", + "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", + "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", + "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", + "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", + "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz", + "integrity": "sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==", + "dependencies": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", + "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", + "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", + "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", + "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", + "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", + "dependencies": { + "@babel/compat-data": "^7.23.3", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", + "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", + "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", + "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", + "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", + "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", + "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", + "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-constant-elements": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.24.1.tgz", + "integrity": "sha512-QXp1U9x0R7tkiGB0FOk8o74jhnap0FlZ5gNkRIWdG3eP+SvMFg118e1zaWewDzgABb106QSKpVsD3Wgd8t6ifA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.23.3.tgz", + "integrity": "sha512-GnvhtVfA2OAtzdX58FJxU19rhoGeQzyVndw3GgtdECQvQFXPEZIOVULHVZGAYmOgmqjXpVpfocAbSjh99V/Fqw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.23.4.tgz", + "integrity": "sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.23.3", + "@babel/types": "^7.23.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz", + "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.23.3.tgz", + "integrity": "sha512-qMFdSS+TUhB7Q/3HVPnEdYJDQIk57jkntAwSuz9xfSE4n+3I+vHYCli3HoHawN1Z3RfCz/y1zXA/JXjG6cVImQ==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", + "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", + "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.23.7.tgz", + "integrity": "sha512-fa0hnfmiXc9fq/weK34MUV0drz2pOL/vfKWvN7Qw127hiUPabFCUMgAbYWcchRzMJit4o5ARsK/s+5h0249pLw==", + "dependencies": { + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "babel-plugin-polyfill-corejs2": "^0.4.7", + "babel-plugin-polyfill-corejs3": "^0.8.7", + "babel-plugin-polyfill-regenerator": "^0.5.4", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", + "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", + "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", + "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", + "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", + "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.6.tgz", + "integrity": "sha512-6cBG5mBvUu4VUD04OHKnYzbuHNP8huDsD3EDqqpIpsswTDoqHCjLoHb6+QgsV1WsT2nipRqCPgxD3LXnEO7XfA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.23.6", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-typescript": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", + "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", + "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", + "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", + "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.7.tgz", + "integrity": "sha512-SY27X/GtTz/L4UryMNJ6p4fH4nsgWbz84y9FE0bQeWJP6O5BhgVCt53CotQKHCOeXJel8VyhlhujhlltKms/CA==", + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.23.3", + "@babel/plugin-syntax-import-attributes": "^7.23.3", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.23.3", + "@babel/plugin-transform-async-generator-functions": "^7.23.7", + "@babel/plugin-transform-async-to-generator": "^7.23.3", + "@babel/plugin-transform-block-scoped-functions": "^7.23.3", + "@babel/plugin-transform-block-scoping": "^7.23.4", + "@babel/plugin-transform-class-properties": "^7.23.3", + "@babel/plugin-transform-class-static-block": "^7.23.4", + "@babel/plugin-transform-classes": "^7.23.5", + "@babel/plugin-transform-computed-properties": "^7.23.3", + "@babel/plugin-transform-destructuring": "^7.23.3", + "@babel/plugin-transform-dotall-regex": "^7.23.3", + "@babel/plugin-transform-duplicate-keys": "^7.23.3", + "@babel/plugin-transform-dynamic-import": "^7.23.4", + "@babel/plugin-transform-exponentiation-operator": "^7.23.3", + "@babel/plugin-transform-export-namespace-from": "^7.23.4", + "@babel/plugin-transform-for-of": "^7.23.6", + "@babel/plugin-transform-function-name": "^7.23.3", + "@babel/plugin-transform-json-strings": "^7.23.4", + "@babel/plugin-transform-literals": "^7.23.3", + "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", + "@babel/plugin-transform-member-expression-literals": "^7.23.3", + "@babel/plugin-transform-modules-amd": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-modules-systemjs": "^7.23.3", + "@babel/plugin-transform-modules-umd": "^7.23.3", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.23.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", + "@babel/plugin-transform-numeric-separator": "^7.23.4", + "@babel/plugin-transform-object-rest-spread": "^7.23.4", + "@babel/plugin-transform-object-super": "^7.23.3", + "@babel/plugin-transform-optional-catch-binding": "^7.23.4", + "@babel/plugin-transform-optional-chaining": "^7.23.4", + "@babel/plugin-transform-parameters": "^7.23.3", + "@babel/plugin-transform-private-methods": "^7.23.3", + "@babel/plugin-transform-private-property-in-object": "^7.23.4", + "@babel/plugin-transform-property-literals": "^7.23.3", + "@babel/plugin-transform-regenerator": "^7.23.3", + "@babel/plugin-transform-reserved-words": "^7.23.3", + "@babel/plugin-transform-shorthand-properties": "^7.23.3", + "@babel/plugin-transform-spread": "^7.23.3", + "@babel/plugin-transform-sticky-regex": "^7.23.3", + "@babel/plugin-transform-template-literals": "^7.23.3", + "@babel/plugin-transform-typeof-symbol": "^7.23.3", + "@babel/plugin-transform-unicode-escapes": "^7.23.3", + "@babel/plugin-transform-unicode-property-regex": "^7.23.3", + "@babel/plugin-transform-unicode-regex": "^7.23.3", + "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.7", + "babel-plugin-polyfill-corejs3": "^0.8.7", + "babel-plugin-polyfill-regenerator": "^0.5.4", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.23.3.tgz", + "integrity": "sha512-tbkHOS9axH6Ysf2OUEqoSZ6T3Fa2SrNH6WTWSPBboxKzdxNc9qOICeLXkNG0ZEwbQ1HY8liwOce4aN/Ceyuq6w==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-transform-react-display-name": "^7.23.3", + "@babel/plugin-transform-react-jsx": "^7.22.15", + "@babel/plugin-transform-react-jsx-development": "^7.22.5", + "@babel/plugin-transform-react-pure-annotations": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.23.3.tgz", + "integrity": "sha512-17oIGVlqz6CchO9RFYn5U6ZpWRZIngayYCtrPRSgANSwC2V1Jb+iP74nVxzzXJte8b8BYxrL1yY96xfhTBrNNQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.15", + "@babel/plugin-syntax-jsx": "^7.23.3", + "@babel/plugin-transform-modules-commonjs": "^7.23.3", + "@babel/plugin-transform-typescript": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" + }, + "node_modules/@babel/runtime": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.7.tgz", + "integrity": "sha512-w06OXVOFso7LcbzMiDGt+3X7Rh7Ho8MmgPoWU3rarH+8upf+wSU/grlGbWzQyr3DkdN6ZeuMFjpdwW0Q+HxobA==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.23.7.tgz", + "integrity": "sha512-ER55qzLREVA5YxeyQ3Qu48tgsF2ZrFjFjUS6V6wF0cikSw+goBJgB9PBRM1T6+Ah4iiM+sxmfS/Sy/jdzFfhiQ==", + "dependencies": { + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", + "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.6", + "@babel/types": "^7.23.6", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", + "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@docsearch/css": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.0.tgz", + "integrity": "sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==", + "license": "MIT" + }, + "node_modules/@docsearch/react": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.0.tgz", + "integrity": "sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "1.9.3", + "@algolia/autocomplete-preset-algolia": "1.9.3", + "@docsearch/css": "3.6.0", + "algoliasearch": "^4.19.1" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, + "node_modules/@docusaurus/core": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.4.0.tgz", + "integrity": "sha512-g+0wwmN2UJsBqy2fQRQ6fhXruoEa62JDeEa5d8IdTJlMoaDaEDfHh7WjwGRn4opuTQWpjAwP/fbcgyHKlE+64w==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.3", + "@babel/generator": "^7.23.3", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-runtime": "^7.22.9", + "@babel/preset-env": "^7.22.9", + "@babel/preset-react": "^7.22.5", + "@babel/preset-typescript": "^7.22.5", + "@babel/runtime": "^7.22.6", + "@babel/runtime-corejs3": "^7.22.6", + "@babel/traverse": "^7.22.8", + "@docusaurus/cssnano-preset": "3.4.0", + "@docusaurus/logger": "3.4.0", + "@docusaurus/mdx-loader": "3.4.0", + "@docusaurus/utils": "3.4.0", + "@docusaurus/utils-common": "3.4.0", + "@docusaurus/utils-validation": "3.4.0", + "autoprefixer": "^10.4.14", + "babel-loader": "^9.1.3", + "babel-plugin-dynamic-import-node": "^2.3.3", + "boxen": "^6.2.1", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "clean-css": "^5.3.2", + "cli-table3": "^0.6.3", + "combine-promises": "^1.1.0", + "commander": "^5.1.0", + "copy-webpack-plugin": "^11.0.0", + "core-js": "^3.31.1", + "css-loader": "^6.8.1", + "css-minimizer-webpack-plugin": "^5.0.1", + "cssnano": "^6.1.2", + "del": "^6.1.1", + "detect-port": "^1.5.1", + "escape-html": "^1.0.3", + "eta": "^2.2.0", + "eval": "^0.1.8", + "file-loader": "^6.2.0", + "fs-extra": "^11.1.1", + "html-minifier-terser": "^7.2.0", + "html-tags": "^3.3.1", + "html-webpack-plugin": "^5.5.3", + "leven": "^3.1.0", + "lodash": "^4.17.21", + "mini-css-extract-plugin": "^2.7.6", + "p-map": "^4.0.0", + "postcss": "^8.4.26", + "postcss-loader": "^7.3.3", + "prompts": "^2.4.2", + "react-dev-utils": "^12.0.1", + "react-helmet-async": "^1.3.0", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", + "react-loadable-ssr-addon-v5-slorber": "^1.0.1", + "react-router": "^5.3.4", + "react-router-config": "^5.1.1", + "react-router-dom": "^5.3.4", + "rtl-detect": "^1.0.4", + "semver": "^7.5.4", + "serve-handler": "^6.1.5", + "shelljs": "^0.8.5", + "terser-webpack-plugin": "^5.3.9", + "tslib": "^2.6.0", + "update-notifier": "^6.0.2", + "url-loader": "^4.1.1", + "webpack": "^5.88.1", + "webpack-bundle-analyzer": "^4.9.0", + "webpack-dev-server": "^4.15.1", + "webpack-merge": "^5.9.0", + "webpackbar": "^5.0.2" + }, + "bin": { + "docusaurus": "bin/docusaurus.mjs" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@docusaurus/cssnano-preset": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.4.0.tgz", + "integrity": "sha512-qwLFSz6v/pZHy/UP32IrprmH5ORce86BGtN0eBtG75PpzQJAzp9gefspox+s8IEOr0oZKuQ/nhzZ3xwyc3jYJQ==", + "license": "MIT", + "dependencies": { + "cssnano-preset-advanced": "^6.1.2", + "postcss": "^8.4.38", + "postcss-sort-media-queries": "^5.2.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@docusaurus/logger": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.4.0.tgz", + "integrity": "sha512-bZwkX+9SJ8lB9kVRkXw+xvHYSMGG4bpYHKGXeXFvyVc79NMeeBSGgzd4TQLHH+DYeOJoCdl8flrFJVxlZ0wo/Q==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@docusaurus/mdx-loader": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.4.0.tgz", + "integrity": "sha512-kSSbrrk4nTjf4d+wtBA9H+FGauf2gCax89kV8SUSJu3qaTdSIKdWERlngsiHaCFgZ7laTJ8a67UFf+xlFPtuTw==", + "license": "MIT", + "dependencies": { + "@docusaurus/logger": "3.4.0", + "@docusaurus/utils": "3.4.0", + "@docusaurus/utils-validation": "3.4.0", + "@mdx-js/mdx": "^3.0.0", + "@slorber/remark-comment": "^1.0.0", + "escape-html": "^1.0.3", + "estree-util-value-to-estree": "^3.0.1", + "file-loader": "^6.2.0", + "fs-extra": "^11.1.1", + "image-size": "^1.0.2", + "mdast-util-mdx": "^3.0.0", + "mdast-util-to-string": "^4.0.0", + "rehype-raw": "^7.0.0", + "remark-directive": "^3.0.0", + "remark-emoji": "^4.0.0", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.0", + "stringify-object": "^3.3.0", + "tslib": "^2.6.0", + "unified": "^11.0.3", + "unist-util-visit": "^5.0.0", + "url-loader": "^4.1.1", + "vfile": "^6.0.1", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@docusaurus/module-type-aliases": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.4.0.tgz", + "integrity": "sha512-A1AyS8WF5Bkjnb8s+guTDuYmUiwJzNrtchebBHpc0gz0PyHJNMaybUlSrmJjHVcGrya0LKI4YcR3lBDQfXRYLw==", + "license": "MIT", + "dependencies": { + "@docusaurus/types": "3.4.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router-config": "*", + "@types/react-router-dom": "*", + "react-helmet-async": "*", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@docusaurus/plugin-client-redirects": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-3.4.0.tgz", + "integrity": "sha512-Pr8kyh/+OsmYCvdZhc60jy/FnrY6flD2TEAhl4rJxeVFxnvvRgEhoaIVX8q9MuJmaQoh6frPk94pjs7/6YgBDQ==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.4.0", + "@docusaurus/logger": "3.4.0", + "@docusaurus/utils": "3.4.0", + "@docusaurus/utils-common": "3.4.0", + "@docusaurus/utils-validation": "3.4.0", + "eta": "^2.2.0", + "fs-extra": "^11.1.1", + "lodash": "^4.17.21", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@docusaurus/plugin-content-blog": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.4.0.tgz", + "integrity": "sha512-vv6ZAj78ibR5Jh7XBUT4ndIjmlAxkijM3Sx5MAAzC1gyv0vupDQNhzuFg1USQmQVj3P5I6bquk12etPV3LJ+Xw==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.4.0", + "@docusaurus/logger": "3.4.0", + "@docusaurus/mdx-loader": "3.4.0", + "@docusaurus/types": "3.4.0", + "@docusaurus/utils": "3.4.0", + "@docusaurus/utils-common": "3.4.0", + "@docusaurus/utils-validation": "3.4.0", + "cheerio": "^1.0.0-rc.12", + "feed": "^4.2.2", + "fs-extra": "^11.1.1", + "lodash": "^4.17.21", + "reading-time": "^1.5.0", + "srcset": "^4.0.0", + "tslib": "^2.6.0", + "unist-util-visit": "^5.0.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@docusaurus/plugin-content-docs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.4.0.tgz", + "integrity": "sha512-HkUCZffhBo7ocYheD9oZvMcDloRnGhBMOZRyVcAQRFmZPmNqSyISlXA1tQCIxW+r478fty97XXAGjNYzBjpCsg==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.4.0", + "@docusaurus/logger": "3.4.0", + "@docusaurus/mdx-loader": "3.4.0", + "@docusaurus/module-type-aliases": "3.4.0", + "@docusaurus/types": "3.4.0", + "@docusaurus/utils": "3.4.0", + "@docusaurus/utils-common": "3.4.0", + "@docusaurus/utils-validation": "3.4.0", + "@types/react-router-config": "^5.0.7", + "combine-promises": "^1.1.0", + "fs-extra": "^11.1.1", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "tslib": "^2.6.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@docusaurus/plugin-content-pages": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.4.0.tgz", + "integrity": "sha512-h2+VN/0JjpR8fIkDEAoadNjfR3oLzB+v1qSXbIAKjQ46JAHx3X22n9nqS+BWSQnTnp1AjkjSvZyJMekmcwxzxg==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.4.0", + "@docusaurus/mdx-loader": "3.4.0", + "@docusaurus/types": "3.4.0", + "@docusaurus/utils": "3.4.0", + "@docusaurus/utils-validation": "3.4.0", + "fs-extra": "^11.1.1", + "tslib": "^2.6.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@docusaurus/plugin-debug": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.4.0.tgz", + "integrity": "sha512-uV7FDUNXGyDSD3PwUaf5YijX91T5/H9SX4ErEcshzwgzWwBtK37nUWPU3ZLJfeTavX3fycTOqk9TglpOLaWkCg==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.4.0", + "@docusaurus/types": "3.4.0", + "@docusaurus/utils": "3.4.0", + "fs-extra": "^11.1.1", + "react-json-view-lite": "^1.2.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@docusaurus/plugin-google-analytics": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.4.0.tgz", + "integrity": "sha512-mCArluxEGi3cmYHqsgpGGt3IyLCrFBxPsxNZ56Mpur0xSlInnIHoeLDH7FvVVcPJRPSQ9/MfRqLsainRw+BojA==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.4.0", + "@docusaurus/types": "3.4.0", + "@docusaurus/utils-validation": "3.4.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@docusaurus/plugin-google-gtag": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.4.0.tgz", + "integrity": "sha512-Dsgg6PLAqzZw5wZ4QjUYc8Z2KqJqXxHxq3vIoyoBWiLEEfigIs7wHR+oiWUQy3Zk9MIk6JTYj7tMoQU0Jm3nqA==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.4.0", + "@docusaurus/types": "3.4.0", + "@docusaurus/utils-validation": "3.4.0", + "@types/gtag.js": "^0.0.12", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@docusaurus/plugin-google-tag-manager": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.4.0.tgz", + "integrity": "sha512-O9tX1BTwxIhgXpOLpFDueYA9DWk69WCbDRrjYoMQtFHSkTyE7RhNgyjSPREUWJb9i+YUg3OrsvrBYRl64FCPCQ==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.4.0", + "@docusaurus/types": "3.4.0", + "@docusaurus/utils-validation": "3.4.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@docusaurus/plugin-sitemap": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.4.0.tgz", + "integrity": "sha512-+0VDvx9SmNrFNgwPoeoCha+tRoAjopwT0+pYO1xAbyLcewXSemq+eLxEa46Q1/aoOaJQ0qqHELuQM7iS2gp33Q==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.4.0", + "@docusaurus/logger": "3.4.0", + "@docusaurus/types": "3.4.0", + "@docusaurus/utils": "3.4.0", + "@docusaurus/utils-common": "3.4.0", + "@docusaurus/utils-validation": "3.4.0", + "fs-extra": "^11.1.1", + "sitemap": "^7.1.1", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@docusaurus/preset-classic": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.4.0.tgz", + "integrity": "sha512-Ohj6KB7siKqZaQhNJVMBBUzT3Nnp6eTKqO+FXO3qu/n1hJl3YLwVKTWBg28LF7MWrKu46UuYavwMRxud0VyqHg==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.4.0", + "@docusaurus/plugin-content-blog": "3.4.0", + "@docusaurus/plugin-content-docs": "3.4.0", + "@docusaurus/plugin-content-pages": "3.4.0", + "@docusaurus/plugin-debug": "3.4.0", + "@docusaurus/plugin-google-analytics": "3.4.0", + "@docusaurus/plugin-google-gtag": "3.4.0", + "@docusaurus/plugin-google-tag-manager": "3.4.0", + "@docusaurus/plugin-sitemap": "3.4.0", + "@docusaurus/theme-classic": "3.4.0", + "@docusaurus/theme-common": "3.4.0", + "@docusaurus/theme-search-algolia": "3.4.0", + "@docusaurus/types": "3.4.0" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@docusaurus/theme-classic": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.4.0.tgz", + "integrity": "sha512-0IPtmxsBYv2adr1GnZRdMkEQt1YW6tpzrUPj02YxNpvJ5+ju4E13J5tB4nfdaen/tfR1hmpSPlTFPvTf4kwy8Q==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.4.0", + "@docusaurus/mdx-loader": "3.4.0", + "@docusaurus/module-type-aliases": "3.4.0", + "@docusaurus/plugin-content-blog": "3.4.0", + "@docusaurus/plugin-content-docs": "3.4.0", + "@docusaurus/plugin-content-pages": "3.4.0", + "@docusaurus/theme-common": "3.4.0", + "@docusaurus/theme-translations": "3.4.0", + "@docusaurus/types": "3.4.0", + "@docusaurus/utils": "3.4.0", + "@docusaurus/utils-common": "3.4.0", + "@docusaurus/utils-validation": "3.4.0", + "@mdx-js/react": "^3.0.0", + "clsx": "^2.0.0", + "copy-text-to-clipboard": "^3.2.0", + "infima": "0.2.0-alpha.43", + "lodash": "^4.17.21", + "nprogress": "^0.2.0", + "postcss": "^8.4.26", + "prism-react-renderer": "^2.3.0", + "prismjs": "^1.29.0", + "react-router-dom": "^5.3.4", + "rtlcss": "^4.1.0", + "tslib": "^2.6.0", + "utility-types": "^3.10.0" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@docusaurus/theme-common": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.4.0.tgz", + "integrity": "sha512-0A27alXuv7ZdCg28oPE8nH/Iz73/IUejVaCazqu9elS4ypjiLhK3KfzdSQBnL/g7YfHSlymZKdiOHEo8fJ0qMA==", + "license": "MIT", + "dependencies": { + "@docusaurus/mdx-loader": "3.4.0", + "@docusaurus/module-type-aliases": "3.4.0", + "@docusaurus/plugin-content-blog": "3.4.0", + "@docusaurus/plugin-content-docs": "3.4.0", + "@docusaurus/plugin-content-pages": "3.4.0", + "@docusaurus/utils": "3.4.0", + "@docusaurus/utils-common": "3.4.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router-config": "*", + "clsx": "^2.0.0", + "parse-numeric-range": "^1.3.0", + "prism-react-renderer": "^2.3.0", + "tslib": "^2.6.0", + "utility-types": "^3.10.0" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@docusaurus/theme-search-algolia": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.4.0.tgz", + "integrity": "sha512-aiHFx7OCw4Wck1z6IoShVdUWIjntC8FHCw9c5dR8r3q4Ynh+zkS8y2eFFunN/DL6RXPzpnvKCg3vhLQYJDmT9Q==", + "license": "MIT", + "dependencies": { + "@docsearch/react": "^3.5.2", + "@docusaurus/core": "3.4.0", + "@docusaurus/logger": "3.4.0", + "@docusaurus/plugin-content-docs": "3.4.0", + "@docusaurus/theme-common": "3.4.0", + "@docusaurus/theme-translations": "3.4.0", + "@docusaurus/utils": "3.4.0", + "@docusaurus/utils-validation": "3.4.0", + "algoliasearch": "^4.18.0", + "algoliasearch-helper": "^3.13.3", + "clsx": "^2.0.0", + "eta": "^2.2.0", + "fs-extra": "^11.1.1", + "lodash": "^4.17.21", + "tslib": "^2.6.0", + "utility-types": "^3.10.0" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@docusaurus/theme-translations": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.4.0.tgz", + "integrity": "sha512-zSxCSpmQCCdQU5Q4CnX/ID8CSUUI3fvmq4hU/GNP/XoAWtXo9SAVnM3TzpU8Gb//H3WCsT8mJcTfyOk3d9ftNg==", + "license": "MIT", + "dependencies": { + "fs-extra": "^11.1.1", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@docusaurus/types": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.4.0.tgz", + "integrity": "sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A==", + "license": "MIT", + "dependencies": { + "@mdx-js/mdx": "^3.0.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "commander": "^5.1.0", + "joi": "^17.9.2", + "react-helmet-async": "^1.3.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1", + "webpack-merge": "^5.9.0" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@docusaurus/utils": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.4.0.tgz", + "integrity": "sha512-fRwnu3L3nnWaXOgs88BVBmG1yGjcQqZNHG+vInhEa2Sz2oQB+ZjbEMO5Rh9ePFpZ0YDiDUhpaVjwmS+AU2F14g==", + "license": "MIT", + "dependencies": { + "@docusaurus/logger": "3.4.0", + "@docusaurus/utils-common": "3.4.0", + "@svgr/webpack": "^8.1.0", + "escape-string-regexp": "^4.0.0", + "file-loader": "^6.2.0", + "fs-extra": "^11.1.1", + "github-slugger": "^1.5.0", + "globby": "^11.1.0", + "gray-matter": "^4.0.3", + "jiti": "^1.20.0", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "micromatch": "^4.0.5", + "prompts": "^2.4.2", + "resolve-pathname": "^3.0.0", + "shelljs": "^0.8.5", + "tslib": "^2.6.0", + "url-loader": "^4.1.1", + "utility-types": "^3.10.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "@docusaurus/types": "*" + }, + "peerDependenciesMeta": { + "@docusaurus/types": { + "optional": true + } + } + }, + "node_modules/@docusaurus/utils-common": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.4.0.tgz", + "integrity": "sha512-NVx54Wr4rCEKsjOH5QEVvxIqVvm+9kh7q8aYTU5WzUU9/Hctd6aTrcZ3G0Id4zYJ+AeaG5K5qHA4CY5Kcm2iyQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "@docusaurus/types": "*" + }, + "peerDependenciesMeta": { + "@docusaurus/types": { + "optional": true + } + } + }, + "node_modules/@docusaurus/utils-validation": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.4.0.tgz", + "integrity": "sha512-hYQ9fM+AXYVTWxJOT1EuNaRnrR2WGpRdLDQG07O8UOpsvCPWUVOeo26Rbm0JWY2sGLfzAb+tvJ62yF+8F+TV0g==", + "license": "MIT", + "dependencies": { + "@docusaurus/logger": "3.4.0", + "@docusaurus/utils": "3.4.0", + "@docusaurus/utils-common": "3.4.0", + "fs-extra": "^11.2.0", + "joi": "^17.9.2", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@easyops-cn/autocomplete.js": { + "version": "0.38.1", + "resolved": "https://registry.npmjs.org/@easyops-cn/autocomplete.js/-/autocomplete.js-0.38.1.tgz", + "integrity": "sha512-drg76jS6syilOUmVNkyo1c7ZEBPcPuK+aJA7AksM5ZIIbV57DMHCywiCr+uHyv8BE5jUTU98j/H7gVrkHrWW3Q==", + "dependencies": { + "cssesc": "^3.0.0", + "immediate": "^3.2.3" + } + }, + "node_modules/@easyops-cn/docusaurus-search-local": { + "version": "0.40.1", + "resolved": "https://registry.npmjs.org/@easyops-cn/docusaurus-search-local/-/docusaurus-search-local-0.40.1.tgz", + "integrity": "sha512-4HMFZMpKKdd5qq1nFB8cvrAkgzZ1kNxphVciI64YHtmDYGIthVGZVG6+Ci7AAhzCR+ixLJkYwtVekvuMLjr2ZQ==", + "dependencies": { + "@docusaurus/plugin-content-docs": "^2 || ^3", + "@docusaurus/theme-translations": "^2 || ^3", + "@docusaurus/utils": "^2 || ^3", + "@docusaurus/utils-common": "^2 || ^3", + "@docusaurus/utils-validation": "^2 || ^3", + "@easyops-cn/autocomplete.js": "^0.38.1", + "@node-rs/jieba": "^1.6.0", + "cheerio": "^1.0.0-rc.3", + "clsx": "^1.1.1", + "debug": "^4.2.0", + "fs-extra": "^10.0.0", + "klaw-sync": "^6.0.0", + "lunr": "^2.3.9", + "lunr-languages": "^1.4.0", + "mark.js": "^8.11.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "@docusaurus/theme-common": "^2 || ^3", + "react": "^16.14.0 || ^17 || ^18", + "react-dom": "^16.14.0 || 17 || ^18" + } + }, + "node_modules/@easyops-cn/docusaurus-search-local/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@easyops-cn/docusaurus-search-local/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@emnapi/core": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-0.45.0.tgz", + "integrity": "sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.45.0.tgz", + "integrity": "sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@gracefullight/docusaurus-plugin-microsoft-clarity": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@gracefullight/docusaurus-plugin-microsoft-clarity/-/docusaurus-plugin-microsoft-clarity-1.0.0.tgz", + "integrity": "sha512-tOFMgF8GdWL4VgdkuPiT/mDYbi4GgtOpq4hVWCg4O3XfSUd7TNGSrzNXnLiOzcO64gQSrBMpB3aQwIpi4gshdw==", + "dependencies": { + "@docusaurus/utils-validation": "^3" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" + }, + "node_modules/@mdx-js/mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.0.0.tgz", + "integrity": "sha512-Icm0TBKBLYqroYbNW3BPnzMGn+7mwpQOK310aZ7+fkCtiU3aqv2cdcX+nd0Ydo3wI5Rx8bX2Z2QmGb/XcAClCw==", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdx": "^2.0.0", + "collapse-white-space": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-util-to-js": "^2.0.0", + "estree-walker": "^3.0.0", + "hast-util-to-estree": "^3.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "markdown-extensions": "^2.0.0", + "periscopic": "^3.0.0", + "remark-mdx": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "source-map": "^0.7.0", + "unified": "^11.0.0", + "unist-util-position-from-estree": "^2.0.0", + "unist-util-stringify-position": "^4.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mdx-js/react": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.0.0.tgz", + "integrity": "sha512-nDctevR9KyYFyV+m+/+S4cpzCWHqj+iHDHq3QrsWezcC+B17uZdIWgCguESUkwFhM3n/56KxWVE3V6EokrmONQ==", + "dependencies": { + "@types/mdx": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=16", + "react": ">=16" + } + }, + "node_modules/@node-rs/jieba": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@node-rs/jieba/-/jieba-1.8.0.tgz", + "integrity": "sha512-aZrqp40IRD5niZb+oxGCq+l0GAnAZaWy+sHNONOhgZIRBrixT86eu84IvvPHm+LxEy4G2moDMsFviDcsK59G5A==", + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@node-rs/jieba-android-arm-eabi": "1.8.0", + "@node-rs/jieba-android-arm64": "1.8.0", + "@node-rs/jieba-darwin-arm64": "1.8.0", + "@node-rs/jieba-darwin-x64": "1.8.0", + "@node-rs/jieba-freebsd-x64": "1.8.0", + "@node-rs/jieba-linux-arm-gnueabihf": "1.8.0", + "@node-rs/jieba-linux-arm64-gnu": "1.8.0", + "@node-rs/jieba-linux-arm64-musl": "1.8.0", + "@node-rs/jieba-linux-x64-gnu": "1.8.0", + "@node-rs/jieba-linux-x64-musl": "1.8.0", + "@node-rs/jieba-wasm32-wasi": "1.8.0", + "@node-rs/jieba-win32-arm64-msvc": "1.8.0", + "@node-rs/jieba-win32-ia32-msvc": "1.8.0", + "@node-rs/jieba-win32-x64-msvc": "1.8.0" + } + }, + "node_modules/@node-rs/jieba-android-arm-eabi": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-android-arm-eabi/-/jieba-android-arm-eabi-1.8.0.tgz", + "integrity": "sha512-aytaOo5eMnwEH/PWIuT/XL1QajXxIYZ12nu6ANIJsIXS04tCHvpcORI/2jekTxWLPEegMBQfiT5x7VDrGLawFQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-android-arm64": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-android-arm64/-/jieba-android-arm64-1.8.0.tgz", + "integrity": "sha512-lOLC44H8rseNK8/8Zxe4OZLcrDDeVN0LbUyJUBmn54Iwnmrju8bdVFHMOXhOZx2lo77aJKupFDxF+Q9ao9OaSw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-darwin-arm64": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-darwin-arm64/-/jieba-darwin-arm64-1.8.0.tgz", + "integrity": "sha512-c2MlVgqgU9ARyaWGswYeZL1Zt9aRPbuZchbFL2Okbf6Tnwv8so5kawKeSr/1zR8WJabAdkT0jHNWa1gyhVBpaA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-darwin-x64": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-darwin-x64/-/jieba-darwin-x64-1.8.0.tgz", + "integrity": "sha512-8TZgcFKRrJI82LY+P0sw0A1ASHoCwn+WdyBHSQ9xZAVtfWHtRgzG3ccSsN/DdWIv+Kc686j9joV7Mhb08QRYaw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-freebsd-x64": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-freebsd-x64/-/jieba-freebsd-x64-1.8.0.tgz", + "integrity": "sha512-dBnkMi9urpS7vKlMekPq7s80euf6o2Spj5cueD3g89GSFKH0vuDrJizSxwOlA6EE1tmA9SpS1rv5DWGh3Lev4Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-linux-arm-gnueabihf": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-linux-arm-gnueabihf/-/jieba-linux-arm-gnueabihf-1.8.0.tgz", + "integrity": "sha512-lWZoQnkBXm+y2d88Scw1EsS7Smr+PLkeUOJosB9aPPNEqjnewJpAC1hJBcSm+xnvimk7l4H1E12DR2+8/yFIYQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-linux-arm64-gnu": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-linux-arm64-gnu/-/jieba-linux-arm64-gnu-1.8.0.tgz", + "integrity": "sha512-TKndzSs9l0TRa6WT0Dn/J5Tvwf94ZuVuoctJ3vk4odWaKnqgIjEAauochHpRn8hfpfs5bEAEJns8YZBQGaS/Vw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-linux-arm64-musl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-linux-arm64-musl/-/jieba-linux-arm64-musl-1.8.0.tgz", + "integrity": "sha512-c5MOWuczY/76Fip4DCnQy+OYMd3dLpLe9O2Q2PXIOR/wSnS0m7Ob/eR0fZGQSZkdVXKCp/POyGaWxHxD4fKKsA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-linux-x64-gnu": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-linux-x64-gnu/-/jieba-linux-x64-gnu-1.8.0.tgz", + "integrity": "sha512-+R4gR3InDXsREpXjrlknDf3QPiBTm/Ai4jVCqHUhvbF1HWtrW3VOBmrDbGJ801o5UoS4+jVbiXqTt5slehh3pw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-linux-x64-musl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-linux-x64-musl/-/jieba-linux-x64-musl-1.8.0.tgz", + "integrity": "sha512-HL+6NWAVDtRy0F1FvwL8f7PNBTOgPJf1kyRPvFo5OsW1acpOt9tqOyPWrL8NzSSu2O2eUAZup/KDCidP1Iw77g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-wasm32-wasi": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-wasm32-wasi/-/jieba-wasm32-wasi-1.8.0.tgz", + "integrity": "sha512-IKj9+SZmdiSD/lXpGH8qQnqCdRqEDF74vEYTRUN5lzNL2YF4FyjN3cHSuIxUZSCj9hJZf+js34qZm+ZOFYYvSw==", + "cpu": [ + "wasm32" + ], + "optional": true, + "dependencies": { + "@emnapi/core": "^0.45.0", + "@emnapi/runtime": "^0.45.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-win32-arm64-msvc": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-win32-arm64-msvc/-/jieba-win32-arm64-msvc-1.8.0.tgz", + "integrity": "sha512-gKCs1r6NxrWM3xjY6Vc4nnXDinpkY0XWXZD9nCI3mYroDi7FDt9Txzf4ymUk6WFaBLDYeZA33csukgnMOn+e9A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-win32-ia32-msvc": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-win32-ia32-msvc/-/jieba-win32-ia32-msvc-1.8.0.tgz", + "integrity": "sha512-S5V8vTkMVJPf50HW1d2BqmYLFWHGwy5TwRMUwxCcJCbH1g0JzZPCBVh855FZcLkIdGh5rpJR/fYuoKP3JT2GVw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/jieba-win32-x64-msvc": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@node-rs/jieba-win32-x64-msvc/-/jieba-win32-x64-msvc-1.8.0.tgz", + "integrity": "sha512-9Y5k2eIHz4xiQ8WrDy1fbfh7k0yrkeS1kiXPRDZPhR4wylDSZHRjDaNz8o2PtQJ8dIuXudM/Vlo9pXv6+2xU5g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + }, + "node_modules/@pnpm/npm-conf": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.2.2.tgz", + "integrity": "sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==", + "dependencies": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.24", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.24.tgz", + "integrity": "sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==" + }, + "node_modules/@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@slorber/remark-comment": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@slorber/remark-comment/-/remark-comment-1.0.0.tgz", + "integrity": "sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.1.0", + "micromark-util-symbol": "^1.0.1" + } + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@svgr/plugin-svgo": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", + "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", + "dependencies": { + "cosmiconfig": "^8.1.3", + "deepmerge": "^4.3.1", + "svgo": "^3.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@svgr/webpack": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-8.1.0.tgz", + "integrity": "sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==", + "dependencies": { + "@babel/core": "^7.21.3", + "@babel/plugin-transform-react-constant-elements": "^7.21.3", + "@babel/preset-env": "^7.20.2", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.0", + "@svgr/core": "8.1.0", + "@svgr/plugin-jsx": "8.1.0", + "@svgr/plugin-svgo": "8.1.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@types/acorn": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz", + "integrity": "sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.1.tgz", + "integrity": "sha512-18PLWRzhy9glDQp3+wOgfLYRWlhgX0azxgJ63rdpoUHyrC9z0f5CkFburjQx4uD7ZCruw85ZtMt6K+L+R8fLJQ==", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.3.tgz", + "integrity": "sha512-pvQ+TKeRHeiUGRhvYwRrQ/ISnohKkSJR14fT2yqyZ4e9K5vqc7hrtY2Y1Dw0ZwAzQ6DQsxsaCUuSIIi8v0Cq6w==", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.41", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", + "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/gtag.js": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@types/gtag.js/-/gtag.js-0.0.12.tgz", + "integrity": "sha512-YQV9bUsemkzG81Ea295/nF/5GijnD2Af7QhEofh7xu+kvCN6RdodgNwwGWXB5GMI3NoyvQo0odNctoH/qLMIpg==" + }, + "node_modules/@types/hast": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.3.tgz", + "integrity": "sha512-2fYGlaDy/qyLlhidX42wAH0KBi2TCjKMH8CHmBXgRlJ3Y+OXTiqsPQ6IWarZKwF1JoUcAJdPogv1d4b0COTpmQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==" + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.14", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", + "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + }, + "node_modules/@types/mdast": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.3.tgz", + "integrity": "sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdx": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.10.tgz", + "integrity": "sha512-Rllzc5KHk0Al5/WANwgSPl1/CwjqCy+AZrGd78zuK+jO9aDM6ffblZ+zIjgPNAaEBmlO0RYDvLNh7wD0zKVgEg==" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" + }, + "node_modules/@types/node": { + "version": "20.10.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.6.tgz", + "integrity": "sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, + "node_modules/@types/prismjs": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.3.tgz", + "integrity": "sha512-A0D0aTXvjlqJ5ZILMz3rNfDBOx9hHxLZYv2by47Sm/pqW35zzjusrZTryatjN/Rf8Us2gZrJD+KeHbUSTux1Cw==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" + }, + "node_modules/@types/qs": { + "version": "6.9.11", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", + "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" + }, + "node_modules/@types/react": { + "version": "18.2.46", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.46.tgz", + "integrity": "sha512-nNCvVBcZlvX4NU1nRRNV/mFl1nNRuTuslAJglQsq+8ldXe5Xv0Wd2f7WTE3jOxhLH2BFfiZGC6GCp+kHQbgG+w==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-config": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@types/react-router-config/-/react-router-config-5.0.11.tgz", + "integrity": "sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "^5.1.0" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", + "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" + }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", + "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/address": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", + "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/algoliasearch": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.23.3.tgz", + "integrity": "sha512-Le/3YgNvjW9zxIQMRhUHuhiUjAlKY/zsdZpfq4dlLqg6mEm0nL6yk+7f2hDOtLpxsgE4jSzDmvHL7nXdBp5feg==", + "license": "MIT", + "dependencies": { + "@algolia/cache-browser-local-storage": "4.23.3", + "@algolia/cache-common": "4.23.3", + "@algolia/cache-in-memory": "4.23.3", + "@algolia/client-account": "4.23.3", + "@algolia/client-analytics": "4.23.3", + "@algolia/client-common": "4.23.3", + "@algolia/client-personalization": "4.23.3", + "@algolia/client-search": "4.23.3", + "@algolia/logger-common": "4.23.3", + "@algolia/logger-console": "4.23.3", + "@algolia/recommend": "4.23.3", + "@algolia/requester-browser-xhr": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/requester-node-http": "4.23.3", + "@algolia/transporter": "4.23.3" + } + }, + "node_modules/algoliasearch-helper": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.21.0.tgz", + "integrity": "sha512-hjVOrL15I3Y3K8xG0icwG1/tWE+MocqBrhW6uVBWpU+/kVEMK0BnM2xdssj6mZM61eJ4iRxHR0djEI3ENOpR8w==", + "license": "MIT", + "dependencies": { + "@algolia/events": "^4.0.1" + }, + "peerDependencies": { + "algoliasearch": ">= 3.1 < 6" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/astring": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.8.6.tgz", + "integrity": "sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", + "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001599", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/babel-loader": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", + "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==", + "dependencies": { + "find-cache-dir": "^4.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dependencies": { + "object.assign": "^4.1.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz", + "integrity": "sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==", + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.4.4", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz", + "integrity": "sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.4", + "core-js-compat": "^3.33.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.4.tgz", + "integrity": "sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.4" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==" + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/bonjour-service": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", + "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", + "dependencies": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "node_modules/boxen": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-6.2.1.tgz", + "integrity": "sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^6.2.0", + "chalk": "^4.1.2", + "cli-boxes": "^3.0.0", + "string-width": "^5.0.1", + "type-fest": "^2.5.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", + "dependencies": { + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request/node_modules/normalize-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", + "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001617", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001617.tgz", + "integrity": "sha512-mLyjzNI9I+Pix8zwcrpxEbGlfqOkF9kM3ptzmKNw5tizSyYwMe+nGLTqMK9cO+0E+Bh6TsBxNAaHWEM8xwSsmA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", + "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-table3/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/cli-table3/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clone-deep/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + }, + "node_modules/combine-promises": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/combine-promises/-/combine-promises-1.2.0.tgz", + "integrity": "sha512-VcQB1ziGD0NXrhKxiwyNbCDmRzs/OShMs2GqW2DlU2A/Sd0nQxE1oWDAE5O0ygSx5mgQOn9eIFh7yKPgFRVkPQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compressible/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/configstore": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", + "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==", + "dependencies": { + "dot-prop": "^6.0.1", + "graceful-fs": "^4.2.6", + "unique-string": "^3.0.0", + "write-file-atomic": "^3.0.3", + "xdg-basedir": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/yeoman/configstore?sponsor=1" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/consola": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", + "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==" + }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/copy-text-to-clipboard": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.0.tgz", + "integrity": "sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dependencies": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/core-js": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.35.0.tgz", + "integrity": "sha512-ntakECeqg81KqMueeGJ79Q5ZgQNR+6eaE8sxGCx62zMbAIj65q+uYvatToew3m6eAGdU4gNZwpZ34NMe4GYswg==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.0.tgz", + "integrity": "sha512-5blwFAddknKeNgsjBzilkdQ0+YK8L1PfqPYq40NOYMYFSS38qj+hpTcLLWwpIwA2A5bje/x5jmVn2tzUMg9IVw==", + "dependencies": { + "browserslist": "^4.22.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-pure": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.35.0.tgz", + "integrity": "sha512-f+eRYmkou59uh7BPcyJ8MC76DiGhspj1KMxVIcF24tzP8NA9HVa1uC7BTW2tgx7E1QVCzDzsgp7kArrzhlz8Ew==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "dependencies": { + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/css-declaration-sorter": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", + "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-loader": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", + "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.21", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.3", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.8" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/css-minimizer-webpack-plugin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", + "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "cssnano": "^6.0.1", + "jest-worker": "^29.4.3", + "postcss": "^8.4.24", + "schema-utils": "^4.0.1", + "serialize-javascript": "^6.0.1" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@parcel/css": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "lightningcss": { + "optional": true + } + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", + "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", + "dependencies": { + "cssnano-preset-default": "^6.1.2", + "lilconfig": "^3.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-preset-advanced": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz", + "integrity": "sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ==", + "license": "MIT", + "dependencies": { + "autoprefixer": "^10.4.19", + "browserslist": "^4.23.0", + "cssnano-preset-default": "^6.1.2", + "postcss-discard-unused": "^6.0.5", + "postcss-merge-idents": "^6.0.3", + "postcss-reduce-idents": "^6.0.3", + "postcss-zindex": "^6.0.2" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-preset-default": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", + "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "dependencies": { + "browserslist": "^4.23.0", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^4.0.2", + "postcss-calc": "^9.0.1", + "postcss-colormin": "^6.1.0", + "postcss-convert-values": "^6.1.0", + "postcss-discard-comments": "^6.0.2", + "postcss-discard-duplicates": "^6.0.3", + "postcss-discard-empty": "^6.0.3", + "postcss-discard-overridden": "^6.0.2", + "postcss-merge-longhand": "^6.0.5", + "postcss-merge-rules": "^6.1.1", + "postcss-minify-font-values": "^6.1.0", + "postcss-minify-gradients": "^6.0.3", + "postcss-minify-params": "^6.1.0", + "postcss-minify-selectors": "^6.0.4", + "postcss-normalize-charset": "^6.0.2", + "postcss-normalize-display-values": "^6.0.2", + "postcss-normalize-positions": "^6.0.2", + "postcss-normalize-repeat-style": "^6.0.2", + "postcss-normalize-string": "^6.0.2", + "postcss-normalize-timing-functions": "^6.0.2", + "postcss-normalize-unicode": "^6.1.0", + "postcss-normalize-url": "^6.0.2", + "postcss-normalize-whitespace": "^6.0.2", + "postcss-ordered-values": "^6.0.2", + "postcss-reduce-initial": "^6.1.0", + "postcss-reduce-transforms": "^6.0.2", + "postcss-svgo": "^6.0.3", + "postcss-unique-selectors": "^6.0.4" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-utils": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", + "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano/node_modules/lilconfig": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", + "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/del": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", + "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", + "dependencies": { + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + }, + "node_modules/detect-port": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.5.1.tgz", + "integrity": "sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==", + "dependencies": { + "address": "^1.0.1", + "debug": "4" + }, + "bin": { + "detect": "bin/detect-port.js", + "detect-port": "bin/detect-port.js" + } + }, + "node_modules/detect-port-alt": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", + "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", + "dependencies": { + "address": "^1.0.1", + "debug": "^2.6.0" + }, + "bin": { + "detect": "bin/detect-port", + "detect-port": "bin/detect-port" + }, + "engines": { + "node": ">= 4.2.1" + } + }, + "node_modules/detect-port-alt/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/detect-port-alt/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" + }, + "node_modules/dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==" + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/docusaurus-pushfeedback": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/docusaurus-pushfeedback/-/docusaurus-pushfeedback-1.0.0.tgz", + "integrity": "sha512-caf9C8bRpGhzd7foJ0WqSLTSZonPgAFLPEN0U+lKYpiFLVJOHnEK3gOn26kibhhqrVx7FrF2fRBHLO5kJbnkhg==", + "peerDependencies": { + "@docusaurus/core": "3.x" + } + }, + "node_modules/docusaurus-theme-github-codeblock": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/docusaurus-theme-github-codeblock/-/docusaurus-theme-github-codeblock-2.0.1.tgz", + "integrity": "sha512-qwLgDVThQidqBZLyEmyV0MpOkQHY7XOwAkPZv05feTezeywtmLL+BC6czEl4poEZJf5YQXI3bAkF0UW/3Mv8Ng==", + "dependencies": { + "@docusaurus/types": "^3.0.0" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dot-prop/node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.763", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.763.tgz", + "integrity": "sha512-k4J8NrtJ9QrvHLRo8Q18OncqBCB7tIUyqxRcJnlonQ0ioHKYB988GcDFF3ZePmnb8eHEopDs/wPHR/iGAFgoUQ==" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/emoticon": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-4.0.1.tgz", + "integrity": "sha512-dqx7eA9YaqyvYtUhJwT4rC1HIp82j5ybS1/vQ42ur+jBe17dJMwZE4+gvL1XadSFfxaPFFGt3Xsw+Y8akThDlw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", + "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==" + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-goat": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", + "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-util-attach-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", + "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", + "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-value-to-estree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.0.1.tgz", + "integrity": "sha512-b2tdzTurEIbwRh+mKrEcaWfu1wgb8J1hVsgREg7FFiecWwK/PhO8X0kyc+0bIcKNtD4sqxIdNoRy6/p/TvECEA==", + "dependencies": { + "@types/estree": "^1.0.0", + "is-plain-obj": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/remcohaszing" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eta": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eta/-/eta-2.2.0.tgz", + "integrity": "sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g==", + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "url": "https://github.com/eta-dev/eta?sponsor=1" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eval": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eval/-/eval-0.1.8.tgz", + "integrity": "sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==", + "dependencies": { + "@types/node": "*", + "require-like": ">= 0.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/express/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/express/node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/express/node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-url-parser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", + "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", + "dependencies": { + "punycode": "^1.3.2" + } + }, + "node_modules/fastq": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", + "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fault": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", + "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/feed": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz", + "integrity": "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==", + "dependencies": { + "xml-js": "^1.6.11" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/file-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", + "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/file-loader/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/file-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/file-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/file-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/filesize": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", + "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/find-cache-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", + "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "dependencies": { + "common-path-prefix": "^3.0.0", + "pkg-dir": "^7.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fork-ts-checker-webpack-plugin": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz", + "integrity": "sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==", + "dependencies": { + "@babel/code-frame": "^7.8.3", + "@types/json-schema": "^7.0.5", + "chalk": "^4.1.0", + "chokidar": "^3.4.2", + "cosmiconfig": "^6.0.0", + "deepmerge": "^4.2.2", + "fs-extra": "^9.0.0", + "glob": "^7.1.6", + "memfs": "^3.1.2", + "minimatch": "^3.0.4", + "schema-utils": "2.7.0", + "semver": "^7.3.2", + "tapable": "^1.0.0" + }, + "engines": { + "node": ">=10", + "yarn": ">=1.0.0" + }, + "peerDependencies": { + "eslint": ">= 6", + "typescript": ">= 2.7", + "vue-template-compiler": "*", + "webpack": ">= 4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + }, + "vue-template-compiler": { + "optional": true + } + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "dependencies": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", + "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dependencies": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-slugger": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", + "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + }, + "node_modules/global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/global-dirs/node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/got/node_modules/@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/gray-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/gray-matter/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-yarn": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-3.0.0.tgz", + "integrity": "sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz", + "integrity": "sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^8.0.0", + "property-information": "^6.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.1.tgz", + "integrity": "sha512-5m1gmba658Q+lO5uqL5YNGQWeh1MYWZbZmWrM5lncdcuiXuo5E2HT/CIOp0rLF8ksfSwiCVJ3twlgVRyTGThGA==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-estree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.0.tgz", + "integrity": "sha512-lfX5g6hqVh9kjS/B9E2gSkvHH4SZNiQFiqWS0x9fENzEl+8W12RqdRxX6d/Cwxi30tPQs3bIO+aolQJNp1bIyw==", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-attach-comments": "^3.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^0.4.0", + "unist-util-position": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz", + "integrity": "sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/inline-style-parser": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.2.tgz", + "integrity": "sha512-EcKzdTHVe8wFVOGEYXiW9WmJXPjqi1T+234YpJr98RiFYKHV3cdy1+3mkTE+KHTHxFFLH51SfaGOoUdW+v7ViQ==" + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/style-to-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.5.tgz", + "integrity": "sha512-rDRwHtoDD3UMMrmZ6BzOW0naTjMsVZLIjsGleSKS/0Oz+cgCfAPRspaqJuE8rDzpKha/nEvnM0IF4seEAZUTKQ==", + "dependencies": { + "inline-style-parser": "0.2.2" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", + "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz", + "integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, + "node_modules/history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "dependencies": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-entities": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", + "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" + }, + "node_modules/html-minifier-terser": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", + "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "~5.3.2", + "commander": "^10.0.0", + "entities": "^4.4.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.15.1" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "engines": { + "node": ">=14" + } + }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", + "integrity": "sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw==", + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.20.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/html-webpack-plugin/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/html-webpack-plugin/node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/http-proxy-middleware/node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz", + "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==", + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, + "node_modules/immediate": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", + "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==" + }, + "node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/infima": { + "version": "0.2.0-alpha.43", + "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.43.tgz", + "integrity": "sha512-2uw57LvUqW0rK/SWYnd/2rRfxNA5DDNOh33jxF7fy46VWoNhGxiUQyVZHbBMjQ33mQem0cjdDVwgWVAmlRfgyQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/inline-style-parser": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", + "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/ipaddr.js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", + "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-npm": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.0.0.tgz", + "integrity": "sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-reference": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", + "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-root": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", + "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-yarn-global": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz", + "integrity": "sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/joi": { + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", + "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==", + "dependencies": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "engines": { + "node": ">=6" + } + }, + "node_modules/latest-version": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", + "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", + "dependencies": { + "package-json": "^8.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/launch-editor": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.1.tgz", + "integrity": "sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==", + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==" + }, + "node_modules/lunr-languages": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/lunr-languages/-/lunr-languages-1.14.0.tgz", + "integrity": "sha512-hWUAb2KqM3L7J5bcrngszzISY4BxrXn/Xhbb9TTCJYEGqlR1nG67/M14sp09+PTIRklobrn57IAxcdcO/ZFyNA==" + }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==" + }, + "node_modules/markdown-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-table": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", + "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-directive": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.0.0.tgz", + "integrity": "sha512-JUpYOqKI4mM3sZcNxmF/ox04XYFFkNwr0CFlrQIkCwbvH0xzMCqkMqAde9wRd80VAhaUrwFwKm2nxretdT1h7Q==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz", + "integrity": "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.0.tgz", + "integrity": "sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mdast-util-frontmatter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", + "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "escape-string-regexp": "^5.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-frontmatter/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz", + "integrity": "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.0.tgz", + "integrity": "sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz", + "integrity": "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.0.tgz", + "integrity": "sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.0.0.tgz", + "integrity": "sha512-XZuPPzQNBPAlaqsTTgRrcJnyFbSOBovSadFgbFu8SnuNgm+6Bdx1K+IWoitsmj6Lq6MNtI+ytOqwN70n//NaBA==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-remove-position": "^5.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.0.0.tgz", + "integrity": "sha512-xadSsJayQIucJ9n053dfQwVu1kuXg7jCTdYsMK8rqzKZh52nLfSH/k0sAxE0u+pj/zKZX+o5wB+ML5mRayOxFA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.0.2.tgz", + "integrity": "sha512-U5I+500EOOw9e3ZrclN3Is3fRpw8c19SMyNZlZ2IS+7vLsNzb2Om11VpIVOR+/0137GhZsFEF6YiKD5+0Hr2Og==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz", + "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromark": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz", + "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.0.tgz", + "integrity": "sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark/node_modules/micromark-factory-space": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark/node_modules/micromark-util-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-extension-directive": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.0.tgz", + "integrity": "sha512-61OI07qpQrERc+0wEysLHMvoiO3s2R56x5u7glHq2Yqq6EHbH4dW25G9GfDdGCDYqA21KE6DWgNSzxSwHc2hSg==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-directive/node_modules/micromark-factory-space": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive/node_modules/micromark-util-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-extension-frontmatter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", + "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", + "dependencies": { + "fault": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.0.0.tgz", + "integrity": "sha512-rTHfnpt/Q7dEAK1Y5ii0W8bhfJlVJFnJMHIPisfPK3gpVNuOP0VnRl96+YJ3RYWV/P4gFeQoGKNlT3RhuvpqAg==", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.0.0.tgz", + "integrity": "sha512-6Rzu0CYRKDv3BfLAUnZsSlzx3ak6HAoI85KTiijuKIz5UxZxbUI+pD6oHgw+6UtQuiRwnGRhzMmPRv4smcz0fg==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-factory-space": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-c3BR1ClMp5fxxmwP6AoOY2fXO9U8uFMKs4ADD66ahLTNcwzSCyRVU4k7LPV5Nxo/VJiR4TdzxRQY2v3qIUceCw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.0.0.tgz", + "integrity": "sha512-PoHlhypg1ItIucOaHmKE8fbin3vTLpDOUg8KAr8gRCF1MOZI9Nquq2i/44wFvviM4WuxJzc3demT8Y3dkfvYrw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table/node_modules/micromark-factory-space": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.0.1.tgz", + "integrity": "sha512-cY5PzGcnULaN5O7T+cOzfMoHjBW7j+T9D2sucA5d/KbsBTPcYdebm9zUd9zzdgJGCwahV+/W78Z3nbulBYVbTw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-factory-space": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.0.tgz", + "integrity": "sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-expression/node_modules/micromark-factory-space": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.0.tgz", + "integrity": "sha512-uvhhss8OGuzR4/N17L1JwvmJIpPhAd8oByMawEKx6NVdBCbesjH4t+vjEp3ZXft9DwvlKSD07fCeI44/N0Vf2w==", + "dependencies": { + "@types/acorn": "^4.0.0", + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-factory-space": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz", + "integrity": "sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination/node_modules/micromark-util-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-factory-label": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz", + "integrity": "sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label/node_modules/micromark-util-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.1.tgz", + "integrity": "sha512-F0ccWIUHRLRrYp5TC9ZYXmZo+p2AM13ggbsW4T0b5CRKP8KHVRB8t4pwtBgTxtjRmwrK0Irwm7vs2JOZabHZfg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-space/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-factory-title": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz", + "integrity": "sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title/node_modules/micromark-factory-space": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title/node_modules/micromark-util-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz", + "integrity": "sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace/node_modules/micromark-factory-space": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace/node_modules/micromark-util-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-character/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz", + "integrity": "sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz", + "integrity": "sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character/node_modules/micromark-util-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz", + "integrity": "sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz", + "integrity": "sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz", + "integrity": "sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string/node_modules/micromark-util-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.2.tgz", + "integrity": "sha512-Fk+xmBrOv9QZnEDguL9OI9/NQQp6Hz4FuQ4YmCb/5V7+9eAh1s6AYSvL20kHkD67YIg7EpE54TiSlcsf3vyZgA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/acorn": "^4.0.0", + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-util-events-to-acorn/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz", + "integrity": "sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz", + "integrity": "sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-normalize-identifier/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz", + "integrity": "sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.0.tgz", + "integrity": "sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark/node_modules/micromark-factory-space": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark/node_modules/micromark-util-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", + "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.7.6", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz", + "integrity": "sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==", + "dependencies": { + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mrmime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-emoji": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.3.tgz", + "integrity": "sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==", + "dependencies": { + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nprogress": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", + "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==", + "license": "MIT" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz", + "integrity": "sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==", + "dependencies": { + "got": "^12.1.0", + "registry-auth-token": "^5.0.1", + "registry-url": "^6.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", + "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-numeric-range": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/periscopic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", + "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^3.0.0", + "is-reference": "^3.0.0" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "dependencies": { + "find-up": "^6.3.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-up/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-calc": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", + "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-colormin": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", + "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "colord": "^2.9.3", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-convert-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", + "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-comments": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", + "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", + "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-empty": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", + "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", + "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-unused": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz", + "integrity": "sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-import": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-16.0.0.tgz", + "integrity": "sha512-e77lhVvrD1I2y7dYmBv0k9ULTdArgEYZt97T4w6sFIU5uxIHvDFQlKgUUyY7v7Barj0Yf/zm5A4OquZN7jKm5Q==", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz", + "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", + "engines": { + "node": ">=14" + } + }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/postcss-loader": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", + "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", + "dependencies": { + "cosmiconfig": "^8.3.5", + "jiti": "^1.20.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-merge-idents": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz", + "integrity": "sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g==", + "license": "MIT", + "dependencies": { + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-merge-longhand": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", + "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^6.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-merge-rules": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", + "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^4.0.2", + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", + "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", + "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", + "dependencies": { + "colord": "^2.9.3", + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-params": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", + "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", + "dependencies": { + "browserslist": "^4.23.0", + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", + "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", + "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.0.tgz", + "integrity": "sha512-SaIbK8XW+MZbd0xHPf7kdfA/3eOt7vxJ72IRecn3EzuZVLr1r0orzf0MX/pN8m+NMDoo6X/SQd8oeKqGZd8PXg==", + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", + "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", + "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", + "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", + "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-string": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", + "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", + "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", + "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-url": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", + "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", + "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-ordered-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", + "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "dependencies": { + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-idents": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz", + "integrity": "sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", + "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", + "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", + "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-sort-media-queries": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-5.2.0.tgz", + "integrity": "sha512-AZ5fDMLD8SldlAYlvi8NIqo0+Z8xnXU2ia0jxmuhxAU+Lqt9K+AlmLNJ/zWEnE9x+Zx3qL3+1K20ATgNOr3fAA==", + "license": "MIT", + "dependencies": { + "sort-css-media-queries": "2.2.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.4.23" + } + }, + "node_modules/postcss-svgo": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", + "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^3.2.0" + }, + "engines": { + "node": "^14 || ^16 || >= 18" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", + "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "node_modules/postcss-zindex": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-6.0.2.tgz", + "integrity": "sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg==", + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/pretty-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", + "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/prism-react-renderer": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.3.1.tgz", + "integrity": "sha512-Rdf+HzBLR7KYjzpJ1rSoxT9ioO85nZngQEoFIhL07XhtJHlCU3SOz0GJ6+qvMyQe0Se+BV3qpe6Yd/NmQF5Juw==", + "dependencies": { + "@types/prismjs": "^1.26.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.0.0" + } + }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/property-information": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.4.0.tgz", + "integrity": "sha512-9t5qARVofg2xQqKtytzt+lZ4d1Qvj8t5B8fEwXK6qOfgRLgH/b13QlgEyDh033NOS31nXeFbYv7CLUDG1CeifQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" + }, + "node_modules/pupa": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz", + "integrity": "sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==", + "dependencies": { + "escape-goat": "^4.0.0" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "dependencies": { + "inherits": "~2.0.3" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dev-utils": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", + "integrity": "sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==", + "dependencies": { + "@babel/code-frame": "^7.16.0", + "address": "^1.1.2", + "browserslist": "^4.18.1", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "detect-port-alt": "^1.1.6", + "escape-string-regexp": "^4.0.0", + "filesize": "^8.0.6", + "find-up": "^5.0.0", + "fork-ts-checker-webpack-plugin": "^6.5.0", + "global-modules": "^2.0.0", + "globby": "^11.0.4", + "gzip-size": "^6.0.0", + "immer": "^9.0.7", + "is-root": "^2.1.0", + "loader-utils": "^3.2.0", + "open": "^8.4.0", + "pkg-up": "^3.1.0", + "prompts": "^2.4.2", + "react-error-overlay": "^6.0.11", + "recursive-readdir": "^2.2.2", + "shell-quote": "^1.7.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/react-dev-utils/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dev-utils/node_modules/loader-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", + "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/react-dev-utils/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dev-utils/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dev-utils/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dev-utils/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/react-dev-utils/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-error-overlay": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", + "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" + }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, + "node_modules/react-helmet-async": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-1.3.0.tgz", + "integrity": "sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "invariant": "^2.2.4", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.2.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": "^16.6.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.6.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-json-view-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-1.4.0.tgz", + "integrity": "sha512-wh6F6uJyYAmQ4fK0e8dSQMEWuvTs2Wr3el3sLD9bambX1+pSWUVXIz1RFaoy3TI1mZ0FqdpKq9YgbgTTgyrmXA==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-loadable": { + "name": "@docusaurus/react-loadable", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz", + "integrity": "sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==", + "dependencies": { + "@types/react": "*" + }, + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-loadable-ssr-addon-v5-slorber": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz", + "integrity": "sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A==", + "dependencies": { + "@babel/runtime": "^7.10.3" + }, + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "react-loadable": "*", + "webpack": ">=4.41.1 || 5.x" + } + }, + "node_modules/react-router": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", + "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router-config": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/react-router-config/-/react-router-config-5.1.1.tgz", + "integrity": "sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==", + "dependencies": { + "@babel/runtime": "^7.1.2" + }, + "peerDependencies": { + "react": ">=15", + "react-router": ">=5" + } + }, + "node_modules/react-router-dom": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz", + "integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.3.4", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reading-time": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.5.0.tgz", + "integrity": "sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==" + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/recursive-readdir": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", + "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", + "dependencies": { + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/registry-auth-token": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz", + "integrity": "sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==", + "dependencies": { + "@pnpm/npm-conf": "^2.1.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/registry-url": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", + "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", + "dependencies": { + "rc": "1.2.8" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remark-directive": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.0.tgz", + "integrity": "sha512-l1UyWJ6Eg1VPU7Hm/9tt0zKtReJQNOA4+iDMAxTyZNWnJnFlbS/7zhiel/rogTLQ2vMYwDzSJa4BiVNqGlqIMA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-directive": "^3.0.0", + "micromark-extension-directive": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-emoji": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-emoji/-/remark-emoji-4.0.1.tgz", + "integrity": "sha512-fHdvsTR1dHkWKev9eNyhTo4EFwbUvJ8ka9SgeWkMPYFX4WoI7ViVBms3PjlQYgw5TLvNQso3GUB/b/8t3yo+dg==", + "dependencies": { + "@types/mdast": "^4.0.2", + "emoticon": "^4.0.1", + "mdast-util-find-and-replace": "^3.0.1", + "node-emoji": "^2.1.0", + "unified": "^11.0.4" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/remark-frontmatter": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz", + "integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-frontmatter": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz", + "integrity": "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.0.0.tgz", + "integrity": "sha512-O7yfjuC6ra3NHPbRVxfflafAj3LTwx3b73aBvkEFU5z4PsD6FD4vrqJAkE5iNGLz71GdjXfgRqm3SQ0h0VuE7g==", + "dependencies": { + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.0.0.tgz", + "integrity": "sha512-vx8x2MDMcxuE4lBmQ46zYUDfcFMmvg80WYX+UNLeG6ixjdCCLcw1lrgAukwBTuOFsS78eoAedHGn9sNM0w7TPw==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/renderkid/node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/renderkid/node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-like": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", + "integrity": "sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==", + "engines": { + "node": "*" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, + "node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rtl-detect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/rtl-detect/-/rtl-detect-1.1.2.tgz", + "integrity": "sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==" + }, + "node_modules/rtlcss": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.1.1.tgz", + "integrity": "sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ==", + "license": "MIT", + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0", + "postcss": "^8.4.21", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "rtlcss": "bin/rtlcss.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sax": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/search-insights": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.14.0.tgz", + "integrity": "sha512-OLN6MsPMCghDOqlCtsIsYgtsC0pnwVTyT9Mu6A3ewOj1DxvzZF6COrn2g86E/c05xbktB0XN04m/t1Z+n+fTGw==", + "license": "MIT", + "peer": true + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==" + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", + "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/send/node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-handler": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.5.tgz", + "integrity": "sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==", + "dependencies": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "fast-url-parser": "1.1.3", + "mime-types": "2.1.18", + "minimatch": "3.1.2", + "path-is-inside": "1.0.2", + "path-to-regexp": "2.2.1", + "range-parser": "1.2.0" + } + }, + "node_modules/serve-handler/node_modules/path-to-regexp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", + "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==" + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + }, + "node_modules/sitemap": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.1.tgz", + "integrity": "sha512-mK3aFtjz4VdJN0igpIJrinf3EO8U8mxOPsTBzSsy06UtjZQJ3YY3o3Xa7zSc5nMqcMrRwlChHZ18Kxg0caiPBg==", + "dependencies": { + "@types/node": "^17.0.5", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.2.4" + }, + "bin": { + "sitemap": "dist/cli.js" + }, + "engines": { + "node": ">=12.0.0", + "npm": ">=5.6.0" + } + }, + "node_modules/sitemap/node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" + }, + "node_modules/skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "dependencies": { + "unicode-emoji-modifier-base": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/sort-css-media-queries": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz", + "integrity": "sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA==", + "license": "MIT", + "engines": { + "node": ">= 6.3.0" + } + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/srcset": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz", + "integrity": "sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz", + "integrity": "sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-to-object": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz", + "integrity": "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==", + "dependencies": { + "inline-style-parser": "0.1.1" + } + }, + "node_modules/stylehacks": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", + "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" + }, + "node_modules/svgo": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.0.tgz", + "integrity": "sha512-VigzymniH77knD1dryXbyxR+ePHihHociZbXnLZHUyzf2MMs2ZVqlUrZ3FvpXP8pno9JzmILt1sZPD19M3IxtA==", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.19.1", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tailwindcss/node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.26.0.tgz", + "integrity": "sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" + }, + "node_modules/tiny-invariant": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", + "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", + "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unified": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.4.tgz", + "integrity": "sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "dependencies": { + "crypto-random-string": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/update-notifier": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-6.0.2.tgz", + "integrity": "sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==", + "dependencies": { + "boxen": "^7.0.0", + "chalk": "^5.0.1", + "configstore": "^6.0.0", + "has-yarn": "^3.0.0", + "import-lazy": "^4.0.0", + "is-ci": "^3.0.1", + "is-installed-globally": "^0.4.0", + "is-npm": "^6.0.0", + "is-yarn-global": "^0.4.0", + "latest-version": "^7.0.0", + "pupa": "^3.1.0", + "semver": "^7.3.7", + "semver-diff": "^4.0.0", + "xdg-basedir": "^5.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/boxen": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz", + "integrity": "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.1", + "chalk": "^5.2.0", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-notifier/node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-notifier/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/url-loader": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", + "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", + "dependencies": { + "loader-utils": "^2.0.0", + "mime-types": "^2.1.27", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "file-loader": "*", + "webpack": "^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "file-loader": { + "optional": true + } + } + }, + "node_modules/url-loader/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/url-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/url-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/url-loader/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/url-loader/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/url-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==" + }, + "node_modules/utility-types": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", + "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vfile": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", + "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.2.tgz", + "integrity": "sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/webpack": { + "version": "5.89.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", + "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-bundle-analyzer": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.1.tgz", + "integrity": "sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==", + "dependencies": { + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "commander": "^7.2.0", + "debounce": "^1.2.1", + "escape-string-regexp": "^4.0.0", + "gzip-size": "^6.0.0", + "html-escaper": "^2.0.2", + "is-plain-object": "^5.0.0", + "opener": "^1.5.2", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-middleware/node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", + "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.5", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.13.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/webpack/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpackbar": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-5.0.2.tgz", + "integrity": "sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ==", + "dependencies": { + "chalk": "^4.1.0", + "consola": "^2.15.3", + "pretty-time": "^1.1.0", + "std-env": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "webpack": "3 || 4 || 5" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xdg-basedir": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", + "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "dependencies": { + "sax": "^1.2.4" + }, + "bin": { + "xml-js": "bin/cli.js" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000..b16c8f9 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,56 @@ +{ + "name": "docs", + "version": "0.0.0", + "private": true, + "scripts": { + "docusaurus": "docusaurus", + "start": "docusaurus start", + "dev": "docusaurus start", + "build": "docusaurus build", + "swizzle": "docusaurus swizzle", + "deploy": "docusaurus deploy", + "clear": "docusaurus clear", + "serve": "docusaurus serve", + "write-translations": "docusaurus write-translations", + "write-heading-ids": "docusaurus write-heading-ids" + }, + "dependencies": { + "@docusaurus/core": "^3.4.0", + "@docusaurus/plugin-client-redirects": "^3.4.0", + "@docusaurus/plugin-content-docs": "^3.4.0", + "@docusaurus/plugin-google-gtag": "^3.4.0", + "@docusaurus/plugin-sitemap": "^3.4.0", + "@docusaurus/preset-classic": "^3.4.0", + "@easyops-cn/docusaurus-search-local": "^0.40.1", + "@gracefullight/docusaurus-plugin-microsoft-clarity": "^1.0.0", + "@mdx-js/react": "^3.0.0", + "autoprefixer": "^10.4.16", + "clsx": "^2.1.0", + "docusaurus-pushfeedback": "^1.0.0", + "docusaurus-theme-github-codeblock": "^2.0.1", + "postcss": "^8.4.32", + "postcss-import": "^16.0.0", + "prism-react-renderer": "^2.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "tailwindcss": "^3.4.0" + }, + "devDependencies": { + "@docusaurus/module-type-aliases": "^3.3.2" + }, + "browserslist": { + "production": [ + ">0.5%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "engines": { + "node": ">=18.0" + } +} diff --git a/docs/params/params.md b/docs/params/params.md new file mode 100644 index 0000000..fdce6cc --- /dev/null +++ b/docs/params/params.md @@ -0,0 +1,24 @@ +--- +slug: /params.md +--- + +# Parameters + +## 02-Client + +The 02-client submodule contains the following parameters: + +| Key | Type | Default Value | +| ---------------- | -------- | ------------- | +| `AllowedClients` | []string | `"*"` | + +### AllowedClients + +The allowed clients parameter defines an allow list of client types supported by the chain. The +default value is a single-element list containing the `AllowAllClients` wildcard (`"*"`). When the +wildcard is used, then all client types are supported by default. Alternatively, the parameter +may be set with a list of client types (e.g. `"06-solomachine","07-tendermint","09-localhost"`). +A client type that is not registered on this list will fail upon creation or on genesis validation. +Note that, since the client type is an arbitrary string, chains must not register two light clients +which return the same value for the `ClientType()` function, otherwise the allow list check can be +bypassed. diff --git a/docs/requirements/ics08-requirements.md b/docs/requirements/ics08-requirements.md new file mode 100644 index 0000000..8310c4c --- /dev/null +++ b/docs/requirements/ics08-requirements.md @@ -0,0 +1,124 @@ + +# Business requirements + +Using IBC as a mean of communicating between chains and ecosystems has proven to be useful within Cosmos. There is then value in extending +this feature into other ecosystems, bringing a battle-tested protocol of trust-minimized communication as an option to send assets and data. + +This is especially useful to protocols and companies whose business model is to improve cross-chain user interface, or to enable it when +it's not. The main use case for this is bridging assets between chains. There are multiple protocols and companies currently performing such +a service but none has yet been able to do it using IBC outside of the Cosmos ecosystem. + +A core piece for this to happen is to have a light client implementation of each ecosystem that has to be integrated, and uses a **new** consensus +algorithm. This module broadens the horizon of light client development to not be limited to using Golang only for chains wanting to use IBC and ibc-go, +but instead expands the choice to any programming language and toolchain that is able to compile to Wasm instead. + +Bridging assets is likely the simplest form of interchain communication. Its value is confirmed on a daily basis, when considering the volumes that protocols +like [Axelar](https://dappradar.com/multichain/defi/axelar-network), Gravity, [Wormhole](https://dappradar.com/multichain/defi/wormhole/) and +Layer0 process. + +## Problem + +In order to export IBC outside of Tendermint-based ecosystems, there is a need to introduce new light clients. This is a core need for +companies and protocols trying to bridge ecosystems such as Ethereum, NEAR, Polkadot, etc. as none of these uses Tendermint as their +consensus mechanism. Introducing a new light client implementation is not straightforward: sometimes cryptographic primitives are not +available, or support for operating with certain data structures (like specific tries/trees, etc) are not available in Go. The implementer needs to follow +the light client's specification, and will try to make use of all available tools to keep the development cost reasonable. + +Normally, most of available tools to implement a light client stem from the blockchain ecosystem this client belongs to. Say for example, if a developer +wants to implement the Polkadot finality gadget called GRANDPA, she will find that most of the tools are available on Substrate. Hence, being able to have a way +to let developers implement these light clients using the best and most accessible tools for the job is very beneficial, as it avoids having to re-implement +features that are otherwise available and likely heavily audited already. And since Wasm is a well supported target that most programming languages support, +it becomes a proper solution to port the code for ibc-go to interpret without requiring the entire light client being written using Go. + +## Objectives + +The objective of this module is to have allow two chains with heterogeneous consensus algorithms being connected through light clients that are not necessarily written in Go, but compiled to Wasm instead. + +## Scope + +The scope of this feature is to allow any implementation written in Wasm to be compliant with the interface +expressed in [02-client `ClientState` interface](https://github.com/cosmos/ibc-go/blob/main/modules/core/exported/client.go#L44-L139). + +| Features | Release | +| ---------------------- | ------- | +| Store light client contract bytecode by means of a governance proposal. | v1 | +| Dispatch messages to a light client written in Wasm following the `ClientState` interface. | v1 | +| Migrate the contract instance of a light client to a newer contract bytecode. | v1 | +| Remove checksums from the list of allowed checksums to disallow contract instantiation. | v1 | +| Support GRANDPA light client. | v1 | + +# User requirements + +## Use cases + +The first use case that this module will enable is the connection between GRANDPA light client chains and Tendermint light client chains. Further implementation of other light clients, such as NEAR, Ethereum, etc. will likely consider building on top of this module. + +# Functional requirements + +## Assumptions + +1. This feature expects the [02-client refactor completed](https://github.com/cosmos/ibc-go/milestone/16), which is enabled in ibc-go v7. + +## Features + +### 1 - Configuration + +| ID | Description | Verification | Status | Release | +| -- | ----------- | ------------ | ------ | ------- | +| 1.01 | To enable the usage of the Wasm client module, chains must update the `AllowedClients` parameter in the 02-client submodule. | The `AllowedClients` needs to be updated to add the `08-wasm` client type. | `Verified` | v0.1.0 | +| 1.02 | The genesis state of the Wasm client module consists of the list of contracts' bytecode for each light client Wasm contract. | See [here](https://github.com/cosmos/ibc-go/blob/modules/light-clients/08-wasm/v0.1.0%2Bibc-go-v8.0-wasmvm-v1.5/proto/ibc/lightclients/wasm/v1/genesis.proto#L12). | `Verified` | v0.1.0 | +| 1.03 | A chain shall have the ability to initialize the Wasm client module genesis state. | See [here](https://github.com/cosmos/ibc-go/blob/modules/light-clients/08-wasm/v0.1.0%2Bibc-go-v8.0-wasmvm-v1.5/modules/light-clients/08-wasm/keeper/genesis.go#L12). | `Verified` | v0.1.0 | +| 1.04 | A chain shall have the ability to export the Wasm client module genesis state. | See [here](https://github.com/cosmos/ibc-go/blob/modules/light-clients/08-wasm/v0.1.0%2Bibc-go-v8.0-wasmvm-v1.5/modules/light-clients/08-wasm/keeper/genesis.go#L24). | `Verified` | v0.1.0 | +| 1.05 | Chains that integrate the wasmd module may have the option to use the same wasm VM instance for both wasmd and the 08-wasm module. | A [keeper constructor function](https://github.com/cosmos/ibc-go/blob/modules/light-clients/08-wasm/v0.1.0%2Bibc-go-v8.0-wasmvm-v1.5/modules/light-clients/08-wasm/keeper/keeper.go#L39) is provided that accepts a wasm VM pointer. | `Verified` | v0.1.0 | +| 1.06 | Chains that do not integrate the wasmd module may have the option to delegate to the 08-wasm module the instantiation of the necessary wasm VM. | A [keeper constructor function](https://github.com/cosmos/ibc-go/blob/modules/light-clients/08-wasm/v0.1.0%2Bibc-go-v8.0-wasmvm-v1.5/modules/light-clients/08-wasm/keeper/keeper.go#L88) is provided that accepts parameters to configure the wasm VM that would instantiated by the module. | `Verified` | v0.1.0 | +| 1.07 | It may be possible to register custom query plugins for the 08-wasm module. | See [parameter in keeper constructor function](https://github.com/cosmos/ibc-go/blob/modules/light-clients/08-wasm/v0.1.0%2Bibc-go-v8.0-wasmvm-v1.5/modules/light-clients/08-wasm/keeper/keeper.go#L45). | `Verified` | v0.1.0 | + +### 2 - Initiation + +| ID | Description | Verification | Status | Release | +| -- | ----------- | ------------ | ------ | ------- | +| 2.01 | Users must submit a governance proposal to store a light client implementation compiled in Wasm bytecode. | [`MsgStoreCode` is authority-gated](https://github.com/cosmos/ibc-go/blob/modules/light-clients/08-wasm/v0.1.0%2Bibc-go-v8.0-wasmvm-v1.5/modules/light-clients/08-wasm/keeper/msg_server.go#L20). | `Verified` | v0.1.0 | +| 2.02 | Once a light client Wasm contract has been stored, every light client will be created with a new instance of the contract. | The [`Instantiate` endpoint](https://github.com/cosmos/ibc-go/blob/modules/light-clients/08-wasm/v0.1.0%2Bibc-go-v8.0-wasmvm-v1.5/modules/light-clients/08-wasm/types/client_state.go#L129) of the contract is called when creating a new light client. | `Verified` | v0.1.0 | +| 2.03 | It must not be possible to initialize a light client with a bytecode checksum that has not been previously stored via `MsgStoreCode`. | Se check [here](https://github.com/cosmos/ibc-go/blob/modules/light-clients/08-wasm/v0.1.0%2Bibc-go-v8.0-wasmvm-v1.5/modules/light-clients/08-wasm/types/client_state.go#L119). | `Verified` | v0.1.0 | + +### 3 - Contract migration + +| ID | Description | Verification | Status | Release | +| -- | ----------- | ------------ | ------ | ------- | +| 3.01 | Users may submit a governance proposal to remove a particular bytecode checksum from the list of allowed checksums. | [`MsgRemoveChecksum`](https://github.com/cosmos/ibc-go/blob/modules/light-clients/08-wasm/v0.1.0%2Bibc-go-v8.0-wasmvm-v1.5/proto/ibc/lightclients/wasm/v1/tx.proto#L39) is available. | `Verified` | v0.1.0 | +| 3.02 | Users may submit a governance proposal to migrate a light client to a new contract instance specified by its contract checksum. | [`MsgMigrateContract`](https://github.com/cosmos/ibc-go/blob/modules/light-clients/08-wasm/v0.1.0%2Bibc-go-v8.0-wasmvm-v1.5/proto/ibc/lightclients/wasm/v1/tx.proto#L52) is available. | `Verified` | v0.1.0 | + +# Non-functional requirements + +## 4 - Storage + +| ID | Description | Verification | Status | Release | +| -- | ----------- | ------------ | ------ | ------- | +| 4.01 | The bytecode for each light client Wasm contract does not need to be stored in a client-prefixed store. | The [bytecode is stored only in the wasm VM](https://github.com/cosmos/ibc-go/blob/modules/light-clients/08-wasm/v0.1.0%2Bibc-go-v8.0-wasmvm-v1.5/modules/light-clients/08-wasm/keeper/keeper.go#L137). | `Verified` | v0.1.0 | +| 4.02 | When a contract bytecode is stored it should also be pinned the wasm VM in-memory cache. | See [here](https://github.com/cosmos/ibc-go/blob/modules/light-clients/08-wasm/v0.1.0%2Bibc-go-v8.0-wasmvm-v1.5/modules/light-clients/08-wasm/keeper/keeper.go#L148). | `Verified` | v0.1.0 | +| 4.03 | The size in bytes of bytecode of the light client Wasm contract must be > 0 and <= 3 MiB. | See validation [here](https://github.com/cosmos/ibc-go/blob/modules/light-clients/08-wasm/v0.1.0%2Bibc-go-v8.0-wasmvm-v1.5/modules/light-clients/08-wasm/types/validation.go#L20). | `Verified` | v0.1.0 | + +## 5 - Memory + +| ID | Description | Verification | Status | Release | +| -- | ----------- | ------------ | ------ | ------- | +| 5.01 | Each contract execution memory limit is 32 MiB. | See [here](https://github.com/cosmos/ibc-go/blob/modules/light-clients/08-wasm/v0.1.0%2Bibc-go-v8.0-wasmvm-v1.5/modules/light-clients/08-wasm/types/config.go#L8). | `Verified` | v0.1.0 | + +## 6 - Security + +| ID | Description | Verification | Status | Release | +| -- | ----------- | ------------ | ------ | ------- | +| 6.01 | The 08-wasm module must ensure that the contracts do not remove or corrupt the stored client state state. | See [here](https://github.com/cosmos/ibc-go/blob/modules/light-clients/08-wasm/v0.1.0%2Bibc-go-v8.0-wasmvm-v1.5/modules/light-clients/08-wasm/types/vm.go#L227). | `Verified` | v0.1.0 | +| 6.02 | The 08-wasm module must ensure that the contracts do not include in the response to sudo, instantiate or migrate calls messages, events or attributes. | See [here](https://github.com/cosmos/ibc-go/blob/modules/light-clients/08-wasm/v0.1.0%2Bibc-go-v8.0-wasmvm-v1.5/modules/light-clients/08-wasm/types/vm.go#L300). | `Verified` | v0.1.0 | + +# External interface requirements + +## 7 - CLI + +### Query + +| ID | Description | Verification | Status | Release | +| -- | ----------- | ------------ | ------ | ------- | +| 6.01 | There shall be a CLI command available to query the bytecode of a light client Wasm contract by checksum. | See [here](https://github.com/cosmos/ibc-go/blob/modules/light-clients/08-wasm/v0.1.0%2Bibc-go-v8.0-wasmvm-v1.5/modules/light-clients/08-wasm/keeper/grpc_query.go#L23). | `Verified` | v0.1.0 | +| 7.02 | There shall be a CLI command available to query the checksums for all deployed light client Wasm contracts. | See [here](https://github.com/cosmos/ibc-go/blob/modules/light-clients/08-wasm/v0.1.0%2Bibc-go-v8.0-wasmvm-v1.5/modules/light-clients/08-wasm/keeper/grpc_query.go#L49). | `Verified` | v0.1.0 | + diff --git a/docs/requirements/ics27-multiplexed-requirements.md b/docs/requirements/ics27-multiplexed-requirements.md new file mode 100644 index 0000000..39f329b --- /dev/null +++ b/docs/requirements/ics27-multiplexed-requirements.md @@ -0,0 +1,101 @@ + + +# Business requirements + +The Interchain Accounts feature has enabled control of an account on a host chain remotely from a controller chain. This has opened up many possibilities for more complex cross-chain workflows beyond token transfer alone, but the current architecture is not easy to use for the case where there are on the order of 1000s of host accounts controlled by many controllers. These requirements aim to alleviate current pain points and enhance the usability of the feature. + +## Problem + +The current pain points for existing ICA users are listed: + +- *Account Flexibility* - Currently host accounts can only be of type `BaseAccount` + +- *Ordered Channels* - Timeouts and subsequent channel closures are time-consuming and difficult to manage + +- *Scalability* - When there are many interchain accounts, and many channels (in the order of 1000s), relayers struggle with the number of queries on startup and performance is compromised. Additionally, account registration being linked to the channel means that multiple packet round trips are required before your host account can execute messages. + +- *Workflows requiring tokens in ICA* - there are no atomic guarantees for a workflow containing multiple applications - i.e. a token transfer followed by an ICA message. The transfer could succeed and the ICA message fail, resulting in an incomplete workflow. + +- *Lack of default callbacks* - users are still relying on custom auth modules for packet callbacks, the callbacks middleware solves this problem but is an add-on to the application rather than a default + +*Whitelisting messages* - this devex is more complicated than having a blacklist + +## Objectives + +- Enable different account types, to be controlled remotely +- To streamline workflows using token transfer and interchain accounts in combination +- To enable scalable and efficient account creation + +## Scope + +| Features | Release | +| --------- | ------- | +| Multi-plexed Interchain Accounts | ibc-go version tbd | + +# User requirements + +## Use cases + +Existing use cases have been detailed in ics27 and ics27 v2 requirements, some other notable use cases used in production are cross chain liquid staking, yield through leveraged lending and cross chain NFT minting. + +### Liquid Staking + +Chains such as Stride, Quicksilver and pStake control Interchain Accounts on a host chain to stake tokens on behalf of their users and mint representative liquid staking tokens so that users can transact with liquid tokens, rather than a locked staked asset but still earn staking rewards through autocompounding on the Interchain Account. More information on Stride's technical architecture can be found [here](https://github.com/Stride-Labs/stride/tree/main?tab=readme-ov-file#strides-technical-architecture). + +### Leveraged Lending + +Nolus enables users to borrow assets and use the inflation from staking rewards to repay the interest on the principle. e.g. a user could deposit 10 OSMO as collateral for a 25 OSMO position. When a user opens a position, an Interchain Account is opened on Osmosis and the loan amount is sent to the account with management of the account through the Nolus chain. + +### Cross-chain NFT minting + +Nomos enable NFTs to be minted on Injective on host accounts controlled from the Xion chain, providing a single interface for users, enabling chain abstraction. + +# Functional requirements + +## Assumptions and dependencies + +- Although having atomic transfer plus action workflows with Interchain Accounts is desirable, it is out of scope for these requirements, as a solution is applicable to applications beyond Interchain Accounts alone. +Migration of an existing Interchain Account using a prior version of Interchain Accounts that does not satisfy these requirements is not required. +- Assumes use of the `x/accounts` sdk module. + +## Features + +### 1 - Configuration + +| ID | Description | Verification | Status | +| -- | ----------- | ------------ | ------ | +| 1.01 | The host chain should accept all message types by default and maintain a blacklist of message types it does not permit | ------------ | ------ | + +### 2 - Registration + +| ID | Description | Verification | Status | +| -- | ----------- | ------------ | ------ | +| 2.01 | The controller of the interchain account must have authority over the account on the host chain to execute messages | -- | ----------- | +| 2.02 | A registered interchain account can be any account type supported by `x/accounts` | ------------ | ------ | + +### 3 - Control + +| ID | Description | Verification | Status | +| -- | ----------- | ------------ | ------ | +| 3.01 | The channel type through which a controller sends transactions to the host should be unordered | ------------ | ------ | +| 3.02 | The message execution order should be determined at the packet level | ------------ | ------ | +| 3.03 | Many controllers can send messages to many host accounts through the same channel | ------------ | ------ | +| 3.04 | The controller of the interchain account should be able to receive information about the balance of the interchain account in the acknowledgment after a transaction was executed by the host | ------------ | ------ | +| 3.05 | The user of the controller should be able to receive all the information contained in the acknowledgment without implementing additional middleware on a per-user basis | ------------ | ------ | +| 3.06 | Callbacks on the packet lifecycle should be supported by default | ------------ | ------ | +| 3.07 | A user can perform module safe queries through a host chain account and return the result in the acknowledgment | ------------ | ------ | + +### 4 - Host execution + +| ID | Description | Verification | Status | +| -- | ----------- | ------------ | ------ | +| 4.01 | It should be possible to ensure a packet lifecycle from a different application completes before a message from a controller is executed | ------------ | ------ | +| 4.02 | It should be possible for a controller to authorise a host account to execute specific actions on a host chain without needing a packet round trip each time (e.g. auto-compounding) | ------------ | ------ | + +# Non-functional requirements + +## 5 - Performance + +| ID | Description | Verification | Status | +| -- | ----------- | ------------ | ------ | +| 5.01 | The number of packet round trips to register an account, load the account with tokens and execute messages on the account should be minimised | ------------ | ------ | diff --git a/docs/requirements/ics27-requirements.md b/docs/requirements/ics27-requirements.md new file mode 100644 index 0000000..df52832 --- /dev/null +++ b/docs/requirements/ics27-requirements.md @@ -0,0 +1,118 @@ +# Business requirements + +> **TL;DR**: Rather than creating an IBC application to expose cross-chain access to every module's features, the Interchain Accounts feature would allow to leverage the capabilities of an account to access a blockchain's application-specific features. + +## Problem + +Without Interchain Accounts, cross-chain access to chain-specific features (such as staking, sending, voting, etc) has to be built as separate applications on top of the IBC TAO (Transport, Authentication, Ordering) layer. Creating new IBC application standards and implementations for each application-specific feature requires considerable time and resources. Interchain Accounts will allow new chain-specific features to be immediately available over IBC. + +## Objectives + +Provide a way to programmatically create accounts on a destination blockchain (called the host) and control them via transactions over IBC. An IBC packet will take a message from the controller blockchain to the host blockchain where it will be executed. This will allow new features on a blockchain to be immediately supported as IBC transactions, since the (destination blockchain) native messages are encapsulated in an IBC packet in an agnostic way. This will allow all of the modules on a chain to take advantage of the network effects created by the IBC ecosystem. + +## Scope + +| Features | Release | +| --------- | ------- | +| Deterministically create a new interchain account over IBC on the host chain. | v3.0.0 | +| Send over IBC a packet that contains the message to be executed by the interchain account on the host. | v3.0.0 | + +# User requirements + +## Use cases + +### Injective x Band Chain + +Currently, Injective sends an IBC transaction to Band Chain via their custom IBC oracle module, which is a data request. When this IBC packet is executed on Band Chain, validators on Band Chain fetch prices for 10 different markets. A random selection of validators will post this selection on-chain. Once a minimum quorum has been reached, an IBC packet is sent to Injective with the prices of markets. The roundtrip latency of this flow is around 30 seconds when things go well (no packet timeouts or delays in validation). + +However, Injective wants to minimise as much as possible the latency between real world price updates and price updates on Injective. They can simplify this two-transaction flow to a single transaction using Interchain Accounts: Injective opens an interchain account on Band Chain, which would be able to pay for a continuous set of update transactions and maintain a standing request for the prices of marke. This would simplify the transaction flow to a single transaction, and introduce a simple flow to update the standing request if necessary. + +### Umee x Cosmos Hub + +Users on the Hub would send their ATOM to Umee. In return, the user gets equivalent amount of meTokens (this token would be a form of a liquid staking token), which could then be staked on the Hub, in some other liquidity pool, etc, in addition to other business logic which Umee could perform on behalf of the users in return for the ATOM. + +Umee then stakes these ATOM tokens on the Hub on behalf of Umee (ATOMs get inflation rewards, etc). Without Interchain Accounts, Umee would have to use validator controlled multisig, because for this flow Umee needs an account on the Hub which can be controlled externally in a decentralised way. With Interchain Accounts, Umee can register an interchain account on the Hub and then receive the staking rewards for the ATOM, figure out distribution back to Umee chain, and send back to the corresponding existing account on Umee. + +### Hub custodial services + +The problem the Cosmos ecosystem faces is fragmentation of services. When a new chain goes live, they need to talk to custodial solutions and exchanges to integrate. Many exchanges and custodial solutions don't want to integrate tens of chains unless paid in advance. + +An alternative is offering the custodial service through the Hub. When a new chain goes live, the tokens of the chain are transferred through IBC to the Hub. This means that the custodial service would just have to integrate with one chain (the Hub), rather with an arbitrary number of them. + +Using Interchain Accounts, a service could be built in which a user sends tokens to an interchain account address on chain `X`, which corresponds to the registered interchain account of chain `X` on the Hub. This account would handle the token transfer to the Hub and then further on to the custodial wallet. + +# Functional requirements + +## Assumptions + +1. Interchain account packets will rarely timeout with application-set values. +2. Cosmos-SDK modules deployed on a chain are not malicious. +3. Authentication modules may implement their own permissioning scheme. + +## Features + +### 1 - Configuration + +| ID | Description | Verification | Status | Release | +| --- | ----------- | ------------ | ------ | ------- | +| 1.01 | A chain shall have the ability to enable or disable Interchain Accounts controller functionality in the genesis state. | The controller parameters have a [flag](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/host/types/host.pb.go#L30) to enable/disable the controller submodule, and this flag [is stored during genesis initialization](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/controller/keeper/params.go#L24). | `Verified` | v3.0.0 | +| 1.02 | A chain shall have the ability to export the Interchain Accounts controller genesis state. | [Acceptance tests](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/controller/keeper/genesis_test.go#L47) | `Verified` | v3.0.0 | +| 1.03 | A chain shall have the ability to initialize the Interchain Accounts controller genesis state. | [Acceptance tests](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/controller/keeper/genesis_test.go#L10) | `Verified` | v3.0.0 | +| 1.04 | A chain shall have the ability to set the Interchain Accounts controller parameters when upgrading or via proposal. | [Acceptance tests](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/module_test.go#L33) | `Verified` | v3.0.0 | +| 1.05 | A chain shall have the ability to enable or disable Interchain Accounts host functionality in the genesis state. | The host parameters have a [flag](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/host/types/host.pb.go#L30) to enable/disable the host submodule, and this flag [is stored during genesis initialization](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/host/keeper/params.go#L31) | `Verified` | v3.0.0 | +| 1.06 | A chain shall have the ability to export the Interchain Accounts host genesis state. | [Acceptance tests](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/host/keeper/genesis_test.go#L46) | `Verified` | v3.0.0 | +| 1.07 | A chain shall have the ability to initialize the Interchain Accounts host genesis state. | [Acceptance tests](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/host/keeper/genesis_test.go#L10) | `Verified` | v3.0.0 | +| 1.08 | A chain shall have the ability to set the Interchain Accounts host parameters when upgrading or via proposal. | [Acceptance tests](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/module_test.go#L33) | `Verified` | v3.0.0 | +| 1.09 | The host chain shall have the ability to whitelist what types of messages or transactions that it chooses to facilitate (e.g. it can decide that registered interchain accounts cannot execute staking messages). | [Acceptance tests](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/host/keeper/params_test.go#L5) | `Verified` | v3.0.0 | + +### 2 - Registration + +| ID | Description | Verification | Status | Release | +| --- | ----------- | ------------ | ------ | ------- | +| 2.01 | The controller chain can programmatically create interchain accounts on the host chain that shall be controlled only by the owner account on the controller chain. | [Acceptance tests](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/host/keeper/account_test.go#L10) | `Verified` | v3.0.0 | +| 2.02 | An interchain account shall be created by any actor without the approval of a third party (e.g. chain governance). | [Acceptance tests](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/host/keeper/account_test.go#L10) | `Verified` | v3.0.0 | + +### 3 - Control + +| ID | Description | Verification | Status | Release | +| --- | ----------- | ------------ | ------ | ------- | +| 3.01 | The controller chain can programmatically control the interchain account by submitting transactions to be executed on the host chain on the behalf of the interchain account. | [Acceptance tests](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/controller/keeper/relay_test.go#L29) | `Verified` | v3.0.0 | +| 3.02 | Under no circumstances shall the owner account on the controller chain irretrievably lose control over the registered interchain account on the host chain. | If the channel between controller and host closes, then [a relayer can open a new channel on the existing controller port](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/controller/keeper/account.go#L16-L17). | `Verified` | v3.0.0 | + +### 4 - Host execution + +| ID | Description | Verification | Status | Release | +| --- | ----------- | ------------ | ------ | ------- | +| 4.01 | Transactions shall be executed by an interchain account on the host chain in exactly the same order in which they are submitted by the controller chain. | IBC packets with SDK messages will be sent from the controller to the host over an [ordered channel](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/controller/keeper/account.go#L60). | `Verified` | v3.0.0 | +| 4.02 | The host shall execute only messages in the allow list. | [Acceptance tests](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/host/keeper/relay_test.go#L340) | `Verified` | v3.0.0 | +| 4.03 | The controller chain shall know how the host chain will handle the transaction bytes in advance. | [Acceptance tests](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go#L109-L133) | `Verified` | v3.0.0 | +| 4.04 | Each transaction submitted by the controller chain shall be executed only once by the interchain account on the host chain. | [Acceptance tests](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/host/keeper/relay_test.go#L248) | `Verified` | v3.0.0 | + +# Non-functional requirements + +## 5 - Security + +| ID | Description | Verification | Status | Release | +| -- | ----------- | ------------ | ------ | ------- | +| 5.01 | There shall be no means for the interchain account to execute transactions that have not been submitted first by the respective owner account on the controller chain. |[Acceptance tests](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/host/keeper/relay_test.go#L361) | `Verified` | v3.0.0 | +| 5.02 | Every interchain account on the host chain shall have one and only one respective owner account on the controller chain. | The interchain account on the host [is generated using the host connection ID and the address of the owner on the controller](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/host/keeper/handshake.go#L73-L76). | `Verified` | v3.0.0 | +| 5.03 | The owner account on a controller chain shall not be able to control interchain accounts registered by other owner accounts on the same controller chain. | Before the host logic executes the received messages, it [retrieves the interchain account associated with the port ID](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/host/keeper/relay.go#L94) over which it received the message. For owner address B to be able to execute a message with the interchain account registered with owner address A, it would need to send the messages over a channel that binds to a port ID that contains the owner address A, and since we have assumption number 3, this should not be allowed by applications. | `Verified` | v3.0.0 | +| 5.04 | A controller chain shall not be able to control interchain accounts registered by owner accounts on different controller chains. | Same as 5.03. | `Verified` | v3.0.0 | +| 5.05 | Each interchain account on the host chain is owned by a single owner account on the controller chain. It shall not be possible to register a second interchain account with the same owner account on the controller chain. | [Acceptance tests](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/controller/keeper/account_test.go#L42) | `Verified` | v3.0.0 | + +# External interface requirements + +## 6 - CLI + +| ID | Description | Verification | Status | Release | +| -- | ----------- | ------------ | ------ | ------- | +| 6.01 | There shall be a CLI command available to query the host parameters. | [Acceptance tests](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/host/client/cli/query.go#L22) | `Verified` | v3.0.0 | +| 6.02 | There shall be a CLI command available to query the receive packet events on the host chain to check the result of the execution of the message on the host. | [Acceptance tests](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/host/client/cli/query.go#L51) | `Verified` | v3.0.0 | +| 6.03 | There shall be a CLI command available to query the controller parameters. | [Acceptance tests](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/controller/client/cli/query.go#L15) | `Verified` | v3.0.0 | + +## 7 - Application developers + +| ID | Description | Verification | Status | Release | +| -- | ----------- | ------------ | ------ | ------- | +| 7.01 | An IBC application developer shall be able to develop an Interchain Accounts authentication module that can register interchain accounts. | The [`RegisterInterchainAccount` function](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/controller/keeper/account.go#L18) is the entry point to registering an interchain account. | `Verified` | v3.0.0 | +| 7.02 | An IBC application developer shall be able to develop an Interchain Accounts authentication module that can send messages from the controller to the host. | The [`SendTx` function](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/controller/keeper/relay.go#L18) takes pre-built packet data containing messages to be executed on the host chain from an authentication module and attempts to send the packet. | `Verified` | v3.0.0 | diff --git a/docs/requirements/ics27-v2-requirements.md b/docs/requirements/ics27-v2-requirements.md new file mode 100644 index 0000000..ac25229 --- /dev/null +++ b/docs/requirements/ics27-v2-requirements.md @@ -0,0 +1,87 @@ +# Business requirements + +> **TL;DR**: The lack of a default underlying app (previously called the *authentication module*), and the need to separate application and authentication concerns were recognised as primary reasons for the slow adoption of ICS 27 Interchain Accounts (ICA). + +## Problem + +We believe that the lack of controller chains so far has been because: + +- We did not develop a standardized authentication module, which created a bottleneck for chains looking to integrate the controller submodule. +- We did not have a clear understanding of all the use cases ICA would facilitate. +- We expected more chains to want to design custom authentication for ICA. +- Coupling ICA authentication and application logic was a misdesign. + +## Objectives + +- Make controller functionality integration in a chain easier. +- Introduce a message server in the controller submodule that exposes APIs for interchain account registration and control. +- Once application callbacks are implemented (via ADR 008) deprecate the APIs introduced in ibc-go v3.0.0. + +## Scope + +| Features | Release | +| --------- | ------- | +| Register interchain accounts and send transactions to host chain via message passing. | v6.0.0 | +| Support application callbacks with message server in controller submodule (requires ADR 008). | N/A | + +# User requirements + +## Use cases + +### Custom authentication module needs to access IBC packet callbacks + +Application developers of custom authentication modules that wish to consume IBC packet callbacks and react upon packet acknowledgements or timeouts must continue using the controller submodule's legacy APIs. + +### Custom authentication module does not need access to IBC packet callbacks + +The authentication module should interact with the controller submodule via the message server for registering interchain accounts and sending messages to it. + +### No need for custom authentication module + +Chains not only want individual accounts to be able to use Interchain Accounts, but also for generic Cosmos SDK authentication modules such as `x/gov` and `x/group` to be able to register an interchain account and send messages. An example use case with `x/gov`: the Cosmos Hub (controller), upon governance authorization, sends some of its inflationary rewards to Osmosis (host) to provide liquidity and purchase ATOM GAMM shares, which are then sent back to the Hub in one flow. + +# Functional requirements + +## Assumptions + +No further assumptions besides the ones listed in the v1 requirements document. + +## Known limitations + +1. Custom authentication modules that wish to consume IBC packet callbacks need to use the legacy APIs until ADR 008 is implemented. + +## Terminology + +See section [Definitions](https://github.com/cosmos/ibc/blob/main/spec/app/ics-027-interchain-accounts/README.md#definitions) in ICS 27 spec. + +## Features + +### 1 - Registration + +| ID | Description | Verification | Status | Release | +| --- | ----------- | ------------ | ------ | ------- | +| 1.01 | An application shall have the ability to use an RPC endpoint to create interchain accounts on the host chain. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v6.0.0/modules/apps/27-interchain-accounts/controller/keeper/msg_server_test.go#L31) | `Verified` | v6.0.0 | + +### 2 - Control + +| ID | Description | Verification | Status | Release | +| --- | ----------- | ------------ | ------ | ------- | +| 2.01 | An application shall have the ability to use an RPC endpoint to submit transactions to be executed on the host chain on behalf of the interchain account. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v6.0.0/modules/apps/27-interchain-accounts/controller/keeper/msg_server_test.go#L31) | `Verified` | v6.0.0 | + +# Non-functional requirements + +## 3 - Migration + +| ID | Description | Verification | Status | Release | +| -- | ----------- | ------------ | ------ | ------- | +| 3.01 | Chains shall be able to run a migration to assign ownership of the channel capability of a custom authentication module to the ICS 27 controller submodule. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v6.0.0/modules/apps/27-interchain-accounts/controller/migrations/v6/migrations_test.go#L89) | `Verified` | v6.0.0 | + +# External interface requirements + +## 4 - CLI + +### Transaction + +| ID | Description | Verification | Status | Release | +| -- | ----------- | ------------ | ------ | ------- | +| 4.01 | There shall be a CLI command available to generate the Interchain Accounts packet data required to send. | [CLI](https://github.com/cosmos/ibc-go/blob/v6.0.0/modules/apps/27-interchain-accounts/host/client/cli/tx.go#L21) | `Verified` | v6.0.0 | diff --git a/docs/requirements/ics29-v1-requirements.md b/docs/requirements/ics29-v1-requirements.md new file mode 100644 index 0000000..9fef968 --- /dev/null +++ b/docs/requirements/ics29-v1-requirements.md @@ -0,0 +1,177 @@ +# Business requirements + +> **TL;DR**: There is no general incentivization mechanism to reward relayers for their service ensuring Interchain liveness. + +## Problem + +The Interchain dream will never scale unless there is a clear way to incentivize relayers. An interface that can be easily adopted by any applications is needed, without precluding chains that do not use tokens. + +Though IBC does not depend on relayer operators for transaction verification, the relayer infrastructure ensures liveness of the Interchain network — operators listen for packets sent through channels opened between chains, and perform the vital service of ferrying these packets (and proof of the transaction on the sending chain/receipt on the receiving chain) to the clients on each side of the channel. Though relaying is permissionless and completely decentralized and accessible, it does come with operational costs. Running full nodes to query transaction proofs and paying for transaction fees associated with IBC packets are two of the primary cost burdens which have driven the overall discussion on a general incentivization mechanism for relayers. + +## Objectives + +Provide a general fee payment design that can be adopted by any ICS application protocol as middleware. + +## Scope + +| Features | Release | +| --------- | ------- | +| Incentivize timely delivery of a packet (successfully submit `MsgRecvPacket`). | v1 | +| Incentivize relaying acknowledgement for a packet (successfully submit `MsgAcknowledgement`). | v1 | +| Incentivize relaying timeout for a packet when the timeout has expired before packet is delivered (successfully submit `MsgTimeout`). | v1 | +| Enable permissionless or permissioned relaying. | v1 | +| Allow opt-in for chains (an application with fee support on chain A could connect to the couterparty application without fee support on chain B). | v1 | + +# User requirements + +## Use cases + +- A packet already sent may be incentivized for one or more of the messages `MsgRecvPacket`, `MsgAcknowledgement`, `MsgTimeout`. +- The next packet to be sent may be incentivized for one or more of the messages `MsgRecvPacket`, `MsgAcknowledgement`, `MsgTimeout`. + +# Functional requirements + +## Assumptions + +1. Relayer addresses should not be forgeable. + +## Known limitations + +1. Without channel upgradability it is not possible to use fee incentivization on an existing channel. + +## Terminology + +See section [Definitions](https://github.com/cosmos/ibc/tree/main/spec/app/ics-029-fee-payment#definitions) in ICS 29 spec. + +## Features + +### 1 - Configuration + +| ID | Description | Verification | Status | +| --- | ----------- | ------------ | ------ | +| 1.02 | A chain shall have the ability to export the fee middleware genesis state. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/genesis_test.go#L69) | `Verified` | +| 1.03 | A chain shall have the ability to initialize the fee middleware genesis state. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/genesis_test.go#L9) | `Verified` | + +### 2 - Payee registration + +| ID | Description | Verification | Status | +| --- | ----------- | ------------ | ------ | +| 2.01 | A relayer shall have the ability to register for a channel an optional payee address to which fees shall be distributed for successful relaying `MsgAcknowledgement` and `MsgTimeout`. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/msg_server_test.go#L23) | `Verified` | +| 2.02 | The payee address shall only be registered if the channel exist. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/msg_server_test.go#L28) | `Verified` | +| 2.03 | The payee address shall only be registered if the channel is fee enabled. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/msg_server_test.go#L35) | `Verified` | +| 2.04 | The payee address shall only be registered if the address is a valid `Bech32` address. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/msg_server_test.go#L42) | `Verified` | +| 2.05 | The payee address shall only be registered if the address is not blocked. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/msg_server_test.go#L49) | `Verified` | + +### 3 - Counterparty payee registration + +| ID | Description | Verification | Status | +| --- | ----------- | ------------ | ------ | +| 3.01 | A relayer shall have the ability to register a counterparty payee to which fees shall be distributed for successful relaying of `MsgRecvPacket`. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/msg_server_test.go#L102) | `Verified` | +| 3.02 | The counterparty payee address shall only be registered if the channel exists. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/msg_server_test.go#L115) | `Verified` | +| 3.03 | The counterparty payee address shall only be registered if the channel is fee enabled. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/msg_server_test.go#L122) | `Verified` | +| 3.04 | The counterparty payee address may be an arbitrary string (since the counterparty chain may not be Cosmos-SDK based). | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/msg_server_test.go#L42) | `Verified` | + +### 4 - Fee incentivization + +#### Synchronously + +| ID | Description | Verification | Status | +| --- | ----------- | ------------ | ------ | +| 4.01 | If a channel is fee-enabled, the next sequence packet may be incentivized. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/msg_server_test.go#L178) | `Verified` | +| 4.02 | If the fee module is not locked, the next sequence packet may be incentivized. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/msg_server_test.go#L210) | `Verified` | +| 4.03 | The next sequence packet may be incentivized for some or all of `MsgRecvPacket`, `MsgAcknowledgement`, `MsgTimeout`. | Fees for any of the messages may be zero, but [not all of them](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/types/fee.go#L87). | `Verified` | +| 4.04 | A blocked account address shall not be allowed to incentivize the next sequence packet. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/msg_server_test.go#L239) | `Verified` | +| 4.05 | A non-valid `Bech32` account address shall not be allowed to incentivize the next sequence packet. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/msg_server_test.go#L225) | `Verified` | +| 4.06 | The next sequence packet may be incentivized by multiple account addresses. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/msg_server_test.go#L183) | `Verified` | +| 4.07 | The next sequence packet shall only be incentivized with valid coin denominations. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/msg_server_test.go#L246-L266) | `Verified` | + +#### Asynchronously + +| ID | Description | Verification | Status | +| --- | ----------- | ------------ | ------ | +| 4.08 | Only packets that have been sent may be incentivized. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/msg_server_test.go#L376) | `Verified` | +| 4.09 | A packet sent may be incentivized for some or all of `MsgRecvPacket`, `MsgAcknowledgement`, `MsgTimeout`. | Fees for any of the messages may be zero, but [not all of them](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/types/fee.go#L87). | `Verified` | +| 4.10 | Only packets that have not gone through the packet life cycle (i.e. have not been acknowledged or timed out yet) may be incentivized. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/msg_server_test.go#L382-L410) | `Verified` | +| 4.11 | If a channel exists, a packet sent may be incentivized. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/msg_server_test.go#L365) | `Verified` | +| 4.12 | If a channel is fee-enabled, a packet sent may be incentivized. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/msg_server_test.go#L357) | `Verified` | +| 4.13 | If the fee module is not locked, a packet sent may be incentivized. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/msg_server_test.go#L350) | `Verified` | +| 4.14 | A non-valid `Bech32` account address shall not be allowed to incentivize a packet sent. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/msg_server_test.go#L412) | `Verified` | +| 4.15 | An account that does not exist shall not be allowed to incentivize a packet sent. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/msg_server_test.go#L419) | `Verified` | +| 4.16 | A blocked account shall not be allowed to incentivize a packet sent. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/msg_server_test.go#L426) | `Verified` | + +### 5 - Fee distribution + +| ID | Description | Verification | Status | +| --- | ----------- | ------------ | ------ | +| 5.01 | Fee distribution shall occur on the source chain from which packets originate. | Either in [`OnAcknowledgementPacket`](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/ibc_middleware.go#L288) or in [`OnTimeoutPacket`](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/ibc_middleware.go#L330). | `Verified` | + +#### `OnAcknowledgementPacket` + +| ID | Description | Verification | Status | +| --- | ----------- | ------------ | ------ | +| 5.02 | Fees for successful relaying of `MsgAcknowledgement` shall be distributed to the relayer address (or its associated payee address if one has been registered). | If a payee address exists for the relayer address, then [fees are distributed to the payee address](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/ibc_middleware.go#L288); otherwise [fees are distributed to the relayer address](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/ibc_middleware.go#L277). | `Verified` | +| 5.03 | Fees for successful relaying of `MsgRecvPacket` shall be distributed to the payee address of the relayer address. | [Fees are distributed to the payee address registered on the counterparty chain](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/escrow.go#L93) if it is a valid `Bech32` address and is not a blocked address. | `Verified` | + +#### `OnTimeoutPacket` + +| ID | Description | Verification | Status | +| --- | ----------- | ------------ | ------ | +| 5.05 | Fees for successful relaying of `MsgTimeout` shall be distributed to the relayer address (or its associated payee address if one has been registered). | If a payee address exists for the relayer address, then [fees are distributed to the payee address](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/ibc_middleware.go#L330); otherwise [fees are distributed to the relayer address](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/ibc_middleware.go#L319). | `Verified` | + +### 6 - Fee refunding + +#### `OnAcknowledgementPacket` + +| ID | Description | Verification | Status | +| --- | ----------- | ------------ | ------ | +| 6.01 | On successful processing of `MsgAcknowledgement`, fees for successful relay of `MsgTimeout` shall be refunded. | [Fees are refunded is the refund address is a valid `Bech32` address](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/escrow.go#L103). | `Verified` | +| 6.02 | On successful processing of `MsgAcknowledgement`, if fees for successful relay of `MsgRecvPacket` cannot be distributed, then they should be refunded. | [Fees are refunded](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/escrow.go#L96) if the payee address registered on the counterparty chain for the relayer is either [invalid](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/escrow_test.go#L105) or a [blocked address](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/escrow_test.go#L120). | `Verified` | + +#### `OnTimeoutPacket` + +| ID | Description | Verification | Status | +| --- | ----------- | ------------ | ------ | +| 6.03 | On successful processing of `MsgTimeout`, fees for successful relay of `MsgRecvPacket` shall be refunded. | [Fees are refunded is the refund address is a valid `Bech32` address](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/escrow.go#L146). | `Verified` | +| 6.04 | On successful processing of `MsgTimeout`, fees for successful relay of `MsgAcknowledgement` shall be refunded. | [Fees are refunded is the refund address is a valid `Bech32` address](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/escrow.go#L149). | `Verified` | + +#### Channel closure + +| ID | Description | Verification | Status | +| --- | ----------- | ------------ | ------ | +| 6.04 | Packet fees are refunded on channel closure. | Both on [`OnChanCloseInit`](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/ibc_middleware.go#L182) and [`OnChanCloseConfirm`](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/ibc_middleware.go#L207). | `Verified` | + +# Non-functional requirements + +## 7 - Security + +| ID | Description | Verification | Status | +| -- | ----------- | ------------ | ------ | +| 7.01 | If the escrow account does not have sufficient funds while distributing fees in `OnAcknowledgementPacket`, the fee module shall become locked. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/escrow_test.go#L85) | `Verified` | +| 7.02 | If the escrow account does not have sufficient funds while distributing fees in `OnTimeoutPacket`, the fee module shall become locked. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/escrow_test.go#L236) | `Verified` | +| 7.03 | If the escrow account does not have sufficient funds while refunding fees in channel closure, the fee module shall become locked. | [Acceptance test](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/keeper/escrow_test.go#L377) | `Verified` | + +# External interface requirements + +## 8 - CLI + +### Query + +| ID | Description | Verification | Status | +| -- | ----------- | ------------ | ------ | +| 8.01 | There shall be a CLI command available to query for the incentivization of an unrelayed packet by port ID, channel ID and packet sequence. | [CLI](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/client/cli/query.go#L64) | `Verified` | +| 8.02 | There shall be a CLI command to query for the incentivization of all unrelayed packets on all open channels. | [CLI](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/client/cli/query.go#L64) | `Verified` | +| 8.03 | There shall be a CLI command available to query for the total fees for `MsgRecvPacket` for a given port ID, channel ID and packet sequence. | [CLI](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/client/cli/query.go#L105) | `Verified` | +| 8.04 | There shall be a CLI command available to query for the total fees for `MsgAcknowledgement` for a given port ID, channel ID and packet sequence. | [CLI](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/client/cli/query.go#L151) | `Verified` | +| 8.05 | There shall be a CLI command available to query for the total fees for `MsgTimeout` for a given port ID, channel ID and packet sequence. | [CLI](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/client/cli/query.go#L197) | `Verified` | +| 8.06 | There shall be a CLI command available to query for the payee address registered for a relayer for a specific channel ID. | [CLI](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/client/cli/query.go#243) | `Verified` | +| 8.07 | There shall be a CLI command available to query for the counterparty payee address registered for a relayer for a specific channel ID. | [CLI](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/client/cli/query.go#282) | `Verified` | +| 8.08 | There shall be a CLI command available to query if a channel is fee-enabled for a port ID and channel ID. | [CLI](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/client/cli/query.go#L362) | `Verified` | +| 8.09 | There shall be a CLI command available to query for all the unrelayed incentivized packets in a channel by a port ID and channel ID. | [CLI](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/client/cli/query.go#L397) | `Verified` | + +### Transaction + +| ID | Description | Verification | Status | +| -- | ----------- | ------------ | ------ | +| 8.10 | There shall be a CLI command available to register a payee address for a channel by port ID and channel ID. | [CLI](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/client/cli/tx.go#L26) | `Verified` | +| 8.11 | There shall be a CLI command available to register a counterparty payee address for a channel by port ID and channel ID. | [CLI](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/client/cli/tx.go#L51) | `Verified` | +| 8.12 | There shall be a CLI command available to incentivize a packet by port ID, channel ID and packet sequence. | [CLI](https://github.com/cosmos/ibc-go/blob/v4.0.0/modules/apps/29-fee/client/cli/tx.go#L76) | `Verified` | diff --git a/docs/requirements/localhost-requirements.md b/docs/requirements/localhost-requirements.md new file mode 100644 index 0000000..4ee9dd8 --- /dev/null +++ b/docs/requirements/localhost-requirements.md @@ -0,0 +1,69 @@ + + +# Business requirements + +The localhost client provides a unified interface to interact with different applications on a single chain. + +## Problem + +Currently applications, or smart contracts, on a single chain cannot communicate through a single interface. This means a user must interact with each application or smart contract directly which can be cumbersome, especially when a user wants to interact with many applications on one chain. + +## Objectives + +To provide a single interface in line with IBC application layer semantics, that enables a user to interact with multiple different applications or smart contracts remotely on a given blockchain. + +## Scope + +| Features | Release | +| --------- | ------- | +| To interface with applications or smart contracts on a blockchain through the IBC application layer | v7.1.0 | +| To use the localhost client without requiring third party relayer infrastructure | N/A | + +# User requirements + +## Use cases + +### Interacting with Smart Contracts + +Many IBC connected blockchains utilise smart contracts, these could be written in rust, solidity or javascript depending on the smart contract platform being used. Through the localhost client, a user can interact with and call into smart contracts by sending ibc messages from the localhost client to the smart contract. + +On Agoric, a planned use case is for managing delegations and staking with smart contracts on a given chain. MsgDelegate, MsgUndelegate, MsgBeginRedelegate messages would be sent using the localhost client to the staking module. The same interface could be used to manage staking cross chain, sending the same messages through IBC to an interchain account. + +### Interoperability Infrastructure + +Polymer plans to leverage the localhost client with multiple connections as part of their architecture to connect chains not using Tendermint or the Cosmos SDK to IBC enabled chains. Polymer will act as a hub connecting multiple blockchains together. + +# Functional requirements + +## Assumptions and dependencies + +1. Chains utilising the localhost client must be using an ibc-go release in the v7 line. +2. The channel behaviour of a localhost client will be as ICS 04 specifies. + +## Features + +### 1 - Configuration + +| ID | Description | Verification | Status | Release | +| -- | ----------- | ------------ | ------ | ------- | +| 1.01 | The localhost client shall have a client ID of the string `09-localhost`. | [Localhost client is created with client ID `09-localhost`](https://github.com/cosmos/ibc-go/blob/release/v7.1.x/modules/core/02-client/keeper/keeper.go#L60). | `Verified` | v7.1.0 | +| 1.02 | The localhost client shall have a sentinel connection ID of the string `connection-localhost`. | [Creation of sentinel connection with ID `connection-localhost`](https://github.com/cosmos/ibc-go/blob/release/v7.1.x/modules/core/03-connection/keeper/keeper.go#L200). | `Verified` | v7.1.0 | +| 1.03 | Only 1 localhost connection is required | Localhost connection handshakes are forbidden in [Init](https://github.com/cosmos/ibc-go/blob/release/v7.1.x/modules/core/03-connection/types/msgs.go#L47) and [Try](https://github.com/cosmos/ibc-go/blob/release/v7.1.x/modules/core/03-connection/types/msgs.go#L110). | `Verified` | v7.1.0 | +| 1.04 | When the localhost client is initialised the consensus state must be `nil`. | [Error is returned if consensus state is not `nil`](https://github.com/cosmos/ibc-go/blob/release/v7.1.x/modules/light-clients/09-localhost/client_state.go#L57). | `Verified` | v7.1.0 | +| 1.05 | The localhost client can be added to a chain through an upgrade. | [Automatic migration handler configured in core IBC module to set the localhost `ClientState` and sentinel `ConnectionEnd` in state.](https://github.com/cosmos/ibc-go/blob/release/v7.1.x/modules/core/module.go#L132-L145). | `Verified` | v7.1.0 | +| 1.06 | A chain can enable the localhost client by initialising the client in the genesis state. | [`InitGenesis` handler in 02-client](https://github.com/cosmos/ibc-go/blob/release/v7.1.x/modules/core/02-client/genesis.go#L52) and [`InitGenesis` handler in 03-connection](https://github.com/cosmos/ibc-go/blob/release/v7.1.x/modules/core/03-connection/genesis.go#L23). | `Verified` | v7.1.0 | + +### 2 - Operation + +| ID | Description | Verification | Status | Release | +| -- | ----------- | ------------ | ------ | ------- | +| 2.01 | A user of the localhost client can send IBC messages to an application on the same chain. | [e2e test with transfer](https://github.com/cosmos/ibc-go/blob/main/e2e/tests/transfer/localhost_test.go#L32). | `Verified`| v7.1.0 | +| 2.02 | A user can use the localhost client through the existing IBC application module interfaces. | [e2e test with transfer](https://github.com/cosmos/ibc-go/blob/main/e2e/tests/transfer/localhost_test.go#L32). | `Verified` | v7.1.0 | + +# External interface requirements + +## 3 - CLI + +| ID | Description | Verification | Status | Release | +| -- | ----------- | ------------ | ------ | ------- | +| 3.01 | Existing CLI interfaces used with IBC application modules shall be usable with the localhost client. | Manual test | `Verified` | v7.1.0 | diff --git a/docs/requirements/path-unwinding-forwarding-requirements.md b/docs/requirements/path-unwinding-forwarding-requirements.md new file mode 100644 index 0000000..b405fa9 --- /dev/null +++ b/docs/requirements/path-unwinding-forwarding-requirements.md @@ -0,0 +1,99 @@ + + +# Business requirements + +The implementation of fungible token path unwinding vastly simplifies token transfers for end users. End users are unlikely to understand IBC denominations in great detail, and the consequences a direct transfer from chain A, to chain B can have on the fungibility of a token at the destination chain B, when the token sent is not a native or originating token from chain A, and is native to another chain, e.g. chain C. + +Path unwinding reduces the complexity of token transfer for the end user; a user simply needs to choose the final destination for their tokens and the complexity of determining the optimal route is abstracted away. This is a huge user experience improvement. + +In addition to unwinding, when a user receives their token on a destination chain, they then want to use the token in some way. By enabling token forwarding, a user can receive a token, perform some action with that token, for example a swap, and then send the token onto another chain. We observe that the complexity of IBC is increasingly being abstracted away from end users and automating workflows such as transfer, swap and forward with a single signed transaction significantly enhances usability. + +## Problem + +A fungible token A transferred from chain A to chain B is an IBC denomination at chain B, where the IBC denom trace records the path the token has travelled to reach its destination chain. + +A user now wants to send this IBC denomination of token A, originating from chain A, onto another chain, chain C. If a user transfers token A on chain B directly to chain C, it will not be fungible with token A sent directly from chain A to chain C. This is because the IBC denomination of token A on chain C is different in both cases due to token A travelling along different paths to reach the same destination. This is the most simple case of the problem involving only 3 chains. + +However, this problem is prevalent within the ecosystem and there are cases of IBC denominations on chains with >2 hops in the path. + +Regarding forwarding, if a user wants to transfer tokens between chains, then perform an action with those tokens, without forwarding, a user would have to sign each transaction on every chain and wait for the tokens to arrive at the destination before performing the next action. This is time consuming and a provides a poor user experience, a user also cannot just specify the desired outcome of their workflow in a trivial way. + +## Objectives + +To enable end users to automatically and atomically unwind fungible tokens when they specify a destination chain, so that tokens arrive at the destination chain with only 1 hop in the path and to be able to forward the token to another destination after it has been unwound. + +## Scope + +| Features | Release | +| --------- | ------- | +| Automatic and atomic path unwinding for fungible tokens suitable for end users initiating a transfer | v9.0.0 | +| Token forwarding for fungible tokens for end users initiating a transfer | v9.0.0 | + +# User requirements + +## Use cases + +### 1. Moving non-native assets between DeFi opportunities on different chains + +Users transfer tokens from an origin chain to a DeFi chain to benefit from yield opportunities or other use cases on that chain, different from the origin chain. A better yield opportunity could then arise and a user would want to move the tokens to another chain to take advantage of this opportunity. Rather than having to manually route the tokens back through the originating chain onto the new chain, it would be much simpler if they could only be concerned with the final destination they want the tokens to arrive at. + +For example, ATOM is native to the Cosmos Hub, a user could transfer ATOM to Osmosis and deposit in a liquidity pool to earn yield on this token. After depositing their ATOM into a pool on Osmosis, a better yield opportunity could arise, for example a better pool APY on another Cosmos DEX, e.g. Crescent or a better yield on lending ATOM on Umee. The user would then want to transfer the ATOM from Osmosis to Crescent (or Umee). + +### 2. Transferring liquid staking derivatives + +Liquid staking derivatives are minted on liquid staking zones and represent a staked asset. There is a common misconception from users that these derivatives originate on the chain of the original staked token. This results in users sending derivatives back to the chain of the natively staked token and then onto the next destination. + +For examples, a user has stATOM on Osmosis, they want to move the stATOM to Evmos, instead of going from Osmosis --> Stride --> Evmos, a user tries to unwind themself and routes the tokens Osmosis --> Cosmos Hub --> Evmos. + +### 3. Moving a token that originated from an interoperability zone or chain, or asset issuer + +Tokens that originate from other blockchain ecosystems that don't yet support IBC, flow into the Cosmos ecosystem through interoperability zones. These tokens are then sent onto other chains with a specific use case for these tokens and a user could want to move this token from one chain to another. + +For example, ETH from Ethereum flows into Osmosis via Axelar, where the final step moving ETH from Ethereum to Osmosis uses an ICS-20 transfer from Axelar to Osmosis. A user may then want to move ETH from Osmosis onto another chain, for example Injective. However, the path with 1 hop would be a transfer from Axelar to Injective. + +### 4. Moving a token from one chain to another, swapping the token and transferring it onwards to a new destination + +A user on one chain, for example the Cosmos Hub holds an asset, e.g. ATOM and wants to instead have AKT on Akash. The user must transfer to ATOM to a DEX chain, swap the ATOM for AKT and then send the AKT onwards to Akash. + +# Functional requirements + +## Assumptions and dependencies + +1. A functional relayer implementation is required for this feature. +2. Routing information for the final hop for unwinding or for forwarding is configured for the user through a front end client, or configured by another means using off-chain data. +3. The feature will have no impact on the existing function of a typical ICS-20 transfer where a native token is sent to another chain. +4. Fees are not within the scope of these requirements. +5. The functionality to enable a specific action before forwarding is not in scope of these requirements. +6. If a transfer contains multiple unique tokens, the unwinding and forwarding functionalities only need to support unwinding or forwarding through the same path, i.e. if there is a transfer containing a native token and 1 hop denom being sent from source to destination, both tokens would always go through the same path. In the future, it may be desirable for different tokens to be unwound through different paths, to support actions such as atomic transfer and supply liquidity to a liquidity pool, but it is currently out of scope for the first version of this feature. + +## Features + +| ID | Description | Verification | Status | +| -- | ----------- | ------------ | ------ | +| 1.01 | When a user initiates a transfer to a destination chain with an IBC denom with > 1 hop, the token shall be sent back to its originating chain before being sent onto the destination as a user selected option | | `Draft` | +| 1.02 | If a user wants to unwind tokens, then they can select this as option for the transfer | | `Draft` | +| 1.03 | The unwinding shall completely succeed or the tokens are recoverable on the chain they were sent from by the user | | `Draft` | +| 1.04 | When unwinding is used in combination with forwarding, both the unwind and forwarding should succeed or the tokens should be recoverable on the sending chain | | `Draft` | +| 1.05 | The forwarding mechanism shall allow a user to transfer tokens beyond the first destination for those tokens | | `Draft` | +| 1.06 | The forwarding mechanism shall allow tokens to have some action performed on them before being sent onto a new destination | | `Draft` | +| 1.07 | The routing information for forwarding or to go from unwound token to destination must be input with the initial transfer | | `Draft` | +| 1.08 | If an intermediate chain does not have the unwinding or forwarding functionality, the tokens must be recoverable on the sending chain | | `Draft` | +| 1.09 | If unwinding or forwarding fails, then the reason for the failure should be returned in an error | | `Draft` | +| 1.10 | When unwinding, it should be possible for the forwarding path to be evaluated implicitly from introspecting the denomination trace or to be explicitly input as forwarding hops by the user | | `Draft` | +| 1.11 | When using unwinding in combination with x/authz, a granter can specify the allowed forwarding paths | | `Draft` | + +# External interface requirements + +| ID | Description | Verification | Status | +| -- | ----------- | ------------ | ------ | +| 2.01 | There must be a CLI interface to initiate a transfer using path unwinding | | `Draft` | +| 2.02 | There must be a CLI interface to initiate a transfer using forwarding | | `Draft` | +| 2.03 | There must be a CLI interface to initiate a transfer using unwinding and forwarding in combination | | `Draft` | + +# Non-functional requirements + +## Security + +| ID | Description | Verification | Status | +| -- | ----------- | ------------ | ------ | +| 3.01 | It must not be possible for a users tokens to be intercepted by another actor during path-unwinding or token forwarding | | `Draft` | diff --git a/docs/requirements/requirements-template.md b/docs/requirements/requirements-template.md new file mode 100644 index 0000000..37d4274 --- /dev/null +++ b/docs/requirements/requirements-template.md @@ -0,0 +1,53 @@ + + +# Business requirements + + + + + +## Problem + + + +## Objectives + + + +## Scope + + + + + +# User requirements + +## Use cases + + + +# Functional requirements + + + +## Assumptions and dependencies + + + +## Features + + + +# External interface requirements + + + +# Non-functional requirements + + \ No newline at end of file diff --git a/docs/sidebars.js b/docs/sidebars.js new file mode 100644 index 0000000..2abff26 --- /dev/null +++ b/docs/sidebars.js @@ -0,0 +1,67 @@ +/** + * Creating a sidebar enables you to: + - create an ordered group of docs + - render a sidebar for each doc of that group + - provide next/previous navigation + + The sidebars can be generated from the filesystem, or explicitly defined here. + + Create as many sidebars as you want. + */ + +// @ts-check + +/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ +const sidebars = { + // By default, Docusaurus generates a sidebar from the docs folder structure + defaultSidebar: [ + { type: "autogenerated", dirName: "." }, + { + type: "category", + label: "Resources", + collapsed: false, + items: [ + { + type: "link", + label: "IBC Specification", + href: "https://github.com/cosmos/ibc", + }, + { + type: "link", + label: "Protobuf Documentation", + href: "https://buf.build/cosmos/ibc/docs/main", + }, + { + type: "link", + label: "Developer Portal", + href: "https://tutorials.cosmos.network", + }, + { + type: "link", + label: "Awesome Cosmos", + href: "https://github.com/cosmos/awesome-cosmos", + }, + { + type: "link", + label: "Roadmap", + href: "https://github.com/orgs/cosmos/projects/38/views/14", + }, + ], + }, + ], + + // But you can create a sidebar manually + /* + tutorialSidebar: [ + 'intro', + 'hello', + { + type: 'category', + label: 'Tutorial', + items: ['tutorial-basics/create-a-document'], + }, + ], + */ +}; + +module.exports = sidebars; diff --git a/docs/src/components/HighlightBox.jsx b/docs/src/components/HighlightBox.jsx new file mode 100644 index 0000000..d27e662 --- /dev/null +++ b/docs/src/components/HighlightBox.jsx @@ -0,0 +1,157 @@ +import React from "react"; + +import ReadingIcon from "@site/static/img/icons/hi-reading-icon.svg"; +import PrereqIcon from "@site/static/img/icons/hi-prerequisite-icon.svg"; +import TargetIcon from "@site/static/img/icons/hi-target-icon.svg"; +import StarIcon from "@site/static/img/icons/hi-star-icon.svg"; +import TipIcon from "@site/static/img/icons/hi-tip-icon.svg"; +import NoteIcon from "@site/static/img/icons/hi-note-icon.svg"; +import CoffeeIcon from "@site/static/img/icons/hi-coffee-icon.svg"; +import InfoIcon from "@site/static/img/icons/hi-info-icon.svg"; +import WarnIcon from "@site/static/img/icons/hi-warn-icon.svg"; + +const typeToStyles = { + tip: { + color1: "#336667", + color2: "#00B067", + icon: , + darkMode: true, + }, + reading: { + color1: "#F46800", + color2: "#F24CF4", + icon: , + darkMode: false, + }, + info: { + color1: "#336667", + color2: "#00B067", + icon: , + darkMode: true, + }, + warn: { + color1: "#00B067", + color2: "#FFD303", + icon: , + darkMode: false, + }, + warning: { + color1: "#00B067", + color2: "#FFD303", + icon: , + darkMode: false, + }, + synopsis: { + color1: "#1c1c1c", + color2: "#1c1c1c", + icon: null, + darkMode: true, + }, + prerequisite: { + color1: "lightgray", + color2: "lightgray", + icon: , + darkMode: false, + }, + learning: { + color1: "#6836D0", + color2: "#05BDFC", + icon: , + darkMode: true, + }, + "best-practice": { + color1: "#6836D0", + color2: "#6836D0", + icon: , + darkMode: true, + }, + remember: { + color1: "#6D0000", + color2: "#F66800", + icon: , + darkMode: true, + }, + note: { + color1: "#F69900", + color2: "#FFCE15", + icon: , + darkMode: false, + }, + docs: { + color1: "#6836D0", + color2: "#F44CF6", + icon: , + darkMode: true, + }, + // add as many types as you like +}; + +const gradientStyles = ({ color1, color2 }) => ({ + background: `linear-gradient(78.06deg, ${color1} 1.14%, ${color2} 98.88%)`, + border: 0, + borderRadius: 16, + padding: "0 30px", + display: "flex", + width: "100%", + // alignItems: "center", + justifyContent: "start", + marginBottom: 20, + fontSize: 21, + flexWrap: "wrap", + flexDirection: "column", +}); + +function HighlightBox({ type, title, children }) { + const styles = typeToStyles[type] || typeToStyles.info; // default to 'info' if type doesn't exist + + const codeStyle = { + backgroundColor: 'var(--docusaurus-highlighted-code-line-bg)', + }; + const iconStyles = { + alignSelf: "center", + marginTop: "10px", + filter: styles.darkMode ? "brightness(0) invert(1)" : "brightness(0)", + }; + const titleStyle = { + marginTop: "10px", + marginLeft: "10px", + color: styles.darkMode ? "#e6e6e6" : "black", + }; + const childrenStyle = { + color: styles.darkMode ? "#e6e6e6" : "black", + marginBottom: "10px", + }; + + const childrenWithStyles = React.Children.map(children, child => { + if (child.type === 'a') { + return React.cloneElement(child, { + style: anchorStyle, + }); + } + if (child.type === 'code') { + return React.cloneElement(child, { + style: codeStyle, + }); + } + return child; + }); + + return ( +
+ +
+
{styles.icon}
+

{title}

+
+
{childrenWithStyles}
+
+ ); +} + +export default HighlightBox; diff --git a/docs/src/components/HighlightTag.jsx b/docs/src/components/HighlightTag.jsx new file mode 100644 index 0000000..0dfb2fb --- /dev/null +++ b/docs/src/components/HighlightTag.jsx @@ -0,0 +1,71 @@ +import React from "react"; + +const tags = { + devops: { + color: "#54ffe0", + label: "DevOps", + isBright: true, + }, + "cosmos-sdk": { + color: "#F69900", + label: "Cosmos SDK", + isBright: true, + }, + "ibc-go": { + color: "#ff1717", + label: "IBC-Go", + }, + cosmjs: { + color: "#6836D0", + label: "CosmJS", + }, + cosmwasm: { + color: "#05BDFC", + label: "CosmWasm", + }, + cometbft: { + color: "#00B067", + label: "CometBFT", + }, + "cosmos-hub": { + color: "#f7f199", + label: "Cosmos Hub", + isBright: true, + }, + concepts: { + color: "#AABAFF", + label: "Concept", + isBright: true, + }, + tutorial: { + color: "#F46800", + label: "Tutorial", + }, + "guided-coding": { + color: "#F24CF4", + label: "Guided Coding", + }, +}; + +const HighlightTag = ({ type, version }) => { + const styles = tags[type] || tags["ibc-go"]; // default to 'ibc-go' if type doesn't exist + + return ( + + {styles.label} + {version ? ` ${version}` : ""} + + ); +}; + +export default HighlightTag; diff --git a/docs/src/css/base.css b/docs/src/css/base.css new file mode 100644 index 0000000..7f86e74 --- /dev/null +++ b/docs/src/css/base.css @@ -0,0 +1,32 @@ +/* + Copied from https://github.com/ignite/cli/blob/develop/docs/src/css/base.css +*/ + +@layer base { + html { + @apply font-inter; + font-feature-settings: 'kern', 'liga', 'calt', 'zero' 0; + -webkit-font-feature-settings: 'kern', 'liga', 'calt', 'zero' 0; + text-size-adjust: 100%; + -moz-osx-font-smoothing: grayscale; + font-smoothing: antialiased; + font-variant-ligatures: contextual common-ligatures; + font-kerning: normal; + text-rendering: optimizeLegibility; + + @supports (font-variation-settings: normal) { + @apply font-intervar + } + } + + *, + *::before, + *::after { + box-sizing: border-box; + margin: 0; + } + + svg { display: inline; } + + ::selection{} +} \ No newline at end of file diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css new file mode 100644 index 0000000..f6d86dc --- /dev/null +++ b/docs/src/css/custom.css @@ -0,0 +1,553 @@ +/* + Slightly modified version of https://github.com/ignite/cli/blob/develop/docs/src/css/custom.css +*/ + +@import "tailwindcss/base"; +@import "./fonts.css"; +@import "./base.css"; +@import "tailwindcss/components"; +@import "tailwindcss/utilities"; + +/* Github Style Themed Images */ +[data-theme='light'] img[src$='#gh-dark-mode-only'], +[data-theme='dark'] img[src$='#gh-light-mode-only'] { + display: none; +} + +/* You can override the default Infima variables here. */ +:root { + --ifm-color-primary: #5064fb; + --ifm-color-primary-dark: theme(colors.gray.1000); + --ifm-color-primary-darker: theme(colors.gray.1000); + --ifm-color-primary-darkest: theme(colors.gray.1000); + --ifm-color-primary-light: theme(colors.gray.1000); + --ifm-color-primary-lighter: theme(colors.gray.1000); + --ifm-color-primary-lightest: theme(colors.gray.1000); + --ifm-code-font-size: 95%; + --ifm-breadcrumb-item-background-active: transparent; + --ifm-breadcrumb-padding-horizontal: 0; + --ifm-list-paragraph-margin: 0; + --ifm-spacing-horizontal: theme(spacing.7); + --ifm-blockquote-border-color: theme(colors.gray.1000); + --ifm-menu-link-padding-vertical: 0.6rem; + --ifm-background-color: theme(colors.gray.0); + --ifm-footer-link-color: var(--ifm-font-color-base); + --ifm-menu-link-sublist-icon: url("~/img/ico-chevron.svg"); + --docsearch-searchbox-background: #f7f7f7; + --docsearch-modal-background: theme(colors.card) !important; + --ifm-navbar-height: 5.563rem; + --ifm-navbar-sidebar-width: 100vw; + --docsearch-highlight-color: theme(colors.fg) !important; + --docsearch-searchbox-shadow: inset 0 0 0 1px var(--docsearch-primary-color); + + /* temp: local search bar */ + --aa-primary-color-rgb: 0, 0, 0; + + @media screen and (prefers-reduced-motion) { + transition: ; + } + --ifm-menu-color-background-active: none; + --ifm-menu-color-background-hover: none; + --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); +} + +/* For readability concerns, you should choose a lighter palette in dark mode. */ +html[data-theme="dark"] { + --ifm-color-primary: theme(colors.gray.0); + --ifm-color-primary-dark: #e6e6e6; + --ifm-color-primary-darker: #d9d9d9; + --ifm-color-primary-darkest: #b3b3b3; + --ifm-color-primary-light: theme(colors.gray.0); + --ifm-color-primary-lighter: theme(colors.gray.0); + --ifm-color-primary-lightest: theme(colors.gray.0); + --ifm-background-color: theme(colors.gray.1000); + --docusaurus-highlighted-code-line-bg: theme(colors.inactive); + --docsearch-modal-background: theme(colors.gray.1000) !important; + --docsearch-highlight-color: theme(colors.inactiveLight) !important; + --docsearch-hit-background: theme(colors.lightfg) !important; + --docsearch-searchbox-shadow: inset 0 0 0 1px var(--docsearch-primary-color); + --docsearch-key-gradient: linear-gradient( + -26.5deg, + #5d5d5d, + #3c3c3c + ) !important; + --docsearch-key-shadow: inset 0 -2px 0 0 #353535, inset 0 0 1px 1px #7a7a7b, + 0 2px 2px 0 rgba(45, 45, 45, 0.3) !important; +} + +/* Custom colors for links embedded into markdown files in dark mode */ +html[data-theme="dark"] .markdown a { + color: #85c1e9; /* Choose the color you prefer */ +} + +/* Custom highlight color for @easyops-cn/docusaurus-search-local in dark mode */ +html[data-theme="dark"] { + --search-local-highlight-color: #d23669; +} + +html { + @apply bg-docusaurusBgColor; + /* Banner */ + + #__docusaurus > div[role="banner"] { + @apply bg-gray-30 text-gray-1000 font-normal; + a { + @apply no-underline w-full text-2; + } + } + + /* MAINNAV */ + .navbar { + @apply py-2 h-auto border-b border-b-docusaurusColorBorder shadow-none bg-docusaurusBgColor; + &__toggle { + @apply bg-card rounded-s h-8 w-8 flex justify-center items-center; + @media (min-width: 997px) { + @apply hidden; + } + } + &__brand { + & + * { + @apply ml-auto; + } + } + &__link--active { + @apply text-muted; + } + &__items:not(:last-child) { + @apply justify-between; + @media (min-width: 997px) { + @apply justify-start px-3; + } + button { + @apply order-2 mr-0; + } + } + &__items--right > :last-child { + @apply right-9.5; + } + } + &[data-theme="dark"] .navbar__item { + @apply text-mutedLight; + } + &[data-theme="dark"] .navbar__toggle { + @apply bg-fg; + } + + .github-icon { + @apply hover:opacity-50; + } + + /* SEARCHBAR */ + /* algolia */ + .DocSearch { + &-Hits mark { + @apply text-docusaurusColorBase; + } + &-Button { + @apply text-inactive rounded-sm h-8 w-8 bg-card justify-center mr-3; + @media (min-width: 997px) { + @apply w-auto justify-between; + } + .DocSearch-Search-Icon { + @apply text-docusaurusColorBase; + } + .DocSearch-Button-Keys { + @apply hidden; + } + .DocSearch-Button-Placeholder { + @apply pr-10; + } + } + + &-Logo path { + @apply fill-docusaurusColorBase; + } + } + + .navbar-sidebar { + @apply w-full; + &__brand { + @apply px-6 h-auto; + } + &__item { + @apply px-6 w-full; + } + &__back { + @apply px-0 hidden; + } + &__close { + @apply bg-gray-1000 rounded-s h-8 w-8 flex justify-center items-center ml-0; + & > svg > g { + @apply stroke-gray-0; + } + } + } + &[data-theme="dark"] .navbar-sidebar { + @apply bg-gray-1000; + &__brand { + @apply shadow-none relative; + &::after { + content: ""; + @apply absolute block h-px bg-linkHover bottom-0 right-3 left-0 mx-6; + } + } + &__close { + @apply bg-gray-0; + & > svg > g { + @apply stroke-gray-1000; + } + } + } + &[data-theme="dark"] .DocSearch-Modal { + @apply bg-gray-1000; + } + &[data-theme="dark"] .DocSearch-Footer { + @apply bg-gray-1000; + } + &[data-theme="dark"] .DocSearch-Button { + @apply bg-fg text-inactiveLight; + } + &[data-theme="dark"] .DocSearch-Button-Key { + @apply text-inactiveLight border-inactiveLight; + } + + /* BREADCRUMBS */ + .breadcrumbs__item { + &:first-child { + & > a { + &::after { + content: "Docs"; + } + & > svg { + @apply hidden; + } + } + } + &:not(:last-child)::after { + content: ">"; + @apply bg-none; + } + } + .breadcrumbs__link { + color: black !important; + } + &[data-theme="dark"] .breadcrumbs__link { + color: white !important; + } + .theme-doc-breadcrumbs { + @media (min-width: 997px) { + @apply pt-[calc(theme(spacing.7)-1rem)]; + } + } + .theme-doc-toc-mobile { + @apply bg-card px-6 py-5.5 pb-0 rounded; + & > button { + @apply p-0 pb-5.5 flex justify-between; + &::after { + @apply order-last ml-5; + background-image: var(--ifm-menu-link-sublist-icon); + background-size: 70%; + } + } + & ul li { + @apply my-5 mx-0; + } + } + &[data-theme="dark"] .theme-doc-toc-mobile { + @apply bg-fg; + } + + /* SIDEBAR */ + .theme-doc-sidebar-container { + @media (min-width: 997px) { + @apply ml-4 border-r border-r-docusaurusColorBorder; + } + & > div:first-child > a { + @apply m-0; + } + } + + &[data-theme="dark"] .theme-doc-sidebar-menu .menu__list::before { + @apply bg-inactiveLight; + } + .theme-doc-sidebar-menu { + @apply font-normal; + + .menu__list { + @apply relative pl-0; + &::before { + content: ""; + @apply absolute block left-3 top-0 h-full w-[2px] bg-border; + } + ul::before { + @apply hidden; + } + } + + .menu__link { + @apply pl-0 pr-5; + &--active:not(.menu__link--sublist) { + @apply text-docusaurusColorBase font-medium; + } + } + + li li { + @apply pl-7; + .menu__link--active:not(.menu__link--sublist) { + @apply relative text-docusaurusColorBase font-medium; + &::before { + content: ""; + @apply absolute block left-0 top-0 h-full w-[2px] bg-docusaurusColorBase; + @apply -left-[calc(theme(space.7)-theme(space.3))]; + } + } + } + li li li { + @apply pl-5; + } + li li li .menu__link--active:not(.menu__link--sublist)::before { + @apply -left-[calc(theme(space.5)*1+theme(space.7)-theme(space.3))]; + } + li li li li .menu__link--active:not(.menu__link--sublist)::before { + @apply -left-[calc(theme(space.5)*2+theme(space.7)-theme(space.3))]; + } + li li li li li .menu__link--active:not(.menu__link--sublist)::before { + @apply -left-[calc(theme(space.5)*3+theme(space.7)-theme(space.3))]; + } + li li li li li li .menu__link--active:not(.menu__link--sublist)::before { + @apply -left-[calc(theme(space.5)*4+theme(space.7)-theme(space.3))]; + } + li li li li li li li .menu__link--active:not(.menu__link--sublist)::before { + @apply -left-[calc(theme(space.5)*5+theme(space.7)-theme(space.3))]; + } + } + + &[data-theme="dark"] .menu__link { + @apply text-mutedLight; + } + .theme-doc-sidebar-item-link .menu__link[target="_blank"] { + &::after { + content: "\2197"; + @apply ml-1; + } + } + .menu__link { + @apply text-muted; + &:hover { + text-shadow: 0.1px 0.1px 0 var(--ifm-font-color-base), + -0.1px -0.1px 0 var(--ifm-font-color-base), + 0.1px -0.1px 0 var(--ifm-font-color-base), + -0.1px 0.1px 0 var(--ifm-font-color-base), + -0.1px 0 0 var(--ifm-font-color-base), + 0.1px 0 0 var(--ifm-font-color-base), + 0 0.1px 0 var(--ifm-font-color-base), + 0 -0.1px 0 var(--ifm-font-color-base); + @apply text-docusaurusColorBase; + } + + & > svg { + @apply hidden; + } + } + + .menu__link--sublist-caret { + @apply flex; + &::after { + background-size: 16px; + background-repeat: no-repeat; + @apply order-first ml-0 mr-4; + } + } + .menu__list-item--collapsed .menu__link--sublist:after, + .menu__list-item--collapsed .menu__caret:before { + transform: rotateZ(0); + } + .menu__caret, + li li .menu__link--sublist-caret::after { + @apply hidden; + } + + /* TOC */ + .table-of-contents__link:hover, + .table-of-contents__link--active { + text-shadow: 0.1px 0.1px 0 var(--ifm-font-color-base), + -0.1px -0.1px 0 var(--ifm-font-color-base), + 0.1px -0.1px 0 var(--ifm-font-color-base), + -0.1px 0.1px 0 var(--ifm-font-color-base), + -0.1px 0 0 var(--ifm-font-color-base), + 0.1px 0 0 var(--ifm-font-color-base), 0 0.1px 0 var(--ifm-font-color-base), + 0 -0.1px 0 var(--ifm-font-color-base); + } + + /* CODE BLOCK */ + .code-block-minus-diff-line { + background-color: #ff000020; + display: block; + margin: 0 calc(-1 * var(--ifm-pre-padding)); + padding: 0 var(--ifm-pre-padding); + border-left: 3px solid #ff000080; + } + + .code-block-plus-diff-line { + background-color: #00ff0020; + display: block; + margin: 0 calc(-1 * var(--ifm-pre-padding)); + padding: 0 var(--ifm-pre-padding); + border-left: 3px solid #00ff0080; + } + + /* RELATED ARTICLES */ + &[data-theme="dark"] .pagination-nav > a { + @apply bg-fg; + } + .pagination-nav { + @apply pb-7 mt-9; + & > a { + box-shadow: 0px 0px 80px rgba(0, 0, 0, 0.07); + @apply border-transparent rounded pb-8.5 col-span-2 pt-6 px-6 hover:shadow-none; + + @media (min-width: 997px) { + @apply col-span-1; + } + } + + .pagination-nav { + &__link--next { + @apply text-left; + @media (min-width: 997px) { + @apply text-right; + } + } + &__sublabel { + @apply mb-3.5 text-gray-1000 dark:text-docusaurusColorBase text-3; + } + &__label { + @apply text-4 font-semibold; + } + } + } + + /* FOOTER */ + .footer { + background-color: var(--ifm-background-color); + @apply border-t border-t-docusaurusColorBorder pt-10 mb-10; + &__link-item { + @apply hover:underline; + } + &__bottom { + margin: 0 calc(var(--ifm-spacing-horizontal) * -1); + } + &__copyright { + @apply text-center mt-9 text-2; + } + } + .footer__col:not(:first-child) { + @apply basis-1/2; + @media (min-width: 997px) { + @apply basis-0; + } + } + .footer__col:first-child .footer__title { + @apply hidden; + } + .footer__link-item { + & > svg { + @apply hidden; + } + } + + .theme-back-to-top-button { + @apply rotate-180; + &::after { + @apply w-1/2; + } + } + + /* MARKDOWN */ + .theme-code-block { + @apply font-jetbrain mt-3; + } + + .markdown { + --ifm-heading-vertical-rhythm-bottom: 1; + --ifm-h1-vertical-rhythm-bottom: 1; + } + .theme-doc-markdown { + @apply mt-7 pb-8 border-b border-b-border; + + h1 { + @apply text-7 font-bold leading-10 tracking-tight; + } + h2 { + @apply text-6 font-bold leading-9 tracking-tight; + } + h3 { + @apply text-4 font-semibold leading-8 tracking-tight; + } + h4 { + @apply text-3 font-semibold leading-7 tracking-tight; + } + h5 { + @apply text-3 font-semibold leading-6 tracking-wide; + } + p { + @apply leading-relaxed; + } + p, + ul, + ol, + blockquote { + @apply text-[1rem]; + } + code { + @apply border-0 px-3; + } + blockquote { + @apply my-7; + } + a { + @apply no-underline hover:underline; + color: var(--ifm-color-primary); + } + ol, + ul { + @apply my-6; + } + ul li { + @apply relative pl-6 mb-4 before:absolute before:block before:w-[4px] before:h-[4px] before:bg-current before:left-0 before:top-[calc(1em/2)]; + } + ul li li { + @apply last:mb-6 before:border before:border-current before:bg-transparent; + } + li:last-child li { + @apply last:mb-0; + } + ol { + list-style-type: none; + counter-reset: item; + & > li { + @apply relative pl-8 mb-5.5; + &::before { + counter-increment: item; + content: counters(item, ".", decimal-leading-zero) "."; + @apply absolute flex left-0 top-[.2rem] text-3 font-semibold tracking-tight; + } + } + } + ol ol { + counter-reset: subitem; + & > li { + &::before { + counter-increment: subitem; + content: counters(subitem, ".", decimal-leading-zero) "."; + } + } + } + li { + & > ul, + & > ol { + @apply my-5; + } + } + } +} diff --git a/docs/src/css/fonts.css b/docs/src/css/fonts.css new file mode 100644 index 0000000..f26f401 --- /dev/null +++ b/docs/src/css/fonts.css @@ -0,0 +1,64 @@ +/* + + FONT FAMILY GROUPS + +*/ + +/* + Copied from https://github.com/ignite/cli/blob/develop/docs/src/css/fonts.css +*/ + +/* Inter */ +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url("~/static/fonts/inter/Inter-Regular.woff2?v=3.19") format("woff2"), + url("~/static/fonts/inter/Inter-Regular.woff?v=3.19") format("woff"); +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url("~/static/fonts/inter/Inter-Medium.woff2?v=3.19") format("woff2"), + url("~/static/fonts/inter/Inter-Medium.woff?v=3.19") format("woff"); +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url("~/static/fonts/inter/Inter-Bold.woff2?v=3.19") format("woff2"), + url("~/static/fonts/inter/Inter-Bold.woff?v=3.19") format("woff"); +} + +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 900; + font-display: swap; + src: url("~/static/fonts/inter/Inter-Black.woff2?v=3.19") format("woff2"), + url("~/static/fonts/inter/Inter-Black.woff?v=3.19") format("woff"); +} + +/* Inter var */ +@font-face { + font-family: "Inter var"; + font-weight: 100 900; + font-display: swap; + font-style: oblique 0deg 10deg; + src: url("~/static/fonts/intervar/Inter.var.woff2?v=3.19") format("woff2"); +} + +/* JetBrains Mono */ +@font-face { + font-family: "JetBrains Mono"; + font-weight: normal; + font-style: normal; + src: url("~/static/fonts/jetbrainsmono/JetBrainsMono-Regular.woff2") + format("woff2"); +} diff --git a/docs/src/theme/CodeBlock/index.js b/docs/src/theme/CodeBlock/index.js new file mode 100644 index 0000000..17b7cc2 --- /dev/null +++ b/docs/src/theme/CodeBlock/index.js @@ -0,0 +1,16 @@ +import React from 'react'; +import CodeBlock from '@theme-original/CodeBlock'; + +export default function CodeBlockWrapper(props) { + const { source, ...codeBlockProps } = props; + return ( + <> + + {source && + + } + + ); +} diff --git a/docs/static/.nojekyll b/docs/static/.nojekyll new file mode 100644 index 0000000..473a0f4 diff --git a/docs/static/CNAME b/docs/static/CNAME new file mode 100644 index 0000000..b001d0f --- /dev/null +++ b/docs/static/CNAME @@ -0,0 +1 @@ +ibc.cosmos.network diff --git a/docs/static/fonts/inter/Inter-Black.woff b/docs/static/fonts/inter/Inter-Black.woff new file mode 100644 index 0000000..82819fd Binary files /dev/null and b/docs/static/fonts/inter/Inter-Black.woff differ diff --git a/docs/static/fonts/inter/Inter-Black.woff2 b/docs/static/fonts/inter/Inter-Black.woff2 new file mode 100644 index 0000000..d8f2cde Binary files /dev/null and b/docs/static/fonts/inter/Inter-Black.woff2 differ diff --git a/docs/static/fonts/inter/Inter-BlackItalic.woff b/docs/static/fonts/inter/Inter-BlackItalic.woff new file mode 100644 index 0000000..8d14d3e Binary files /dev/null and b/docs/static/fonts/inter/Inter-BlackItalic.woff differ diff --git a/docs/static/fonts/inter/Inter-BlackItalic.woff2 b/docs/static/fonts/inter/Inter-BlackItalic.woff2 new file mode 100644 index 0000000..1123f2b Binary files /dev/null and b/docs/static/fonts/inter/Inter-BlackItalic.woff2 differ diff --git a/docs/static/fonts/inter/Inter-Bold.woff b/docs/static/fonts/inter/Inter-Bold.woff new file mode 100644 index 0000000..1141494 Binary files /dev/null and b/docs/static/fonts/inter/Inter-Bold.woff differ diff --git a/docs/static/fonts/inter/Inter-Bold.woff2 b/docs/static/fonts/inter/Inter-Bold.woff2 new file mode 100644 index 0000000..5989a29 Binary files /dev/null and b/docs/static/fonts/inter/Inter-Bold.woff2 differ diff --git a/docs/static/fonts/inter/Inter-BoldItalic.woff b/docs/static/fonts/inter/Inter-BoldItalic.woff new file mode 100644 index 0000000..738d1e5 Binary files /dev/null and b/docs/static/fonts/inter/Inter-BoldItalic.woff differ diff --git a/docs/static/fonts/inter/Inter-BoldItalic.woff2 b/docs/static/fonts/inter/Inter-BoldItalic.woff2 new file mode 100644 index 0000000..1ae84b2 Binary files /dev/null and b/docs/static/fonts/inter/Inter-BoldItalic.woff2 differ diff --git a/docs/static/fonts/inter/Inter-ExtraBold.woff b/docs/static/fonts/inter/Inter-ExtraBold.woff new file mode 100644 index 0000000..98bbcef Binary files /dev/null and b/docs/static/fonts/inter/Inter-ExtraBold.woff differ diff --git a/docs/static/fonts/inter/Inter-ExtraBold.woff2 b/docs/static/fonts/inter/Inter-ExtraBold.woff2 new file mode 100644 index 0000000..d8afe93 Binary files /dev/null and b/docs/static/fonts/inter/Inter-ExtraBold.woff2 differ diff --git a/docs/static/fonts/inter/Inter-ExtraBoldItalic.woff b/docs/static/fonts/inter/Inter-ExtraBoldItalic.woff new file mode 100644 index 0000000..0daf683 Binary files /dev/null and b/docs/static/fonts/inter/Inter-ExtraBoldItalic.woff differ diff --git a/docs/static/fonts/inter/Inter-ExtraBoldItalic.woff2 b/docs/static/fonts/inter/Inter-ExtraBoldItalic.woff2 new file mode 100644 index 0000000..5381d07 Binary files /dev/null and b/docs/static/fonts/inter/Inter-ExtraBoldItalic.woff2 differ diff --git a/docs/static/fonts/inter/Inter-ExtraLight.woff b/docs/static/fonts/inter/Inter-ExtraLight.woff new file mode 100644 index 0000000..cccc842 Binary files /dev/null and b/docs/static/fonts/inter/Inter-ExtraLight.woff differ diff --git a/docs/static/fonts/inter/Inter-ExtraLight.woff2 b/docs/static/fonts/inter/Inter-ExtraLight.woff2 new file mode 100644 index 0000000..1d0debd Binary files /dev/null and b/docs/static/fonts/inter/Inter-ExtraLight.woff2 differ diff --git a/docs/static/fonts/inter/Inter-ExtraLightItalic.woff b/docs/static/fonts/inter/Inter-ExtraLightItalic.woff new file mode 100644 index 0000000..911d90e Binary files /dev/null and b/docs/static/fonts/inter/Inter-ExtraLightItalic.woff differ diff --git a/docs/static/fonts/inter/Inter-ExtraLightItalic.woff2 b/docs/static/fonts/inter/Inter-ExtraLightItalic.woff2 new file mode 100644 index 0000000..f9f0269 Binary files /dev/null and b/docs/static/fonts/inter/Inter-ExtraLightItalic.woff2 differ diff --git a/docs/static/fonts/inter/Inter-Italic.woff b/docs/static/fonts/inter/Inter-Italic.woff new file mode 100644 index 0000000..6e983bd Binary files /dev/null and b/docs/static/fonts/inter/Inter-Italic.woff differ diff --git a/docs/static/fonts/inter/Inter-Italic.woff2 b/docs/static/fonts/inter/Inter-Italic.woff2 new file mode 100644 index 0000000..43ff839 Binary files /dev/null and b/docs/static/fonts/inter/Inter-Italic.woff2 differ diff --git a/docs/static/fonts/inter/Inter-Light.woff b/docs/static/fonts/inter/Inter-Light.woff new file mode 100644 index 0000000..5e63725 Binary files /dev/null and b/docs/static/fonts/inter/Inter-Light.woff differ diff --git a/docs/static/fonts/inter/Inter-Light.woff2 b/docs/static/fonts/inter/Inter-Light.woff2 new file mode 100644 index 0000000..742e621 Binary files /dev/null and b/docs/static/fonts/inter/Inter-Light.woff2 differ diff --git a/docs/static/fonts/inter/Inter-LightItalic.woff b/docs/static/fonts/inter/Inter-LightItalic.woff new file mode 100644 index 0000000..28993e2 Binary files /dev/null and b/docs/static/fonts/inter/Inter-LightItalic.woff differ diff --git a/docs/static/fonts/inter/Inter-LightItalic.woff2 b/docs/static/fonts/inter/Inter-LightItalic.woff2 new file mode 100644 index 0000000..31e4ca7 Binary files /dev/null and b/docs/static/fonts/inter/Inter-LightItalic.woff2 differ diff --git a/docs/static/fonts/inter/Inter-Medium.woff b/docs/static/fonts/inter/Inter-Medium.woff new file mode 100644 index 0000000..2281510 Binary files /dev/null and b/docs/static/fonts/inter/Inter-Medium.woff differ diff --git a/docs/static/fonts/inter/Inter-Medium.woff2 b/docs/static/fonts/inter/Inter-Medium.woff2 new file mode 100644 index 0000000..17caef8 Binary files /dev/null and b/docs/static/fonts/inter/Inter-Medium.woff2 differ diff --git a/docs/static/fonts/inter/Inter-MediumItalic.woff b/docs/static/fonts/inter/Inter-MediumItalic.woff new file mode 100644 index 0000000..31d5ee1 Binary files /dev/null and b/docs/static/fonts/inter/Inter-MediumItalic.woff differ diff --git a/docs/static/fonts/inter/Inter-MediumItalic.woff2 b/docs/static/fonts/inter/Inter-MediumItalic.woff2 new file mode 100644 index 0000000..6eff543 Binary files /dev/null and b/docs/static/fonts/inter/Inter-MediumItalic.woff2 differ diff --git a/docs/static/fonts/inter/Inter-Regular.woff b/docs/static/fonts/inter/Inter-Regular.woff new file mode 100644 index 0000000..7e3669b Binary files /dev/null and b/docs/static/fonts/inter/Inter-Regular.woff differ diff --git a/docs/static/fonts/inter/Inter-Regular.woff2 b/docs/static/fonts/inter/Inter-Regular.woff2 new file mode 100644 index 0000000..bc8a184 Binary files /dev/null and b/docs/static/fonts/inter/Inter-Regular.woff2 differ diff --git a/docs/static/fonts/inter/Inter-SemiBold.woff b/docs/static/fonts/inter/Inter-SemiBold.woff new file mode 100644 index 0000000..c0766d8 Binary files /dev/null and b/docs/static/fonts/inter/Inter-SemiBold.woff differ diff --git a/docs/static/fonts/inter/Inter-SemiBold.woff2 b/docs/static/fonts/inter/Inter-SemiBold.woff2 new file mode 100644 index 0000000..921f994 Binary files /dev/null and b/docs/static/fonts/inter/Inter-SemiBold.woff2 differ diff --git a/docs/static/fonts/inter/Inter-SemiBoldItalic.woff b/docs/static/fonts/inter/Inter-SemiBoldItalic.woff new file mode 100644 index 0000000..efc3779 Binary files /dev/null and b/docs/static/fonts/inter/Inter-SemiBoldItalic.woff differ diff --git a/docs/static/fonts/inter/Inter-SemiBoldItalic.woff2 b/docs/static/fonts/inter/Inter-SemiBoldItalic.woff2 new file mode 100644 index 0000000..ca5c286 Binary files /dev/null and b/docs/static/fonts/inter/Inter-SemiBoldItalic.woff2 differ diff --git a/docs/static/fonts/inter/Inter-Thin.woff b/docs/static/fonts/inter/Inter-Thin.woff new file mode 100644 index 0000000..1fda5af Binary files /dev/null and b/docs/static/fonts/inter/Inter-Thin.woff differ diff --git a/docs/static/fonts/inter/Inter-Thin.woff2 b/docs/static/fonts/inter/Inter-Thin.woff2 new file mode 100644 index 0000000..59101f1 Binary files /dev/null and b/docs/static/fonts/inter/Inter-Thin.woff2 differ diff --git a/docs/static/fonts/inter/Inter-ThinItalic.woff b/docs/static/fonts/inter/Inter-ThinItalic.woff new file mode 100644 index 0000000..38a2324 Binary files /dev/null and b/docs/static/fonts/inter/Inter-ThinItalic.woff differ diff --git a/docs/static/fonts/inter/Inter-ThinItalic.woff2 b/docs/static/fonts/inter/Inter-ThinItalic.woff2 new file mode 100644 index 0000000..ac52629 Binary files /dev/null and b/docs/static/fonts/inter/Inter-ThinItalic.woff2 differ diff --git a/docs/static/fonts/inter/Inter-italic.var.woff2 b/docs/static/fonts/inter/Inter-italic.var.woff2 new file mode 100644 index 0000000..b985a1e Binary files /dev/null and b/docs/static/fonts/inter/Inter-italic.var.woff2 differ diff --git a/docs/static/fonts/inter/Inter-roman.var.woff2 b/docs/static/fonts/inter/Inter-roman.var.woff2 new file mode 100644 index 0000000..51088e4 Binary files /dev/null and b/docs/static/fonts/inter/Inter-roman.var.woff2 differ diff --git a/docs/static/fonts/intervar/Inter.var.woff2 b/docs/static/fonts/intervar/Inter.var.woff2 new file mode 100644 index 0000000..1723192 Binary files /dev/null and b/docs/static/fonts/intervar/Inter.var.woff2 differ diff --git a/docs/static/fonts/jetbrainsmono/JetBrainsMono-Bold.woff2 b/docs/static/fonts/jetbrainsmono/JetBrainsMono-Bold.woff2 new file mode 100644 index 0000000..f8ce701 Binary files /dev/null and b/docs/static/fonts/jetbrainsmono/JetBrainsMono-Bold.woff2 differ diff --git a/docs/static/fonts/jetbrainsmono/JetBrainsMono-BoldItalic.woff2 b/docs/static/fonts/jetbrainsmono/JetBrainsMono-BoldItalic.woff2 new file mode 100644 index 0000000..aa029da Binary files /dev/null and b/docs/static/fonts/jetbrainsmono/JetBrainsMono-BoldItalic.woff2 differ diff --git a/docs/static/fonts/jetbrainsmono/JetBrainsMono-ExtraBold.woff2 b/docs/static/fonts/jetbrainsmono/JetBrainsMono-ExtraBold.woff2 new file mode 100644 index 0000000..b2cf085 Binary files /dev/null and b/docs/static/fonts/jetbrainsmono/JetBrainsMono-ExtraBold.woff2 differ diff --git a/docs/static/fonts/jetbrainsmono/JetBrainsMono-ExtraBoldItalic.woff2 b/docs/static/fonts/jetbrainsmono/JetBrainsMono-ExtraBoldItalic.woff2 new file mode 100644 index 0000000..c9a5b00 Binary files /dev/null and b/docs/static/fonts/jetbrainsmono/JetBrainsMono-ExtraBoldItalic.woff2 differ diff --git a/docs/static/fonts/jetbrainsmono/JetBrainsMono-ExtraLight.woff2 b/docs/static/fonts/jetbrainsmono/JetBrainsMono-ExtraLight.woff2 new file mode 100644 index 0000000..45594a2 Binary files /dev/null and b/docs/static/fonts/jetbrainsmono/JetBrainsMono-ExtraLight.woff2 differ diff --git a/docs/static/fonts/jetbrainsmono/JetBrainsMono-ExtraLightItalic.woff2 b/docs/static/fonts/jetbrainsmono/JetBrainsMono-ExtraLightItalic.woff2 new file mode 100644 index 0000000..7912eb1 Binary files /dev/null and b/docs/static/fonts/jetbrainsmono/JetBrainsMono-ExtraLightItalic.woff2 differ diff --git a/docs/static/fonts/jetbrainsmono/JetBrainsMono-Italic.woff2 b/docs/static/fonts/jetbrainsmono/JetBrainsMono-Italic.woff2 new file mode 100644 index 0000000..e3dec81 Binary files /dev/null and b/docs/static/fonts/jetbrainsmono/JetBrainsMono-Italic.woff2 differ diff --git a/docs/static/fonts/jetbrainsmono/JetBrainsMono-Light.woff2 b/docs/static/fonts/jetbrainsmono/JetBrainsMono-Light.woff2 new file mode 100644 index 0000000..e63fcff Binary files /dev/null and b/docs/static/fonts/jetbrainsmono/JetBrainsMono-Light.woff2 differ diff --git a/docs/static/fonts/jetbrainsmono/JetBrainsMono-LightItalic.woff2 b/docs/static/fonts/jetbrainsmono/JetBrainsMono-LightItalic.woff2 new file mode 100644 index 0000000..07f9106 Binary files /dev/null and b/docs/static/fonts/jetbrainsmono/JetBrainsMono-LightItalic.woff2 differ diff --git a/docs/static/fonts/jetbrainsmono/JetBrainsMono-Medium.woff2 b/docs/static/fonts/jetbrainsmono/JetBrainsMono-Medium.woff2 new file mode 100644 index 0000000..9f0d29b Binary files /dev/null and b/docs/static/fonts/jetbrainsmono/JetBrainsMono-Medium.woff2 differ diff --git a/docs/static/fonts/jetbrainsmono/JetBrainsMono-MediumItalic.woff2 b/docs/static/fonts/jetbrainsmono/JetBrainsMono-MediumItalic.woff2 new file mode 100644 index 0000000..61c9e91 Binary files /dev/null and b/docs/static/fonts/jetbrainsmono/JetBrainsMono-MediumItalic.woff2 differ diff --git a/docs/static/fonts/jetbrainsmono/JetBrainsMono-Regular.woff2 b/docs/static/fonts/jetbrainsmono/JetBrainsMono-Regular.woff2 new file mode 100644 index 0000000..c3d6c74 Binary files /dev/null and b/docs/static/fonts/jetbrainsmono/JetBrainsMono-Regular.woff2 differ diff --git a/docs/static/fonts/jetbrainsmono/JetBrainsMono-SemiBold.woff2 b/docs/static/fonts/jetbrainsmono/JetBrainsMono-SemiBold.woff2 new file mode 100644 index 0000000..caff24f Binary files /dev/null and b/docs/static/fonts/jetbrainsmono/JetBrainsMono-SemiBold.woff2 differ diff --git a/docs/static/fonts/jetbrainsmono/JetBrainsMono-SemiBoldItalic.woff2 b/docs/static/fonts/jetbrainsmono/JetBrainsMono-SemiBoldItalic.woff2 new file mode 100644 index 0000000..86a177d Binary files /dev/null and b/docs/static/fonts/jetbrainsmono/JetBrainsMono-SemiBoldItalic.woff2 differ diff --git a/docs/static/fonts/jetbrainsmono/JetBrainsMono-Thin.woff2 b/docs/static/fonts/jetbrainsmono/JetBrainsMono-Thin.woff2 new file mode 100644 index 0000000..d9dd191 Binary files /dev/null and b/docs/static/fonts/jetbrainsmono/JetBrainsMono-Thin.woff2 differ diff --git a/docs/static/fonts/jetbrainsmono/JetBrainsMono-ThinItalic.woff2 b/docs/static/fonts/jetbrainsmono/JetBrainsMono-ThinItalic.woff2 new file mode 100644 index 0000000..17650dc Binary files /dev/null and b/docs/static/fonts/jetbrainsmono/JetBrainsMono-ThinItalic.woff2 differ diff --git a/docs/static/img/IBC-go-cover.svg b/docs/static/img/IBC-go-cover.svg new file mode 100644 index 0000000..1f63d70 --- /dev/null +++ b/docs/static/img/IBC-go-cover.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/static/img/black-ibc-logo-400x400.svg b/docs/static/img/black-ibc-logo-400x400.svg new file mode 100644 index 0000000..bb82dac --- /dev/null +++ b/docs/static/img/black-ibc-logo-400x400.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/docs/static/img/black-ibc-logo.svg b/docs/static/img/black-ibc-logo.svg new file mode 100644 index 0000000..c04b381 --- /dev/null +++ b/docs/static/img/black-ibc-logo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/docs/static/img/black-large-ibc-logo.svg b/docs/static/img/black-large-ibc-logo.svg new file mode 100644 index 0000000..a9986da --- /dev/null +++ b/docs/static/img/black-large-ibc-logo.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/static/img/cosmos-logo-bw.svg b/docs/static/img/cosmos-logo-bw.svg new file mode 100644 index 0000000..c2076ac --- /dev/null +++ b/docs/static/img/cosmos-logo-bw.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/docs/static/img/ibc-go-docs-social-card.png b/docs/static/img/ibc-go-docs-social-card.png new file mode 100644 index 0000000..625f8af Binary files /dev/null and b/docs/static/img/ibc-go-docs-social-card.png differ diff --git a/docs/static/img/ico-chevron.svg b/docs/static/img/ico-chevron.svg new file mode 100644 index 0000000..999a390 --- /dev/null +++ b/docs/static/img/ico-chevron.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/static/img/icons/hi-coffee-icon.svg b/docs/static/img/icons/hi-coffee-icon.svg new file mode 100644 index 0000000..e5a6fab --- /dev/null +++ b/docs/static/img/icons/hi-coffee-icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/docs/static/img/icons/hi-info-icon.svg b/docs/static/img/icons/hi-info-icon.svg new file mode 100644 index 0000000..ae531bb --- /dev/null +++ b/docs/static/img/icons/hi-info-icon.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/docs/static/img/icons/hi-note-icon.svg b/docs/static/img/icons/hi-note-icon.svg new file mode 100644 index 0000000..2024829 --- /dev/null +++ b/docs/static/img/icons/hi-note-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/static/img/icons/hi-prerequisite-icon.svg b/docs/static/img/icons/hi-prerequisite-icon.svg new file mode 100644 index 0000000..c9ef7dc --- /dev/null +++ b/docs/static/img/icons/hi-prerequisite-icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/docs/static/img/icons/hi-reading-icon.svg b/docs/static/img/icons/hi-reading-icon.svg new file mode 100644 index 0000000..e18f118 --- /dev/null +++ b/docs/static/img/icons/hi-reading-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/static/img/icons/hi-star-icon.svg b/docs/static/img/icons/hi-star-icon.svg new file mode 100644 index 0000000..d755997 --- /dev/null +++ b/docs/static/img/icons/hi-star-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/static/img/icons/hi-target-icon.svg b/docs/static/img/icons/hi-target-icon.svg new file mode 100644 index 0000000..e21e143 --- /dev/null +++ b/docs/static/img/icons/hi-target-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/static/img/icons/hi-tip-icon.svg b/docs/static/img/icons/hi-tip-icon.svg new file mode 100644 index 0000000..dc0401e --- /dev/null +++ b/docs/static/img/icons/hi-tip-icon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/docs/static/img/icons/hi-warn-icon.svg b/docs/static/img/icons/hi-warn-icon.svg new file mode 100644 index 0000000..e09a68d --- /dev/null +++ b/docs/static/img/icons/hi-warn-icon.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/docs/static/img/spirograph-white.svg b/docs/static/img/spirograph-white.svg new file mode 100644 index 0000000..cd2e055 --- /dev/null +++ b/docs/static/img/spirograph-white.svg @@ -0,0 +1,5 @@ + + + diff --git a/docs/static/img/white-cosmos-icon.svg b/docs/static/img/white-cosmos-icon.svg new file mode 100644 index 0000000..43ef062 --- /dev/null +++ b/docs/static/img/white-cosmos-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/static/img/white-ibc-logo-400x400.svg b/docs/static/img/white-ibc-logo-400x400.svg new file mode 100644 index 0000000..202ddad --- /dev/null +++ b/docs/static/img/white-ibc-logo-400x400.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/docs/static/img/white-ibc-logo.svg b/docs/static/img/white-ibc-logo.svg new file mode 100644 index 0000000..7b736fa --- /dev/null +++ b/docs/static/img/white-ibc-logo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/docs/static/img/white-large-ibc-logo.svg b/docs/static/img/white-large-ibc-logo.svg new file mode 100644 index 0000000..d0990b3 --- /dev/null +++ b/docs/static/img/white-large-ibc-logo.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/tailwind.config.js b/docs/tailwind.config.js new file mode 100644 index 0000000..d57a57b --- /dev/null +++ b/docs/tailwind.config.js @@ -0,0 +1,104 @@ +const defaultTheme = require("tailwindcss/defaultTheme"); + +// Px to REM function (static base of 16) +const pxToRem = (dest) => 1 / (16 / dest); + +// Config +module.exports = { + content: ["./src/**/*.{js,jsx,ts,tsx}"], + corePlugins: { + // preflight: false, // avoid reset all docusaurus css + }, + theme: { + borderRadius: { + none: "0", + xs: `${pxToRem(4)}rem`, + s: `${pxToRem(8)}rem`, + sm: `${pxToRem(10)}rem`, + DEFAULT: `${pxToRem(16)}rem`, + md: `${pxToRem(20)}rem`, + lg: `${pxToRem(100)}rem`, + circle: "100%", + }, + fontFamily: { + intervar: ['"Inter var"', defaultTheme.fontFamily.sans], + inter: ["Inter", defaultTheme.fontFamily.sans], + jetbrain: ["JetBrains Mono", defaultTheme.fontFamily.mono], + }, + fontSize: { + 0: "0", + 1: [`${pxToRem(10)}rem`], + 2: [`${pxToRem(13)}rem`], + 3: [`${pxToRem(16)}rem`], + 4: [`${pxToRem(21)}rem`], + 5: [`${pxToRem(28)}rem`], + 6: [`${pxToRem(32)}rem`], + 7: [`${pxToRem(38)}rem`], + 8: [`${pxToRem(51)}rem`], + 9: [`${pxToRem(56)}rem`], + 10: [`${pxToRem(76)}rem`], + }, + spacing: { + inherit: "inherit", + auto: "auto", + full: "100%", + px: "1px", + "1/2": "50%", + "1/3": "33.333%", + "2/3": "66.666%", + "1/4": "25%", + "3/4": "75%", + "1/5": "20%", + "2/5": "40%", + "3/5": "60%", + "4/5": "80%", + 0: "0", + 1: ".25rem", + 2: `${pxToRem(6)}rem`, + 3: `${pxToRem(8)}rem`, + 3.5: `${pxToRem(10)}rem`, + 4: `${pxToRem(12)}rem`, + 5: `${pxToRem(16)}rem`, + 5.5: `${pxToRem(20)}rem`, + 6: `${pxToRem(24)}rem`, + 7: `${pxToRem(32)}rem`, + 7.5: `${pxToRem(40)}rem`, + 8: `${pxToRem(48)}rem`, + 8.5: `${pxToRem(52)}rem`, + 9: `${pxToRem(64)}rem`, + 9.5: "5rem", + 9.75: `${pxToRem(84)}rem`, + 10: `${pxToRem(96)}rem`, + 11: `${pxToRem(128)}rem`, + 12: `${pxToRem(144)}rem`, + 13: `${pxToRem(160)}rem`, + 14: `${pxToRem(192)}rem`, + 15: `${pxToRem(208)}rem`, + }, + colors: { + transparent: "transparent", + current: "currentColor", + inherit: "inherit", + gray: { + 0: "#FFFFFF", + 30: "rgba(0, 0, 0, 0.03)", + 1000: "#000000", + }, + card: "#F7F7F7", + border: "rgba(0, 0, 0, 0.07)", + inactive: "rgba(0, 0, 0, 0.33)", + inactiveLight: "rgba(255, 255, 255, 0.44)", + muted: "#555555", + mutedLight: "rgba(255, 255, 255, 0.67)", + fg: "rgba(24, 24, 24, 0.67)", + lightfg: "rgba(24, 24, 24, 0.67)", + link: "#000000", + linkHover: "#555555", + docusaurusColorBase: "var(--ifm-font-color-base)", + docusaurusBgColor: "var(--ifm-background-color)", + docusaurusColorBorder: "var(--ifm-color-emphasis-200)", + }, + extend: {}, + }, + plugins: [], +}; diff --git a/docs/versioned_docs/version-v10.1.x/00-intro.md b/docs/versioned_docs/version-v10.1.x/00-intro.md new file mode 100644 index 0000000..ae4706d --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/00-intro.md @@ -0,0 +1,42 @@ +--- +slug: / +sidebar_position: 0 +--- + +# IBC-Go Documentation + +Welcome to the documentation for IBC-Go, the Golang implementation of the Inter-Blockchain Communication Protocol! + +The Inter-Blockchain Communication Protocol (IBC) is a protocol that allows blockchains to talk to each other. Chains that speak IBC can share any type of data as long as it's encoded in bytes, enabling the industry’s most feature-rich cross-chain interactions. IBC can be used to build a wide range of cross-chain applications that include token transfers, atomic swaps, multi-chain smart contracts (with or without mutually comprehensible VMs), and cross-chain account control. IBC is secure and permissionless. + +The protocol realizes this interoperability by specifying a set of data structures, abstractions, and semantics that can be implemented by any distributed ledger that satisfies a small set of requirements. + +:::note Notice +Since ibc-go v10, there are two versions of the protocol in the same release: IBC classic and IBC v2. The protocols are seperate - a connection uses either IBC classic or IBC v2 +::: + +## High-level overview of IBC v2 + +For a high level overview of IBC v2, please refer to [this blog post.](https://ibcprotocol.dev/blog/ibc-v2-announcement) For a more detailed understanding of the IBC v2 protocol, please refer to the [IBC v2 protocol specification.](https://github.com/cosmos/ibc/tree/main/spec/IBC_V2) + +If you are interested in using the cannonical deployment of IBC v2, connecting Cosmos chains and Ethereum, take a look at the [IBC Eureka](https://docs.skip.build/go/eureka/eureka-overview) documentation to get started. + +## High-level overview of IBC Classic + +The following diagram shows how IBC works at a high level: + +![Light Mode IBC Overview](./images/ibcoverview-light.svg#gh-light-mode-only)![Dark Mode IBC Overview](./images/ibcoverview-dark.svg#gh-dark-mode-only) + +The transport layer (TAO) provides the necessary infrastructure to establish secure connections and authenticate data packets between chains. The application layer builds on top of the transport layer and defines exactly how data packets should be packaged and interpreted by the sending and receiving chains. + +IBC provides a reliable, permissionless, and generic base layer (allowing for the secure relaying of data packets), while allowing for composability and modularity with separation of concerns by moving application designs (interpreting and acting upon the packet data) to a higher-level layer. This separation is reflected in the categories: + +- **IBC/TAO** comprises the Transport, Authentication, and Ordering of packets, i.e. the infrastructure layer. +- **IBC/APP** consists of the application handlers for the data packets being passed over the transport layer. These include but are not limited to fungible token transfers (ICS-20), NFT transfers (ICS-721), and interchain accounts (ICS-27). +- **Application module:** groups any application, middleware or smart contract that may wrap downstream application handlers to provide enhanced functionality. + +Note three crucial elements in the diagram: + +- The chains depend on relayers to communicate. [Relayers](https://github.com/cosmos/ibc/blob/main/spec/relayer/ics-018-relayer-algorithms/README.md) are the "physical" connection layer of IBC: off-chain processes responsible for relaying data between two chains running the IBC protocol by scanning the state of each chain, constructing appropriate datagrams, and executing them on the opposite chain as is allowed by the protocol. +- Many relayers can serve one or more channels to send messages between the chains. +- Each side of the connection uses the light client of the other chain to quickly verify incoming messages. diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/01-overview.md b/docs/versioned_docs/version-v10.1.x/01-ibc/01-overview.md new file mode 100644 index 0000000..2f3608a --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/01-overview.md @@ -0,0 +1,293 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /ibc/overview +--- + +# Overview + +:::note Synopsis +Learn about IBC, its components, and its use cases. +::: + +## What is the Inter-Blockchain Communication Protocol (IBC)? + +This document serves as a guide for developers who want to write their own Inter-Blockchain +Communication Protocol (IBC) applications for custom use cases. + +> IBC applications must be written as self-contained modules. + +Due to the modular design of the IBC Protocol, IBC +application developers do not need to be concerned with the low-level details of clients, +connections, and proof verification. + +This brief explanation of the lower levels of the +stack gives application developers a broad understanding of the IBC +Protocol. Abstraction layer details for channels and ports are most relevant for application developers and describe how to define custom packets and `IBCModule` callbacks. + +The requirements to have your module interact over IBC are: + +- Bind to a port or ports. +- Define your packet data. +- Use the default acknowledgment struct provided by core IBC or optionally define a custom acknowledgment struct. +- Standardize an encoding of the packet data. +- Implement the `IBCModule` interface. +- Implement the `UpgradableModule` interface (optional). + +Read on for a detailed explanation of how to write a self-contained IBC application module. + +## Components overview + +### [Clients](https://github.com/cosmos/ibc-go/blob/main/modules/core/02-client) + +IBC clients are on-chain light clients. Each light client is identified by a unique client ID. +IBC clients track the consensus states of other blockchains, along with the proof spec necessary to +properly verify proofs against the client's consensus state. A client can be associated with any number +of connections to the counterparty chain. The client identifier is auto generated using the client type +and the global client counter appended in the format: `{client-type}-{N}`. + +A `ClientState` should contain chain specific and light client specific information necessary for verifying updates +and upgrades to the IBC client. The `ClientState` may contain information such as chain ID, latest height, proof specs, +unbonding periods or the status of the light client. The `ClientState` should not contain information that +is specific to a given block at a certain height, this is the function of the `ConsensusState`. Each `ConsensusState` +should be associated with a unique block and should be referenced using a height. IBC clients are given a +client identifier prefixed store to store their associated client state and consensus states along with +any metadata associated with the consensus states. Consensus states are stored using their associated height. + +The supported IBC clients are: + +- [Solo Machine light client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/06-solomachine): Devices such as phones, browsers, or laptops. +- [Tendermint light client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/07-tendermint): The default for Cosmos SDK-based chains. +- [Wasm client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/08-wasm): Proxy client useful for running light clients written in a Wasm-compilable language. +- [Localhost (loopback) client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/09-localhost): Useful for testing, simulation, and relaying packets to modules on the same application. + +### IBC client heights + +IBC Client Heights are represented by the struct: + +```go +type Height struct { + RevisionNumber uint64 + RevisionHeight uint64 +} +``` + +The `RevisionNumber` represents the revision of the chain that the height is representing. +A revision typically represents a continuous, monotonically increasing range of block-heights. +The `RevisionHeight` represents the height of the chain within the given revision. + +On any reset of the `RevisionHeight`—for example, when hard-forking a Tendermint chain, +the `RevisionNumber` will get incremented. This allows IBC clients to distinguish between a +block height `n` of a previous revision of the chain (at revision `p`) and block-height `n` of the current +revision of the chain (at revision `e`). + +`Height`s that share the same revision number can be compared by simply comparing their respective `RevisionHeight`s. +`Height`s that do not share the same revision number will only be compared using their respective `RevisionNumber`s. +Thus a height `h` with revision number `e+1` will always be greater than a height `g` with revision number `e`, +**REGARDLESS** of the difference in revision heights. + +For example: + +```go +Height{RevisionNumber: 3, RevisionHeight: 0} > Height{RevisionNumber: 2, RevisionHeight: 100000000000} +``` + +When a Tendermint chain is running a particular revision, relayers can simply submit headers and proofs with the revision number +given by the chain's `chainID`, and the revision height given by the Tendermint block height. When a chain updates using a hard-fork +and resets its block-height, it is responsible for updating its `chainID` to increment the revision number. +IBC Tendermint clients then verifies the revision number against their `chainID` and treat the `RevisionHeight` as the Tendermint block-height. + +Tendermint chains wishing to use revisions to maintain persistent IBC connections even across height-resetting upgrades must format their `chainID`s +in the following manner: `{chainID}-{revision_number}`. On any height-resetting upgrade, the `chainID` **MUST** be updated with a higher revision number +than the previous value. + +For example: + +- Before upgrade `chainID`: `gaiamainnet-3` +- After upgrade `chainID`: `gaiamainnet-4` + +Clients that do not require revisions, such as the `06-solomachine` client, can simply hardcode `0` into the revision number whenever they +need to return an IBC height when implementing IBC interfaces and use the `RevisionHeight` exclusively. + +Other client types can implement their own logic to verify the IBC heights that relayers provide in their `Update`, `Misbehavior`, and +`Verify` functions respectively. + +The IBC interfaces expect an `ibcexported.Height` interface, however all clients must use the concrete implementation provided in +`02-client/types` and reproduced above. + +### [Connections](https://github.com/cosmos/ibc-go/blob/main/modules/core/03-connection) + +Connections encapsulate two [`ConnectionEnd`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/connection/v1/connection.proto#L17) +objects on two separate blockchains. Each `ConnectionEnd` is associated with a client of the +other blockchain (for example, the counterparty blockchain). The connection handshake is responsible +for verifying that the light clients on each chain are correct for their respective counterparties. +Connections, once established, are responsible for facilitating all cross-chain verifications of IBC state. +A connection can be associated with any number of channels. + +The connection handshake is a 4-step handshake. Briefly, if a given chain A wants to open a connection with +chain B using already established light clients on both chains: + +1. chain A sends a `ConnectionOpenInit` message to signal a connection initialization attempt with chain B. +2. chain B sends a `ConnectionOpenTry` message to try opening the connection on chain A. +3. chain A sends a `ConnectionOpenAck` message to mark its connection end state as open. +4. chain B sends a `ConnectionOpenConfirm` message to mark its connection end state as open. + +#### Time delayed connections + +Connections can be opened with a time delay by setting the `delay_period` field (in nanoseconds) in the [`MsgConnectionOpenInit`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/connection/v1/tx.proto#L45). +The time delay is used to require that the underlying light clients have been updated to a certain height before commitment verification can be performed. + +`delayPeriod` is used in conjunction with the [`max_expected_time_per_block`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/connection/v1/connection.proto#L113) parameter of the connection submodule to determine the `blockDelay`, which is number of blocks that the connection must be delayed by. + +When commitment verification is performed, the connection submodule will pass `delayPeriod` and `blockDelay` to the light client. It is up to the light client to determine whether the light client has been updated to the required height. Only the following light clients in `ibc-go` support time delayed connections: + +- `07-tendermint` +- `08-wasm` (passed to the contact) + +### [Proofs](https://github.com/cosmos/ibc-go/blob/main/modules/core/23-commitment) and [paths](https://github.com/cosmos/ibc-go/blob/main/modules/core/24-host) + +In IBC, blockchains do not directly pass messages to each other over the network. Instead, to +communicate, a blockchain commits some state to a specifically defined path that is reserved for a +specific message type and a specific counterparty. For example, for storing a specific connectionEnd as part +of a handshake or a packet intended to be relayed to a module on the counterparty chain. A relayer +process monitors for updates to these paths and relays messages by submitting the data stored +under the path and a proof to the counterparty chain. + +Proofs are passed from core IBC to light clients as bytes. It is up to light client implementations to interpret these bytes appropriately. + +- The paths that all IBC implementations must use for committing IBC messages is defined in +[ICS-24 Host State Machine Requirements](https://github.com/cosmos/ibc/tree/master/spec/core/ics-024-host-requirements). +- The proof format that all implementations must be able to produce and verify is defined in [ICS-23 Proofs](https://github.com/cosmos/ics23) implementation. + +### [Ports](https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port) + +An IBC module can bind to any number of ports. Each port must be identified by a unique `portID`. +Since IBC is designed to be secure with mutually distrusted modules operating on the same ledger, +binding a port returns a dynamic object capability. In order to take action on a particular port +(for example, an open channel with its port ID), a module must provide the dynamic object capability to the IBC +handler. This requirement prevents a malicious module from opening channels with ports it does not own. Thus, +IBC modules are responsible for claiming the capability that is returned on `BindPort`. + +### [Channels](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +An IBC channel can be established between two IBC ports. Currently, a port is exclusively owned by a +single module. IBC packets are sent over channels. Just as IP packets contain the destination IP +address and IP port, and the source IP address and source IP port, IBC packets contain +the destination port ID and channel ID, and the source port ID and channel ID. This packet structure enables IBC to +correctly route packets to the destination module while allowing modules receiving packets to +know the sender module. + +A channel can be `ORDERED`, where packets from a sending module must be processed by the +receiving module in the order they were sent. Or a channel can be `UNORDERED`, where packets +from a sending module are processed in the order they arrive (might be in a different order than they were sent). + +Modules can choose which channels they wish to communicate over with, thus IBC expects modules to +implement callbacks that are called during the channel handshake. These callbacks can do custom +channel initialization logic. If any callback returns an error, the channel handshake fails. Thus, by +returning errors on callbacks, modules can programmatically reject and accept channels. + +The channel handshake is a 4-step handshake. Briefly, if a given chain A wants to open a channel with +chain B using an already established connection: + +1. chain A sends a `ChanOpenInit` message to signal a channel initialization attempt with chain B. +2. chain B sends a `ChanOpenTry` message to try opening the channel on chain A. +3. chain A sends a `ChanOpenAck` message to mark its channel end status as open. +4. chain B sends a `ChanOpenConfirm` message to mark its channel end status as open. + +If all handshake steps are successful, the channel is opened on both sides. At each step in the handshake, the module +associated with the `ChannelEnd` executes its callback. So +on `ChanOpenInit`, the module on chain A executes its callback `OnChanOpenInit`. + +The channel identifier is auto derived in the format: `channel-{N}` where `N` is the next sequence to be used. + +#### Closing channels + +Closing a channel occurs in 2 handshake steps as defined in [ICS 04](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics). +Once a channel is closed, it cannot be reopened. The channel handshake steps are: + +**`ChanCloseInit`** closes a channel on the executing chain if + +- the channel exists and it is not already closed, +- the connection it exists upon is `OPEN`, +- the [IBC module callback `OnChanCloseInit`](./03-apps/02-ibcmodule.md#channel-closing-callbacks) returns `nil`. + +`ChanCloseInit` can be initiated by any user by submitting a `MsgChannelCloseInit` transaction. +Note that channels are automatically closed when a packet times out on an `ORDERED` channel. +A timeout on an `ORDERED` channel skips the `ChanCloseInit` step and immediately closes the channel. + +**`ChanCloseConfirm`** is a response to a counterparty channel executing `ChanCloseInit`. The channel +on the executing chain closes if + +- the channel exists and is not already closed, +- the connection the channel exists upon is `OPEN`, +- the executing chain successfully verifies that the counterparty channel has been closed +- the [IBC module callback `OnChanCloseConfirm`](./03-apps/02-ibcmodule.md#channel-closing-callbacks) returns `nil`. + +Currently, none of the IBC applications provided in ibc-go support `ChanCloseInit`. + +### [Packets](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +Modules communicate with each other by sending packets over IBC channels. All +IBC packets contain the destination `portID` and `channelID` along with the source `portID` and +`channelID`. This packet structure allows modules to know the sender module of a given packet. IBC packets +contain a sequence to optionally enforce ordering. + +IBC packets also contain a `TimeoutHeight` and a `TimeoutTimestamp` that determine the deadline before the receiving module must process a packet. + +Modules send custom application data to each other inside the `Data` `[]byte` field of the IBC packet. +Thus, packet data is opaque to IBC handlers. It is incumbent on a sender module to encode +their application-specific packet information into the `Data` field of packets. The receiver +module must decode that `Data` back to the original application data. + +### [Receipts and timeouts](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +Since IBC works over a distributed network and relies on potentially faulty relayers to relay messages between ledgers, +IBC must handle the case where a packet does not get sent to its destination in a timely manner or at all. Packets must +specify a non-zero value for timeout height (`TimeoutHeight`) or timeout timestamp (`TimeoutTimestamp` ) after which a packet can no longer be successfully received on the destination chain. + +- The `timeoutHeight` indicates a consensus height on the destination chain after which the packet is no longer to be processed, and instead counts as having timed-out. +- The `timeoutTimestamp` indicates a timestamp on the destination chain after which the packet is no longer to be processed, and instead counts as having timed-out. + +If the timeout passes without the packet being successfully received, the packet can no longer be +received on the destination chain. The sending module can timeout the packet and take appropriate actions. + +If the timeout is reached, then a proof of packet timeout can be submitted to the original chain. The original chain can then perform +application-specific logic to timeout the packet, perhaps by rolling back the packet send changes (refunding senders any locked funds, etc). + +- In `ORDERED` channels, a timeout of a single packet in the channel causes the channel to close. + + - If packet sequence `n` times out, then a packet at sequence `k > n` cannot be received without violating the contract of `ORDERED` channels that packets are processed in the order that they are sent. + - Since `ORDERED` channels enforce this invariant, a proof that sequence `n` has not been received on the destination chain by the specified timeout of packet `n` is sufficient to timeout packet `n` and close the channel. + +- In `UNORDERED` channels, the application-specific timeout logic for that packet is applied and the channel is not closed. + + - Packets can be received in any order. + - IBC writes a packet receipt for each sequence received in the `UNORDERED` channel. This receipt does not contain information; it is simply a marker intended to signify that the `UNORDERED` channel has received a packet at the specified sequence. + - To timeout a packet on an `UNORDERED` channel, a proof is required that a packet receipt **does not exist** for the packet's sequence by the specified timeout. + +For this reason, most modules should use `UNORDERED` channels as they require fewer liveness guarantees to function effectively for users of that channel. + +### [Acknowledgments](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +Modules can also choose to write application-specific acknowledgments upon processing a packet. Acknowledgments can be done: + +- Synchronously on `OnRecvPacket` if the module processes packets as soon as they are received from IBC module. +- Asynchronously if module processes packets at some later point after receiving the packet. + +This acknowledgment data is opaque to IBC much like the packet `Data` and is treated by IBC as a simple byte string `[]byte`. Receiver modules must encode their acknowledgment so that the sender module can decode it correctly. The encoding must be negotiated between the two parties during version negotiation in the channel handshake. + +The acknowledgment can encode whether the packet processing succeeded or failed, along with additional information that allows the sender module to take appropriate action. + +After the acknowledgment has been written by the receiving chain, a relayer relays the acknowledgment back to the original sender module. + +The original sender module then executes application-specific acknowledgment logic using the contents of the acknowledgment. + +- After an acknowledgement fails, packet-send changes can be rolled back (for example, refunding senders in ICS 20). +- After an acknowledgment is received successfully on the original sender on the chain, the corresponding packet commitment is deleted since it is no longer needed. + +## Further readings and specs + +If you want to learn more about IBC, check the following specifications: + +- [IBC specification overview](https://github.com/cosmos/ibc/blob/master/README.md) diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/02-integration.md b/docs/versioned_docs/version-v10.1.x/01-ibc/02-integration.md new file mode 100644 index 0000000..b58928c --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/02-integration.md @@ -0,0 +1,356 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /ibc/integration +--- + +# Integration + +:::note Synopsis +Learn how to integrate IBC to your application +::: + +This document outlines the required steps to integrate and configure the [IBC +module](https://github.com/cosmos/ibc-go/tree/main/modules/core) to your Cosmos SDK application and enable sending fungible token transfers to other chains. An [example app using ibc-go v10 is linked](https://github.com/gjermundgaraba/probe/tree/ibc/v10). + +## Integrating the IBC module + +Integrating the IBC module to your SDK-based application is straightforward. The general changes can be summarized in the following steps: + +- [Define additional `Keeper` fields for the new modules on the `App` type](#add-application-fields-to-app). +- [Add the module's `StoreKey`s and initialize their `Keeper`s](#configure-the-keepers). +- [Create Application Stacks with Middleware](#create-application-stacks-with-middleware) +- [Set up IBC router and add route for the `transfer` module](#register-module-routes-in-the-ibc-router). +- [Grant permissions to `transfer`'s `ModuleAccount`](#module-account-permissions). +- [Add the modules to the module `Manager`](#module-manager-and-simulationmanager). +- [Update the module `SimulationManager` to enable simulations](#module-manager-and-simulationmanager). +- [Integrate light client modules (e.g. `07-tendermint`)](#integrating-light-clients). +- [Add modules to `Begin/EndBlockers` and `InitGenesis`](#application-abci-ordering). + +### Add application fields to `App` + +We need to register the core `ibc` and `transfer` `Keeper`s. To support the use of IBC v2, `transferv2` and `callbacksv2` must also be registered as follows: + +```go title="app.go" +import ( + // other imports + // ... + ibckeeper "github.com/cosmos/ibc-go/v10/modules/core/keeper" + ibctransferkeeper "github.com/cosmos/ibc-go/v10/modules/apps/transfer/keeper" + // ibc v2 imports + transferv2 "github.com/cosmos/ibc-go/v10/modules/apps/transfer/v2" + ibccallbacksv2 "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/v2" +) + +type App struct { + // baseapp, keys and subspaces definitions + + // other keepers + // ... + IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly + TransferKeeper ibctransferkeeper.Keeper // for cross-chain fungible token transfers + + // ... + // module and simulation manager definitions +} +``` + +### Configure the `Keeper`s + +Initialize the IBC `Keeper`s (for core `ibc` and `transfer` modules), and any additional modules you want to include. + +:::note Notice +The capability module has been removed in ibc-go v10, therefore the `ScopedKeeper` has also been removed +::: + +```go +import ( + // other imports + // ... + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibckeeper "github.com/cosmos/ibc-go/v10/modules/core/keeper" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer" + ibctransfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" +) + +func NewApp(...args) *App { + // define codecs and baseapp + + // ... other module keepers + + // Create IBC Keeper + app.IBCKeeper = ibckeeper.NewKeeper( + appCodec, + runtime.NewKVStoreService(keys[ibcexported.StoreKey]), + app.GetSubspace(ibcexported.ModuleName), + app.UpgradeKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // Create Transfer Keeper + app.TransferKeeper = ibctransferkeeper.NewKeeper( + appCodec, + runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]), + app.GetSubspace(ibctransfertypes.ModuleName), + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeper, + app.MsgServiceRouter(), + app.AccountKeeper, + app.BankKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // ... continues +} +``` + +### Create Application Stacks with Middleware + +Middleware stacks in IBC allow you to wrap an `IBCModule` with additional logic for packets and acknowledgements. This is a chain of handlers that execute in order. The transfer stack below shows how to wire up transfer to use packet forward middleware, and the callbacks middleware. Note that the order is important. + +```go +// Create Transfer Stack for IBC Classic +maxCallbackGas := uint64(10_000_000) +wasmStackIBCHandler := wasm.NewIBCHandler(app.WasmKeeper, app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper) + +var transferStack porttypes.IBCModule +transferStack = transfer.NewIBCModule(app.TransferKeeper) +// callbacks wraps the transfer stack as its base app, and uses PacketForwardKeeper as the ICS4Wrapper +// i.e. packet-forward-middleware is higher on the stack and sits between callbacks and the ibc channel keeper +// Since this is the lowest level middleware of the transfer stack, it should be the first entrypoint for transfer keeper's +// WriteAcknowledgement. +cbStack := ibccallbacks.NewIBCMiddleware(transferStack, app.PacketForwardKeeper, wasmStackIBCHandler, maxCallbackGas) +transferStack = packetforward.NewIBCMiddleware( + cbStack, + app.PacketForwardKeeper, + 0, // retries on timeout + packetforwardkeeper.DefaultForwardTransferPacketTimeoutTimestamp, +) +``` + +#### IBC v2 Application Stack + +For IBC v2, an example transfer stack is shown below. In this case the transfer stack is using the callbacks middleware. + +```go +// Create IBC v2 transfer middleware stack +// the callbacks gas limit is recommended to be 10M for use with wasm contracts +maxCallbackGas := uint64(10_000_000) +wasmStackIBCHandler := wasm.NewIBCHandler(app.WasmKeeper, app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper) + +var ibcv2TransferStack ibcapi.IBCModule + ibcv2TransferStack = transferv2.NewIBCModule(app.TransferKeeper) + ibcv2TransferStack = ibccallbacksv2.NewIBCMiddleware(transferv2.NewIBCModule(app.TransferKeeper), app.IBCKeeper.ChannelKeeperV2, wasmStackIBCHandler, app.IBCKeeper.ChannelKeeperV2, maxCallbackGas) +``` + +### Register module routes in the IBC `Router` + +IBC needs to know which module is bound to which port so that it can route packets to the +appropriate module and call the appropriate callbacks. The port to module name mapping is handled by +IBC's port `Keeper`. However, the mapping from module name to the relevant callbacks is accomplished +by the port +[`Router`](https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port/types/router.go) on the +`ibc` module. + +Adding the module routes allows the IBC handler to call the appropriate callback when processing a channel handshake or a packet. + +Currently, a `Router` is static so it must be initialized and set correctly on app initialization. +Once the `Router` has been set, no new routes can be added. + +```go title="app.go" +import ( + // other imports + // ... + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + ibctransfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +) + +func NewApp(...args) *App { + // .. continuation from above + + // Create static IBC router, add transfer module route, then set and seal it + ibcRouter := porttypes.NewRouter() + ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) + // Setting Router will finalize all routes by sealing router + // No more routes can be added + app.IBCKeeper.SetRouter(ibcRouter) + + // ... continues +``` + +#### IBC v2 Router + +With IBC v2, there is a new [router](https://github.com/cosmos/ibc-go/blob/main/modules/core/api/router.go) that needs to register the routes for a portID to a given IBCModule. + +```go +// IBC v2 router creation + ibcRouterV2 := ibcapi.NewRouter() + ibcRouterV2.AddRoute(ibctransfertypes.PortID, ibcv2TransferStack) + // Setting Router will finalize all routes by sealing router + // No more routes can be added + app.IBCKeeper.SetRouterV2(ibcRouterV2) +``` + +### Module `Manager` and `SimulationManager` + +In order to use IBC, we need to add the new modules to the module `Manager` and to the `SimulationManager`, in case your application supports [simulations](https://docs.cosmos.network/main/learn/advanced/simulation). + +```go title="app.go" +import ( + // other imports + // ... + "github.com/cosmos/cosmos-sdk/types/module" + + ibc "github.com/cosmos/ibc-go/v10/modules/core" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer" +) + +func NewApp(...args) *App { + // ... continuation from above + + app.ModuleManager = module.NewManager( + // other modules + // ... + // highlight-start ++ ibc.NewAppModule(app.IBCKeeper), ++ transfer.NewAppModule(app.TransferKeeper), + // highlight-end + ) + + // ... + + app.simulationManager = module.NewSimulationManagerFromAppModules( + // other modules + // ... + app.ModuleManager.Modules, + map[string]module.AppModuleSimulation{}, + ) + + // ... continues +``` + +### Module account permissions + +After that, we need to grant `Minter` and `Burner` permissions to +the `transfer` `ModuleAccount` to mint and burn relayed tokens. + +```go title="app.go" +import ( + // other imports + // ... + "github.com/cosmos/cosmos-sdk/types/module" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + + // highlight-next-line ++ ibctransfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +) + +// app.go +var ( + // module account permissions + maccPerms = map[string][]string{ + // other module accounts permissions + // ... + ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + } +) +``` + +### Integrating light clients + +> Note that from v10 onwards, all light clients are expected to implement the [`LightClientInterface` interface](../03-light-clients/01-developer-guide/02-light-client-module.md#implementing-the-lightclientmodule-interface) defined by core IBC, and have to be explicitly registered in a chain's app.go. This is in contrast to earlier versions of ibc-go when `07-tendermint` and `06-solomachine` were added out of the box. Follow the steps below to integrate the `07-tendermint` light client. + +All light clients must be registered with `module.Manager` in a chain's app.go file. The following code example shows how to instantiate `07-tendermint` light client module and register its `ibctm.AppModule`. + +```go title="app.go" +import ( + // other imports + // ... + "github.com/cosmos/cosmos-sdk/types/module" + // highlight-next-line ++ ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" +) + +// app.go +// after sealing the IBC router +clientKeeper := app.IBCKeeper.ClientKeeper +storeProvider := app.IBCKeeper.ClientKeeper.GetStoreProvider() + +tmLightClientModule := ibctm.NewLightClientModule(appCodec, storeProvider) +clientKeeper.AddRoute(ibctm.ModuleName, &tmLightClientModule) +// ... +app.ModuleManager = module.NewManager( + // ... + ibc.NewAppModule(app.IBCKeeper), + transfer.NewAppModule(app.TransferKeeper), // i.e ibc-transfer module + + // register light clients on IBC + // highlight-next-line ++ ibctm.NewAppModule(tmLightClientModule), +) +``` + +#### Allowed Clients Params + +The allowed clients parameter defines an allow list of client types supported by the chain. The +default value is a single-element list containing the [`AllowedClients`](https://github.com/cosmos/ibc-go/blob/main/modules/core/02-client/types/client.pb.go#L248-L253) wildcard (`"*"`). Alternatively, the parameter +may be set with a list of client types (e.g. `"06-solomachine","07-tendermint","09-localhost"`). +A client type that is not registered on this list will fail upon creation or on genesis validation. +Note that, since the client type is an arbitrary string, chains must not register two light clients +which return the same value for the `ClientType()` function, otherwise the allow list check can be +bypassed. + +### Application ABCI ordering + +One addition from IBC is the concept of `HistoricalInfo` which is stored in the Cosmos SDK `x/staking` module. The number of records stored by `x/staking` is controlled by the `HistoricalEntries` parameter which stores `HistoricalInfo` on a per-height basis. +Each entry contains the historical information for the `Header` and `ValidatorSet` of this chain which is stored +at each height during the `BeginBlock` call. The `HistoricalInfo` is required to introspect a blockchain's prior state at a given height in order to verify the light client `ConsensusState` during the +connection handshake. + +```go title="app.go" +import ( + // other imports + // ... + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibckeeper "github.com/cosmos/ibc-go/v10/modules/core/keeper" + ibctransfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +) + +func NewApp(...args) *App { + // ... continuation from above + + // add x/staking, ibc and transfer modules to BeginBlockers + app.ModuleManager.SetOrderBeginBlockers( + // other modules ... + stakingtypes.ModuleName, + ibcexported.ModuleName, + ibctransfertypes.ModuleName, + ) + app.ModuleManager.SetOrderEndBlockers( + // other modules ... + stakingtypes.ModuleName, + ibcexported.ModuleName, + ibctransfertypes.ModuleName, + ) + + // ... + + genesisModuleOrder := []string{ + // other modules + // ... + ibcexported.ModuleName, + ibctransfertypes.ModuleName, + } + app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...) + + // ... continues +``` + +That's it! You have now wired up the IBC module and the `transfer` module, and are now able to send fungible tokens across +different chains. If you want to have a broader view of the changes take a look into the SDK's +[`SimApp`](https://github.com/cosmos/ibc-go/blob/main/testing/simapp/app.go). diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/00-ibcv2apps.md b/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/00-ibcv2apps.md new file mode 100644 index 0000000..f68bc39 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/00-ibcv2apps.md @@ -0,0 +1,316 @@ +--- +title: IBC v2 Applications +sidebar_label: IBC v2 Applications +sidebar_position: 0 +slug: /ibc/apps/ibcv2apps +--- +:::note Synopsis +Learn how to implement IBC v2 applications +::: + +To build an IBC v2 application the following steps are required: + +1. [Implement the `IBCModule` interface](#implement-the-ibcmodule-interface) +2. [Bind Ports](#bind-ports) +3. [Implement the IBCModule Keeper](#implement-the-ibcmodule-keeper) +4. [Implement application payload and success acknowledgement](#packets-and-payloads) +5. [Set and Seal the IBC Router](#routing) + +Highlighted improvements for app developers with IBC v2: + +- No need to support channel handshake callbacks +- Flexibility on upgrading application versioning, no need to use channel upgradability to renegotiate an application version, simply support the application version on both sides of the connection. +- Flexibility to choose your desired encoding type. + +## Implement the `IBCModule` interface + +The Cosmos SDK expects all IBC modules to implement the [`IBCModule` +interface](https://github.com/cosmos/ibc-go/blob/main/modules/core/api/module.go#L9-L53). This interface contains all of the callbacks IBC expects modules to implement. Note that for IBC v2, an application developer no longer needs to implement callbacks for the channel handshake. Note that this interface is distinct from the [porttypes.IBCModule interface][porttypes.IBCModule] used for IBC Classic. + +```go +// IBCModule implements the application interface given the keeper. +// The implementation of the IBCModule interface could for example be in a file called ibc_module.go, +// but ultimately file structure is up to the developer +type IBCModule struct { + keeper keeper.Keeper +} +``` + +Additionally, in the `module.go` file, add the following line: + +```go +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + // Add this line + _ porttypes.IBCModule = IBCModule{} +) +``` + +### Packet callbacks + +IBC expects modules to implement callbacks for handling the packet lifecycle, as defined in the `IBCModule` interface. + +With IBC v2, modules are not directly connected. Instead a pair of clients are connected and register the counterparty clientID. Packets are routed to the relevant application module by the portID registered in the Router. Relayers send packets between the routers/packet handlers on each chain. + +![IBC packet flow diagram](./images/packet_flow_v2.png) + +Briefly, a successful packet flow works as follows: + +1. A user sends a message to the IBC packet handler +2. The IBC packet handler validates the message, creates the packet and stores the commitment and returns the packet sequence number. The [`Payload`](https://github.com/cosmos/ibc-go/blob/fe25b216359fab71b3228461b05dbcdb1a554158/proto/ibc/core/channel/v2/packet.proto#L26-L38), which contains application specific data, is routed to the relevant application. +3. If the counterparty writes an acknowledgement of the packet then the sending chain will process the acknowledgement. +4. If the packet is not successfully received before the timeout, then the sending chain processes the packet's timeout. + +#### Sending packets + +[`MsgSendPacket`](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel/v2/types/tx.pb.go#L69-L75) is sent by a user to the [channel v2 message server](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel/v2/keeper/msg_server.go), which calls `ChannelKeeperV2.SendPacket`. This validates the message, creates the packet, stores the commitment and returns the packet sequence number. The application must specify its own payload which is used by the application and sent with `MsgSendPacket`. + +An application developer needs to implement the custom logic the application executes when a packet is sent. + +```go + +// OnSendPacket logic +func (im *IBCModule) OnSendPacket( + ctx sdk.Context, + sourceChannel string, + destinationChannel string, + sequence uint64, + payload channeltypesv2.Payload, + signer sdk.AccAddress) error { + +// implement any validation + +// implement payload decoding and validation + +// call the relevant keeper method for state changes as a result of application logic + +// emit events or telemetry data + +return nil +} + +``` + +#### Receiving packets + +To handle receiving packets, the module must implement the `OnRecvPacket` callback. An application module should validate and confirm support for the given version and encoding method used as there is greater flexibility in IBC v2 to support a range of versions and encoding methods. +The `OnRecvPacket` callback is invoked by the IBC module after the packet has been proven to be valid and correctly processed by the IBC +keepers. +Thus, the `OnRecvPacket` callback only needs to worry about making the appropriate state +changes given the packet data without worrying about whether the packet is valid or not. + +Modules may return to the IBC handler an acknowledgement which implements the `Acknowledgement` interface. +The IBC handler will then commit this acknowledgement of the packet so that a relayer may relay the +acknowledgement back to the sender module. + +The state changes that occurr during this callback could be: + +- the packet processing was successful as indicated by the `PacketStatus_Success` and an `Acknowledgement()` will be written +- if the packet processing was unsuccessful as indicated by the `PacketStatus_Failure` and an `ackErr` will be written + +Note that with IBC v2 the error acknowledgements are standardised and cannot be customised. + +```go +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, payload channeltypesv2.Payload, relayer sdk.AccAddress) channeltypesv2.RecvPacketResult { + + // do application state changes based on payload and return the result + // state changes should be written via the `RecvPacketResult` + + return recvResult +} +``` + +#### Acknowledging packets + +After a module writes an acknowledgement, a relayer can relay back the acknowledgement to the sender module. The sender module can +then process the acknowledgement using the `OnAcknowledgementPacket` callback. The contents of the +acknowledgement is entirely up to the application developer. + +IBC will pass in the acknowledgements as `[]byte` to this callback. The callback +is responsible for decoding the acknowledgement and processing it. The acknowledgement is serialised and deserialised using JSON. + +```go +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, acknowledgement []byte, payload channeltypesv2.Payload, relayer sdk.AccAddress) error { + + // check the type of the acknowledgement + + // if not ackErr, unmarshal the JSON acknowledgement and unmarshal packet payload + + // perform any application specific logic for processing acknowledgement + + // emit events + + return nil +} +``` + +#### Timeout packets + +If the timeout for a packet is reached before the packet is successfully received or the receiving +chain can no longer process the packet the sending chain must process the timeout using +`OnTimeoutPacket`. Again the IBC module will verify that the timeout is +valid, so our module only needs to implement the state machine logic for what to do once a +timeout is reached and the packet can no longer be received. + +```go +func (im IBCModule) OnTimeoutPacket( + ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, payload channeltypesv2.Payload, relayer sdk.AccAddress) error { + + // unmarshal packet data + + // do custom timeout logic, e.g. refund tokens for transfer +} +``` + +#### PacketDataUnmarshaler + +The `PacketDataUnmarshaler` interface is required for IBC v2 applications to implement because the encoding type is specified by the `Payload` and multiple encoding types are supported. + +```go +type PacketDataUnmarshaler interface { + // UnmarshalPacketData unmarshals the packet data into a concrete type + // the payload is provided and the packet data interface is returned + UnmarshalPacketData(payload channeltypesv2.Payload) (interface{}, error) +} +``` + +## Bind Ports + +Currently, ports must be bound on app initialization. In order to bind modules to their respective ports on initialization, the following needs to be implemented: + +> Note that `portID` does not refer to a certain numerical ID, like `localhost:8080` with a `portID` 8080. Rather it refers to the application module the port binds. For IBC Modules built with the Cosmos SDK, it defaults to the module's name and for Cosmwasm contracts it defaults to the contract address. + +Add port ID to the `GenesisState` proto definition: + +```protobuf +message GenesisState { + string port_id = 1; + // other fields +} +``` + +You can see an example for transfer [here](https://github.com/cosmos/ibc-go/blob/main/proto/ibc/applications/transfer/v1/genesis.proto). + +Add port ID as a key to the module store: + +```go +// x//types/keys.go +const ( + // ModuleName defines the IBC Module name + ModuleName = "moduleName" + + // PortID is the default port id that module binds to + PortID = "portID" + + // ... +) +``` + +Note that with IBC v2, the version does not need to be added as a key (as required with IBC classic) because versioning of applications is now contained within the [packet Payload](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel/v2/types/packet.go#L23-L32). + +Add port ID to `x//types/genesis.go`: + +```go +// in x//types/genesis.go + +// DefaultGenesisState returns a GenesisState +// with the portID defined in keys.go +func DefaultGenesisState() *GenesisState { + return &GenesisState{ + PortId: PortID, + // additional k-v fields + } +} + +// Validate performs basic genesis state validation +// returning an error upon any failure. +func (gs GenesisState) Validate() error { + if err := host.PortIdentifierValidator(gs.PortId); err != nil { + return err + } + //additional validations + + return gs.Params.Validate() +} +``` + +Set the port in the module keeper's for `InitGenesis`: + +```go +// SetPort sets the portID for the transfer module. Used in InitGenesis +func (k Keeper) SetPort(ctx sdk.Context, portID string) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Set(types.PortKey, []byte(portID)); err != nil { + panic(err) + } +} + + // Initialize any other module state, like params with SetParams. +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { + store := k.storeService.OpenKVStore(ctx) + bz := k.cdc.MustMarshal(¶ms) + if err := store.Set([]byte(types.ParamsKey), bz); err != nil { + panic(err) + } +} + // ... + +``` + +The module is set to the desired port. The setting and sealing happens during creation of the IBC router. + +## Implement the IBCModule Keeper + +More information on implementing the IBCModule Keepers can be found in the [keepers section](04-keeper.md) + +## Packets and Payloads + +Applications developers need to define the `Payload` contained within an [IBC packet](https://github.com/cosmos/ibc-go/blob/fe25b216359fab71b3228461b05dbcdb1a554158/proto/ibc/core/channel/v2/packet.proto#L11-L24). Note that in IBC v2 the `timeoutHeight` has been removed and only `timeoutTimestamp` is used. A packet can contain multiple payloads in a list. Each Payload includes: + +```go +// Payload contains the source and destination ports and payload for the application (version, encoding, raw bytes) +message Payload { + // specifies the source port of the packet. + string source_port = 1; + // specifies the destination port of the packet. + string destination_port = 2; + // version of the specified application. + string version = 3; + // the encoding used for the provided value. + string encoding = 4; + // the raw bytes for the payload. + bytes value = 5; +} +``` + +Note that compared to IBC classic, where the applications version and encoding is negotiated during the channel handshake, IBC v2 provides enhanced flexibility. The application version and encoding used by the Payload is defined in the Payload. An example Payload is illustrated below: + +```go +type MyAppPayloadData struct { + Field1 string + Field2 uint64 +} + +// Marshal your payload to bytes using your encoding +bz, err := json.Marshal(MyAppPayloadData{Field1: "example", Field2: 7}) + +// Wrap it in a channel v2 Payload +payload := channeltypesv2.NewPayload( + sourcePort, + destPort, + "my-app-v1", // App version + channeltypesv2.EncodingJSON, // Encoding type, e.g. JSON, protobuf or ABI + bz, // Encoded data +) +``` + +It is also possible to define your own custom success acknowledgement which will be returned to the sender if the packet is successfully recieved and is returned in the `RecvPacketResult`. Note that if the packet processing fails, it is not possible to define a custom error acknowledgment, a constant ackErr is returned. + +## Routing + +More information on implementing the IBC Router can be found in the [routing section](../03-apps/06-routing.md). + +[porttypes.IBCModule]: https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port/types/module.go diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/01-apps.md b/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/01-apps.md new file mode 100644 index 0000000..dae4179 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/01-apps.md @@ -0,0 +1,446 @@ +--- +title: IBC Applications +sidebar_label: IBC Applications +sidebar_position: 1 +slug: /ibc/apps/apps +--- + +# IBC Applications + +:::warning +This page is relevant for IBC Classic, naviagate to the IBC v2 applications page for information on v2 apps +::: + +Learn how to configure your application to use IBC and send data packets to other chains. + +This document serves as a guide for developers who want to write their own Inter-blockchain +Communication Protocol (IBC) applications for custom use cases. + +Due to the modular design of the IBC protocol, IBC +application developers do not need to concern themselves with the low-level details of clients, +connections, and proof verification, however a brief explaination is given. Then the document goes into detail on the abstraction layer most relevant for application +developers (channels and ports), and describes how to define your own custom packets, and +`IBCModule` callbacks. + +To have your module interact over IBC you must: bind to a port(s), define your own packet data and acknowledgement structs as well as how to encode/decode them, and implement the +`IBCModule` interface. Below is a more detailed explanation of how to write an IBC application +module correctly. + +:::note + +## Pre-requisites Readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: + +## Create a custom IBC application module + +### Implement `IBCModule` Interface and callbacks + +The Cosmos SDK expects all IBC modules to implement the [`IBCModule` +interface](https://github.com/cosmos/ibc-go/tree/main/modules/core/05-port/types/module.go). This +interface contains all of the callbacks IBC expects modules to implement. This section will describe +the callbacks that are called during channel handshake execution. + +Here are the channel handshake callbacks that modules are expected to implement: + +```go +// Called by IBC Handler on MsgOpenInit +func (k Keeper) OnChanOpenInit(ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + counterparty channeltypes.Counterparty, + version string, +) error { + + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + // Examples: Abort if order == UNORDERED, + // Abort if version is unsupported + err := checkArguments(args) + return err +} + +// Called by IBC Handler on MsgOpenTry +OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + if err := checkArguments(args); err != nil { + return err + } + + // Construct application version + // IBC applications must return the appropriate application version + // This can be a simple string or it can be a complex version constructed + // from the counterpartyVersion and other arguments. + // The version returned will be the channel version used for both channel ends. + appVersion := negotiateAppVersion(counterpartyVersion, args) + + return appVersion, nil +} + +// Called by IBC Handler on MsgOpenAck +OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyVersion string, +) error { + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} + +// Called by IBC Handler on MsgOpenConfirm +OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} +``` + +The channel closing handshake will also invoke module callbacks that can return errors to abort the +closing handshake. Closing a channel is a 2-step handshake, the initiating chain calls +`ChanCloseInit` and the finalizing chain calls `ChanCloseConfirm`. + +```go +// Called by IBC Handler on MsgCloseInit +OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + // ... do custom finalization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} + +// Called by IBC Handler on MsgCloseConfirm +OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // ... do custom finalization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} +``` + +#### Channel Handshake Version Negotiation + +Application modules are expected to verify versioning used during the channel handshake procedure. + +- `ChanOpenInit` callback should verify that the `MsgChanOpenInit.Version` is valid +- `ChanOpenTry` callback should construct the application version used for both channel ends. If no application version can be constructed, it must return an error. +- `ChanOpenAck` callback should verify that the `MsgChanOpenAck.CounterpartyVersion` is valid and supported. + +IBC expects application modules to perform application version negotiation in `OnChanOpenTry`. The negotiated version +must be returned to core IBC. If the version cannot be negotiated, an error should be returned. + +Versions must be strings but can implement any versioning structure. If your application plans to +have linear releases then semantic versioning is recommended. If your application plans to release +various features in between major releases then it is advised to use the same versioning scheme +as IBC. This versioning scheme specifies a version identifier and compatible feature set with +that identifier. Valid version selection includes selecting a compatible version identifier with +a subset of features supported by your application for that version. The struct is used for this +scheme can be found in `03-connection/types`. + +Since the version type is a string, applications have the ability to do simple version verification +via string matching or they can use the already implemented versioning system and pass the proto +encoded version into each handhshake call as necessary. + +ICS20 currently implements basic string matching with a single supported version. + +### Custom Packets + +Modules connected by a channel must agree on what application data they are sending over the +channel, as well as how they will encode/decode it. This process is not specified by IBC as it is up +to each application module to determine how to implement this agreement. However, for most +applications this will happen as a version negotiation during the channel handshake. While more +complex version negotiation is possible to implement inside the channel opening handshake, a very +simple version negotiation is implemented in the [ibc-transfer module](https://github.com/cosmos/ibc-go/tree/main/modules/apps/transfer/module.go). + +Thus, a module must define its custom packet data structure, along with a well-defined way to +encode and decode it to and from `[]byte`. + +```go +// Custom packet data defined in application module +type CustomPacketData struct { + // Custom fields ... +} + +EncodePacketData(packetData CustomPacketData) []byte { + // encode packetData to bytes +} + +DecodePacketData(encoded []byte) (CustomPacketData) { + // decode from bytes to packet data +} +``` + +Then a module must encode its packet data before sending it through IBC. + +```go +// Sending custom application packet data +data := EncodePacketData(customPacketData) +packet.Data = data +// Send packet to IBC, authenticating with channelCap +sequence, err := IBCChannelKeeper.SendPacket( + ctx, + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + data, +) +``` + +A module receiving a packet must decode the `PacketData` into a structure it expects so that it can +act on it. + +```go +// Receiving custom application packet data (in OnRecvPacket) +packetData := DecodePacketData(packet.Data) +// handle received custom packet data +``` + +#### Packet Flow Handling + +Just as IBC expected modules to implement callbacks for channel handshakes, IBC also expects modules +to implement callbacks for handling the packet flow through a channel. + +Once a module A and module B are connected to each other, relayers can start relaying packets and +acknowledgements back and forth on the channel. + +![IBC packet flow diagram](https://media.githubusercontent.com/media/cosmos/ibc/old/spec/ics-004-channel-and-packet-semantics/channel-state-machine.png) + +Briefly, a successful packet flow works as follows: + +1. module A sends a packet through the IBC module +2. the packet is received by module B +3. if module B writes an acknowledgement of the packet then module A will process the + acknowledgement +4. if the packet is not successfully received before the timeout, then module A processes the + packet's timeout. + +##### Sending Packets + +Modules do not send packets through callbacks, since the modules initiate the action of sending +packets to the IBC module, as opposed to other parts of the packet flow where msgs sent to the IBC +module must trigger execution on the port-bound module through the use of callbacks. Thus, to send a +packet a module simply needs to call `SendPacket` on the `IBCChannelKeeper`. + +```go +// Sending custom application packet data +data := EncodePacketData(customPacketData) +// Send packet to IBC, authenticating with channelCap +sequence, err := IBCChannelKeeper.SendPacket( + ctx, + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + data, +) +``` + +##### Receiving Packets + +To handle receiving packets, the module must implement the `OnRecvPacket` callback. This gets +invoked by the IBC module after the packet has been proved valid and correctly processed by the IBC +keepers. Thus, the `OnRecvPacket` callback only needs to worry about making the appropriate state +changes given the packet data without worrying about whether the packet is valid or not. + +Modules may return to the IBC handler an acknowledgement which implements the Acknowledgement interface. +The IBC handler will then commit this acknowledgement of the packet so that a relayer may relay the +acknowledgement back to the sender module. + +The state changes that occurred during this callback will only be written if: + +- the acknowledgement was successful as indicated by the `Success()` function of the acknowledgement +- if the acknowledgement returned is nil indicating that an asynchronous process is occurring + +NOTE: Applications which process asynchronous acknowledgements must handle reverting state changes +when appropriate. Any state changes that occurred during the `OnRecvPacket` callback will be written +for asynchronous acknowledgements. + +```go +OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, +) ibcexported.Acknowledgement { + // Decode the packet data + packetData := DecodePacketData(packet.Data) + + // do application state changes based on packet data and return the acknowledgement + // NOTE: The acknowledgement will indicate to the IBC handler if the application + // state changes should be written via the `Success()` function. Application state + // changes are only written if the acknowledgement is successful or the acknowledgement + // returned is nil indicating that an asynchronous acknowledgement will occur. + ack := processPacket(ctx, packet, packetData) + + return ack +} +``` + +The Acknowledgement interface: + +```go +// Acknowledgement defines the interface used to return +// acknowledgements in the OnRecvPacket callback. +type Acknowledgement interface { + Success() bool + Acknowledgement() []byte +} +``` + +### Acknowledgements + +Modules may commit an acknowledgement upon receiving and processing a packet in the case of synchronous packet processing. +In the case where a packet is processed at some later point after the packet has been received (asynchronous execution), the acknowledgement +will be written once the packet has been processed by the application which may be well after the packet receipt. + +NOTE: Most blockchain modules will want to use the synchronous execution model in which the module processes and writes the acknowledgement +for a packet as soon as it has been received from the IBC module. + +This acknowledgement can then be relayed back to the original sender chain, which can take action +depending on the contents of the acknowledgement. + +Just as packet data was opaque to IBC, acknowledgements are similarly opaque. Modules must pass and +receive acknowledegments with the IBC modules as byte strings. + +Thus, modules must agree on how to encode/decode acknowledgements. The process of creating an +acknowledgement struct along with encoding and decoding it, is very similar to the packet data +example above. [ICS 04](https://github.com/cosmos/ibc/blob/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope) +specifies a recommended format for acknowledgements. This acknowledgement type can be imported from +[channel types](https://github.com/cosmos/ibc-go/tree/main/modules/core/04-channel/types). + +While modules may choose arbitrary acknowledgement structs, a default acknowledgement types is provided by IBC [here](https://github.com/cosmos/ibc-go/blob/main/proto/ibc/core/channel/v1/channel.proto): + +```proto +// Acknowledgement is the recommended acknowledgement format to be used by +// app-specific protocols. +// NOTE: The field numbers 21 and 22 were explicitly chosen to avoid accidental +// conflicts with other protobuf message formats used for acknowledgements. +// The first byte of any message with this format will be the non-ASCII values +// `0xaa` (result) or `0xb2` (error). Implemented as defined by ICS: +// https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope +message Acknowledgement { + // response contains either a result or an error and must be non-empty + oneof response { + bytes result = 21; + string error = 22; + } +} +``` + +#### Acknowledging Packets + +After a module writes an acknowledgement, a relayer can relay back the acknowledgement to the sender module. The sender module can +then process the acknowledgement using the `OnAcknowledgementPacket` callback. The contents of the +acknowledgement is entirely up to the modules on the channel (just like the packet data); however, it +may often contain information on whether the packet was successfully processed along +with some additional data that could be useful for remediation if the packet processing failed. + +Since the modules are responsible for agreeing on an encoding/decoding standard for packet data and +acknowledgements, IBC will pass in the acknowledgements as `[]byte` to this callback. The callback +is responsible for decoding the acknowledgement and processing it. + +```go +OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, +) (*sdk.Result, error) { + // Decode acknowledgement + ack := DecodeAcknowledgement(acknowledgement) + + // process ack + res, err := processAck(ack) + return res, err +} +``` + +#### Timeout Packets + +If the timeout for a packet is reached before the packet is successfully received or the +counterparty channel end is closed before the packet is successfully received, then the receiving +chain can no longer process it. Thus, the sending chain must process the timeout using +`OnTimeoutPacket` to handle this situation. Again the IBC module will verify that the timeout is +indeed valid, so our module only needs to implement the state machine logic for what to do once a +timeout is reached and the packet can no longer be received. + +```go +OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, +) (*sdk.Result, error) { + // do custom timeout logic +} +``` + +### Routing + +As mentioned above, modules must implement the IBC module interface (which contains both channel +handshake callbacks and packet handling callbacks). The concrete implementation of this interface +must be registered with the module name as a route on the IBC `Router`. + +```go +// app.go +func NewApp(...args) *App { +// ... + +// Create static IBC router, add module routes, then set and seal it +ibcRouter := port.NewRouter() + +ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferModule) +// Note: moduleCallbacks must implement IBCModule interface +ibcRouter.AddRoute(moduleName, moduleCallbacks) + +// Setting Router will finalize all routes by sealing router +// No more routes can be added +app.IBCKeeper.SetRouter(ibcRouter) +``` + +## Working Example + +For a real working example of an IBC application, you can look through the `ibc-transfer` module +which implements everything discussed above. + +Here are the useful parts of the module to look at: + +[Binding to transfer +port](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/genesis.go) + +[Sending transfer +packets](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/relay.go) + +[Implementing IBC +callbacks](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/ibc_module.go) diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/02-ibcmodule.md b/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/02-ibcmodule.md new file mode 100644 index 0000000..ca3477d --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/02-ibcmodule.md @@ -0,0 +1,367 @@ +--- +title: Implement IBCModule interface and callbacks +sidebar_label: Implement IBCModule interface and callbacks +sidebar_position: 2 +slug: /ibc/apps/ibcmodule +--- + +# Implement `IBCModule` interface and callbacks + +:::note Synopsis +Learn how to implement the `IBCModule` interface and all of the callbacks it requires. +::: + +The Cosmos SDK expects all IBC modules to implement the [`IBCModule` +interface](https://github.com/cosmos/ibc-go/tree/main/modules/core/05-port/types/module.go). This interface contains all of the callbacks IBC expects modules to implement. They include callbacks related to channel handshake, closing and packet callbacks (`OnRecvPacket`, `OnAcknowledgementPacket` and `OnTimeoutPacket`). + +```go +// IBCModule implements the ICS26 interface for given the keeper. +// The implementation of the IBCModule interface could for example be in a file called ibc_module.go, +// but ultimately file structure is up to the developer +type IBCModule struct { + keeper keeper.Keeper +} +``` + +Additionally, in the `module.go` file, add the following line: + +```go +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + // Add this line + _ porttypes.IBCModule = IBCModule{} +) +``` + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: + +## Channel handshake callbacks + +This section will describe the callbacks that are called during channel handshake execution. + +Here are the channel handshake callbacks that modules are expected to implement: + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `checkArguments` and `negotiateAppVersion` functions. + +```go +// Called by IBC Handler on MsgOpenInit +func (im IBCModule) OnChanOpenInit(ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + // Examples: + // - Abort if order == UNORDERED, + // - Abort if version is unsupported + if err := checkArguments(args); err != nil { + return "", err + } + + + return version, nil +} + +// Called by IBC Handler on MsgOpenTry +func (im IBCModule) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + if err := checkArguments(args); err != nil { + return "", err + } + + // Construct application version + // IBC applications must return the appropriate application version + // This can be a simple string or it can be a complex version constructed + // from the counterpartyVersion and other arguments. + // The version returned will be the channel version used for both channel ends. + appVersion := negotiateAppVersion(counterpartyVersion, args) + + return appVersion, nil +} + +// Called by IBC Handler on MsgOpenAck +func (im IBCModule) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyVersion string, +) error { + if counterpartyVersion != types.Version { + return sdkerrors.Wrapf(types.ErrInvalidVersion, "invalid counterparty version: %s, expected %s", counterpartyVersion, types.Version) + } + + // do custom logic + + return nil +} + +// Called by IBC Handler on MsgOpenConfirm +func (im IBCModule) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // do custom logic + + return nil +} +``` + +### Channel closing callbacks + +The channel closing handshake will also invoke module callbacks that can return errors to abort the closing handshake. Closing a channel is a 2-step handshake, the initiating chain calls `ChanCloseInit` and the finalizing chain calls `ChanCloseConfirm`. + +Currently, all IBC modules in this repository return an error for `OnChanCloseInit` to prevent the channels from closing. This is because any user can call `ChanCloseInit` by submitting a `MsgChannelCloseInit` transaction. + +```go +// Called by IBC Handler on MsgCloseInit +func (im IBCModule) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + // ... do custom finalization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} + +// Called by IBC Handler on MsgCloseConfirm +func (im IBCModule) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // ... do custom finalization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} +``` + +### Channel handshake version negotiation + +Application modules are expected to verify versioning used during the channel handshake procedure. + +- `OnChanOpenInit` will verify that the relayer-chosen parameters + are valid and perform any custom `INIT` logic. + It may return an error if the chosen parameters are invalid + in which case the handshake is aborted. + If the provided version string is non-empty, `OnChanOpenInit` should return + the version string if valid or an error if the provided version is invalid. + **If the version string is empty, `OnChanOpenInit` is expected to + return a default version string representing the version(s) + it supports.** + If there is no default version string for the application, + it should return an error if the provided version is an empty string. +- `OnChanOpenTry` will verify the relayer-chosen parameters along with the + counterparty-chosen version string and perform custom `TRY` logic. + If the relayer-chosen parameters + are invalid, the callback must return an error to abort the handshake. + If the counterparty-chosen version is not compatible with this module's + supported versions, the callback must return an error to abort the handshake. + If the versions are compatible, the try callback must select the final version + string and return it to core IBC. + `OnChanOpenTry` may also perform custom initialization logic. +- `OnChanOpenAck` will error if the counterparty selected version string + is invalid and abort the handshake. It may also perform custom ACK logic. + +Versions must be strings but can implement any versioning structure. If your application plans to +have linear releases then semantic versioning is recommended. If your application plans to release +various features in between major releases then it is advised to use the same versioning scheme +as IBC. This versioning scheme specifies a version identifier and compatible feature set with +that identifier. Valid version selection includes selecting a compatible version identifier with +a subset of features supported by your application for that version. The struct used for this +scheme can be found in [03-connection/types](https://github.com/cosmos/ibc-go/blob/main/modules/core/03-connection/types/version.go#L16). + +Since the version type is a string, applications have the ability to do simple version verification +via string matching or they can use the already implemented versioning system and pass the proto +encoded version into each handhshake call as necessary. + +ICS20 currently implements basic string matching with a single supported version. + +## Packet callbacks + +Just as IBC expects modules to implement callbacks for channel handshakes, it also expects modules to implement callbacks for handling the packet flow through a channel, as defined in the `IBCModule` interface. + +Once a module A and module B are connected to each other, relayers can start relaying packets and acknowledgements back and forth on the channel. + +![IBC packet flow diagram](./images/packet_flow.png) + +Briefly, a successful packet flow works as follows: + +1. Module A sends a packet through the IBC module +2. The packet is received by module B +3. If module B writes an acknowledgement of the packet then module A will process the acknowledgement +4. If the packet is not successfully received before the timeout, then module A processes the packet's timeout. + +### Sending packets + +Modules **do not send packets through callbacks**, since the modules initiate the action of sending packets to the IBC module, as opposed to other parts of the packet flow where messages sent to the IBC +module must trigger execution on the port-bound module through the use of callbacks. Thus, to send a packet a module simply needs to call `SendPacket` on the `IBCChannelKeeper`. + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `EncodePacketData(customPacketData)` function. + +```go +// Sending custom application packet data +data := EncodePacketData(customPacketData) +// Send packet to IBC, authenticating with channelCap +sequence, err := IBCChannelKeeper.SendPacket( + ctx, + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + data, +) +``` + +### Receiving packets + +To handle receiving packets, the module must implement the `OnRecvPacket` callback. This gets +invoked by the IBC module after the packet has been proved valid and correctly processed by the IBC +keepers. Thus, the `OnRecvPacket` callback only needs to worry about making the appropriate state +changes given the packet data without worrying about whether the packet is valid or not. + +Modules may return to the IBC handler an acknowledgement which implements the `Acknowledgement` interface. +The IBC handler will then commit this acknowledgement of the packet so that a relayer may relay the +acknowledgement back to the sender module. + +The state changes that occurred during this callback will only be written if: + +- the acknowledgement was successful as indicated by the `Success()` function of the acknowledgement +- if the acknowledgement returned is nil indicating that an asynchronous process is occurring + +NOTE: Applications which process asynchronous acknowledgements must handle reverting state changes +when appropriate. Any state changes that occurred during the `OnRecvPacket` callback will be written +for asynchronous acknowledgements. + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `DecodePacketData(packet.Data)` function. + +```go +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, +) ibcexported.Acknowledgement { + // Decode the packet data + packetData := DecodePacketData(packet.Data) + + // do application state changes based on packet data and return the acknowledgement + // NOTE: The acknowledgement will indicate to the IBC handler if the application + // state changes should be written via the `Success()` function. Application state + // changes are only written if the acknowledgement is successful or the acknowledgement + // returned is nil indicating that an asynchronous acknowledgement will occur. + ack := processPacket(ctx, packet, packetData) + + return ack +} +``` + +Reminder, the `Acknowledgement` interface: + +```go +// Acknowledgement defines the interface used to return +// acknowledgements in the OnRecvPacket callback. +type Acknowledgement interface { + Success() bool + Acknowledgement() []byte +} +``` + +### Acknowledging packets + +After a module writes an acknowledgement, a relayer can relay back the acknowledgement to the sender module. The sender module can +then process the acknowledgement using the `OnAcknowledgementPacket` callback. The contents of the +acknowledgement is entirely up to the modules on the channel (just like the packet data); however, it +may often contain information on whether the packet was successfully processed along +with some additional data that could be useful for remediation if the packet processing failed. + +Since the modules are responsible for agreeing on an encoding/decoding standard for packet data and +acknowledgements, IBC will pass in the acknowledgements as `[]byte` to this callback. The callback +is responsible for decoding the acknowledgement and processing it. + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `DecodeAcknowledgement(acknowledgments)` and `processAck(ack)` functions. + +```go +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, +) (*sdk.Result, error) { + // Decode acknowledgement + ack := DecodeAcknowledgement(acknowledgement) + + // process ack + res, err := processAck(ack) + return res, err +} +``` + +### Timeout packets + +If the timeout for a packet is reached before the packet is successfully received or the +counterparty channel end is closed before the packet is successfully received, then the receiving +chain can no longer process it. Thus, the sending chain must process the timeout using +`OnTimeoutPacket` to handle this situation. Again the IBC module will verify that the timeout is +indeed valid, so our module only needs to implement the state machine logic for what to do once a +timeout is reached and the packet can no longer be received. + +```go +func (im IBCModule) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, +) (*sdk.Result, error) { + // do custom timeout logic +} +``` + +### Optional interfaces + +The following interface are optional and MAY be implemented by an IBCModule. + +#### PacketDataUnmarshaler + +The `PacketDataUnmarshaler` interface is defined as follows: + +```go +// PacketDataUnmarshaler defines an optional interface which allows a middleware to +// request the packet data to be unmarshaled by the base application. +type PacketDataUnmarshaler interface { + // UnmarshalPacketData unmarshals the packet data into a concrete type + // ctx, portID, channelID are provided as arguments, so that (if needed) + // the packet data can be unmarshaled based on the channel version. + // The version of the underlying app is also returned. + UnmarshalPacketData(ctx sdk.Context, portID, channelID string, bz []byte) (interface{}, string, error) +} +``` + +The implementation of `UnmarshalPacketData` should unmarshal the bytes into the packet data type defined for an IBC stack. +The base application of an IBC stack should unmarshal the bytes into its packet data type, while a middleware may simply defer the call to the underlying application. + +This interface allows middlewares to unmarshal a packet data in order to make use of interfaces the packet data type implements. +For example, the callbacks middleware makes use of this function to access packet data types which implement the `PacketData` and `PacketDataProvider` interfaces. diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/03-bindports.md b/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/03-bindports.md new file mode 100644 index 0000000..6eb8568 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/03-bindports.md @@ -0,0 +1,106 @@ +--- +title: Bind ports +sidebar_label: Bind ports +sidebar_position: 3 +slug: /ibc/apps/bindports +--- + +# Bind ports + +:::note Synopsis +Learn what changes to make to bind modules to their ports on initialization. +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: +Currently, ports must be bound on app initialization. In order to bind modules to their respective ports on initialization, the following needs to be implemented: + +> Note that `portID` does not refer to a certain numerical ID, like `localhost:8080` with a `portID` 8080. Rather it refers to the application module the port binds. For IBC Modules built with the Cosmos SDK, it defaults to the module's name and for Cosmwasm contracts it defaults to the contract address. + +1. Add port ID to the `GenesisState` proto definition: + +```protobuf +message GenesisState { + string port_id = 1; + // other fields +} +``` + +2. Add port ID as a key to the module store: + +```go +// x//types/keys.go +const ( + // ModuleName defines the IBC Module name + ModuleName = "moduleName" + + // Version defines the current version the IBC + // module supports + Version = "moduleVersion-1" + + // PortID is the default port id that module binds to + PortID = "portID" + + // ... +) +``` + +3. Add port ID to `x//types/genesis.go`: + +```go +// in x//types/genesis.go + +// DefaultGenesisState returns a GenesisState with "portID" as the default PortID. +func DefaultGenesisState() *GenesisState { + return &GenesisState{ + PortId: PortID, + // additional k-v fields + } +} + +// Validate performs basic genesis state validation returning an error upon any +// failure. +func (gs GenesisState) Validate() error { + if err := host.PortIdentifierValidator(gs.PortId); err != nil { + return err + } + //additional validations + + return gs.Params.Validate() +} +``` + +4. Set the port in the module keeper's for `InitGenesis`: + +:::note +The capability module has been removed so port binding has also changed +::: + +```go +// SetPort sets the portID for the transfer module. Used in InitGenesis +func (k Keeper) SetPort(ctx sdk.Context, portID string) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Set(types.PortKey, []byte(portID)); err != nil { + panic(err) + } +} + + // Initialize any other module state, like params with SetParams. +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { + store := k.storeService.OpenKVStore(ctx) + bz := k.cdc.MustMarshal(¶ms) + if err := store.Set([]byte(types.ParamsKey), bz); err != nil { + panic(err) + } +} + // ... + +``` + +The module is set to the desired port. The setting and sealing happens during creation of the IBC router. diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/04-keeper.md b/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/04-keeper.md new file mode 100644 index 0000000..4c0d124 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/04-keeper.md @@ -0,0 +1,70 @@ +--- +title: Keeper +sidebar_label: Keeper +sidebar_position: 4 +slug: /ibc/apps/keeper +--- + +# Keeper + +:::note Synopsis +Learn how to implement the IBC Module keeper. Relevant for IBC classic and v2 +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: +In the previous sections, on channel handshake callbacks and port binding in `InitGenesis`, a reference was made to keeper methods that need to be implemented when creating a custom IBC module. Below is an overview of how to define an IBC module's keeper. + +> Note that some code has been left out for clarity, to get a full code overview, please refer to [the transfer module's keeper in the ibc-go repo](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/keeper.go). + +```go +// Keeper defines the IBC app module keeper +type Keeper struct { + storeKey sdk.StoreKey + cdc codec.BinaryCodec + paramSpace paramtypes.Subspace + + channelKeeper types.ChannelKeeper + portKeeper types.PortKeeper + + // ... additional according to custom logic +} + +// NewKeeper creates a new IBC app module Keeper instance +func NewKeeper( + // args +) Keeper { + // ... + + return Keeper{ + cdc: cdc, + storeKey: key, + paramSpace: paramSpace, + + channelKeeper: channelKeeper, + portKeeper: portKeeper, + + // ... additional according to custom logic + } +} + +// GetPort returns the portID for the IBC app module. Used in ExportGenesis +func (k Keeper) GetPort(ctx sdk.Context) string { + store := ctx.KVStore(k.storeKey) + return string(store.Get(types.PortKey)) +} + +// SetPort sets the portID for the IBC app module. Used in InitGenesis +func (k Keeper) SetPort(ctx sdk.Context, portID string) { + store := ctx.KVStore(k.storeKey) + store.Set(types.PortKey, []byte(portID)) +} + +// ... additional according to custom logic +``` diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/05-packets_acks.md b/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/05-packets_acks.md new file mode 100644 index 0000000..de96b87 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/05-packets_acks.md @@ -0,0 +1,163 @@ +--- +title: Define packets and acks +sidebar_label: Define packets and acks +sidebar_position: 5 +slug: /ibc/apps/packets_acks +--- + +# Define packets and acks + +:::note Synopsis +Learn how to define custom packet and acknowledgement structs and how to encode and decode them. +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: + +## Custom packets + +Modules connected by a channel must agree on what application data they are sending over the +channel, as well as how they will encode/decode it. This process is not specified by IBC as it is up +to each application module to determine how to implement this agreement. However, for most +applications this will happen as a version negotiation during the channel handshake. While more +complex version negotiation is possible to implement inside the channel opening handshake, a very +simple version negotiation is implemented in the [ibc-transfer module](https://github.com/cosmos/ibc-go/tree/main/modules/apps/transfer/module.go). + +Thus, a module must define its custom packet data structure, along with a well-defined way to +encode and decode it to and from `[]byte`. + +```go +// Custom packet data defined in application module +type CustomPacketData struct { + // Custom fields ... +} + +EncodePacketData(packetData CustomPacketData) []byte { + // encode packetData to bytes +} + +DecodePacketData(encoded []byte) (CustomPacketData) { + // decode from bytes to packet data +} +``` + +> Note that the `CustomPacketData` struct is defined in the proto definition and then compiled by the protobuf compiler. + +Then a module must encode its packet data before sending it through IBC. + +```go +// Sending custom application packet data +data := EncodePacketData(customPacketData) +// Send packet to IBC, authenticating with channelCap +sequence, err := IBCChannelKeeper.SendPacket( + ctx, + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + data, +) +``` + +A module receiving a packet must decode the `PacketData` into a structure it expects so that it can +act on it. + +```go +// Receiving custom application packet data (in OnRecvPacket) +packetData := DecodePacketData(packet.Data) +// handle received custom packet data +``` + +### Optional interfaces + +The following interfaces are optional and MAY be implemented by a custom packet type. +They allow middlewares such as callbacks to access information stored within the packet data. + +#### PacketData interface + +The `PacketData` interface is defined as follows: + +```go +// PacketData defines an optional interface which an application's packet data structure may implement. +type PacketData interface { + // GetPacketSender returns the sender address of the packet data. + // If the packet sender is unknown or undefined, an empty string should be returned. + GetPacketSender(sourcePortID string) string +} +``` + +The implementation of `GetPacketSender` should return the sender of the packet data. +If the packet sender is unknown or undefined, an empty string should be returned. + +This interface is intended to give IBC middlewares access to the packet sender of a packet data type. + +#### PacketDataProvider interface + +The `PacketDataProvider` interface is defined as follows: + +```go +// PacketDataProvider defines an optional interfaces for retrieving custom packet data stored on behalf of another application. +// An existing problem in the IBC middleware design is the inability for a middleware to define its own packet data type and insert packet sender provided information. +// A short term solution was introduced into several application's packet data to utilize a memo field to carry this information on behalf of another application. +// This interfaces standardizes that behaviour. Upon realization of the ability for middleware's to define their own packet data types, this interface will be deprecated and removed with time. +type PacketDataProvider interface { + // GetCustomPacketData returns the packet data held on behalf of another application. + // The name the information is stored under should be provided as the key. + // If no custom packet data exists for the key, nil should be returned. + GetCustomPacketData(key string) interface{} +} +``` + +The implementation of `GetCustomPacketData` should return packet data held on behalf of another application (if present and supported). +If this functionality is not supported, it should return nil. Otherwise it should return the packet data associated with the provided key. + +This interface gives IBC applications access to the packet data information embedded into the base packet data type. +Within transfer and interchain accounts, the embedded packet data is stored within the Memo field. + +Once all IBC applications within an IBC stack are capable of creating/maintaining their own packet data type's, this interface function will be deprecated and removed. + +## Acknowledgements + +Modules may commit an acknowledgement upon receiving and processing a packet in the case of synchronous packet processing. +In the case where a packet is processed at some later point after the packet has been received (asynchronous execution), the acknowledgement +will be written once the packet has been processed by the application which may be well after the packet receipt. + +NOTE: Most blockchain modules will want to use the synchronous execution model in which the module processes and writes the acknowledgement +for a packet as soon as it has been received from the IBC module. + +This acknowledgement can then be relayed back to the original sender chain, which can take action +depending on the contents of the acknowledgement. + +Just as packet data was opaque to IBC, acknowledgements are similarly opaque. Modules must pass and +receive acknowledegments with the IBC modules as byte strings. + +Thus, modules must agree on how to encode/decode acknowledgements. The process of creating an +acknowledgement struct along with encoding and decoding it, is very similar to the packet data +example above. [ICS 04](https://github.com/cosmos/ibc/blob/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope) +specifies a recommended format for acknowledgements. This acknowledgement type can be imported from +[channel types](https://github.com/cosmos/ibc-go/tree/main/modules/core/04-channel/types). + +While modules may choose arbitrary acknowledgement structs, a default acknowledgement types is provided by IBC [here](https://github.com/cosmos/ibc-go/blob/main/proto/ibc/core/channel/v1/channel.proto): + +```protobuf +// Acknowledgement is the recommended acknowledgement format to be used by +// app-specific protocols. +// NOTE: The field numbers 21 and 22 were explicitly chosen to avoid accidental +// conflicts with other protobuf message formats used for acknowledgements. +// The first byte of any message with this format will be the non-ASCII values +// `0xaa` (result) or `0xb2` (error). Implemented as defined by ICS: +// https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope +message Acknowledgement { + // response contains either a result or an error and must be non-empty + oneof response { + bytes result = 21; + string error = 22; + } +} +``` diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/06-routing.md b/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/06-routing.md new file mode 100644 index 0000000..e84c2d3 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/06-routing.md @@ -0,0 +1,44 @@ +--- +title: Routing +sidebar_label: Routing +sidebar_position: 6 +slug: /ibc/apps/routing +--- + +# Routing + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: +:::note Synopsis +Learn how to hook a route to the IBC router for the custom IBC module. +::: + +As mentioned above, modules must implement the `IBCModule` interface (which contains both channel +handshake callbacks for IBC classic only, and packet handling callbacks for IBC classic and v2). The concrete implementation of this interface +must be registered with the module name as a route on the IBC `Router`. + +```go +// app.go +func NewApp(...args) *App { + // ... + + // Create static IBC router, add module routes, then set and seal it + ibcRouter := port.NewRouter() + + ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferModule) + // Note: moduleCallbacks must implement IBCModule interface + ibcRouter.AddRoute(moduleName, moduleCallbacks) + + // Setting Router will finalize all routes by sealing router + // No more routes can be added + app.IBCKeeper.SetRouter(ibcRouter) + + // ... +} +``` diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/_category_.json b/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/_category_.json new file mode 100644 index 0000000..1c34da9 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Applications", + "position": 3, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/images/packet_flow.png b/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/images/packet_flow.png new file mode 100644 index 0000000..e5bae3f Binary files /dev/null and b/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/images/packet_flow.png differ diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/images/packet_flow_v2.png b/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/images/packet_flow_v2.png new file mode 100644 index 0000000..7e3d673 Binary files /dev/null and b/docs/versioned_docs/version-v10.1.x/01-ibc/03-apps/images/packet_flow_v2.png differ diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/04-middleware/01-overview.md b/docs/versioned_docs/version-v10.1.x/01-ibc/04-middleware/01-overview.md new file mode 100644 index 0000000..d26c73b --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/04-middleware/01-overview.md @@ -0,0 +1,55 @@ +--- +title: IBC middleware +sidebar_label: IBC middleware +sidebar_position: 1 +slug: /ibc/middleware/overview +--- + +# IBC middleware + +:::note Synopsis +Learn how to write your own custom middleware to wrap an IBC application, and understand how to hook different middleware to IBC base applications to form different IBC application stacks +::: + +This documentation serves as a guide for middleware developers who want to write their own middleware and for chain developers who want to use IBC middleware on their chains. + +After going through the overview they can consult respectively: + +- [documentation on developing custom middleware](02-develop.md) +- [documentation on integrating middleware into a stack on a chain](03-integration.md) + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC Integration](../02-integration.md) +- [IBC Application Developer Guide](../03-apps/01-apps.md) + +::: + +## Why middleware? + +IBC applications are designed to be self-contained modules that implement their own application-specific logic through a set of interfaces with the core IBC handlers. These core IBC handlers, in turn, are designed to enforce the correctness properties of IBC (transport, authentication, ordering) while delegating all application-specific handling to the IBC application modules. **However, there are cases where some functionality may be desired by many applications, yet not appropriate to place in core IBC.** + +Middleware allows developers to define the extensions as separate modules that can wrap over the base application. This middleware can thus perform its own custom logic, and pass data into the application so that it may run its logic without being aware of the middleware's existence. This allows both the application and the middleware to implement its own isolated logic while still being able to run as part of a single packet flow. + +## Definitions + +`Middleware`: A self-contained module that sits between core IBC and an underlying IBC application during packet execution. All messages between core IBC and underlying application must flow through middleware, which may perform its own custom logic. + +`Underlying Application`: An underlying application is the application that is directly connected to the middleware in question. This underlying application may itself be middleware that is chained to a base application. + +`Base Application`: A base application is an IBC application that does not contain any middleware. It may be nested by 0 or multiple middleware to form an application stack. + +`Application Stack (or stack)`: A stack is the complete set of application logic (middleware(s) + base application) that gets connected to core IBC. A stack may be just a base application, or it may be a series of middlewares that nest a base application. + +The diagram below gives an overview of a middleware stack consisting of two middleware (one stateless, the other stateful). + +![middleware-stack.png](./images/middleware-stack.png) + +Keep in mind that: + +- **The order of the middleware matters** (more on how to correctly define your stack in the code will follow in the [integration section](03-integration.md)). +- Depending on the type of message, it will either be passed on from the base application up the middleware stack to core IBC or down the stack in the reverse situation (handshake and packet callbacks). +- IBC middleware will wrap over an underlying IBC application and sits between core IBC and the application. It has complete control in modifying any message coming from IBC to the application, and any message coming from the application to core IBC. **Middleware must be completely trusted by chain developers who wish to integrate them**, as this gives them complete flexibility in modifying the application(s) they wrap. diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/04-middleware/02-develop.md b/docs/versioned_docs/version-v10.1.x/01-ibc/04-middleware/02-develop.md new file mode 100644 index 0000000..1ed72e4 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/04-middleware/02-develop.md @@ -0,0 +1,478 @@ +--- +title: Create a custom IBC middleware +sidebar_label: Create a custom IBC middleware +sidebar_position: 3 +slug: /ibc/middleware/develop +--- + + +# Create a custom IBC middleware + +IBC middleware will wrap over an underlying IBC application (a base application or downstream middleware) and sits between core IBC and the base application. + +:::warning +middleware developers must use the same serialization and deserialization method as in ibc-go's codec: transfertypes.ModuleCdc.[Must]MarshalJSON +::: + +For middleware builders this means: + +```go +import transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +transfertypes.ModuleCdc.[Must]MarshalJSON +func MarshalAsIBCDoes(ack channeltypes.Acknowledgement) ([]byte, error) { + return transfertypes.ModuleCdc.MarshalJSON(&ack) +} +``` + +The interfaces a middleware must implement are found [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/05-port/types/module.go). + +```go +// Middleware implements the ICS26 Module interface +type Middleware interface { + IBCModule // middleware has access to an underlying application which may be wrapped by more middleware + ICS4Wrapper // middleware has access to ICS4Wrapper which may be core IBC Channel Handler or a higher-level middleware that wraps this middleware. +} +``` + +An `IBCMiddleware` struct implementing the `Middleware` interface, can be defined with its constructor as follows: + +```go +// @ x/module_name/ibc_middleware.go + +// IBCMiddleware implements the ICS26 callbacks and ICS4Wrapper for the fee middleware given the +// fee keeper and the underlying application. +type IBCMiddleware struct { + app porttypes.IBCModule + keeper keeper.Keeper +} + +// NewIBCMiddleware creates a new IBCMiddleware given the keeper and underlying application +func NewIBCMiddleware(app porttypes.IBCModule, k keeper.Keeper) IBCMiddleware { + return IBCMiddleware{ + app: app, + keeper: k, + } +} +``` + +## Implement `IBCModule` interface + +`IBCMiddleware` is a struct that implements the [ICS-26 `IBCModule` interface (`porttypes.IBCModule`)](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/05-port/types/module.go#L14-L107). It is recommended to separate these callbacks into a separate file `ibc_middleware.go`. + +> Note how this is analogous to implementing the same interfaces for IBC applications that act as base applications. + +As will be mentioned in the [integration section](03-integration.md), this struct should be different than the struct that implements `AppModule` in case the middleware maintains its own internal state and processes separate SDK messages. + +The middleware must have access to the underlying application, and be called before it during all ICS-26 callbacks. It may execute custom logic during these callbacks, and then call the underlying application's callback. + +> Middleware **may** choose not to call the underlying application's callback at all. Though these should generally be limited to error cases. + +The `IBCModule` interface consists of the channel handshake callbacks and packet callbacks. Most of the custom logic will be performed in the packet callbacks, in the case of the channel handshake callbacks, introducing the middleware requires consideration to the version negotiation. + +### Channel handshake callbacks + +#### Version negotiation + +In the case where the IBC middleware expects to speak to a compatible IBC middleware on the counterparty chain, they must use the channel handshake to negotiate the middleware version without interfering in the version negotiation of the underlying application. + +Middleware accomplishes this by formatting the version in a JSON-encoded string containing the middleware version and the application version. The application version may as well be a JSON-encoded string, possibly including further middleware and app versions, if the application stack consists of multiple milddlewares wrapping a base application. The format of the version is specified in ICS-30 as the following: + +```json +{ + "": "", + "app_version": "" +} +``` + +The `` key in the JSON struct should be replaced by the actual name of the key for the corresponding middleware (e.g. `fee_version`). + +During the handshake callbacks, the middleware can unmarshal the version string and retrieve the middleware and application versions. It can do its negotiation logic on ``, and pass the `` to the underlying application. + +> **NOTE**: Middleware that does not need to negotiate with a counterparty middleware on the remote stack will not implement the version unmarshalling and negotiation, and will simply perform its own custom logic on the callbacks without relying on the counterparty behaving similarly. + +#### `OnChanOpenInit` + +```go +func (im IBCMiddleware) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + if version != "" { + // try to unmarshal JSON-encoded version string and pass + // the app-specific version to app callback. + // otherwise, pass version directly to app callback. + metadata, err := Unmarshal(version) + if err != nil { + // Since it is valid for fee version to not be specified, + // the above middleware version may be for another middleware. + // Pass the entire version string onto the underlying application. + return im.app.OnChanOpenInit( + ctx, + order, + connectionHops, + portID, + channelID, + counterparty, + version, + ) + } + else { + metadata = { + // set middleware version to default value + MiddlewareVersion: defaultMiddlewareVersion, + // allow application to return its default version + AppVersion: "", + } + } + } + + doCustomLogic() + + // if the version string is empty, OnChanOpenInit is expected to return + // a default version string representing the version(s) it supports + appVersion, err := im.app.OnChanOpenInit( + ctx, + order, + connectionHops, + portID, + channelID, + counterparty, + metadata.AppVersion, // note we only pass app version here + ) + if err != nil { + return "", err + } + + version := constructVersion(metadata.MiddlewareVersion, appVersion) + + return version, nil +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L36-L83) an example implementation of this callback for the ICS-29 Fee Middleware module. + +#### `OnChanOpenTry` + +```go +func (im IBCMiddleware) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + // try to unmarshal JSON-encoded version string and pass + // the app-specific version to app callback. + // otherwise, pass version directly to app callback. + cpMetadata, err := Unmarshal(counterpartyVersion) + if err != nil { + return app.OnChanOpenTry( + ctx, + order, + connectionHops, + portID, + channelID, + counterparty, + counterpartyVersion, + ) + } + + doCustomLogic() + + // Call the underlying application's OnChanOpenTry callback. + // The try callback must select the final app-specific version string and return it. + appVersion, err := app.OnChanOpenTry( + ctx, + order, + connectionHops, + portID, + channelID, + counterparty, + cpMetadata.AppVersion, // note we only pass counterparty app version here + ) + if err != nil { + return "", err + } + + // negotiate final middleware version + middlewareVersion := negotiateMiddlewareVersion(cpMetadata.MiddlewareVersion) + version := constructVersion(middlewareVersion, appVersion) + + return version, nil +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L88-L125) an example implementation of this callback for the ICS-29 Fee Middleware module. + +#### `OnChanOpenAck` + +```go +func (im IBCMiddleware) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyChannelID string, + counterpartyVersion string, +) error { + // try to unmarshal JSON-encoded version string and pass + // the app-specific version to app callback. + // otherwise, pass version directly to app callback. + cpMetadata, err = UnmarshalJSON(counterpartyVersion) + if err != nil { + return app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) + } + + if !isCompatible(cpMetadata.MiddlewareVersion) { + return error + } + doCustomLogic() + + // call the underlying application's OnChanOpenTry callback + return app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, cpMetadata.AppVersion) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L128-L153)) an example implementation of this callback for the ICS-29 Fee Middleware module. + +#### `OnChanOpenConfirm` + +```go +func OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + doCustomLogic() + + return app.OnChanOpenConfirm(ctx, portID, channelID) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L156-L163) an example implementation of this callback for the ICS-29 Fee Middleware module. + +#### `OnChanCloseInit` + +```go +func OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + doCustomLogic() + + return app.OnChanCloseInit(ctx, portID, channelID) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L166-L188) an example implementation of this callback for the ICS-29 Fee Middleware module. + +#### `OnChanCloseConfirm` + +```go +func OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + doCustomLogic() + + return app.OnChanCloseConfirm(ctx, portID, channelID) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L191-L213) an example implementation of this callback for the ICS-29 Fee Middleware module. + +### Packet callbacks + +The packet callbacks just like the handshake callbacks wrap the application's packet callbacks. The packet callbacks are where the middleware performs most of its custom logic. The middleware may read the packet flow data and perform some additional packet handling, or it may modify the incoming data before it reaches the underlying application. This enables a wide degree of usecases, as a simple base application like token-transfer can be transformed for a variety of usecases by combining it with custom middleware. + +#### `OnRecvPacket` + +```go +func (im IBCMiddleware) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) ibcexported.Acknowledgement { + doCustomLogic(packet) + + ack := app.OnRecvPacket(ctx, packet, relayer) + + doCustomLogic(ack) // middleware may modify outgoing ack + + return ack +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L217-L238) an example implementation of this callback for the ICS-29 Fee Middleware module. + +#### `OnAcknowledgementPacket` + +```go +func (im IBCMiddleware) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + doCustomLogic(packet, ack) + + return app.OnAcknowledgementPacket(ctx, packet, ack, relayer) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L242-L293) an example implementation of this callback for the ICS-29 Fee Middleware module. + +#### `OnTimeoutPacket` + +```go +func (im IBCMiddleware) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + doCustomLogic(packet) + + return app.OnTimeoutPacket(ctx, packet, relayer) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L297-L335) an example implementation of this callback for the ICS-29 Fee Middleware module. + +## ICS-04 wrappers + +Middleware must also wrap ICS-04 so that any communication from the application to the `channelKeeper` goes through the middleware first. Similar to the packet callbacks, the middleware may modify outgoing acknowledgements and packets in any way it wishes. + +To ensure optimal generalisability, the `ICS4Wrapper` abstraction serves to abstract away whether a middleware is the topmost middleware (and thus directly calling into the ICS-04 `channelKeeper`) or itself being wrapped by another middleware. + +Remember that middleware can be stateful or stateless. When defining the stateful middleware's keeper, the `ics4Wrapper` field is included. Then the appropriate keeper can be passed when instantiating the middleware's keeper in `app.go` + +```go +type Keeper struct { + storeKey storetypes.StoreKey + cdc codec.BinaryCodec + + ics4Wrapper porttypes.ICS4Wrapper + channelKeeper types.ChannelKeeper + portKeeper types.PortKeeper + ... +} +``` + +For stateless middleware, the `ics4Wrapper` can be passed on directly without having to instantiate a keeper struct for the middleware. + +[The interface](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/05-port/types/module.go#L110-L133) looks as follows: + +```go +// This is implemented by ICS4 and all middleware that are wrapping base application. +// The base application will call `sendPacket` or `writeAcknowledgement` of the middleware directly above them +// which will call the next middleware until it reaches the core IBC handler. +type ICS4Wrapper interface { + SendPacket( + ctx sdk.Context, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + data []byte, + ) (sequence uint64, err error) + + WriteAcknowledgement( + ctx sdk.Context, + packet exported.PacketI, + ack exported.Acknowledgement, + ) error + + GetAppVersion( + ctx sdk.Context, + portID, + channelID string, + ) (string, bool) +} +``` + +:warning: In the following paragraphs, the methods are presented in pseudo code which has been kept general, not stating whether the middleware is stateful or stateless. Remember that when the middleware is stateful, `ics4Wrapper` can be accessed through the keeper. + +Check out the references provided for an actual implementation to clarify, where the `ics4Wrapper` methods in `ibc_middleware.go` simply call the equivalent keeper methods where the actual logic resides. + +### `SendPacket` + +```go +func SendPacket( + ctx sdk.Context, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + appData []byte, +) (uint64, error) { + // middleware may modify data + data = doCustomLogic(appData) + + return ics4Wrapper.SendPacket( + ctx, + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + data, + ) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/keeper/relay.go#L17-L27) an example implementation of this function for the ICS-29 Fee Middleware module. + +### `WriteAcknowledgement` + +```go +// only called for async acks +func WriteAcknowledgement( + ctx sdk.Context, + packet exported.PacketI, + ack exported.Acknowledgement, +) error { + // middleware may modify acknowledgement + ack_bytes = doCustomLogic(ack) + + return ics4Wrapper.WriteAcknowledgement(packet, ack_bytes) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/keeper/relay.go#L31-L55) an example implementation of this function for the ICS-29 Fee Middleware module. + +### `GetAppVersion` + +```go +// middleware must return the underlying application version +func GetAppVersion( + ctx sdk.Context, + portID, + channelID string, +) (string, bool) { + version, found := ics4Wrapper.GetAppVersion(ctx, portID, channelID) + if !found { + return "", false + } + + if !MiddlewareEnabled { + return version, true + } + + // unwrap channel version + metadata, err := Unmarshal(version) + if err != nil { + panic(fmt.Errof("unable to unmarshal version: %w", err)) + } + + return metadata.AppVersion, true +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/keeper/relay.go#L58-L74) an example implementation of this function for the ICS-29 Fee Middleware module. diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/04-middleware/02-developIBCv2.md b/docs/versioned_docs/version-v10.1.x/01-ibc/04-middleware/02-developIBCv2.md new file mode 100644 index 0000000..94e2e44 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/04-middleware/02-developIBCv2.md @@ -0,0 +1,260 @@ +--- +title: Create and integrate IBC v2 middleware +sidebar_label: Create and integrate IBC v2 middleware +sidebar_position: 2 +slug: /ibc/middleware/developIBCv2 +--- + +# Quick Navigation + +1. [Create a custom IBC v2 middleware](#create-a-custom-ibc-v2-middleware) +2. [Implement `IBCModule` interface](#implement-ibcmodule-interface) +3. [WriteAckWrapper](#writeackwrapper) +4. [Integrate IBC v2 Middleware](#integrate-ibc-v2-middleware) +5. [Security Model](#security-model) +6. [Design Principles](#design-principles) + +## Create a custom IBC v2 middleware + +IBC middleware will wrap over an underlying IBC application (a base application or downstream middleware) and sits between core IBC and the base application. + +:::warning +middleware developers must use the same serialization and deserialization method as in ibc-go's codec: transfertypes.ModuleCdc.[Must]MarshalJSON +::: + +For middleware builders this means: + +```go +import transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +transfertypes.ModuleCdc.[Must]MarshalJSON +func MarshalAsIBCDoes(ack channeltypes.Acknowledgement) ([]byte, error) { + return transfertypes.ModuleCdc.MarshalJSON(&ack) +} +``` + +The interfaces a middleware must implement are found in [core/api](https://github.com/cosmos/ibc-go/blob/main/modules/core/api/module.go#L11). Note that this interface has changed from IBC classic. + +An `IBCMiddleware` struct implementing the `Middleware` interface, can be defined with its constructor as follows: + +```go +// @ x/module_name/ibc_middleware.go + +// IBCMiddleware implements the IBCv2 middleware interface +type IBCMiddleware struct { + app api.IBCModule // underlying app or middleware + writeAckWrapper api. WriteAcknowledgementWrapper // writes acknowledgement for an async acknowledgement + PacketDataUnmarshaler api.PacketDataUnmarshaler // optional interface + keeper types.Keeper // required for stateful middleware + // Keeper may include middleware specific keeper and the ChannelKeeperV2 + + // additional middleware specific fields +} + +// NewIBCMiddleware creates a new IBCMiddleware given the keeper and underlying application +func NewIBCMiddleware(app api.IBCModule, +writeAckWrapper api.WriteAcknowledgementWrapper, +k types.Keeper +) IBCMiddleware { + return IBCMiddleware{ + app: app, + writeAckWrapper: writeAckWrapper, + keeper: k, + } +} +``` + +:::note +The ICS4Wrapper has been removed in IBC v2 and there are no channel handshake callbacks, a writeAckWrapper has been added to the interface +::: + +## Implement `IBCModule` interface + +`IBCMiddleware` is a struct that implements the [`IBCModule` interface (`api.IBCModule`)](https://github.com/cosmos/ibc-go/blob/main/modules/core/api/module.go#L11-L53). It is recommended to separate these callbacks into a separate file `ibc_middleware.go`. + +> Note how this is analogous to implementing the same interfaces for IBC applications that act as base applications. + +The middleware must have access to the underlying application, and be called before it during all ICS-26 callbacks. It may execute custom logic during these callbacks, and then call the underlying application's callback. + +> Middleware **may** choose not to call the underlying application's callback at all. Though these should generally be limited to error cases. + +The `IBCModule` interface consists of the packet callbacks where cutom logic is performed. + +### Packet callbacks + +The packet callbacks are where the middleware performs most of its custom logic. The middleware may read the packet flow data and perform some additional packet handling, or it may modify the incoming data before it reaches the underlying application. This enables a wide degree of usecases, as a simple base application like token-transfer can be transformed for a variety of usecases by combining it with custom middleware, for example acting as a filter for which tokens can be sent and recieved. + +#### `OnRecvPacket` + +```go +func (im IBCMiddleware) OnRecvPacket( + ctx sdk.Context, + sourceClient string, + destinationClient string, + sequence uint64, + payload channeltypesv2.Payload, + relayer sdk.AccAddress, +) channeltypesv2.RecvPacketResult { + // Middleware may choose to do custom preprocessing logic before calling the underlying app OnRecvPacket + // Middleware may choose to error early and return a RecvPacketResult Failure + // Middleware may choose to modify the payload before passing on to OnRecvPacket though this + // should only be done to support very advanced custom behavior + // Middleware MUST NOT modify client identifiers and sequence + doCustomPreProcessLogic() + + // call underlying app OnRecvPacket + recvResult := im.app.OnRecvPacket(ctx, sourceClient, destinationClient, sequence, payload, relayer) + if recvResult.Status == PACKET_STATUS_FAILURE { + return recvResult + } + + doCustomPostProcessLogic(recvResult) // middleware may modify recvResult + + return recvResult +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/v2/ibc_middleware.go#L161-L230) an example implementation of this callback for the Callbacks Middleware module. + +#### `OnAcknowledgementPacket` + +```go +func (im IBCMiddleware) OnAcknowledgementPacket( + ctx sdk.Context, + sourceClient string, + destinationClient string, + sequence uint64, + acknowledgement []byte, + payload channeltypesv2.Payload, + relayer sdk.AccAddress, +) error { + // preprocessing logic may modify the acknowledgement before passing to + // the underlying app though this should only be done in advanced cases + // Middleware may return error early + // it MUST NOT change the identifiers of the clients or the sequence + doCustomPreProcessLogic(payload, acknowledgement) + + // call underlying app OnAcknowledgementPacket + err = im.app.OnAcknowledgementPacket( + sourceClient, destinationClient, sequence, + acknowledgement, payload, relayer + ) + if err != nil { + return err + } + + // may perform some post acknowledgement logic and return error here + return doCustomPostProcessLogic() +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/v2/ibc_middleware.go#L236-L302) an example implementation of this callback for the Callbacks Middleware module. + +#### `OnTimeoutPacket` + +```go +func (im IBCMiddleware) OnTimeoutPacket( + ctx sdk.Context, + sourceClient string, + destinationClient string, + sequence uint64, + payload channeltypesv2.Payload, + relayer sdk.AccAddress, +) error { + // Middleware may choose to do custom preprocessing logic before calling the underlying app OnTimeoutPacket + // Middleware may return error early + doCustomPreProcessLogic(payload) + + // call underlying app OnTimeoutPacket + err = im.app.OnTimeoutPacket( + sourceClient, destinationClient, sequence, + payload, relayer + ) + if err != nil { + return err + } + + // may perform some post timeout logic and return error here + return doCustomPostProcessLogic() +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/v2/ibc_middleware.go#L309-L367) an example implementation of this callback for the Callbacks Middleware module. + +### WriteAckWrapper + +Middleware must also wrap the `WriteAcknowledgement` interface so that any acknowledgement written by the application passes through the middleware first. This allows middleware to modify or delay writing an acknowledgment before committed to the IBC store. + +```go +// WithWriteAckWrapper sets the WriteAcknowledgementWrapper for the middleware. +func (im *IBCMiddleware) WithWriteAckWrapper(writeAckWrapper api.WriteAcknowledgementWrapper) { + im.writeAckWrapper = writeAckWrapper +} + +// GetWriteAckWrapper returns the WriteAckWrapper +func (im *IBCMiddleware) GetWriteAckWrapper() api.WriteAcknowledgementWrapper { + return im.writeAckWrapper +} +``` + +### `WriteAcknowledgement` + +This is where the middleware acknowledgement handling is finalised. An example is shown in the [callbacks middleware](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/v2/ibc_middleware.go#L369-L454) + +```go +// WriteAcknowledgement facilitates acknowledgment being written asynchronously +// The call stack flows from the IBC application to the IBC core handler +// Thus this function is called by the IBC app or a lower-level middleware +func (im IBCMiddleware) WriteAcknowledgement( + ctx sdk.Context, + clientID string, + sequence uint64, + ack channeltypesv2.Acknowledgement, +) error { + doCustomPreProcessLogic() // may modify acknowledgement + + return im.writeAckWrapper.WriteAcknowledgement( + ctx, clientId, sequence, ack, + ) +} +``` + +## Integrate IBC v2 Middleware + +Middleware should be registered within the module manager in `app.go`. + +The order of middleware **matters**, function calls from IBC to the application travel from top-level middleware to the bottom middleware and then to the application. Function calls from the application to IBC goes through the bottom middleware in order to the top middleware and then to core IBC handlers. Thus the same set of middleware put in different orders may produce different effects. + +### Example Integration + +The example integration is detailed for an IBC v2 stack using transfer and the callbacks middleware. + +```go +// Middleware Stacks +// initialising callbacks middleware + maxCallbackGas := uint64(10_000_000) + wasmStackIBCHandler := wasm.NewIBCHandler(app.WasmKeeper, app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper) + +// Create the transferv2 stack with transfer and callbacks middleware + var ibcv2TransferStack ibcapi.IBCModule + ibcv2TransferStack = transferv2.NewIBCModule(app.TransferKeeper) + ibcv2TransferStack = ibccallbacksv2.NewIBCMiddleware(transferv2.NewIBCModule(app.TransferKeeper), app.IBCKeeper.ChannelKeeperV2, wasmStackIBCHandler, app.IBCKeeper.ChannelKeeperV2, maxCallbackGas) + +// Create static IBC v2 router, add app routes, then set and seal it + ibcRouterV2 := ibcapi.NewRouter() + ibcRouterV2.AddRoute(ibctransfertypes.PortID, ibcv2TransferStack) + app.IBCKeeper.SetRouterV2(ibcRouterV2) +``` + +## Security Model + +IBC Middleware completely wraps all communication between IBC core and the application that it is wired with. Thus, the IBC Middleware has complete control to modify any packets and acknowledgements the underlying application receives or sends. Thus, if a chain chooses to wrap an application with a given middleware, that middleware is **completely trusted** and part of the application's security model. **Do not use middlewares that are untrusted.** + +## Design Principles + +The middleware follows a decorator pattern that wraps an underlying application's connection to the IBC core handlers. Thus, when implementing a middleware for a specific purpose, it is recommended to be as **unintrusive** as possible in the middleware design while still accomplishing the intended behavior. + +The least intrusive middleware is stateless. They simply read the ICS26 callback arguments before calling the underlying app's callback and error if the arguments are not acceptable (e.g. whitelisting packets). Stateful middleware that are used solely for erroring are also very simple to build, an example of this would be a rate-limiting middleware that prevents transfer outflows from getting too high within a certain time frame. + +Middleware that directly interfere with the payload or acknowledgement before passing control to the underlying app are way more intrusive to the underyling app processing. This makes such middleware more error-prone when implementing as incorrect handling can cause the underlying app to break or worse execute unexpected behavior. Moreover, such middleware typically needs to be built for a specific underlying app rather than being generic. An example of this is the packet-forwarding middleware which modifies the payload and is specifically built for transfer. + +Middleware that modifies the payload or acknowledgement such that it is no longer readable by the underlying application is the most complicated middleware. Since it is not readable by the underlying apps, if these middleware write additional state into payloads and acknowledgements that get committed to IBC core provable state, there MUST be an equivalent counterparty middleware that is able to parse and intepret this additional state while also converting the payload and acknowledgment back to a readable form for the underlying application on its side. Thus, such middleware requires deployment on both sides of an IBC connection or the packet processing will break. This is the hardest type of middleware to implement, integrate and deploy. Thus, it is not recommended unless absolutely necessary to fulfill the given use case. diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/04-middleware/03-integration.md b/docs/versioned_docs/version-v10.1.x/01-ibc/04-middleware/03-integration.md new file mode 100644 index 0000000..5ceff37 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/04-middleware/03-integration.md @@ -0,0 +1,72 @@ +--- +title: Integrating IBC middleware into a chain +sidebar_label: Integrating IBC middleware into a chain +sidebar_position: 4 +slug: /ibc/middleware/integration +--- + + +# Integrating IBC middleware into a chain + +Learn how to integrate IBC middleware(s) with a base application to your chain. The following document only applies for Cosmos SDK chains. + +If the middleware is maintaining its own state and/or processing SDK messages, then it should create and register its SDK module with the module manager in `app.go`. + +All middleware must be connected to the IBC router and wrap over an underlying base IBC application. An IBC application may be wrapped by many layers of middleware, only the top layer middleware should be hooked to the IBC router, with all underlying middlewares and application getting wrapped by it. + +The order of middleware **matters**, function calls from IBC to the application travel from top-level middleware to the bottom middleware and then to the application. Function calls from the application to IBC goes through the bottom middleware in order to the top middleware and then to core IBC handlers. Thus the same set of middleware put in different orders may produce different effects. + +## Example integration + +```go +// app.go pseudocode + + + +// middleware 1 and middleware 3 are stateful middleware, +// perhaps implementing separate sdk.Msg and Handlers +mw1Keeper := mw1.NewKeeper(storeKey1, ..., ics4Wrapper: channelKeeper, ...) // in stack 1 & 3 +// middleware 2 is stateless +mw3Keeper1 := mw3.NewKeeper(storeKey3,..., ics4Wrapper: mw1Keeper, ...) // in stack 1 +mw3Keeper2 := mw3.NewKeeper(storeKey3,..., ics4Wrapper: channelKeeper, ...) // in stack 2 + +// Only create App Module **once** and register in app module +// if the module maintains independent state and/or processes sdk.Msgs +app.moduleManager = module.NewManager( + ... + mw1.NewAppModule(mw1Keeper), + mw3.NewAppModule(mw3Keeper1), + mw3.NewAppModule(mw3Keeper2), + transfer.NewAppModule(transferKeeper), + custom.NewAppModule(customKeeper) +) + +// NOTE: IBC Modules may be initialized any number of times provided they use a separate +// Keeper and underlying port. + +customKeeper1 := custom.NewKeeper(..., KeeperCustom1, ...) +customKeeper2 := custom.NewKeeper(..., KeeperCustom2, ...) + +// initialize base IBC applications +// if you want to create two different stacks with the same base application, +// they must be given different Keepers and assigned different ports. +transferIBCModule := transfer.NewIBCModule(transferKeeper) +customIBCModule1 := custom.NewIBCModule(customKeeper1, "portCustom1") +customIBCModule2 := custom.NewIBCModule(customKeeper2, "portCustom2") + +// create IBC stacks by combining middleware with base application +// NOTE: since middleware2 is stateless it does not require a Keeper +// stack 1 contains mw1 -> mw3 -> transfer +stack1 := mw1.NewIBCMiddleware(mw3.NewIBCMiddleware(transferIBCModule, mw3Keeper1), mw1Keeper) +// stack 2 contains mw3 -> mw2 -> custom1 +stack2 := mw3.NewIBCMiddleware(mw2.NewIBCMiddleware(customIBCModule1), mw3Keeper2) +// stack 3 contains mw2 -> mw1 -> custom2 +stack3 := mw2.NewIBCMiddleware(mw1.NewIBCMiddleware(customIBCModule2, mw1Keeper)) + +// associate each stack with the moduleName provided by the underlying Keeper +ibcRouter := porttypes.NewRouter() +ibcRouter.AddRoute("transfer", stack1) +ibcRouter.AddRoute("custom1", stack2) +ibcRouter.AddRoute("custom2", stack3) +app.IBCKeeper.SetRouter(ibcRouter) +``` diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/04-middleware/_category_.json b/docs/versioned_docs/version-v10.1.x/01-ibc/04-middleware/_category_.json new file mode 100644 index 0000000..ec27d4e --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/04-middleware/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Middleware", + "position": 4, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/04-middleware/images/middleware-stack.png b/docs/versioned_docs/version-v10.1.x/01-ibc/04-middleware/images/middleware-stack.png new file mode 100644 index 0000000..ed5852f Binary files /dev/null and b/docs/versioned_docs/version-v10.1.x/01-ibc/04-middleware/images/middleware-stack.png differ diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/05-upgrades/00-intro.md b/docs/versioned_docs/version-v10.1.x/01-ibc/05-upgrades/00-intro.md new file mode 100644 index 0000000..d7bb473 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/05-upgrades/00-intro.md @@ -0,0 +1,15 @@ +--- +title: Upgrading IBC Chains Overview +sidebar_label: Overview +sidebar_position: 0 +slug: /ibc/upgrades/intro +--- + +# Upgrading IBC Chains Overview + +This directory contains information on how to upgrade an IBC chain without breaking counterparty clients and connections. + +IBC-connected chains must be able to upgrade without breaking connections to other chains. Otherwise there would be a massive disincentive towards upgrading and disrupting high-value IBC connections, thus preventing chains in the IBC ecosystem from evolving and improving. Many chain upgrades may be irrelevant to IBC, however some upgrades could potentially break counterparty clients if not handled correctly. Thus, any IBC chain that wishes to perform an IBC-client-breaking upgrade must perform an IBC upgrade in order to allow counterparty clients to securely upgrade to the new light client. + +1. The [quick-guide](./01-quick-guide.md) describes how IBC-connected chains can perform client-breaking upgrades and how relayers can securely upgrade counterparty clients using the SDK. +2. The [developer-guide](./02-developer-guide.md) is a guide for developers intending to develop IBC client implementations with upgrade functionality. diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/05-upgrades/01-quick-guide.md b/docs/versioned_docs/version-v10.1.x/01-ibc/05-upgrades/01-quick-guide.md new file mode 100644 index 0000000..23448f7 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/05-upgrades/01-quick-guide.md @@ -0,0 +1,60 @@ +--- +title: How to Upgrade IBC Chains and their Clients +sidebar_label: How to Upgrade IBC Chains and their Clients +sidebar_position: 1 +slug: /ibc/upgrades/quick-guide +--- + + +# How to Upgrade IBC Chains and their Clients + +:::note Synopsis +Learn how to upgrade your chain and counterparty clients. +::: + +The information in this doc for upgrading chains is relevant to SDK chains. However, the guide for counterparty clients is relevant to any Tendermint client that enables upgrades. + +## IBC Client Breaking Upgrades + +IBC-connected chains must perform an IBC upgrade if their upgrade will break counterparty IBC clients. The current IBC protocol supports upgrading tendermint chains for a specific subset of IBC-client-breaking upgrades. Here is the exhaustive list of IBC client-breaking upgrades and whether the IBC protocol currently supports such upgrades. + +IBC currently does **NOT** support unplanned upgrades. All of the following upgrades must be planned and committed to in advance by the upgrading chain, in order for counterparty clients to maintain their connections securely. + +Note: Since upgrades are only implemented for Tendermint clients, this doc only discusses upgrades on Tendermint chains that would break counterparty IBC Tendermint Clients. + +1. Changing the Chain-ID: **Supported** +2. Changing the UnbondingPeriod: **Partially Supported**, chains may increase the unbonding period with no issues. However, decreasing the unbonding period may irreversibly break some counterparty clients. Thus, it is **not recommended** that chains reduce the unbonding period. +3. Changing the height (resetting to 0): **Supported**, so long as chains remember to increment the revision number in their chain-id. +4. Changing the ProofSpecs: **Supported**, this should be changed if the proof structure needed to verify IBC proofs is changed across the upgrade. Ex: Switching from an IAVL store, to a SimpleTree Store +5. Changing the UpgradePath: **Supported**, this might involve changing the key under which upgraded clients and consensus states are stored in the upgrade store, or even migrating the upgrade store itself. +6. Migrating the IBC store: **Unsupported**, as the IBC store location is negotiated by the connection. +7. Upgrading to a backwards compatible version of IBC: Supported +8. Upgrading to a non-backwards compatible version of IBC: **Unsupported**, as IBC version is negotiated on connection handshake. +9. Changing the Tendermint LightClient algorithm: **Partially Supported**. Changes to the light client algorithm that do not change the ClientState or ConsensusState struct may be supported, provided that the counterparty is also upgraded to support the new light client algorithm. Changes that require updating the ClientState and ConsensusState structs themselves are theoretically possible by providing a path to translate an older ClientState struct into the new ClientState struct; however this is not currently implemented. + +## Step-by-Step Upgrade Process for SDK chains + +If the IBC-connected chain is conducting an upgrade that will break counterparty clients, it must ensure that the upgrade is first supported by IBC using the list above and then execute the upgrade process described below in order to prevent counterparty clients from breaking. + +1. Create a governance proposal with the [`MsgIBCSoftwareUpgrade`](https://buf.build/cosmos/ibc/docs/main:ibc.core.client.v1#ibc.core.client.v1.MsgIBCSoftwareUpgrade) message which contains an `UpgradePlan` and a new IBC `ClientState` in the `UpgradedClientState` field. Note that the `UpgradePlan` must specify an upgrade height **only** (no upgrade time), and the `ClientState` should only include the fields common to all valid clients (chain-specified parameters) and zero out any client-customizable fields (such as `TrustingPeriod`). +2. Vote on and pass the governance proposal. + +Upon passing the governance proposal, the upgrade module will commit the `UpgradedClient` under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedClient`. On the block right before the upgrade height, the upgrade module will also commit an initial consensus state for the next chain under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedConsState`. + +Once the chain reaches the upgrade height and halts, a relayer can upgrade the counterparty clients to the last block of the old chain. They can then submit the proofs of the `UpgradedClient` and `UpgradedConsensusState` against this last block and upgrade the counterparty client. + +## Step-by-Step Upgrade Process for Relayers Upgrading Counterparty Clients + +Once the upgrading chain has committed to upgrading, relayers must wait till the chain halts at the upgrade height before upgrading counterparty clients. This is because chains may reschedule or cancel upgrade plans before they occur. Thus, relayers must wait till the chain reaches the upgrade height and halts before they can be sure the upgrade will take place. + +Thus, the upgrade process for relayers trying to upgrade the counterparty clients is as follows: + +1. Wait for the upgrading chain to reach the upgrade height and halt +2. Query a full node for the proofs of `UpgradedClient` and `UpgradedConsensusState` at the last height of the old chain. +3. Update the counterparty client to the last height of the old chain using the `UpdateClient` msg. +4. Submit an `UpgradeClient` msg to the counterparty chain with the `UpgradedClient`, `UpgradedConsensusState` and their respective proofs. +5. Submit an `UpdateClient` msg to the counterparty chain with a header from the new upgraded chain. + +The Tendermint client on the counterparty chain will verify that the upgrading chain did indeed commit to the upgraded client and upgraded consensus state at the upgrade height (since the upgrade height is included in the key). If the proofs are verified against the upgrade height, then the client will upgrade to the new client while retaining all of its client-customized fields. Thus, it will retain its old TrustingPeriod, TrustLevel, MaxClockDrift, etc; while adopting the new chain-specified fields such as UnbondingPeriod, ChainId, UpgradePath, etc. Note, this can lead to an invalid client since the old client-chosen fields may no longer be valid given the new chain-chosen fields. Upgrading chains should try to avoid these situations by not altering parameters that can break old clients. For an example, see the UnbondingPeriod example in the supported upgrades section. + +The upgraded consensus state will serve purely as a basis of trust for future `UpdateClientMsgs` and will not contain a consensus root to perform proof verification against. Thus, relayers must submit an `UpdateClientMsg` with a header from the new chain so that the connection can be used for proof verification again. diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/05-upgrades/02-developer-guide.md b/docs/versioned_docs/version-v10.1.x/01-ibc/05-upgrades/02-developer-guide.md new file mode 100644 index 0000000..c55b698 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/05-upgrades/02-developer-guide.md @@ -0,0 +1,14 @@ +--- +title: IBC Client Developer Guide to Upgrades +sidebar_label: IBC Client Developer Guide to Upgrades +sidebar_position: 2 +slug: /ibc/upgrades/developer-guide +--- + +# IBC Client Developer Guide to Upgrades + +:::note Synopsis +Learn how to implement upgrade functionality for your custom IBC client. +::: + +Please see the section [Handling upgrades](../../03-light-clients/01-developer-guide/06-upgrades.md) from the light client developer guide for more information. diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/05-upgrades/03-genesis-restart.md b/docs/versioned_docs/version-v10.1.x/01-ibc/05-upgrades/03-genesis-restart.md new file mode 100644 index 0000000..8f1d612 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/05-upgrades/03-genesis-restart.md @@ -0,0 +1,52 @@ +--- +title: Genesis Restart Upgrades +sidebar_label: Genesis Restart Upgrades +sidebar_position: 3 +slug: /ibc/upgrades/genesis-restart +--- + + +# Genesis Restart Upgrades + +:::note Synopsis +Learn how to upgrade your chain and counterparty clients using genesis restarts. +::: + +**NOTE**: Regular genesis restarts are currently unsupported by relayers! + +## IBC Client Breaking Upgrades + +IBC client breaking upgrades are possible using genesis restarts. +It is highly recommended to use the in-place migrations instead of a genesis restart. +Genesis restarts should be used sparingly and as backup plans. + +Genesis restarts still require the usage of an IBC upgrade proposal in order to correctly upgrade counterparty clients. + +### Step-by-Step Upgrade Process for SDK Chains + +If the IBC-connected chain is conducting an upgrade that will break counterparty clients, it must ensure that the upgrade is first supported by IBC using the [IBC Client Breaking Upgrade List](./01-quick-guide.md#ibc-client-breaking-upgrades) and then execute the upgrade process described below in order to prevent counterparty clients from breaking. + +1. Create a governance proposal with the [`MsgIBCSoftwareUpgrade`](https://buf.build/cosmos/ibc/docs/main:ibc.core.client.v1#ibc.core.client.v1.MsgIBCSoftwareUpgrade) which contains an `UpgradePlan` and a new IBC `ClientState` in the `UpgradedClientState` field. Note that the `UpgradePlan` must specify an upgrade height **only** (no upgrade time), and the `ClientState` should only include the fields common to all valid clients and zero out any client-customizable fields (such as `TrustingPeriod`). +2. Vote on and pass the governance proposal. +3. Halt the node after successful upgrade. +4. Export the genesis file. +5. Swap to the new binary. +6. Run migrations on the genesis file. +7. Remove the upgrade plan set by the governance proposal from the genesis file. This may be done by migrations. +8. Change desired chain-specific fields (chain id, unbonding period, etc). This may be done by migrations. +8. Reset the node's data. +9. Start the chain. + +Upon passing the governance proposal, the upgrade module will commit the `UpgradedClient` under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedClient`. On the block right before the upgrade height, the upgrade module will also commit an initial consensus state for the next chain under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedConsState`. + +Once the chain reaches the upgrade height and halts, a relayer can upgrade the counterparty clients to the last block of the old chain. They can then submit the proofs of the `UpgradedClient` and `UpgradedConsensusState` against this last block and upgrade the counterparty client. + +### Step-by-Step Upgrade Process for Relayers Upgrading Counterparty Clients + +These steps are identical to the regular [IBC client breaking upgrade process](./01-quick-guide.md#step-by-step-upgrade-process-for-relayers-upgrading-counterparty-clients). + +## Non-IBC Client Breaking Upgrades + +While ibc-go supports genesis restarts which do not break IBC clients, relayers do not support this upgrade path. +Here is a tracking issue on [Hermes](https://github.com/informalsystems/ibc-rs/issues/1152). +Please do not attempt a regular genesis restarts unless you have a tool to update counterparty clients correctly. diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/05-upgrades/_category_.json b/docs/versioned_docs/version-v10.1.x/01-ibc/05-upgrades/_category_.json new file mode 100644 index 0000000..c2db1ee --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/05-upgrades/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Upgrades", + "position": 5, + "link": { "type": "doc", "id": "intro" } +} diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/07-relayer.md b/docs/versioned_docs/version-v10.1.x/01-ibc/07-relayer.md new file mode 100644 index 0000000..84ce164 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/07-relayer.md @@ -0,0 +1,53 @@ +--- +title: Relayer +sidebar_label: Relayer +sidebar_position: 7 +slug: /ibc/relayer +--- + +# Relayer + +:::note + +## Pre-requisite readings + +- [IBC Overview](01-overview.md) +- [Events](https://docs.cosmos.network/main/learn/advanced/events) + +::: + +## Events + +Events are emitted for every transaction processed by the base application to indicate the execution +of some logic clients may want to be aware of. This is extremely useful when relaying IBC packets. +Any message that uses IBC will emit events for the corresponding TAO logic executed as defined in +the [IBC events document](/events/events). + +In the SDK, it can be assumed that for every message there is an event emitted with the type `message`, +attribute key `action`, and an attribute value representing the type of message sent +(`channel_open_init` would be the attribute value for `MsgChannelOpenInit`). If a relayer queries +for transaction events, it can split message events using this event Type/Attribute Key pair. + +The Event Type `message` with the Attribute Key `module` may be emitted multiple times for a single +message due to application callbacks. It can be assumed that any TAO logic executed will result in +a module event emission with the attribute value `ibc_` (02-client emits `ibc_client`). + +### Subscribing with Tendermint + +Calling the Tendermint RPC method `Subscribe` via Tendermint's Websocket will return events using +Tendermint's internal representation of them. Instead of receiving back a list of events as they +were emitted, Tendermint will return the type `map[string][]string` which maps a string in the +form `.` to `attribute_value`. This causes extraction of the event +ordering to be non-trivial, but still possible. + +A relayer should use the `message.action` key to extract the number of messages in the transaction +and the type of IBC transactions sent. For every IBC transaction within the string array for +`message.action`, the necessary information should be extracted from the other event fields. If +`send_packet` appears at index 2 in the value for `message.action`, a relayer will need to use the +value at index 2 of the key `send_packet.packet_sequence`. This process should be repeated for each +piece of information needed to relay a packet. + +## Example Implementations + +- [Golang Relayer](https://github.com/cosmos/relayer) +- [Hermes](https://github.com/informalsystems/hermes) diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/08-best-practices.md b/docs/versioned_docs/version-v10.1.x/01-ibc/08-best-practices.md new file mode 100644 index 0000000..77cba9b --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/08-best-practices.md @@ -0,0 +1,25 @@ +--- +title: Best Practices +sidebar_label: Best Practices +sidebar_position: 8 +slug: /ibc/best-practices +--- + +# Best practices + +## Identifying legitimate channels + +Identifying which channel to use can be difficult as it requires verifying information about the chains you want to connect to. +Channels are based on a light client. A chain can be uniquely identified by its chain ID, validator set pairing. It is unsafe to rely only on the chain ID. +Any user can create a client with any chain ID, but only the chain with correct validator set and chain ID can produce headers which would update that client. + +Which channel to use is based on social consensus. The desired channel should have the following properties: + +- based on a valid client (can only be updated by the chain it connects to) +- has sizable activity +- the underlying client is active + +To verify if a client is valid. You will need to obtain a header from the chain you want to connect to. This can be done by running a full node for that chain or relying on a trusted rpc address. +Then you should query the light client you want to verify and obtain its latest consensus state. All consensus state fields must match the header queried for at same height as the consensus state (root, timestamp, next validator set hash). + +Explorers and wallets are highly encouraged to follow this practice. It is unsafe to algorithmically add new channels without following this process. diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/09-permissioning.md b/docs/versioned_docs/version-v10.1.x/01-ibc/09-permissioning.md new file mode 100644 index 0000000..edfbdfc --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/09-permissioning.md @@ -0,0 +1,32 @@ +--- +title: Permissioning +sidebar_label: Permissioning +sidebar_position: 9 +slug: /ibc/permissioning +--- + +# Permissioning + +IBC is designed at its base level to be a permissionless protocol. This does not mean that chains cannot add in permissioning on top of IBC. In ibc-go this can be accomplished by implementing and wiring an ante-decorator that checks if the IBC message is signed by a permissioned authority. If the signer address check passes, the tx can go through; otherwise it is rejected from the mempool. + +The antehandler runs before message-processing so it acts as a customizable filter that can reject messages before they get included in the block. The Cosmos SDK allows developers to write ante-decorators that can be stacked with others to add multiple independent customizable filters that run in sequence. Thus, chain developers that want to permission IBC messages are advised to implement their own custom permissioned IBC ante-decorator to add to the standard ante-decorator stack. + +## Best practices + +`MsgCreateClient`: permissioning the client creation is the most important for permissioned IBC. This will prevent malicious relayers from creating clients to fake chains. If a chain wants to control which chains are connected to it directly over IBC, the best way to do this is by controlling which clients get created. The permissioned authority can create clients only of counterparties that the chain approves of. The permissioned authority can be the governance account, however `MsgCreateClient` contains a consensus state that can be expired by the time governance passes the proposal to execute the message. Thus, if the voting period is longer than the unbonding period of the counterparty, it is advised to use a permissioned authority that can immediately execute the transaction (e.g. a trusted multisig). + +`MsgConnectionOpenInit`: permissioning this message will give the chain control over the connections that are opened and also will control which connection identifier is associated with which counterparty. + +`MsgConnectionOpenTry`: permissioning this message through a permissioned address check is ill-advised because it will prevent relayers from easily completing the handshake that was initialized on the counterparty. However, if the chain does want strict control of exactly which connections are opened, it can permission this message. Be aware, if two chains with strict permissions try to open a connection it may take much longer than expected. + +`MsgChannelOpenInit`: permissioning this message will give the chain control over the channels that are opened and also will control which channel identifier is associated with which counterparty. + +`MsgChannelOpenTry`: permissioning this message through a permissioned address check is ill-advised because it will prevent relayers from easily completing the handshake that was initialized on the counterparty. However, if the chain does want strict control of exactly which channels are opened, it can permission this message. Be aware, if two chains with strict permissions try to open a channel it may take much longer than expected. + +It is not advised to permission any other message from ibc-go. Permissionless relayers should still be allowed to complete handshakes that were authorized by permissioned parties, and to relay user packets on channels that were also authorized by permissioned parties. This provides the maximum liveness provided by a permissionless relayer network with the safety guarantees provided by permissioned client, connection, and channel creation. + +## Genesis setup + +Chains that are starting up from genesis have the option of initializing authorized clients, connections and channels from genesis. This allows chains to automatically connect to desired chains with a desired identifier. + +Note: The chain must be launched soon after the genesis file is created so that the client creation does not occur with an expired consensus state. The connections and channels must also simply have their `INIT` messages executed so that relayers can complete the rest of the handshake. diff --git a/docs/versioned_docs/version-v10.1.x/01-ibc/_category_.json b/docs/versioned_docs/version-v10.1.x/01-ibc/_category_.json new file mode 100644 index 0000000..afc6d18 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/01-ibc/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Using IBC-Go", + "position": 1, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/01-overview.md b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/01-overview.md new file mode 100644 index 0000000..b731368 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/01-overview.md @@ -0,0 +1,142 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /apps/transfer/ics20-v1/overview +--- + +# Overview + +:::note Synopsis +Learn about what the token Transfer module is +::: + +## What is the Transfer module? + +Transfer is the Cosmos SDK implementation of the [ICS-20](https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer) protocol, which enables cross-chain fungible token transfers. + +## Concepts + +### Acknowledgements + +ICS20 uses the recommended acknowledgement format as specified by [ICS 04](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope). + +A successful receive of a transfer packet will result in a Result Acknowledgement being written +with the value `[]byte{byte(1)}` in the `Response` field. + +An unsuccessful receive of a transfer packet will result in an Error Acknowledgement being written +with the error message in the `Response` field. + +### Denomination trace + +The denomination trace corresponds to the information that allows a token to be traced back to its +origin chain. It contains a sequence of port and channel identifiers ordered from the most recent to +the oldest in the timeline of transfers. + +:::tip +When using transfer with IBC v2 connecting to e.g. Ethereum, the source channel identifier will be the source client identifier instead. +::: + +This information is included on the token's base denomination field in the form of a hash to prevent an +unbounded denomination length. For example, the token `transfer/channelToA/uatom` will be displayed +as `ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2`. The human readable denomination +is stored using `x/bank` module's [denom metadata](https://docs.cosmos.network/main/build/modules/bank#denom-metadata) +feature. You may display the human readable denominations by querying balances with the `--resolve-denom` flag, as in: + +```shell +simd query bank balances [address] --resolve-denom +``` + +Each send to any chain other than the one it was previously received from is a movement forwards in +the token's timeline. This causes trace to be added to the token's history and the destination port +and destination channel to be prefixed to the denomination. In these instances the sender chain is +acting as the "source zone". When the token is sent back to the chain it previously received from, the +prefix is removed. This is a backwards movement in the token's timeline and the sender chain is +acting as the "sink zone". + +It is strongly recommended to read the full details of [ADR 001: Coin Source Tracing](/architecture/adr-001-coin-source-tracing) to understand the implications and context of the IBC token representations. + +## UX suggestions for clients + +For clients (wallets, exchanges, applications, block explorers, etc) that want to display the source of the token, it is recommended to use the following alternatives for each of the cases below: + +### Direct connection + +If the denomination trace contains a single identifier prefix pair (as in the example above), then +the easiest way to retrieve the chain and light client identifier is to map the trace information +directly. In summary, this requires querying the channel from the denomination trace identifiers, +and then the counterparty client state using the counterparty port and channel identifiers from the +retrieved channel. + +A general pseudo algorithm would look like the following: + +1. Query the full denomination trace. +2. Query the channel with the `portID/channelID` pair, which corresponds to the first destination of the + token. +3. Query the client state using the identifiers pair. Note that this query will return a `"Not +Found"` response if the current chain is not connected to this channel. +4. Retrieve the client identifier or chain identifier from the client state (eg: on + Tendermint clients) and store it locally. + +Using the gRPC gateway client service the steps above would be, with a given IBC token `ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2` stored on `chainB`: + +1. `GET /ibc/apps/transfer/v1/denom_traces/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2` -> `{"path": "transfer/channelToA", "base_denom": "uatom"}` +2. `GET /ibc/apps/transfer/v1/channels/channelToA/ports/transfer/client_state"` -> `{"client_id": "clientA", "chain-id": "chainA", ...}` +3. `GET /ibc/apps/transfer/v1/channels/channelToA/ports/transfer"` -> `{"channel_id": "channelToA", port_id": "transfer", counterparty: {"channel_id": "channelToB", port_id": "transfer"}, ...}` +4. `GET /ibc/apps/transfer/v1/channels/channelToB/ports/transfer/client_state" -> {"client_id": "clientB", "chain-id": "chainB", ...}` + +Then, the token transfer chain path for the `uatom` denomination would be: `chainA` -> `chainB`. + +### Multiple hops + +The multiple channel hops case applies when the token has passed through multiple chains between the original source and final destination chains. + +The IBC protocol doesn't know the topology of the overall network (i.e connections between chains and identifier names between them). For this reason, in the multiple hops case, a particular chain in the timeline of the individual transfers can't query the chain and client identifiers of the other chains. + +Take for example the following sequence of transfers `A -> B -> C` for an IBC token, with a final prefix path (trace info) of `transfer/channelChainC/transfer/channelChainB`. What the paragraph above means is that even in the case that chain `C` is directly connected to chain `A`, querying the port and channel identifiers that chain `B` uses to connect to chain `A` (eg: `transfer/channelChainA`) can be completely different from the one that chain `C` uses to connect to chain `A` (eg: `transfer/channelToChainA`). + +Thus the proposed solution for clients that the IBC team recommends are the following: + +- **Connect to all chains**: Connecting to all the chains in the timeline would allow clients to + perform the queries outlined in the [direct connection](#direct-connection) section to each + relevant chain. By repeatedly following the port and channel denomination trace transfer timeline, + clients should always be able to find all the relevant identifiers. This comes at the tradeoff + that the client must connect to nodes on each of the chains in order to perform the queries. +- **Relayer as a Service (RaaS)**: A longer term solution is to use/create a relayer service that + could map the denomination trace to the chain path timeline for each token (i.e `origin chain -> +chain #1 -> ... -> chain #(n-1) -> final chain`). These services could provide merkle proofs in + order to allow clients to optionally verify the path timeline correctness for themselves by + running light clients. If the proofs are not verified, they should be considered as trusted third + parties services. Additionally, client would be advised in the future to use RaaS that support the + largest number of connections between chains in the ecosystem. Unfortunately, none of the existing + public relayers (in [Golang](https://github.com/cosmos/relayer) and + [Rust](https://github.com/informalsystems/ibc-rs)), provide this service to clients. + +:::tip +The only viable alternative for clients (at the time of writing) to tokens with multiple connection hops, is to connect to all chains directly and perform relevant queries to each of them in the sequence. +::: + +## Locked funds + +In some [exceptional cases](/architecture/adr-026-ibc-client-recovery-mechanisms#exceptional-cases), a client state associated with a given channel cannot be updated. This causes that funds from fungible tokens in that channel will be permanently locked and thus can no longer be transferred. + +To mitigate this, a client update governance proposal can be submitted to update the frozen client +with a new valid header. Once the proposal passes the client state will be unfrozen and the funds +from the associated channels will then be unlocked. This mechanism only applies to clients that +allow updates via governance, such as Tendermint clients. + +In addition to this, it's important to mention that a token must be sent back along the exact route +that it took originally in order to return it to its original form on the source chain (eg: the +Cosmos Hub for the `uatom`). Sending a token back to the same chain across a different channel will +**not** move the token back across its timeline. If a channel in the chain history closes before the +token can be sent back across that channel, then the token will not be returnable to its original +form. + +## Security considerations + +For safety, no other module must be capable of minting tokens with the `ibc/` prefix. The IBC +transfer module needs a subset of the denomination space that only it can create tokens in. + +## Channel Closure + +The IBC transfer module does not support channel closure. diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/02-state.md b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/02-state.md new file mode 100644 index 0000000..7239b01 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/02-state.md @@ -0,0 +1,14 @@ +--- +title: State +sidebar_label: State +sidebar_position: 2 +slug: /apps/transfer/ics20-v1/state +--- + +# State + +The IBC transfer application module keeps state of the port to which the module is binded and the denomination trace information. + +- `PortKey`: `0x01 -> ProtocolBuffer(string)` +- `DenomTraceKey`: `0x02 | []bytes(traceHash) -> ProtocolBuffer(Denom)` +- `DenomKey` : `0x03 | []bytes(traceHash) -> ProtocolBuffer(Denom)` diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/03-state-transitions.md b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/03-state-transitions.md new file mode 100644 index 0000000..85b8733 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/03-state-transitions.md @@ -0,0 +1,37 @@ +--- +title: State Transitions +sidebar_label: State Transitions +sidebar_position: 3 +slug: /apps/transfer/ics20-v1/state-transitions +--- + +# State transitions + +## Send fungible tokens + +A successful fungible token send has two state transitions depending if the transfer is a movement forward or backwards in the token's timeline: + +1. Sender chain is the source chain, *i.e* a transfer to any chain other than the one it was previously received from is a movement forwards in the token's timeline. This results in the following state transitions: + + - The coins are transferred to an escrow address (i.e locked) on the sender chain. + - The coins are transferred to the receiving chain through IBC TAO logic. + +2. Sender chain is the sink chain, *i.e* the token is sent back to the chain it previously received from. This is a backwards movement in the token's timeline. This results in the following state transitions: + + - The coins (vouchers) are burned on the sender chain. + - The coins are transferred to the receiving chain through IBC TAO logic. + +## Receive fungible tokens + +A successful fungible token receive has two state transitions depending if the transfer is a movement forward or backwards in the token's timeline: + +1. Receiver chain is the source chain. This is a backwards movement in the token's timeline. This results in the following state transitions: + + - The leftmost port and channel identifier pair is removed from the token denomination prefix. + - The tokens are unescrowed and sent to the receiving address. + +2. Receiver chain is the sink chain. This is a movement forwards in the token's timeline. This results in the following state transitions: + + - Token vouchers are minted by prefixing the destination port and channel identifiers to the trace information. + - The receiving chain stores the new trace information in the store (if not set already). + - The vouchers are sent to the receiving address. diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/04-messages.md b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/04-messages.md new file mode 100644 index 0000000..feeeedb --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/04-messages.md @@ -0,0 +1,76 @@ +--- +title: Messages +sidebar_label: Messages +sidebar_position: 4 +slug: /apps/transfer/ics20-v1/messages +--- + +# Messages + +## `MsgTransfer` + +A fungible token cross chain transfer is achieved by using the `MsgTransfer`: + +```go +type MsgTransfer struct { + SourcePort string + // with IBC v2 SourceChannel will be a client ID + SourceChannel string + Token sdk.Coin + Sender string + Receiver string + // If you are sending with IBC v1 protocol, either timeout_height or timeout_timestamp must be set. + // If you are sending with IBC v2 protocol, timeout_timestamp must be set, and timeout_height must be omitted. + TimeoutHeight ibcexported.Height + // Timeout timestamp in absolute nanoseconds since unix epoch. + TimeoutTimestamp uint64 + // optional Memo field + Memo string + // optional Encoding field + Encoding string +} +``` + +This message is expected to fail if: + +- `SourcePort` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators). +- `SourceChannel` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +- `Token` is invalid: + - `Amount` is not positive. + - `Denom` is not a valid IBC denomination as per [ADR 001 - Coin Source Tracing](/architecture/adr-001-coin-source-tracing). +- `Sender` is empty. +- `Receiver` is empty or contains more than 2048 bytes. +- `Memo` contains more than 32768 bytes. +- `TimeoutHeight` and `TimeoutTimestamp` are both zero for IBC Classic. + - Note that `TimeoutHeight` as a concept is removed in IBC v2, hence this must always be emitted and only `TimeoutTimestamp` used. + +This message will send a fungible token to the counterparty chain represented by the counterparty Channel End connected to the Channel End with the identifiers `SourcePort` and `SourceChannel`. Note that in IBC v2 a pair of clients are connected and the `SourceChannel` is referring to the source `ClientID`. + +The denomination provided for transfer should correspond to the same denomination represented on this chain. The prefixes will be added as necessary upon by the receiving chain. + +If the `Amount` is set to the maximum value for a 256-bit unsigned integer (i.e. 2^256 - 1), then the whole balance of the corresponding denomination will be transferred. The helper function `UnboundedSpendLimit` in the `types` package of the `transfer` module provides the sentinel value that can be used. + +### Memo + +The memo field was added to allow applications and users to attach metadata to transfer packets. The field is optional and may be left empty. When it is used to attach metadata for a particular middleware, the memo field should be represented as a json object where different middlewares use different json keys. + +For example, the following memo field is used by the callbacks middleware to attach a source callback to a transfer packet: + +```jsonc +{ + "src_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} +``` + +You can find more information about other applications that use the memo field in the [chain registry](https://github.com/cosmos/chain-registry/blob/master/_memo_keys/ICS20_memo_keys.json). + +### Encoding + +In IBC v2, the encoding method used by an application has more flexibility as it is specified within a `Payload`, rather than negotiated and fixed during an IBC classic channel handshake. Certain encoding types may be more suited to specific blockchains, e.g. ABI encoding is more gas efficient to decode in an EVM than JSON or Protobuf. + +Within ibc-go, JSON, protobuf and ABI encoding are supported and can be used, see the [transfer packet types](https://github.com/cosmos/ibc-go/blob/14bc17e26ad12cee6bdb99157a05296fcf58b762/modules/apps/transfer/types/packet.go#L36-L40). + \ No newline at end of file diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/05-events.md b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/05-events.md new file mode 100644 index 0000000..c53192b --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/05-events.md @@ -0,0 +1,59 @@ +--- +title: Events +sidebar_label: Events +sidebar_position: 5 +slug: /apps/transfer/ics20-v1/events +--- + +# Events + +## `MsgTransfer` + +| Type | Attribute Key | Attribute Value | +|--------------|-----------------|-----------------| +| ibc_transfer | sender | \{sender\} | +| ibc_transfer | receiver | \{receiver\} | +| ibc_transfer | denom | \{denom\} | +| ibc_transfer | denom_hash | \{denom_hash\} | +| ibc_transfer | amount | \{amount\} | +| ibc_transfer | memo | \{memo\} | +| message | module | transfer | + +## `OnRecvPacket` callback + +| Type | Attribute Key | Attribute Value | +|-----------------------|-----------------|-----------------| +| fungible_token_packet | sender | \{sender\} | +| fungible_token_packet | receiver | \{receiver\} | +| fungible_token_packet | denom | \{denom\} | +| fungible_token_packet | denom_hash | \{denom_hash\} | +| fungible_token_packet | amount | \{amount\} | +| fungible_token_packet | memo | \{memo\} | +| fungible_token_packet | success | \{ackSuccess\} | +| fungible_token_packet | error | \{ackError\} | +| message | module | transfer | + +## `OnAcknowledgePacket` callback + +| Type | Attribute Key | Attribute Value | +|-----------------------|-----------------|------------------| +| fungible_token_packet | sender | \{sender\} | +| fungible_token_packet | receiver | \{receiver\} | +| fungible_token_packet | denom | \{denom\} | +| fungible_token_packet | denom_hash | \{denom_hash\} | +| fungible_token_packet | amount | \{amount\} | +| fungible_token_packet | memo | \{memo\} | +| fungible_token_packet | acknowledgement | \{ack.String()\} | +| fungible_token_packet | success / error | \{ack.Response\} | +| message | module | transfer | + +## `OnTimeoutPacket` callback + +| Type | Attribute Key | Attribute Value | +|---------|-----------------|-----------------| +| timeout | refund_receiver | \{receiver\} | +| timeout | refund_tokens | \{jsonTokens\} | +| timeout | denom | \{denom\} | +| timeout | denom_hash | \{denom_hash\} | +| timeout | memo | \{memo\} | +| message | module | transfer | diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/06-metrics.md b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/06-metrics.md new file mode 100644 index 0000000..de85e0b --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/06-metrics.md @@ -0,0 +1,17 @@ +--- +title: Metrics +sidebar_label: Metrics +sidebar_position: 6 +slug: /apps/transfer/ics20-v1/metrics +--- + +# Metrics + +The IBC transfer application module exposes the following set of [metrics](https://docs.cosmos.network/main/learn/advanced/telemetry). + +| Metric | Description | Unit | Type | +|:--------------------------------|:------------------------------------------------------------------------------------------|:----------------|:--------| +| `tx_msg_ibc_transfer` | The total amount of tokens transferred via IBC in a `MsgTransfer` (source or sink chain) | token | gauge | +| `ibc_transfer_packet_receive` | The total amount of tokens received in a `FungibleTokenPacketData` (source or sink chain) | token | gauge | +| `ibc_transfer_send` | Total number of IBC transfers sent from a chain (source or sink) | transfer | counter | +| `ibc_transfer_receive` | Total number of IBC transfers received to a chain (source or sink) | transfer | counter | diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/07-params.md b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/07-params.md new file mode 100644 index 0000000..efa4e0b --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/07-params.md @@ -0,0 +1,93 @@ +--- +title: Params +sidebar_label: Params +sidebar_position: 7 +slug: /apps/transfer/ics20-v1/params +--- + +# Parameters + +The IBC transfer application module contains the following parameters: + +| Name | Type | Default Value | +| ---------------- | ---- | ------------- | +| `SendEnabled` | bool | `true` | +| `ReceiveEnabled` | bool | `true` | + +The IBC transfer module stores its parameters under its `StoreKey` + +## `SendEnabled` + +The `SendEnabled` parameter controls send cross-chain transfer capabilities for all fungible tokens. + +To prevent a single token from being transferred from the chain, set the `SendEnabled` parameter to `true` and then, depending on the Cosmos SDK version, do one of the following: + +- For Cosmos SDK v0.46.x or earlier, set the bank module's [`SendEnabled` parameter](https://github.com/cosmos/cosmos-sdk/blob/release/v0.46.x/x/bank/spec/05_params.md#sendenabled) for the denomination to `false`. +- For Cosmos SDK versions above v0.46.x, set the bank module's `SendEnabled` entry for the denomination to `false` using `MsgSetSendEnabled` as a governance proposal. + +:::warning +Doing so will prevent the token from being transferred between any accounts in the blockchain. +::: + +## `ReceiveEnabled` + +The transfers enabled parameter controls receive cross-chain transfer capabilities for all fungible tokens. + +To prevent a single token from being transferred to the chain, set the `ReceiveEnabled` parameter to `true` and then, depending on the Cosmos SDK version, do one of the following: + +- For Cosmos SDK v0.46.x or earlier, set the bank module's [`SendEnabled` parameter](https://github.com/cosmos/cosmos-sdk/blob/release/v0.46.x/x/bank/spec/05_params.md#sendenabled) for the denomination to `false`. +- For Cosmos SDK versions above v0.46.x, set the bank module's `SendEnabled` entry for the denomination to `false` using `MsgSetSendEnabled` as a governance proposal. + +:::warning +Doing so will prevent the token from being transferred between any accounts in the blockchain. +::: + +## Queries + +Current parameter values can be queried via a query message. + + + +```protobuf +// proto/ibc/applications/transfer/v1/query.proto + +// QueryParamsRequest is the request type for the Query/Params RPC method. +message QueryParamsRequest {} + +// QueryParamsResponse is the response type for the Query/Params RPC method. +message QueryParamsResponse { + // params defines the parameters of the module. + Params params = 1; +} +``` + +To execute the query in `simd`, you use the following command: + +```bash +simd query ibc-transfer params +``` + +## Changing Parameters + +To change the parameter values, you must make a governance proposal that executes the `MsgUpdateParams` message. + + + +```protobuf +// proto/ibc/applications/transfer/v1/tx.proto + +// MsgUpdateParams is the Msg/UpdateParams request type. +message MsgUpdateParams { + // signer address (it may be the address that controls the module, which defaults to x/gov unless overwritten). + string signer = 1; + + // params defines the transfer parameters to update. + // + // NOTE: All parameters must be supplied. + Params params = 2 [(gogoproto.nullable) = false]; +} + +// MsgUpdateParamsResponse defines the response structure for executing a +// MsgUpdateParams message. +message MsgUpdateParamsResponse {} +``` diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/08-authorizations.md b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/08-authorizations.md new file mode 100644 index 0000000..be4b5e4 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/08-authorizations.md @@ -0,0 +1,56 @@ +--- +title: Authorizations +sidebar_label: Authorizations +sidebar_position: 8 +slug: /apps/transfer/ics20-v1/authorizations +--- + +# `TransferAuthorization` + +`TransferAuthorization` implements the `Authorization` interface for `ibc.applications.transfer.v1.MsgTransfer`. It allows a granter to grant a grantee the privilege to submit `MsgTransfer` on its behalf. Please see the [Cosmos SDK docs](https://docs.cosmos.network/v0.47/modules/authz) for more details on granting privileges via the `x/authz` module. + +More specifically, the granter allows the grantee to transfer funds that belong to the granter over a specified channel. + +For the specified channel, the granter must be able to specify a spend limit of a specific denomination they wish to allow the grantee to be able to transfer. + +The granter may be able to specify the list of addresses that they allow to receive funds. If empty, then all addresses are allowed. + +It takes: + +- a `SourcePort` and a `SourceChannel` which together comprise the unique transfer channel identifier over which authorized funds can be transferred. +- a `SpendLimit` that specifies the maximum amount of tokens the grantee can transfer. The `SpendLimit` is updated as the tokens are transferred, unless the sentinel value of the maximum value for a 256-bit unsigned integer (i.e. 2^256 - 1) is used for the amount, in which case the `SpendLimit` will not be updated (please be aware that using this sentinel value will grant the grantee the privilege to transfer **all** the tokens of a given denomination available at the granter's account). The helper function `UnboundedSpendLimit` in the `types` package of the `transfer` module provides the sentinel value that can be used. This `SpendLimit` may also be updated to increase or decrease the limit as the granter wishes. +- an `AllowList` list that specifies the list of addresses that are allowed to receive funds. If this list is empty, then all addresses are allowed to receive funds from the `TransferAuthorization`. +- an `AllowedPacketData` list that specifies the list of memo strings that are allowed to be included in the memo field of the packet. If this list is empty, then only an empty memo is allowed (a `memo` field with non-empty content will be denied). If this list includes a single element equal to `"*"`, then any content in `memo` field will be allowed. + +Setting a `TransferAuthorization` is expected to fail if: + +- the spend limit is nil +- the denomination of the spend limit is an invalid coin type +- the source port ID is invalid +- the source channel ID is invalid +- there are duplicate entries in the `AllowList` +- the `memo` field is not allowed by `AllowedPacketData` + +Below is the `TransferAuthorization` message: + +```go +func NewTransferAuthorization(allocations ...Allocation) *TransferAuthorization { + return &TransferAuthorization{ + Allocations: allocations, + } +} + +type Allocation struct { + // the port on which the packet will be sent + SourcePort string + // the channel by which the packet will be sent + SourceChannel string + // spend limitation on the channel + SpendLimit sdk.Coins + // allow list of receivers, an empty allow list permits any receiver address + AllowList []string + // allow list of memo strings, an empty list prohibits all memo strings; + // a list only with "*" permits any memo string + AllowedPacketData []string +} +``` diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/09-client.md b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/09-client.md new file mode 100644 index 0000000..a26807b --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/09-client.md @@ -0,0 +1,94 @@ +--- +title: Client +sidebar_label: Client +sidebar_position: 9 +slug: /apps/transfer/ics20-v1/client +--- + +# Client + +## CLI + +A user can query and interact with the `transfer` module using the CLI. Use the `--help` flag to discover the available commands: + +### Query + +The `query` commands allow users to query `transfer` state. + +```shell +simd query ibc-transfer --help +``` + +#### Transactions + +The `tx` commands allow users to interact with the controller submodule. + +```shell +simd tx ibc-transfer --help +``` + +#### `transfer` + +The `transfer` command allows users to execute cross-chain token transfers from the source port ID and channel ID on the sending chain. + +```shell +simd tx ibc-transfer transfer [src-port] [src-channel] [receiver] [coins] [flags] +``` + +The `coins` parameter accepts the amount and denomination (e.g. `100uatom`) of the tokens to be transferred. + +The additional flags that can be used with the command are: + +- `--packet-timeout-height` to specify the timeout block height in the format `{revision}-{height}`. The default value is `0-0`, which effectively disables the timeout. Timeout height can only be absolute, therefore this option must be used in combination with `--absolute-timeouts` set to true. On IBC v1 protocol, either `--packet-timeout-height` or `--packet-timeout-timestamp` must be set. On IBC v2 protocol `--packet-timeout-timestamp` must be set. +- `--packet-timeout-timestamp` to specify the timeout timestamp in nanoseconds. The timeout can be either relative (from the current UTC time) or absolute. The default value is 10 minutes (and thus relative). On IBC v1 protocol, either `--packet-timeout-height` or `--packet-timeout-timestamp` must be set. On IBC v2 protocol `--packet-timeout-timestamp` must be set. +- `--absolute-timeouts` to interpret the timeout timestamp as an absolute value (when set to true). The default value is false (and thus the timeout is considered relative to current UTC time). +- `--memo` to specify the memo string to be sent along with the transfer packet. If forwarding is used, then the memo string will be carried through the intermediary chains to the final destination. + +#### `total-escrow` + +The `total-escrow` command allows users to query the total amount in escrow for a particular coin denomination regardless of the transfer channel from where the coins were sent out. + +```shell +simd query ibc-transfer total-escrow [denom] [flags] +``` + +Example: + +```shell +simd query ibc-transfer total-escrow samoleans +``` + +Example Output: + +```shell +amount: "100" +``` + +## gRPC + +A user can query the `transfer` module using gRPC endpoints. + +### `TotalEscrowForDenom` + +The `TotalEscrowForDenom` endpoint allows users to query the total amount in escrow for a particular coin denomination regardless of the transfer channel from where the coins were sent out. + +```shell +ibc.applications.transfer.v1.Query/TotalEscrowForDenom +``` + +Example: + +```shell +grpcurl -plaintext \ + -d '{"denom":"samoleans"}' \ + localhost:9090 \ + ibc.applications.transfer.v1.Query/TotalEscrowForDenom +``` + +Example output: + +```shell +{ + "amount": "100" +} +``` diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/10-IBCv2-transfer.md b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/10-IBCv2-transfer.md new file mode 100644 index 0000000..bf4512c --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/10-IBCv2-transfer.md @@ -0,0 +1,65 @@ +--- +title: IBC v2 Transfer +sidebar_label: IBC v2 Transfer +sidebar_position: 10 +slug: /apps/transfer/ics20-v1/ibcv2transfer +--- + +# IBC v2 Transfer + +Much of the core business logic of sending and recieving tokens between chains is unchanged between IBC Classic and IBC v2. Some of the key differences to pay attention to are detailed below. + +## No Channel Handshakes, New Packet Format and Encoding Support + +- IBC v2 does not establish connection between applications with a channel handshake. Channel identifiers represent Client IDs and are included in the `Payload` + - The source and destination port must be `"transfer"` + - The channel IDs [must be valid client IDs](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/v2/ibc_module.go#L46-L47) of the format `{clientID}-{sequence}`, e.g. 08-wasm-007 +- The [`Payload`](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel/v2/types/packet.pb.go#L146-L158) contains the [`FungibleTokenPacketData`](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/types/packet.pb.go#L28-L39) for a token transfer. + +The code snippet shows the `Payload` struct. + +```go +// Payload contains the source and destination ports and payload for the application (version, encoding, raw bytes) +type Payload struct { + // specifies the source port of the packet, e.g. transfer + SourcePort string `protobuf:"bytes,1,opt,name=source_port,json=sourcePort,proto3" json:"source_port,omitempty"` + // specifies the destination port of the packet, e.g. trasnfer + DestinationPort string `protobuf:"bytes,2,opt,name=destination_port,json=destinationPort,proto3" json:"destination_port,omitempty"` + // version of the specified application + Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` + // the encoding used for the provided value, for transfer this could be JSON, protobuf or ABI + Encoding string `protobuf:"bytes,4,opt,name=encoding,proto3" json:"encoding,omitempty"` + // the raw bytes for the payload. + Value []byte `protobuf:"bytes,5,opt,name=value,proto3" json:"value,omitempty"` +} +``` + +The code snippet shows the structure of the `Payload` bytes for token transfer + +```go +// FungibleTokenPacketData defines a struct for the packet payload +// See FungibleTokenPacketData spec: +// https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#data-structures +type FungibleTokenPacketData struct { + // the token denomination to be transferred + Denom string `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom,omitempty"` + // the token amount to be transferred + Amount string `protobuf:"bytes,2,opt,name=amount,proto3" json:"amount,omitempty"` + // the sender address + Sender string `protobuf:"bytes,3,opt,name=sender,proto3" json:"sender,omitempty"` + // the recipient address on the destination chain + Receiver string `protobuf:"bytes,4,opt,name=receiver,proto3" json:"receiver,omitempty"` + // optional memo + Memo string `protobuf:"bytes,5,opt,name=memo,proto3" json:"memo,omitempty"` +} +``` + +## Base Denoms cannot contain slashes + +With the new [`Denom`](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/types/token.pb.go#L81-L87) struct, the base denom, i.e. uatom, is seperated from the trace - the path the token has travelled. The trace is presented as an array of [`Hop`](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/types/token.pb.go#L136-L140)s. + +Because IBC v2 no longer uses channels, it is no longer possible to rely on a fixed format for an identifier so using a base denom that contains a "/" is dissallowed. + +## Changes to the application module interface + +Instead of implementing token transfer for `port.IBCModule`, IBC v2 uses the new application interface `api.IBCModule`. More information on the interface differences can be found in the [application section](../../01-ibc/03-apps/00-ibcv2apps.md). diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/_category_.json b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/_category_.json new file mode 100644 index 0000000..a803e10 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Token Transfer", + "position": 1, + "link": null +} diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/images/forwarding-3-chains-dark.png b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/images/forwarding-3-chains-dark.png new file mode 100644 index 0000000..3defdd2 Binary files /dev/null and b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/images/forwarding-3-chains-dark.png differ diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/images/forwarding-3-chains-light.png b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/images/forwarding-3-chains-light.png new file mode 100644 index 0000000..5eb2954 Binary files /dev/null and b/docs/versioned_docs/version-v10.1.x/02-apps/01-transfer/images/forwarding-3-chains-light.png differ diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/01-overview.md b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/01-overview.md new file mode 100644 index 0000000..5a0aafa --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/01-overview.md @@ -0,0 +1,51 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /apps/interchain-accounts/overview +--- + +:::warning +Interchain Accounts is only compatible with IBC Classic, not IBC v2 +::: + +# Overview + +:::note Synopsis +Learn about what the Interchain Accounts module is +::: + +## What is the Interchain Accounts module? + +Interchain Accounts is the Cosmos SDK implementation of the ICS-27 protocol, which enables cross-chain account management built upon IBC. + +- How does an interchain account differ from a regular account? + +Regular accounts use a private key to sign transactions. Interchain Accounts are instead controlled programmatically by counterparty chains via IBC packets. + +## Concepts + +`Host Chain`: The chain where the interchain account is registered. The host chain listens for IBC packets from a controller chain which should contain instructions (e.g. Cosmos SDK messages) for which the interchain account will execute. + +`Controller Chain`: The chain registering and controlling an account on a host chain. The controller chain sends IBC packets to the host chain to control the account. + +`Interchain Account`: An account on a host chain created using the ICS-27 protocol. An interchain account has all the capabilities of a normal account. However, rather than signing transactions with a private key, a controller chain will send IBC packets to the host chain which signals what transactions the interchain account should execute. + +`Authentication Module`: A custom application module on the controller chain that uses the Interchain Accounts module to build custom logic for the creation & management of interchain accounts. It can be either an IBC application module using the [legacy API](10-legacy/03-keeper-api.md), or a regular Cosmos SDK application module sending messages to the controller submodule's `MsgServer` (this is the recommended approach from ibc-go v6 if access to packet callbacks is not needed). Please note that the legacy API will eventually be removed and IBC applications will not be able to use them in later releases. + +## SDK security model + +SDK modules on a chain are assumed to be trustworthy. For example, there are no checks to prevent an untrustworthy module from accessing the bank keeper. + +The implementation of ICS-27 in ibc-go uses this assumption in its security considerations. + +The implementation assumes other IBC application modules will not bind to ports within the ICS-27 namespace. + +## Channel Closure + +The provided interchain account host and controller implementations do not support `ChanCloseInit`. However, they do support `ChanCloseConfirm`. +This means that the host and controller modules cannot close channels, but they will confirm channel closures initiated by other implementations of ICS-27. + +In the event of a channel closing (due to a packet timeout in an ordered channel, for example), the interchain account associated with that channel can become accessible again if a new channel is created with a (JSON-formatted) version string that encodes the exact same `Metadata` information of the previous channel. The channel can be reopened using either [`MsgRegisterInterchainAccount`](./05-messages.md#msgregisterinterchainaccount) or `MsgChannelOpenInit`. If `MsgRegisterInterchainAccount` is used, then it is possible to leave the `version` field of the message empty, since it will be filled in by the controller submodule. If `MsgChannelOpenInit` is used, then the `version` field must be provided with the correct JSON-encoded `Metadata` string. See section [Understanding Active Channels](./09-active-channels.md#understanding-active-channels) for more information. + +When reopening a channel with the default controller submodule, the ordering of the channel cannot be changed. diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/02-development.md b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/02-development.md new file mode 100644 index 0000000..f949a92 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/02-development.md @@ -0,0 +1,40 @@ +--- +title: Development Use Cases +sidebar_label: Development Use Cases +sidebar_position: 2 +slug: /apps/interchain-accounts/development +--- + + +# Development use cases + +The initial version of Interchain Accounts allowed for the controller submodule to be extended by providing it with an underlying application which would handle all packet callbacks. +That functionality is now being deprecated in favor of alternative approaches. +This document will outline potential use cases and redirect each use case to the appropriate documentation. + +## Custom authentication + +Interchain accounts may be associated with alternative types of authentication relative to the traditional public/private key signing. +If you wish to develop or use Interchain Accounts with a custom authentication module and do not need to execute custom logic on the packet callbacks, we recommend you use ibc-go v6 or greater and that your custom authentication module interacts with the controller submodule via the [`MsgServer`](./05-messages.md). + +If you wish to consume and execute custom logic in the packet callbacks, then please read the section [Packet callbacks](#packet-callbacks) below. + +## Redirection to a smart contract + +It may be desirable to allow smart contracts to control an interchain account. +To facilitate such an action, the controller submodule may be provided an underlying application which redirects to smart contract callers. +An improved design has been suggested in [ADR 008](https://github.com/cosmos/ibc-go/pull/1976) which performs this action via middleware. + +Implementers of this use case are recommended to follow the ADR 008 approach. +The underlying application may continue to be used as a short term solution for ADR 008 and the [legacy API](./10-legacy/01-auth-modules.md) should continue to be utilized in such situations. + +## Packet callbacks + +If a developer requires access to packet callbacks for their use case, then they have the following options: + +1. Write a smart contract which is connected via an ADR 008 or equivalent IBC application (recommended). +2. Use the controller's underlying application to implement packet callback logic. + +In the first case, the smart contract should use the [`MsgServer`](./05-messages.md). + +In the second case, the underlying application should use the [legacy API](./10-legacy/03-keeper-api.md). diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/03-auth-modules.md b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/03-auth-modules.md new file mode 100644 index 0000000..bac035e --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/03-auth-modules.md @@ -0,0 +1,27 @@ +--- +title: Authentication Modules +sidebar_label: Authentication Modules +sidebar_position: 3 +slug: /apps/interchain-accounts/auth-modules +--- + + +# Building an authentication module + +:::note Synopsis +Authentication modules enable application developers to perform custom logic when interacting with the Interchain Accounts controller sumbmodule's `MsgServer`. +::: + +The controller submodule is used for account registration and packet sending. It executes only logic required of all controllers of interchain accounts. The type of authentication used to manage the interchain accounts remains unspecified. There may exist many different types of authentication which are desirable for different use cases. Thus the purpose of the authentication module is to wrap the controller submodule with custom authentication logic. + +In ibc-go, authentication modules can communicate with the controller submodule by passing messages through `baseapp`'s `MsgServiceRouter`. To implement an authentication module, the `IBCModule` interface need not be fulfilled; it is only required to fulfill Cosmos SDK's `AppModuleBasic` interface, just like any regular Cosmos SDK application module. + +The authentication module must: + +- Authenticate interchain account owners. +- Track the associated interchain account address for an owner. +- Send packets on behalf of an owner (after authentication). + +## Integration into `app.go` file + +To integrate the authentication module into your chain, please follow the steps outlined in [`app.go` integration](04-integration.md#example-integration). diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/04-integration.md b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/04-integration.md new file mode 100644 index 0000000..aa72536 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/04-integration.md @@ -0,0 +1,195 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 4 +slug: /apps/interchain-accounts/integration +--- + + +# Integration + +:::note Synopsis +Learn how to integrate Interchain Accounts host and controller functionality to your chain. The following document only applies for Cosmos SDK chains. +::: + +The Interchain Accounts module contains two submodules. Each submodule has its own IBC application. The Interchain Accounts module should be registered as an `AppModule` in the same way all SDK modules are registered on a chain, but each submodule should create its own `IBCModule` as necessary. A route should be added to the IBC router for each submodule which will be used. + +Chains who wish to support ICS-27 may elect to act as a host chain, a controller chain or both. Disabling host or controller functionality may be done statically by excluding the host or controller submodule entirely from the `app.go` file or it may be done dynamically by taking advantage of the on-chain parameters which enable or disable the host or controller submodules. + +Interchain Account authentication modules (both custom or generic, such as the `x/gov`, `x/group` or `x/auth` Cosmos SDK modules) can send messages to the controller submodule's [`MsgServer`](05-messages.md) to register interchain accounts and send packets to the interchain account. To accomplish this, the authentication module needs to be composed with `baseapp`'s `MsgServiceRouter`. + +![ica-v6.png](./images/ica-v6.png) + +## Example integration + +```go +// app.go + +// Register the AppModule for the Interchain Accounts module and the authentication module +// Note: No `icaauth` exists, this must be substituted with an actual Interchain Accounts authentication module +ModuleBasics = module.NewBasicManager( + ... + ica.AppModuleBasic{}, + icaauth.AppModuleBasic{}, + ... +) + +... + +// Add module account permissions for the Interchain Accounts module +// Only necessary for host chain functionality +// Each Interchain Account created on the host chain is derived from the module account created +maccPerms = map[string][]string{ + ... + icatypes.ModuleName: nil, +} + +... + +// Add Interchain Accounts Keepers for each submodule used and the authentication module +// If a submodule is being statically disabled, the associated Keeper does not need to be added. +type App struct { + ... + + ICAControllerKeeper icacontrollerkeeper.Keeper + ICAHostKeeper icahostkeeper.Keeper + ICAAuthKeeper icaauthkeeper.Keeper + + ... +} + +... + +// Create store keys for each submodule Keeper and the authentication module +keys := sdk.NewKVStoreKeys( + ... + icacontrollertypes.StoreKey, + icahosttypes.StoreKey, + icaauthtypes.StoreKey, + ... +) + +... + + +// Create the Keeper for each submodule +app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, app.IBCKeeper.PortKeeper, + app.MsgServiceRouter(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), +) +app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, app.IBCKeeper.PortKeeper, app.AccountKeeper, + app.MsgServiceRouter(), app.GRPCQueryRouter(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), +) + +// Create Interchain Accounts AppModule +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper) + +// Create your Interchain Accounts authentication module +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.MsgServiceRouter()) + +// ICA auth AppModule +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) + +// Create controller IBC application stack and host IBC module as desired +icaControllerStack := icacontroller.NewIBCMiddleware(app.ICAControllerKeeper) +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host and authentication routes +ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icahosttypes.SubModuleName, icaHostIBCModule) +... + +// Register Interchain Accounts and authentication module AppModule's +app.moduleManager = module.NewManager( + ... + icaModule, + icaAuthModule, +) + +... + +// Add Interchain Accounts to begin blocker logic +app.moduleManager.SetOrderBeginBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add Interchain Accounts to end blocker logic +app.moduleManager.SetOrderEndBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add Interchain Accounts module InitGenesis logic +app.moduleManager.SetOrderInitGenesis( + ... + icatypes.ModuleName, + ... +) + +// initParamsKeeper init params keeper and its subspaces +func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey sdk.StoreKey) paramskeeper.Keeper { + ... + paramsKeeper.Subspace(icahosttypes.SubModuleName) + paramsKeeper.Subspace(icacontrollertypes.SubModuleName) + ... +} +``` + +If no custom athentication module is needed and a generic Cosmos SDK authentication module can be used, then from the sample integration code above all references to `ICAAuthKeeper` and `icaAuthModule` can be removed. That's it, the following code would not be needed: + +```go +// Create your Interchain Accounts authentication module +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.MsgServiceRouter()) + +// ICA auth AppModule +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) +``` + +### Using submodules exclusively + +As described above, the Interchain Accounts application module is structured to support the ability of exclusively enabling controller or host functionality. +This can be achieved by simply omitting either controller or host `Keeper` from the Interchain Accounts `NewAppModule` constructor function, and mounting only the desired submodule via the `IBCRouter`. +Alternatively, submodules can be enabled and disabled dynamically using [on-chain parameters](06-parameters.md). + +The following snippets show basic examples of statically disabling submodules using `app.go`. + +#### Disabling controller chain functionality + +```go +// Create Interchain Accounts AppModule omitting the controller keeper +icaModule := ica.NewAppModule(nil, &app.ICAHostKeeper) + +// Create host IBC Module +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host route +ibcRouter.AddRoute(icahosttypes.SubModuleName, icaHostIBCModule) +``` + +#### Disabling host chain functionality + +```go +// Create Interchain Accounts AppModule omitting the host keeper +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, nil) + + +// Optionally instantiate your custom authentication module if needed, or not otherwise +... + +// Create controller IBC application stack +icaControllerStack := icacontroller.NewIBCMiddleware(app.ICAControllerKeeper) + +// Register controller route +ibcRouter.AddRoute(icacontrollertypes.SubModuleName, icaControllerStack) +``` diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/05-messages.md b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/05-messages.md new file mode 100644 index 0000000..cbdb98a --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/05-messages.md @@ -0,0 +1,153 @@ +--- +title: Messages +sidebar_label: Messages +sidebar_position: 5 +slug: /apps/interchain-accounts/messages +--- + + +# Messages + +## `MsgRegisterInterchainAccount` + +An Interchain Accounts channel handshake can be initiated using `MsgRegisterInterchainAccount`: + +```go +type MsgRegisterInterchainAccount struct { + Owner string + ConnectionID string + Version string + Ordering channeltypes.Order +} +``` + +This message is expected to fail if: + +- `Owner` is an empty string or contains more than 2048 bytes. +- `ConnectionID` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). + +This message will construct a new `MsgChannelOpenInit` on chain and route it to the core IBC message server to initiate the opening step of the channel handshake. + +The controller submodule will generate a new port identifier. The caller is expected to provide an appropriate application version string. For example, this may be an ICS-27 JSON encoded [`Metadata`](https://github.com/cosmos/ibc-go/blob/v6.0.0/proto/ibc/applications/interchain_accounts/v1/metadata.proto#L11) type or an ICS-29 JSON encoded [`Metadata`](https://github.com/cosmos/ibc-go/blob/v6.0.0/proto/ibc/applications/fee/v1/metadata.proto#L11) type with a nested application version. +If the `Version` string is omitted, the controller submodule will construct a default version string in the `OnChanOpenInit` handshake callback. + +```go +type MsgRegisterInterchainAccountResponse struct { + ChannelID string + PortId string +} +``` + +The `ChannelID` and `PortID` are returned in the message response. + +## `MsgSendTx` + +An Interchain Accounts transaction can be executed on a remote host chain by sending a `MsgSendTx` from the corresponding controller chain: + +```go +type MsgSendTx struct { + Owner string + ConnectionID string + PacketData InterchainAccountPacketData + RelativeTimeout uint64 +} +``` + +This message is expected to fail if: + +- `Owner` is an empty string or contains more than 2048 bytes. +- `ConnectionID` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +- `PacketData` contains an `UNSPECIFIED` type enum, the length of `Data` bytes is zero or the `Memo` field exceeds 256 characters in length. +- `RelativeTimeout` is zero. + +This message will create a new IBC packet with the provided `PacketData` and send it via the channel associated with the `Owner` and `ConnectionID`. +The `PacketData` is expected to contain a list of serialized `[]sdk.Msg` in the form of `CosmosTx`. Please note the signer field of each `sdk.Msg` must be the interchain account address. +When the packet is relayed to the host chain, the `PacketData` is unmarshalled and the messages are authenticated and executed. + +```go +type MsgSendTxResponse struct { + Sequence uint64 +} +``` + +The packet `Sequence` is returned in the message response. + +### Queries + +It is possible to use [`MsgModuleQuerySafe`](https://github.com/cosmos/ibc-go/blob/eecfa5c09a4c38a5c9f2cc2a322d2286f45911da/proto/ibc/applications/interchain_accounts/host/v1/tx.proto#L41-L51) to execute a list of queries on the host chain. This message can be included in the list of encoded `sdk.Msg`s of `InterchainPacketData`. The host chain will return on the acknowledgment the responses for all the queries. Please note that only module safe queries can be executed ([deterministic queries that are safe to be called from within the state machine](https://docs.cosmos.network/main/build/building-modules/query-services#calling-queries-from-the-state-machine)). + +The queries available from Cosmos SDK are: + +```plaintext +/cosmos.auth.v1beta1.Query/Accounts +/cosmos.auth.v1beta1.Query/Account +/cosmos.auth.v1beta1.Query/AccountAddressByID +/cosmos.auth.v1beta1.Query/Params +/cosmos.auth.v1beta1.Query/ModuleAccounts +/cosmos.auth.v1beta1.Query/ModuleAccountByName +/cosmos.auth.v1beta1.Query/AccountInfo +/cosmos.bank.v1beta1.Query/Balance +/cosmos.bank.v1beta1.Query/AllBalances +/cosmos.bank.v1beta1.Query/SpendableBalances +/cosmos.bank.v1beta1.Query/SpendableBalanceByDenom +/cosmos.bank.v1beta1.Query/TotalSupply +/cosmos.bank.v1beta1.Query/SupplyOf +/cosmos.bank.v1beta1.Query/Params +/cosmos.bank.v1beta1.Query/DenomMetadata +/cosmos.bank.v1beta1.Query/DenomMetadataByQueryString +/cosmos.bank.v1beta1.Query/DenomsMetadata +/cosmos.bank.v1beta1.Query/DenomOwners +/cosmos.bank.v1beta1.Query/SendEnabled +/cosmos.circuit.v1.Query/Account +/cosmos.circuit.v1.Query/Accounts +/cosmos.circuit.v1.Query/DisabledList +/cosmos.staking.v1beta1.Query/Validators +/cosmos.staking.v1beta1.Query/Validator +/cosmos.staking.v1beta1.Query/ValidatorDelegations +/cosmos.staking.v1beta1.Query/ValidatorUnbondingDelegations +/cosmos.staking.v1beta1.Query/Delegation +/cosmos.staking.v1beta1.Query/UnbondingDelegation +/cosmos.staking.v1beta1.Query/DelegatorDelegations +/cosmos.staking.v1beta1.Query/DelegatorUnbondingDelegations +/cosmos.staking.v1beta1.Query/Redelegations +/cosmos.staking.v1beta1.Query/DelegatorValidators +/cosmos.staking.v1beta1.Query/DelegatorValidator +/cosmos.staking.v1beta1.Query/HistoricalInfo +/cosmos.staking.v1beta1.Query/Pool +/cosmos.staking.v1beta1.Query/Params +``` + +And the query available from ibc-go is: + +```plaintext +/ibc.core.client.v1.Query/VerifyMembership +``` + +The following code block shows an example of how `MsgModuleQuerySafe` can be used to query the account balance of an account on the host chain. The resulting packet data variable is used to set the `PacketData` of `MsgSendTx`. + +```go +balanceQuery := banktypes.NewQueryBalanceRequest("cosmos1...", "uatom") +queryBz, err := balanceQuery.Marshal() + +// signer of message must be the interchain account on the host +queryMsg := icahosttypes.NewMsgModuleQuerySafe("cosmos2...", []icahosttypes.QueryRequest{ + { + Path: "/cosmos.bank.v1beta1.Query/Balance", + Data: queryBz, + }, +}) + +bz, err := icatypes.SerializeCosmosTx(cdc, []proto.Message{queryMsg}, icatypes.EncodingProtobuf) + +packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: bz, + Memo: "", +} +``` + +## Atomicity + +As the Interchain Accounts module supports the execution of multiple transactions using the Cosmos SDK `Msg` interface, it provides the same atomicity guarantees as Cosmos SDK-based applications, leveraging the [`CacheMultiStore`](https://docs.cosmos.network/main/learn/advanced/store#cachemultistore) architecture provided by the [`Context`](https://docs.cosmos.network/main/learn/advanced/context.html) type. + +This provides atomic execution of transactions when using Interchain Accounts, where state changes are only committed if all `Msg`s succeed. diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/06-parameters.md b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/06-parameters.md new file mode 100644 index 0000000..5004118 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/06-parameters.md @@ -0,0 +1,65 @@ +--- +title: Parameters +sidebar_label: Parameters +sidebar_position: 6 +slug: /apps/interchain-accounts/parameters +--- + + +# Parameters + +The Interchain Accounts module contains the following on-chain parameters, logically separated for each distinct submodule: + +## Controller Submodule Parameters + +| Name | Type | Default Value | +|------------------------|------|---------------| +| `ControllerEnabled` | bool | `true` | + +### ControllerEnabled + +The `ControllerEnabled` parameter controls a chains ability to service ICS-27 controller specific logic. This includes the sending of Interchain Accounts packet data as well as the following ICS-26 callback handlers: + +- `OnChanOpenInit` +- `OnChanOpenAck` +- `OnChanCloseConfirm` +- `OnAcknowledgementPacket` +- `OnTimeoutPacket` + +## Host Submodule Parameters + +| Name | Type | Default Value | +|------------------------|----------|---------------| +| `HostEnabled` | bool | `true` | +| `AllowMessages` | []string | `["*"]` | + +### HostEnabled + +The `HostEnabled` parameter controls a chains ability to service ICS-27 host specific logic. This includes the following ICS-26 callback handlers: + +- `OnChanOpenTry` +- `OnChanOpenConfirm` +- `OnChanCloseConfirm` +- `OnRecvPacket` + +### AllowMessages + +The `AllowMessages` parameter provides the ability for a chain to limit the types of messages or transactions that hosted interchain accounts are authorized to execute by defining an allowlist using the Protobuf message type URL format. + +For example, a Cosmos SDK-based chain that elects to provide hosted Interchain Accounts with the ability of governance voting and staking delegations will define its parameters as follows: + +```json +"params": { + "host_enabled": true, + "allow_messages": ["/cosmos.staking.v1beta1.MsgDelegate", "/cosmos.gov.v1beta1.MsgVote"] +} +``` + +There is also a special wildcard `"*"` value which allows any type of message to be executed by the interchain account. This must be the only value in the `allow_messages` array. + +```json +"params": { + "host_enabled": true, + "allow_messages": ["*"] +} +``` diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/07-tx-encoding.md b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/07-tx-encoding.md new file mode 100644 index 0000000..c30ee07 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/07-tx-encoding.md @@ -0,0 +1,58 @@ +--- +title: Transaction Encoding +sidebar_label: Transaction Encoding +sidebar_position: 7 +slug: /apps/interchain-accounts/tx-encoding +--- + +# Transaction Encoding + +When orchestrating an interchain account transaction, which comprises multiple `sdk.Msg` objects represented as `Any` types, the transactions must be encoded as bytes within [`InterchainAccountPacketData`](https://github.com/cosmos/ibc-go/blob/v7.2.0/proto/ibc/applications/interchain_accounts/v1/packet.proto#L21-L26). + +```protobuf +// InterchainAccountPacketData is comprised of a raw transaction, type of transaction and optional memo field. +message InterchainAccountPacketData { + Type type = 1; + bytes data = 2; + string memo = 3; +} +``` + +The `data` field must be encoded as a [`CosmosTx`](https://github.com/cosmos/ibc-go/blob/v7.2.0/proto/ibc/applications/interchain_accounts/v1/packet.proto#L28-L31). + +```protobuf +// CosmosTx contains a list of sdk.Msg's. It should be used when sending transactions to an SDK host chain. +message CosmosTx { + repeated google.protobuf.Any messages = 1; +} +``` + +The encoding method for `CosmosTx` is determined during the channel handshake process. If the channel version [metadata's `encoding` field](https://github.com/cosmos/ibc-go/blob/v7.2.0/proto/ibc/applications/interchain_accounts/v1/metadata.proto#L22) is marked as `proto3`, then `CosmosTx` undergoes protobuf encoding. Conversely, if the field is set to `proto3json`, then [proto3 json](https://protobuf.dev/programming-guides/proto3/#json) encoding takes place, which generates a JSON representation of the protobuf message. + +## Protobuf Encoding + +Protobuf encoding serves as the standard encoding process for `CosmosTx`. This occurs if the channel handshake initiates with an empty channel version metadata or if the `encoding` field explicitly denotes `proto3`. In Golang, the protobuf encoding procedure utilizes the `proto.Marshal` function. Every protobuf autogenerated Golang type comes equipped with a `Marshal` method that can be employed to encode the message. + +## (Protobuf) JSON Encoding + +The proto3 JSON encoding presents an alternative encoding technique for `CosmosTx`. It is selected if the channel handshake begins with the channel version metadata `encoding` field labeled as `proto3json`. In Golang, the Proto3 canonical encoding in JSON is implemented by the `"github.com/cosmos/gogoproto/jsonpb"` package. Within Cosmos SDK, the `ProtoCodec` structure implements the `JSONCodec` interface, leveraging the `jsonpb` package. This method generates a JSON format as follows: + +```json +{ + "messages": [ + { + "@type": "/cosmos.bank.v1beta1.MsgSend", + "from_address": "cosmos1...", + "to_address": "cosmos1...", + "amount": [ + { + "denom": "uatom", + "amount": "1000000" + } + ] + } + ] +} +``` + +Here, the `"messages"` array is populated with transactions. Each transaction is represented as a JSON object with the `@type` field denoting the transaction type and the remaining fields representing the transaction's attributes. diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/08-client.md b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/08-client.md new file mode 100644 index 0000000..079a97a --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/08-client.md @@ -0,0 +1,202 @@ +--- +title: Client +sidebar_label: Client +sidebar_position: 8 +slug: /apps/interchain-accounts/client +--- + +# Client + +## CLI + +A user can query and interact with the Interchain Accounts module using the CLI. Use the `--help` flag to discover the available commands: + +```shell +simd query interchain-accounts --help +``` + +> Please not that this section does not document all the available commands, but only the ones that deserved extra documentation that was not possible to fit in the command line documentation. + +### Controller + +A user can query and interact with the controller submodule. + +#### Query + +The `query` commands allow users to query the controller submodule. + +```shell +simd query interchain-accounts controller --help +``` + +#### Transactions + +The `tx` commands allow users to interact with the controller submodule. + +```shell +simd tx interchain-accounts controller --help +``` + +#### `register` + +The `register` command allows users to register an interchain account on a host chain on the provided connection. + +```shell +simd tx interchain-accounts controller register [connection-id] [flags] +``` + +During registration a new channel is set up between controller and host. There are two flags available that influence the channel that is created: + +- `--version` to specify the (JSON-formatted) version string of the channel. For example: `{\"version\":\"ics27-1\",\"encoding\":\"proto3\",\"tx_type\":\"sdk_multi_msg\",\"controller_connection_id\":\"connection-0\",\"host_connection_id\":\"connection-0\"}`. Passing a custom version string is useful if you want to specify, for example, the encoding format of the interchain accounts packet data (either `proto3` or `proto3json`). If not specified the controller submodule will generate a default version string. +- `--ordering` to specify the ordering of the channel. Available options are `order_ordered` and `order_unordered` (default if not specified). + +Example: + +```shell +simd tx interchain-accounts controller register connection-0 --ordering order_ordered --from cosmos1.. +``` + +#### `send-tx` + +The `send-tx` command allows users to send a transaction on the provided connection to be executed using an interchain account on the host chain. + +```shell +simd tx interchain-accounts controller send-tx [connection-id] [path/to/packet_msg.json] +``` + +Example: + +```shell +simd tx interchain-accounts controller send-tx connection-0 packet-data.json --from cosmos1.. +``` + +See below for example contents of `packet-data.json`. The CLI handler will unmarshal the following into `InterchainAccountPacketData` appropriately. + +```json +{ + "type":"TYPE_EXECUTE_TX", + "data":"CqIBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEoEBCkFjb3Ntb3MxNWNjc2hobXAwZ3N4MjlxcHFxNmc0em1sdG5udmdteXU5dWV1YWRoOXkybmM1emowc3psczVndGRkehItY29zbW9zMTBoOXN0YzV2Nm50Z2V5Z2Y1eGY5NDVuanFxNWgzMnI1M3VxdXZ3Gg0KBXN0YWtlEgQxMDAw", + "memo":"" +} +``` + +Note the `data` field is a base64 encoded byte string as per the tx encoding agreed upon during the channel handshake. + +A helper CLI is provided in the host submodule which can be used to generate the packet data JSON using the counterparty chain's binary. See the [`generate-packet-data` command](#generate-packet-data) for an example. + +### Host + +A user can query and interact with the host submodule. + +#### Query + +The `query` commands allow users to query the host submodule. + +```shell +simd query interchain-accounts host --help +``` + +#### Transactions + +The `tx` commands allow users to interact with the controller submodule. + +```shell +simd tx interchain-accounts host --help +``` + +##### `generate-packet-data` + +The `generate-packet-data` command allows users to generate protobuf or proto3 JSON encoded interchain accounts packet data for input message(s). The packet data can then be used with the controller submodule's [`send-tx` command](#send-tx). The `--encoding` flag can be used to specify the encoding format (value must be either `proto3` or `proto3json`); if not specified, the default will be `proto3`. The `--memo` flag can be used to include a memo string in the interchain accounts packet data. + +```shell +simd tx interchain-accounts host generate-packet-data [message] +``` + +Example: + +```shell +simd tx interchain-accounts host generate-packet-data '[{ + "@type":"/cosmos.bank.v1beta1.MsgSend", + "from_address":"cosmos15ccshhmp0gsx29qpqq6g4zmltnnvgmyu9ueuadh9y2nc5zj0szls5gtddz", + "to_address":"cosmos10h9stc5v6ntgeygf5xf945njqq5h32r53uquvw", + "amount": [ + { + "denom": "stake", + "amount": "1000" + } + ] +}]' --memo memo +``` + +The command accepts a single `sdk.Msg` or a list of `sdk.Msg`s that will be encoded into the outputs `data` field. + +Example output: + +```json +{ + "type":"TYPE_EXECUTE_TX", + "data":"CqIBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEoEBCkFjb3Ntb3MxNWNjc2hobXAwZ3N4MjlxcHFxNmc0em1sdG5udmdteXU5dWV1YWRoOXkybmM1emowc3psczVndGRkehItY29zbW9zMTBoOXN0YzV2Nm50Z2V5Z2Y1eGY5NDVuanFxNWgzMnI1M3VxdXZ3Gg0KBXN0YWtlEgQxMDAw", + "memo":"memo" +} +``` + +## gRPC + +A user can query the interchain account module using gRPC endpoints. + +### Controller + +A user can query the controller submodule using gRPC endpoints. + +#### `InterchainAccount` + +The `InterchainAccount` endpoint allows users to query the controller submodule for the interchain account address for a given owner on a particular connection. + +```shell +ibc.applications.interchain_accounts.controller.v1.Query/InterchainAccount +``` + +Example: + +```shell +grpcurl -plaintext \ + -d '{"owner":"cosmos1..","connection_id":"connection-0"}' \ + localhost:9090 \ + ibc.applications.interchain_accounts.controller.v1.Query/InterchainAccount +``` + +#### `Params` + +The `Params` endpoint users to query the current controller submodule parameters. + +```shell +ibc.applications.interchain_accounts.controller.v1.Query/Params +``` + +Example: + +```shell +grpcurl -plaintext \ + localhost:9090 \ + ibc.applications.interchain_accounts.controller.v1.Query/Params +``` + +### Host + +A user can query the host submodule using gRPC endpoints. + +#### `Params` + +The `Params` endpoint users to query the current host submodule parameters. + +```shell +ibc.applications.interchain_accounts.host.v1.Query/Params +``` + +Example: + +```shell +grpcurl -plaintext \ + localhost:9090 \ + ibc.applications.interchain_accounts.host.v1.Query/Params +``` diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/09-active-channels.md b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/09-active-channels.md new file mode 100644 index 0000000..862082e --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/09-active-channels.md @@ -0,0 +1,45 @@ +--- +title: Active Channels +sidebar_label: Active Channels +sidebar_position: 9 +slug: /apps/interchain-accounts/active-channels +--- + +# Understanding Active Channels + +The Interchain Accounts module uses either [ORDERED or UNORDERED](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#ordering) channels. + +When using `ORDERED` channels, the order of transactions when sending packets from a controller to a host chain is maintained. + +When using `UNORDERED` channels, there is no guarantee that the order of transactions when sending packets from the controller to the host chain is maintained. If no ordering is specified in `MsgRegisterInterchainAccount`, then the default ordering for new ICA channels is `UNORDERED`. + +> A limitation when using ORDERED channels is that when a packet times out the channel will be closed. + +In the case of a channel closing, a controller chain needs to be able to regain access to the interchain account registered on this channel. `Active Channels` enable this functionality. + +When an Interchain Account is registered using `MsgRegisterInterchainAccount`, a new channel is created on a particular port. During the `OnChanOpenAck` and `OnChanOpenConfirm` steps (on controller & host chain respectively) the `Active Channel` for this interchain account is stored in state. + +It is possible to create a new channel using the same controller chain portID if the previously set `Active Channel` is now in a `CLOSED` state. This channel creation can be initialized programmatically by sending a new `MsgChannelOpenInit` message like so: + +```go +msg := channeltypes.NewMsgChannelOpenInit(portID, string(versionBytes), channeltypes.ORDERED, []string{connectionID}, icatypes.HostPortID, authtypes.NewModuleAddress(icatypes.ModuleName).String()) +handler := keeper.msgRouter.Handler(msg) +res, err := handler(ctx, msg) +if err != nil { + return err +} +``` + +Alternatively, any relayer operator may initiate a new channel handshake for this interchain account once the previously set `Active Channel` is in a `CLOSED` state. This is done by initiating the channel handshake on the controller chain using the same portID associated with the interchain account in question. + +It is important to note that once a channel has been opened for a given interchain account, new channels can not be opened for this account until the currently set `Active Channel` is set to `CLOSED`. + +## Future improvements + +Future versions of the ICS-27 protocol and the Interchain Accounts module will likely use a new channel type that provides ordering of packets without the channel closing in the event of a packet timing out, thus removing the need for `Active Channels` entirely. +The following is a list of issues which will provide the infrastructure to make this possible: + +- [IBC Channel Upgrades](https://github.com/cosmos/ibc-go/issues/1599) +- [Implement ORDERED_ALLOW_TIMEOUT logic in 04-channel](https://github.com/cosmos/ibc-go/issues/1661) +- [Add ORDERED_ALLOW_TIMEOUT as supported ordering in 03-connection](https://github.com/cosmos/ibc-go/issues/1662) +- [Allow ICA channels to be opened as ORDERED_ALLOW_TIMEOUT](https://github.com/cosmos/ibc-go/issues/1663) diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/10-legacy/01-auth-modules.md b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/10-legacy/01-auth-modules.md new file mode 100644 index 0000000..332e05d --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/10-legacy/01-auth-modules.md @@ -0,0 +1,268 @@ +--- +title: Authentication Modules +sidebar_label: Authentication Modules +sidebar_position: 1 +slug: /apps/interchain-accounts/legacy/auth-modules +--- + + +# Building an authentication module + +## Deprecation Notice + +**This document is deprecated and will be removed in future releases**. + +:::note Synopsis +Authentication modules play the role of the `Base Application` as described in [ICS-30 IBC Middleware](https://github.com/cosmos/ibc/tree/master/spec/app/ics-030-middleware), and enable application developers to perform custom logic when working with the Interchain Accounts controller API. +::: + +The controller submodule is used for account registration and packet sending. It executes only logic required of all controllers of interchain accounts. The type of authentication used to manage the interchain accounts remains unspecified. There may exist many different types of authentication which are desirable for different use cases. Thus the purpose of the authentication module is to wrap the controller submodule with custom authentication logic. + +In ibc-go, authentication modules are connected to the controller chain via a middleware stack. The controller submodule is implemented as [middleware](https://github.com/cosmos/ibc/tree/master/spec/app/ics-030-middleware) and the authentication module is connected to the controller submodule as the base application of the middleware stack. To implement an authentication module, the `IBCModule` interface must be fulfilled. By implementing the controller submodule as middleware, any amount of authentication modules can be created and connected to the controller submodule without writing redundant code. + +The authentication module must: + +- Authenticate interchain account owners. +- Track the associated interchain account address for an owner. +- Send packets on behalf of an owner (after authentication). + +## `IBCModule` implementation + +The following `IBCModule` callbacks must be implemented with appropriate custom logic: + +```go +// OnChanOpenInit implements the IBCModule interface +func (im IBCModule) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + // perform custom logic + + return version, nil +} + +// OnChanOpenAck implements the IBCModule interface +func (im IBCModule) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyVersion string, +) error { + // perform custom logic + + return nil +} + +// OnChanCloseConfirm implements the IBCModule interface +func (im IBCModule) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // perform custom logic + + return nil +} + +// OnAcknowledgementPacket implements the IBCModule interface +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + // perform custom logic + + return nil +} + +// OnTimeoutPacket implements the IBCModule interface. +func (im IBCModule) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + // perform custom logic + + return nil +} +``` + +The following functions must be defined to fulfill the `IBCModule` interface, but they will never be called by the controller submodule so they may error or panic. That is because in Interchain Accounts, the channel handshake is always initiated on the controller chain and packets are always sent to the host chain and never to the controller chain. + +```go +// OnChanOpenTry implements the IBCModule interface +func (im IBCModule) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + panic("UNIMPLEMENTED") +} + +// OnChanOpenConfirm implements the IBCModule interface +func (im IBCModule) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + panic("UNIMPLEMENTED") +} + +// OnChanCloseInit implements the IBCModule interface +func (im IBCModule) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + panic("UNIMPLEMENTED") +} + +// OnRecvPacket implements the IBCModule interface. A successful acknowledgement +// is returned if the packet data is successfully decoded and the receive application +// logic returns without error. +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) ibcexported.Acknowledgement { + panic("UNIMPLEMENTED") +} +``` + +## `OnAcknowledgementPacket` + +Controller chains will be able to access the acknowledgement written into the host chain state once a relayer relays the acknowledgement. +The acknowledgement bytes contain either the response of the execution of the message(s) on the host chain or an error. They will be passed to the auth module via the `OnAcknowledgementPacket` callback. Auth modules are expected to know how to decode the acknowledgement. + +If the controller chain is connected to a host chain using the host module on ibc-go, it may interpret the acknowledgement bytes as follows: + +Begin by unmarshaling the acknowledgement into `sdk.TxMsgData`: + +```go +var ack channeltypes.Acknowledgement +if err := channeltypes.SubModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { + return err +} + +txMsgData := &sdk.TxMsgData{} +if err := proto.Unmarshal(ack.GetResult(), txMsgData); err != nil { + return err +} +``` + +If the `txMsgData.Data` field is non nil, the host chain is using SDK version \<\= v0.45. +The auth module should interpret the `txMsgData.Data` as follows: + +```go +switch len(txMsgData.Data) { +case 0: + // see documentation below for SDK 0.46.x or greater +default: + for _, msgData := range txMsgData.Data { + if err := handler(msgData); err != nil { + return err + } + } +... +} +``` + +A handler will be needed to interpret what actions to perform based on the message type sent. +A router could be used, or more simply a switch statement. + +```go +func handler(msgData sdk.MsgData) error { +switch msgData.MsgType { +case sdk.MsgTypeURL(&banktypes.MsgSend{}): + msgResponse := &banktypes.MsgSendResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleBankSendMsg(msgResponse) + +case sdk.MsgTypeURL(&stakingtypes.MsgDelegate{}): + msgResponse := &stakingtypes.MsgDelegateResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleStakingDelegateMsg(msgResponse) + +case sdk.MsgTypeURL(&transfertypes.MsgTransfer{}): + msgResponse := &transfertypes.MsgTransferResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleIBCTransferMsg(msgResponse) + +default: + return +} +``` + +If the `txMsgData.Data` is empty, the host chain is using SDK version > v0.45. +The auth module should interpret the `txMsgData.Responses` as follows: + +```go +... +// switch statement from above +case 0: + for _, any := range txMsgData.MsgResponses { + if err := handleAny(any); err != nil { + return err + } + } +} +``` + +A handler will be needed to interpret what actions to perform based on the type URL of the Any. +A router could be used, or more simply a switch statement. +It may be possible to deduplicate logic between `handler` and `handleAny`. + +```go +func handleAny(any *codectypes.Any) error { +switch any.TypeURL { +case banktypes.MsgSend: + msgResponse, err := unpackBankMsgSendResponse(any) + if err != nil { + return err + } + + handleBankSendMsg(msgResponse) + +case stakingtypes.MsgDelegate: + msgResponse, err := unpackStakingDelegateResponse(any) + if err != nil { + return err + } + + handleStakingDelegateMsg(msgResponse) + + case transfertypes.MsgTransfer: + msgResponse, err := unpackIBCTransferMsgResponse(any) + if err != nil { + return err + } + + handleIBCTransferMsg(msgResponse) + +default: + return +} +``` + +## Integration into `app.go` file + +To integrate the authentication module into your chain, please follow the steps outlined in [`app.go` integration](02-integration.md#example-integration). diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/10-legacy/02-integration.md b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/10-legacy/02-integration.md new file mode 100644 index 0000000..4f71f7d --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/10-legacy/02-integration.md @@ -0,0 +1,196 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /apps/interchain-accounts/legacy/integration +--- + + +# Integration + +## Deprecation Notice + +**This document is deprecated and will be removed in future releases**. + +:::note Synopsis +Learn how to integrate Interchain Accounts host and controller functionality to your chain. The following document only applies for Cosmos SDK chains. +::: + +The Interchain Accounts module contains two submodules. Each submodule has its own IBC application. The Interchain Accounts module should be registered as an `AppModule` in the same way all SDK modules are registered on a chain, but each submodule should create its own `IBCModule` as necessary. A route should be added to the IBC router for each submodule which will be used. + +Chains who wish to support ICS-27 may elect to act as a host chain, a controller chain or both. Disabling host or controller functionality may be done statically by excluding the host or controller module entirely from the `app.go` file or it may be done dynamically by taking advantage of the on-chain parameters which enable or disable the host or controller submodules. + +Interchain Account authentication modules are the base application of a middleware stack. The controller submodule is the middleware in this stack. + +![ica-pre-v6.png](./images/ica-pre-v6.png) + +## Example integration + +```go +// app.go + +// Register the AppModule for the Interchain Accounts module and the authentication module +// Note: No `icaauth` exists, this must be substituted with an actual Interchain Accounts authentication module +ModuleBasics = module.NewBasicManager( + ... + ica.AppModuleBasic{}, + icaauth.AppModuleBasic{}, + ... +) + +... + +// Add module account permissions for the Interchain Accounts module +// Only necessary for host chain functionality +// Each Interchain Account created on the host chain is derived from the module account created +maccPerms = map[string][]string{ + ... + icatypes.ModuleName: nil, +} + +... + +// Add Interchain Accounts Keepers for each submodule used and the authentication module +// If a submodule is being statically disabled, the associated Keeper does not need to be added. +type App struct { + ... + + ICAControllerKeeper icacontrollerkeeper.Keeper + ICAHostKeeper icahostkeeper.Keeper + ICAAuthKeeper icaauthkeeper.Keeper + + ... +} + +... + +// Create store keys for each submodule Keeper and the authentication module +keys := sdk.NewKVStoreKeys( + ... + icacontrollertypes.StoreKey, + icahosttypes.StoreKey, + icaauthtypes.StoreKey, + ... +) + +... + + +... + +// Create the Keeper for each submodule +app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.MsgServiceRouter(), +) +app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.AccountKeeper, app.MsgServiceRouter(), +) + +// Create Interchain Accounts AppModule +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper) + +// Create your Interchain Accounts authentication module +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.ICAControllerKeeper) + +// ICA auth AppModule +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) + +// ICA auth IBC Module +icaAuthIBCModule := icaauth.NewIBCModule(app.ICAAuthKeeper) + +// Create controller IBC application stack and host IBC module as desired +icaControllerStack := icacontroller.NewIBCMiddlewareWithAuth(icaAuthIBCModule, app.ICAControllerKeeper) +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host and authentication routes +ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icahosttypes.SubModuleName, icaHostIBCModule). + AddRoute(icaauthtypes.ModuleName, icaControllerStack) // Note, the authentication module is routed to the top level of the middleware stack + +... + +// Register Interchain Accounts and authentication module AppModule's +app.moduleManager = module.NewManager( + ... + icaModule, + icaAuthModule, +) + +... + +// Add fee middleware to begin blocker logic +app.moduleManager.SetOrderBeginBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add fee middleware to end blocker logic +app.moduleManager.SetOrderEndBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add Interchain Accounts module InitGenesis logic +app.moduleManager.SetOrderInitGenesis( + ... + icatypes.ModuleName, + ... +) + +// initParamsKeeper init params keeper and its subspaces +func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey sdk.StoreKey) paramskeeper.Keeper { + ... + paramsKeeper.Subspace(icahosttypes.SubModuleName) + paramsKeeper.Subspace(icacontrollertypes.SubModuleName) + ... +``` + +## Using submodules exclusively + +As described above, the Interchain Accounts application module is structured to support the ability of exclusively enabling controller or host functionality. +This can be achieved by simply omitting either controller or host `Keeper` from the Interchain Accounts `NewAppModule` constructor function, and mounting only the desired submodule via the `IBCRouter`. +Alternatively, submodules can be enabled and disabled dynamically using [on-chain parameters](../06-parameters.md). + +The following snippets show basic examples of statically disabling submodules using `app.go`. + +### Disabling controller chain functionality + +```go +// Create Interchain Accounts AppModule omitting the controller keeper +icaModule := ica.NewAppModule(nil, &app.ICAHostKeeper) + +// Create host IBC Module +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host route +ibcRouter.AddRoute(icahosttypes.SubModuleName, icaHostIBCModule) +``` + +### Disabling host chain functionality + +```go +// Create Interchain Accounts AppModule omitting the host keeper +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, nil) + +// Create your Interchain Accounts authentication module, setting up the Keeper, AppModule and IBCModule appropriately +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.ICAControllerKeeper) +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) +icaAuthIBCModule := icaauth.NewIBCModule(app.ICAAuthKeeper) + +// Create controller IBC application stack +icaControllerStack := icacontroller.NewIBCMiddlewareWithAuth(icaAuthIBCModule, app.ICAControllerKeeper) + +// Register controller and authentication routes +ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icaauthtypes.ModuleName, icaControllerStack) // Note, the authentication module is routed to the top level of the middleware stack +``` diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/10-legacy/03-keeper-api.md b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/10-legacy/03-keeper-api.md new file mode 100644 index 0000000..8209477 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/10-legacy/03-keeper-api.md @@ -0,0 +1,123 @@ +--- +title: Keeper API +sidebar_label: Keeper API +sidebar_position: 3 +slug: /apps/interchain-accounts/legacy/keeper-api +--- + + +# Keeper API + +## Deprecation Notice + +**This document is deprecated and will be removed in future releases**. + +The controller submodule keeper exposes two legacy functions that allow respectively for custom authentication modules to register interchain accounts and send packets to the interchain account. + +## `RegisterInterchainAccount` + +The authentication module can begin registering interchain accounts by calling `RegisterInterchainAccount`: + +```go +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, connectionID, owner.String(), version, channeltypes.UNORDERED); err != nil { + return err +} + +return nil +``` + +The `version` argument is used to support ICS-29 fee middleware for relayer incentivization of ICS-27 packets. The `ordering` argument allows to specify the ordering of the channel that is created; if `NONE` is passed, then the default ordering will be `UNORDERED`. Consumers of the `RegisterInterchainAccount` are expected to build the appropriate JSON encoded version string themselves and pass it accordingly. If an empty string is passed in the `version` argument, then the version will be initialized to a default value in the `OnChanOpenInit` callback of the controller's handler, so that channel handshake can proceed. + +The following code snippet illustrates how to construct an appropriate interchain accounts `Metadata` and encode it as a JSON bytestring: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, controllerConnectionID, owner.String(), string(appVersion), channeltypes.UNORDERED); err != nil { + return err +} +``` + +Similarly, if the application stack is configured to route through ICS-29 fee middleware and a fee enabled channel is desired, construct the appropriate ICS-29 `Metadata` type: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +feeMetadata := feetypes.Metadata{ + AppVersion: string(appVersion), + FeeVersion: feetypes.Version, +} + +feeEnabledVersion, err := feetypes.ModuleCdc.MarshalJSON(&feeMetadata) +if err != nil { + return err +} + +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, controllerConnectionID, owner.String(), string(feeEnabledVersion), channeltypes.UNORDERED); err != nil { + return err +} +``` + +## `SendTx` + +The authentication module can attempt to send a packet by calling `SendTx`: + +```go +// Authenticate owner +// perform custom logic + +// Construct controller portID based on interchain account owner address +portID, err := icatypes.NewControllerPortID(owner.String()) +if err != nil { + return err +} + +// Obtain data to be sent to the host chain. +// In this example, the owner of the interchain account would like to send a bank MsgSend to the host chain. +// The appropriate serialization function should be called. The host chain must be able to deserialize the transaction. +// If the host chain is using the ibc-go host module, `SerializeCosmosTx` should be used. +msg := &banktypes.MsgSend{FromAddress: fromAddr, ToAddress: toAddr, Amount: amt} +data, err := icatypes.SerializeCosmosTx(keeper.cdc, []proto.Message{msg}) +if err != nil { + return err +} + +// Construct packet data +packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, +} + +// Obtain timeout timestamp +// An appropriate timeout timestamp must be determined based on the usage of the interchain account. +// If the packet times out, the channel will be closed requiring a new channel to be created. +timeoutTimestamp := obtainTimeoutTimestamp() + +// Send the interchain accounts packet, returning the packet sequence +seq, err = keeper.icaControllerKeeper.SendTx(ctx, portID, packetData, timeoutTimestamp) +``` + +The data within an `InterchainAccountPacketData` must be serialized using a format supported by the host chain. +If the host chain is using the ibc-go host chain submodule, `SerializeCosmosTx` should be used. If the `InterchainAccountPacketData.Data` is serialized using a format not supported by the host chain, the packet will not be successfully received. diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/10-legacy/_category_.json b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/10-legacy/_category_.json new file mode 100644 index 0000000..da34288 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/10-legacy/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Legacy", + "position": 10, + "link": null +} diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/10-legacy/images/ica-pre-v6.png b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/10-legacy/images/ica-pre-v6.png new file mode 100644 index 0000000..4529b23 Binary files /dev/null and b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/10-legacy/images/ica-pre-v6.png differ diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/_category_.json b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/_category_.json new file mode 100644 index 0000000..41e3ac2 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Interchain Accounts", + "position": 2, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/images/ica-v6.png b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/images/ica-v6.png new file mode 100644 index 0000000..abe3eba Binary files /dev/null and b/docs/versioned_docs/version-v10.1.x/02-apps/02-interchain-accounts/images/ica-v6.png differ diff --git a/docs/versioned_docs/version-v10.1.x/02-apps/_category_.json b/docs/versioned_docs/version-v10.1.x/02-apps/_category_.json new file mode 100644 index 0000000..83a389b --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/02-apps/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "IBC Application Modules", + "position": 2, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/01-overview.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/01-overview.md new file mode 100644 index 0000000..8545d22 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/01-overview.md @@ -0,0 +1,95 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /ibc/light-clients/overview +--- + +# Overview + +:::note Synopsis +Learn how to build IBC light client modules and fulfill the interfaces required to integrate with core IBC. +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../../01-ibc/01-overview.md) +- [IBC Transport, Authentication, and Ordering Layer - Clients](https://tutorials.cosmos.network/academy/3-ibc/4-clients.html) +- [ICS-002 Client Semantics](https://github.com/cosmos/ibc/tree/main/spec/core/ics-002-client-semantics) + +::: + +IBC uses light clients in order to provide trust-minimized interoperability between sovereign blockchains. Light clients operate under a strict set of rules which provide security guarantees for state updates and facilitate the ability to verify the state of a remote blockchain using merkle proofs. + +The following aims to provide a high level IBC light client module developer guide. Access to IBC light clients are gated by the core IBC `MsgServer` which utilizes the abstractions set by the `02-client` submodule to call into a light client module. A light client module developer is only required to implement a set of interfaces as defined in the `modules/core/exported` package of ibc-go. + +A light client module developer should be concerned with three main interfaces: + +- [`LightClientModule`](#lightclientmodule) a module which manages many light client instances of a certain type. +- [`ClientState`](#clientstate) encapsulates the light client implementation and its semantics. +- [`ConsensusState`](#consensusstate) tracks consensus data used for verification of client updates, misbehaviour detection and proof verification of counterparty state. +- [`ClientMessage`](#clientmessage) used for submitting block headers for client updates and submission of misbehaviour evidence using conflicting headers. + +Throughout this guide the `07-tendermint` light client module may be referred to as a reference example. + +## Concepts and vocabulary + +### `LightClientModule` + +`LightClientModule` is an interface defined by core IBC which allows for modular light client implementations. All light client implementations *must* implement the [`LightClientModule` interface](https://github.com/cosmos/ibc-go/blob/501a8462345da099144efe91d495bfcfa18d760d/modules/core/exported/client.go#L51) so that core IBC may redirect calls to the light client module. + +For example a light client module may need to: + +- create clients +- update clients +- recover and upgrade clients +- verify membership and non-membership + +The methods which make up this interface are detailed at a more granular level in the [`LightClientModule` section of this guide](02-light-client-module.md). + +Please refer to the `07-tendermint`'s [`LightClientModule` definition](https://github.com/cosmos/ibc-go/blob/501a8462345da099144efe91d495bfcfa18d760d/modules/light-clients/07-tendermint/light_client_module.go#L17) for more information. + +### `ClientState` + +`ClientState` is a term used to define the data structure which encapsulates opaque light client state. The `ClientState` contains all the information needed to verify a `ClientMessage` and perform membership and non-membership proof verification of counterparty state. This includes properties that refer to the remote state machine, the light client type and the specific light client instance. + +For example: + +- Constraints used for client updates. +- Constraints used for misbehaviour detection. +- Constraints used for state verification. +- Constraints used for client upgrades. + +The `ClientState` type maintained within the light client module *must* implement the [`ClientState`](https://github.com/cosmos/ibc-go/tree/02-client-refactor-beta1/modules/core/exported/client.go#L36) interface defined in `core/modules/exported/client.go`. +The methods which make up this interface are detailed at a more granular level in the [`ClientState` section of this guide](03-client-state.md). + +Please refer to the `07-tendermint` light client module's [`ClientState` definition](https://github.com/cosmos/ibc-go/tree/02-client-refactor-beta1/proto/ibc/lightclients/tendermint/v1/tendermint.proto#L18) containing information such as chain ID, status, latest height, unbonding period and proof specifications. + +### `ConsensusState` + +`ConsensusState` is a term used to define the data structure which encapsulates consensus data at a particular point in time, i.e. a unique height or sequence number of a state machine. There must exist a single trusted `ConsensusState` for each height. `ConsensusState` generally contains a trusted root, validator set information and timestamp. + +For example, the `ConsensusState` of the `07-tendermint` light client module defines a trusted root which is used by the `ClientState` to perform verification of membership and non-membership commitment proofs, as well as the next validator set hash used for verifying headers can be trusted in client updates. + +The `ConsensusState` type maintained within the light client module *must* implement the [`ConsensusState`](https://github.com/cosmos/ibc-go/tree/02-client-refactor-beta1/modules/core/exported/client.go#L134) interface defined in `modules/core/exported/client.go`. +The methods which make up this interface are detailed at a more granular level in the [`ConsensusState` section of this guide](04-consensus-state.md). + +### `Height` + +`Height` defines a monotonically increasing sequence number which provides ordering of consensus state data persisted through client updates. +IBC light client module developers are expected to use the [concrete type](https://github.com/cosmos/ibc-go/tree/02-client-refactor-beta1/proto/ibc/core/client/v1/client.proto#L89) provided by the `02-client` submodule. This implements the expectations required by the [`Height`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L156) interface defined in `modules/core/exported/client.go`. + +### `ClientMessage` + +`ClientMessage` refers to the interface type [`ClientMessage`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L147) used for performing updates to a `ClientState` stored on chain. +This may be any concrete type which produces a change in state to the IBC client when verified. + +The following are considered as valid update scenarios: + +- A block header which when verified inserts a new `ConsensusState` at a unique height. +- A batch of block headers which when verified inserts `N` `ConsensusState` instances for `N` unique heights. +- Evidence of misbehaviour provided by two conflicting block headers. + +Learn more in the [Handling update and misbehaviour](05-updates-and-misbehaviour.md) section. diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/02-light-client-module.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/02-light-client-module.md new file mode 100644 index 0000000..41f53dd --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/02-light-client-module.md @@ -0,0 +1,76 @@ +--- +title: Light Client Module interface +sidebar_label: Light Client Module interface +sidebar_position: 2 +slug: /ibc/light-clients/light-client-module +--- + + +# Implementing the `LightClientModule` interface + +## `Status` method + +`Status` must return the status of the client. + +- An `Active` status indicates that clients are allowed to process packets. +- A `Frozen` status indicates that misbehaviour was detected in the counterparty chain and the client is not allowed to be used. +- An `Expired` status indicates that a client is not allowed to be used because it was not updated for longer than the trusting period. +- An `Unknown` status indicates that there was an error in determining the status of a client. + +All possible `Status` types can be found [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L22-L32). + +This field is returned in the response of the gRPC [`ibc.core.client.v1.Query/ClientStatus`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/types/query.pb.go#L665) endpoint. + +## `TimestampAtHeight` method + +`TimestampAtHeight` must return the timestamp for the consensus state associated with the provided height. +This value is used to facilitate timeouts by checking the packet timeout timestamp against the returned value. + +## `LatestHeight` method + +`LatestHeight` should return the latest block height that the client state represents. + +## `Initialize` method + +Clients must validate the initial consensus state, and set the initial client state and consensus state in the provided client store. +Clients may also store any necessary client-specific metadata. + +`Initialize` is called when a [client is created](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/keeper/client.go#L30). + +## `UpdateState` method + +`UpdateState` updates and stores as necessary any associated information for an IBC client, such as the `ClientState` and corresponding `ConsensusState`. See section [`UpdateState`](05-updates-and-misbehaviour.md#updatestate) for more information. + +## `UpdateStateOnMisbehaviour` method + +`UpdateStateOnMisbehaviour` should perform appropriate state changes on a client state given that misbehaviour has been detected and verified. See section [`UpdateStateOnMisbehaviour`](05-updates-and-misbehaviour.md#updatestateonmisbehaviour) for more information. + +## `VerifyMembership` method + +`VerifyMembership` must verify the existence of a value at a given commitment path at the specified height. For more information about membership proofs +see the [Existence and non-existence proofs section](07-proofs.md). + +## `VerifyNonMembership` method + +`VerifyNonMembership` must verify the absence of a value at a given commitment path at a specified height. For more information about non-membership proofs +see the [Existence and non-existence proofs section](07-proofs.md). + +## `VerifyClientMessage` method + +`VerifyClientMessage` must verify a `ClientMessage`. A `ClientMessage` could be a `Header`, `Misbehaviour`, or batch update. +It must handle each type of `ClientMessage` appropriately. Calls to `CheckForMisbehaviour`, `UpdateState`, and `UpdateStateOnMisbehaviour` +will assume that the content of the `ClientMessage` has been verified and can be trusted. An error should be returned +if the ClientMessage fails to verify. See section [`VerifyClientMessage`](05-updates-and-misbehaviour.md#verifyclientmessage) for more information. + +## `CheckForMisbehaviour` method + +Checks for evidence of a misbehaviour in `Header` or `Misbehaviour` type. It assumes the `ClientMessage` +has already been verified. See section [`CheckForMisbehaviour`](05-updates-and-misbehaviour.md#checkformisbehaviour) for more information. + +## `RecoverClient` method + +`RecoverClient` is used to recover an expired or frozen client by updating the client with the state of a substitute client. The method must verify that the provided substitute may be used to update the subject client. See section [Implementing `RecoverClient`](./08-proposals.md#implementing-recoverclient) for more information. + +## `VerifyUpgradeAndUpdateState` method + +`VerifyUpgradeAndUpdateState` provides a path to upgrading clients given an upgraded `ClientState`, upgraded `ConsensusState` and proofs for each. See section [Implementing `VerifyUpgradeAndUpdateState`](./06-upgrades.md#implementing-verifyupgradeandupdatestate) for more information. diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/03-client-state.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/03-client-state.md new file mode 100644 index 0000000..f35102e --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/03-client-state.md @@ -0,0 +1,21 @@ +--- +title: Client State interface +sidebar_label: Client State interface +sidebar_position: 3 +slug: /ibc/light-clients/client-state +--- + + +# Implementing the `ClientState` interface + +Learn how to implement the [`ClientState`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L36) interface. + +## `ClientType` method + +`ClientType` should return a unique string identifier of the light client. This will be used when generating a client identifier. +The format is created as follows: `{client-type}-{N}` where `{N}` is the unique global nonce associated with a specific client (e.g `07-tendermint-0`). + +## `Validate` method + +`Validate` should validate every client state field and should return an error if any value is invalid. The light client +implementer is in charge of determining which checks are required. See the [Tendermint light client implementation](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/client_state.go#L111) as a reference. diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/04-consensus-state.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/04-consensus-state.md new file mode 100644 index 0000000..c470cd3 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/04-consensus-state.md @@ -0,0 +1,27 @@ +--- +title: Consensus State interface +sidebar_label: Consensus State interface +sidebar_position: 4 +slug: /ibc/light-clients/consensus-state +--- + + +# Implementing the `ConsensusState` interface + +A `ConsensusState` is the snapshot of the counterparty chain, that an IBC client uses to verify proofs (e.g. a block). + +The further development of multiple types of IBC light clients and the difficulties presented by this generalization problem (see [ADR-006](https://github.com/cosmos/ibc-go/blob/main/docs/architecture/adr-006-02-client-refactor.md) for more information about this historical context) led to the design decision of each client keeping track of and set its own `ClientState` and `ConsensusState`, as well as the simplification of client `ConsensusState` updates through the generalized `ClientMessage` interface. + +The below [`ConsensusState`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L133) interface is a generalized interface for the types of information a `ConsensusState` could contain. For a reference `ConsensusState` implementation, please see the [Tendermint light client `ConsensusState`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/consensus_state.go). + +## `ClientType` method + +This is the type of client consensus. It should be the same as the `ClientType` return value for the [corresponding `ClientState` implementation](03-client-state.md). + +## `GetTimestamp` method + +`GetTimestamp` should return the timestamp (in nanoseconds) of the consensus state snapshot. This function has been deprecated and will be removed in a future release. + +## `ValidateBasic` method + +`ValidateBasic` should validate every consensus state field and should return an error if any value is invalid. The light client implementer is in charge of determining which checks are required. diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/05-updates-and-misbehaviour.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/05-updates-and-misbehaviour.md new file mode 100644 index 0000000..fb25e37 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/05-updates-and-misbehaviour.md @@ -0,0 +1,104 @@ +--- +title: Handling Updates and Misbehaviour +sidebar_label: Handling Updates and Misbehaviour +sidebar_position: 5 +slug: /ibc/light-clients/updates-and-misbehaviour +--- + + +# Handling `ClientMessage`s: updates and misbehaviour + +As mentioned before in the documentation about [implementing the `ConsensusState` interface](04-consensus-state.md), [`ClientMessage`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L147) is an interface used to update an IBC client. This update may be performed by: + +- a single header, +- a batch of headers, +- evidence of misbehaviour, +- or any type which when verified produces a change to the consensus state of the IBC client. + +This interface has been purposefully kept generic in order to give the maximum amount of flexibility to the light client implementer. + +## Implementing the `ClientMessage` interface + +Find the `ClientMessage` interface in `modules/core/exported`: + +```go +type ClientMessage interface { + proto.Message + + ClientType() string + ValidateBasic() error +} +``` + +The `ClientMessage` will be passed to the client to be used in [`UpdateClient`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/keeper/client.go#L48), which retrieves the `LightClientModule` by client type (parsed from the client ID available in `MsgUpdateClient`). This `LightClientModule` implements the [`LightClientModule` interface](02-light-client-module.md) for its specific consenus type (e.g. Tendermint). + +`UpdateClient` will then handle a number of cases including misbehaviour and/or updating the consensus state, utilizing the specific methods defined in the relevant `LightClientModule`. + +```go +VerifyClientMessage(ctx sdk.Context, clientID string, clientMsg ClientMessage) error +CheckForMisbehaviour(ctx sdk.Context, clientID string, clientMsg ClientMessage) bool +UpdateStateOnMisbehaviour(ctx sdk.Context, clientID string, clientMsg ClientMessage) +UpdateState(ctx sdk.Context, clientID string, clientMsg ClientMessage) []Height +``` + +## Handling updates and misbehaviour + +The functions for handling updates to a light client and evidence of misbehaviour are all found in the [`LightClientModule`](https://github.com/cosmos/ibc-go/blob/501a8462345da099144efe91d495bfcfa18d760d/modules/core/exported/client.go#L51) interface, and will be discussed below. + +> It is important to note that `Misbehaviour` in this particular context is referring to misbehaviour on the chain level intended to fool the light client. This will be defined by each light client. + +## `VerifyClientMessage` + +`VerifyClientMessage` must verify a `ClientMessage`. A `ClientMessage` could be a `Header`, `Misbehaviour`, or batch update. To understand how to implement a `ClientMessage`, please refer to the [Implementing the `ClientMessage` interface](#implementing-the-clientmessage-interface) section. + +It must handle each type of `ClientMessage` appropriately. Calls to `CheckForMisbehaviour`, `UpdateState`, and `UpdateStateOnMisbehaviour` will assume that the content of the `ClientMessage` has been verified and can be trusted. An error should be returned if the `ClientMessage` fails to verify. + +For an example of a `VerifyClientMessage` implementation, please check the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/76730ff030b52a351096ee941b7e4da44af9f059/modules/light-clients/07-tendermint/update.go#L23). + +## `CheckForMisbehaviour` + +Checks for evidence of a misbehaviour in `Header` or `Misbehaviour` type. It assumes the `ClientMessage` has already been verified. + +For an example of a `CheckForMisbehaviour` implementation, please check the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/76730ff030b52a351096ee941b7e4da44af9f059/modules/light-clients/07-tendermint/misbehaviour_handle.go#L22). + +> The Tendermint light client [defines `Misbehaviour`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/misbehaviour.go) as two different types of situations: a situation where two conflicting `Header`s with the same height have been submitted to update a client's `ConsensusState` within the same trusting period, or that the two conflicting `Header`s have been submitted at different heights but the consensus states are not in the correct monotonic time ordering (BFT time violation). More explicitly, updating to a new height must have a timestamp greater than the previous consensus state, or, if inserting a consensus at a past height, then time must be less than those heights which come after and greater than heights which come before. + +## `UpdateStateOnMisbehaviour` + +`UpdateStateOnMisbehaviour` should perform appropriate state changes on a client state given that misbehaviour has been detected and verified. This method should only be called when misbehaviour is detected, as it does not perform any misbehaviour checks. Notably, it should freeze the client so that calling the `Status` function on the associated client state no longer returns `Active`. + +For an example of a `UpdateStateOnMisbehaviour` implementation, please check the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/76730ff030b52a351096ee941b7e4da44af9f059/modules/light-clients/07-tendermint/update.go#L202). + +## `UpdateState` + +`UpdateState` updates and stores as necessary any associated information for an IBC client, such as the `ClientState` and corresponding `ConsensusState`. It should perform a no-op on duplicate updates. + +It assumes the `ClientMessage` has already been verified. + +For an example of a `UpdateState` implementation, please check the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/76730ff030b52a351096ee941b7e4da44af9f059/modules/light-clients/07-tendermint/update.go#L134). + +## Putting it all together + +The `02-client` `Keeper` module in ibc-go offers a reference as to how these functions will be used to [update the client](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/keeper/client.go#L48). + +```go +clientModule, found := k.router.GetRoute(clientID) +if !found { + return errorsmod.Wrap(types.ErrRouteNotFound, clientID) +} + +if err := clientModule.VerifyClientMessage(ctx, clientID, clientMsg); err != nil { + return err +} + +foundMisbehaviour := clientModule.CheckForMisbehaviour(ctx, clientID, clientMsg) +if foundMisbehaviour { + clientModule.UpdateStateOnMisbehaviour(ctx, clientID, clientMsg) + // emit misbehaviour event + return +} + +clientModule.UpdateState(ctx, clientID, clientMsg) // expects no-op on duplicate header +// emit update event +return +``` diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/06-upgrades.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/06-upgrades.md new file mode 100644 index 0000000..7816509 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/06-upgrades.md @@ -0,0 +1,62 @@ +--- +title: Handling Upgrades +sidebar_label: Handling Upgrades +sidebar_position: 6 +slug: /ibc/light-clients/upgrades +--- + + +# Handling upgrades + +It is vital that high-value IBC clients can upgrade along with their underlying chains to avoid disruption to the IBC ecosystem. Thus, IBC client developers will want to implement upgrade functionality to enable clients to maintain connections and channels even across chain upgrades. + +## Implementing `VerifyUpgradeAndUpdateState` + +The IBC protocol allows client implementations to provide a path to upgrading clients given the upgraded `ClientState`, upgraded `ConsensusState` and proofs for each. This path is provided in the `VerifyUpgradeAndUpdateState` method: + +```go +// NOTE: proof heights are not included as upgrade to a new revision is expected to pass only on the last +// height committed by the current revision. Clients are responsible for ensuring that the planned last +// height of the current revision is somehow encoded in the proof verification process. +// This is to ensure that no premature upgrades occur, since upgrade plans committed to by the counterparty +// may be cancelled or modified before the last planned height. +// If the upgrade is verified, the upgraded client and consensus states must be set in the client store. +func (l LightClientModule) VerifyUpgradeAndUpdateState( + ctx sdk.Context, + clientID string, + newClient []byte, + newConsState []byte, + upgradeClientProof, + upgradeConsensusStateProof []byte, +) error +``` + +> Please refer to the [Tendermint light client implementation](https://github.com/cosmos/ibc-go/blob/47162061bcbfe74df791161059715a635e31c604/modules/light-clients/07-tendermint/light_client_module.go#L257) as an example for implementation. + +It is important to note that light clients **must** handle all management of client and consensus states including the setting of updated `ClientState` and `ConsensusState` in the client store. This can include verifying that the submitted upgraded `ClientState` is of a valid `ClientState` type, that the height of the upgraded client is not greater than the height of the current client (in order to preserve BFT monotonic time), or that certain parameters which should not be changed have not been altered in the upgraded `ClientState`. + +Developers must ensure that the `MsgUpgradeClient` does not pass until the last height of the old chain has been committed, and after the chain upgrades, the `MsgUpgradeClient` should pass once and only once on all counterparty clients. + +### Upgrade path + +Clients should have **prior knowledge of the merkle path** that the upgraded client and upgraded consensus states will use. The height at which the upgrade has occurred should also be encoded in the proof. + +> The Tendermint client implementation accomplishes this by including an `UpgradePath` in the `ClientState` itself, which is used along with the upgrade height to construct the merkle path under which the client state and consensus state are committed. + +## Chain specific vs client specific client parameters + +Developers should maintain the distinction between client parameters that are uniform across every valid light client of a chain (chain-chosen parameters), and client parameters that are customizable by each individual client (client-chosen parameters). + +When upgrading a client, developers must ensure that the new client adopts all of the new client parameters that must be uniform across every valid light client of a chain (chain-chosen parameters), while maintaining the client parameters that are customizable by each individual client (client-chosen parameters) from the previous version of the client. + +## Security + +Upgrades must adhere to the IBC Security Model. IBC does not rely on the assumption of honest relayers for correctness. Thus users should not have to rely on relayers to maintain client correctness and security (though honest relayers must exist to maintain relayer liveness). While relayers may choose any set of client parameters while creating a new `ClientState`, this still holds under the security model since users can always choose a relayer-created client that suits their security and correctness needs or create a client with their desired parameters if no such client exists. + +However, when upgrading an existing client, one must keep in mind that there are already many users who depend on this client's particular parameters. **We cannot give the upgrading relayer free choice over these parameters once they have already been chosen. This would violate the security model** since users who rely on the client would have to rely on the upgrading relayer to maintain the same level of security. + +Thus, developers must make sure that their upgrade mechanism allows clients to upgrade the chain-specified parameters whenever a chain upgrade changes these parameters (examples in the Tendermint client include `UnbondingPeriod`, `TrustingPeriod`, `ChainID`, `UpgradePath`, etc), while ensuring that the relayer submitting the `MsgUpgradeClient` cannot alter the client-chosen parameters that the users are relying upon (examples in Tendermint client include `TrustLevel`, `MaxClockDrift`, etc). + +### Document potential client parameter conflicts during upgrades + +Counterparty clients can upgrade securely by using all of the chain-chosen parameters from the chain-committed `UpgradedClient` and preserving all of the old client-chosen parameters. This enables chains to securely upgrade without relying on an honest relayer, however it can in some cases lead to an invalid final `ClientState` if the new chain-chosen parameters clash with the old client-chosen parameter. This can happen in the Tendermint client case if the upgrading chain lowers the `UnbondingPeriod` (chain-chosen) to a duration below that of a counterparty client's `TrustingPeriod` (client-chosen). Such cases should be clearly documented by developers, so that chains know which upgrades should be avoided to prevent this problem. The final upgraded client should also be validated in `VerifyUpgradeAndUpdateState` before returning to ensure that the client does not upgrade to an invalid `ClientState`. diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/07-proofs.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/07-proofs.md new file mode 100644 index 0000000..60fcc53 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/07-proofs.md @@ -0,0 +1,71 @@ +--- +title: Existence/Non-Existence Proofs +sidebar_label: Existence/Non-Existence Proofs +sidebar_position: 7 +slug: /ibc/light-clients/proofs +--- + + +# Existence and non-existence proofs + +IBC uses merkle proofs in order to verify the state of a remote counterparty state machine given a trusted root, and [ICS-23](https://github.com/cosmos/ics23/tree/master/go) is a general approach for verifying merkle trees which is used in ibc-go. + +Currently, all Cosmos SDK modules contain their own stores, which maintain the state of the application module in an IAVL (immutable AVL) binary merkle tree format. Specifically with regard to IBC, core IBC maintains its own IAVL store, and IBC apps (e.g. transfer) maintain their own dedicated stores. The Cosmos SDK multistore therefore creates a simple merkle tree of all of these IAVL trees, and from each of these individual IAVL tree root hashes it derives a root hash for the application state tree as a whole (the `AppHash`). + +For the purposes of ibc-go, there are two types of proofs which are important: existence and non-existence proofs, terms which have been used interchangeably with membership and non-membership proofs. For the purposes of this guide, we will stick with "existence" and "non-existence". + +## Existence proofs + +Existence proofs are used in IBC transactions which involve verification of counterparty state for transactions which will result in the writing of provable state. For example, this includes verification of IBC store state for handshakes and packets. + +Put simply, existence proofs prove that a particular key and value exists in the tree. Under the hood, an IBC existence proof is comprised of two proofs: an IAVL proof that the key exists in IBC store/IBC root hash, and a proof that the IBC root hash exists in the multistore root hash. + +## Non-existence proofs + +Non-existence proofs verify the absence of data stored within counterparty state and are used to prove that a key does NOT exist in state. As stated above, these types of proofs can be used to timeout packets by proving that the counterparty has not written a packet receipt into the store, meaning that a token transfer has NOT successfully occurred. + +Some trees (e.g. SMT) may have a sentinel empty child for non-existent keys. In this case, the ICS-23 proof spec should include this `EmptyChild` so that ICS-23 handles the non-existence proof correctly. + +In some cases, there is a necessity to "mock" non-existence proofs if the counterparty does not have ability to prove absence. Since the verification method is designed to give complete control to client implementations, clients can support chains that do not provide absence proofs by verifying the existence of a non-empty sentinel `ABSENCE` value. In these special cases, the proof provided will be an ICS-23 `Existence` proof, and the client will verify that the `ABSENCE` value is stored under the given path for the given height. + +## State verification methods: `VerifyMembership` and `VerifyNonMembership` + +The state verification functions for all IBC data types have been consolidated into two generic methods, `VerifyMembership` and `VerifyNonMembership`. + +From the [`LightClientModule` interface definition](https://github.com/cosmos/ibc-go/blob/main/modules/core/exported/client.go#L56), we find: + +```go +// VerifyMembership is a generic proof verification method which verifies +// a proof of the existence of a value at a given CommitmentPath at the +// specified height. The caller is expected to construct the full CommitmentPath +// from a CommitmentPrefix and a standardized path (as defined in ICS 24). +VerifyMembership( + ctx sdk.Context, + clientID string, + height Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path Path, + value []byte, +) error + +// VerifyNonMembership is a generic proof verification method which verifies +// the absence of a given CommitmentPath at a specified height. The caller is +// expected to construct the full CommitmentPath from a CommitmentPrefix and +// a standardized path (as defined in ICS 24). +VerifyNonMembership( + ctx sdk.Context, + clientStore sdk.KVStore, + cdc codec.BinaryCodec, + height Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path Path, +) error +``` + +Both are expected to be provided with a standardised key path, `exported.Path`, as defined in [ICS-24 host requirements](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements). Membership verification requires callers to provide the value marshalled as `[]byte`. Delay period values should be zero for non-packet processing verification. A zero proof height is now allowed by core IBC and may be passed into `VerifyMembership` and `VerifyNonMembership`. Light clients are responsible for returning an error if a zero proof height is invalid behaviour. + +Please refer to the [ICS-23 implementation](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/23-commitment/types/merkle.go#L131-L205) for a concrete example. diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/08-proposals.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/08-proposals.md new file mode 100644 index 0000000..38bdf1e --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/08-proposals.md @@ -0,0 +1,36 @@ +--- +title: Handling Proposals +sidebar_label: Handling Proposals +sidebar_position: 8 +slug: /ibc/light-clients/proposals +--- + + +# Handling proposals + +It is possible to update the client with the state of the substitute client through a governance proposal. This type of governance proposal is typically used to recover an expired or frozen client, as it can recover the entire state and therefore all existing channels built on top of the client. `RecoverClient` should be implemented to handle the proposal. + +## Implementing `RecoverClient` + +In the [`LightClientModule` interface](https://github.com/cosmos/ibc-go/blob/501a8462345da099144efe91d495bfcfa18d760d/modules/core/exported/client.go#L51), we find: + +```go +// RecoverClient must verify that the provided substitute +// may be used to update the subject client. The light client +// must set the updated client and consensus states within +// the clientStore for the subject client. +RecoverClient( + ctx sdk.Context, + clientID, + substituteClientID string, +) error +``` + +Prior to updating, this function must verify that: + +- the substitute client is the same type as the subject client. For a reference implementation, please see the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/47162061bcbfe74df791161059715a635e31c604/modules/light-clients/07-tendermint/proposal_handle.go#L34). +- the provided substitute may be used to update the subject client. This may mean that certain parameters must remain unaltered. For example, a [valid substitute Tendermint light client](https://github.com/cosmos/ibc-go/blob/47162061bcbfe74df791161059715a635e31c604/modules/light-clients/07-tendermint/proposal_handle.go#L86) must NOT change the chain ID, trust level, max clock drift, unbonding period, proof specs or upgrade path. Please note that `AllowUpdateAfterMisbehaviour` and `AllowUpdateAfterExpiry` have been deprecated (see ADR 026 for more information). + +After these checks are performed, the function must [set the updated client and consensus states](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/proposal_handle.go#L77) within the client store for the subject client. + +Please refer to the [Tendermint light client implementation](https://github.com/cosmos/ibc-go/blob/47162061bcbfe74df791161059715a635e31c604/modules/light-clients/07-tendermint/proposal_handle.go#L79) for reference. diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/09-setup.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/09-setup.md new file mode 100644 index 0000000..61e5e74 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/09-setup.md @@ -0,0 +1,135 @@ +--- +title: Setup +sidebar_label: Setup +sidebar_position: 9 +slug: /ibc/light-clients/setup +--- + + +# Setup + +:::note Synopsis +Learn how to configure light client modules and create clients using core IBC and the `02-client` submodule. +::: + +A last step to finish the development of the light client, is to implement the `AppModuleBasic` interface to allow it to be added to the chain's `app.go` alongside other light client types the chain enables. + +Finally, a succinct rundown is given of the remaining steps to make the light client operational, getting the light client type passed through governance and creating the clients. + +## Configuring a light client module + +An IBC light client module must implement the [`AppModuleBasic`](https://github.com/cosmos/cosmos-sdk/blob/main/types/module/module.go#L50) interface in order to register its concrete types against the core IBC interfaces defined in `modules/core/exported`. This is accomplished via the `RegisterInterfaces` method which provides the light client module with the opportunity to register codec types using the chain's `InterfaceRegistry`. Please refer to the [`07-tendermint` codec registration](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/codec.go#L11). + +The `AppModuleBasic` interface may also be leveraged to install custom CLI handlers for light client module users. Light client modules can safely no-op for interface methods which it does not wish to implement. + +Please refer to the [core IBC documentation](../../01-ibc/02-integration.md#integrating-light-clients) for how to configure additional light client modules alongside `07-tendermint` in `app.go`. + +See below for an example of the `07-tendermint` implementation of `AppModuleBasic`. + +```go +var _ module.AppModuleBasic = AppModuleBasic{} + +// AppModuleBasic defines the basic application module used by the tendermint light client. +// Only the RegisterInterfaces function needs to be implemented. All other function perform +// a no-op. +type AppModuleBasic struct{} + +// Name returns the tendermint module name. +func (AppModuleBasic) Name() string { + return ModuleName +} + +// RegisterLegacyAminoCodec performs a no-op. The Tendermint client does not support amino. +func (AppModuleBasic) RegisterLegacyAminoCodec(*codec.LegacyAmino) {} + +// RegisterInterfaces registers module concrete types into protobuf Any. This allows core IBC +// to unmarshal tendermint light client types. +func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) { + RegisterInterfaces(registry) +} + +// DefaultGenesis performs a no-op. Genesis is not supported for the tendermint light client. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return nil +} + +// ValidateGenesis performs a no-op. Genesis is not supported for the tendermint light client. +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { + return nil +} + +// RegisterGRPCGatewayRoutes performs a no-op. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {} + +// GetTxCmd performs a no-op. Please see the 02-client cli commands. +func (AppModuleBasic) GetTxCmd() *cobra.Command { + return nil +} + +// GetQueryCmd performs a no-op. Please see the 02-client cli commands. +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return nil +} +``` + +## Creating clients + +A client is created by executing a new `MsgCreateClient` transaction composed with a valid `ClientState` and initial `ConsensusState` encoded as protobuf `Any`s. +Generally, this is performed by an off-chain process known as an [IBC relayer](https://github.com/cosmos/ibc/tree/main/spec/relayer/ics-018-relayer-algorithms) however, this is not a strict requirement. + +See below for a list of IBC relayer implementations: + +- [cosmos/relayer](https://github.com/cosmos/relayer) +- [informalsystems/hermes](https://github.com/informalsystems/hermes) +- [confio/ts-relayer](https://github.com/confio/ts-relayer) + +Stateless checks are performed within the [`ValidateBasic`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/types/msgs.go#L48) method of `MsgCreateClient`. + +```protobuf +// MsgCreateClient defines a message to create an IBC client +message MsgCreateClient { + option (gogoproto.goproto_getters) = false; + + // light client state + google.protobuf.Any client_state = 1 [(gogoproto.moretags) = "yaml:\"client_state\""]; + // consensus state associated with the client that corresponds to a given + // height. + google.protobuf.Any consensus_state = 2 [(gogoproto.moretags) = "yaml:\"consensus_state\""]; + // signer address + string signer = 3; +} +``` + +Leveraging protobuf `Any` encoding allows core IBC to [unpack](https://github.com/cosmos/ibc-go/blob/47162061bcbfe74df791161059715a635e31c604/modules/core/keeper/msg_server.go#L38) the `ClientState` into its respective interface type registered previously using the light client module's `RegisterInterfaces` method. + +Within the `02-client` submodule, the [`ClientState` is then initialized](https://github.com/cosmos/ibc-go/blob/47162061bcbfe74df791161059715a635e31c604/modules/core/02-client/keeper/client.go#L40-L42) with its own isolated key-value store, namespaced using a unique client identifier. + +In order to successfully create an IBC client using a new client type, it [must be supported](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/keeper/client.go#L19-L25). Light client support in IBC is gated by on-chain governance. The allow list may be updated by submitting a new governance proposal to update the `02-client` parameter `AllowedClients`. + +See below for example: + +```shell +%s tx gov submit-proposal --from +``` + +where `proposal.json` contains: + +```json +{ + "title": "IBC Clients Param Change", + "summary": "Update allowed clients", + "messages": [ + { + "@type": "/ibc.core.client.v1.MsgUpdateParams", + "signer": "cosmos1...", // The gov module account address + "params": { + "allowed_clients": ["06-solomachine", "07-tendermint", "0x-new-client"] + } + } + ], + "metadata": "AQ==", + "deposit": "100stake" +} +``` + +If the `AllowedClients` list contains a single element that is equal to the wildcard `"*"`, then all client types are allowed and it is thus not necessary to submit a governance proposal to update the parameter. diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/_category_.json b/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/_category_.json new file mode 100644 index 0000000..e1c4f32 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/01-developer-guide/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Developer Guide", + "position": 1, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/02-localhost/01-overview.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/02-localhost/01-overview.md new file mode 100644 index 0000000..bae284d --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/02-localhost/01-overview.md @@ -0,0 +1,56 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /ibc/light-clients/localhost/overview +--- + + +# `09-localhost` + +## Overview + +:::note Synopsis +Learn about the 09-localhost light client module. +::: + +The 09-localhost light client module implements a stateless localhost loopback client with the ability to send and +receive IBC packets to and from the same state machine. + +### Context + +In a multichain environment, application developers will be used to developing cross-chain applications through IBC. +From their point of view, whether or not they are interacting with multiple modules on the same chain or on different +chains should not matter. The localhost client module enables a unified interface to interact with different +applications on a single chain, using the familiar IBC application layer semantics. + +### Implementation + +There exists a localhost light client module which can be invoked with the client identifier `09-localhost`. The light +client is stateless, so the `ClientState` is constructed on demand when required. + +To supplement this, a [sentinel `ConnectionEnd` is stored in core IBC](04-connection.md) state with the connection +identifier `connection-localhost`. This enables IBC applications to create channels directly on top of the sentinel +connection which leverage the 09-localhost loopback functionality. + +[State verification](05-state-verification.md) for channel state in handshakes or processing packets is reduced in +complexity, the `09-localhost` client can simply compare bytes stored under the standardized key paths. + +### Localhost vs *regular* client + +The localhost client aims to provide a unified approach to interacting with applications on a single chain, as the IBC +application layer provides for cross-chain interactions. To achieve this unified interface though, there are a number of +differences under the hood compared to a 'regular' IBC client (excluding `06-solomachine` and `09-localhost` itself). + +The table below lists some important differences: + +| | Regular client | Localhost | +|----------------------------------------------|-----------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------| +| Number of clients | Many instances of a client *type* corresponding to different counterparties | A single sentinel client with the client identifier `09-localhost` | +| Client creation | Relayer (permissionless) | Implicitly made available by the 02-client submodule in core IBC | +| Client updates | Relayer submits headers using `MsgUpdateClient` | No client updates are required as the localhost implementation is stateless | +| Number of connections | Many connections, 1 (or more) per client | A single sentinel connection with the connection identifier `connection-localhost` | +| Connection creation | Connection handshake, provided underlying client | Sentinel `ConnectionEnd` is created and set in store in the `InitGenesis` handler of the 03-connection submodule in core IBC | +| Counterparty | Underlying client, representing another chain | Client with identifier `09-localhost` in same chain | +| `VerifyMembership` and `VerifyNonMembership` | Performs proof verification using consensus state roots | Performs state verification using key-value lookups in the core IBC store | +| `ClientState` storage | `ClientState` stored and directly provable with `VerifyMembership` | Stateless, so `ClientState` is not provable directly with `VerifyMembership` | diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/02-localhost/02-integration.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/02-localhost/02-integration.md new file mode 100644 index 0000000..56d284a --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/02-localhost/02-integration.md @@ -0,0 +1,19 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /ibc/light-clients/localhost/integration +--- + + +# Integration + +The 09-localhost light client module registers codec types within the core IBC module. This differs from other light client module implementations which are expected to register codec types using the `AppModuleBasic` interface. + +The localhost client is implicitly enabled by using the `AllowAllClients` wildcard (`"*"`) in the 02-client submodule default value for param [`allowed_clients`](https://github.com/cosmos/ibc-go/blob/v7.0.0/proto/ibc/core/client/v1/client.proto#L102). + +```go +// DefaultAllowedClients are the default clients for the AllowedClients parameter. +// By default it allows all client types. +var DefaultAllowedClients = []string{AllowAllClients} +``` diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/02-localhost/03-client-state.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/02-localhost/03-client-state.md new file mode 100644 index 0000000..18b0daf --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/02-localhost/03-client-state.md @@ -0,0 +1,10 @@ +--- +title: ClientState +sidebar_label: ClientState +sidebar_position: 3 +slug: /ibc/light-clients/localhost/client-state +--- + +# `ClientState` + +The 09-localhost client is stateless and has no types. diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/02-localhost/04-connection.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/02-localhost/04-connection.md new file mode 100644 index 0000000..480e63d --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/02-localhost/04-connection.md @@ -0,0 +1,29 @@ +--- +title: Connection +sidebar_label: Connection +sidebar_position: 4 +slug: /ibc/light-clients/localhost/connection +--- + + +# Localhost connections + +The 09-localhost light client module integrates with core IBC through a single sentinel localhost connection. +The sentinel `ConnectionEnd` is stored by default in the core IBC store. + +This enables channel handshakes to be initiated out of the box by supplying the localhost connection identifier (`connection-localhost`) in the `connectionHops` parameter of `MsgChannelOpenInit`. + +The `ConnectionEnd` is created and set in store via the `InitGenesis` handler of the 03-connection submodule in core IBC. +The `ConnectionEnd` and its `Counterparty` both reference the `09-localhost` client identifier, and share the localhost connection identifier `connection-localhost`. + +```go +// CreateSentinelLocalhostConnection creates and sets the sentinel localhost connection end in the IBC store. +func (k Keeper) CreateSentinelLocalhostConnection(ctx sdk.Context) { + counterparty := types.NewCounterparty(exported.LocalhostClientID, exported.LocalhostConnectionID, commitmenttypes.NewMerklePrefix(k.GetCommitmentPrefix().Bytes())) + connectionEnd := types.NewConnectionEnd(types.OPEN, exported.LocalhostClientID, counterparty, types.GetCompatibleVersions(), 0) + + k.SetConnection(ctx, exported.LocalhostConnectionID, connectionEnd) +} +``` + +Note that connection handshakes are disallowed when using the `09-localhost` client type. diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/02-localhost/05-state-verification.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/02-localhost/05-state-verification.md new file mode 100644 index 0000000..2c68983 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/02-localhost/05-state-verification.md @@ -0,0 +1,23 @@ +--- +title: State Verification +sidebar_label: State Verification +sidebar_position: 5 +slug: /ibc/light-clients/localhost/state-verification +--- + +# State verification + +The localhost client handles state verification through the `LightClientModule` interface methods `VerifyMembership` and `VerifyNonMembership` by performing read-only operations directly on the core IBC store. + +When verifying channel state in handshakes or processing packets the `09-localhost` client can simply compare bytes stored under the standardized key paths defined by [ICS-24](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements). + +For existence proofs via `VerifyMembership` the 09-localhost client will retrieve the value stored under the provided key path and compare it against the value provided by the caller. In contrast, non-existence proofs via `VerifyNonMembership` assert the absence of a value at the provided key path. + +Relayers are expected to provide a sentinel proof when sending IBC messages. Submission of nil or empty proofs is disallowed in core IBC messaging. +The 09-localhost light client module defines a `SentinelProof` as a single byte. Localhost client state verification will fail if the sentinel proof value is not provided. + +```go +var SentinelProof = []byte{0x01} +``` + +The `ClientState` of `09-localhost` is stateless, so it is not directly provable with `VerifyMembership` or `VerifyNonMembership`. diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/02-localhost/_category_.json b/docs/versioned_docs/version-v10.1.x/03-light-clients/02-localhost/_category_.json new file mode 100644 index 0000000..f2e9bd7 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/02-localhost/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Localhost", + "position": 2, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/03-solomachine/01-solomachine.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/03-solomachine/01-solomachine.md new file mode 100644 index 0000000..91c41b1 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/03-solomachine/01-solomachine.md @@ -0,0 +1,26 @@ +--- +title: Solomachine +sidebar_label: Solomachine +sidebar_position: 1 +slug: /ibc/light-clients/solomachine/solomachine +--- + + +# `solomachine` + +## Abstract + +This paper defines the implementation of the ICS06 protocol on the Cosmos SDK. For the general +specification please refer to the [ICS06 Specification](https://github.com/cosmos/ibc/tree/master/spec/client/ics-006-solo-machine-client). + +This implementation of a solo machine light client supports single and multi-signature public +keys. The client is capable of handling public key updates by header and governance proposals. +The light client is capable of processing client misbehaviour. Proofs of the counterparty state +are generated by the solo machine client by signing over the desired state with a certain sequence, +diversifier, and timestamp. + +## Contents + +1. **[Concepts](02-concepts.md)** +2. **[State](03-state.md)** +3. **[State Transitions](04-state_transitions.md)** diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/03-solomachine/02-concepts.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/03-solomachine/02-concepts.md new file mode 100644 index 0000000..f6c7ff7 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/03-solomachine/02-concepts.md @@ -0,0 +1,168 @@ +--- +title: Concepts +sidebar_label: Concepts +sidebar_position: 2 +slug: /ibc/light-clients/solomachine/concepts +--- + + +# Concepts + +## Client State + +The `ClientState` for a solo machine light client stores the latest sequence, the frozen sequence, +the latest consensus state, and client flag indicating if the client should be allowed to be updated +after a governance proposal. + +If the client is not frozen then the frozen sequence is 0. + +## Consensus State + +The consensus states stores the public key, diversifier, and timestamp of the solo machine light client. + +The diversifier is used to prevent accidental misbehaviour if the same public key is used across +different chains with the same client identifier. It should be unique to the chain the light client +is used on. + +## Public Key + +The public key can be a single public key or a multi-signature public key. The public key type used +must fulfill the tendermint public key interface (this will become the SDK public key interface in the +near future). The public key must be registered on the application codec otherwise encoding/decoding +errors will arise. The public key stored in the consensus state is represented as a protobuf `Any`. +This allows for flexibility in what other public key types can be supported in the future. + +## Counterparty Verification + +The solo machine light client can verify counterparty client state, consensus state, connection state, +channel state, packet commitments, packet acknowledgements, packet receipt absence, +and the next sequence receive. At the end of each successful verification call the light +client sequence number will be incremented. + +Successful verification requires the current public key to sign over the proof. + +## Proofs + +A solo machine proof should verify that the solomachine public key signed +over some specified data. The format for generating marshaled proofs for +the SDK's implementation of solo machine is as follows: + +1. Construct the data using the associated protobuf definition and marshal it. + +For example: + +```go +data := &ClientStateData{ + Path: []byte(path.String()), + ClientState: protoAny, +} + +dataBz, err := cdc.Marshal(data) +``` + +The helper functions `...DataBytes()` in [proof.go](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/06-solomachine/proof.go) handle this +functionality. + +2. Construct the `SignBytes` and marshal it. + +For example: + +```go +signBytes := &SignBytes{ + Sequence: sequence, + Timestamp: timestamp, + Diversifier: diversifier, + DataType: CLIENT, + Data: dataBz, +} + +signBz, err := cdc.Marshal(signBytes) +``` + +The helper functions `...SignBytes()` in [proof.go](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/06-solomachine/proof.go) handle this functionality. +The `DataType` field is used to disambiguate what type of data was signed to prevent potential +proto encoding overlap. + +3. Sign the sign bytes. Embed the signatures into either `SingleSignatureData` or `MultiSignatureData`. +Convert the `SignatureData` to proto and marshal it. + +For example: + +```go +sig, err := key.Sign(signBz) +sigData := &signing.SingleSignatureData{ + Signature: sig, +} + +protoSigData := signing.SignatureDataToProto(sigData) +bz, err := cdc.Marshal(protoSigData) +``` + +4. Construct a `TimestampedSignatureData` and marshal it. The marshaled result can be passed in +as the proof parameter to the verification functions. + +For example: + +```go +timestampedSignatureData := &solomachine.TimestampedSignatureData{ + SignatureData: sigData, + Timestamp: solomachine.Time, +} + +proof, err := cdc.Marshal(timestampedSignatureData) +``` + +NOTE: At the end of this process, the sequence associated with the key needs to be updated. +The sequence must be incremented each time proof is generated. + +## Updates By Header + +An update by a header will only succeed if: + +- the header provided is parseable to solo machine header +- the header sequence matches the current sequence +- the header timestamp is greater than or equal to the consensus state timestamp +- the currently registered public key generated the proof + +If the update is successful: + +- the public key is updated +- the diversifier is updated +- the timestamp is updated +- the sequence is incremented by 1 +- the new consensus state is set in the client state + +## Updates By Proposal + +An update by a governance proposal will only succeed if: + +- the substitute provided is parseable to solo machine client state +- the new consensus state public key does not equal the current consensus state public key + +If the update is successful: + +- the subject client state is updated to the substitute client state +- the subject consensus state is updated to the substitute consensus state +- the client is unfrozen (if it was previously frozen) + +NOTE: Previously, `AllowUpdateAfterProposal` was used to signal the update/recovery options for the solo machine client. However, this has now been deprecated because a code migration can overwrite the client and consensus states regardless of the value of this parameter. If governance would vote to overwrite a client or consensus state, it is likely that governance would also be willing to perform a code migration to do the same. + +## Misbehaviour + +Misbehaviour handling will only succeed if: + +- the misbehaviour provided is parseable to solo machine misbehaviour +- the client is not already frozen +- the current public key signed over two unique data messages at the same sequence and diversifier. + +If the misbehaviour is successfully processed: + +- the client is frozen by setting the frozen sequence to the misbehaviour sequence + +NOTE: Misbehaviour processing is data processing order dependent. A misbehaving solo machine +could update to a new public key to prevent being frozen before misbehaviour is submitted. + +## Upgrades + +Upgrades to solo machine light clients are not supported since an entirely different type of +public key can be set using normal client updates. diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/03-solomachine/03-state.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/03-solomachine/03-state.md new file mode 100644 index 0000000..bdb3102 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/03-solomachine/03-state.md @@ -0,0 +1,12 @@ +--- +title: State +sidebar_label: State +sidebar_position: 3 +slug: /ibc/light-clients/solomachine/state +--- + + +# State + +The solo machine light client will only store consensus states for each update by a header +or a governance proposal. The latest client state is also maintained in the store. diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/03-solomachine/04-state_transitions.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/03-solomachine/04-state_transitions.md new file mode 100644 index 0000000..aabaa58 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/03-solomachine/04-state_transitions.md @@ -0,0 +1,43 @@ +--- +title: State Transitions +sidebar_label: State Transitions +sidebar_position: 4 +slug: /ibc/light-clients/solomachine/state_transitions +--- + + +# State Transitions + +## Client State Verification Functions + +Successful state verification by a solo machine light client will result in: + +- the sequence being incremented by 1. + +## Update By Header + +A successful update of a solo machine light client by a header will result in: + +- the public key being updated to the new public key provided by the header. +- the diversifier being updated to the new diviersifier provided by the header. +- the timestamp being updated to the new timestamp provided by the header. +- the sequence being incremented by 1 +- the consensus state being updated (consensus state stores the public key, diversifier, and timestamp) + +## Update By Governance Proposal + +A successful update of a solo machine light client by a governance proposal will result in: + +- the client state being updated to the substitute client state +- the consensus state being updated to the substitute consensus state (consensus state stores the public key, diversifier, and timestamp) +- the frozen sequence being set to zero (client is unfrozen if it was previously frozen). + +## Upgrade + +Client udgrades are not supported for the solo machine light client. No state transition occurs. + +## Misbehaviour + +Successful misbehaviour processing of a solo machine light client will result in: + +- the frozen sequence being set to the sequence the misbehaviour occurred at diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/03-solomachine/_category_.json b/docs/versioned_docs/version-v10.1.x/03-light-clients/03-solomachine/_category_.json new file mode 100644 index 0000000..3bfa67e --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/03-solomachine/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Solomachine", + "position": 3, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/01-overview.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/01-overview.md new file mode 100644 index 0000000..79e26a2 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/01-overview.md @@ -0,0 +1,26 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /ibc/light-clients/wasm/overview +--- + +# `08-wasm` + +## Overview + +Learn about the `08-wasm` light client proxy module. + +### Context + +Traditionally, light clients used by ibc-go have been implemented only in Go, and since ibc-go v7 (with the release of the 02-client refactor), they are [first-class Cosmos SDK modules](/architecture/adr-010-light-clients-as-sdk-modules). This means that updating existing light client implementations or adding support for new light clients is a multi-step, time-consuming process involving on-chain governance: it is necessary to modify the codebase of ibc-go (if the light client is part of its codebase), re-build chains' binaries, pass a governance proposal and have validators upgrade their nodes. + +### Motivation + +To break the limitation of being able to write light client implementations only in Go, the `08-wasm` adds support to run light clients written in a Wasm-compilable language. The light client byte code implements the entry points of a [CosmWasm](https://docs.cosmwasm.com/docs/) smart contract, and runs inside a Wasm VM. The `08-wasm` module exposes a proxy light client interface that routes incoming messages to the appropriate handler function, inside the Wasm VM, for execution. + +Adding a new light client to a chain is just as simple as submitting a governance proposal with the message that stores the byte code of the light client contract. No coordinated upgrade is needed. When the governance proposal passes and the message is executed, the contract is ready to be instantiated upon receiving a relayer-submitted `MsgCreateClient`. The process of creating a Wasm light client is the same as with a regular light client implemented in Go. + +### Use cases + +- Development of light clients for non-Cosmos ecosystem chains: state machines in other ecosystems are, in many cases, implemented in Rust, and thus there are probably libraries used in their light client implementations for which there is no equivalent in Go. This makes the development of a light client in Go very difficult, but relatively simple to do it in Rust. Therefore, writing a CosmWasm smart contract in Rust that implements the light client algorithm becomes a lower effort. diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/02-concepts.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/02-concepts.md new file mode 100644 index 0000000..4d42c96 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/02-concepts.md @@ -0,0 +1,90 @@ +--- +title: Concepts +sidebar_label: Concepts +sidebar_position: 2 +slug: /ibc/light-clients/wasm/concepts +--- + +# Concepts + +Learn about the differences between a proxy light client and a Wasm light client. + +## Proxy light client + +The `08-wasm` module is not a regular light client in the same sense as, for example, the 07-tendermint light client. `08-wasm` is instead a *proxy* light client module, and this means that the module acts a proxy to the actual implementations of light clients. The module will act as a wrapper for the actual light clients uploaded as Wasm byte code and will delegate all operations to them (i.e. `08-wasm` just passes through the requests to the Wasm light clients). Still, the `08-wasm` module implements all the required interfaces necessary to integrate with core IBC, so that 02-client can call into it as it would for any other light client module. These interfaces are `LightClientModule`, `ClientState`, `ConsensusState` and `ClientMessage`, and we will describe them in the context of `08-wasm` in the following sections. For more information about this set of interfaces, please read section [Overview of the light client module developer guide](../01-developer-guide/01-overview.md#overview). + +### `LightClientModule` + +The `08-wasm`'s `LightClientModule` data structure contains two fields: + +- `keeper` is the `08-wasm` module keeper. +- `storeProvider` encapsulates the IBC core store key and provides access to isolated prefix stores for each client so they can read/write in separate namespaces. + +```go +type LightClientModule struct { + keeper wasmkeeper.Keeper + storeProvider exported.ClientStoreProvider +} +``` + +See section [`LightClientModule` of the light client module developer guide](../01-developer-guide/01-overview.md#lightclientmodule) for more information about the `LightClientModule` interface. + +### `ClientState` + +The `08-wasm`'s `ClientState` data structure contains three fields: + +- `Data` contains the bytes of the Protobuf-encoded client state of the underlying light client implemented as a Wasm contract. For example, if the Wasm light client contract implements the GRANDPA light client algorithm, then `Data` will contain the bytes for a [GRANDPA client state](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L35-L60). +- `Checksum` is the sha256 hash of the Wasm contract's byte code. This hash is used as an identifier to call the right contract. +- `LatestHeight` is the latest height of the counterparty state machine (i.e. the height of the blockchain), whose consensus state the light client tracks. + +```go +type ClientState struct { + // bytes encoding the client state of the underlying + // light client implemented as a Wasm contract + Data []byte + // sha256 hash of Wasm contract byte code + Checksum []byte + // latest height of the counterparty ledger + LatestHeight types.Height +} +``` + +See section [`ClientState` of the light client module developer guide](../01-developer-guide/01-overview.md#clientstate) for more information about the `ClientState` interface. + +### `ConsensusState` + +The `08-wasm`'s `ConsensusState` data structure maintains one field: + +- `Data` contains the bytes of the Protobuf-encoded consensus state of the underlying light client implemented as a Wasm contract. For example, if the Wasm light client contract implements the GRANDPA light client algorithm, then `Data` will contain the bytes for a [GRANDPA consensus state](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L87-L94). + +```go +type ConsensusState struct { + // bytes encoding the consensus state of the underlying light client + // implemented as a Wasm contract. + Data []byte +} +``` + +See section [`ConsensusState` of the light client module developer guide](../01-developer-guide/01-overview.md#consensusstate) for more information about the `ConsensusState` interface. + +### `ClientMessage` + +`ClientMessage` is used for performing updates to a `ClientState` stored on chain. The `08-wasm`'s `ClientMessage` data structure maintains one field: + +- `Data` contains the bytes of the Protobuf-encoded header(s) or misbehaviour for the underlying light client implemented as a Wasm contract. For example, if the Wasm light client contract implements the GRANDPA light client algorithm, then `Data` will contain the bytes of either [header](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L96-L104) or [misbehaviour](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L106-L112) for a GRANDPA light client. + +```go +type ClientMessage struct { + // bytes encoding the header(s) or misbehaviour for the underlying light client + // implemented as a Wasm contract. + Data []byte +} +``` + +See section [`ClientMessage` of the light client module developer guide](../01-developer-guide/01-overview.md#clientmessage) for more information about the `ClientMessage` interface. + +## Wasm light client + +The actual light client can be implemented in any language that compiles to Wasm and implements the interfaces of a [CosmWasm](https://docs.cosmwasm.com/docs/) contract. Even though in theory other languages could be used, in practice (at least for the time being) the most suitable language to use would be Rust, since there is already good support for it for developing CosmWasm smart contracts. + +At the moment of writing there are two contracts available: one for [Tendermint](https://github.com/ComposableFi/composable-ibc/tree/master/light-clients/ics07-tendermint-cw) and one [GRANDPA](https://github.com/ComposableFi/composable-ibc/tree/master/light-clients/ics10-grandpa-cw) (which is being used in production in [Composable Finance's Centauri bridge](https://github.com/ComposableFi/composable-ibc)). And there are others in development (e.g. for Near). diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/03-integration.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/03-integration.md new file mode 100644 index 0000000..3895bde --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/03-integration.md @@ -0,0 +1,398 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 3 +slug: /ibc/light-clients/wasm/integration +--- + +# Integration + +Learn how to integrate the `08-wasm` module in a chain binary and about the recommended approaches depending on whether the [`x/wasm` module](https://github.com/CosmWasm/wasmd/tree/main/x/wasm) is already used in the chain. The following document only applies for Cosmos SDK chains. + +## Importing the `08-wasm` module + +`08-wasm` has no stable releases yet. To use it, you need to import the git commit that contains the module with the compatible versions of `ibc-go` and `wasmvm`. To do so, run the following command with the desired git commit in your project: + +```sh +go get github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10 +``` + +## `app.go` setup + +The sample code below shows the relevant integration points in `app.go` required to set up the `08-wasm` module in a chain binary. Since `08-wasm` is a light client module itself, please check out as well the section [Integrating light clients](../../01-ibc/02-integration.md#integrating-light-clients) for more information: + +```go +// app.go +import ( + ... + "github.com/cosmos/cosmos-sdk/runtime" + + cmtos "github.com/cometbft/cometbft/libs/os" + + ibcwasm "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10" + ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/keeper" + ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + ... +) + +... + +// Register the AppModule for the 08-wasm module +ModuleBasics = module.NewBasicManager( + ... + ibcwasm.AppModuleBasic{}, + ... +) + +// Add 08-wasm Keeper +type SimApp struct { + ... + WasmClientKeeper ibcwasmkeeper.Keeper + ... +} + +func NewSimApp( + logger log.Logger, + db dbm.DB, + traceStore io.Writer, + loadLatest bool, + appOpts servertypes.AppOptions, + baseAppOptions ...func(*baseapp.BaseApp), +) *SimApp { + ... + keys := sdk.NewKVStoreKeys( + ... + ibcwasmtypes.StoreKey, + ) + + // Instantiate 08-wasm's keeper + // This sample code uses a constructor function that + // accepts a pointer to an existing instance of Wasm VM. + // This is the recommended approach when the chain + // also uses `x/wasm`, and then the Wasm VM instance + // can be shared. + app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM( + appCodec, + runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmVM, + app.GRPCQueryRouter(), + ) + + wasmLightClientModule := wasm.NewLightClientModule(app.WasmClientKeeper) + app.IBCKeeper.ClientKeeper.AddRoute(ibcwasmtypes.ModuleName, &wasmLightClientModule) + + app.ModuleManager = module.NewManager( + // SDK app modules + ... + ibcwasm.NewAppModule(app.WasmClientKeeper), + ) + app.ModuleManager.SetOrderBeginBlockers( + ... + ibcwasmtypes.ModuleName, + ... + ) + app.ModuleManager.SetOrderEndBlockers( + ... + ibcwasmtypes.ModuleName, + ... + ) + genesisModuleOrder := []string{ + ... + ibcwasmtypes.ModuleName, + ... + } + app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...) + app.ModuleManager.SetOrderExportGenesis(genesisModuleOrder...) + ... + + // initialize BaseApp + app.SetInitChainer(app.InitChainer) + ... + + // must be before Loading version + if manager := app.SnapshotManager(); manager != nil { + err := manager.RegisterExtensions( + ibcwasmkeeper.NewWasmSnapshotter(app.CommitMultiStore(), &app.WasmClientKeeper), + ) + if err != nil { + panic(fmt.Errorf("failed to register snapshot extension: %s", err)) + } + } + ... + + if loadLatest { + ... + + ctx := app.BaseApp.NewUncachedContext(true, cmtproto.Header{}) + + // Initialize pinned codes in wasmvm as they are not persisted there + if err := app.WasmClientKeeper.InitializePinnedCodes(ctx); err != nil { + cmtos.Exit(fmt.Sprintf("failed initialize pinned codes %s", err)) + } + } +} +``` + +## Keeper instantiation + +When it comes to instantiating `08-wasm`'s keeper, there are two recommended ways of doing it. Choosing one or the other will depend on whether the chain already integrates [`x/wasm`](https://github.com/CosmWasm/wasmd/tree/main/x/wasm) or not. + +### If `x/wasm` is present + +If the chain where the module is integrated uses `x/wasm` then we recommend that both `08-wasm` and `x/wasm` share the same Wasm VM instance. Having two separate Wasm VM instances is still possible, but care should be taken to make sure that both instances do not share the directory when the VM stores blobs and various caches, otherwise unexpected behaviour is likely to happen (from `x/wasm` v0.51 and `08-wasm` v0.2.0+ibc-go-v8.3-wasmvm-v2.0 this will be forbidden anyway, since wasmvm v2.0.0 and above will not allow two different Wasm VM instances to shared the same data folder). + +In order to share the Wasm VM instance, please follow the guideline below. Please note that this requires `x/wasm` v0.41 or above. + +- Instantiate the Wasm VM in `app.go` with the parameters of your choice. +- [Create an `Option` with this Wasm VM instance](https://github.com/CosmWasm/wasmd/blob/db93d7b6c7bb6f4a340d74b96a02cec885729b59/x/wasm/keeper/options.go#L21-L25). +- Add the option created in the previous step to a slice and [pass it to the `x/wasm NewKeeper` constructor function](https://github.com/CosmWasm/wasmd/blob/db93d7b6c7bb6f4a340d74b96a02cec885729b59/x/wasm/keeper/keeper_cgo.go#L36). +- Pass the pointer to the Wasm VM instance to `08-wasm` [`NewKeeperWithVM` constructor function](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/keeper/keeper.go#L39-L47). + +The code to set this up would look something like this: + +```go +// app.go +import ( + ... + "github.com/cosmos/cosmos-sdk/runtime" + + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + + ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/keeper" + ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + ... +) + +... + +// instantiate the Wasm VM with the chosen parameters +wasmer, err := wasmvm.NewVM( + dataDir, + availableCapabilities, + contractMemoryLimit, // default of 32 + contractDebugMode, + memoryCacheSize, +) +if err != nil { + panic(err) +} + +// create an Option slice (or append to an existing one) +// with the option to use a custom Wasm VM instance +wasmOpts = []wasmkeeper.Option{ + wasmkeeper.WithWasmEngine(wasmer), +} + +// the keeper will use the provided Wasm VM instance, +// instead of instantiating a new one +app.WasmKeeper = wasmkeeper.NewKeeper( + appCodec, + keys[wasmtypes.StoreKey], + app.AccountKeeper, + app.BankKeeper, + app.StakingKeeper, + distrkeeper.NewQuerier(app.DistrKeeper), + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeper, + &app.IBCKeeper.PortKeeper, + scopedWasmKeeper, + app.TransferKeeper, + app.MsgServiceRouter(), + app.GRPCQueryRouter(), + wasmDir, + wasmConfig, + availableCapabilities, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmOpts..., +) + +app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM( + appCodec, + runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmer, // pass the Wasm VM instance to `08-wasm` keeper constructor + app.GRPCQueryRouter(), +) +... +``` + +### If `x/wasm` is not present + +If the chain does not use [`x/wasm`](https://github.com/CosmWasm/wasmd/tree/main/x/wasm), even though it is still possible to use the method above from the previous section +(e.g. instantiating a Wasm VM in app.go an pass it to 08-wasm's [`NewKeeperWithVM` constructor function](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/keeper/keeper.go#L39-L47), since there would be no need in this case to share the Wasm VM instance with another module, you can use the [`NewKeeperWithConfig` constructor function](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/keeper/keeper.go#L88-L96) and provide the Wasm VM configuration parameters of your choice instead. A Wasm VM instance will be created in `NewKeeperWithConfig`. The parameters that can set are: + +- `DataDir` is the [directory for Wasm blobs and various caches](https://github.com/CosmWasm/wasmvm/blob/v2.0.0/lib.go#L25). As an example, in `wasmd` this is set to the [`wasm` folder under the home directory](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/app/app.go#L578). In the code snippet below we set this field to the `ibc_08-wasm_client_data` folder under the home directory. +- `SupportedCapabilities` is a [list of capabilities supported by the chain](https://github.com/CosmWasm/wasmvm/blob/v2.0.0/lib.go#L26). [`wasmd` sets this to all the available capabilities](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/app/app.go#L586), but 08-wasm only requires `iterator`. +- `MemoryCacheSize` sets [the size in MiB of an in-memory cache for e.g. module caching](https://github.com/CosmWasm/wasmvm/blob/v2.0.0/lib.go#L29C16-L29C104). It is not consensus-critical and should be defined on a per-node basis, often in the range 100 to 1000 MB. [`wasmd` reads this value of](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/app/app.go#L579). Default value is 256. +- `ContractDebugMode` is a [flag to enable/disable printing debug logs from the contract to STDOUT](https://github.com/CosmWasm/wasmvm/blob/v2.0.0/lib.go#L28). This should be false in production environments. Default value is false. + +Another configuration parameter of the Wasm VM is the contract memory limit (in MiB), which is [set to 32](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/types/config.go#L8), [following the example of `wasmd`](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/x/wasm/keeper/keeper.go#L32-L34). This parameter is not configurable by users of `08-wasm`. + +The following sample code shows how the keeper would be constructed using this method: + +```go +// app.go +import ( + ... + "github.com/cosmos/cosmos-sdk/runtime" + + ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/keeper" + ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + ... +) + +... + +// homePath is the path to the directory where the data +// directory for Wasm blobs and caches will be created +wasmConfig := ibcwasmtypes.WasmConfig{ + DataDir: filepath.Join(homePath, "ibc_08-wasm_client_data"), + SupportedCapabilities: []string{"iterator"}, + ContractDebugMode: false, +} +app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithConfig( + appCodec, + runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmConfig, + app.GRPCQueryRouter(), +) +``` + +Check out also the [`WasmConfig` type definition](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/types/config.go#L21-L31) for more information on each of the configurable parameters. Some parameters allow node-level configurations. There is additionally the function [`DefaultWasmConfig`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/types/config.go#L36-L42) available that returns a configuration with the default values. + +### Options + +The `08-wasm` module comes with an options API inspired by the one in `x/wasm`. +Currently the only option available is the `WithQueryPlugins` option, which allows registration of custom query plugins for the `08-wasm` module. The use of this API is optional and it is only required if the chain wants to register custom query plugins for the `08-wasm` module. + +#### `WithQueryPlugins` + +By default, the `08-wasm` module does not configure any querier options for light client contracts. However, it is possible to register custom query plugins for [`QueryRequest::Custom`](https://github.com/CosmWasm/cosmwasm/blob/v2.0.1/packages/std/src/query/mod.rs#L48) and [`QueryRequest::Stargate`](https://github.com/CosmWasm/cosmwasm/blob/v2.0.1/packages/std/src/query/mod.rs#L57-L65). + +Assuming that the keeper is not yet instantiated, the following sample code shows how to register query plugins for the `08-wasm` module. + +We first construct a [`QueryPlugins`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/types/querier.go#L78-L87) object with the desired query plugins: + +```go +queryPlugins := ibcwasmtypes.QueryPlugins { + Custom: MyCustomQueryPlugin(), + // `myAcceptList` is a `[]string` containing the list of gRPC query paths that the chain wants to allow for the `08-wasm` module to query. + // These queries must be registered in the chain's gRPC query router, be deterministic, and track their gas usage. + // The `AcceptListStargateQuerier` function will return a query plugin that will only allow queries for the paths in the `myAcceptList`. + // The query responses are encoded in protobuf unlike the implementation in `x/wasm`. + Stargate: ibcwasmtypes.AcceptListStargateQuerier(myAcceptList), +} +``` + +Note that the `Stargate` querier appends the user defined accept list of query routes to a default list defined by the `08-wasm` module. +The `defaultAcceptList` defines a single query route: `"/ibc.core.client.v1.Query/VerifyMembership"`. This allows for light client smart contracts to delegate parts of their workflow to other light clients for auxiliary proof verification. For example, proof of inclusion of block and tx data by a data availability provider. + +```go +// defaultAcceptList defines a set of default allowed queries made available to the Querier. +var defaultAcceptList = []string{ + "/ibc.core.client.v1.Query/VerifyMembership", +} +``` + +You may leave any of the fields in the `QueryPlugins` object as `nil` if you do not want to register a query plugin for that query type. + +Then, we pass the `QueryPlugins` object to the `WithQueryPlugins` option: + +```go +querierOption := ibcwasmkeeper.WithQueryPlugins(&queryPlugins) +``` + +Finally, we pass the option to the `NewKeeperWithConfig` or `NewKeeperWithVM` constructor function during [Keeper instantiation](#keeper-instantiation): + +```diff +app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithConfig( + appCodec, + runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmConfig, + app.GRPCQueryRouter(), ++ querierOption, +) +``` + +```diff +app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM( + appCodec, + runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmer, // pass the Wasm VM instance to `08-wasm` keeper constructor + app.GRPCQueryRouter(), ++ querierOption, +) +``` + +## Updating `AllowedClients` + +If the chain's 02-client submodule parameter `AllowedClients` contains the single wildcard `"*"` element, then it is not necessary to do anything in order to allow the creation of `08-wasm` clients. However, if the parameter contains a list of client types (e.g. `["06-solomachine", "07-tendermint"]`), then in order to use the `08-wasm` module chains must update the [`AllowedClients` parameter](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/client/v1/client.proto#L64) of core IBC. This can be configured directly in the application upgrade handler with the sample code below: + +```go +import ( + ... + ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + ... +) + +... + +func CreateWasmUpgradeHandler( + mm *module.Manager, + configurator module.Configurator, + clientKeeper clientkeeper.Keeper, +) upgradetypes.UpgradeHandler { + return func(goCtx context.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + // explicitly update the IBC 02-client params, adding the wasm client type + params := clientKeeper.GetParams(ctx) + params.AllowedClients = append(params.AllowedClients, ibcwasmtypes.Wasm) + clientKeeper.SetParams(ctx, params) + + return mm.RunMigrations(goCtx, configurator, vm) + } +} +``` + +Or alternatively the parameter can be updated via a governance proposal (see at the bottom of section [`Creating clients`](../01-developer-guide/09-setup.md#creating-clients) for an example of how to do this). + +## Adding the module to the store + +As part of the upgrade migration you must also add the module to the upgrades store. + +```go +func (app SimApp) RegisterUpgradeHandlers() { + + ... + + if upgradeInfo.Name == UpgradeName && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { + storeUpgrades := storetypes.StoreUpgrades{ + Added: []string{ + ibcwasmtypes.ModuleName, + }, + } + + // configure store loader that checks if version == upgradeHeight and applies store upgrades + app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) + } +} +``` + +## Adding snapshot support + +In order to use the `08-wasm` module chains are required to register the `WasmSnapshotter` extension in the snapshot manager. This snapshotter takes care of persisting the external state, in the form of contract code, of the Wasm VM instance to disk when the chain is snapshotted. [This code](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/testing/simapp/app.go#L775-L782) should be placed in `NewSimApp` function in `app.go`. + +## Pin byte codes at start + +Wasm byte codes should be pinned to the WasmVM cache on every application start, therefore [this code](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/testing/simapp/app.go#L825-L830) should be placed in `NewSimApp` function in `app.go`. diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/04-messages.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/04-messages.md new file mode 100644 index 0000000..e27513b --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/04-messages.md @@ -0,0 +1,75 @@ +--- +title: Messages +sidebar_label: Messages +sidebar_position: 4 +slug: /ibc/light-clients/wasm/messages +--- + +# Messages + +## `MsgStoreCode` + +Uploading the Wasm light client contract to the Wasm VM storage is achieved by means of `MsgStoreCode`: + +```go +type MsgStoreCode struct { + // signer address + Signer string + // wasm byte code of light client contract. It can be raw or gzip compressed + WasmByteCode []byte +} +``` + +This message is expected to fail if: + +- `Signer` is an invalid Bech32 address, or it does not match the designated authority address. +- `WasmByteCode` is empty or it exceeds the maximum size, currently set to 3MB. + +Only light client contracts stored using `MsgStoreCode` are allowed to be instantiated. An attempt to create a light client from contracts uploaded via other means (e.g. through `x/wasm` if the module shares the same Wasm VM instance with 08-wasm) will fail. Due to the idempotent nature of the Wasm VM's `StoreCode` function, it is possible to store the same byte code multiple times. + +When execution of `MsgStoreCode` succeeds, the checksum of the contract (i.e. the sha256 hash of the contract's byte code) is stored in an allow list. When a relayer submits [`MsgCreateClient`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/client/v1/tx.proto#L25-L37) with 08-wasm's `ClientState`, the client state includes the checksum of the Wasm byte code that should be called. Then 02-client calls [08-wasm's implementation of `Initialize` function](https://github.com/cosmos/ibc-go/blob/06fd8eb5ee1697e3b43be7528a6e42f5e4a4613c/modules/core/02-client/keeper/client.go#L40) (which is an interface function part of `LightClientModule`), and it will check that the checksum in the client state matches one of the checksums in the allow list. If a match is found, the light client is initialized; otherwise, the transaction is aborted. + +## `MsgMigrateContract` + +Migrating a contract to a new Wasm byte code is achieved by means of `MsgMigrateContract`: + +```go +type MsgMigrateContract struct { + // signer address + Signer string + // the client id of the contract + ClientId string + // the SHA-256 hash of the new wasm byte code for the contract + Checksum []byte + // the json-encoded migrate msg to be passed to the contract on migration + Msg []byte +} +``` + +This message is expected to fail if: + +- `Signer` is an invalid Bech32 address, or it does not match the designated authority address. +- `ClientId` is not a valid identifier prefixed by `08-wasm`. +- `Checksum` is not exactly 32 bytes long or it is not found in the list of allowed checksums (a new checksum is added to the list when executing `MsgStoreCode`), or it matches the current checksum of the contract. + +When a Wasm light client contract is migrated to a new Wasm byte code the checksum for the contract will be updated with the new checksum. + +## `MsgRemoveChecksum` + +Removing a checksum from the list of allowed checksums is achieved by means of `MsgRemoveChecksum`: + +```go +type MsgRemoveChecksum struct { + // signer address + Signer string + // Wasm byte code checksum to be removed from the store + Checksum []byte +} +``` + +This message is expected to fail if: + +- `Signer` is an invalid Bech32 address, or it does not match the designated authority address. +- `Checksum` is not exactly 32 bytes long or it is not found in the list of allowed checksums (a new checksum is added to the list when executing `MsgStoreCode`). + +When a checksum is removed from the list of allowed checksums, then the corresponding Wasm byte code will not be available for instantiation in [08-wasm's implementation of `Initialize` function](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/core/02-client/keeper/client.go#L36). diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/05-governance.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/05-governance.md new file mode 100644 index 0000000..2993c5d --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/05-governance.md @@ -0,0 +1,126 @@ +--- +title: Governance +sidebar_label: Governance +sidebar_position: 5 +slug: /ibc/light-clients/wasm/governance +--- + +# Governance + +Learn how to upload Wasm light client byte code on a chain, and how to migrate an existing Wasm light client contract. + +## Setting an authority + +Both the storage of Wasm light client byte code as well as the migration of an existing Wasm light client contract are permissioned (i.e. only allowed to an authority such as governance). The designated authority is specified when instantiating `08-wasm`'s keeper: both [`NewKeeperWithVM`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/keeper/keeper.go#L39-L47) and [`NewKeeperWithConfig`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/keeper/keeper.go#L88-L96) constructor functions accept an `authority` argument that must be the address of the authorized actor. For example, in `app.go`, when instantiating the keeper, you can pass the address of the governance module: + +```go +// app.go +import ( + ... + "github.com/cosmos/cosmos-sdk/runtime" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/keeper" + ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + ... +) + +// app.go +app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM( + appCodec, + runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), // authority + wasmVM, + app.GRPCQueryRouter(), +) +``` + +## Storing new Wasm light client byte code + + If governance is the allowed authority, the governance v1 proposal that needs to be submitted to upload a new light client contract should contain the message [`MsgStoreCode`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/proto/ibc/lightclients/wasm/v1/tx.proto#L23-L30) with the base64-encoded byte code of the Wasm contract. Use the following CLI command and JSON as an example: + +```shell +simd tx gov submit-proposal --from +``` + +where `proposal.json` contains: + +```json +{ + "title": "Upload IBC Wasm light client", + "summary": "Upload wasm client", + "messages": [ + { + "@type": "/ibc.lightclients.wasm.v1.MsgStoreCode", + "signer": "cosmos1...", // the authority address (e.g. the gov module account address) + "wasm_byte_code": "YWJ...PUB+" // standard base64 encoding of the Wasm contract byte code + } + ], + "metadata": "AQ==", + "deposit": "100stake" +} +``` + +To learn more about the `submit-proposal` CLI command, please check out [the relevant section in Cosmos SDK documentation](https://docs.cosmos.network/main/modules/gov#submit-proposal). + +Alternatively, the process of submitting the proposal may be simpler if you use the CLI command `store-code`. This CLI command accepts as argument the file of the Wasm light client contract and takes care of constructing the proposal message with `MsgStoreCode` and broadcasting it. See section [`store-code`](./08-client.md#store-code) for more information. + +## Migrating an existing Wasm light client contract + +If governance is the allowed authority, the governance v1 proposal that needs to be submitted to migrate an existing new Wasm light client contract should contain the message [`MsgMigrateContract`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/proto/ibc/lightclients/wasm/v1/tx.proto#L52-L63) with the checksum of the Wasm byte code to migrate to. Use the following CLI command and JSON as an example: + +```shell +simd tx gov submit-proposal --from +``` + +where `proposal.json` contains: + +```json +{ + "title": "Migrate IBC Wasm light client", + "summary": "Migrate wasm client", + "messages": [ + { + "@type": "/ibc.lightclients.wasm.v1.MsgMigrateContract", + "signer": "cosmos1...", // the authority address (e.g. the gov module account address) + "client_id": "08-wasm-1", // client identifier of the Wasm light client contract that will be migrated + "checksum": "a8ad...4dc0", // SHA-256 hash of the Wasm byte code to migrate to, previously stored with MsgStoreCode + "msg": "{}" // JSON-encoded message to be passed to the contract on migration + } + ], + "metadata": "AQ==", + "deposit": "100stake" +} +``` + +To learn more about the `submit-proposal` CLI command, please check out [the relevant section in Cosmos SDK documentation](https://docs.cosmos.network/main/modules/gov#submit-proposal). + +## Removing an existing checksum + +If governance is the allowed authority, the governance v1 proposal that needs to be submitted to remove a specific checksum from the list of allowed checksums should contain the message [`MsgRemoveChecksum`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/proto/ibc/lightclients/wasm/v1/tx.proto#L39-L46) with the checksum (of a corresponding Wasm byte code). Use the following CLI command and JSON as an example: + +```shell +simd tx gov submit-proposal --from +``` + +where `proposal.json` contains: + +```json +{ + "title": "Remove checksum of Wasm light client byte code", + "summary": "Remove checksum", + "messages": [ + { + "@type": "/ibc.lightclients.wasm.v1.MsgRemoveChecksum", + "signer": "cosmos1...", // the authority address (e.g. the gov module account address) + "checksum": "a8ad...4dc0", // SHA-256 hash of the Wasm byte code that should be removed from the list of allowed checksums + } + ], + "metadata": "AQ==", + "deposit": "100stake" +} +``` + +To learn more about the `submit-proposal` CLI command, please check out [the relevant section in Cosmos SDK documentation](https://docs.cosmos.network/main/modules/gov#submit-proposal). diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/06-events.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/06-events.md new file mode 100644 index 0000000..5d68ce9 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/06-events.md @@ -0,0 +1,26 @@ +--- +title: Events +sidebar_label: Events +sidebar_position: 6 +slug: /ibc/light-clients/wasm/events +--- + +# Events + +The `08-wasm` module emits the following events: + +## `MsgStoreCode` + +| Type | Attribute Key | Attribute Value | +|------------------|----------------|--------------------------| +| store_wasm_code | wasm_checksum | \{hex.Encode(checksum)\} | +| message | module | 08-wasm | + +## `MsgMigrateContract` + +| Type | Attribute Key | Attribute Value | +|------------------|----------------|-----------------------------| +| migrate_contract | client_id | \{clientId\} | +| migrate_contract | wasm_checksum | \{hex.Encode(checksum)\} | +| migrate_contract | new_checksum | \{hex.Encode(newChecksum)\} | +| message | module | 08-wasm | diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/07-contracts.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/07-contracts.md new file mode 100644 index 0000000..48d8bf9 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/07-contracts.md @@ -0,0 +1,110 @@ +--- +title: Contracts +sidebar_label: Contracts +sidebar_position: 7 +slug: /ibc/light-clients/wasm/contracts +--- + +# Contracts + +Learn about the expected behaviour of Wasm light client contracts and the between with `08-wasm`. + +## API + +The `08-wasm` light client proxy performs calls to the Wasm light client via the Wasm VM. The calls require as input JSON-encoded payload messages that fall in the three categories described in the next sections. + +## `InstantiateMessage` + +This is the message sent to the contract's `instantiate` entry point. It contains the bytes of the protobuf-encoded client and consensus states of the underlying light client, both provided in [`MsgCreateClient`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/client/v1/tx.proto#L40-L52). Please note that the bytes contained within the JSON message are represented as base64-encoded strings. + +```go +type InstantiateMessage struct { + ClientState []byte `json:"client_state"` + ConsensusState []byte `json:"consensus_state"` + Checksum []byte `json:"checksum" +} +``` + +The Wasm light client contract is expected to store the client and consensus state in the corresponding keys of the client-prefixed store. + +## `QueryMsg` + +`QueryMsg` acts as a discriminated union type that is used to encode the messages that are sent to the contract's `query` entry point. Only one of the fields of the type should be set at a time, so that the other fields are omitted in the encoded JSON and the payload can be correctly translated to the corresponding element of the enumeration in Rust. + +```go +type QueryMsg struct { + Status *StatusMsg `json:"status,omitempty"` + TimestampAtHeight *TimestampAtHeightMsg `json:"timestamp_at_height,omitempty"` + VerifyClientMessage *VerifyClientMessageMsg `json:"verify_client_message,omitempty"` + CheckForMisbehaviour *CheckForMisbehaviourMsg `json:"check_for_misbehaviour,omitempty"` +} +``` + +```rust +#[cw_serde] +pub enum QueryMsg { + Status(StatusMsg), + TimestampAtHeight(TimestampAtHeightMsg), + VerifyClientMessage(VerifyClientMessageRaw), + CheckForMisbehaviour(CheckForMisbehaviourMsgRaw), +} +``` + +To learn what it is expected from the Wasm light client contract when processing each message, please read the corresponding section of the [Light client developer guide](../01-developer-guide/01-overview.md): + +- For `StatusMsg`, see the section [`Status` method](../01-developer-guide/03-client-state.md#status-method). +- For `TimestampAtHeightMsg`, see the section [`GetTimestampAtHeight` method](../01-developer-guide/03-client-state.md#gettimestampatheight-method). +- For `VerifyClientMessageMsg`, see the section [`VerifyClientMessage`](../01-developer-guide/05-updates-and-misbehaviour.md#verifyclientmessage). +- For `CheckForMisbehaviourMsg`, see the section [`CheckForMisbehaviour` method](../01-developer-guide/03-client-state.md#checkformisbehaviour-method). + +## `SudoMsg` + +`SudoMsg` acts as a discriminated union type that is used to encode the messages that are sent to the contract's `sudo` entry point. Only one of the fields of the type should be set at a time, so that the other fields are omitted in the encoded JSON and the payload can be correctly translated to the corresponding element of the enumeration in Rust. + +The `sudo` entry point is able to perform state-changing writes in the client-prefixed store. + +```go +type SudoMsg struct { + UpdateState *UpdateStateMsg `json:"update_state,omitempty"` + UpdateStateOnMisbehaviour *UpdateStateOnMisbehaviourMsg `json:"update_state_on_misbehaviour,omitempty"` + VerifyUpgradeAndUpdateState *VerifyUpgradeAndUpdateStateMsg `json:"verify_upgrade_and_update_state,omitempty"` + VerifyMembership *VerifyMembershipMsg `json:"verify_membership,omitempty"` + VerifyNonMembership *VerifyNonMembershipMsg `json:"verify_non_membership,omitempty"` + MigrateClientStore *MigrateClientStoreMsg `json:"migrate_client_store,omitempty"` +} +``` + +```rust +#[cw_serde] +pub enum SudoMsg { + UpdateState(UpdateStateMsgRaw), + UpdateStateOnMisbehaviour(UpdateStateOnMisbehaviourMsgRaw), + VerifyUpgradeAndUpdateState(VerifyUpgradeAndUpdateStateMsgRaw), + VerifyMembership(VerifyMembershipMsgRaw), + VerifyNonMembership(VerifyNonMembershipMsgRaw), + MigrateClientStore(MigrateClientStoreMsgRaw), +} +``` + +To learn what it is expected from the Wasm light client contract when processing each message, please read the corresponding section of the [Light client developer guide](../01-developer-guide/01-overview.md): + +- For `UpdateStateMsg`, see the section [`UpdateState`](../01-developer-guide/05-updates-and-misbehaviour.md#updatestate). +- For `UpdateStateOnMisbehaviourMsg`, see the section [`UpdateStateOnMisbehaviour`](../01-developer-guide/05-updates-and-misbehaviour.md#updatestateonmisbehaviour). +- For `VerifyUpgradeAndUpdateStateMsg`, see the section [`GetTimestampAtHeight` method](../01-developer-guide/06-upgrades.md#implementing-verifyupgradeandupdatestate). +- For `VerifyMembershipMsg`, see the section [`VerifyMembership` method](../01-developer-guide/03-client-state.md#verifymembership-method). +- For `VerifyNonMembershipMsg`, see the section [`VerifyNonMembership` method](../01-developer-guide/03-client-state.md#verifynonmembership-method). +- For `MigrateClientStoreMsg`, see the section [Implementing `CheckSubstituteAndUpdateState`](../01-developer-guide/08-proposals.md#implementing-checksubstituteandupdatestate). + +### Migration + +The `08-wasm` proxy light client exposes the `MigrateContract` RPC endpoint that can be used to migrate a given Wasm light client contract (specified by the client identifier) to a new Wasm byte code (specified by the hash of the byte code). The expected use case for this RPC endpoint is to enable contracts to migrate to new byte code in case the current byte code is found to have a bug or vulnerability. The Wasm byte code that contracts are migrated have to be uploaded beforehand using `MsgStoreCode` and must implement the `migrate` entry point. See section[`MsgMigrateContract`](./04-messages.md#msgmigratecontract) for information about the request message for this RPC endpoint. + +## Expected behaviour + +The `08-wasm` proxy light client modules expects the following behaviour from the Wasm light client contracts when executing messages that perform state-changing writes: + +- The contract must not delete the client state from the store. +- The contract must not change the client state to a client state of another type. +- The contract must not change the checksum in the client state. + +Any violation of these rules will result in an error returned from `08-wasm` that will abort the transaction. diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/08-client.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/08-client.md new file mode 100644 index 0000000..9f39830 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/08-client.md @@ -0,0 +1,151 @@ +--- +title: Client +sidebar_label: Client +sidebar_position: 7 +slug: /ibc/light-clients/wasm/client +--- + +# Client + +## CLI + +A user can query and interact with the `08-wasm` module using the CLI. Use the `--help` flag to discover the available commands: + +### Transactions + +The `tx` commands allow users to interact with the `08-wasm` submodule. + +```shell +simd tx ibc-wasm --help +``` + +#### `store-code` + +The `store-code` command allows users to submit a governance proposal with a `MsgStoreCode` to store the byte code of a Wasm light client contract. + +```shell +simd tx ibc-wasm store-code [path/to/wasm-file] [flags] +``` + +`path/to/wasm-file` is the path to the `.wasm` or `.wasm.gz` file. + +#### `migrate-contract` + +The `migrate-contract` command allows users to broadcast a transaction with a `MsgMigrateContract` to migrate the contract for a given light client to a new byte code denoted by the given checksum. + +```shell +simd tx ibc-wasm migrate-contract [client-id] [checksum] [migrate-msg] +``` + +The migrate message must not be emptied and is expected to be a JSON-encoded string. + +### Query + +The `query` commands allow users to query `08-wasm` state. + +```shell +simd query ibc-wasm --help +``` + +#### `checksums` + +The `checksums` command allows users to query the list of checksums of Wasm light client contracts stored in the Wasm VM via the `MsgStoreCode`. The checksums are hex-encoded. + +```shell +simd query ibc-wasm checksums [flags] +``` + +Example: + +```shell +simd query ibc-wasm checksums +``` + +Example Output: + +```shell +checksums: +- c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64 +pagination: + next_key: null + total: "1" +``` + +#### `code` + +The `code` command allows users to query the Wasm byte code of a light client contract given the provided input checksum. + +```shell +./simd q ibc-wasm code +``` + +Example: + +```shell +simd query ibc-wasm code c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64 +``` + +Example Output: + +```shell +code: AGFzb...AqBBE= +``` + +## gRPC + +A user can query the `08-wasm` module using gRPC endpoints. + +### `Checksums` + +The `Checksums` endpoint allows users to query the list of checksums of Wasm light client contracts stored in the Wasm VM via the `MsgStoreCode`. + +```shell +ibc.lightclients.wasm.v1.Query/Checksums +``` + +Example: + +```shell +grpcurl -plaintext \ + -d '{}' \ + localhost:9090 \ + ibc.lightclients.wasm.v1.Query/Checksums +``` + +Example output: + +```shell +{ + "checksums": [ + "c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64" + ], + "pagination": { + "total": "1" + } +} +``` + +### `Code` + +The `Code` endpoint allows users to query the Wasm byte code of a light client contract given the provided input checksum. + +```shell +ibc.lightclients.wasm.v1.Query/Code +``` + +Example: + +```shell +grpcurl -plaintext \ + -d '{"checksum":"c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64"}' \ + localhost:9090 \ + ibc.lightclients.wasm.v1.Query/Code +``` + +Example output: + +```shell +{ + "code": AGFzb...AqBBE= +} +``` diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/09-migrations.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/09-migrations.md new file mode 100644 index 0000000..d29c4b6 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/09-migrations.md @@ -0,0 +1,239 @@ +--- +title: Migrations +sidebar_label: Migrations +sidebar_position: 9 +slug: /ibc/light-clients/wasm/migrations +--- + +# Migrations + +This guide provides instructions for migrating 08-wasm versions. + +Please note that the following releases are retracted. Please refer to the appropriate migrations section for upgrading. + +```bash +v0.3.1-0.20240717085919-bb71eef0f3bf => v0.3.0+ibc-go-v8.3-wasmvm-v2.0 +v0.2.1-0.20240717085554-570d057959e3 => v0.2.0+ibc-go-v7.6-wasmvm-v1.5 +v0.2.1-0.20240523101951-4b45d1822fb6 => v0.2.0+ibc-go-v8.3-wasmvm-v2.0 +v0.1.2-0.20240412103620-7ee2a2452b79 => v0.1.1+ibc-go-v7.3-wasmvm-v1.5 +v0.1.1-0.20231213092650-57fcdb9a9a9d => v0.1.0+ibc-go-v8.0-wasmvm-v1.5 +v0.1.1-0.20231213092633-b306e7a706e1 => v0.1.0+ibc-go-v7.3-wasmvm-v1.5 +``` + +## From ibc-go v8.4.x to ibc-go v9.0.x + +### Chains + +- The `Initialize`, `Status`, `GetTimestampAtHeight`, `GetLatestHeight`, `VerifyMembership`, `VerifyNonMembership`, `VerifyClientMessage`, `UpdateState` and `UpdateStateOnMisbehaviour` functions in `ClientState` have been removed and all their logic has been moved to functions of the `LightClientModule`. +- The `MigrateContract` function has been removed from `ClientState`. +- The `VerifyMembershipMsg` and `VerifyNonMembershipMsg` payloads for `SudoMsg` have been modified. The `Path` field of both structs has been updated from `v1.MerklePath` to `v2.MerklePath`. The new `v2.MerklePath` field contains a `KeyPath` of `[][]byte` as opposed to `[]string`, see [23-commitment](../../05-migrations/13-v8-to-v9.md#23-commitment). This supports proving values stored under keys which contain non-utf8 encoded symbols. As a result, the JSON field `path` containing `key_path` of both messages will marshal elements as a base64 encoded bytestrings. This is a breaking change for 08-wasm client contracts and they should be migrated to correctly support deserialisation of the `v2.MerklePath` field. +- The `ExportMetadataMsg` struct has been removed and is no longer required for contracts to implement. Core IBC will handle exporting all key/value's written to the store by a light client contract. +- The `ZeroCustomFields` interface function has been removed from the `ClientState` interface. Core IBC only used this function to set tendermint client states when scheduling an IBC software upgrade. The interface function has been replaced by a type assertion. +- The `MaxWasmByteSize` function has been removed in favor of the `MaxWasmSize` constant. +- The `HasChecksum`, `GetAllChecksums` and `Logger` functions have been moved from the `types` package to a method on the `Keeper` type in the `keeper` package. +- The `InitializePinnedCodes` function has been moved to a method on the `Keeper` type in the `keeper` package. +- The `CustomQuerier`, `StargateQuerier` and `QueryPlugins` types have been moved from the `types` package to the `keeper` package. +- The `NewDefaultQueryPlugins`, `AcceptListStargateQuerier` and `RejectCustomQuerier` functions has been moved from the `types` package to the `keeper` package. +- The `NewDefaultQueryPlugins` function signature has changed to take an argument: `queryRouter ibcwasm.QueryRouter`. +- The `AcceptListStargateQuerier` function signature has changed to take an additional argument: `queryRouter ibcwasm.QueryRouter`. +- The `WithQueryPlugins` function signature has changed to take in the `QueryPlugins` type from the `keeper` package (previously from the `types` package). +- The `VMGasRegister` variable has been moved from the `types` package to the `keeper` package. + +## From v0.3.0+ibc-go-v8.3-wasmvm-v2.0 to v0.4.1-ibc-go-v8.4-wasmvm-v2.0 + +### Contract developers + +Contract developers are required to update their JSON API message structure for the `SudoMsg` payloads `VerifyMembershipMsg` and `VerifyNonMembershipMsg`. +The `path` field on both JSON API messages has been renamed to `merkle_path`. + +A migration is required for existing 08-wasm client contracts in order to correctly handle the deserialisation of these fields. + +## From v0.2.0+ibc-go-v7.3-wasmvm-v1.5 to v0.3.1-ibc-go-v7.4-wasmvm-v1.5 + +### Contract developers + +Contract developers are required to update their JSON API message structure for the `SudoMsg` payloads `VerifyMembershipMsg` and `VerifyNonMembershipMsg`. +The `path` field on both JSON API messages has been renamed to `merkle_path`. + +A migration is required for existing 08-wasm client contracts in order to correctly handle the deserialisation of these fields. + +## From v0.2.0+ibc-go-v8.3-wasmvm-v2.0 to v0.3.0-ibc-go-v8.3-wasmvm-v2.0 + +### Contract developers + +The `v0.3.0` release of 08-wasm for ibc-go `v8.3.x` and above introduces a breaking change for client contract developers. + +The contract API `SudoMsg` payloads `VerifyMembershipMsg` and `VerifyNonMembershipMsg` have been modified. +The encoding of the `Path` field of both structs has been updated from `v1.MerklePath` to `v2.MerklePath` to support proving values stored under keys which contain non-utf8 encoded symbols. + +As a result, the `Path` field now contains a `MerklePath` composed of `key_path` of `[][]byte` as opposed to `[]string`. The JSON field `path` containing `key_path` of both `VerifyMembershipMsg` and `VerifyNonMembershipMsg` structs will now marshal elements as base64 encoded bytestrings. See below for example JSON diff. + +```diff +{ + "verify_membership": { + "height": { + "revision_height": 1 + }, + "delay_time_period": 0, + "delay_block_period": 0, + "proof":"dmFsaWQgcHJvb2Y=", + "path": { ++ "key_path":["L2liYw==","L2tleS9wYXRo"] +- "key_path":["/ibc","/key/path"] + }, + "value":"dmFsdWU=" + } +} +``` + +A migration is required for existing 08-wasm client contracts in order to correctly handle the deserialisation of `key_path` from `[]string` to `[][]byte`. +Contract developers should familiarise themselves with the migration path offered by 08-wasm [here](./05-governance.md#migrating-an-existing-wasm-light-client-contract). + +An example of the required changes in a client contract may look like: + +```diff +#[cw_serde] +pub struct MerklePath { ++ pub key_path: Vec, +- pub key_path: Vec, +} +``` + +Please refer to the [`cosmwasm_std`](https://docs.rs/cosmwasm-std/2.0.4/cosmwasm_std/struct.Binary.html) documentation for more information. + +## From v0.1.1+ibc-go-v7.3-wasmvm-v1.5 to v0.2.0-ibc-go-v7.3-wasmvm-v1.5 + +### Contract developers + +The `v0.2.0` release of 08-wasm for ibc-go `v7.6.x` and above introduces a breaking change for client contract developers. + +The contract API `SudoMsg` payloads `VerifyMembershipMsg` and `VerifyNonMembershipMsg` have been modified. +The encoding of the `Path` field of both structs has been updated from `v1.MerklePath` to `v2.MerklePath` to support proving values stored under keys which contain non-utf8 encoded symbols. + +As a result, the `Path` field now contains a `MerklePath` composed of `key_path` of `[][]byte` as opposed to `[]string`. The JSON field `path` containing `key_path` of both `VerifyMembershipMsg` and `VerifyNonMembershipMsg` structs will now marshal elements as base64 encoded bytestrings. See below for example JSON diff. + +```diff +{ + "verify_membership": { + "height": { + "revision_height": 1 + }, + "delay_time_period": 0, + "delay_block_period": 0, + "proof":"dmFsaWQgcHJvb2Y=", + "path": { ++ "key_path":["L2liYw==","L2tleS9wYXRo"] +- "key_path":["/ibc","/key/path"] + }, + "value":"dmFsdWU=" + } +} +``` + +A migration is required for existing 08-wasm client contracts in order to correctly handle the deserialisation of `key_path` from `[]string` to `[][]byte`. +Contract developers should familiarise themselves with the migration path offered by 08-wasm [here](./05-governance.md#migrating-an-existing-wasm-light-client-contract). + +An example of the required changes in a client contract may look like: + +```diff +#[cw_serde] +pub struct MerklePath { ++ pub key_path: Vec, +- pub key_path: Vec, +} +``` + +Please refer to the [`cosmwasm_std`](https://docs.rs/cosmwasm-std/2.0.4/cosmwasm_std/struct.Binary.html) documentation for more information. + +## From ibc-go v7.3.x to ibc-go v8.0.x + +### Chains + +In the 08-wasm versions compatible with ibc-go v7.3.x and above from the v7 release line, the checksums of the uploaded Wasm bytecodes are all stored under a single key. From ibc-go v8.0.x the checksums are stored using [`collections.KeySet`](https://docs.cosmos.network/v0.50/build/packages/collections#keyset), whose full functionality became available in Cosmos SDK v0.50. There is therefore an [automatic migration handler](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/module.go#L115-L118) configured in the 08-wasm module to migrate the stored checksums to `collections.KeySet`. + +## From v0.1.0+ibc-go-v8.0-wasmvm-v1.5 to v0.2.0-ibc-go-v8.3-wasmvm-v2.0 + +The `WasmEngine` interface has been updated to reflect changes in the function signatures of Wasm VM: + +```diff +type WasmEngine interface { +- StoreCode(code wasmvm.WasmCode) (wasmvm.Checksum, error) ++ StoreCode(code wasmvm.WasmCode, gasLimit uint64) (wasmvmtypes.Checksum, uint64, error) + + StoreCodeUnchecked(code wasmvm.WasmCode) (wasmvm.Checksum, error) + + Instantiate( + checksum wasmvm.Checksum, + env wasmvmtypes.Env, + info wasmvmtypes.MessageInfo, + initMsg []byte, + store wasmvm.KVStore, + goapi wasmvm.GoAPI, + querier wasmvm.Querier, + gasMeter wasmvm.GasMeter, + gasLimit uint64, + deserCost wasmvmtypes.UFraction, +- ) (*wasmvmtypes.Response, uint64, error) ++ ) (*wasmvmtypes.ContractResult, uint64, error) + + Query( + checksum wasmvm.Checksum, + env wasmvmtypes.Env, + queryMsg []byte, + store wasmvm.KVStore, + goapi wasmvm.GoAPI, + querier wasmvm.Querier, + gasMeter wasmvm.GasMeter, + gasLimit uint64, + deserCost wasmvmtypes.UFraction, +- ) ([]byte, uint64, error) ++ ) (*wasmvmtypes.QueryResult, uint64, error) + + Migrate( + checksum wasmvm.Checksum, + env wasmvmtypes.Env, + migrateMsg []byte, + store wasmvm.KVStore, + goapi wasmvm.GoAPI, + querier wasmvm.Querier, + gasMeter wasmvm.GasMeter, + gasLimit uint64, + deserCost wasmvmtypes.UFraction, +- ) (*wasmvmtypes.Response, uint64, error) ++ ) (*wasmvmtypes.ContractResult, uint64, error) + + Sudo( + checksum wasmvm.Checksum, + env wasmvmtypes.Env, + sudoMsg []byte, + store wasmvm.KVStore, + goapi wasmvm.GoAPI, + querier wasmvm.Querier, + gasMeter wasmvm.GasMeter, + gasLimit uint64, + deserCost wasmvmtypes.UFraction, +- ) (*wasmvmtypes.Response, uint64, error) ++ ) (*wasmvmtypes.ContractResult, uint64, error) + + GetCode(checksum wasmvm.Checksum) (wasmvm.WasmCode, error) + + Pin(checksum wasmvm.Checksum) error + + Unpin(checksum wasmvm.Checksum) error +} +``` + +Similar changes were required in the functions of `MockWasmEngine` interface. + +### Chains + +The `SupportedCapabilities` field of `WasmConfig` is now of type `[]string`: + +```diff +type WasmConfig struct { + DataDir string +- SupportedCapabilities string ++ SupportedCapabilities []string + ContractDebugMode bool +} +``` diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/_category_.json b/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/_category_.json new file mode 100644 index 0000000..51c4eb7 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/04-wasm/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Wasm", + "position": 4, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/05-tendermint/01-overview.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/05-tendermint/01-overview.md new file mode 100644 index 0000000..1971b4c --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/05-tendermint/01-overview.md @@ -0,0 +1,167 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /ibc/light-clients/tendermint/overview +--- + +# `07-tendermint` + +## Overview + +:::note Synopsis +Learn about the 07-tendermint light client module. +::: + +The Tendermint client is the first and most deployed light client in IBC. It implements the IBC [light client module interface](https://github.com/cosmos/ibc-go/blob/v9.0.0-beta.1/modules/core/exported/client.go#L41-L123) to track a counterparty running [CometBFT](https://github.com/cometbft/cometbft) consensus. + +:::note +Tendermint is the old name of CometBFT which has been retained in IBC to avoid expensive migration costs. +::: + +The Tendermint client consists of two important structs that keep track of the state of the counterparty chain and allow for future updates. The `ClientState` struct contains all the parameters necessary for CometBFT header verification. The `ConsensusState`, on the other hand, is a compressed view of a particular header of the counterparty chain. Unlike off chain light clients, IBC does not store full header. Instead it stores only the information it needs to prove verification of key/value pairs in the counterparty state (i.e. the header `AppHash`), and the information necessary to use the consensus state as the next root of trust to add a new consensus state to the client (i.e. the header `NextValidatorsHash` and `Timestamp`). The relayer provides the full trusted header on `UpdateClient`, which will get checked against the compressed root-of-trust consensus state. If the trusted header matches a previous consensus state, and the trusted header and new header pass the CometBFT light client update algorithm, then the new header is compressed into a consensus state and added to the IBC client. + +Each Tendermint Client is composed of a single `ClientState` keyed on the client ID, and multiple consensus states which are keyed on both the clientID and header height. Relayers can use the consensus states to verify merkle proofs of packet commitments, acknowledgements, and receipts against the `AppHash` of the counterparty chain in order to enable verified packet flow. + +If a counterparty chain violates the CometBFT protocol in a way that is detectable to off-chain light clients, this misbehaviour can also be submitted to an IBC client by any off-chain actor. Upon verification of this misbehaviour, the Tendermint IBC Client will freeze, preventing any further packet flow from this malicious chain from occurring. Governance or some other out-of-band protocol may then be used to unwind any damage that has already occurred. + +## Initialization + +The Tendermint light client is initialized with a `ClientState` that contains parameters necessary for CometBFT header verification along with a latest height and `ConsensusState` that encapsulates the application state root of a trusted header that will serve to verify future incoming headers from the counterparty. + +```proto +message ClientState { + // human readable chain-id that will be included in header + // and signed over by the validator set + string chain_id = 1; + // trust level is the fraction of the trusted validator set + // that must sign over a new untrusted header before it is accepted + // it can be a minimum of 1/3 and a maximum of 2/3 + // Note these are the bounds of liveness. 1/3 is the minimum + // honest stake needed to maintain liveness on a chain, + // requiring more than 2/3 to sign over the new header would + // break the BFT threshold of allowing 1/3 malicious validators + Fraction trust_level = 2; + // duration of the period since the LatestTimestamp during which the + // submitted headers are valid for update + google.protobuf.Duration trusting_period = 3; + // duration of the staking unbonding period + google.protobuf.Duration unbonding_period = 4; + // defines how much new (untrusted) header's Time can drift + // into the future relative to our local clock. + google.protobuf.Duration max_clock_drift = 5; + + // Block height when the client was frozen due to a misbehaviour + ibc.core.client.v1.Height frozen_height = 6; + // Latest height the client was updated to + ibc.core.client.v1.Height latest_height = 7; + + // Proof specifications used in verifying counterparty state + repeated cosmos.ics23.v1.ProofSpec proof_specs = 8; + + // Path at which next upgraded client will be committed. + // Each element corresponds to the key for a single CommitmentProof in the + // chained proof. NOTE: ClientState must stored under + // `{upgradePath}/{upgradeHeight}/clientState` ConsensusState must be stored + // under `{upgradepath}/{upgradeHeight}/consensusState` For SDK chains using + // the default upgrade module, upgrade_path should be []string{"upgrade", + // "upgradedIBCState"}` + repeated string upgrade_path = 9; +} +``` + +```proto +message ConsensusState { + // timestamp that corresponds to the block height in which the ConsensusState + // was stored. + google.protobuf.Timestamp timestamp = 1; + // commitment root (i.e app hash) that will be used + // to verify proofs of packet flow messages + ibc.core.commitment.v1.MerkleRoot root = 2; + // hash of the next validator set that will be used as + // a new updated source of trust to verify future updates + bytes next_validators_hash = 3; +} +``` + +## Updates + +Once the initial client state and consensus state are submitted, future consensus states can be added to the client by submitting IBC [headers](https://github.com/cosmos/ibc-go/blob/v9.0.0-beta.1/proto/ibc/lightclients/tendermint/v1/tendermint.proto#L76-L94). These headers contain all necessary information to run the CometBFT light client protocol. + +```proto +message Header { + // this is the new signed header that we want to add + // as a new consensus state to the ibc client. + // the signed header contains the commit signatures of the `validator_set` below + .tendermint.types.SignedHeader signed_header = 1; + + // the validator set which signed the new header + .tendermint.types.ValidatorSet validator_set = 2; + // the trusted height of the consensus state which we are updating from + ibc.core.client.v1.Height trusted_height = 3; + // the trusted validator set, the hash of the trusted validators must be equal to + // `next_validators_hash` of the current consensus state + .tendermint.types.ValidatorSet trusted_validators = 4; +} +``` + +For detailed information on the CometBFT light client protocol and its safety properties please refer to the [original Tendermint whitepaper](https://arxiv.org/abs/1807.04938). + +## Proofs + +As consensus states are added to the client, they can be used for proof verification by relayers wishing to prove packet flow messages against a particular height on the counterparty. This uses the `VerifyMembership` and `VerifyNonMembership` methods on the Tendermint client. + +```go +// VerifyMembership is a generic proof verification method +//which verifies a proof of the existence of a value at a +// given CommitmentPath at the specified height. The caller +// is expected to construct the full CommitmentPath from a +// CommitmentPrefix and a standardized path (as defined in ICS 24). +VerifyMembership( + ctx sdk.Context, + clientID string, + height Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path Path, + value []byte, +) error + +// VerifyNonMembership is a generic proof verification method +// which verifies the absence of a given CommitmentPath at a +// specified height. The caller is expected to construct the +// full CommitmentPath from a CommitmentPrefix and a standardized +// path (as defined in ICS 24). +VerifyNonMembership( + ctx sdk.Context, + clientID string, + height Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path Path, +) error +``` + +The Tendermint client is initialized with an ICS23 proof spec. This allows the Tendermint implementation to support many different merkle tree structures so long as they can be represented in an [`ics23.ProofSpec`](https://github.com/cosmos/ics23/blob/go/v0.10.0/proto/cosmos/ics23/v1/proofs.proto#L145-L170). + +## Misbehaviour + +The Tendermint light client directly tracks consensus of a CometBFT counterparty chain. So long as the counterparty is Byzantine Fault Tolerant, that is to say, the malicious subset of the bonded validators does not exceed the trust level of the client, then the client is secure. + +In case the malicious subset of the validators exceeds the trust level of the client, then the client can be deceived into accepting invalid blocks and the connection is no longer secure. + +The Tendermint client has some mitigations in place to prevent this. If there are two valid blocks signed by the counterparty validator set at the same height [e.g. a valid block signed by an honest subset and an invalid block signed by a malicious one], then these conflicting headers can be submitted to the client as [misbehaviour](https://github.com/cosmos/ibc-go/blob/v9.0.0-beta.1/proto/ibc/lightclients/tendermint/v1/tendermint.proto#L65-L74). The client will verify the headers and freeze the client; preventing any future updates and proof verification from succeeding. This effectively halts communication with the compromised counterparty while out-of-band social consensus can unwind any damage done. + +Similarly, if the timestamps of the headers are not monotonically increasing, this can also be evidence of malicious behaviour and cause the client to freeze. + +Thus, any consensus faults that are detectable by a light client are part of the misbehaviour protocol and can be used to minimize the damage caused by a compromised counterparty chain. + +### Security model + +It is important to note that IBC is not a completely trustless protocol; it is **trust-minimized**. This means that the safety property of bilateral IBC communication between two chains is dependent on the safety properties of the two chains in question. If one of the chains is compromised completely, then the IBC connection to the other chain is liable to receive invalid packets from the malicious chain. For example, if a malicious validator set has taken over more than 2/3 of the validator power on a chain; that malicious validator set can create a single chain of blocks with arbitrary commitment roots and arbitrary commitments to the next validator set. This would seize complete control of the chain and prevent the honest subset from even being able to create a competing honest block. + +In this case, there is no ability for the IBC Tendermint client solely tracking CometBFT consensus to detect the misbehaviour and freeze the client. The IBC protocol would require out-of-band mechanisms to detect and fix such an egregious safety fault on the counterparty chain. Since the Tendermint light client is only tracking consensus and not also verifying the validity of state transitions, malicious behaviour from a validator set that is beyond the BFT fault threshold is an accepted risk of this light client implementation. + +The IBC protocol has principles of fault isolation (e.g. all tokens are prefixed by their channel, so tokens from different chains are not mutually fungible) and fault mitigation (e.g. ability to freeze the client if misbehaviour can be detected before complete malicious takeover) that make this risk as minimal as possible. diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/05-tendermint/_category_.json b/docs/versioned_docs/version-v10.1.x/03-light-clients/05-tendermint/_category_.json new file mode 100644 index 0000000..e45e833 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/05-tendermint/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Tendermint", + "position": 5, + "link": null + } diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/06-proposals.md b/docs/versioned_docs/version-v10.1.x/03-light-clients/06-proposals.md new file mode 100644 index 0000000..85c74b7 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/06-proposals.md @@ -0,0 +1,120 @@ +--- +title: Governance Proposals +sidebar_label: Light Client Recovery +sidebar_position: 6 +slug: /ibc/proposals +--- + +# Light Client Recovery + +In uncommon situations, a highly valued client may become frozen or expire due to uncontrollable +circumstances. A highly valued client might have hundreds of channels being actively used. +Some of those channels might have a significant amount of locked tokens used for ICS 20. + +## Frozen Light Clients + +If the one third of the validator set of the chain the client represents decides to collude, +they can sign off on two valid but conflicting headers each signed by the other one third +of the honest validator set. The light client can now be updated with two valid, but conflicting +headers at the same height. The light client cannot know which header is trustworthy and therefore +evidence of such misbehaviour is likely to be submitted resulting in a frozen light client. + +Frozen light clients cannot be updated under any circumstance except via a governance proposal. +Since a quorum of validators can sign arbitrary state roots which may not be valid executions +of the state machine, a governance proposal has been added to ease the complexity of unfreezing +or updating clients which have become "stuck". Without this mechanism, validator sets would need +to construct a state root to unfreeze the client. Unfreezing clients, re-enables all of the channels +built upon that client. This may result in recovery of otherwise lost funds. + +## Expired Light Clients + +Tendermint light clients may become expired if the trusting period has passed since their +last update. This may occur if relayers stop submitting headers to update the clients. + +An unplanned upgrade by the counterparty chain may also result in expired clients. If the counterparty +chain undergoes an unplanned upgrade, there may be no commitment to that upgrade signed by the validator +set before the chain ID changes. In this situation, the validator set of the last valid update for the +light client is never expected to produce another valid header since the chain ID has changed, which will +ultimately lead the on-chain light client to become expired. + +# How to recover an expired client with a governance proposal + +> **Who is this information for?** +> Although technically anyone can submit the governance proposal to recover an expired client, often it will be **relayer operators** (at least coordinating the submission). + +In the case that a highly valued light client is frozen, expired, or rendered non-updateable, a +governance proposal may be submitted to update this client, known as the subject client. The +proposal includes the client identifier for the subject and the client identifier for a substitute +client. Light client implementations may implement custom updating logic, but in most cases, +the subject will be updated to the latest consensus state of the substitute client, if the proposal passes. +The substitute client is used as a "stand in" while the subject is on trial. It is best practice to create +a substitute client *after* the subject has become frozen to avoid the substitute from also becoming frozen. +An active substitute client allows headers to be submitted during the voting period to prevent accidental expiry +once the proposal passes. + +See also the relevant documentation: [ADR-026, IBC client recovery mechanisms](/architecture/adr-026-ibc-client-recovery-mechanisms) + +## Preconditions + +- There exists an active client (with a known client identifier) for the same counterparty chain as the expired client. +- The governance deposit. + +## Steps + +### Step 1 + +Check if the client is attached to the expected `chain_id`. For example, for an expired Tendermint client representing the Akash chain the client state looks like this on querying the client state: + +```text +{ + client_id: 07-tendermint-146 + client_state: + '@type': /ibc.lightclients.tendermint.v1.ClientState + allow_update_after_expiry: true + allow_update_after_misbehaviour: true + chain_id: akashnet-2 +} +``` + +The client is attached to the expected Akash `chain_id`. Note that although the parameters (`allow_update_after_expiry` and `allow_update_after_misbehaviour`) exist to signal intent, these parameters have been deprecated and will not enforce any checks on the revival of client. See ADR-026 for more context on this deprecation. + +### Step 2 + +Anyone can submit the governance proposal to recover the client by executing the following via CLI. +If the chain is on an ibc-go version older than v8, please see the [relevant documentation](https://ibc.cosmos.network/v7/ibc/proposals). + +- From ibc-go v8 onwards + + ```shell + tx gov submit-proposal [path-to-proposal-json] + ``` + + where `proposal.json` contains: + + ```json + { + "messages": [ + { + "@type": "/ibc.core.client.v1.MsgRecoverClient", + "subject_client_id": "", + "substitute_client_id": "", + "signer": "" + } + ], + "metadata": "", + "deposit": "10stake" + "title": "My proposal", + "summary": "A short summary of my proposal", + "expedited": false + } + ``` + +The `` identifier is the proposed client to be updated. This client must be either frozen or expired. + +The `` represents a substitute client. It carries all the state for the client which may be updated. It must have identical client and chain parameters to the client which may be updated (except for latest height, frozen height, and chain ID). It should be continually updated during the voting period. + +After this, all that remains is deciding who funds the governance deposit and ensuring the governance proposal passes. If it does, the client on trial will be updated to the latest state of the substitute. + +## Important considerations + +Please note that if the counterparty client is also expired, that client will also need to update. This process updates only one client. diff --git a/docs/versioned_docs/version-v10.1.x/03-light-clients/_category_.json b/docs/versioned_docs/version-v10.1.x/03-light-clients/_category_.json new file mode 100644 index 0000000..e42f1b9 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/03-light-clients/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "IBC Light Clients", + "position": 3, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/01-overview.md b/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/01-overview.md new file mode 100644 index 0000000..a5abfd3 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/01-overview.md @@ -0,0 +1,51 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /middleware/callbacks/overview +--- + +# Overview + +Learn about what the Callbacks Middleware is, and how to build custom modules that utilize the Callbacks Middleware functionality + +## What is the Callbacks Middleware? + +IBC was designed with callbacks between core IBC and IBC applications. IBC apps would send a packet to core IBC, and receive a callback on every step of that packet's lifecycle. This allows IBC applications to be built on top of core IBC, and to be able to execute custom logic on packet lifecycle events (e.g. unescrow tokens for ICS-20). + +This setup worked well for off-chain users interacting with IBC applications. However, we are now seeing the desire for secondary applications (e.g. smart contracts, modules) to call into IBC apps as part of their state machine logic and then do some actions on packet lifecycle events. + +The Callbacks Middleware allows for this functionality by allowing the packets of the underlying IBC applications to register callbacks to secondary applications for lifecycle events. These callbacks are then executed by the Callbacks Middleware when the corresponding packet lifecycle event occurs. + +After much discussion, the design was expanded to [an ADR](/architecture/adr-008-app-caller-cbs), and the Callbacks Middleware is an implementation of that ADR. + +## Concepts + +Callbacks Middleware was built with smart contracts in mind, but can be used by any secondary application that wants to allow IBC packets to call into it. Think of the Callbacks Middleware as a bridge between core IBC and a secondary application. + +We have the following definitions: + +- `Underlying IBC application`: The IBC application that is wrapped by the Callbacks Middleware. This is the IBC application that is actually sending and receiving packet lifecycle events from core IBC. For example, the transfer module, or the ICA controller submodule. +- `IBC Actor`: IBC Actor is an on-chain or off-chain entity that can initiate a packet on the underlying IBC application. For example, a smart contract, an off-chain user, or a module that sends a transfer packet are all IBC Actors. +- `Secondary application`: The application that is being called into by the Callbacks Middleware for packet lifecycle events. This is the application that is receiving the callback directly from the Callbacks Middleware module. For example, the `x/wasm` module. +- `Callback Actor`: The on-chain smart contract or module that is registered to receive callbacks from the secondary application. For example, a Wasm smart contract (gatekeeped by the `x/wasm` module). Note that the Callback Actor is not necessarily the same as the IBC Actor. For example, an off-chain user can initiate a packet on the underlying IBC application, but the Callback Actor could be a smart contract. The secondary application may want to check that the IBC Actor is allowed to call into the Callback Actor, for example, by checking that the IBC Actor is the same as the Callback Actor. +- `Callback Address`: Address of the Callback Actor. This is the address that the secondary application will call into when a packet lifecycle event occurs. For example, the address of the Wasm smart contract. +- `Maximum gas limit`: The maximum amount of gas that the Callbacks Middleware will allow the secondary application to use when it executes its custom logic. +- `User defined gas limit`: The amount of gas that the IBC Actor wants to allow the secondary application to use when it executes its custom logic. This is the gas limit that the IBC Actor specifies when it sends a packet to the underlying IBC application. This cannot be greater than the maximum gas limit. + +Think of the secondary application as a bridge between the Callbacks Middleware and the Callback Actor. The secondary application is responsible for executing the custom logic of the Callback Actor when a packet lifecycle event occurs. The secondary application is also responsible for checking that the IBC Actor is allowed to call into the Callback Actor. + +Note that it is possible that the IBC Actor, Secondary Application, and Callback Actor are all the same entity. In which case, the Callback Address should be the secondary application's module address. + +The following diagram shows how a typical `RecvPacket`, `AcknowledgementPacket`, and `TimeoutPacket` execution flow would look like: +![callbacks-middleware](./images/callbackflow.svg) + +And the following diagram shows how a typical `SendPacket` and `WriteAcknowledgement` execution flow would look like: +![callbacks-middleware](./images/ics4-callbackflow.svg) + +## Known Limitations + +- Callbacks are always executed after the underlying IBC application has executed its logic. +- Maximum gas limit is hardcoded manually during wiring. It requires a coordinated upgrade to change the maximum gas limit. +- The receive packet callback does not pass the relayer address to the secondary application. This is so that we can use the same callback for both synchronous and asynchronous acknowledgements. +- The receive packet callback does not pass IBC Actor's address, this is because the IBC Actor lives in the counterparty chain and cannot be trusted. diff --git a/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/02-integration.md b/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/02-integration.md new file mode 100644 index 0000000..332a5e4 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/02-integration.md @@ -0,0 +1,81 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /middleware/callbacks/integration +--- + +# Integration + +Learn how to integrate the callbacks middleware with IBC applications. The following document is intended for developers building on top of the Cosmos SDK and only applies for Cosmos SDK chains. + +:::tip +An example integration for an IBC v2 transfer stack using the callbacks middleware can be found in the [ibc-go module integration](../../01-ibc/02-integration.md) section +::: + +The callbacks middleware is a minimal and stateless implementation of the IBC middleware interface. It does not have a keeper, nor does it store any state. It simply routes IBC middleware messages to the appropriate callback function, which is implemented by the secondary application. Therefore, it doesn't need to be registered as a module, nor does it need to be added to the module manager. It only needs to be added to the IBC application stack. + +## Pre-requisite Readings + +- [IBC middleware development](../../01-ibc/04-middleware/02-develop.md) +- [IBC middleware integration](../../01-ibc/04-middleware/03-integration.md) + +The callbacks middleware, as the name suggests, plays the role of an IBC middleware and as such must be configured by chain developers to route and handle IBC messages correctly. +For Cosmos SDK chains this setup is done via the `app/app.go` file, where modules are constructed and configured in order to bootstrap the blockchain application. + +## Configuring an application stack with the callbacks middleware + +As mentioned in [IBC middleware development](../../01-ibc/04-middleware/02-develop.md) an application stack may be composed of many or no middlewares that nest a base application. +These layers form the complete set of application logic that enable developers to build composable and flexible IBC application stacks. +For example, an application stack may just be a single base application like `transfer`, however, the same application stack composed with `packet-forward-middleware` and `callbacks` will nest the `transfer` base application twice by wrapping it with the callbacks module and then packet forward middleware. + +The callbacks middleware also **requires** a secondary application that will receive the callbacks to implement the [`ContractKeeper`](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/types/expected_keepers.go#L12-L100). The wasmd contract keeper has been implemented [here](https://github.com/CosmWasm/wasmd/tree/main/x/wasm/keeper) and is referenced as the `WasmKeeper`. + +### Transfer + +See below for an example of how to create an application stack using `transfer`, `packet-forward-middleware`, and `callbacks`. Feel free to omit the `packet-forward-middleware` if you do not want to use it. +The following `transferStack` is configured in `app/app.go` and added to the IBC `Router`. +The in-line comments describe the execution flow of packets between the application stack and IBC core. + +```go +// Create Transfer Stack +// SendPacket, since it is originating from the application to core IBC: +// transferKeeper.SendPacket -> callbacks.SendPacket -> feeKeeper.SendPacket -> channel.SendPacket + +// RecvPacket, message that originates from core IBC and goes down to app, the flow is the other way +// channel.RecvPacket -> fee.OnRecvPacket -> callbacks.OnRecvPacket -> transfer.OnRecvPacket + +// transfer stack contains (from top to bottom): +// - IBC Packet Forward Middleware +// - IBC Callbacks Middleware +// - Transfer + +// initialise the gas limit for callbacks, recommended to be 10M for use with cosmwasm contracts +maxCallbackGas := uint64(10_000_000) + +// the keepers for the callbacks middleware +wasmStackIBCHandler := wasm.NewIBCHandler(app.WasmKeeper, app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper) + +// create IBC module from bottom to top of stack +// Create Transfer Stack + var transferStack porttypes.IBCModule + transferStack = transfer.NewIBCModule(app.TransferKeeper) +// callbacks wraps the transfer stack as its base app, and uses PacketForwardKeeper as the ICS4Wrapper +// i.e. packet-forward-middleware is higher on the stack and sits between callbacks and the ibc channel keeper +// Since this is the lowest level middleware of the transfer stack, it should be the first entrypoint for transfer keeper's +// WriteAcknowledgement. + cbStack := ibccallbacks.NewIBCMiddleware(transferStack, app.PacketForwardKeeper, wasmStackIBCHandler, maxCallbackGas) + transferStack = packetforward.NewIBCMiddleware( + cbStack, + app.PacketForwardKeeper, + 0, + packetforwardkeeper.DefaultForwardTransferPacketTimeoutTimestamp, + ) + app.TransferKeeper.WithICS4Wrapper(cbStack) + +// Create static IBC router, add app routes, then set and seal it + ibcRouter := porttypes.NewRouter() + ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) + ibcRouter.AddRoute(wasmtypes.ModuleName, wasmStackIBCHandler) + app.IBCKeeper.SetRouter(ibcRouter) +``` diff --git a/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/03-interfaces.md b/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/03-interfaces.md new file mode 100644 index 0000000..9830f3b --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/03-interfaces.md @@ -0,0 +1,170 @@ +--- +title: Interfaces +sidebar_label: Interfaces +sidebar_position: 3 +slug: /middleware/callbacks/interfaces +--- + +# Interfaces + +The callbacks middleware requires certain interfaces to be implemented by the underlying IBC applications and the secondary application. If you're simply wiring up the callbacks middleware to an existing IBC application stack and a secondary application such as `icacontroller` and `x/wasm`, you can skip this section. + +## Interfaces for developing the Underlying IBC Application + +### `PacketDataUnmarshaler` + +```go +// PacketDataUnmarshaler defines an optional interface which allows a middleware to +// request the packet data to be unmarshaled by the base application. +type PacketDataUnmarshaler interface { + // UnmarshalPacketData unmarshals the packet data into a concrete type + // ctx, portID, channelID are provided as arguments, so that (if needed) + // the packet data can be unmarshaled based on the channel version. + // The version of the underlying app is also returned. + UnmarshalPacketData(ctx sdk.Context, portID, channelID string, bz []byte) (interface{}, string, error) +} +``` + +The callbacks middleware **requires** the underlying ibc application to implement the [`PacketDataUnmarshaler`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/core/05-port/types/module.go#L142-L147) interface so that it can unmarshal the packet data bytes into the appropriate packet data type. This allows usage of interface functions implemented by the packet data type. The packet data type is expected to implement the `PacketDataProvider` interface (see section below), which is used to parse the callback data that is currently stored in the packet memo field for `transfer` and `ica` packets as a JSON string. See its implementation in the [`transfer`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/transfer/ibc_module.go#L303-L313) and [`icacontroller`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/27-interchain-accounts/controller/ibc_middleware.go#L258-L268) modules for reference. + +If the underlying application is a middleware itself, then it can implement this interface by simply passing the function call to its underlying application. See its implementation in the [`fee middleware`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/29-fee/ibc_middleware.go#L368-L378) for reference. + +### `PacketDataProvider` + +```go +// PacketDataProvider defines an optional interfaces for retrieving custom packet data stored on behalf of another application. +// An existing problem in the IBC middleware design is the inability for a middleware to define its own packet data type and insert packet sender provided information. +// A short term solution was introduced into several application's packet data to utilize a memo field to carry this information on behalf of another application. +// This interfaces standardizes that behaviour. Upon realization of the ability for middleware's to define their own packet data types, this interface will be deprecated and removed with time. +type PacketDataProvider interface { + // GetCustomPacketData returns the packet data held on behalf of another application. + // The name the information is stored under should be provided as the key. + // If no custom packet data exists for the key, nil should be returned. + GetCustomPacketData(key string) interface{} +} +``` + +The callbacks middleware also **requires** the underlying ibc application's packet data type to implement the [`PacketDataProvider`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/core/exported/packet.go#L43-L52) interface. This interface is used to retrieve the callback data from the packet data (using the memo field in the case of `transfer` and `ica`). For example, see its implementation in the [`transfer`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/transfer/types/packet.go#L85-L105) module. + +Since middlewares do not have packet types, they do not need to implement this interface. + +### `PacketData` + +```go +// PacketData defines an optional interface which an application's packet data structure may implement. +type PacketData interface { + // GetPacketSender returns the sender address of the packet data. + // If the packet sender is unknown or undefined, an empty string should be returned. + GetPacketSender(sourcePortID string) string +} +``` + +[`PacketData`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/core/exported/packet.go#L36-L41) is an optional interface that can be implemented by the underlying ibc application's packet data type. It is used to retrieve the packet sender address from the packet data. The callbacks middleware uses this interface to retrieve the packet sender address and pass it to the callback function during a source callback. If this interface is not implemented, then the callbacks middleware passes and empty string as the sender address. For example, see its implementation in the [`transfer`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/transfer/types/packet.go#L74-L83) and [`ica`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/27-interchain-accounts/types/packet.go#L78-L92) module. + +This interface was added so that secondary applications can retrieve the packet sender address to perform custom authorization logic if needed. + +Since middlewares do not have packet types, they do not need to implement this interface. + +## Interfaces for developing the Secondary Application + +### `ContractKeeper` + +The callbacks middleware requires the secondary application to implement the [`ContractKeeper`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/callbacks/types/expected_keepers.go#L11-L83) interface. The contract keeper will be invoked at each step of the packet lifecycle. When a packet is sent, if callback information is provided, the contract keeper will be invoked via the `IBCSendPacketCallback`. This allows the contract keeper to prevent packet sends when callback information is provided, for example if the sender is unauthorized to perform callbacks on the given information. If the packet send is successful, the contract keeper on the destination (if present) will be invoked when a packet has been received and the acknowledgement is written, this will occur via `IBCReceivePacketCallback`. At the end of the packet lifecycle, when processing acknowledgements or timeouts, the source contract keeper will be invoked either via `IBCOnAcknowledgementPacket` or `IBCOnTimeoutPacket`. Once a packet has been sent, each step of the packet lifecycle can be processed given that a relayer sets the gas limit to be more than or equal to the required `CommitGasLimit`. State changes performed in the callback will only be committed upon successful execution. + +```go +// ContractKeeper defines the entry points exposed to the VM module which invokes a smart contract +type ContractKeeper interface { + // IBCSendPacketCallback is called in the source chain when a PacketSend is executed. The + // packetSenderAddress is determined by the underlying module, and may be empty if the sender is + // unknown or undefined. The contract is expected to handle the callback within the user defined + // gas limit, and handle any errors, or panics gracefully. + // This entry point is called with a cached context. If an error is returned, then the changes in + // this context will not be persisted, and the error will be propagated to the underlying IBC + // application, resulting in a packet send failure. + // + // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform + // validation on the origin of a given packet. It is recommended to perform the same validation + // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This + // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. + // + // The version provided is the base application version for the given packet send. This allows + // contracts to determine how to unmarshal the packetData. + IBCSendPacketCallback( + cachedCtx sdk.Context, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + packetData []byte, + contractAddress, + packetSenderAddress string, + version string, + ) error + // IBCOnAcknowledgementPacketCallback is called in the source chain when a packet acknowledgement + // is received. The packetSenderAddress is determined by the underlying module, and may be empty if + // the sender is unknown or undefined. The contract is expected to handle the callback within the + // user defined gas limit, and handle any errors, or panics gracefully. + // This entry point is called with a cached context. If an error is returned, then the changes in + // this context will not be persisted, but the packet lifecycle will not be blocked. + // + // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform + // validation on the origin of a given packet. It is recommended to perform the same validation + // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This + // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. + // + // The version provided is the base application version for the given packet send. This allows + // contracts to determine how to unmarshal the packetData. + IBCOnAcknowledgementPacketCallback( + cachedCtx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, + contractAddress, + packetSenderAddress string, + version string, + ) error + // IBCOnTimeoutPacketCallback is called in the source chain when a packet is not received before + // the timeout height. The packetSenderAddress is determined by the underlying module, and may be + // empty if the sender is unknown or undefined. The contract is expected to handle the callback + // within the user defined gas limit, and handle any error, out of gas, or panics gracefully. + // This entry point is called with a cached context. If an error is returned, then the changes in + // this context will not be persisted, but the packet lifecycle will not be blocked. + // + // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform + // validation on the origin of a given packet. It is recommended to perform the same validation + // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This + // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. + // + // The version provided is the base application version for the given packet send. This allows + // contracts to determine how to unmarshal the packetData. + IBCOnTimeoutPacketCallback( + cachedCtx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, + contractAddress, + packetSenderAddress string, + version string, + ) error + // IBCReceivePacketCallback is called in the destination chain when a packet acknowledgement is written. + // The contract is expected to handle the callback within the user defined gas limit, and handle any errors, + // out of gas, or panics gracefully. + // This entry point is called with a cached context. If an error is returned, then the changes in + // this context will not be persisted, but the packet lifecycle will not be blocked. + // + // The version provided is the base application version for the given packet send. This allows + // contracts to determine how to unmarshal the packetData. + IBCReceivePacketCallback( + cachedCtx sdk.Context, + packet ibcexported.PacketI, + ack ibcexported.Acknowledgement, + contractAddress string, + version string, + ) error +} +``` + +These are the callback entry points exposed to the secondary application. The secondary application is expected to execute its custom logic within these entry points. The callbacks middleware will handle the execution of these callbacks and revert the state if needed. + +:::tip +Note that the source callback entry points are provided with the `packetSenderAddress` and MAY choose to use this to perform validation on the origin of a given packet. It is recommended to perform the same validation on all source chain callbacks (SendPacket, AcknowledgePacket, TimeoutPacket). This defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. +::: diff --git a/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/04-events.md b/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/04-events.md new file mode 100644 index 0000000..448a56f --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/04-events.md @@ -0,0 +1,39 @@ +--- +title: Events +sidebar_label: Events +sidebar_position: 4 +slug: /middleware/callbacks/events +--- + +# Events + +An overview of all events related to the callbacks middleware. There are two types of events, `"ibc_src_callback"` and `"ibc_dest_callback"`. + +## Shared Attributes + +Both of these event types share the following attributes: + +| **Attribute Key** | **Attribute Values** | **Optional** | +|:-------------------------:|:---------------------------------------------------------------------------------------:|:------------------:| +| module | "ibccallbacks" | | +| callback_type | **One of**: "send_packet", "acknowledgement_packet", "timeout_packet", "receive_packet" | | +| callback_address | string | | +| callback_exec_gas_limit | string (parsed from uint64) | | +| callback_commit_gas_limit | string (parsed from uint64) | | +| packet_sequence | string (parsed from uint64) | | +| callback_result | **One of**: "success", "failure" | | +| callback_error | string (parsed from callback err) | Yes, if err != nil | + +## `ibc_src_callback` Attributes + +| **Attribute Key** | **Attribute Values** | +|:------------------:|:------------------------:| +| packet_src_port | string (sourcePortID) | +| packet_src_channel | string (sourceChannelID) | + +## `ibc_dest_callback` Attributes + +| **Attribute Key** | **Attribute Values** | +|:-------------------:|:------------------------:| +| packet_dest_port | string (destPortID) | +| packet_dest_channel | string (destChannelID) | diff --git a/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/05-end-users.md b/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/05-end-users.md new file mode 100644 index 0000000..2142168 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/05-end-users.md @@ -0,0 +1,96 @@ +--- +title: End Users +sidebar_label: End Users +sidebar_position: 5 +slug: /middleware/callbacks/end-users +--- + +# Usage + +This section explains how to use the callbacks middleware from the perspective of an IBC Actor. Callbacks middleware provides two types of callbacks: + +- Source callbacks: + - `SendPacket` callback + - `OnAcknowledgementPacket` callback + - `OnTimeoutPacket` callback +- Destination callbacks: + - `ReceivePacket` callback + +For a given channel, the source callbacks are supported if the source chain has the callbacks middleware wired up in the channel's IBC stack. Similarly, the destination callbacks are supported if the destination chain has the callbacks middleware wired up in the channel's IBC stack. + +:::tip +Callbacks are always executed after the packet has been processed by the underlying IBC module. +::: + +:::warning +If the underlying application module is doing an asynchronous acknowledgement on packet receive (for example, if the [packet forward middleware](https://github.com/cosmos/ibc-apps/tree/main/middleware/packet-forward-middleware) is in the stack, and is being used by this packet), then the callbacks middleware will execute the `ReceivePacket` callback after the acknowledgement has been received. +::: + +## Source Callbacks + +Source callbacks are natively supported in the following ibc modules (if they are wrapped by the callbacks middleware): + +- `transfer` +- `icacontroller` + +To have your source callbacks be processed by the callbacks middleware, you must set the memo in the application's packet data to the following format: + +```jsonc +{ + "src_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} +``` + +## Destination Callbacks + +Destination callbacks are natively only supported in the transfer module. Note that wrapping icahost is not supported. This is because icahost should be able to execute an arbitrary transaction anyway, and can call contracts or modules directly. + +To have your destination callbacks processed by the callbacks middleware, you must set the memo in the application's packet data to the following format: + +```jsonc +{ + "dest_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} +``` + +Note that a packet can have both a source and destination callback. + +```jsonc +{ + "src_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + }, + "dest_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} +``` + +# User Defined Gas Limit + +User defined gas limit was added for the following reasons: + +- To prevent callbacks from blocking packet lifecycle. +- To prevent relayers from being able to DOS the callback execution by sending a packet with a low amount of gas. + +:::tip +There is a chain wide parameter that sets the maximum gas limit that a user can set for a callback. This is to prevent a user from setting a gas limit that is too high for relayers. If the `"gas_limit"` is not set in the packet memo, then the maximum gas limit is used. +::: + +These goals are achieved by creating a minimum gas amount required for callback execution. If the relayer provides at least the minimum gas limit for the callback execution, then the packet lifecycle will not be blocked if the callback runs out of gas during execution, and the callback cannot be retried. If the relayer does not provided the minimum amount of gas and the callback executions runs out of gas, the entire tx is reverted and it may be executed again. + +:::tip +`SendPacket` callback is always reverted if the callback execution fails or returns an error for any reason. This is so that the packet is not sent if the callback execution fails. +::: diff --git a/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/06-gas.md b/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/06-gas.md new file mode 100644 index 0000000..869d220 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/06-gas.md @@ -0,0 +1,74 @@ +--- +title: Gas Management +sidebar_label: Gas Management +sidebar_position: 6 +slug: /middleware/callbacks/gas +--- + +# Gas Management + +## Overview + +Executing arbitrary code on a chain can be arbitrarily expensive. In general, a callback may consume infinite gas (think of a callback that loops forever). This is problematic for a few reasons: + +- It can block the packet lifecycle. +- It can be used to consume all of the relayer's funds and gas. +- A relayer can DOS the callback execution by sending a packet with a low amount of gas. + +To prevent these, the callbacks middleware introduces two gas limits: a chain wide gas limit (`maxCallbackGas`) and a user defined gas limit. + +### Chain Wide Gas Limit + +Since the callbacks middleware does not have a keeper, it does not use a governance parameter to set the chain wide gas limit. Instead, the chain wide gas limit is passed in as a parameter to the callbacks middleware during initialization. + +```go +// app.go + +maxCallbackGas := uint64(10_000_000) + +var transferStack porttypes.IBCModule +transferStack = transfer.NewIBCModule(app.TransferKeeper) +transferStack = ibccallbacks.NewIBCMiddleware(transferStack, app.MockContractKeeper, maxCallbackGas) + +// Add transfer stack to IBC Router +ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) +``` + +### User Defined Gas Limit + +The user defined gas limit is set by the IBC Actor during packet creation. The user defined gas limit is set in the packet memo. If the user defined gas limit is not set or if the user defined gas limit is greater than the chain wide gas limit, then the chain wide gas limit is used as the user defined gas limit. + +```jsonc +{ + "src_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + }, + "dest_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} +``` + +## Gas Limit Enforcement + +During a callback execution, there are three types of gas limits that are enforced: + +- User defined gas limit +- Chain wide gas limit +- Context gas limit (amount of gas that the relayer has left for this execution) + +Chain wide gas limit is used as a maximum to the user defined gas limit as explained in the [previous section](#user-defined-gas-limit). It may also be used as a default value if no user gas limit is provided. Therefore, we can ignore the chain wide gas limit for the rest of this section and work with the minimum of the chain wide gas limit and user defined gas limit. This minimum is called the commit gas limit. + +The gas limit enforcement is done by executing the callback inside a cached context with a new gas meter. The gas meter is initialized with the minimum of the commit gas limit and the context gas limit. This minimum is called the execution gas limit. We say that retries are allowed if `context gas limit < commit gas limit`. Otherwise, we say that retries are not allowed. + +If the callback execution fails due to an out of gas error, then the middleware checks if retries are allowed. If retries are not allowed, then it recovers from the out of gas error, consumes execution gas limit from the original context, and continues with the packet life cycle. If retries are allowed, then it panics with an out of gas error to revert the entire tx. The packet can then be submitted again with a higher gas limit. The out of gas panic descriptor is shown below. + +```go +fmt.Sprintf("ibc %s callback out of gas; commitGasLimit: %d", callbackType, callbackData.CommitGasLimit)} +``` + +If the callback execution does not fail due to an out of gas error then the callbacks middleware does not block the packet life cycle regardless of whether retries are allowed or not. diff --git a/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/07-callbacks-IBCv2.md b/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/07-callbacks-IBCv2.md new file mode 100644 index 0000000..74d88b2 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/07-callbacks-IBCv2.md @@ -0,0 +1,29 @@ +--- +title: Callbacks with IBC v2 +sidebar_label: Callbacks with IBC v2 +sidebar_position: 7 +slug: /middleware/callbacks/callbacks-with-v2 +--- + +# Use with IBC v2 + +This page highlights some of the differences between IBC v2 and IBC classic relevant for the callbacks middleware and how to use the module with IBC v2. More details on middleware for IBC v2 can be found in the [middleware section](../../01-ibc/04-middleware/02-developIBCv2.md). + +## Interfaces + +Some of the interface differences are: + +- The callbacks middleware for IBC v2 requires the [`Underlying Application`](../01-callbacks/01-overview.md) to implement the new [`CallbacksCompatibleModuleV2`](https://github.com/cosmos/ibc-go/blob/main/modules/apps/callbacks/types/callbacks.go#L53-L58) interface. +- `channeltypesv2.Payload` is now used instead of `channeltypes.Packet` +- With IBC classic, the `OnRecvPacket` callback returns the `ack`, whereas v2 returns the `recvResult` which is the [status of the packet](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel/v2/types/packet.pb.go#L26-L38): unspecified, success, failue or asynchronous +- `api.WriteAcknowledgementWrapper` is used instead of `ICS4Wrapper.WriteAcknowledgement`. It is only needed if the lower level application is going to write an asynchronous acknowledgement. + +## Contract Developers + +The wasmd contract keeper enables cosmwasm developers to use the callbacks middleware. The [cosmwasm documentation](https://cosmwasm.cosmos.network/ibc/extensions/callbacks) provides information for contract developers. The IBC v2 callbacks implementation uses a `Payload` but reconstructs an IBC classic `Packet` to preserve the cosmwasm contract keeper interface. Additionally contracts must now handle the IBC v2 `ErrorAcknowledgement` sentinel value in the case of a failure. + +The callbacks middleware can be used for transfer + action workflows, for example a transfer and swap on recieve. These workflows require knowledge of the ibc denom that has been recieved. To assist with parsing the ics20 packet, [helper functions](https://github.com/cosmos/solidity-ibc-eureka/blob/a8870b023e58622fb7b3f733572c684851f8e5ee/packages/cosmwasm/ibc-callbacks-helpers/src/ics20.rs#L7-L41) can be found in the solidity-ibc-eureka repository. + +## Integration + +An example integration of the callbacks middleware in a transfer stack that is using IBC v2 can be found in the [ibc-go integration section](../../01-ibc/02-integration.md) diff --git a/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/_category_.json b/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/_category_.json new file mode 100644 index 0000000..4c2654b --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Callbacks Middleware", + "position": 2, + "link": null +} diff --git a/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/images/callbackflow.svg b/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/images/callbackflow.svg new file mode 100644 index 0000000..df58e93 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/images/callbackflow.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/images/ics4-callbackflow.svg b/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/images/ics4-callbackflow.svg new file mode 100644 index 0000000..4b42440 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/04-middleware/01-callbacks/images/ics4-callbackflow.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/versioned_docs/version-v10.1.x/04-middleware/_category_.json b/docs/versioned_docs/version-v10.1.x/04-middleware/_category_.json new file mode 100644 index 0000000..cc8fa26 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/04-middleware/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "IBC Middleware Modules", + "position": 4, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v10.1.x/05-migrations/01-support-denoms-with-slashes.md b/docs/versioned_docs/version-v10.1.x/05-migrations/01-support-denoms-with-slashes.md new file mode 100644 index 0000000..ac1481d --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/05-migrations/01-support-denoms-with-slashes.md @@ -0,0 +1,87 @@ +--- +title: Support transfer of coins whose base denom contains slashes +sidebar_label: Support transfer of coins whose base denom contains slashes +sidebar_position: 1 +slug: /migrations/support-denoms-with-slashes +--- +# Migrating from not supporting base denoms with slashes to supporting base denoms with slashes + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +This document is necessary when chains are upgrading from a version that does not support base denoms with slashes (e.g. v3.0.0) to a version that does (e.g. v3.2.0). All versions of ibc-go smaller than v1.5.0 for the v1.x release line, v2.3.0 for the v2.x release line, and v3.1.0 for the v3.x release line do **NOT** support IBC token transfers of coins whose base denoms contain slashes. Therefore the in-place of genesis migration described in this document are required when upgrading. + +If a chain receives coins of a base denom with slashes before it upgrades to supporting it, the receive may pass however the trace information will be incorrect. + +E.g. If a base denom of `testcoin/testcoin/testcoin` is sent to a chain that does not support slashes in the base denom, the receive will be successful. However, the trace information stored on the receiving chain will be: `Trace: "transfer/{channel-id}/testcoin/testcoin", BaseDenom: "testcoin"`. + +This incorrect trace information must be corrected when the chain does upgrade to fully supporting denominations with slashes. + +To do so, chain binaries should include a migration script that will run when the chain upgrades from not supporting base denominations with slashes to supporting base denominations with slashes. + +## Chains + +### ICS20 - Transfer + +The transfer module will now support slashes in base denoms, so we must iterate over current traces to check if any of them are incorrectly formed and correct the trace information. + +### Upgrade Proposal + +```go +app.UpgradeKeeper.SetUpgradeHandler("MigrateTraces", + func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + // transfer module consensus version has been bumped to 2 + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) +``` + +This is only necessary if there are denom traces in the store with incorrect trace information from previously received coins that had a slash in the base denom. However, it is recommended that any chain upgrading to support base denominations with slashes runs this code for safety. + +For a more detailed sample, please check out the code changes in [this pull request](https://github.com/cosmos/ibc-go/pull/1680). + +### Genesis Migration + +If the chain chooses to add support for slashes in base denoms via genesis export, then the trace information must be corrected during genesis migration. + +The migration code required may look like: + +```go +func migrateGenesisSlashedDenomsUpgrade(appState genutiltypes.AppMap, clientCtx client.Context, genDoc *tmtypes.GenesisDoc) (genutiltypes.AppMap, error) { + if appState[ibctransfertypes.ModuleName] != nil { + transferGenState := &ibctransfertypes.GenesisState{} + clientCtx.Codec.MustUnmarshalJSON(appState[ibctransfertypes.ModuleName], transferGenState) + + substituteTraces := make([]ibctransfertypes.DenomTrace, len(transferGenState.DenomTraces)) + for i, dt := range transferGenState.DenomTraces { + // replace all previous traces with the latest trace if validation passes + // note most traces will have same value + newTrace := ibctransfertypes.ParseDenomTrace(dt.GetFullDenomPath()) + + if err := newTrace.Validate(); err != nil { + substituteTraces[i] = dt + } else { + substituteTraces[i] = newTrace + } + } + + transferGenState.DenomTraces = substituteTraces + + // delete old genesis state + delete(appState, ibctransfertypes.ModuleName) + + // set new ibc transfer genesis state + appState[ibctransfertypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(transferGenState) + } + + return appState, nil +} +``` + +For a more detailed sample, please check out the code changes in [this pull request](https://github.com/cosmos/ibc-go/pull/1528). diff --git a/docs/versioned_docs/version-v10.1.x/05-migrations/02-sdk-to-v1.md b/docs/versioned_docs/version-v10.1.x/05-migrations/02-sdk-to-v1.md new file mode 100644 index 0000000..93e7f58 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/05-migrations/02-sdk-to-v1.md @@ -0,0 +1,195 @@ +--- +title: SDK v0.43 to IBC-Go v1 +sidebar_label: SDK v0.43 to IBC-Go v1 +sidebar_position: 2 +slug: /migrations/sdk-to-v1 +--- + +# Migrating to ibc-go + +This file contains information on how to migrate from the IBC module contained in the SDK 0.41.x and 0.42.x lines to the IBC module in the ibc-go repository based on the 0.44 SDK version. + +## Import Changes + +The most obvious changes is import name changes. We need to change: + +- applications -> apps +- cosmos-sdk/x/ibc -> ibc-go + +On my GNU/Linux based machine I used the following commands, executed in order: + +```shell +grep -RiIl 'cosmos-sdk\/x\/ibc\/applications' | xargs sed -i 's/cosmos-sdk\/x\/ibc\/applications/ibc-go\/modules\/apps/g' +``` + +```shell +grep -RiIl 'cosmos-sdk\/x\/ibc' | xargs sed -i 's/cosmos-sdk\/x\/ibc/ibc-go\/modules/g' +``` + +ref: [explanation of the above commands](https://www.internalpointers.com/post/linux-find-and-replace-text-multiple-files) + +Executing these commands out of order will cause issues. + +Feel free to use your own method for modifying import names. + +NOTE: Updating to the `v0.44.0` SDK release and then running `go mod tidy` will cause a downgrade to `v0.42.0` in order to support the old IBC import paths. +Update the import paths before running `go mod tidy`. + +## Chain Upgrades + +Chains may choose to upgrade via an upgrade proposal or genesis upgrades. Both in-place store migrations and genesis migrations are supported. + +**WARNING**: Please read at least the quick guide for [IBC client upgrades](../01-ibc/05-upgrades/01-quick-guide.md) before upgrading your chain. It is highly recommended you do not change the chain-ID during an upgrade, otherwise you must follow the IBC client upgrade instructions. + +Both in-place store migrations and genesis migrations will: + +- migrate the solo machine client state from v1 to v2 protobuf definitions +- prune all solo machine consensus states +- prune all expired tendermint consensus states + +Chains must set a new connection parameter during either in place store migrations or genesis migration. The new parameter, max expected block time, is used to enforce packet processing delays on the receiving end of an IBC packet flow. Checkout the [docs](https://github.com/cosmos/ibc-go/blob/release/v1.0.x/docs/ibc/proto-docs.md#params-2) for more information. + +### In-Place Store Migrations + +The new chain binary will need to run migrations in the upgrade handler. The fromVM (previous module version) for the IBC module should be 1. This will allow migrations to be run for IBC updating the version from 1 to 2. + +Ex: + +```go +app.UpgradeKeeper.SetUpgradeHandler("my-upgrade-proposal", + func(ctx sdk.Context, _ upgradetypes.Plan, _ module.VersionMap) (module.VersionMap, error) { + // set max expected block time parameter. Replace the default with your expected value + // https://github.com/cosmos/ibc-go/blob/release/v1.0.x/docs/ibc/proto-docs.md#params-2 + app.IBCKeeper.ConnectionKeeper.SetParams(ctx, ibcconnectiontypes.DefaultParams()) + + fromVM := map[string]uint64{ + ... // other modules + "ibc": 1, + ... + } + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) + +``` + +### Genesis Migrations + +To perform genesis migrations, the following code must be added to your existing migration code. + +```go +// add imports as necessary +import ( + ibcv100 "github.com/cosmos/ibc-go/modules/core/legacy/v100" + ibchost "github.com/cosmos/ibc-go/modules/core/24-host" +) + +... + +// add in migrate cmd function +// expectedTimePerBlock is a new connection parameter +// https://github.com/cosmos/ibc-go/blob/release/v1.0.x/docs/ibc/proto-docs.md#params-2 +newGenState, err = ibcv100.MigrateGenesis(newGenState, clientCtx, *genDoc, expectedTimePerBlock) +if err != nil { + return err +} +``` + +**NOTE:** The genesis chain-id, time and height MUST be updated before migrating IBC, otherwise the tendermint consensus state will not be pruned. + +## IBC Keeper Changes + +The IBC Keeper now takes in the Upgrade Keeper. Please add the chains' Upgrade Keeper after the Staking Keeper: + +```diff +// Create IBC Keeper +app.IBCKeeper = ibckeeper.NewKeeper( +- appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, scopedIBCKeeper, ++ appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, +) +``` + +## Proposals + +### UpdateClientProposal + +The `UpdateClient` has been modified to take in two client-identifiers and one initial height. + +### UpgradeProposal + +A new IBC proposal type has been added, `UpgradeProposal`. This handles an IBC (breaking) Upgrade. +The previous `UpgradedClientState` field in an Upgrade `Plan` has been deprecated in favor of this new proposal type. + +### Proposal Handler Registration + +The `ClientUpdateProposalHandler` has been renamed to `ClientProposalHandler`. +It handles both `UpdateClientProposal`s and `UpgradeProposal`s. + +Add this import: + +```diff ++ ibcclienttypes "github.com/cosmos/ibc-go/modules/core/02-client/types" +``` + +Please ensure the governance module adds the correct route: + +```diff +- AddRoute(ibchost.RouterKey, ibcclient.NewClientUpdateProposalHandler(app.IBCKeeper.ClientKeeper)) ++ AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)) +``` + +NOTE: Simapp registration was incorrect in the 0.41.x releases. The `UpdateClient` proposal handler should be registered with the router key belonging to `ibc-go/core/02-client/types` +as shown in the diffs above. + +### Proposal CLI Registration + +Please ensure both proposal type CLI commands are registered on the governance module by adding the following arguments to `gov.NewAppModuleBasic()`: + +Add the following import: + +```diff ++ ibcclientclient "github.com/cosmos/ibc-go/modules/core/02-client/client" +``` + +Register the cli commands: + +```diff +gov.NewAppModuleBasic( + paramsclient.ProposalHandler, distrclient.ProposalHandler, upgradeclient.ProposalHandler, upgradeclient.CancelProposalHandler, ++ ibcclientclient.UpdateClientProposalHandler, ibcclientclient.UpgradeProposalHandler, +), +``` + +REST routes are not supported for these proposals. + +## Proto file changes + +The gRPC querier service endpoints have changed slightly. The previous files used `v1beta1` gRPC route, this has been updated to `v1`. + +The solo machine has replaced the FrozenSequence uint64 field with a IsFrozen boolean field. The package has been bumped from `v1` to `v2` + +## IBC callback changes + +### OnRecvPacket + +Application developers need to update their `OnRecvPacket` callback logic. + +The `OnRecvPacket` callback has been modified to only return the acknowledgement. The acknowledgement returned must implement the `Acknowledgement` interface. The acknowledgement should indicate if it represents a successful processing of a packet by returning true on `Success()` and false in all other cases. A return value of false on `Success()` will result in all state changes which occurred in the callback being discarded. More information can be found in the [documentation](../01-ibc/03-apps/01-apps.md#receiving-packets). + +The `OnRecvPacket`, `OnAcknowledgementPacket`, and `OnTimeoutPacket` callbacks are now passed the `sdk.AccAddress` of the relayer who relayed the IBC packet. Applications may use or ignore this information. + +## IBC Event changes + +The `packet_data` attribute has been deprecated in favor of `packet_data_hex`, in order to provide standardized encoding/decoding of packet data in events. While the `packet_data` event still exists, all relayers and IBC Event consumers are strongly encouraged to switch over to using `packet_data_hex` as soon as possible. + +The `packet_ack` attribute has also been deprecated in favor of `packet_ack_hex` for the same reason stated above. All relayers and IBC Event consumers are strongly encouraged to switch over to using `packet_ack_hex` as soon as possible. + +The `consensus_height` attribute has been removed in the Misbehaviour event emitted. IBC clients no longer have a frozen height and misbehaviour does not necessarily have an associated height. + +## Relevant SDK changes + +- (codec) [\#9226](https://github.com/cosmos/cosmos-sdk/pull/9226) Rename codec interfaces and methods, to follow a general Go interfaces: + - `codec.Marshaler` → `codec.Codec` (this defines objects which serialize other objects) + - `codec.BinaryMarshaler` → `codec.BinaryCodec` + - `codec.JSONMarshaler` → `codec.JSONCodec` + - Removed `BinaryBare` suffix from `BinaryCodec` methods (`MarshalBinaryBare`, `UnmarshalBinaryBare`, ...) + - Removed `Binary` infix from `BinaryCodec` methods (`MarshalBinaryLengthPrefixed`, `UnmarshalBinaryLengthPrefixed`, ...) diff --git a/docs/versioned_docs/version-v10.1.x/05-migrations/03-v1-to-v2.md b/docs/versioned_docs/version-v10.1.x/05-migrations/03-v1-to-v2.md new file mode 100644 index 0000000..13afccd --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/05-migrations/03-v1-to-v2.md @@ -0,0 +1,59 @@ +--- +title: IBC-Go v1 to v2 +sidebar_label: IBC-Go v1 to v2 +sidebar_position: 3 +slug: /migrations/v1-to-v2 +--- +# Migrating from ibc-go v1 to v2 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go -> github.com/cosmos/ibc-go/v2 +``` + +## Chains + +- No relevant changes were made in this release. + +## IBC Apps + +A new function has been added to the app module interface: + +```go +// NegotiateAppVersion performs application version negotiation given the provided channel ordering, connectionID, portID, counterparty and proposed version. +// An error is returned if version negotiation cannot be performed. For example, an application module implementing this interface +// may decide to return an error in the event of the proposed version being incompatible with it's own +NegotiateAppVersion( + ctx sdk.Context, + order channeltypes.Order, + connectionID string, + portID string, + counterparty channeltypes.Counterparty, + proposedVersion string, +) (version string, err error) +``` + +This function should perform application version negotiation and return the negotiated version. If the version cannot be negotiated, an error should be returned. This function is only used on the client side. + +### `sdk.Result` removed + +`sdk.Result` has been removed as a return value in the application callbacks. Previously it was being discarded by core IBC and was thus unused. + +## Relayers + +A new gRPC has been added to 05-port, `AppVersion`. It returns the negotiated app version. This function should be used for the `ChanOpenTry` channel handshake step to decide upon the application version which should be set in the channel. + +## IBC Light Clients + +- No relevant changes were made in this release. diff --git a/docs/versioned_docs/version-v10.1.x/05-migrations/04-v2-to-v3.md b/docs/versioned_docs/version-v10.1.x/05-migrations/04-v2-to-v3.md new file mode 100644 index 0000000..2db12c5 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/05-migrations/04-v2-to-v3.md @@ -0,0 +1,186 @@ +--- +title: IBC-Go v2 to v3 +sidebar_label: IBC-Go v2 to v3 +sidebar_position: 4 +slug: /migrations/v2-to-v3 +--- + +# Migrating from ibc-go v2 to v3 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go/v2 -> github.com/cosmos/ibc-go/v3 +``` + +No genesis or in-place migrations are required when upgrading from v1 or v2 of ibc-go. + +## Chains + +### ICS20 + +The `transferkeeper.NewKeeper(...)` now takes in an ICS4Wrapper. +The ICS4Wrapper should be the IBC Channel Keeper unless ICS 20 is being connected to a middleware application. + +### ICS27 + +ICS27 Interchain Accounts has been added as a supported IBC application of ibc-go. +Please see the [ICS27 documentation](../02-apps/02-interchain-accounts/01-overview.md) for more information. + +### Upgrade Proposal + +If the chain will adopt ICS27, it must set the appropriate params during the execution of the upgrade handler in `app.go`: + +```go +app.UpgradeKeeper.SetUpgradeHandler("v3", + func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + // set the ICS27 consensus version so InitGenesis is not run + fromVM[icatypes.ModuleName] = icamodule.ConsensusVersion() + + // create ICS27 Controller submodule params + controllerParams := icacontrollertypes.Params{ + ControllerEnabled: true, + } + + // create ICS27 Host submodule params + hostParams := icahosttypes.Params{ + HostEnabled: true, + AllowMessages: []string{"/cosmos.bank.v1beta1.MsgSend", ...}, + } + + // initialize ICS27 module + icamodule.InitModule(ctx, controllerParams, hostParams) + + ... + + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) +``` + +The host and controller submodule params only need to be set if the chain integrates those submodules. +For example, if a chain chooses not to integrate a controller submodule, it may pass empty params into `InitModule`. + +#### Add `StoreUpgrades` for ICS27 module + +For ICS27 it is also necessary to [manually add store upgrades](https://docs.cosmos.network/main/learn/advanced/upgrade#add-storeupgrades-for-new-modules) for the new ICS27 module and then configure the store loader to apply those upgrades in `app.go`: + +```go +if upgradeInfo.Name == "v3" && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { + storeUpgrades := store.StoreUpgrades{ + Added: []string{icacontrollertypes.StoreKey, icahosttypes.StoreKey}, + } + + app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) +} +``` + +This ensures that the new module's stores are added to the multistore before the migrations begin. +The host and controller submodule keys only need to be added if the chain integrates those submodules. +For example, if a chain chooses not to integrate a controller submodule, it does not need to add the controller key to the `Added` field. + +### Genesis migrations + +If the chain will adopt ICS27 and chooses to upgrade via a genesis export, then the ICS27 parameters must be set during genesis migration. + +The migration code required may look like: + +```go + controllerGenesisState := icatypes.DefaultControllerGenesis() + // overwrite parameters as desired + controllerGenesisState.Params = icacontrollertypes.Params{ + ControllerEnabled: true, + } + + hostGenesisState := icatypes.DefaultHostGenesis() + // overwrite parameters as desired + hostGenesisState.Params = icahosttypes.Params{ + HostEnabled: true, + AllowMessages: []string{"/cosmos.bank.v1beta1.MsgSend", ...}, + } + + icaGenesisState := icatypes.NewGenesisState(controllerGenesisState, hostGenesisState) + + // set new ics27 genesis state + appState[icatypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(icaGenesisState) +``` + +### Ante decorator + +The field of type `channelkeeper.Keeper` in the `AnteDecorator` structure has been replaced with a field of type `*keeper.Keeper`: + +```diff +type AnteDecorator struct { +- k channelkeeper.Keeper ++ k *keeper.Keeper +} + +- func NewAnteDecorator(k channelkeeper.Keeper) AnteDecorator { ++ func NewAnteDecorator(k *keeper.Keeper) AnteDecorator { + return AnteDecorator{k: k} +} +``` + +## IBC Apps + +### `OnChanOpenTry` must return negotiated application version + +The `OnChanOpenTry` application callback has been modified. +The return signature now includes the application version. +IBC applications must perform application version negotiation in `OnChanOpenTry` using the counterparty version. +The negotiated application version then must be returned in `OnChanOpenTry` to core IBC. +Core IBC will set this version in the TRYOPEN channel. + +### `OnChanOpenAck` will take additional `counterpartyChannelID` argument + +The `OnChanOpenAck` application callback has been modified. +The arguments now include the counterparty channel id. + +### `NegotiateAppVersion` removed from `IBCModule` interface + +Previously this logic was handled by the `NegotiateAppVersion` function. +Relayers would query this function before calling `ChanOpenTry`. +Applications would then need to verify that the passed in version was correct. +Now applications will perform this version negotiation during the channel handshake, thus removing the need for `NegotiateAppVersion`. + +### Channel state will not be set before application callback + +The channel handshake logic has been reorganized within core IBC. +Channel state will not be set in state after the application callback is performed. +Applications must rely only on the passed in channel parameters instead of querying the channel keeper for channel state. + +### IBC application callbacks moved from `AppModule` to `IBCModule` + +Previously, IBC module callbacks were apart of the `AppModule` type. +The recommended approach is to create an `IBCModule` type and move the IBC module callbacks from `AppModule` to `IBCModule` in a separate file `ibc_module.go`. + +The mock module go API has been broken in this release by applying the above format. +The IBC module callbacks have been moved from the mock modules `AppModule` into a new type `IBCModule`. + +As apart of this release, the mock module now supports middleware testing. Please see the [README](https://github.com/cosmos/ibc-go/blob/v3.0.0/testing/README.md#middleware-testing) for more information. + +Please review the [mock](https://github.com/cosmos/ibc-go/blob/v3.0.0/testing/mock/ibc_module.go) and [transfer](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/transfer/ibc_module.go) modules as examples. Additionally, [simapp](https://github.com/cosmos/ibc-go/blob/v3.0.0/testing/simapp/app.go) provides an example of how `IBCModule` types should now be added to the IBC router in favour of `AppModule`. + +### IBC testing package + +`TestChain`s are now created with chainID's beginning from an index of 1. Any calls to `GetChainID(0)` will now fail. Please increment all calls to `GetChainID` by 1. + +## Relayers + +`AppVersion` gRPC has been removed. +The `version` string in `MsgChanOpenTry` has been deprecated and will be ignored by core IBC. +Relayers no longer need to determine the version to use on the `ChanOpenTry` step. +IBC applications will determine the correct version using the counterparty version. + +## IBC Light Clients + +The `GetProofSpecs` function has been removed from the `ClientState` interface. This function was previously unused by core IBC. Light clients which don't use this function may remove it. diff --git a/docs/versioned_docs/version-v10.1.x/05-migrations/05-v3-to-v4.md b/docs/versioned_docs/version-v10.1.x/05-migrations/05-v3-to-v4.md new file mode 100644 index 0000000..cf2c2c3 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/05-migrations/05-v3-to-v4.md @@ -0,0 +1,159 @@ +--- +title: IBC-Go v3 to v4 +sidebar_label: IBC-Go v3 to v4 +sidebar_position: 5 +slug: /migrations/v3-to-v4 +--- +# Migrating from ibc-go v3 to v4 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go/v3 -> github.com/cosmos/ibc-go/v4 +``` + +No genesis or in-place migrations required when upgrading from v1 or v2 of ibc-go. + +## Chains + +### ICS27 - Interchain Accounts + +The controller submodule implements now the 05-port `Middleware` interface instead of the 05-port `IBCModule` interface. Chains that integrate the controller submodule, need to create it with the `NewIBCMiddleware` constructor function. For example: + +```diff +- icacontroller.NewIBCModule(app.ICAControllerKeeper, icaAuthIBCModule) ++ icacontroller.NewIBCMiddleware(icaAuthIBCModule, app.ICAControllerKeeper) +``` + +where `icaAuthIBCModule` is the Interchain Accounts authentication IBC Module. + +### ICS29 - Fee Middleware + +The Fee Middleware module, as the name suggests, plays the role of an IBC middleware and as such must be configured by chain developers to route and handle IBC messages correctly. + +Please read the Fee Middleware integration documentation for an in depth guide on how to configure the module correctly in order to incentivize IBC packets. + +Take a look at the following diff for an [example setup](https://github.com/cosmos/ibc-go/pull/1432/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08aL366) of how to incentivize ics27 channels. + +### Migration to fix support for base denoms with slashes + +As part of [v1.5.0](https://github.com/cosmos/ibc-go/releases/tag/v1.5.0), [v2.3.0](https://github.com/cosmos/ibc-go/releases/tag/v2.3.0) and [v3.1.0](https://github.com/cosmos/ibc-go/releases/tag/v3.1.0) some [migration handler code sample was documented](./01-support-denoms-with-slashes.md#upgrade-proposal) that needs to run in order to correct the trace information of coins transferred using ICS20 whose base denom contains slashes. + +Based on feedback from the community we add now an improved solution to run the same migration that does not require copying a large piece of code over from the migration document, but instead requires only adding a one-line upgrade handler. + +If the chain will migrate to supporting base denoms with slashes, it must set the appropriate params during the execution of the upgrade handler in `app.go`: + +```go +app.UpgradeKeeper.SetUpgradeHandler("MigrateTraces", + func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + // transfer module consensus version has been bumped to 2 + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) +``` + +If a chain receives coins of a base denom with slashes before it upgrades to supporting it, the receive may pass however the trace information will be incorrect. + +E.g. If a base denom of `testcoin/testcoin/testcoin` is sent to a chain that does not support slashes in the base denom, the receive will be successful. However, the trace information stored on the receiving chain will be: `Trace: "transfer/{channel-id}/testcoin/testcoin", BaseDenom: "testcoin"`. + +This incorrect trace information must be corrected when the chain does upgrade to fully supporting denominations with slashes. + +## IBC Apps + +### ICS03 - Connection + +Crossing hellos have been removed from 03-connection handshake negotiation. +`PreviousConnectionId` in `MsgConnectionOpenTry` has been deprecated and is no longer used by core IBC. + +`NewMsgConnectionOpenTry` no longer takes in the `PreviousConnectionId` as crossing hellos are no longer supported. A non-empty `PreviousConnectionId` will fail basic validation for this message. + +### ICS04 - Channel + +The `WriteAcknowledgement` API now takes the `exported.Acknowledgement` type instead of passing in the acknowledgement byte array directly. +This is an API breaking change and as such IBC application developers will have to update any calls to `WriteAcknowledgement`. + +The `OnChanOpenInit` application callback has been modified. +The return signature now includes the application version as detailed in the latest IBC [spec changes](https://github.com/cosmos/ibc/pull/629). + +The `NewErrorAcknowledgement` method signature has changed. +It now accepts an `error` rather than a `string`. This was done in order to prevent accidental state changes. +All error acknowledgements now contain a deterministic ABCI code and error message. It is the responsibility of the application developer to emit error details in events. + +Crossing hellos have been removed from 04-channel handshake negotiation. +IBC Applications no longer need to account from already claimed capabilities in the `OnChanOpenTry` callback. The capability provided by core IBC must be able to be claimed with error. +`PreviousChannelId` in `MsgChannelOpenTry` has been deprecated and is no longer used by core IBC. + +`NewMsgChannelOpenTry` no longer takes in the `PreviousChannelId` as crossing hellos are no longer supported. A non-empty `PreviousChannelId` will fail basic validation for this message. + +### ICS27 - Interchain Accounts + +The `RegisterInterchainAccount` API has been modified to include an additional `version` argument. This change has been made in order to support ICS29 fee middleware, for relayer incentivization of ICS27 packets. +Consumers of the `RegisterInterchainAccount` are now expected to build the appropriate JSON encoded version string themselves and pass it accordingly. +This should be constructed within the interchain accounts authentication module which leverages the APIs exposed via the interchain accounts `controllerKeeper`. If an empty string is passed in the `version` argument, then the version will be initialized to a default value in the `OnChanOpenInit` callback of the controller's handler, so that channel handshake can proceed. + +The following code snippet illustrates how to construct an appropriate interchain accounts `Metadata` and encode it as a JSON bytestring: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +if err := k.icaControllerKeeper.RegisterInterchainAccount(ctx, msg.ConnectionId, msg.Owner, string(appVersion)); err != nil { + return err +} +``` + +Similarly, if the application stack is configured to route through ICS29 fee middleware and a fee enabled channel is desired, construct the appropriate ICS29 `Metadata` type: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +feeMetadata := feetypes.Metadata{ + AppVersion: string(appVersion), + FeeVersion: feetypes.Version, +} + +feeEnabledVersion, err := feetypes.ModuleCdc.MarshalJSON(&feeMetadata) +if err != nil { + return err +} + +if err := k.icaControllerKeeper.RegisterInterchainAccount(ctx, msg.ConnectionId, msg.Owner, string(feeEnabledVersion)); err != nil { + return err +} +``` + +## Relayers + +When using the `DenomTrace` gRPC, the full IBC denomination with the `ibc/` prefix may now be passed in. + +Crossing hellos are no longer supported by core IBC for 03-connection and 04-channel. The handshake should be completed in the logical 4 step process (INIT, TRY, ACK, CONFIRM). diff --git a/docs/versioned_docs/version-v10.1.x/05-migrations/06-v4-to-v5.md b/docs/versioned_docs/version-v10.1.x/05-migrations/06-v4-to-v5.md new file mode 100644 index 0000000..d3fdafb --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/05-migrations/06-v4-to-v5.md @@ -0,0 +1,441 @@ +--- +title: IBC-Go v4 to v5 +sidebar_label: IBC-Go v4 to v5 +sidebar_position: 6 +slug: /migrations/v4-to-v5 +--- + +# Migrating from v4 to v5 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- [Chains](#chains) +- [IBC Apps](#ibc-apps) +- [Relayers](#relayers) +- [IBC Light Clients](#ibc-light-clients) + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go/v4 -> github.com/cosmos/ibc-go/v5 +``` + +## Chains + +### Ante decorator + +The `AnteDecorator` type in `core/ante` has been renamed to `RedundantRelayDecorator` (and the corresponding constructor function to `NewRedundantRelayDecorator`). Therefore in the function that creates the instance of the `sdk.AnteHandler` type (e.g. `NewAnteHandler`) the change would be like this: + +```diff +func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { + // parameter validation + + anteDecorators := []sdk.AnteDecorator{ + // other ante decorators +- ibcante.NewAnteDecorator(opts.IBCkeeper), ++ ibcante.NewRedundantRelayDecorator(options.IBCKeeper), + } + + return sdk.ChainAnteDecorators(anteDecorators...), nil +} +``` + +The `AnteDecorator` was actually renamed twice, but in [this PR](https://github.com/cosmos/ibc-go/pull/1820) you can see the changes made for the final rename. + +## IBC Apps + +### Core + +The `key` parameter of the `NewKeeper` function in `modules/core/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + stakingKeeper clienttypes.StakingKeeper, + upgradeKeeper clienttypes.UpgradeKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +) *Keeper +``` + +The `RegisterRESTRoutes` function in `modules/core` has been removed. + +### ICS03 - Connection + +The `key` parameter of the `NewKeeper` function in `modules/core/03-connection/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + ck types.ClientKeeper +) Keeper +``` + +### ICS04 - Channel + +The function `NewPacketId` in `modules/core/04-channel/types` has been renamed to `NewPacketID`: + +```diff +- func NewPacketId( ++ func NewPacketID( + portID, + channelID string, + seq uint64 +) PacketId +``` + +The `key` parameter of the `NewKeeper` function in `modules/core/04-channel/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + clientKeeper types.ClientKeeper, + connectionKeeper types.ConnectionKeeper, + portKeeper types.PortKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +) Keeper +``` + +### ICS20 - Transfer + +The `key` parameter of the `NewKeeper` function in `modules/apps/transfer/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + ics4Wrapper types.ICS4Wrapper, + channelKeeper types.ChannelKeeper, + portKeeper types.PortKeeper, + authKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +) Keeper +``` + +The `amount` parameter of function `GetTransferCoin` in `modules/apps/transfer/types` is now of type `math.Int` (`"cosmossdk.io/math"`): + +```diff +func GetTransferCoin( + portID, channelID, baseDenom string, +- amount sdk.Int ++ amount math.Int +) sdk.Coin +``` + +The `RegisterRESTRoutes` function in `modules/apps/transfer` has been removed. + +### ICS27 - Interchain Accounts + +The `key` and `msgRouter` parameters of the `NewKeeper` functions in + +- `modules/apps/27-interchain-accounts/controller/keeper` +- and `modules/apps/27-interchain-accounts/host/keeper` + +have changed type. The `key` parameter is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`), and the `msgRouter` parameter is now of type `*icatypes.MessageRouter` (where `icatypes` is an import alias for `"github.com/cosmos/ibc-go/v5/modules/apps/27-interchain-accounts/types"`): + +```diff +// NewKeeper creates a new interchain accounts controller Keeper instance +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + ics4Wrapper icatypes.ICS4Wrapper, + channelKeeper icatypes.ChannelKeeper, + portKeeper icatypes.PortKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +- msgRouter *baseapp.MsgServiceRouter, ++ msgRouter *icatypes.MessageRouter, +) Keeper +``` + +```diff +// NewKeeper creates a new interchain accounts host Keeper instance +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + channelKeeper icatypes.ChannelKeeper, + portKeeper icatypes.PortKeeper, + accountKeeper icatypes.AccountKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +- msgRouter *baseapp.MsgServiceRouter, ++ msgRouter *icatypes.MessageRouter, +) Keeper +``` + +The new `MessageRouter` interface is defined as: + +```go +type MessageRouter interface { + Handler(msg sdk.Msg) baseapp.MsgServiceHandler +} +``` + +The `RegisterRESTRoutes` function in `modules/apps/27-interchain-accounts` has been removed. + +An additional parameter, `ics4Wrapper` has been added to the `host` submodule `NewKeeper` function in `modules/apps/27-interchain-accounts/host/keeper`. +This allows the `host` submodule to correctly unwrap the channel version for channel reopening handshakes in the `OnChanOpenTry` callback. + +```diff +func NewKeeper( + cdc codec.BinaryCodec, + key storetypes.StoreKey, + paramSpace paramtypes.Subspace, ++ ics4Wrapper icatypes.ICS4Wrapper, + channelKeeper icatypes.ChannelKeeper, + portKeeper icatypes.PortKeeper, + accountKeeper icatypes.AccountKeeper, + scopedKeeper icatypes.ScopedKeeper, + msgRouter icatypes.MessageRouter, +) Keeper +``` + +#### Cosmos SDK message handler responses in packet acknowledgement + +The construction of the transaction response of a message execution on the host chain has changed. The `Data` field in the `sdk.TxMsgData` has been deprecated and since Cosmos SDK 0.46 the `MsgResponses` field contains the message handler responses packed into `Any`s. + +For chains on Cosmos SDK 0.45 and below, the message response was constructed like this: + +```go +txMsgData := &sdk.TxMsgData{ + Data: make([]*sdk.MsgData, len(msgs)), +} + +for i, msg := range msgs { + // message validation + + msgResponse, err := k.executeMsg(cacheCtx, msg) + // return if err != nil + + txMsgData.Data[i] = &sdk.MsgData{ + MsgType: sdk.MsgTypeURL(msg), + Data: msgResponse, + } +} + +// emit events + +txResponse, err := proto.Marshal(txMsgData) +// return if err != nil + +return txResponse, nil +``` + +And for chains on Cosmos SDK 0.46 and above, it is now done like this: + +```go +txMsgData := &sdk.TxMsgData{ + MsgResponses: make([]*codectypes.Any, len(msgs)), +} + +for i, msg := range msgs { + // message validation + + protoAny, err := k.executeMsg(cacheCtx, msg) + // return if err != nil + + txMsgData.MsgResponses[i] = protoAny +} + +// emit events + +txResponse, err := proto.Marshal(txMsgData) +// return if err != nil + +return txResponse, nil +``` + +When handling the acknowledgement in the `OnAcknowledgementPacket` callback of a custom ICA controller module, then depending on whether `txMsgData.Data` is empty or not, the logic to handle the message handler response will be different. **Only controller chains on Cosmos SDK 0.46 or above will be able to write the logic needed to handle the response from a host chain on Cosmos SDK 0.46 or above.** + +```go +var ack channeltypes.Acknowledgement +if err := channeltypes.SubModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { + return err +} + +var txMsgData sdk.TxMsgData +if err := proto.Unmarshal(ack.GetResult(), txMsgData); err != nil { + return err +} + +switch len(txMsgData.Data) { +case 0: // for SDK 0.46 and above + for _, msgResponse := range txMsgData.MsgResponses { + // unmarshall msgResponse and execute logic based on the response + } + return nil +default: // for SDK 0.45 and below + for _, msgData := range txMsgData.Data { + // unmarshall msgData and execute logic based on the response + } +} +``` + +See [ADR-03](/architecture/adr-003-ics27-acknowledgement#next-major-version-format) for more information or the [corresponding documentation about authentication modules](../02-apps/02-interchain-accounts/03-auth-modules.md#onacknowledgementpacket). + +### ICS29 - Fee Middleware + +The `key` parameter of the `NewKeeper` function in `modules/apps/29-fee` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + ics4Wrapper types.ICS4Wrapper, + channelKeeper types.ChannelKeeper, + portKeeper types.PortKeeper, + authKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, +) Keeper +``` + +The `RegisterRESTRoutes` function in `modules/apps/29-fee` has been removed. + +### IBC testing package + +The `MockIBCApp` type has been renamed to `IBCApp` (and the corresponding constructor function to `NewIBCApp`). This has resulted therefore in: + +- The `IBCApp` field of the `*IBCModule` in `testing/mock` to change its type as well to `*IBCApp`: + +```diff +type IBCModule struct { + appModule *AppModule +- IBCApp *MockIBCApp // base application of an IBC middleware stack ++ IBCApp *IBCApp // base application of an IBC middleware stack +} +``` + +- The `app` parameter to `*NewIBCModule` in `testing/mock` to change its type as well to `*IBCApp`: + +```diff +func NewIBCModule( + appModule *AppModule, +- app *MockIBCApp ++ app *IBCApp +) IBCModule +``` + +The `MockEmptyAcknowledgement` type has been renamed to `EmptyAcknowledgement` (and the corresponding constructor function to `NewEmptyAcknowledgement`). + +The `TestingApp` interface in `testing` has gone through some modifications: + +- The return type of the function `GetStakingKeeper` is not the concrete type `stakingkeeper.Keeper` anymore (where `stakingkeeper` is an import alias for `"github.com/cosmos/cosmos-sdk/x/staking/keeper"`), but it has been changed to the interface `ibctestingtypes.StakingKeeper` (where `ibctestingtypes` is an import alias for `""github.com/cosmos/ibc-go/v5/testing/types"`). See this [PR](https://github.com/cosmos/ibc-go/pull/2028) for more details. The `StakingKeeper` interface is defined as: + +```go +type StakingKeeper interface { + GetHistoricalInfo(ctx sdk.Context, height int64) (stakingtypes.HistoricalInfo, bool) +} +``` + +- The return type of the function `LastCommitID` has changed to `storetypes.CommitID` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`). + +See the following `git diff` for more details: + +```diff +type TestingApp interface { + abci.Application + + // ibc-go additions + GetBaseApp() *baseapp.BaseApp +- GetStakingKeeper() stakingkeeper.Keeper ++ GetStakingKeeper() ibctestingtypes.StakingKeeper + GetIBCKeeper() *keeper.Keeper + GetScopedIBCKeeper() capabilitykeeper.ScopedKeeper + GetTxConfig() client.TxConfig + + // Implemented by SimApp + AppCodec() codec.Codec + + // Implemented by BaseApp +- LastCommitID() sdk.CommitID ++ LastCommitID() storetypes.CommitID + LastBlockHeight() int64 +} +``` + +The `powerReduction` parameter of the function `SetupWithGenesisValSet` in `testing` is now of type `math.Int` (`"cosmossdk.io/math"`): + +```diff +func SetupWithGenesisValSet( + t *testing.T, + valSet *tmtypes.ValidatorSet, + genAccs []authtypes.GenesisAccount, + chainID string, +- powerReduction sdk.Int, ++ powerReduction math.Int, + balances ...banktypes.Balance +) TestingApp +``` + +The `accAmt` parameter of the functions + +- `AddTestAddrsFromPubKeys` , +- `AddTestAddrs` +- and `AddTestAddrsIncremental` + +in `testing/simapp` are now of type `math.Int` (`"cosmossdk.io/math"`): + +```diff +func AddTestAddrsFromPubKeys( + app *SimApp, + ctx sdk.Context, + pubKeys []cryptotypes.PubKey, +- accAmt sdk.Int, ++ accAmt math.Int +) +func addTestAddrs( + app *SimApp, + ctx sdk.Context, + accNum int, +- accAmt sdk.Int, ++ accAmt math.Int, + strategy GenerateAccountStrategy +) []sdk.AccAddress +func AddTestAddrsIncremental( + app *SimApp, + ctx sdk.Context, + accNum int, +- accAmt sdk.Int, ++ accAmt math.Int +) []sdk.AccAddress +``` + +The `RegisterRESTRoutes` function in `testing/mock` has been removed. + +## Relayers + +- No relevant changes were made in this release. + +## IBC Light Clients + +### ICS02 - Client + +The `key` parameter of the `NewKeeper` function in `modules/core/02-client/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + sk types.StakingKeeper, + uk types.UpgradeKeeper +) Keeper +``` diff --git a/docs/versioned_docs/version-v10.1.x/05-migrations/07-v5-to-v6.md b/docs/versioned_docs/version-v10.1.x/05-migrations/07-v5-to-v6.md new file mode 100644 index 0000000..4fe56a7 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/05-migrations/07-v5-to-v6.md @@ -0,0 +1,299 @@ +--- +title: IBC-Go v5 to v6 +sidebar_label: IBC-Go v5 to v6 +sidebar_position: 7 +slug: /migrations/v5-to-v6 +--- + +# Migrating from ibc-go v5 to v6 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +## Chains + +The `ibc-go/v6` release introduces a new set of migrations for `27-interchain-accounts`. Ownership of ICS27 channel capabilities is transferred from ICS27 authentication modules and will now reside with the ICS27 controller submodule moving forward. + +For chains which contain a custom authentication module using the ICS27 controller submodule this requires a migration function to be included in the chain upgrade handler. A subsequent migration handler is run automatically, asserting the ownership of ICS27 channel capabilities has been transferred successfully. + +This migration is not required for chains which *do not* contain a custom authentication module using the ICS27 controller submodule. + +This migration facilitates the addition of the ICS27 controller submodule `MsgServer` which provides a standardised approach to integrating existing forms of authentication such as `x/gov` and `x/group` provided by the Cosmos SDK. + +For more information please refer to [ADR 009](/architecture/adr-009-v6-ics27-msgserver). + +### Upgrade proposal + +Please refer to [PR #2383](https://github.com/cosmos/ibc-go/pull/2383) for integrating the ICS27 channel capability migration logic or follow the steps outlined below: + +1. Add the upgrade migration logic to chain distribution. This may be, for example, maintained under a package `app/upgrades/v6`. + +```go +package v6 + +import ( + "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + + v6 "github.com/cosmos/ibc-go/v6/modules/apps/27-interchain-accounts/controller/migrations/v6" +) + +const ( + UpgradeName = "v6" +) + +func CreateUpgradeHandler( + mm *module.Manager, + configurator module.Configurator, + cdc codec.BinaryCodec, + capabilityStoreKey *storetypes.KVStoreKey, + capabilityKeeper *capabilitykeeper.Keeper, + moduleName string, +) upgradetypes.UpgradeHandler { + return func(ctx sdk.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + if err := v6.MigrateICS27ChannelCapability(ctx, cdc, capabilityStoreKey, capabilityKeeper, moduleName); err != nil { + return nil, err + } + + return mm.RunMigrations(ctx, configurator, vm) + } +} +``` + +2. Set the upgrade handler in `app.go`. The `moduleName` parameter refers to the authentication module's `ScopedKeeper` name. This is the name provided upon instantiation in `app.go` via the [`x/capability` keeper `ScopeToModule(moduleName string)`](https://github.com/cosmos/cosmos-sdk/blob/v0.46.1/x/capability/keeper/keeper.go#L70) method. [See here for an example in `simapp`](https://github.com/cosmos/ibc-go/blob/v5.0.0/testing/simapp/app.go#L304). + +```go +app.UpgradeKeeper.SetUpgradeHandler( + v6.UpgradeName, + v6.CreateUpgradeHandler( + app.mm, + app.configurator, + app.appCodec, + app.keys[capabilitytypes.ModuleName], + app.CapabilityKeeper, + >>>> moduleName <<<<, + ), +) +``` + +## IBC Apps + +### ICS27 - Interchain Accounts + +#### Controller APIs + +In previous releases of ibc-go, chain developers integrating the ICS27 interchain accounts controller functionality were expected to create a custom `Base Application` referred to as an authentication module, see the section [Building an authentication module](../02-apps/02-interchain-accounts/03-auth-modules.md) from the documentation. + +The `Base Application` was intended to be composed with the ICS27 controller submodule `Keeper` and facilitate many forms of message authentication depending on a chain's particular use case. + +Prior to ibc-go v6 the controller submodule exposed only these two functions (to which we will refer as the legacy APIs): + +- [`RegisterInterchainAccount`](https://github.com/cosmos/ibc-go/blob/v5.0.0/modules/apps/27-interchain-accounts/controller/keeper/account.go#L19) +- [`SendTx`](https://github.com/cosmos/ibc-go/blob/v5.0.0/modules/apps/27-interchain-accounts/controller/keeper/relay.go#L18) + +However, these functions have now been deprecated in favour of the new controller submodule `MsgServer` and will be removed in later releases. + +Both APIs remain functional and maintain backwards compatibility in ibc-go v6, however consumers of these APIs are now recommended to follow the message passing paradigm outlined in Cosmos SDK [ADR 031](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-031-msg-service.md) and [ADR 033](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-033-protobuf-inter-module-comm.md). This is facilitated by the Cosmos SDK [`MsgServiceRouter`](https://github.com/cosmos/cosmos-sdk/blob/main/baseapp/msg_service_router.go#L17) and chain developers creating custom application logic can now omit the ICS27 controller submodule `Keeper` from their module and instead depend on message routing. + +Depending on the use case, developers of custom authentication modules face one of three scenarios: + +![auth-module-decision-tree.png](./images/auth-module-decision-tree.png) + +**My authentication module needs to access IBC packet callbacks** + +Application developers that wish to consume IBC packet callbacks and react upon packet acknowledgements **must** continue using the controller submodule's legacy APIs. The authentication modules will not need a `ScopedKeeper` anymore, though, because the channel capability will be claimed by the controller submodule. For example, given an Interchain Accounts authentication module keeper `ICAAuthKeeper`, the authentication module's `ScopedKeeper` (`scopedICAAuthKeeper`) is not needed anymore and can be removed for the argument list of the keeper constructor function, as shown here: + +```diff +app.ICAAuthKeeper = icaauthkeeper.NewKeeper( + appCodec, + keys[icaauthtypes.StoreKey], + app.ICAControllerKeeper, +- scopedICAAuthKeeper, +) +``` + +Please note that the authentication module's `ScopedKeeper` name is still needed as part of the channel capability migration described in section [Upgrade proposal](#upgrade-proposal) above. Therefore the authentication module's `ScopedKeeper` cannot be completely removed from the chain code until the migration has run. + +In the future, the use of the legacy APIs for accessing packet callbacks will be replaced by IBC Actor Callbacks (see [ADR 008](https://github.com/cosmos/ibc-go/pull/1976) for more details) and it will also be possible to access them with the `MsgServiceRouter`. + +**My authentication module does not need access to IBC packet callbacks** + +The authentication module can migrate from using the legacy APIs and it can be composed instead with the `MsgServiceRouter`, so that the authentication module is able to pass messages to the controller submodule's `MsgServer` to register interchain accounts and send packets to the interchain account. For example, given an Interchain Accounts authentication module keeper `ICAAuthKeeper`, the ICS27 controller submodule keeper (`ICAControllerKeeper`) and authentication module scoped keeper (`scopedICAAuthKeeper`) are not needed anymore and can be replaced with the `MsgServiceRouter`, as shown here: + +```diff +app.ICAAuthKeeper = icaauthkeeper.NewKeeper( + appCodec, + keys[icaauthtypes.StoreKey], +- app.ICAControllerKeeper, +- scopedICAAuthKeeper, ++ app.MsgServiceRouter(), +) +``` + +In your authentication module you can route messages to the controller submodule's `MsgServer` instead of using the legacy APIs. For example, for registering an interchain account: + +```diff +- if err := keeper.icaControllerKeeper.RegisterInterchainAccount( +- ctx, +- connectionID, +- owner.String(), +- version, +- ); err != nil { +- return err +- } ++ msg := controllertypes.NewMsgRegisterInterchainAccount( ++ connectionID, ++ owner.String(), ++ version, ++ ) ++ handler := keeper.msgRouter.Handler(msg) ++ res, err := handler(ctx, msg) ++ if err != nil { ++ return err ++ } +``` + +where `controllertypes` is an import alias for `"github.com/cosmos/ibc-go/v6/modules/apps/27-interchain-accounts/controller/types"`. + +In addition, in this use case the authentication module does not need to implement the `IBCModule` interface anymore. + +**I do not need a custom authentication module anymore** + +If your authentication module does not have any extra functionality compared to the default authentication module added in ibc-go v6 (the `MsgServer`), or if you can use a generic authentication module, such as the `x/auth`, `x/gov` or `x/group` modules from the Cosmos SDK (v0.46 and later), then you can remove your authentication module completely and use instead the gRPC endpoints of the `MsgServer` or the CLI added in ibc-go v6. + +Please remember that the authentication module's `ScopedKeeper` name is still needed as part of the channel capability migration described in section [Upgrade proposal](#upgrade-proposal) above. + +#### Host params + +The ICS27 host submodule default params have been updated to include the `AllowAllHostMsgs` wildcard `*`. +This enables execution of any `sdk.Msg` type for ICS27 registered on the host chain `InterfaceRegistry`. + +```diff +// AllowAllHostMsgs holds the string key that allows all message types on interchain accounts host module +const AllowAllHostMsgs = "*" + +... + +// DefaultParams is the default parameter configuration for the host submodule +func DefaultParams() Params { +- return NewParams(DefaultHostEnabled, nil) ++ return NewParams(DefaultHostEnabled, []string{AllowAllHostMsgs}) +} +``` + +#### API breaking changes + +`SerializeCosmosTx` takes in a `[]proto.Message` instead of `[]sdk.Message`. This allows for the serialization of proto messages without requiring the fulfillment of the `sdk.Msg` interface. + +The `27-interchain-accounts` genesis types have been moved to their own package: `modules/apps/27-interchain-accounts/genesis/types`. +This change facilitates the addition of the ICS27 controller submodule `MsgServer` and avoids cyclic imports. This should have minimal disruption to chain developers integrating `27-interchain-accounts`. + +The ICS27 host submodule `NewKeeper` function in `modules/apps/27-interchain-accounts/host/keeper` now includes an additional parameter of type `ICS4Wrapper`. +This provides the host submodule with the ability to correctly unwrap channel versions in the event of a channel reopening handshake. + +```diff +func NewKeeper( + cdc codec.BinaryCodec, key storetypes.StoreKey, paramSpace paramtypes.Subspace, +- channelKeeper icatypes.ChannelKeeper, portKeeper icatypes.PortKeeper, ++ ics4Wrapper icatypes.ICS4Wrapper, channelKeeper icatypes.ChannelKeeper, portKeeper icatypes.PortKeeper, + accountKeeper icatypes.AccountKeeper, scopedKeeper icatypes.ScopedKeeper, msgRouter icatypes.MessageRouter, +) Keeper +``` + +### ICS29 - `NewKeeper` API change + +The `NewKeeper` function of ICS29 has been updated to remove the `paramSpace` parameter as it was unused. + +```diff +func NewKeeper( +- cdc codec.BinaryCodec, key storetypes.StoreKey, paramSpace paramtypes.Subspace, +- ics4Wrapper types.ICS4Wrapper, channelKeeper types.ChannelKeeper, portKeeper types.PortKeeper, authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, ++ cdc codec.BinaryCodec, key storetypes.StoreKey, ++ ics4Wrapper types.ICS4Wrapper, channelKeeper types.ChannelKeeper, ++ portKeeper types.PortKeeper, authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, +) Keeper { +``` + +### ICS20 - `SendTransfer` is no longer exported + +The `SendTransfer` function of ICS20 has been removed. IBC transfers should now be initiated with `MsgTransfer` and routed to the ICS20 `MsgServer`. + +See below for example: + +```go +if handler := msgRouter.Handler(msgTransfer); handler != nil { + if err := msgTransfer.ValidateBasic(); err != nil { + return nil, err + } + + res, err := handler(ctx, msgTransfer) + if err != nil { + return nil, err + } +} +``` + +### ICS04 - `SendPacket` API change + +The `SendPacket` API has been simplified: + +```diff +// SendPacket is called by a module in order to send an IBC packet on a channel +func (k Keeper) SendPacket( + ctx sdk.Context, + channelCap *capabilitytypes.Capability, +- packet exported.PacketI, +-) error { ++ sourcePort string, ++ sourceChannel string, ++ timeoutHeight clienttypes.Height, ++ timeoutTimestamp uint64, ++ data []byte, ++) (uint64, error) { +``` + +Callers no longer need to pass in a pre-constructed packet. +The destination port/channel identifiers and the packet sequence will be determined by core IBC. +`SendPacket` will return the packet sequence. + +### IBC testing package + +The `SendPacket` API has been simplified: + +```diff +// SendPacket is called by a module in order to send an IBC packet on a channel +func (k Keeper) SendPacket( + ctx sdk.Context, + channelCap *capabilitytypes.Capability, +- packet exported.PacketI, +-) error { ++ sourcePort string, ++ sourceChannel string, ++ timeoutHeight clienttypes.Height, ++ timeoutTimestamp uint64, ++ data []byte, ++) (uint64, error) { +``` + +Callers no longer need to pass in a pre-constructed packet. `SendPacket` will return the packet sequence. + +## Relayers + +- No relevant changes were made in this release. + +## IBC Light Clients + +- No relevant changes were made in this release. diff --git a/docs/versioned_docs/version-v10.1.x/05-migrations/08-v6-to-v7.md b/docs/versioned_docs/version-v10.1.x/05-migrations/08-v6-to-v7.md new file mode 100644 index 0000000..e2214cf --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/05-migrations/08-v6-to-v7.md @@ -0,0 +1,357 @@ +--- +title: IBC-Go v6 to v7 +sidebar_label: IBC-Go v6 to v7 +sidebar_position: 8 +slug: /migrations/v6-to-v7 +--- +# Migrating from ibc-go v6 to v7 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +## Chains + +Chains will perform automatic migrations to remove existing localhost clients and to migrate the solomachine to v3 of the protobuf definition. + +An optional upgrade handler has been added to prune expired tendermint consensus states. It may be used during any upgrade (from v7 onwards). +Add the following to the function call to the upgrade handler in `app/app.go`, to perform the optional state pruning. + +```go +import ( + // ... + ibctmmigrations "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint/migrations" +) + +// ... + +app.UpgradeKeeper.SetUpgradeHandler( + upgradeName, + func(ctx sdk.Context, _ upgradetypes.Plan, _ module.VersionMap) (module.VersionMap, error) { + // prune expired tendermint consensus states to save storage space + _, err := ibctmmigrations.PruneExpiredConsensusStates(ctx, app.Codec, app.IBCKeeper.ClientKeeper) + if err != nil { + return nil, err + } + + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }, +) +``` + +Checkout the logs to see how many consensus states are pruned. + +### Light client registration + +Chains must explicitly register the types of any light client modules it wishes to integrate. + +#### Tendermint registration + +To register the tendermint client, modify the `app.go` file to include the tendermint `AppModuleBasic`: + +```diff +import ( + // ... ++ ibctm "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" +) + +// ... + +ModuleBasics = module.NewBasicManager( + ... + ibc.AppModuleBasic{}, ++ ibctm.AppModuleBasic{}, + ... +) +``` + +It may be useful to reference the [PR](https://github.com/cosmos/ibc-go/pull/2825) which added the `AppModuleBasic` for the tendermint client. + +#### Solo machine registration + +To register the solo machine client, modify the `app.go` file to include the solo machine `AppModuleBasic`: + +```diff +import ( + // ... ++ solomachine "github.com/cosmos/ibc-go/v7/modules/light-clients/06-solomachine" +) + +// ... + +ModuleBasics = module.NewBasicManager( + ... + ibc.AppModuleBasic{}, ++ solomachine.AppModuleBasic{}, + ... +) +``` + +It may be useful to reference the [PR](https://github.com/cosmos/ibc-go/pull/2826) which added the `AppModuleBasic` for the solo machine client. + +### Testing package API + +The `SetChannelClosed` utility method in `testing/endpoint.go` has been updated to `SetChannelState`, which will take a `channeltypes.State` argument so that the `ChannelState` can be set to any of the possible channel states. + +## IBC Apps + +- No relevant changes were made in this release. + +## Relayers + +- No relevant changes were made in this release. + +## IBC Light Clients + +### `ClientState` interface changes + +The `VerifyUpgradeAndUpdateState` function has been modified. The client state and consensus state return values have been removed. + +Light clients **must** handle all management of client and consensus states including the setting of updated client state and consensus state in the client store. + +The `Initialize` method is now expected to set the initial client state, consensus state and any client-specific metadata in the provided store upon client creation. + +The `CheckHeaderAndUpdateState` method has been split into 4 new methods: + +- `VerifyClientMessage` verifies a `ClientMessage`. A `ClientMessage` could be a `Header`, `Misbehaviour`, or batch update. Calls to `CheckForMisbehaviour`, `UpdateState`, and `UpdateStateOnMisbehaviour` will assume that the content of the `ClientMessage` has been verified and can be trusted. An error should be returned if the `ClientMessage` fails to verify. + +- `CheckForMisbehaviour` checks for evidence of a misbehaviour in `Header` or `Misbehaviour` types. + +- `UpdateStateOnMisbehaviour` performs appropriate state changes on a `ClientState` given that misbehaviour has been detected and verified. + +- `UpdateState` updates and stores as necessary any associated information for an IBC client, such as the `ClientState` and corresponding `ConsensusState`. An error is returned if `ClientMessage` is of type `Misbehaviour`. Upon successful update, a list containing the updated consensus state height is returned. + +The `CheckMisbehaviourAndUpdateState` function has been removed from `ClientState` interface. This functionality is now encapsulated by the usage of `VerifyClientMessage`, `CheckForMisbehaviour`, `UpdateStateOnMisbehaviour`. + +The function `GetTimestampAtHeight` has been added to the `ClientState` interface. It should return the timestamp for a consensus state associated with the provided height. + +Prior to ibc-go/v7 the `ClientState` interface defined a method for each data type which was being verified in the counterparty state store. +The state verification functions for all IBC data types have been consolidated into two generic methods, `VerifyMembership` and `VerifyNonMembership`. +Both are expected to be provided with a standardised key path, `exported.Path`, as defined in [ICS 24 host requirements](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements). Membership verification requires callers to provide the marshalled value `[]byte`. Delay period values should be zero for non-packet processing verification. A zero proof height is now allowed by core IBC and may be passed into `VerifyMembership` and `VerifyNonMembership`. Light clients are responsible for returning an error if a zero proof height is invalid behaviour. + +See below for an example of how ibc-go now performs channel state verification. + +```go +merklePath := commitmenttypes.NewMerklePath(host.ChannelPath(portID, channelID)) +merklePath, err := commitmenttypes.ApplyPrefix(connection.GetCounterparty().GetPrefix(), merklePath) +if err != nil { + return err +} + +channelEnd, ok := channel.(channeltypes.Channel) +if !ok { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "invalid channel type %T", channel) +} + +bz, err := k.cdc.Marshal(&channelEnd) +if err != nil { + return err +} + +if err := clientState.VerifyMembership( + ctx, clientStore, k.cdc, height, + 0, 0, // skip delay period checks for non-packet processing verification + proof, merklePath, bz, +); err != nil { + return sdkerrors.Wrapf(err, "failed channel state verification for client (%s)", clientID) +} +``` + +### `Header` and `Misbehaviour` + +`exported.Header` and `exported.Misbehaviour` interface types have been merged and renamed to `ClientMessage` interface. + +`GetHeight` function has been removed from `exported.Header` and thus is not included in the `ClientMessage` interface + +### `ConsensusState` + +The `GetRoot` function has been removed from consensus state interface since it was not used by core IBC. + +### Client keeper + +Keeper function `CheckMisbehaviourAndUpdateState` has been removed since function `UpdateClient` can now handle updating `ClientState` on `ClientMessage` type which can be any `Misbehaviour` implementations. + +### SDK message + +`MsgSubmitMisbehaviour` is deprecated since `MsgUpdateClient` can now submit a `ClientMessage` type which can be any `Misbehaviour` implementations. + +The field `header` in `MsgUpdateClient` has been renamed to `client_message`. + +## Solomachine + +The `06-solomachine` client implementation has been simplified in ibc-go/v7. In-place store migrations have been added to migrate solomachine clients from `v2` to `v3`. + +### `ClientState` + +The `ClientState` protobuf message definition has been updated to remove the deprecated `bool` field `allow_update_after_proposal`. + +```diff +message ClientState { + option (gogoproto.goproto_getters) = false; + + uint64 sequence = 1; + bool is_frozen = 2 [(gogoproto.moretags) = "yaml:\"is_frozen\""]; + ConsensusState consensus_state = 3 [(gogoproto.moretags) = "yaml:\"consensus_state\""]; +- bool allow_update_after_proposal = 4 [(gogoproto.moretags) = "yaml:\"allow_update_after_proposal\""]; +} +``` + +### `Header` and `Misbehaviour` + +The `06-solomachine` protobuf message `Header` has been updated to remove the `sequence` field. This field was seen as redundant as the implementation can safely rely on the `sequence` value maintained within the `ClientState`. + +```diff +message Header { + option (gogoproto.goproto_getters) = false; + +- uint64 sequence = 1; +- uint64 timestamp = 2; +- bytes signature = 3; +- google.protobuf.Any new_public_key = 4 [(gogoproto.moretags) = "yaml:\"new_public_key\""]; +- string new_diversifier = 5 [(gogoproto.moretags) = "yaml:\"new_diversifier\""]; ++ uint64 timestamp = 1; ++ bytes signature = 2; ++ google.protobuf.Any new_public_key = 3 [(gogoproto.moretags) = "yaml:\"new_public_key\""]; ++ string new_diversifier = 4 [(gogoproto.moretags) = "yaml:\"new_diversifier\""]; +} +``` + +Similarly, the `Misbehaviour` protobuf message has been updated to remove the `client_id` field. + +```diff +message Misbehaviour { + option (gogoproto.goproto_getters) = false; + +- string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""]; +- uint64 sequence = 2; +- SignatureAndData signature_one = 3 [(gogoproto.moretags) = "yaml:\"signature_one\""]; +- SignatureAndData signature_two = 4 [(gogoproto.moretags) = "yaml:\"signature_two\""]; ++ uint64 sequence = 1; ++ SignatureAndData signature_one = 2 [(gogoproto.moretags) = "yaml:\"signature_one\""]; ++ SignatureAndData signature_two = 3 [(gogoproto.moretags) = "yaml:\"signature_two\""]; +} +``` + +### `SignBytes` + +Most notably, the `SignBytes` protobuf definition has been modified to replace the `data_type` field with a new field, `path`. The `path` field is defined as `bytes` and represents a serialized [ICS-24](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements) standardized key path under which the `data` is stored. + +```diff +message SignBytes { + option (gogoproto.goproto_getters) = false; + + uint64 sequence = 1; + uint64 timestamp = 2; + string diversifier = 3; +- DataType data_type = 4 [(gogoproto.moretags) = "yaml:\"data_type\""]; ++ bytes path = 4; + bytes data = 5; +} +``` + +The `DataType` enum and all associated data types have been removed, greatly reducing the number of message definitions and complexity in constructing the `SignBytes` message type. Likewise, solomachine implementations must now use the serialized `path` value when constructing `SignatureAndData` for signature verification of `SignBytes` data. + +```diff +message SignatureAndData { + option (gogoproto.goproto_getters) = false; + + bytes signature = 1; +- DataType data_type = 2 [(gogoproto.moretags) = "yaml:\"data_type\""]; ++ bytes path = 2; + bytes data = 3; + uint64 timestamp = 4; +} +``` + +For more information, please refer to [ADR-007](https://github.com/cosmos/ibc-go/blob/02-client-refactor-beta1/docs/architecture/adr-007-solomachine-signbytes.md). + +### IBC module constants + +IBC module constants have been moved from the `host` package to the `exported` package. Any usages will need to be updated. + +```diff +import ( + // ... +- host "github.com/cosmos/ibc-go/v7/modules/core/24-host" ++ ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" + // ... +) + +- host.ModuleName ++ ibcexported.ModuleName + +- host.StoreKey ++ ibcexported.StoreKey + +- host.QuerierRoute ++ ibcexported.QuerierRoute + +- host.RouterKey ++ ibcexported.RouterKey +``` + +## Upgrading to Cosmos SDK 0.47 + +The following should be considered as complementary to [Cosmos SDK v0.47 UPGRADING.md](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc2/UPGRADING.md). + +### Protobuf + +Protobuf code generation, linting and formatting have been updated to leverage the `ghcr.io/cosmos/proto-builder:0.11.5` docker container. IBC protobuf definitions are now packaged and published to [buf.build/cosmos/ibc](https://buf.build/cosmos/ibc) via CI workflows. The `third_party/proto` directory has been removed in favour of dependency management using [buf.build](https://docs.buf.build/introduction). + +### App modules + +Legacy APIs of the `AppModule` interface have been removed from ibc-go modules. For example, for + +```diff +- // Route implements the AppModule interface +- func (am AppModule) Route() sdk.Route { +- return sdk.Route{} +- } +- +- // QuerierRoute implements the AppModule interface +- func (AppModule) QuerierRoute() string { +- return types.QuerierRoute +- } +- +- // LegacyQuerierHandler implements the AppModule interface +- func (am AppModule) LegacyQuerierHandler(*codec.LegacyAmino) sdk.Querier { +- return nil +- } +- +- // ProposalContents doesn't return any content functions for governance proposals. +- func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalContent { +- return nil +- } +``` + +### Imports + +Imports for ics23 have been updated as the repository have been migrated from confio to cosmos. + +```diff +import ( + // ... +- ics23 "github.com/confio/ics23/go" ++ ics23 "github.com/cosmos/ics23/go" + // ... +) +``` + +Imports for gogoproto have been updated. + +```diff +import ( + // ... +- "github.com/gogo/protobuf/proto" ++ "github.com/cosmos/gogoproto/proto" + // ... +) +``` diff --git a/docs/versioned_docs/version-v10.1.x/05-migrations/09-v7-to-v7_1.md b/docs/versioned_docs/version-v10.1.x/05-migrations/09-v7-to-v7_1.md new file mode 100644 index 0000000..04b7b30 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/05-migrations/09-v7-to-v7_1.md @@ -0,0 +1,66 @@ +--- +title: IBC-Go v7 to v7.1 +sidebar_label: IBC-Go v7 to v7.1 +sidebar_position: 9 +slug: /migrations/v7-to-v7_1 +--- + +# Migrating from v7 to v7.1 + +This guide provides instructions for migrating to version `v7.1.0` of ibc-go. + +There are four sections based on the four potential user groups of this document: + +- [Migrating from v7 to v7.1](#migrating-from-v7-to-v71) + - [Chains](#chains) + - [IBC Apps](#ibc-apps) + - [Relayers](#relayers) + - [IBC Light Clients](#ibc-light-clients) + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. + +## Chains + +In the previous release of ibc-go, the localhost `v1` light client module was deprecated and removed. The ibc-go `v7.1.0` release introduces `v2` of the 09-localhost light client module. + +An [automatic migration handler](https://github.com/cosmos/ibc-go/blob/v7.2.0/modules/core/module.go#L127-L145) is configured in the core IBC module to set the localhost `ClientState` and sentinel `ConnectionEnd` in state. + +In order to use the 09-localhost client chains must update the `AllowedClients` parameter in the 02-client submodule of core IBC. This can be configured directly in the application upgrade handler or alternatively updated via the legacy governance parameter change proposal. +We **strongly** recommend chains to perform this action so that intra-ledger communication can be carried out using the familiar IBC interfaces. + +See the upgrade handler code sample provided below or [follow this link](https://github.com/cosmos/ibc-go/blob/v7.2.0/testing/simapp/upgrades/upgrades.go#L85) for the upgrade handler used by the ibc-go simapp. + +```go +func CreateV7LocalhostUpgradeHandler( + mm *module.Manager, + configurator module.Configurator, + clientKeeper clientkeeper.Keeper, +) upgradetypes.UpgradeHandler { + return func(ctx sdk.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + // explicitly update the IBC 02-client params, adding the localhost client type + params := clientKeeper.GetParams(ctx) + params.AllowedClients = append(params.AllowedClients, exported.Localhost) + clientKeeper.SetParams(ctx, params) + + return mm.RunMigrations(ctx, configurator, vm) + } +} +``` + +### Transfer migration + +An [automatic migration handler](https://github.com/cosmos/ibc-go/blob/v7.2.0/modules/apps/transfer/module.go#L111-L113) is configured in the transfer module to set the total amount in escrow for all denominations of coins that have been sent out. For each denomination a state entry is added with the total amount of coins in escrow regardless of the channel from which they were transferred. + +## IBC Apps + +- No relevant changes were made in this release. + +## Relayers + +The event attribute `packet_connection` (`connectiontypes.AttributeKeyConnection`) has been deprecated. +Please use the `connection_id` attribute (`connectiontypes.AttributeKeyConnectionID`) which is emitted by all channel events. +Only send packet, receive packet, write acknowledgement, and acknowledge packet events used `packet_connection` previously. + +## IBC Light Clients + +- No relevant changes were made in this release. diff --git a/docs/versioned_docs/version-v10.1.x/05-migrations/10-v7_2-to-v7_3.md b/docs/versioned_docs/version-v10.1.x/05-migrations/10-v7_2-to-v7_3.md new file mode 100644 index 0000000..762bf2b --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/05-migrations/10-v7_2-to-v7_3.md @@ -0,0 +1,50 @@ +--- +title: IBC-Go v7.2 to v7.3 +sidebar_label: IBC-Go v7.2 to v7.3 +sidebar_position: 10 +slug: /migrations/v7_2-to-v7_3 +--- + +# Migrating from v7.2 to v7.3 + +This guide provides instructions for migrating to version `v7.3.0` of ibc-go. + +There are four sections based on the four potential user groups of this document: + +- [Migrating from v7.2 to v7.3](#migrating-from-v72-to-v73) + - [Chains](#chains) + - [IBC Apps](#ibc-apps) + - [Relayers](#relayers) + - [IBC Light Clients](#ibc-light-clients) + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. + +## Chains + +- No relevant changes were made in this release. + +## IBC Apps + +A set of interfaces have been added that IBC applications may optionally implement. Developers interested in integrating their applications with the [callbacks middleware](../04-middleware/01-callbacks/01-overview.md) should implement these interfaces so that the callbacks middleware can retrieve the desired callback addresses on the source and destination chains and execute actions on packet lifecycle events. The interfaces are [`PacketDataUnmarshaler`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/core/05-port/types/module.go#L142-L147), [`PacketDataProvider`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/core/exported/packet.go#L43-L52) and [`PacketData`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/core/exported/packet.go#L36-L41). + +Sample implementations are available for reference. For `transfer`: + +- [`PacketDataUnmarshaler`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/transfer/ibc_module.go#L303-L313), +- [`PacketDataProvider`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/transfer/types/packet.go#L85-L105) +- and [`PacketData`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/transfer/types/packet.go#L74-L83). + +For `27-interchain-accounts`: + +- [`PacketDataUnmarshaler`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/27-interchain-accounts/controller/ibc_middleware.go#L258-L268), +- [`PacketDataProvider`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/27-interchain-accounts/types/packet.go#L94-L114) +- and [`PacketData`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/27-interchain-accounts/types/packet.go#L78-L92). + +## Relayers + +- No relevant changes were made in this release. + +## IBC Light Clients + +### 06-solomachine + +Solo machines are now expected to sign data on a path that 1) does not include a connection prefix (e.g `ibc`) and 2) does not escape any characters. See PR [#4429](https://github.com/cosmos/ibc-go/pull/4429) for more details. We recommend **NOT** using the solo machine light client of versions lower than v7.3.0. diff --git a/docs/versioned_docs/version-v10.1.x/05-migrations/11-v7-to-v8.md b/docs/versioned_docs/version-v10.1.x/05-migrations/11-v7-to-v8.md new file mode 100644 index 0000000..b9814ff --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/05-migrations/11-v7-to-v8.md @@ -0,0 +1,221 @@ +--- +title: IBC-Go v7 to v8 +sidebar_label: IBC-Go v7 to v8 +sidebar_position: 11 +slug: /migrations/v7-to-v8 +--- + +# Migrating from v7 to v8 + +This guide provides instructions for migrating to version `v8.0.0` of ibc-go. + +There are four sections based on the four potential user groups of this document: + +- [Migrating from v7 to v8](#migrating-from-v7-to-v8) + - [Chains](#chains) + - [Cosmos SDK v0.50 upgrade](#cosmos-sdk-v050-upgrade) + - [Authority](#authority) + - [Testing package](#testing-package) + - [Params migration](#params-migration) + - [Governance V1 migration](#governance-v1-migration) + - [Transfer migration](#transfer-migration) + - [IBC Apps](#ibc-apps) + - [ICS20 - Transfer](#ics20---transfer) + - [ICS27 - Interchain Accounts](#ics27---interchain-accounts) + - [Relayers](#relayers) + - [IBC Light Clients](#ibc-light-clients) + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. + +## Chains + +The type of the `PortKeeper` field of the IBC keeper have been changed to `*portkeeper.Keeper`: + +```diff +// Keeper defines each ICS keeper for IBC +type Keeper struct { + // implements gRPC QueryServer interface + types.QueryServer + + cdc codec.BinaryCodec + + ClientKeeper clientkeeper.Keeper + ConnectionKeeper connectionkeeper.Keeper + ChannelKeeper channelkeeper.Keeper +- PortKeeper portkeeper.Keeper ++ PortKeeper *portkeeper.Keeper + Router *porttypes.Router + + authority string +} +``` + +See [this PR](https://github.com/cosmos/ibc-go/pull/4703/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08a) for the changes required in `app.go`. + +An extra parameter `totalEscrowed` of type `sdk.Coins` has been added to transfer module's [`NewGenesisState` function](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/transfer/types/genesis.go#L10). This parameter specifies the total amount of tokens that are in the module's escrow accounts. + +### Cosmos SDK v0.50 upgrade + +Version `v8.0.0` of ibc-go upgrades to Cosmos SDK v0.50. Please follow the [Cosmos SDK v0.50 upgrading guide](https://github.com/cosmos/cosmos-sdk/blob/v0.50.1/UPGRADING.md) to account for its API breaking changes. + +### Authority + +An authority identifier (e.g. an address) needs to be passed in the `NewKeeper` functions of the following keepers: + +- You must pass the `authority` to the ica/host keeper (implemented in [#3520](https://github.com/cosmos/ibc-go/pull/3520)). See [diff](https://github.com/cosmos/ibc-go/pull/3520/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08a): + +```diff +// app.go + +// ICA Host keeper +app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), + app.IBCFeeKeeper, // use ics29 fee as ics4Wrapper in middleware stack + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.AccountKeeper, scopedICAHostKeeper, app.MsgServiceRouter(), ++ authtypes.NewModuleAddress(govtypes.ModuleName).String(), +) +``` + +- You must pass the `authority` to the ica/controller keeper (implemented in [#3590](https://github.com/cosmos/ibc-go/pull/3590)). See [diff](https://github.com/cosmos/ibc-go/pull/3590/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08a): + +```diff +// app.go + +// ICA Controller keeper +app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName), + app.IBCFeeKeeper, // use ics29 fee as ics4Wrapper in middleware stack + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + scopedICAControllerKeeper, app.MsgServiceRouter(), ++ authtypes.NewModuleAddress(govtypes.ModuleName).String(), +) +``` + +- You must pass the `authority` to the ibctransfer keeper (implemented in [#3553](https://github.com/cosmos/ibc-go/pull/3553)). See [diff](https://github.com/cosmos/ibc-go/pull/3553/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08a): + +```diff +// app.go + +// Create Transfer Keeper and pass IBCFeeKeeper as expected Channel and PortKeeper +// since fee middleware will wrap the IBCKeeper for underlying application. +app.TransferKeeper = ibctransferkeeper.NewKeeper( + appCodec, keys[ibctransfertypes.StoreKey], app.GetSubspace(ibctransfertypes.ModuleName), + app.IBCFeeKeeper, // ISC4 Wrapper: fee IBC middleware + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.AccountKeeper, app.BankKeeper, scopedTransferKeeper, ++ authtypes.NewModuleAddress(govtypes.ModuleName).String(), +) +``` + +- You should pass the `authority` to the IBC keeper (implemented in [#3640](https://github.com/cosmos/ibc-go/pull/3640) and [#3650](https://github.com/cosmos/ibc-go/pull/3650)). See [diff](https://github.com/cosmos/ibc-go/pull/3640/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08a): + +```diff +// app.go + +// IBC Keepers +app.IBCKeeper = ibckeeper.NewKeeper( + appCodec, + keys[ibcexported.StoreKey], + app.GetSubspace(ibcexported.ModuleName), + app.StakingKeeper, + app.UpgradeKeeper, + scopedIBCKeeper, ++ authtypes.NewModuleAddress(govtypes.ModuleName).String(), +) +``` + +The authority determines the transaction signer allowed to execute certain messages (e.g. `MsgUpdateParams`). + +### Testing package + +- The function `SetupWithGenesisAccounts` has been removed. +- The function [`RelayPacketWithResults`](https://github.com/cosmos/ibc-go/blob/v8.0.0/testing/path.go#L66) has been added. This function returns the result of the packet receive transaction, the acknowledgement written on the receiving chain, an error if a relay step fails or the packet commitment does not exist on either chain. + +### Params migration + +Params are now self managed in the following submodules: + +- ica/controller [#3590](https://github.com/cosmos/ibc-go/pull/3590) +- ica/host [#3520](https://github.com/cosmos/ibc-go/pull/3520) +- ibc/connection [#3650](https://github.com/cosmos/ibc-go/pull/3650) +- ibc/client [#3640](https://github.com/cosmos/ibc-go/pull/3640) +- ibc/transfer [#3553](https://github.com/cosmos/ibc-go/pull/3553) + +Each module has a corresponding `MsgUpdateParams` message with a `Params` which can be specified in full to update the modules' `Params`. + +Legacy params subspaces must still be initialised in app.go in order to successfully migrate from `x/params`` to the new self-contained approach. See reference [this](https://github.com/cosmos/ibc-go/blob/v8.0.0/testing/simapp/app.go#L1007-L1012) for reference. + +For new chains which do not rely on migration of parameters from `x/params`, an expected interface has been added for each module. This allows chain developers to provide `nil` as the `legacySubspace` argument to `NewKeeper` functions. + +### Governance V1 migration + +Proposals have been migrated to [gov v1 messages](https://docs.cosmos.network/v0.50/modules/gov#messages) (see [#4620](https://github.com/cosmos/ibc-go/pull/4620)). The proposal `ClientUpdateProposal` has been deprecated and [`MsgRecoverClient`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/client/v1/tx.proto#L121-L134) should be used instead. Likewise, the proposal `UpgradeProposal` has been deprecated and [`MsgIBCSoftwareUpgrade`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/client/v1/tx.proto#L139-L154) should be used instead. Both proposals will be removed in the next major release. + +`MsgRecoverClient` and `MsgIBCSoftwareUpgrade` will only be allowed to be executed if the signer is the authority designated at the time of instantiating the IBC keeper. So please make sure that the correct authority is provided to the IBC keeper. + +Remove the `UpgradeProposalHandler` and `UpdateClientProposalHandler` from the `BasicModuleManager`: + +```diff +app.BasicModuleManager = module.NewBasicManagerFromManager( + app.ModuleManager, + map[string]module.AppModuleBasic{ + genutiltypes.ModuleName: genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator), + govtypes.ModuleName: gov.NewAppModuleBasic( + []govclient.ProposalHandler{ + paramsclient.ProposalHandler, +- ibcclientclient.UpdateClientProposalHandler, +- ibcclientclient.UpgradeProposalHandler, + }, + ), +}) +``` + +Support for in-flight legacy recover client proposals (i.e. `ClientUpdateProposal`) will be made for v8, but chains should use `MsgRecoverClient` only afterwards to avoid in-flight client recovery failing when upgrading to v9. See [this issue](https://github.com/cosmos/ibc-go/issues/4721) for more information. + +Please note that ibc-go offers facilities to test an ibc-go upgrade: + +- All e2e tests of the repository can be [run with custom Docker chain images](https://github.com/cosmos/ibc-go/blob/c5bac5e03a0eae449b9efe0d312258115c1a1e85/e2e/README.md#running-tests-with-custom-images). +- An [importable workflow](https://github.com/cosmos/ibc-go/blob/c5bac5e03a0eae449b9efe0d312258115c1a1e85/e2e/README.md#importable-workflow) that can be used from any other repository to test chain upgrades. + +### Transfer migration + +An [automatic migration handler](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/transfer/module.go#L136) is configured in the transfer module to set the [denomination metadata](https://github.com/cosmos/cosmos-sdk/blob/v0.50.1/proto/cosmos/bank/v1beta1/bank.proto#L96-L125) for the IBC denominations of all vouchers minted by the transfer module. + +## IBC Apps + +### ICS20 - Transfer + +- The function `IsBound` has been renamed to [`hasCapability`](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/transfer/keeper/keeper.go#L98) and made unexported. + +### ICS27 - Interchain Accounts + +- Functions [`SerializeCosmosTx`](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/27-interchain-accounts/types/codec.go#L32) and [`DeserializeCosmosTx`](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/27-interchain-accounts/types/codec.go#L76) now accept an extra parameter `encoding` of type `string` that specifies the format in which the transaction messages are marshaled. Both [protobuf and proto3 JSON formats](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/27-interchain-accounts/types/metadata.go#L14-L17) are supported. +- The function `IsBound` of controller submodule has been renamed to [`hasCapability`](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/27-interchain-accounts/controller/keeper/keeper.go#L111) and made unexported. +- The function `IsBound` of host submodule has been renamed to [`hasCapability`](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/27-interchain-accounts/host/keeper/keeper.go#L94) and made unexported. + +## Relayers + +- Getter functions in `MsgChannelOpenInitResponse`, `MsgChannelOpenTryResponse`, `MsgTransferResponse`, `MsgRegisterInterchainAccountResponse` and `MsgSendTxResponse` have been removed. The fields can be accessed directly. +- `channeltypes.EventTypeTimeoutPacketOnClose` (where `channeltypes` is an import alias for `"github.com/cosmos/ibc-go/v8/modules/core/04-channel"`) has been removed, since core IBC does not emit any event with this key. +- Attribute with key `counterparty_connection_id` has been removed from event with key `connectiontypes.EventTypeConnectionOpenInit` (where `connectiontypes` is an import alias for `"github.com/cosmos/ibc-go/v8/modules/core/03-connection/types"`) and attribute with key `counterparty_channel_id` has been removed from event with key `channeltypes.EventTypeChannelOpenInit` (where `channeltypes` is an import alias for `"github.com/cosmos/ibc-go/v8/modules/core/04-channel"`) since both (counterparty connection ID and counterparty channel ID) are empty on `ConnectionOpenInit` and `ChannelOpenInit` respectively. +- As part of the migration to [governance V1 messages](#governance-v1-migration) the following changes in events have been made: + +```diff +// IBC client events vars +var ( + EventTypeCreateClient = "create_client" + EventTypeUpdateClient = "update_client" + EventTypeUpgradeClient = "upgrade_client" + EventTypeSubmitMisbehaviour = "client_misbehaviour" +- EventTypeUpdateClientProposal = "update_client_proposal" +- EventTypeUpgradeClientProposal = "upgrade_client_proposal" ++ EventTypeRecoverClient = "recover_client" ++ EventTypeScheduleIBCSoftwareUpgrade = "schedule_ibc_software_upgrade" + EventTypeUpgradeChain = "upgrade_chain" +) +``` + +## IBC Light Clients + +- Functions `Pretty` and `String` of type `MerklePath` have been [removed](https://github.com/cosmos/ibc-go/pull/4459/files#diff-dd94ec1dde9b047c0cdfba204e30dad74a81de202e3b09ac5b42f493153811af). diff --git a/docs/versioned_docs/version-v10.1.x/05-migrations/12-v8-to-v8_1.md b/docs/versioned_docs/version-v10.1.x/05-migrations/12-v8-to-v8_1.md new file mode 100644 index 0000000..7808e81 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/05-migrations/12-v8-to-v8_1.md @@ -0,0 +1,42 @@ +--- +title: IBC-Go v8 to v8.1 +sidebar_label: IBC-Go v8 to v8.1 +sidebar_position: 12 +slug: /migrations/v8-to-v8_1 +--- + +# Migrating from v8 to v8.1 + +This guide provides instructions for migrating to version `v8.1.0` of ibc-go. + +There are four sections based on the four potential user groups of this document: + +- [Migrating from v8 to v8.1](#migrating-from-v8-to-v81) + - [Chains](#chains) + - [IBC apps](#ibc-apps) + - [Relayers](#relayers) + - [IBC light clients](#ibc-light-clients) + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. + +## Chains + +### `04-channel` params migration + +Self-managed [params](https://github.com/cosmos/ibc-go/blob/v8.1.0/proto/ibc/core/channel/v1/channel.proto#L183-L187) have been added for `04-channel` module. The params include the `upgrade_timeout` that is used in channel upgradability to specify the interval of time during which the counterparty chain must flush all in-flight packets on its end and move to `FLUSH_COMPLETE` state). An [automatic migration handler](https://github.com/cosmos/ibc-go/blob/v8.1.0/modules/core/module.go#L162-L166) is configured in the `04-channel` module that sets the default params (with a default upgrade timeout of 10 minutes). The module has a corresponding [`MsgUpdateParams` message](https://github.com/cosmos/ibc-go/blob/v8.1.0/proto/ibc/core/channel/v1/tx.proto#L435-L447) with a `Params` field which can be specified in full to update the module's `Params`. + +### Fee migration + +In ibc-go v8.1.0 an improved, more efficient escrow calculation of fees for packet incentivisation has been introduced (see [this issue](https://github.com/cosmos/ibc-go/issues/5509) for more information). Before v8.1.0 the amount escrowed was `(ReckFee + AckFee + TimeoutFee)`; from ibc-go v8.1.0, the calculation is changed to `Max(RecvFee + AckFee, TimeoutFee)`. In order to guarantee that the correct amount of fees are refunded for packets that are in-flight during the upgrade to ibc-go v8.1.0, an [automatic migration handler](https://github.com/cosmos/ibc-go/blob/v8.1.0/modules/apps/29-fee/module.go#L113-L115) is configured in the `29-fee` module to refund the leftover fees (i.e `(ReckFee + AckFee + TimeoutFee) - Max(RecvFee + AckFee, TimeoutFee)`) that otherwise would not be refunded when the packet lifecycle completes and the new calculation is used. + +## IBC apps + +- No relevant changes were made in this release. + +## Relayers + +- No relevant changes were made in this release. + +## IBC light clients + +- No relevant changes were made in this release. diff --git a/docs/versioned_docs/version-v10.1.x/05-migrations/13-v8_1-to-v10.md b/docs/versioned_docs/version-v10.1.x/05-migrations/13-v8_1-to-v10.md new file mode 100644 index 0000000..60cfcaf --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/05-migrations/13-v8_1-to-v10.md @@ -0,0 +1,289 @@ +--- +title: IBC-Go v8.1 to v10 +sidebar_label: IBC-Go v8.1 to v10 +sidebar_position: 13 +slug: /migrations/v8_1-to-v10 +--- + +# Migrating from v8.1 to v10 + +This guide provides instructions for migrating to a new version of ibc-go. + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. In addition, for this release, the 08-wasm module has been released as v10, and the callbacks middleware has been moved into the ibc-go module itself. + +Diff examples are shown after the list of overall changes: + +- To add support for IBC v2, Chains will need to wire up a new IBC v2 Transfer stack +- Chains will need to wire up the new light client modules +- Chains will need to update Keeper construction calls to comply with the new signatures +- Chains will need to remove the route for the legacy proposal handler for 02-client from their `app/app.go` +- Chains will need to remove the capability keeper and all related setup, including the scoped keepers from their `app/app.go` +- Chains will need to remove ibc fee middleware (29-fee) +- Chains will need, if using this module, to update their imports and usage of `github.com/cosmos/ibc-go/modules/light-clients/08-wasm/` to `github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10` +- Chains will need, if using this module, to update their imports and usage of `github.com/cosmos/ibc-go/modules/apps/callbacks` to `github.com/cosmos/ibc-go/v10/modules/apps/callbacks` + +To add IBC v2 support, wire up a new transfer stack. Example below showing wired up with IBC Callbacks module: + +```diff ++ var ibcv2TransferStack ibcapi.IBCModule ++ ibcv2TransferStack = transferv2.NewIBCModule(app.TransferKeeper) ++ ibcv2TransferStack = ibccallbacksv2.NewIBCMiddleware( ++ transferv2.NewIBCModule(app.TransferKeeper), ++ app.IBCKeeper.ChannelKeeperV2, ++ wasmStackIBCHandler, ++ app.IBCKeeper.ChannelKeeperV2, ++ maxCallbackGas, ++ ) +``` + +Wire up each light client as a separate module and add them to the client keeper router. Example below for 07-tendermint and 08-wasm: + +```diff ++ // Light client modules ++ clientKeeper := app.IBCKeeper.ClientKeeper ++ storeProvider := app.IBCKeeper.ClientKeeper.GetStoreProvider() ++ ++ tmLightClientModule := ibctm.NewLightClientModule(appCodec, storeProvider) ++ clientKeeper.AddRoute(ibctm.ModuleName, &tmLightClientModule) ++ ++ wasmLightClientModule := ibcwasm.NewLightClientModule(app.WasmClientKeeper, storeProvider) ++ clientKeeper.AddRoute(ibcwasmtypes.ModuleName, &wasmLightClientModule) +``` + +Remove ibc fee module name (if used) from module account permissions: + +```diff + // app.go + ... + // module account permissions + var maccPerms = map[string][]string{ + ... +- ibcfeetypes.ModuleName: nil, + ... + } +``` + +Remove `CapabilityKeeper`, `IBCFeeKeeper` and all `capabilitykeeper.ScopedKeeper` Scoped keepers from the App struct: + +```diff + // ChainApp extended ABCI application + type ChainApp struct { + ... +- CapabilityKeeper *capabilitykeeper.Keeper + ... +- IBCFeeKeeper ibcfeekeeper.Keeper + ... +- ScopedIBCKeeper capabilitykeeper.ScopedKeeper +- ScopedICAHostKeeper capabilitykeeper.ScopedKeeper +- ScopedICAControllerKeeper capabilitykeeper.ScopedKeeper +- ScopedTransferKeeper capabilitykeeper.ScopedKeeper +- ScopedIBCFeeKeeper capabilitykeeper.ScopedKeeper + ... + } + ... +- app.ScopedIBCKeeper = scopedIBCKeeper +- app.ScopedTransferKeeper = scopedTransferKeeper +- app.ScopedWasmKeeper = scopedWasmKeeper +- app.ScopedICAHostKeeper = scopedICAHostKeeper +- app.ScopedICAControllerKeeper = scopedICAControllerKeeper +``` + +Remove capability and ibc fee middleware store keys from the `NewKVStoreKeys` call: + +```diff +... + keys := storetypes.NewKVStoreKeys( + ... +- capabilitytypes.StoreKey, +- ibcfeetypes.StoreKey, + ... + } +``` + +Remove the in-memory store keys previously used by the capability module: + +```diff +- memKeys := storetypes.NewMemoryStoreKeys(capabilitytypes.MemStoreKey) +... +- app.MountMemoryStores(memKeys) +``` + +Remove creation of the capability keeper: + +```diff +- // add capability keeper and ScopeToModule for ibc module +- app.CapabilityKeeper = capabilitykeeper.NewKeeper( +- appCodec, +- keys[capabilitytypes.StoreKey], +- memKeys[capabilitytypes.MemStoreKey], +- ) + +- scopedIBCKeeper := app.CapabilityKeeper.ScopeToModule(ibcexported.ModuleName) +- scopedICAHostKeeper := app.CapabilityKeeper.ScopeToModule(icahosttypes.SubModuleName) +- scopedICAControllerKeeper := app.CapabilityKeeper.ScopeToModule(icacontrollertypes.SubModuleName) +- scopedTransferKeeper := app.CapabilityKeeper.ScopeToModule(ibctransfertypes.ModuleName) +- scopedWasmKeeper := app.CapabilityKeeper.ScopeToModule(wasmtypes.ModuleName) +- app.CapabilityKeeper.Seal() +``` + +Remove the legacy route for the client keeper: + +```diff +... + govRouter.AddRoute(govtypes.RouterKey, govv1beta1.ProposalHandler). +- AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(app.ParamsKeeper)). +- AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)) ++ AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(app.ParamsKeeper)) +``` + +Update Core IBC Keeper constructor: + +```diff + app.IBCKeeper = ibckeeper.NewKeeper( + appCodec, +- keys[ibcexported.StoreKey], ++ runtime.NewKVStoreService(keys[ibcexported.StoreKey]), + app.GetSubspace(ibcexported.ModuleName), +- app.StakingKeeper, + app.UpgradeKeeper, +- scopedIBCKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) +``` + +Update IBC Transfer keeper constructor: + +```diff + app.TransferKeeper = ibctransferkeeper.NewKeeper( + appCodec, +- keys[ibctransfertypes.StoreKey], ++ runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]), + app.GetSubspace(ibctransfertypes.ModuleName), + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeper, +- app.IBCKeeper.PortKeeper, ++ app.MsgServiceRouter(), + app.AccountKeeper, + app.BankKeeper, +- scopedTransferKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) +``` + +Update ICA Host keeper constructor, notice the removal of the `WithQueryRouter` call in particular: + +```diff + app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, +- keys[icahosttypes.StoreKey], ++ runtime.NewKVStoreService(keys[icahosttypes.StoreKey]), + app.GetSubspace(icahosttypes.SubModuleName), +- app.IBCFeeKeeper, // use ics29 fee as ics4Wrapper in middleware stack + app.IBCKeeper.ChannelKeeper, +- app.IBCKeeper.PortKeeper, ++ app.IBCKeeper.ChannelKeeper, + app.AccountKeeper, +- scopedICAHostKeeper, + app.MsgServiceRouter(), ++ app.GRPCQueryRouter(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) +- app.ICAHostKeeper.WithQueryRouter(app.GRPCQueryRouter()) +``` + +Remove IBC Fee Module keeper: + +```diff +- app.IBCFeeKeeper = ibcfeekeeper.NewKeeper( +- appCodec, keys[ibcfeetypes.StoreKey], +- app.IBCKeeper.ChannelKeeper, // may be replaced with IBC middleware +- app.IBCKeeper.ChannelKeeper, +- app.IBCKeeper.PortKeeper, app.AccountKeeper, app.BankKeeper, +- ) +``` + +Update Transfer stack to remove the fee middleware. The example below shows the correct way to wire up a middleware stack with the IBC callbacks middleware: + +```diff + // Create Transfer Stack + var transferStack porttypes.IBCModule + transferStack = transfer.NewIBCModule(app.TransferKeeper) +- transferStack = ibccallbacks.NewIBCMiddleware(transferStack, app.IBCFeeKeeper, wasmStackIBCHandler, maxCallbackGas) +- transferStack = ibcfee.NewIBCMiddleware(transferStack, app.IBCFeeKeeper) ++ // callbacks wraps the transfer stack as its base app, and uses PacketForwardKeeper as the ICS4Wrapper ++ // i.e. packet-forward-middleware is higher on the stack and sits between callbacks and the ibc channel keeper ++ // Since this is the lowest level middleware of the transfer stack, it should be the first entrypoint for transfer keeper's ++ // WriteAcknowledgement. ++ cbStack := ibccallbacks.NewIBCMiddleware(transferStack, app.PacketForwardKeeper, wasmStackIBCHandler, maxCallbackGas) +transferStack = packetforward.NewIBCMiddleware( +- transferStack, ++ cbStack, + app.PacketForwardKeeper, + 0, + packetforwardkeeper.DefaultForwardTransferPacketTimeoutTimestamp, + ) ++ app.TransferKeeper.WithICS4Wrapper(cbStack) +``` + +Remove ibc fee middleware and any empty IBCModule (often dubbed `noAuthzModule`) from the ICA Controller stack creation: + +```diff +- var noAuthzModule porttypes.IBCModule +- icaControllerStack = icacontroller.NewIBCMiddleware(noAuthzModule, app.ICAControllerKeeper) +- icaControllerStack = ibcfee.NewIBCMiddleware(icaControllerStack, app.IBCFeeKeeper) ++ icaControllerStack = icacontroller.NewIBCMiddleware(app.ICAControllerKeeper) +``` + +Remove ibc fee middleware from ICA Host stack creation: + +```diff + icaHostStack = icahost.NewIBCModule(app.ICAHostKeeper) +- icaHostStack = ibcfee.NewIBCMiddleware(icaHostStack, app.IBCFeeKeeper) +``` + +Update the module manager creation by removing the capability module, fee module and updating the tendermint app module constructor: + +```diff + app.ModuleManager = module.NewManager( + ... +- capability.NewAppModule(appCodec, *app.CapabilityKeeper, false), + ... +- ibcfee.NewAppModule(app.IBCFeeKeeper), + ... +- ibctm.NewAppModule(), ++ ibctm.NewAppModule(tmLightClientModule), + ... + ) +``` + +Remove the capability module and ibc fee middleware from `SetOrderBeginBlockers`, `SetOrderEndBlockers`, `SetOrderInitGenesis` and `SetOrderExportGenesis`: + +```diff +- capabilitytypes.ModuleName, +- ibcfeetypes.ModuleName, +``` + +If you use 08-wasm, you will need to update the go module that is used for `QueryPlugins` and `AcceptListStargateQuerier`. + +```diff +- wasmLightClientQuerier := ibcwasmtypes.QueryPlugins{ ++ wasmLightClientQuerier := ibcwasmkeeper.QueryPlugins{ +- Stargate: ibcwasmtypes.AcceptListStargateQuerier([]string{ ++ Stargate: ibcwasmkeeper.AcceptListStargateQuerier([]string{ + "/ibc.core.client.v1.Query/ClientState", + "/ibc.core.client.v1.Query/ConsensusState", + "/ibc.core.connection.v1.Query/Connection", +- }), ++ }, app.GRPCQueryRouter()), + } +``` + +If you use 08-wasm, you will need to use the wasm client keeper rather than the go module to initialize pinned codes: + +```diff +- if err := ibcwasmkeeper.InitializePinnedCodes(ctx); err != nil { +- panic(fmt.Sprintf("ibcwasmkeeper failed initialize pinned codes %s", err)) ++ if err := app.WasmClientKeeper.InitializePinnedCodes(ctx); err != nil { ++ panic(fmt.Sprintf("WasmClientKeeper failed initialize pinned codes %s", err)) ++ } +``` diff --git a/docs/versioned_docs/version-v10.1.x/05-migrations/_category_.json b/docs/versioned_docs/version-v10.1.x/05-migrations/_category_.json new file mode 100644 index 0000000..354a84e --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/05-migrations/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Migrations", + "position": 5, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v10.1.x/05-migrations/images/auth-module-decision-tree.png b/docs/versioned_docs/version-v10.1.x/05-migrations/images/auth-module-decision-tree.png new file mode 100644 index 0000000..1122ddb Binary files /dev/null and b/docs/versioned_docs/version-v10.1.x/05-migrations/images/auth-module-decision-tree.png differ diff --git a/docs/versioned_docs/version-v10.1.x/05-migrations/migration.template.md b/docs/versioned_docs/version-v10.1.x/05-migrations/migration.template.md new file mode 100644 index 0000000..182686e --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/05-migrations/migration.template.md @@ -0,0 +1,28 @@ +# Migrating from \ to \ + +This guide provides instructions for migrating to a new version of ibc-go. + +There are four sections based on the four potential user groups of this document: + +- [Chains](#chains) +- [IBC Apps](#ibc-apps) +- [Relayers](#relayers) +- [IBC Light Clients](#ibc-light-clients) + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. + +## Chains + +- No relevant changes were made in this release. + +## IBC Apps + +- No relevant changes were made in this release. + +## Relayers + +- No relevant changes were made in this release. + +## IBC Light Clients + +- No relevant changes were made in this release. diff --git a/docs/versioned_docs/version-v10.1.x/images/ibcoverview-dark.svg b/docs/versioned_docs/version-v10.1.x/images/ibcoverview-dark.svg new file mode 100644 index 0000000..d8c12ed --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/images/ibcoverview-dark.svg @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/versioned_docs/version-v10.1.x/images/ibcoverview-light.svg b/docs/versioned_docs/version-v10.1.x/images/ibcoverview-light.svg new file mode 100644 index 0000000..12e38b8 --- /dev/null +++ b/docs/versioned_docs/version-v10.1.x/images/ibcoverview-light.svg @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/versioned_docs/version-v4.6.x/00-intro.md b/docs/versioned_docs/version-v4.6.x/00-intro.md new file mode 100644 index 0000000..e053110 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/00-intro.md @@ -0,0 +1,20 @@ +--- +slug: / +sidebar_position: 0 +--- + +:::danger +This version of ibc-go is not supported anymore. Please upgrade to the latest version. +::: + +# IBC-Go Documentation + +Welcome to the IBC-Go documentation! + +The Inter-Blockchain Communication protocol (IBC) is an end-to-end, connection-oriented, stateful protocol for reliable, ordered, and authenticated communication between heterogeneous blockchains arranged in an unknown and dynamic topology. + +IBC is a protocol that allows blockchains to talk to each other. + +The protocol realizes this interoperability by specifying a set of data structures, abstractions, and semantics that can be implemented by any distributed ledger that satisfies a small set of requirements. + +IBC can be used to build a wide range of cross-chain applications that include token transfers, atomic swaps, multi-chain smart contracts (with or without mutually comprehensible VMs), and data and code sharding of various kinds. diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/01-overview.md b/docs/versioned_docs/version-v4.6.x/01-ibc/01-overview.md new file mode 100644 index 0000000..4c3c0b1 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/01-ibc/01-overview.md @@ -0,0 +1,297 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /ibc/overview +--- + + +# Overview + +:::note Synopsis +Learn about IBC, its components, and IBC use cases. +::: + +## What is the Inter-Blockchain Communication Protocol (IBC)? + +This document serves as a guide for developers who want to write their own Inter-Blockchain +Communication protocol (IBC) applications for custom use cases. + +> IBC applications must be written as self-contained modules. + +Due to the modular design of the IBC protocol, IBC +application developers do not need to be concerned with the low-level details of clients, +connections, and proof verification. + +This brief explanation of the lower levels of the +stack gives application developers a broad understanding of the IBC +protocol. Abstraction layer details for channels and ports are most relevant for application developers and describe how to define custom packets and `IBCModule` callbacks. + +The requirements to have your module interact over IBC are: + +- Bind to a port or ports. +- Define your packet data. +- Use the default acknowledgment struct provided by core IBC or optionally define a custom acknowledgment struct. +- Standardize an encoding of the packet data. +- Implement the `IBCModule` interface. + +Read on for a detailed explanation of how to write a self-contained IBC application module. + +## Components Overview + +### [Clients](https://github.com/cosmos/ibc-go/blob/main/modules/core/02-client) + +IBC clients are on-chain light clients. Each light client is identified by a unique client-id. +IBC clients track the consensus states of other blockchains, along with the proof spec necessary to +properly verify proofs against the client's consensus state. A client can be associated with any number +of connections to the counterparty chain. The client identifier is auto generated using the client type +and the global client counter appended in the format: `{client-type}-{N}`. + +A `ClientState` should contain chain specific and light client specific information necessary for verifying updates +and upgrades to the IBC client. The `ClientState` may contain information such as chain-id, latest height, proof specs, +unbonding periods or the status of the light client. The `ClientState` should not contain information that +is specific to a given block at a certain height, this is the function of the `ConsensusState`. Each `ConsensusState` +should be associated with a unique block and should be referenced using a height. IBC clients are given a +client identifier prefixed store to store their associated client state and consensus states along with +any metadata associated with the consensus states. Consensus states are stored using their associated height. + +The supported IBC clients are: + +- [Solo Machine light client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/06-solomachine): Devices such as phones, browsers, or laptops. +- [Tendermint light client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/07-tendermint): The default for Cosmos SDK-based chains. +- [Localhost (loopback) client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/09-localhost): Useful for +testing, simulation, and relaying packets to modules on the same application. + +### IBC Client Heights + +IBC Client Heights are represented by the struct: + +```go +type Height struct { + RevisionNumber uint64 + RevisionHeight uint64 +} +``` + +The `RevisionNumber` represents the revision of the chain that the height is representing. +A revision typically represents a continuous, monotonically increasing range of block-heights. +The `RevisionHeight` represents the height of the chain within the given revision. + +On any reset of the `RevisionHeight`—for example, when hard-forking a Tendermint chain— +the `RevisionNumber` will get incremented. This allows IBC clients to distinguish between a +block-height `n` of a previous revision of the chain (at revision `p`) and block-height `n` of the current +revision of the chain (at revision `e`). + +`Height`s that share the same revision number can be compared by simply comparing their respective `RevisionHeight`s. +`Height`s that do not share the same revision number will only be compared using their respective `RevisionNumber`s. +Thus a height `h` with revision number `e+1` will always be greater than a height `g` with revision number `e`, +**REGARDLESS** of the difference in revision heights. + +Ex: + +```go +Height{RevisionNumber: 3, RevisionHeight: 0} > Height{RevisionNumber: 2, RevisionHeight: 100000000000} +``` + +When a Tendermint chain is running a particular revision, relayers can simply submit headers and proofs with the revision number +given by the chain's `chainID`, and the revision height given by the Tendermint block height. When a chain updates using a hard-fork +and resets its block-height, it is responsible for updating its `chainID` to increment the revision number. +IBC Tendermint clients then verifies the revision number against their `chainID` and treat the `RevisionHeight` as the Tendermint block-height. + +Tendermint chains wishing to use revisions to maintain persistent IBC connections even across height-resetting upgrades must format their `chainID`s +in the following manner: `{chainID}-{revision_number}`. On any height-resetting upgrade, the `chainID` **MUST** be updated with a higher revision number +than the previous value. + +Ex: + +- Before upgrade `chainID`: `gaiamainnet-3` +- After upgrade `chainID`: `gaiamainnet-4` + +Clients that do not require revisions, such as the solo-machine client, simply hardcode `0` into the revision number whenever they +need to return an IBC height when implementing IBC interfaces and use the `RevisionHeight` exclusively. + +Other client-types can implement their own logic to verify the IBC heights that relayers provide in their `Update`, `Misbehavior`, and +`Verify` functions respectively. + +The IBC interfaces expect an `ibcexported.Height` interface, however all clients must use the concrete implementation provided in +`02-client/types` and reproduced above. + +### [Connections](https://github.com/cosmos/ibc-go/blob/main/modules/core/03-connection) + +Connections encapsulate two `ConnectionEnd` objects on two separate blockchains. Each +`ConnectionEnd` is associated with a client of the other blockchain (for example, the counterparty blockchain). +The connection handshake is responsible for verifying that the light clients on each chain are +correct for their respective counterparties. Connections, once established, are responsible for +facilitating all cross-chain verifications of IBC state. A connection can be associated with any +number of channels. + +### [Proofs](https://github.com/cosmos/ibc-go/blob/main/modules/core/23-commitment) and [Paths](https://github.com/cosmos/ibc-go/blob/main/modules/core/24-host) + +In IBC, blockchains do not directly pass messages to each other over the network. Instead, to +communicate, a blockchain commits some state to a specifically defined path that is reserved for a +specific message type and a specific counterparty. For example, for storing a specific connectionEnd as part +of a handshake or a packet intended to be relayed to a module on the counterparty chain. A relayer +process monitors for updates to these paths and relays messages by submitting the data stored +under the path and a proof to the counterparty chain. + +Proofs are passed from core IBC to light-clients as bytes. It is up to light client implementation to interpret these bytes appropriately. + +- The paths that all IBC implementations must use for committing IBC messages is defined in +[ICS-24 Host State Machine Requirements](https://github.com/cosmos/ibc/tree/master/spec/core/ics-024-host-requirements). +- The proof format that all implementations must be able to produce and verify is defined in [ICS-23 Proofs](https://github.com/confio/ics23) implementation. + +### Capabilities + +IBC is intended to work in execution environments where modules do not necessarily trust each +other. Thus, IBC must authenticate module actions on ports and channels so that only modules with the +appropriate permissions can use them. + +This module authentication is accomplished using a [dynamic +capability store](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-003-dynamic-capability-store.md). Upon binding to a port or +creating a channel for a module, IBC returns a dynamic capability that the module must claim in +order to use that port or channel. The dynamic capability module prevents other modules from using that port or channel since +they do not own the appropriate capability. + +While this background information is useful, IBC modules do not need to interact at all with +these lower-level abstractions. The relevant abstraction layer for IBC application developers is +that of channels and ports. IBC applications must be written as self-contained **modules**. + +A module on one blockchain can communicate with other modules on other blockchains by sending, +receiving, and acknowledging packets through channels that are uniquely identified by the +`(channelID, portID)` tuple. + +A useful analogy is to consider IBC modules as internet applications on +a computer. A channel can then be conceptualized as an IP connection, with the IBC portID being +analogous to an IP port and the IBC channelID being analogous to an IP address. Thus, a single +instance of an IBC module can communicate on the same port with any number of other modules and +IBC correctly routes all packets to the relevant module using the (channelID, portID tuple). An +IBC module can also communicate with another IBC module over multiple ports, with each +`(portID<->portID)` packet stream being sent on a different unique channel. + +### [Ports](https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port) + +An IBC module can bind to any number of ports. Each port must be identified by a unique `portID`. +Since IBC is designed to be secure with mutually distrusted modules operating on the same ledger, +binding a port returns a dynamic object capability. In order to take action on a particular port +(for example, an open channel with its portID), a module must provide the dynamic object capability to the IBC +handler. This requirement prevents a malicious module from opening channels with ports it does not own. Thus, +IBC modules are responsible for claiming the capability that is returned on `BindPort`. + +### [Channels](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +An IBC channel can be established between two IBC ports. Currently, a port is exclusively owned by a +single module. IBC packets are sent over channels. Just as IP packets contain the destination IP +address and IP port, and the source IP address and source IP port, IBC packets contain +the destination portID and channelID, and the source portID and channelID. This packet structure enables IBC to +correctly route packets to the destination module while allowing modules receiving packets to +know the sender module. + +A channel can be `ORDERED`, where packets from a sending module must be processed by the +receiving module in the order they were sent. Or a channel can be `UNORDERED`, where packets +from a sending module are processed in the order they arrive (might be in a different order than they were sent). + +Modules can choose which channels they wish to communicate over with, thus IBC expects modules to +implement callbacks that are called during the channel handshake. These callbacks can do custom +channel initialization logic. If any callback returns an error, the channel handshake fails. Thus, by +returning errors on callbacks, modules can programmatically reject and accept channels. + +The channel handshake is a 4-step handshake. Briefly, if a given chain A wants to open a channel with +chain B using an already established connection: + +1. chain A sends a `ChanOpenInit` message to signal a channel initialization attempt with chain B. +2. chain B sends a `ChanOpenTry` message to try opening the channel on chain A. +3. chain A sends a `ChanOpenAck` message to mark its channel end status as open. +4. chain B sends a `ChanOpenConfirm` message to mark its channel end status as open. + +If all handshake steps are successful, the channel is opened on both sides. At each step in the handshake, the module +associated with the `ChannelEnd` executes its callback. So +on `ChanOpenInit`, the module on chain A executes its callback `OnChanOpenInit`. + +The channel identifier is auto derived in the format: `channel-{N}` where N is the next sequence to be used. + +Just as ports came with dynamic capabilities, channel initialization returns a dynamic capability +that the module **must** claim so that they can pass in a capability to authenticate channel actions +like sending packets. The channel capability is passed into the callback on the first parts of the +handshake; either `OnChanOpenInit` on the initializing chain or `OnChanOpenTry` on the other chain. + +#### Closing channels + +Closing a channel occurs in 2 handshake steps as defined in [ICS 04](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics). + +`ChanCloseInit` closes a channel on the executing chain if the channel exists, it is not +already closed and the connection it exists upon is OPEN. Channels can only be closed by a +calling module or in the case of a packet timeout on an ORDERED channel. + +`ChanCloseConfirm` is a response to a counterparty channel executing `ChanCloseInit`. The channel +on the executing chain closes if the channel exists, the channel is not already closed, +the connection the channel exists upon is OPEN and the executing chain successfully verifies +that the counterparty channel has been closed. + +### [Packets](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +Modules communicate with each other by sending packets over IBC channels. All +IBC packets contain the destination `portID` and `channelID` along with the source `portID` and +`channelID`. This packet structure allows modules to know the sender module of a given packet. IBC packets +contain a sequence to optionally enforce ordering. + +IBC packets also contain a `TimeoutHeight` and a `TimeoutTimestamp` that determine the deadline before the receiving module must process a packet. + +Modules send custom application data to each other inside the `Data []byte` field of the IBC packet. +Thus, packet data is opaque to IBC handlers. It is incumbent on a sender module to encode +their application-specific packet information into the `Data` field of packets. The receiver +module must decode that `Data` back to the original application data. + +### [Receipts and Timeouts](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +Since IBC works over a distributed network and relies on potentially faulty relayers to relay messages between ledgers, +IBC must handle the case where a packet does not get sent to its destination in a timely manner or at all. Packets must +specify a non-zero value for timeout height (`TimeoutHeight`) or timeout timestamp (`TimeoutTimestamp` ) after which a packet can no longer be successfully received on the destination chain. + +- The `timeoutHeight` indicates a consensus height on the destination chain after which the packet is no longer be processed, and instead counts as having timed-out. +- The `timeoutTimestamp` indicates a timestamp on the destination chain after which the packet is no longer be processed, and instead counts as having timed-out. + +If the timeout passes without the packet being successfully received, the packet can no longer be +received on the destination chain. The sending module can timeout the packet and take appropriate actions. + +If the timeout is reached, then a proof of packet timeout can be submitted to the original chain. The original chain can then perform +application-specific logic to timeout the packet, perhaps by rolling back the packet send changes (refunding senders any locked funds, etc.). + +- In ORDERED channels, a timeout of a single packet in the channel causes the channel to close. + + - If packet sequence `n` times out, then a packet at sequence `k > n` cannot be received without violating the contract of ORDERED channels that packets are processed in the order that they are sent. + - Since ORDERED channels enforce this invariant, a proof that sequence `n` has not been received on the destination chain by the specified timeout of packet `n` is sufficient to timeout packet `n` and close the channel. + +- In UNORDERED channels, the application-specific timeout logic for that packet is applied and the channel is not closed. + + - Packets can be received in any order. + + - IBC writes a packet receipt for each sequence receives in the UNORDERED channel. This receipt does not contain information; it is simply a marker intended to signify that the UNORDERED channel has received a packet at the specified sequence. + + - To timeout a packet on an UNORDERED channel, a proof is required that a packet receipt **does not exist** for the packet's sequence by the specified timeout. + +For this reason, most modules should use UNORDERED channels as they require fewer liveness guarantees to function effectively for users of that channel. + +### [Acknowledgments](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +Modules can also choose to write application-specific acknowledgments upon processing a packet. Acknowledgments can be done: + +- Synchronously on `OnRecvPacket` if the module processes packets as soon as they are received from IBC module. +- Asynchronously if module processes packets at some later point after receiving the packet. + +This acknowledgment data is opaque to IBC much like the packet `Data` and is treated by IBC as a simple byte string `[]byte`. Receiver modules must encode their acknowledgment so that the sender module can decode it correctly. The encoding must be negotiated between the two parties during version negotiation in the channel handshake. + +The acknowledgment can encode whether the packet processing succeeded or failed, along with additional information that allows the sender module to take appropriate action. + +After the acknowledgment has been written by the receiving chain, a relayer relays the acknowledgment back to the original sender module. + +The original sender module then executes application-specific acknowledgment logic using the contents of the acknowledgment. + +- After an acknowledgement fails, packet-send changes can be rolled back (for example, refunding senders in ICS20). + +- After an acknowledgment is received successfully on the original sender on the chain, the corresponding packet commitment is deleted since it is no longer needed. + +## Further Readings and Specs + +If you want to learn more about IBC, check the following specifications: + +- [IBC specification overview](https://github.com/cosmos/ibc/blob/master/README.md) diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/02-integration.md b/docs/versioned_docs/version-v4.6.x/01-ibc/02-integration.md new file mode 100644 index 0000000..9148547 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/01-ibc/02-integration.md @@ -0,0 +1,222 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /ibc/integration +--- + +# Integration + +:::note Synopsis +Learn how to integrate IBC to your application and send data packets to other chains. +::: + +This document outlines the required steps to integrate and configure the [IBC +module](https://github.com/cosmos/ibc-go/tree/main/modules/core) to your Cosmos SDK application and +send fungible token transfers to other chains. + +## Integrating the IBC module + +Integrating the IBC module to your SDK-based application is straightforward. The general changes can be summarized in the following steps: + +- Add required modules to the `module.BasicManager` +- Define additional `Keeper` fields for the new modules on the `App` type +- Add the module's `StoreKeys` and initialize their `Keepers` +- Set up corresponding routers and routes for the `ibc` module +- Add the modules to the module `Manager` +- Add modules to `Begin/EndBlockers` and `InitGenesis` +- Update the module `SimulationManager` to enable simulations + +### Module `BasicManager` and `ModuleAccount` permissions + +The first step is to add the following modules to the `BasicManager`: `x/capability`, `x/ibc`, +and `x/ibc-transfer`. After that, we need to grant `Minter` and `Burner` permissions to +the `ibc-transfer` `ModuleAccount` to mint and burn relayed tokens. + +```go +// app.go +var ( + + ModuleBasics = module.NewBasicManager( + // ... + capability.AppModuleBasic{}, + ibc.AppModuleBasic{}, + transfer.AppModuleBasic{}, // i.e ibc-transfer module + ) + + // module account permissions + maccPerms = map[string][]string{ + // other module accounts permissions + // ... + ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner}, +) +``` + +### Application fields + +Then, we need to register the `Keepers` as follows: + +```go +// app.go +type App struct { + // baseapp, keys and subspaces definitions + + // other keepers + // ... + IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly + TransferKeeper ibctransferkeeper.Keeper // for cross-chain fungible token transfers + + // make scoped keepers public for test purposes + ScopedIBCKeeper capabilitykeeper.ScopedKeeper + ScopedTransferKeeper capabilitykeeper.ScopedKeeper + + /// ... + /// module and simulation manager definitions +} +``` + +### Configure the `Keepers` + +During initialization, besides initializing the IBC `Keepers` (for the `x/ibc`, and +`x/ibc-transfer` modules), we need to grant specific capabilities through the capability module +`ScopedKeepers` so that we can authenticate the object-capability permissions for each of the IBC +channels. + +```go +func NewApp(...args) *App { + // define codecs and baseapp + + // add capability keeper and ScopeToModule for ibc module + app.CapabilityKeeper = capabilitykeeper.NewKeeper(appCodec, keys[capabilitytypes.StoreKey], memKeys[capabilitytypes.MemStoreKey]) + + // grant capabilities for the ibc and ibc-transfer modules + scopedIBCKeeper := app.CapabilityKeeper.ScopeToModule(ibchost.ModuleName) + scopedTransferKeeper := app.CapabilityKeeper.ScopeToModule(ibctransfertypes.ModuleName) + + // ... other modules keepers + + // Create IBC Keeper + app.IBCKeeper = ibckeeper.NewKeeper( + appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, + ) + + // Create Transfer Keepers + app.TransferKeeper = ibctransferkeeper.NewKeeper( + appCodec, keys[ibctransfertypes.StoreKey], app.GetSubspace(ibctransfertypes.ModuleName), + app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.AccountKeeper, app.BankKeeper, scopedTransferKeeper, + ) + transferModule := transfer.NewAppModule(app.TransferKeeper) + + // .. continues +} +``` + +### Register `Routers` + +IBC needs to know which module is bound to which port so that it can route packets to the +appropriate module and call the appropriate callbacks. The port to module name mapping is handled by +IBC's port `Keeper`. However, the mapping from module name to the relevant callbacks is accomplished +by the port +[`Router`](https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port/types/router.go) on the +IBC module. + +Adding the module routes allows the IBC handler to call the appropriate callback when processing a +channel handshake or a packet. + +Currently, a `Router` is static so it must be initialized and set correctly on app initialization. +Once the `Router` has been set, no new routes can be added. + +```go +// app.go +func NewApp(...args) *App { + // .. continuation from above + + // Create static IBC router, add ibc-transfer module route, then set and seal it + ibcRouter := port.NewRouter() + ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferModule) + // Setting Router will finalize all routes by sealing router + // No more routes can be added + app.IBCKeeper.SetRouter(ibcRouter) + + // .. continues +``` + +### Module Managers + +In order to use IBC, we need to add the new modules to the module `Manager` and to the `SimulationManager` in case your application supports simulations. + +```go +// app.go +func NewApp(...args) *App { + // .. continuation from above + + app.mm = module.NewManager( + // other modules + // ... + capability.NewAppModule(appCodec, *app.CapabilityKeeper), + ibc.NewAppModule(app.IBCKeeper), + transferModule, + ) + + // ... + + app.sm = module.NewSimulationManager( + // other modules + // ... + capability.NewAppModule(appCodec, *app.CapabilityKeeper), + ibc.NewAppModule(app.IBCKeeper), + transferModule, + ) + + // .. continues +``` + +### Application ABCI Ordering + +One addition from IBC is the concept of `HistoricalEntries` which are stored on the staking module. +Each entry contains the historical information for the `Header` and `ValidatorSet` of this chain which is stored +at each height during the `BeginBlock` call. The historical info is required to introspect the +past historical info at any given height in order to verify the light client `ConsensusState` during the +connection handshake. + +The IBC module also has +[`BeginBlock`](https://github.com/cosmos/ibc-go/blob/main/modules/core/02-client/abci.go) logic as well. This is optional as it is only required if your application uses the localhost client to connect two different modules from the same chain. + +:::tip +Only register the ibc module to the `SetOrderBeginBlockers` if your application will use the +localhost (*aka* loopback) client. +::: + +```go +// app.go +func NewApp(...args) *App { + // .. continuation from above + + // add staking and ibc modules to BeginBlockers + app.mm.SetOrderBeginBlockers( + // other modules ... + stakingtypes.ModuleName, ibchost.ModuleName, + ) + + // ... + + // NOTE: Capability module must occur first so that it can initialize any capabilities + // so that other modules that want to create or claim capabilities afterwards in InitChain + // can do so safely. + app.mm.SetOrderInitGenesis( + capabilitytypes.ModuleName, + // other modules ... + ibchost.ModuleName, ibctransfertypes.ModuleName, + ) + + // .. continues +``` + +:::warning +**IMPORTANT**: The capability module **must** be declared first in `SetOrderInitGenesis` +::: + +That's it! You have now wired up the IBC module and are now able to send fungible tokens across +different chains. If you want to have a broader view of the changes take a look into the SDK's +[`SimApp`](https://github.com/cosmos/ibc-go/blob/main/testing/simapp/app.go). diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/01-apps.md b/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/01-apps.md new file mode 100644 index 0000000..6f1881d --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/01-apps.md @@ -0,0 +1,56 @@ +--- +title: IBC Applications +sidebar_label: IBC Applications +sidebar_position: 1 +slug: /ibc/apps/apps +--- + +# IBC Applications + +:::note Synopsis +Learn how to build custom IBC application modules that enable packets to be sent to and received from other IBC-enabled chains. +::: + +This document serves as a guide for developers who want to write their own Inter-blockchain Communication Protocol (IBC) applications for custom use cases. + +Due to the modular design of the IBC protocol, IBC application developers do not need to concern themselves with the low-level details of clients, connections, and proof verification. Nevertheless, an overview of these low-level concepts can be found in [the Overview section](../01-overview.md). +The document goes into detail on the abstraction layer most relevant for application developers (channels and ports), and describes how to define your own custom packets, `IBCModule` callbacks and more to make an application module IBC ready. + +**To have your module interact over IBC you must:** + +- implement the `IBCModule` interface, i.e.: + - channel (opening) handshake callbacks + - channel closing handshake callbacks + - packet callbacks +- bind to a port(s) +- add keeper methods +- define your own packet data and acknowledgement structs as well as how to encode/decode them +- add a route to the IBC router + +The following sections provide a more detailed explanation of how to write an IBC application +module correctly corresponding to the listed steps. + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: + +## Working example + +For a real working example of an IBC application, you can look through the `ibc-transfer` module +which implements everything discussed in this section. + +Here are the useful parts of the module to look at: + +[Binding to transfer +port](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/genesis.go) + +[Sending transfer +packets](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/relay.go) + +[Implementing IBC +callbacks](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/ibc_module.go) diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/02-ibcmodule.md b/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/02-ibcmodule.md new file mode 100644 index 0000000..c3cb0a8 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/02-ibcmodule.md @@ -0,0 +1,351 @@ +--- +title: Implement `IBCModule` interface and callbacks +sidebar_label: Implement `IBCModule` interface and callbacks +sidebar_position: 2 +slug: /ibc/apps/ibcmodule +--- + +# Implement `IBCModule` interface and callbacks + +:::note Synopsis +Learn how to implement the `IBCModule` interface and all of the callbacks it requires. +::: + +The Cosmos SDK expects all IBC modules to implement the [`IBCModule` +interface](https://github.com/cosmos/ibc-go/tree/main/modules/core/05-port/types/module.go). This interface contains all of the callbacks IBC expects modules to implement. They include callbacks related to channel handshake, closing and packet callbacks (`OnRecvPacket`, `OnAcknowledgementPacket` and `OnTimeoutPacket`). + +```go +// IBCModule implements the ICS26 interface for given the keeper. +// The implementation of the IBCModule interface could for example be in a file called ibc_module.go, +// but ultimately file structure is up to the developer +type IBCModule struct { + keeper keeper.Keeper +} +``` + +Additionally, in the `module.go` file, add the following line: + +```go +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + // Add this line + _ porttypes.IBCModule = IBCModule{} +) +``` + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: + +## Channel handshake callbacks + +This section will describe the callbacks that are called during channel handshake execution. Among other things, it will claim channel capabilities passed on from core IBC. For a refresher on capabilities, check [the Overview section](../01-overview.md#capabilities). + +Here are the channel handshake callbacks that modules are expected to implement: + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `checkArguments` and `negotiateAppVersion` functions. + +```go +// Called by IBC Handler on MsgOpenInit +func (im IBCModule) OnChanOpenInit(ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + // Examples: + // - Abort if order == UNORDERED, + // - Abort if version is unsupported + if err := checkArguments(args); err != nil { + return "", err + } + + // OpenInit must claim the channelCapability that IBC passes into the callback + if err := im.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return "", err + } + + return version, nil +} + +// Called by IBC Handler on MsgOpenTry +func (im IBCModule) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + if err := checkArguments(args); err != nil { + return "", err + } + + // OpenTry must claim the channelCapability that IBC passes into the callback + if err := im.keeper.scopedKeeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return err + } + + // Construct application version + // IBC applications must return the appropriate application version + // This can be a simple string or it can be a complex version constructed + // from the counterpartyVersion and other arguments. + // The version returned will be the channel version used for both channel ends. + appVersion := negotiateAppVersion(counterpartyVersion, args) + + return appVersion, nil +} + +// Called by IBC Handler on MsgOpenAck +func (im IBCModule) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyVersion string, +) error { + if counterpartyVersion != types.Version { + return sdkerrors.Wrapf(types.ErrInvalidVersion, "invalid counterparty version: %s, expected %s", counterpartyVersion, types.Version) + } + + // do custom logic + + return nil +} + +// Called by IBC Handler on MsgOpenConfirm +func (im IBCModule) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // do custom logic + + return nil +} +``` + +The channel closing handshake will also invoke module callbacks that can return errors to abort the closing handshake. Closing a channel is a 2-step handshake, the initiating chain calls `ChanCloseInit` and the finalizing chain calls `ChanCloseConfirm`. + +```go +// Called by IBC Handler on MsgCloseInit +func (im IBCModule) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + // ... do custom finalization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} + +// Called by IBC Handler on MsgCloseConfirm +func (im IBCModule) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // ... do custom finalization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} +``` + +### Channel handshake version negotiation + +Application modules are expected to verify versioning used during the channel handshake procedure. + +- `OnChanOpenInit` will verify that the relayer-chosen parameters + are valid and perform any custom `INIT` logic. + It may return an error if the chosen parameters are invalid + in which case the handshake is aborted. + If the provided version string is non-empty, `OnChanOpenInit` should return + the version string if valid or an error if the provided version is invalid. + **If the version string is empty, `OnChanOpenInit` is expected to + return a default version string representing the version(s) + it supports.** + If there is no default version string for the application, + it should return an error if the provided version is an empty string. +- `OnChanOpenTry` will verify the relayer-chosen parameters along with the + counterparty-chosen version string and perform custom `TRY` logic. + If the relayer-chosen parameters + are invalid, the callback must return an error to abort the handshake. + If the counterparty-chosen version is not compatible with this module's + supported versions, the callback must return an error to abort the handshake. + If the versions are compatible, the try callback must select the final version + string and return it to core IBC. + `OnChanOpenTry` may also perform custom initialization logic. +- `OnChanOpenAck` will error if the counterparty selected version string + is invalid and abort the handshake. It may also perform custom ACK logic. + +Versions must be strings but can implement any versioning structure. If your application plans to +have linear releases then semantic versioning is recommended. If your application plans to release +various features in between major releases then it is advised to use the same versioning scheme +as IBC. This versioning scheme specifies a version identifier and compatible feature set with +that identifier. Valid version selection includes selecting a compatible version identifier with +a subset of features supported by your application for that version. The struct used for this +scheme can be found in [03-connection/types](https://github.com/cosmos/ibc-go/blob/main/modules/core/03-connection/types/version.go#L16). + +Since the version type is a string, applications have the ability to do simple version verification +via string matching or they can use the already implemented versioning system and pass the proto +encoded version into each handhshake call as necessary. + +ICS20 currently implements basic string matching with a single supported version. + +## Packet callbacks + +Just as IBC expects modules to implement callbacks for channel handshakes, it also expects modules to implement callbacks for handling the packet flow through a channel, as defined in the `IBCModule` interface. + +Once a module A and module B are connected to each other, relayers can start relaying packets and acknowledgements back and forth on the channel. + +![IBC packet flow diagram](./images/packet_flow.png) + +Briefly, a successful packet flow works as follows: + +1. module A sends a packet through the IBC module +2. the packet is received by module B +3. if module B writes an acknowledgement of the packet then module A will process the + acknowledgement +4. if the packet is not successfully received before the timeout, then module A processes the + packet's timeout. + +### Sending packets + +Modules **do not send packets through callbacks**, since the modules initiate the action of sending packets to the IBC module, as opposed to other parts of the packet flow where messages sent to the IBC +module must trigger execution on the port-bound module through the use of callbacks. Thus, to send a packet a module simply needs to call `SendPacket` on the `IBCChannelKeeper`. + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `EncodePacketData(customPacketData)` function. + +```go +// retrieve the dynamic capability for this channel +channelCap := scopedKeeper.GetCapability(ctx, channelCapName) +// Sending custom application packet data +data := EncodePacketData(customPacketData) +packet.Data = data +// Send packet to IBC, authenticating with channelCap +IBCChannelKeeper.SendPacket(ctx, channelCap, packet) +``` + +:::warning +In order to prevent modules from sending packets on channels they do not own, IBC expects +modules to pass in the correct channel capability for the packet's source channel. +::: + +### Receiving packets + +To handle receiving packets, the module must implement the `OnRecvPacket` callback. This gets +invoked by the IBC module after the packet has been proved valid and correctly processed by the IBC +keepers. Thus, the `OnRecvPacket` callback only needs to worry about making the appropriate state +changes given the packet data without worrying about whether the packet is valid or not. + +Modules may return to the IBC handler an acknowledgement which implements the `Acknowledgement` interface. +The IBC handler will then commit this acknowledgement of the packet so that a relayer may relay the +acknowledgement back to the sender module. + +The state changes that occurred during this callback will only be written if: + +- the acknowledgement was successful as indicated by the `Success()` function of the acknowledgement +- if the acknowledgement returned is nil indicating that an asynchronous process is occurring + +NOTE: Applications which process asynchronous acknowledgements must handle reverting state changes +when appropriate. Any state changes that occurred during the `OnRecvPacket` callback will be written +for asynchronous acknowledgements. + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `DecodePacketData(packet.Data)` function. + +```go +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, +) ibcexported.Acknowledgement { + // Decode the packet data + packetData := DecodePacketData(packet.Data) + + // do application state changes based on packet data and return the acknowledgement + // NOTE: The acknowledgement will indicate to the IBC handler if the application + // state changes should be written via the `Success()` function. Application state + // changes are only written if the acknowledgement is successful or the acknowledgement + // returned is nil indicating that an asynchronous acknowledgement will occur. + ack := processPacket(ctx, packet, packetData) + + return ack +} +``` + +Reminder, the `Acknowledgement` interface: + +```go +// Acknowledgement defines the interface used to return +// acknowledgements in the OnRecvPacket callback. +type Acknowledgement interface { + Success() bool + Acknowledgement() []byte +} +``` + +### Acknowledging packets + +After a module writes an acknowledgement, a relayer can relay back the acknowledgement to the sender module. The sender module can +then process the acknowledgement using the `OnAcknowledgementPacket` callback. The contents of the +acknowledgement is entirely up to the modules on the channel (just like the packet data); however, it +may often contain information on whether the packet was successfully processed along +with some additional data that could be useful for remediation if the packet processing failed. + +Since the modules are responsible for agreeing on an encoding/decoding standard for packet data and +acknowledgements, IBC will pass in the acknowledgements as `[]byte` to this callback. The callback +is responsible for decoding the acknowledgement and processing it. + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `DecodeAcknowledgement(acknowledgments)` and `processAck(ack)` functions. + +```go +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, +) (*sdk.Result, error) { + // Decode acknowledgement + ack := DecodeAcknowledgement(acknowledgement) + + // process ack + res, err := processAck(ack) + return res, err +} +``` + +### Timeout packets + +If the timeout for a packet is reached before the packet is successfully received or the +counterparty channel end is closed before the packet is successfully received, then the receiving +chain can no longer process it. Thus, the sending chain must process the timeout using +`OnTimeoutPacket` to handle this situation. Again the IBC module will verify that the timeout is +indeed valid, so our module only needs to implement the state machine logic for what to do once a +timeout is reached and the packet can no longer be received. + +```go +func (im IBCModule) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, +) (*sdk.Result, error) { + // do custom timeout logic +} +``` diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/03-bindports.md b/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/03-bindports.md new file mode 100644 index 0000000..303168d --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/03-bindports.md @@ -0,0 +1,122 @@ +--- +title: Bind ports +sidebar_label: Bind ports +sidebar_position: 3 +slug: /ibc/apps/bindports +--- + +# Bind ports + +:::note Synopsis +Learn what changes to make to bind modules to their ports on initialization. +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: +Currently, ports must be bound on app initialization. In order to bind modules to their respective ports on initialization, the following needs to be implemented: + +> Note that `portID` does not refer to a certain numerical ID, like `localhost:8080` with a `portID` 8080. Rather it refers to the application module the port binds. For IBC Modules built with the Cosmos SDK, it defaults to the module's name and for Cosmwasm contracts it defaults to the contract address. + +1. Add port ID to the `GenesisState` proto definition: + + ```protobuf + message GenesisState { + string port_id = 1; + // other fields + } + ``` + +1. Add port ID as a key to the module store: + + ```go + // x//types/keys.go + const ( + // ModuleName defines the IBC Module name + ModuleName = "moduleName" + + // Version defines the current version the IBC + // module supports + Version = "moduleVersion-1" + + // PortID is the default port id that module binds to + PortID = "portID" + + // ... + ) + ``` + +1. Add port ID to `x//types/genesis.go`: + + ```go + // in x//types/genesis.go + + // DefaultGenesisState returns a GenesisState with "transfer" as the default PortID. + func DefaultGenesisState() *GenesisState { + return &GenesisState{ + PortId: PortID, + // additional k-v fields + } + } + + // Validate performs basic genesis state validation returning an error upon any + // failure. + func (gs GenesisState) Validate() error { + if err := host.PortIdentifierValidator(gs.PortId); err != nil { + return err + } + //additional validations + + return gs.Params.Validate() + } + ``` + +1. Bind to port(s) in the module keeper's `InitGenesis`: + + ```go + // InitGenesis initializes the ibc-module state and binds to PortID. + func (k Keeper) InitGenesis(ctx sdk.Context, state types.GenesisState) { + k.SetPort(ctx, state.PortId) + + // ... + + // Only try to bind to port if it is not already bound, since we may already own + // port capability from capability InitGenesis + if !k.IsBound(ctx, state.PortId) { + // transfer module binds to the transfer port on InitChain + // and claims the returned capability + err := k.BindPort(ctx, state.PortId) + if err != nil { + panic(fmt.Sprintf("could not claim port capability: %v", err)) + } + } + + // ... + } + ``` + + With: + + ```go + // IsBound checks if the module is already bound to the desired port + func (k Keeper) IsBound(ctx sdk.Context, portID string) bool { + _, ok := k.scopedKeeper.GetCapability(ctx, host.PortPath(portID)) + return ok + } + + // BindPort defines a wrapper function for the port Keeper's function in + // order to expose it to module's InitGenesis function + func (k Keeper) BindPort(ctx sdk.Context, portID string) error { + cap := k.portKeeper.BindPort(ctx, portID) + return k.ClaimCapability(ctx, cap, host.PortPath(portID)) + } + ``` + + The module binds to the desired port(s) and returns the capabilities. + + In the above we find reference to keeper methods that wrap other keeper functionality, in the next section the keeper methods that need to be implemented will be defined. diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/04-keeper.md b/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/04-keeper.md new file mode 100644 index 0000000..cf4fac6 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/04-keeper.md @@ -0,0 +1,96 @@ +--- +title: Keeper +sidebar_label: Keeper +sidebar_position: 4 +slug: /ibc/apps/keeper +--- + +# Keeper + +:::note Synopsis +Learn how to implement the IBC Module keeper. +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: +In the previous sections, on channel handshake callbacks and port binding in `InitGenesis`, a reference was made to keeper methods that need to be implemented when creating a custom IBC module. Below is an overview of how to define an IBC module's keeper. + +> Note that some code has been left out for clarity, to get a full code overview, please refer to [the transfer module's keeper in the ibc-go repo](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/keeper.go). + +```go +// Keeper defines the IBC app module keeper +type Keeper struct { + storeKey sdk.StoreKey + cdc codec.BinaryCodec + paramSpace paramtypes.Subspace + + channelKeeper types.ChannelKeeper + portKeeper types.PortKeeper + scopedKeeper capabilitykeeper.ScopedKeeper + + // ... additional according to custom logic +} + +// NewKeeper creates a new IBC app module Keeper instance +func NewKeeper( + // args +) Keeper { + // ... + + return Keeper{ + cdc: cdc, + storeKey: key, + paramSpace: paramSpace, + + channelKeeper: channelKeeper, + portKeeper: portKeeper, + scopedKeeper: scopedKeeper, + + // ... additional according to custom logic + } +} + +// IsBound checks if the IBC app module is already bound to the desired port +func (k Keeper) IsBound(ctx sdk.Context, portID string) bool { + _, ok := k.scopedKeeper.GetCapability(ctx, host.PortPath(portID)) + return ok +} + +// BindPort defines a wrapper function for the port Keeper's function in +// order to expose it to module's InitGenesis function +func (k Keeper) BindPort(ctx sdk.Context, portID string) error { + cap := k.portKeeper.BindPort(ctx, portID) + return k.ClaimCapability(ctx, cap, host.PortPath(portID)) +} + +// GetPort returns the portID for the IBC app module. Used in ExportGenesis +func (k Keeper) GetPort(ctx sdk.Context) string { + store := ctx.KVStore(k.storeKey) + return string(store.Get(types.PortKey)) +} + +// SetPort sets the portID for the IBC app module. Used in InitGenesis +func (k Keeper) SetPort(ctx sdk.Context, portID string) { + store := ctx.KVStore(k.storeKey) + store.Set(types.PortKey, []byte(portID)) +} + +// AuthenticateCapability wraps the scopedKeeper's AuthenticateCapability function +func (k Keeper) AuthenticateCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) bool { + return k.scopedKeeper.AuthenticateCapability(ctx, cap, name) +} + +// ClaimCapability allows the IBC app module to claim a capability that core IBC +// passes to it +func (k Keeper) ClaimCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) error { + return k.scopedKeeper.ClaimCapability(ctx, cap, name) +} + +// ... additional according to custom logic +``` diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/05-packets_acks.md b/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/05-packets_acks.md new file mode 100644 index 0000000..e1f8dc1 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/05-packets_acks.md @@ -0,0 +1,108 @@ +--- +title: Define packets and acks +sidebar_label: Define packets and acks +sidebar_position: 5 +slug: /ibc/apps/packets_acks +--- + +# Define packets and acks + +:::note Synopsis +Learn how to define custom packet and acknowledgement structs and how to encode and decode them. +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: + +## Custom packets + +Modules connected by a channel must agree on what application data they are sending over the +channel, as well as how they will encode/decode it. This process is not specified by IBC as it is up +to each application module to determine how to implement this agreement. However, for most +applications this will happen as a version negotiation during the channel handshake. While more +complex version negotiation is possible to implement inside the channel opening handshake, a very +simple version negotiation is implemented in the [ibc-transfer module](https://github.com/cosmos/ibc-go/tree/main/modules/apps/transfer/module.go). + +Thus, a module must define its custom packet data structure, along with a well-defined way to +encode and decode it to and from `[]byte`. + +```go +// Custom packet data defined in application module +type CustomPacketData struct { + // Custom fields ... +} + +EncodePacketData(packetData CustomPacketData) []byte { + // encode packetData to bytes +} + +DecodePacketData(encoded []byte) (CustomPacketData) { + // decode from bytes to packet data +} +``` + +> Note that the `CustomPacketData` struct is defined in the proto definition and then compiled by the protobuf compiler. + +Then a module must encode its packet data before sending it through IBC. + +```go +// Sending custom application packet data +data := EncodePacketData(customPacketData) +packet.Data = data +IBCChannelKeeper.SendPacket(ctx, packet) +``` + +A module receiving a packet must decode the `PacketData` into a structure it expects so that it can +act on it. + +```go +// Receiving custom application packet data (in OnRecvPacket) +packetData := DecodePacketData(packet.Data) +// handle received custom packet data +``` + +## Acknowledgements + +Modules may commit an acknowledgement upon receiving and processing a packet in the case of synchronous packet processing. +In the case where a packet is processed at some later point after the packet has been received (asynchronous execution), the acknowledgement +will be written once the packet has been processed by the application which may be well after the packet receipt. + +NOTE: Most blockchain modules will want to use the synchronous execution model in which the module processes and writes the acknowledgement +for a packet as soon as it has been received from the IBC module. + +This acknowledgement can then be relayed back to the original sender chain, which can take action +depending on the contents of the acknowledgement. + +Just as packet data was opaque to IBC, acknowledgements are similarly opaque. Modules must pass and +receive acknowledegments with the IBC modules as byte strings. + +Thus, modules must agree on how to encode/decode acknowledgements. The process of creating an +acknowledgement struct along with encoding and decoding it, is very similar to the packet data +example above. [ICS 04](https://github.com/cosmos/ibc/blob/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope) +specifies a recommended format for acknowledgements. This acknowledgement type can be imported from +[channel types](https://github.com/cosmos/ibc-go/tree/main/modules/core/04-channel/types). + +While modules may choose arbitrary acknowledgement structs, a default acknowledgement types is provided by IBC [here](https://github.com/cosmos/ibc-go/blob/main/proto/ibc/core/channel/v1/channel.proto): + +```protobuf +// Acknowledgement is the recommended acknowledgement format to be used by +// app-specific protocols. +// NOTE: The field numbers 21 and 22 were explicitly chosen to avoid accidental +// conflicts with other protobuf message formats used for acknowledgements. +// The first byte of any message with this format will be the non-ASCII values +// `0xaa` (result) or `0xb2` (error). Implemented as defined by ICS: +// https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope +message Acknowledgement { + // response contains either a result or an error and must be non-empty + oneof response { + bytes result = 21; + string error = 22; + } +} +``` diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/06-routing.md b/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/06-routing.md new file mode 100644 index 0000000..7e6c205 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/06-routing.md @@ -0,0 +1,44 @@ +--- +title: Routing +sidebar_label: Routing +sidebar_position: 6 +slug: /ibc/apps/routing +--- + +# Routing + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: +:::note Synopsis +Learn how to hook a route to the IBC router for the custom IBC module. +::: + +As mentioned above, modules must implement the `IBCModule` interface (which contains both channel +handshake callbacks and packet handling callbacks). The concrete implementation of this interface +must be registered with the module name as a route on the IBC `Router`. + +```go +// app.go +func NewApp(...args) *App { +// ... + +// Create static IBC router, add module routes, then set and seal it +ibcRouter := port.NewRouter() + +ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferModule) +// Note: moduleCallbacks must implement IBCModule interface +ibcRouter.AddRoute(moduleName, moduleCallbacks) + +// Setting Router will finalize all routes by sealing router +// No more routes can be added +app.IBCKeeper.SetRouter(ibcRouter) + +// ... +} +``` diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/_category_.json b/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/_category_.json new file mode 100644 index 0000000..1c34da9 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Applications", + "position": 3, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/images/packet_flow.png b/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/images/packet_flow.png new file mode 100644 index 0000000..e5bae3f Binary files /dev/null and b/docs/versioned_docs/version-v4.6.x/01-ibc/03-apps/images/packet_flow.png differ diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/04-middleware/01-develop.md b/docs/versioned_docs/version-v4.6.x/01-ibc/04-middleware/01-develop.md new file mode 100644 index 0000000..5a0ccce --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/01-ibc/04-middleware/01-develop.md @@ -0,0 +1,422 @@ +--- +title: IBC middleware +sidebar_label: IBC middleware +sidebar_position: 1 +slug: /ibc/middleware/develop +--- + +# IBC middleware + +:::note Synopsis +Learn how to write your own custom middleware to wrap an IBC application, and understand how to hook different middleware to IBC base applications to form different IBC application stacks +:::. + +This document serves as a guide for middleware developers who want to write their own middleware and for chain developers who want to use IBC middleware on their chains. + +IBC applications are designed to be self-contained modules that implement their own application-specific logic through a set of interfaces with the core IBC handlers. These core IBC handlers, in turn, are designed to enforce the correctness properties of IBC (transport, authentication, ordering) while delegating all application-specific handling to the IBC application modules. However, there are cases where some functionality may be desired by many applications, yet not appropriate to place in core IBC. + +Middleware allows developers to define the extensions as separate modules that can wrap over the base application. This middleware can thus perform its own custom logic, and pass data into the application so that it may run its logic without being aware of the middleware's existence. This allows both the application and the middleware to implement its own isolated logic while still being able to run as part of a single packet flow. + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC Integration](../02-integration.md) +- [IBC Application Developer Guide](../03-apps/01-apps.md) + +::: + +## Definitions + +`Middleware`: A self-contained module that sits between core IBC and an underlying IBC application during packet execution. All messages between core IBC and underlying application must flow through middleware, which may perform its own custom logic. + +`Underlying Application`: An underlying application is the application that is directly connected to the middleware in question. This underlying application may itself be middleware that is chained to a base application. + +`Base Application`: A base application is an IBC application that does not contain any middleware. It may be nested by 0 or multiple middleware to form an application stack. + +`Application Stack (or stack)`: A stack is the complete set of application logic (middleware(s) + base application) that gets connected to core IBC. A stack may be just a base application, or it may be a series of middlewares that nest a base application. + +## Create a custom IBC middleware + +IBC middleware will wrap over an underlying IBC application and sits between core IBC and the application. It has complete control in modifying any message coming from IBC to the application, and any message coming from the application to core IBC. Thus, middleware must be completely trusted by chain developers who wish to integrate them, however this gives them complete flexibility in modifying the application(s) they wrap. + +### Interfaces + +```go +// Middleware implements the ICS26 Module interface +type Middleware interface { + porttypes.IBCModule // middleware has access to an underlying application which may be wrapped by more middleware + ics4Wrapper: ICS4Wrapper // middleware has access to ICS4Wrapper which may be core IBC Channel Handler or a higher-level middleware that wraps this middleware. +} +``` + +```typescript +// This is implemented by ICS4 and all middleware that are wrapping base application. +// The base application will call `sendPacket` or `writeAcknowledgement` of the middleware directly above them +// which will call the next middleware until it reaches the core IBC handler. +type ICS4Wrapper interface { + SendPacket(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet exported.Packet) error + WriteAcknowledgement(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet exported.Packet, ack exported.Acknowledgement) error + GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) +} +``` + +### Implement `IBCModule` interface and callbacks + +The `IBCModule` is a struct that implements the [ICS-26 interface (`porttypes.IBCModule`)](https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port/types/module.go#L11-L106). It is recommended to separate these callbacks into a separate file `ibc_module.go`. As will be mentioned in the [integration section](02-integration.md), this struct should be different than the struct that implements `AppModule` in case the middleware maintains its own internal state and processes separate SDK messages. + +The middleware must have access to the underlying application, and be called before during all ICS-26 callbacks. It may execute custom logic during these callbacks, and then call the underlying application's callback. Middleware **may** choose not to call the underlying application's callback at all. Though these should generally be limited to error cases. + +In the case where the IBC middleware expects to speak to a compatible IBC middleware on the counterparty chain, they must use the channel handshake to negotiate the middleware version without interfering in the version negotiation of the underlying application. + +Middleware accomplishes this by formatting the version in a JSON-encoded string containing the middleware version and the application version. The application version may as well be a JSON-encoded string, possibly including further middleware and app versions, if the application stack consists of multiple milddlewares wrapping a base application. The format of the version is specified in ICS-30 as the following: + +```json +{ + "": "", + "app_version": "" +} +``` + +The `` key in the JSON struct should be replaced by the actual name of the key for the corresponding middleware (e.g. `fee_version`). + +During the handshake callbacks, the middleware can unmarshal the version string and retrieve the middleware and application versions. It can do its negotiation logic on ``, and pass the `` to the underlying application. + +The middleware should simply pass the capability in the callback arguments along to the underlying application so that it may be claimed by the base application. The base application will then pass the capability up the stack in order to authenticate an outgoing packet/acknowledgement. + +In the case where the middleware wishes to send a packet or acknowledgment without the involvement of the underlying application, it should be given access to the same `scopedKeeper` as the base application so that it can retrieve the capabilities by itself. + +### Handshake callbacks + +#### `OnChanOpenInit` + +```go +func (im IBCModule) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + if version != "" { + // try to unmarshal JSON-encoded version string and pass + // the app-specific version to app callback. + // otherwise, pass version directly to app callback. + metadata, err := Unmarshal(version) + if err != nil { + // Since it is valid for fee version to not be specified, + // the above middleware version may be for another middleware. + // Pass the entire version string onto the underlying application. + return im.app.OnChanOpenInit( + ctx, + order, + connectionHops, + portID, + channelID, + channelCap, + counterparty, + version, + ) + } + else { + metadata = { + // set middleware version to default value + MiddlewareVersion: defaultMiddlewareVersion, + // allow application to return its default version + AppVersion: "", + } + } + + doCustomLogic() + + // if the version string is empty, OnChanOpenInit is expected to return + // a default version string representing the version(s) it supports + appVersion, err := im.app.OnChanOpenInit( + ctx, + order, + connectionHops, + portID, + channelID, + channelCap, + counterparty, + metadata.AppVersion, // note we only pass app version here + ) + if err != nil { + return "", err + } + + version := constructVersion(metadata.MiddlewareVersion, appVersion) + + return version, nil +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L34-L82) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnChanOpenTry` + +```go +func OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + // try to unmarshal JSON-encoded version string and pass + // the app-specific version to app callback. + // otherwise, pass version directly to app callback. + cpMetadata, err := Unmarshal(counterpartyVersion) + if err != nil { + return app.OnChanOpenTry( + ctx, + order, + connectionHops, + portID, + channelID, + channelCap, + counterparty, + counterpartyVersion, + ) + } + + doCustomLogic() + + // Call the underlying application's OnChanOpenTry callback. + // The try callback must select the final app-specific version string and return it. + appVersion, err := app.OnChanOpenTry( + ctx, + order, + connectionHops, + portID, + channelID, + channelCap, + counterparty, + cpMetadata.AppVersion, // note we only pass counterparty app version here + ) + if err != nil { + return "", err + } + + // negotiate final middleware version + middlewareVersion := negotiateMiddlewareVersion(cpMetadata.MiddlewareVersion) + version := constructVersion(middlewareVersion, appVersion) + + return version, nil +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L84-L124) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnChanOpenAck` + +```go +func OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyChannelID string, + counterpartyVersion string, +) error { + // try to unmarshal JSON-encoded version string and pass + // the app-specific version to app callback. + // otherwise, pass version directly to app callback. + cpMetadata, err = UnmarshalJSON(counterpartyVersion) + if err != nil { + return app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) + } + + if !isCompatible(cpMetadata.MiddlewareVersion) { + return error + } + doCustomLogic() + + // call the underlying application's OnChanOpenTry callback + return app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, cpMetadata.AppVersion) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L126-L152) an example implementation of this callback for the ICS29 Fee Middleware module. + +### `OnChanOpenConfirm` + +```go +func OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + doCustomLogic() + + return app.OnChanOpenConfirm(ctx, portID, channelID) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L154-L162) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnChanCloseInit` + +```go +func OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + doCustomLogic() + + return app.OnChanCloseInit(ctx, portID, channelID) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L164-L187) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnChanCloseConfirm` + +```go +func OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + doCustomLogic() + + return app.OnChanCloseConfirm(ctx, portID, channelID) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L189-L212) an example implementation of this callback for the ICS29 Fee Middleware module. + +**NOTE**: Middleware that does not need to negotiate with a counterparty middleware on the remote stack will not implement the version unmarshalling and negotiation, and will simply perform its own custom logic on the callbacks without relying on the counterparty behaving similarly. + +### Packet callbacks + +The packet callbacks just like the handshake callbacks wrap the application's packet callbacks. The packet callbacks are where the middleware performs most of its custom logic. The middleware may read the packet flow data and perform some additional packet handling, or it may modify the incoming data before it reaches the underlying application. This enables a wide degree of usecases, as a simple base application like token-transfer can be transformed for a variety of usecases by combining it with custom middleware. + +#### `OnRecvPacket` + +```go +func OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) ibcexported.Acknowledgement { + doCustomLogic(packet) + + ack := app.OnRecvPacket(ctx, packet, relayer) + + doCustomLogic(ack) // middleware may modify outgoing ack + return ack +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L214-L237) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnAcknowledgementPacket` + +```go +func OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + doCustomLogic(packet, ack) + + return app.OnAcknowledgementPacket(ctx, packet, ack, relayer) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L239-L292) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnTimeoutPacket` + +```go +func OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + doCustomLogic(packet) + + return app.OnTimeoutPacket(ctx, packet, relayer) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L294-L334) an example implementation of this callback for the ICS29 Fee Middleware module. + +### ICS-4 wrappers + +Middleware must also wrap ICS-4 so that any communication from the application to the `channelKeeper` goes through the middleware first. Similar to the packet callbacks, the middleware may modify outgoing acknowledgements and packets in any way it wishes. + +#### `SendPacket` + +```go +func SendPacket( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + appPacket exported.PacketI, +) { + // middleware may modify packet + packet = doCustomLogic(appPacket) + + return ics4Keeper.SendPacket(ctx, chanCap, packet) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L336-L343) an example implementation of this function for the ICS29 Fee Middleware module. + +#### `WriteAcknowledgement` + +```go +// only called for async acks +func WriteAcknowledgement( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + packet exported.PacketI, + ack exported.Acknowledgement, +) { + // middleware may modify acknowledgement + ack_bytes = doCustomLogic(ack) + + return ics4Keeper.WriteAcknowledgement(packet, ack_bytes) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L345-L353) an example implementation of this function for the ICS29 Fee Middleware module. + +#### `GetAppVersion` + +```go +// middleware must return the underlying application version +func GetAppVersion( + ctx sdk.Context, + portID, + channelID string, +) (string, bool) { + version, found := ics4Keeper.GetAppVersion(ctx, portID, channelID) + if !found { + return "", false + } + + if !MiddlewareEnabled { + return version, true + } + + // unwrap channel version + metadata, err := Unmarshal(version) + if err != nil { + panic(fmt.Errof("unable to unmarshal version: %w", err)) + } + + return metadata.AppVersion, true +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L355-L358) an example implementation of this function for the ICS29 Fee Middleware module. diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/04-middleware/02-integration.md b/docs/versioned_docs/version-v4.6.x/01-ibc/04-middleware/02-integration.md new file mode 100644 index 0000000..d721023 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/01-ibc/04-middleware/02-integration.md @@ -0,0 +1,72 @@ +--- +title: Integrating IBC middleware into a chain +sidebar_label: Integrating IBC middleware into a chain +sidebar_position: 2 +slug: /ibc/middleware/integration +--- + + +# Integrating IBC middleware into a chain + +Learn how to integrate IBC middleware(s) with a base application to your chain. The following document only applies for Cosmos SDK chains. + +If the middleware is maintaining its own state and/or processing SDK messages, then it should create and register its SDK module **only once** with the module manager in `app.go`. + +All middleware must be connected to the IBC router and wrap over an underlying base IBC application. An IBC application may be wrapped by many layers of middleware, only the top layer middleware should be hooked to the IBC router, with all underlying middlewares and application getting wrapped by it. + +The order of middleware **matters**, function calls from IBC to the application travel from top-level middleware to the bottom middleware and then to the application. Function calls from the application to IBC goes through the bottom middleware in order to the top middleware and then to core IBC handlers. Thus the same set of middleware put in different orders may produce different effects. + +## Example integration + +```go +// app.go + +// middleware 1 and middleware 3 are stateful middleware, +// perhaps implementing separate sdk.Msg and Handlers +mw1Keeper := mw1.NewKeeper(storeKey1) +mw3Keeper := mw3.NewKeeper(storeKey3) + +// Only create App Module **once** and register in app module +// if the module maintains independent state and/or processes sdk.Msgs +app.moduleManager = module.NewManager( + ... + mw1.NewAppModule(mw1Keeper), + mw3.NewAppModule(mw3Keeper), + transfer.NewAppModule(transferKeeper), + custom.NewAppModule(customKeeper) +) + +mw1IBCModule := mw1.NewIBCModule(mw1Keeper) +mw2IBCModule := mw2.NewIBCModule() // middleware2 is stateless middleware +mw3IBCModule := mw3.NewIBCModule(mw3Keeper) + +scopedKeeperTransfer := capabilityKeeper.NewScopedKeeper("transfer") +scopedKeeperCustom1 := capabilityKeeper.NewScopedKeeper("custom1") +scopedKeeperCustom2 := capabilityKeeper.NewScopedKeeper("custom2") + +// NOTE: IBC Modules may be initialized any number of times provided they use a separate +// scopedKeeper and underlying port. + +// initialize base IBC applications +// if you want to create two different stacks with the same base application, +// they must be given different scopedKeepers and assigned different ports. +transferIBCModule := transfer.NewIBCModule(transferKeeper) +customIBCModule1 := custom.NewIBCModule(customKeeper, "portCustom1") +customIBCModule2 := custom.NewIBCModule(customKeeper, "portCustom2") + +// create IBC stacks by combining middleware with base application +// NOTE: since middleware2 is stateless it does not require a Keeper +// stack 1 contains mw1 -> mw3 -> transfer +stack1 := mw1.NewIBCMiddleware(mw3.NewIBCMiddleware(transferIBCModule, mw3Keeper), mw1Keeper) +// stack 2 contains mw3 -> mw2 -> custom1 +stack2 := mw3.NewIBCMiddleware(mw2.NewIBCMiddleware(customIBCModule1), mw3Keeper) +// stack 3 contains mw2 -> mw1 -> custom2 +stack3 := mw2.NewIBCMiddleware(mw1.NewIBCMiddleware(customIBCModule2, mw1Keeper)) + +// associate each stack with the moduleName provided by the underlying scopedKeeper +ibcRouter := porttypes.NewRouter() +ibcRouter.AddRoute("transfer", stack1) +ibcRouter.AddRoute("custom1", stack2) +ibcRouter.AddRoute("custom2", stack3) +app.IBCKeeper.SetRouter(ibcRouter) +``` diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/04-middleware/_category_.json b/docs/versioned_docs/version-v4.6.x/01-ibc/04-middleware/_category_.json new file mode 100644 index 0000000..ec27d4e --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/01-ibc/04-middleware/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Middleware", + "position": 4, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/05-upgrades/00-intro.md b/docs/versioned_docs/version-v4.6.x/01-ibc/05-upgrades/00-intro.md new file mode 100644 index 0000000..eff2de6 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/01-ibc/05-upgrades/00-intro.md @@ -0,0 +1,15 @@ +--- +title: Upgrading IBC Chains Overview +sidebar_label: Overview +sidebar_position: 0 +slug: /ibc/upgrades/intro +--- + +### Upgrading IBC Chains Overview + +This directory contains information on how to upgrade an IBC chain without breaking counterparty clients and connections. + +IBC-connected chains must be able to upgrade without breaking connections to other chains. Otherwise there would be a massive disincentive towards upgrading and disrupting high-value IBC connections, thus preventing chains in the IBC ecosystem from evolving and improving. Many chain upgrades may be irrelevant to IBC, however some upgrades could potentially break counterparty clients if not handled correctly. Thus, any IBC chain that wishes to perform an IBC-client-breaking upgrade must perform an IBC upgrade in order to allow counterparty clients to securely upgrade to the new light client. + +1. The [quick-guide](./01-quick-guide.md) describes how IBC-connected chains can perform client-breaking upgrades and how relayers can securely upgrade counterparty clients using the SDK. +2. The [developer-guide](./02-developer-guide.md) is a guide for developers intending to develop IBC client implementations with upgrade functionality. diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/05-upgrades/01-quick-guide.md b/docs/versioned_docs/version-v4.6.x/01-ibc/05-upgrades/01-quick-guide.md new file mode 100644 index 0000000..ad21816 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/01-ibc/05-upgrades/01-quick-guide.md @@ -0,0 +1,60 @@ +--- +title: How to Upgrade IBC Chains and their Clients +sidebar_label: How to Upgrade IBC Chains and their Clients +sidebar_position: 1 +slug: /ibc/upgrades/quick-guide +--- + + +# How to Upgrade IBC Chains and their Clients + +:::note Synopsis +Learn how to upgrade your chain and counterparty clients. +::: + +The information in this doc for upgrading chains is relevant to SDK chains. However, the guide for counterparty clients is relevant to any Tendermint client that enables upgrades. + +## IBC Client Breaking Upgrades + +IBC-connected chains must perform an IBC upgrade if their upgrade will break counterparty IBC clients. The current IBC protocol supports upgrading tendermint chains for a specific subset of IBC-client-breaking upgrades. Here is the exhaustive list of IBC client-breaking upgrades and whether the IBC protocol currently supports such upgrades. + +IBC currently does **NOT** support unplanned upgrades. All of the following upgrades must be planned and committed to in advance by the upgrading chain, in order for counterparty clients to maintain their connections securely. + +Note: Since upgrades are only implemented for Tendermint clients, this doc only discusses upgrades on Tendermint chains that would break counterparty IBC Tendermint Clients. + +1. Changing the Chain-ID: **Supported** +2. Changing the UnbondingPeriod: **Partially Supported**, chains may increase the unbonding period with no issues. However, decreasing the unbonding period may irreversibly break some counterparty clients. Thus, it is **not recommended** that chains reduce the unbonding period. +3. Changing the height (resetting to 0): **Supported**, so long as chains remember to increment the revision number in their chain-id. +4. Changing the ProofSpecs: **Supported**, this should be changed if the proof structure needed to verify IBC proofs is changed across the upgrade. Ex: Switching from an IAVL store, to a SimpleTree Store +5. Changing the UpgradePath: **Supported**, this might involve changing the key under which upgraded clients and consensus states are stored in the upgrade store, or even migrating the upgrade store itself. +6. Migrating the IBC store: **Unsupported**, as the IBC store location is negotiated by the connection. +7. Upgrading to a backwards compatible version of IBC: Supported +8. Upgrading to a non-backwards compatible version of IBC: **Unsupported**, as IBC version is negotiated on connection handshake. +9. Changing the Tendermint LightClient algorithm: **Partially Supported**. Changes to the light client algorithm that do not change the ClientState or ConsensusState struct may be supported, provided that the counterparty is also upgraded to support the new light client algorithm. Changes that require updating the ClientState and ConsensusState structs themselves are theoretically possible by providing a path to translate an older ClientState struct into the new ClientState struct; however this is not currently implemented. + +### Step-by-Step Upgrade Process for SDK chains + +If the IBC-connected chain is conducting an upgrade that will break counterparty clients, it must ensure that the upgrade is first supported by IBC using the list above and then execute the upgrade process described below in order to prevent counterparty clients from breaking. + +1. Create a 02-client [`UpgradeProposal`](https://github.com/cosmos/ibc-go/blob/v4.4.2/proto/ibc/core/client/v1/client.proto#L58-L77) with an `UpgradePlan` and a new IBC ClientState in the `UpgradedClientState` field. Note that the `UpgradePlan` must specify an upgrade height **only** (no upgrade time), and the `ClientState` should only include the fields common to all valid clients and zero out any client-customizable fields (such as TrustingPeriod). +2. Vote on and pass the `UpgradeProposal` + +Upon the `UpgradeProposal` passing, the upgrade module will commit the UpgradedClient under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedClient`. On the block right before the upgrade height, the upgrade module will also commit an initial consensus state for the next chain under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedConsState`. + +Once the chain reaches the upgrade height and halts, a relayer can upgrade the counterparty clients to the last block of the old chain. They can then submit the proofs of the `UpgradedClient` and `UpgradedConsensusState` against this last block and upgrade the counterparty client. + +### Step-by-Step Upgrade Process for Relayers Upgrading Counterparty Clients + +Once the upgrading chain has committed to upgrading, relayers must wait till the chain halts at the upgrade height before upgrading counterparty clients. This is because chains may reschedule or cancel upgrade plans before they occur. Thus, relayers must wait till the chain reaches the upgrade height and halts before they can be sure the upgrade will take place. + +Thus, the upgrade process for relayers trying to upgrade the counterparty clients is as follows: + +1. Wait for the upgrading chain to reach the upgrade height and halt +2. Query a full node for the proofs of `UpgradedClient` and `UpgradedConsensusState` at the last height of the old chain. +3. Update the counterparty client to the last height of the old chain using the `UpdateClient` msg. +4. Submit an `UpgradeClient` msg to the counterparty chain with the `UpgradedClient`, `UpgradedConsensusState` and their respective proofs. +5. Submit an `UpdateClient` msg to the counterparty chain with a header from the new upgraded chain. + +The Tendermint client on the counterparty chain will verify that the upgrading chain did indeed commit to the upgraded client and upgraded consensus state at the upgrade height (since the upgrade height is included in the key). If the proofs are verified against the upgrade height, then the client will upgrade to the new client while retaining all of its client-customized fields. Thus, it will retain its old TrustingPeriod, TrustLevel, MaxClockDrift, etc; while adopting the new chain-specified fields such as UnbondingPeriod, ChainId, UpgradePath, etc. Note, this can lead to an invalid client since the old client-chosen fields may no longer be valid given the new chain-chosen fields. Upgrading chains should try to avoid these situations by not altering parameters that can break old clients. For an example, see the UnbondingPeriod example in the supported upgrades section. + +The upgraded consensus state will serve purely as a basis of trust for future `UpdateClientMsgs` and will not contain a consensus root to perform proof verification against. Thus, relayers must submit an `UpdateClientMsg` with a header from the new chain so that the connection can be used for proof verification again. diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/05-upgrades/02-developer-guide.md b/docs/versioned_docs/version-v4.6.x/01-ibc/05-upgrades/02-developer-guide.md new file mode 100644 index 0000000..98f1d42 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/01-ibc/05-upgrades/02-developer-guide.md @@ -0,0 +1,56 @@ +--- +title: IBC Client Developer Guide to Upgrades +sidebar_label: IBC Client Developer Guide to Upgrades +sidebar_position: 2 +slug: /ibc/upgrades/developer-guide +--- + + +# IBC Client Developer Guide to Upgrades + +:::note Synopsis +Learn how to implement upgrade functionality for your custom IBC client. +::: + +As mentioned in the [README](./00-intro.md), it is vital that high-value IBC clients can upgrade along with their underlying chains to avoid disruption to the IBC ecosystem. Thus, IBC client developers will want to implement upgrade functionality to enable clients to maintain connections and channels even across chain upgrades. + +The IBC protocol allows client implementations to provide a path to upgrading clients given the upgraded client state, upgraded consensus state and proofs for each. + +```go +// Upgrade functions +// NOTE: proof heights are not included as upgrade to a new revision is expected to pass only on the last +// height committed by the current revision. Clients are responsible for ensuring that the planned last +// height of the current revision is somehow encoded in the proof verification process. +// This is to ensure that no premature upgrades occur, since upgrade plans committed to by the counterparty +// may be cancelled or modified before the last planned height. +VerifyUpgradeAndUpdateState( + ctx sdk.Context, + cdc codec.BinaryCodec, + store sdk.KVStore, + newClient ClientState, + newConsState ConsensusState, + proofUpgradeClient, + proofUpgradeConsState []byte, +) (upgradedClient ClientState, upgradedConsensus ConsensusState, err error) +``` + +Note that the clients should have prior knowledge of the merkle path that the upgraded client and upgraded consensus states will use. The height at which the upgrade has occurred should also be encoded in the proof. The Tendermint client implementation accomplishes this by including an `UpgradePath` in the ClientState itself, which is used along with the upgrade height to construct the merkle path under which the client state and consensus state are committed. + +Developers must ensure that the `UpgradeClientMsg` does not pass until the last height of the old chain has been committed, and after the chain upgrades, the `UpgradeClientMsg` should pass once and only once on all counterparty clients. + +Developers must ensure that the new client adopts all of the new Client parameters that must be uniform across every valid light client of a chain (chain-chosen parameters), while maintaining the Client parameters that are customizable by each individual client (client-chosen parameters) from the previous version of the client. + +Upgrades must adhere to the IBC Security Model. IBC does not rely on the assumption of honest relayers for correctness. Thus users should not have to rely on relayers to maintain client correctness and security (though honest relayers must exist to maintain relayer liveness). While relayers may choose any set of client parameters while creating a new `ClientState`, this still holds under the security model since users can always choose a relayer-created client that suits their security and correctness needs or create a Client with their desired parameters if no such client exists. + +However, when upgrading an existing client, one must keep in mind that there are already many users who depend on this client's particular parameters. We cannot give the upgrading relayer free choice over these parameters once they have already been chosen. This would violate the security model since users who rely on the client would have to rely on the upgrading relayer to maintain the same level of security. Thus, developers must make sure that their upgrade mechanism allows clients to upgrade the chain-specified parameters whenever a chain upgrade changes these parameters (examples in the Tendermint client include `UnbondingPeriod`, `TrustingPeriod`, `ChainID`, `UpgradePath`, etc.), while ensuring that the relayer submitting the `UpgradeClientMsg` cannot alter the client-chosen parameters that the users are relying upon (examples in Tendermint client include `TrustLevel`, `MaxClockDrift`, etc). + +Developers should maintain the distinction between Client parameters that are uniform across every valid light client of a chain (chain-chosen parameters), and Client parameters that are customizable by each individual client (client-chosen parameters); since this distinction is necessary to implement the `ZeroCustomFields` method in the `ClientState` interface: + +```go +// Utility function that zeroes out any client customizable fields in client state +// Ledger enforced fields are maintained while all custom fields are zero values +// Used to verify upgrades +ZeroCustomFields() ClientState +``` + +Counterparty clients can upgrade securely by using all of the chain-chosen parameters from the chain-committed `UpgradedClient` and preserving all of the old client-chosen parameters. This enables chains to securely upgrade without relying on an honest relayer, however it can in some cases lead to an invalid final `ClientState` if the new chain-chosen parameters clash with the old client-chosen parameter. This can happen in the Tendermint client case if the upgrading chain lowers the `UnbondingPeriod` (chain-chosen) to a duration below that of a counterparty client's `TrustingPeriod` (client-chosen). Such cases should be clearly documented by developers, so that chains know which upgrades should be avoided to prevent this problem. The final upgraded client should also be validated in `VerifyUpgradeAndUpdateState` before returning to ensure that the client does not upgrade to an invalid `ClientState`. diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/05-upgrades/03-genesis-restart.md b/docs/versioned_docs/version-v4.6.x/01-ibc/05-upgrades/03-genesis-restart.md new file mode 100644 index 0000000..bdd2bdb --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/01-ibc/05-upgrades/03-genesis-restart.md @@ -0,0 +1,52 @@ +--- +title: Genesis Restart Upgrades +sidebar_label: Genesis Restart Upgrades +sidebar_position: 3 +slug: /ibc/upgrades/genesis-restart +--- + + +# Genesis Restart Upgrades + +:::note Synopsis +Learn how to upgrade your chain and counterparty clients using genesis restarts. +::: + +**NOTE**: Regular genesis restarts are currently unsupported by relayers! + +## IBC Client Breaking Upgrades + +IBC client breaking upgrades are possible using genesis restarts. +It is highly recommended to use the in-place migrations instead of a genesis restart. +Genesis restarts should be used sparingly and as backup plans. + +Genesis restarts still require the usage of an IBC upgrade proposal in order to correctly upgrade counterparty clients. + +### Step-by-Step Upgrade Process for SDK Chains + +If the IBC-connected chain is conducting an upgrade that will break counterparty clients, it must ensure that the upgrade is first supported by IBC using the [IBC Client Breaking Upgrade List](./01-quick-guide.md#ibc-client-breaking-upgrades) and then execute the upgrade process described below in order to prevent counterparty clients from breaking. + +1. Create a 02-client [`UpgradeProposal`](https://github.com/cosmos/ibc-go/blob/v4.4.2/proto/ibc/core/client/v1/client.proto#L58-L77) with an `UpgradePlan` and a new IBC ClientState in the `UpgradedClientState` field. Note that the `UpgradePlan` must specify an upgrade height **only** (no upgrade time), and the `ClientState` should only include the fields common to all valid clients and zero out any client-customizable fields (such as TrustingPeriod). +2. Vote on and pass the `UpgradeProposal` +3. Halt the node after successful upgrade. +4. Export the genesis file. +5. Swap to the new binary. +6. Run migrations on the genesis file. +7. Remove the `UpgradeProposal` plan from the genesis file. This may be done by migrations. +8. Change desired chain-specific fields (chain id, unbonding period, etc). This may be done by migrations. +8. Reset the node's data. +9. Start the chain. + +Upon the `UpgradeProposal` passing, the upgrade module will commit the UpgradedClient under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedClient`. On the block right before the upgrade height, the upgrade module will also commit an initial consensus state for the next chain under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedConsState`. + +Once the chain reaches the upgrade height and halts, a relayer can upgrade the counterparty clients to the last block of the old chain. They can then submit the proofs of the `UpgradedClient` and `UpgradedConsensusState` against this last block and upgrade the counterparty client. + +#### Step-by-Step Upgrade Process for Relayers Upgrading Counterparty Clients + +These steps are identical to the regular [IBC client breaking upgrade process](./01-quick-guide.md#step-by-step-upgrade-process-for-relayers-upgrading-counterparty-clients). + +### Non-IBC Client Breaking Upgrades + +While ibc-go supports genesis restarts which do not break IBC clients, relayers do not support this upgrade path. +Here is a tracking issue on [Hermes](https://github.com/informalsystems/ibc-rs/issues/1152). +Please do not attempt a regular genesis restarts unless you have a tool to update counterparty clients correctly. diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/05-upgrades/_category_.json b/docs/versioned_docs/version-v4.6.x/01-ibc/05-upgrades/_category_.json new file mode 100644 index 0000000..439f7ee --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/01-ibc/05-upgrades/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Upgrades", + "position": 5, + "link": { "type": "doc", "id": "intro" } +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/06-proposals.md b/docs/versioned_docs/version-v4.6.x/01-ibc/06-proposals.md new file mode 100644 index 0000000..6e1fea8 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/01-ibc/06-proposals.md @@ -0,0 +1,91 @@ +--- +title: Governance Proposals +sidebar_label: Governance Proposals +sidebar_position: 6 +slug: /ibc/proposals +--- + +# Governance Proposals + +In uncommon situations, a highly valued client may become frozen due to uncontrollable +circumstances. A highly valued client might have hundreds of channels being actively used. +Some of those channels might have a significant amount of locked tokens used for ICS 20. + +If the one third of the validator set of the chain the client represents decides to collude, +they can sign off on two valid but conflicting headers each signed by the other one third +of the honest validator set. The light client can now be updated with two valid, but conflicting +headers at the same height. The light client cannot know which header is trustworthy and therefore +evidence of such misbehaviour is likely to be submitted resulting in a frozen light client. + +Frozen light clients cannot be updated under any circumstance except via a governance proposal. +Since a quorum of validators can sign arbitrary state roots which may not be valid executions +of the state machine, a governance proposal has been added to ease the complexity of unfreezing +or updating clients which have become "stuck". Without this mechanism, validator sets would need +to construct a state root to unfreeze the client. Unfreezing clients, re-enables all of the channels +built upon that client. This may result in recovery of otherwise lost funds. + +Tendermint light clients may become expired if the trusting period has passed since their +last update. This may occur if relayers stop submitting headers to update the clients. + +An unplanned upgrade by the counterparty chain may also result in expired clients. If the counterparty +chain undergoes an unplanned upgrade, there may be no commitment to that upgrade signed by the validator +set before the chain-id changes. In this situation, the validator set of the last valid update for the +light client is never expected to produce another valid header since the chain-id has changed, which will +ultimately lead the on-chain light client to become expired. + +In the case that a highly valued light client is frozen, expired, or rendered non-updateable, a +governance proposal may be submitted to update this client, known as the subject client. The +proposal includes the client identifier for the subject and the client identifier for a substitute +client. Light client implementations may implement custom updating logic, but in most cases, +the subject will be updated to the latest consensus state of the substitute client, if the proposal passes. +The substitute client is used as a "stand in" while the subject is on trial. It is best practice to create +a substitute client *after* the subject has become frozen to avoid the substitute from also becoming frozen. +An active substitute client allows headers to be submitted during the voting period to prevent accidental expiry +once the proposal passes. + +## How to recover an expired client with a governance proposal + +See also the relevant documentation: [ADR-026, IBC client recovery mechanisms](/architecture/adr-026-ibc-client-recovery-mechanisms) + +### Preconditions + +- The chain is updated with ibc-go >= v1.1.0. +- The client identifier of an active client for the same counterparty chain. +- The governance deposit. + +## Steps + +### Step 1 + +Check if the client is attached to the expected `chain-id`. For example, for an expired Tendermint client representing the Akash chain the client state looks like this on querying the client state: + +```text +{ + client_id: 07-tendermint-146 + client_state: + '@type': /ibc.lightclients.tendermint.v1.ClientState + allow_update_after_expiry: true + allow_update_after_misbehaviour: true + chain_id: akashnet-2 +} +``` + +The client is attached to the expected Akash `chain-id`. Note that although the parameters (`allow_update_after_expiry` and `allow_update_after_misbehaviour`) exist to signal intent, these parameters have been deprecated and will not enforce any checks on the revival of client. See ADR-026 for more context on this deprecation. + +### Step 2 + +If the chain has been updated to ibc-go >= v1.1.0, anyone can submit the governance proposal to recover the client by executing this via cli: + +```bash + tx gov submit-proposal update-client +``` + +The `` should be a client identifier on the same chain as the expired or frozen client. This client identifier should connect to the same chain as the expired or frozen client. This means: use the active client that is currently being used to relay packets between the two chains as the replacement client. + +After this, it is just a question of who funds the governance deposit and if the chain in question votes yes. + +## Important considerations + +Please note that from v1.0.0 of ibc-go it will not be allowed for transactions to go to expired clients anymore, so please update to at least this version to prevent similar issues in the future. + +Please also note that if the client on the other end of the transaction is also expired, that client will also need to update. This process updates only one client. diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/07-relayer.md b/docs/versioned_docs/version-v4.6.x/01-ibc/07-relayer.md new file mode 100644 index 0000000..3d63e97 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/01-ibc/07-relayer.md @@ -0,0 +1,53 @@ +--- +title: Relayer +sidebar_label: Relayer +sidebar_position: 7 +slug: /ibc/relayer +--- + +# Relayer + +:::note + +## Pre-requisite readings + +- [IBC Overview](01-overview.md) +- Events + +::: + +## Events + +Events are emitted for every transaction processed by the base application to indicate the execution +of some logic clients may want to be aware of. This is extremely useful when relaying IBC packets. +Any message that uses IBC will emit events for the corresponding TAO logic executed as defined in +the [IBC events document](/events/events). + +In the SDK, it can be assumed that for every message there is an event emitted with the type `message`, +attribute key `action`, and an attribute value representing the type of message sent +(`channel_open_init` would be the attribute value for `MsgChannelOpenInit`). If a relayer queries +for transaction events, it can split message events using this event Type/Attribute Key pair. + +The Event Type `message` with the Attribute Key `module` may be emitted multiple times for a single +message due to application callbacks. It can be assumed that any TAO logic executed will result in +a module event emission with the attribute value `ibc_` (02-client emits `ibc_client`). + +### Subscribing with Tendermint + +Calling the Tendermint RPC method `Subscribe` via Tendermint's Websocket will return events using +Tendermint's internal representation of them. Instead of receiving back a list of events as they +were emitted, Tendermint will return the type `map[string][]string` which maps a string in the +form `.` to `attribute_value`. This causes extraction of the event +ordering to be non-trivial, but still possible. + +A relayer should use the `message.action` key to extract the number of messages in the transaction +and the type of IBC transactions sent. For every IBC transaction within the string array for +`message.action`, the necessary information should be extracted from the other event fields. If +`send_packet` appears at index 2 in the value for `message.action`, a relayer will need to use the +value at index 2 of the key `send_packet.packet_sequence`. This process should be repeated for each +piece of information needed to relay a packet. + +## Example Implementations + +- [Golang Relayer](https://github.com/cosmos/relayer) +- [Hermes](https://github.com/informalsystems/hermes) diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/08-proto-docs.md b/docs/versioned_docs/version-v4.6.x/01-ibc/08-proto-docs.md new file mode 100644 index 0000000..886dc03 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/01-ibc/08-proto-docs.md @@ -0,0 +1,10 @@ +--- +title: Protobuf Documentation +sidebar_label: Protobuf Documentation +sidebar_position: 8 +slug: /ibc/proto-docs +--- + +# Protobuf documentation + +See [ibc-go v4.4.x Buf Protobuf documentation](https://github.com/cosmos/ibc-go/blob/release/v4.4.x/docs/ibc/proto-docs.md). diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/09-roadmap.md b/docs/versioned_docs/version-v4.6.x/01-ibc/09-roadmap.md new file mode 100644 index 0000000..41e36f7 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/01-ibc/09-roadmap.md @@ -0,0 +1,61 @@ +--- +title: Roadmap +sidebar_label: Roadmap +sidebar_position: 9 +slug: /roadmap/roadmap +--- + +# Roadmap ibc-go + +*Last update: March 31, 2022* + +This document endeavours to inform the wider IBC community about plans and priorities for work on ibc-go by the team at Interchain GmbH. It is intended to broadly inform all users of ibc-go, including developers and operators of IBC, relayer, chain and wallet applications. + +This roadmap should be read as a high-level guide, rather than a commitment to schedules and deliverables. The degree of specificity is inversely proportional to the timeline. We will update this document periodically to reflect the status and plans. + +## Q2 - 2022 + +At a high level we will focus on: + +- Finishing the implementation of [relayer incentivisation](https://github.com/orgs/cosmos/projects/7/views/8). +- Finishing the [refactoring of 02-client](https://github.com/cosmos/ibc-go/milestone/16). +- Finishing the upgrade to Cosmos SDK v0.46 and Tendermint v0.35. +- Implementing and testing the changes needed to support the [transition to SMT storage](https://github.com/cosmos/ibc-go/milestone/21) in the Cosmos SDK. +- Designing the implementation and scoping the engineering work for [channel upgradability](https://github.com/cosmos/ibc/blob/master/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md). +- Improving the project's documentation and writing guides for [light client](https://github.com/cosmos/ibc-go/issues/59) and middleware implementation. +- Working on [core backlog issues](https://github.com/cosmos/ibc-go/milestone/8). +- Spending time on expanding and deepening our knowledge of IBC, but also other parts of the Cosmos stack. +- And last, but not least, onboarding new members to the team. + +For a detail view of each iteration's planned work, please check out our [project board](https://github.com/orgs/cosmos/projects/7). + +### Release schedule + +#### **April** + +In the first half of the month we will probably cut: + +- Alpha/beta pre-releases with the upgrade to SDK 0.46 and Tendermint v0.35. +- [Alpha](https://github.com/cosmos/ibc-go/milestone/5) pre-release with the implementation of relayer incentivisation. + +In the second half, and depending on the date of the final release of Cosmos SDK 0.46, we will probably cut the final release with the upgrade to SDK 0.46 and Tendermint v0.35, and also a [beta](https://github.com/cosmos/ibc-go/milestone/23) pre-release with the implementation of relayer incentivisation. + +In the second half of the month we also plan to do a second internal audit of the implementation of relayer incentivisation, and issues will most likely will be created from the audit. Depending on the nature and type of the issues we create, those would be released in a second beta pre-release or in a [release candidate](https://github.com/cosmos/ibc-go/milestone/24). + +#### **May** + +In the first half we will probably start cutting release candidates with relayer incentivisation and the 02-client refactor. Final release would most likely come out at the end of the month or beginning of June. + +#### **June** + +We will probably cut at the end of the month or beginning of Q3 patch or minor releases on all the supported release lines with the [small features and core improvements](https://github.com/cosmos/ibc-go/milestone/8) that we work on during the quarter. + +## Q3 - 2022 + +We will most likely start the implementation of [channel upgradability](https://github.com/cosmos/ibc/blob/master/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md). At the end of Q2 or maybe beginning of Q3 we might also work on designing the implementation and scoping the engineering work to add support for [ordered channels that can timeout](https://github.com/cosmos/ibc/pull/636), and we could potentially work on this feature also in Q3. + +We will also probably do an audit of the implementation of the [CCV application](https://github.com/cosmos/interchain-security/tree/main/x/ccv) for Interchain Security. + +### Release schedule + +In this quarter we will make the final release to support the migration to SMT storage. diff --git a/docs/versioned_docs/version-v4.6.x/01-ibc/_category_.json b/docs/versioned_docs/version-v4.6.x/01-ibc/_category_.json new file mode 100644 index 0000000..afc6d18 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/01-ibc/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Using IBC-Go", + "position": 1, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/01-overview.md b/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/01-overview.md new file mode 100644 index 0000000..ed696b3 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/01-overview.md @@ -0,0 +1,128 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /apps/transfer/overview +--- + +# Overview + +:::note Synopsis +Learn about what the token Transfer module is +::: + +## What is the Transfer module? + +Transfer is the Cosmos SDK implementation of the [ICS-20](https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer) protocol, which enables cross-chain fungible token transfers. + +## Concepts + +### Acknowledgements + +ICS20 uses the recommended acknowledgement format as specified by [ICS 04](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope). + +A successful receive of a transfer packet will result in a Result Acknowledgement being written +with the value `[]byte{byte(1)}` in the `Response` field. + +An unsuccessful receive of a transfer packet will result in an Error Acknowledgement being written +with the error message in the `Response` field. + +### Denomination trace + +The denomination trace corresponds to the information that allows a token to be traced back to its +origin chain. It contains a sequence of port and channel identifiers ordered from the most recent to +the oldest in the timeline of transfers. + +This information is included on the token denomination field in the form of a hash to prevent an +unbounded denomination length. For example, the token `transfer/channelToA/uatom` will be displayed +as `ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2`. + +Each send to any chain other than the one it was previously received from is a movement forwards in +the token's timeline. This causes trace to be added to the token's history and the destination port +and destination channel to be prefixed to the denomination. In these instances the sender chain is +acting as the "source zone". When the token is sent back to the chain it previously received from, the +prefix is removed. This is a backwards movement in the token's timeline and the sender chain is +acting as the "sink zone". + +It is strongly recommended to read the full details of [ADR 001: Coin Source Tracing](/architecture/adr-001-coin-source-tracing) to understand the implications and context of the IBC token representations. + +## UX suggestions for clients + +For clients (wallets, exchanges, applications, block explorers, etc) that want to display the source of the token, it is recommended to use the following alternatives for each of the cases below: + +### Direct connection + +If the denomination trace contains a single identifier prefix pair (as in the example above), then +the easiest way to retrieve the chain and light client identifier is to map the trace information +directly. In summary, this requires querying the channel from the denomination trace identifiers, +and then the counterparty client state using the counterparty port and channel identifiers from the +retrieved channel. + +A general pseudo algorithm would look like the following: + +1. Query the full denomination trace. +2. Query the channel with the `portID/channelID` pair, which corresponds to the first destination of the + token. +3. Query the client state using the identifiers pair. Note that this query will return a `"Not +Found"` response if the current chain is not connected to this channel. +4. Retrieve the client identifier or chain identifier from the client state (eg: on + Tendermint clients) and store it locally. + +Using the gRPC gateway client service the steps above would be, with a given IBC token `ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2` stored on `chainB`: + +1. `GET /ibc/apps/transfer/v1/denom_traces/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2` -> `{"path": "transfer/channelToA", "base_denom": "uatom"}` +2. `GET /ibc/apps/transfer/v1/channels/channelToA/ports/transfer/client_state"` -> `{"client_id": "clientA", "chain-id": "chainA", ...}` +3. `GET /ibc/apps/transfer/v1/channels/channelToA/ports/transfer"` -> `{"channel_id": "channelToA", port_id": "transfer", counterparty: {"channel_id": "channelToB", port_id": "transfer"}, ...}` +4. `GET /ibc/apps/transfer/v1/channels/channelToB/ports/transfer/client_state" -> {"client_id": "clientB", "chain-id": "chainB", ...}` + +Then, the token transfer chain path for the `uatom` denomination would be: `chainA` -> `chainB`. + +### Multiple hops + +The multiple channel hops case applies when the token has passed through multiple chains between the original source and final destination chains. + +The IBC protocol doesn't know the topology of the overall network (i.e connections between chains and identifier names between them). For this reason, in the multiple hops case, a particular chain in the timeline of the individual transfers can't query the chain and client identifiers of the other chains. + +Take for example the following sequence of transfers `A -> B -> C` for an IBC token, with a final prefix path (trace info) of `transfer/channelChainC/transfer/channelChainB`. What the paragraph above means is that even in the case that chain `C` is directly connected to chain `A`, querying the port and channel identifiers that chain `B` uses to connect to chain `A` (eg: `transfer/channelChainA`) can be completely different from the one that chain `C` uses to connect to chain `A` (eg: `transfer/channelToChainA`). + +Thus the proposed solution for clients that the IBC team recommends are the following: + +- **Connect to all chains**: Connecting to all the chains in the timeline would allow clients to + perform the queries outlined in the [direct connection](#direct-connection) section to each + relevant chain. By repeatedly following the port and channel denomination trace transfer timeline, + clients should always be able to find all the relevant identifiers. This comes at the tradeoff + that the client must connect to nodes on each of the chains in order to perform the queries. +- **Relayer as a Service (RaaS)**: A longer term solution is to use/create a relayer service that + could map the denomination trace to the chain path timeline for each token (i.e `origin chain -> +chain #1 -> ... -> chain #(n-1) -> final chain`). These services could provide merkle proofs in + order to allow clients to optionally verify the path timeline correctness for themselves by + running light clients. If the proofs are not verified, they should be considered as trusted third + parties services. Additionally, client would be advised in the future to use RaaS that support the + largest number of connections between chains in the ecosystem. Unfortunately, none of the existing + public relayers (in [Golang](https://github.com/cosmos/relayer) and + [Rust](https://github.com/informalsystems/ibc-rs)), provide this service to clients. + +:::tip +The only viable alternative for clients (at the time of writing) to tokens with multiple connection hops, is to connect to all chains directly and perform relevant queries to each of them in the sequence. +::: + +## Locked funds + +In some [exceptional cases](/architecture/adr-026-ibc-client-recovery-mechanisms#exceptional-cases), a client state associated with a given channel cannot be updated. This causes that funds from fungible tokens in that channel will be permanently locked and thus can no longer be transferred. + +To mitigate this, a client update governance proposal can be submitted to update the frozen client +with a new valid header. Once the proposal passes the client state will be unfrozen and the funds +from the associated channels will then be unlocked. This mechanism only applies to clients that +allow updates via governance, such as Tendermint clients. + +In addition to this, it's important to mention that a token must be sent back along the exact route +that it took originally in order to return it to its original form on the source chain (eg: the +Cosmos Hub for the `uatom`). Sending a token back to the same chain across a different channel will +**not** move the token back across its timeline. If a channel in the chain history closes before the +token can be sent back across that channel, then the token will not be returnable to its original +form. + +## Security considerations + +For safety, no other module must be capable of minting tokens with the `ibc/` prefix. The IBC +transfer module needs a subset of the denomination space that only it can create tokens in. diff --git a/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/02-state.md b/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/02-state.md new file mode 100644 index 0000000..17f48a2 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/02-state.md @@ -0,0 +1,13 @@ +--- +title: State +sidebar_label: State +sidebar_position: 2 +slug: /apps/transfer/state +--- + +# State + +The IBC transfer application module keeps state of the port to which the module is binded and the denomination trace information as outlined in [ADR 001](/architecture/adr-001-coin-source-tracing). + +- `Port`: `0x01 -> ProtocolBuffer(string)` +- `DenomTrace`: `0x02 | []bytes(traceHash) -> ProtocolBuffer(DenomTrace)` diff --git a/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/03-state-transitions.md b/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/03-state-transitions.md new file mode 100644 index 0000000..23bf1bf --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/03-state-transitions.md @@ -0,0 +1,37 @@ +--- +title: State Transitions +sidebar_label: State Transitions +sidebar_position: 3 +slug: /apps/transfer/state-transitions +--- + +# State transitions + +## Send fungible tokens + +A successful fungible token send has two state transitions depending if the transfer is a movement forward or backwards in the token's timeline: + +1. Sender chain is the source chain, *i.e* a transfer to any chain other than the one it was previously received from is a movement forwards in the token's timeline. This results in the following state transitions: + + - The coins are transferred to an escrow address (i.e locked) on the sender chain. + - The coins are transferred to the receiving chain through IBC TAO logic. + +2. Sender chain is the sink chain, *i.e* the token is sent back to the chain it previously received from. This is a backwards movement in the token's timeline. This results in the following state transitions: + + - The coins (vouchers) are burned on the sender chain. + - The coins are transferred to the receiving chain through IBC TAO logic. + +## Receive fungible tokens + +A successful fungible token receive has two state transitions depending if the transfer is a movement forward or backwards in the token's timeline: + +1. Receiver chain is the source chain. This is a backwards movement in the token's timeline. This results in the following state transitions: + + - The leftmost port and channel identifier pair is removed from the token denomination prefix. + - The tokens are unescrowed and sent to the receiving address. + +2. Receiver chain is the sink chain. This is a movement forwards in the token's timeline. This results in the following state transitions: + + - Token vouchers are minted by prefixing the destination port and channel identifiers to the trace information. + - The receiving chain stores the new trace information in the store (if not set already). + - The vouchers are sent to the receiving address. diff --git a/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/04-messages.md b/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/04-messages.md new file mode 100644 index 0000000..1e27c60 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/04-messages.md @@ -0,0 +1,46 @@ +--- +title: Messages +sidebar_label: Messages +sidebar_position: 4 +slug: /apps/transfer/messages +--- + +# Messages + +## `MsgTransfer` + +A fungible token cross chain transfer is achieved by using the `MsgTransfer`: + +```go +type MsgTransfer struct { + SourcePort string + SourceChannel string + Token sdk.Coin + Sender string + Receiver string + TimeoutHeight ibcexported.Height + TimeoutTimestamp uint64 + Memo string +} +``` + +This message is expected to fail if: + +- `SourcePort` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators). +- `SourceChannel` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +- `Token` is invalid (denom is invalid or amount is negative) + - `Token.Amount` is not positive. + - `Token.Denom` is not a valid IBC denomination as per [ADR 001 - Coin Source Tracing](/architecture/adr-001-coin-source-tracing). +- `Sender` is empty. +- `Receiver` is empty. +- `TimeoutHeight` and `TimeoutTimestamp` are both zero. + +This message will send a fungible token to the counterparty chain represented by the counterparty Channel End connected to the Channel End with the identifiers `SourcePort` and `SourceChannel`. + +The denomination provided for transfer should correspond to the same denomination represented on this chain. The prefixes will be added as necessary upon by the receiving chain. + +### Memo + +The memo field was added to allow applications and users to attach metadata to transfer packets. The field is optional and may be left empty. When it is used to attach metadata for a particular middleware, the memo field should be represented as a json object where different middlewares use different json keys. + +You can find more information about applications that use the memo field in the [chain registry](https://github.com/cosmos/chain-registry/blob/master/_memo_keys/ICS20_memo_keys.json). diff --git a/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/05-events.md b/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/05-events.md new file mode 100644 index 0000000..68eb4a4 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/05-events.md @@ -0,0 +1,55 @@ +--- +title: Events +sidebar_label: Events +sidebar_position: 5 +slug: /apps/transfer/events +--- + + +# Events + +## `MsgTransfer` + +| Type | Attribute Key | Attribute Value | +|--------------|---------------|-----------------| +| ibc_transfer | sender | \{sender\} | +| ibc_transfer | receiver | \{receiver\} | +| message | module | transfer | + +## `OnRecvPacket` callback + +| Type | Attribute Key | Attribute Value | +|-----------------------|---------------|-----------------| +| fungible_token_packet | module | transfer | +| fungible_token_packet | sender | \{sender\} | +| fungible_token_packet | receiver | \{receiver\} | +| fungible_token_packet | denom | \{denom\} | +| fungible_token_packet | amount | \{amount\} | +| fungible_token_packet | memo | \{memo\} | +| fungible_token_packet | success | \{ackSuccess\} | +| fungible_token_packet | error | \{ackError\} | +| denomination_trace | trace_hash | \{hex_hash\} | +| denomination_trace | denom | \{voucherDenom\}| + +## `OnAcknowledgePacket` callback + +| Type | Attribute Key | Attribute Value | +|-----------------------|-----------------|------------------| +| fungible_token_packet | module | transfer | +| fungible_token_packet | sender | \{sender\} | +| fungible_token_packet | receiver | \{receiver\} | +| fungible_token_packet | denom | \{denom\} | +| fungible_token_packet | amount | \{amount\} | +| fungible_token_packet | memo | \{memo\} | +| fungible_token_packet | acknowledgement | \{ack.String()\} | +| fungible_token_packet | success / error | \{ack.Response\} | + +## `OnTimeoutPacket` callback + +| Type | Attribute Key | Attribute Value | +|-----------------------|-----------------|-----------------| +| fungible_token_packet | module | transfer | +| fungible_token_packet | refund_receiver | \{receiver\} | +| fungible_token_packet | denom | \{denom\} | +| fungible_token_packet | amount | \{amount\} | +| fungible_token_packet | memo | \{memo\} | diff --git a/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/06-metrics.md b/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/06-metrics.md new file mode 100644 index 0000000..7f6087e --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/06-metrics.md @@ -0,0 +1,18 @@ +--- +title: Metrics +sidebar_label: Metrics +sidebar_position: 6 +slug: /apps/transfer/metrics +--- + + +# Metrics + +The IBC transfer application module exposes the following set of metrics. + +| Metric | Description | Unit | Type | +|:--------------------------------|:------------------------------------------------------------------------------------------|:----------------|:--------| +| `tx_msg_ibc_transfer` | The total amount of tokens transferred via IBC in a `MsgTransfer` (source or sink chain) | token | gauge | +| `ibc_transfer_packet_receive` | The total amount of tokens received in a `FungibleTokenPacketData` (source or sink chain) | token | gauge | +| `ibc_transfer_send` | Total number of IBC transfers sent from a chain (source or sink) | transfer | counter | +| `ibc_transfer_receive` | Total number of IBC transfers received to a chain (source or sink) | transfer | counter | diff --git a/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/07-params.md b/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/07-params.md new file mode 100644 index 0000000..c4c9330 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/07-params.md @@ -0,0 +1,34 @@ +--- +title: Params +sidebar_label: Params +sidebar_position: 7 +slug: /apps/transfer/params +--- + + +# Parameters + +The IBC transfer application module contains the following parameters: + +| Key | Type | Default Value | +|------------------|------|---------------| +| `SendEnabled` | bool | `true` | +| `ReceiveEnabled` | bool | `true` | + +## `SendEnabled` + +The transfers enabled parameter controls send cross-chain transfer capabilities for all fungible tokens. + +To prevent a single token from being transferred from the chain, set the `SendEnabled` parameter to `true` and then, depending on the Cosmos SDK version, do one of the following: + +- For Cosmos SDK v0.46.x or earlier, set the bank module's [`SendEnabled` parameter](https://github.com/cosmos/cosmos-sdk/blob/release/v0.46.x/x/bank/spec/05_params.md#sendenabled) for the denomination to `false`. +- For Cosmos SDK versions above v0.46.x, set the bank module's `SendEnabled` entry for the denomination to `false` using `MsgSetSendEnabled` as a governance proposal. + +## `ReceiveEnabled` + +The transfers enabled parameter controls receive cross-chain transfer capabilities for all fungible tokens. + +To prevent a single token from being transferred to the chain, set the `ReceiveEnabled` parameter to `true` and then, depending on the Cosmos SDK version, do one of the following: + +- For Cosmos SDK v0.46.x or earlier, set the bank module's [`SendEnabled` parameter](https://github.com/cosmos/cosmos-sdk/blob/release/v0.46.x/x/bank/spec/05_params.md#sendenabled) for the denomination to `false`. +- For Cosmos SDK versions above v0.46.x, set the bank module's `SendEnabled` entry for the denomination to `false` using `MsgSetSendEnabled` as a governance proposal. diff --git a/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/_category_.json b/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/_category_.json new file mode 100644 index 0000000..50d492b --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/02-apps/01-transfer/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Transfer", + "position": 1, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/01-overview.md b/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/01-overview.md new file mode 100644 index 0000000..cd21176 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/01-overview.md @@ -0,0 +1,47 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /apps/interchain-accounts/overview +--- + + +# Overview + +:::note Synopsis +Learn about what the Interchain Accounts module is, and how to build custom modules that utilize Interchain Accounts functionality +::: + +## What is the Interchain Accounts module? + +Interchain Accounts is the Cosmos SDK implementation of the ICS-27 protocol, which enables cross-chain account management built upon IBC. Chains using the Interchain Accounts module can programmatically create accounts on other chains and control these accounts via IBC transactions. + +Interchain Accounts exposes a simple-to-use API which means IBC application developers do not require an in-depth knowledge of the underlying low-level details of IBC or the ICS-27 protocol. + +Developers looking to build upon Interchain Accounts must write custom logic in their own IBC application module, called authentication modules. + +- How is an interchain account different than a regular account? + +Regular accounts use a private key to sign transactions on-chain. Interchain Accounts are instead controlled programmatically by separate chains via IBC transactions. Interchain Accounts are implemented as sub-accounts of the interchain accounts module account. + +## Concepts + +`Host Chain`: The chain where the interchain account is registered. The host chain listens for IBC packets from a controller chain which should contain instructions (e.g. cosmos SDK messages) for which the interchain account will execute. + +`Controller Chain`: The chain registering and controlling an account on a host chain. The controller chain sends IBC packets to the host chain to control the account. A controller chain must have at least one interchain accounts authentication module in order to act as a controller chain. + +`Authentication Module`: A custom IBC application module on the controller chain that uses the Interchain Accounts module API to build custom logic for the creation & management of interchain accounts. For a controller chain to utilize the interchain accounts module functionality, an authentication module is required. + +`Interchain Account`: An account on a host chain. An interchain account has all the capabilities of a normal account. However, rather than signing transactions with a private key, a controller chain's authentication module will send IBC packets to the host chain which signals what transactions the interchain account should execute. + +## SDK Security Model + +SDK modules on a chain are assumed to be trustworthy. For example, there are no checks to prevent an untrustworthy module from accessing the bank keeper. + +The implementation of ICS27 on ibc-go uses this assumption in its security considerations. The implementation assumes the authentication module will not try to open channels on owner addresses it does not control. + +The implementation assumes other IBC application modules will not bind to ports within the ICS27 namespace. + +## Known Bugs + +- Fee-enabled Interchain Accounts channels cannot be reopened in case of closure due to packet timeout. Regular channels (non fee-enabled) can be reopened. A fix for this bug has been implemented, but, since it is API breaking, it is only available from v5.x. See [this PR](https://github.com/cosmos/ibc-go/pull/2302) for more details. diff --git a/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/02-auth-modules.md b/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/02-auth-modules.md new file mode 100644 index 0000000..899672a --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/02-auth-modules.md @@ -0,0 +1,402 @@ +--- +title: Authentication Modules +sidebar_label: Authentication Modules +sidebar_position: 2 +slug: /apps/interchain-accounts/auth-modules +--- + + +# Building an authentication module + +:::note Synopsis +Authentication modules play the role of the `Base Application` as described in [ICS30 IBC Middleware](https://github.com/cosmos/ibc/tree/master/spec/app/ics-030-middleware), and enable application developers to perform custom logic when working with the Interchain Accounts controller API. +::: + +The controller submodule is used for account registration and packet sending. +It executes only logic required of all controllers of interchain accounts. +The type of authentication used to manage the interchain accounts remains unspecified. +There may exist many different types of authentication which are desirable for different use cases. +Thus the purpose of the authentication module is to wrap the controller module with custom authentication logic. + +In ibc-go, authentication modules are connected to the controller chain via a middleware stack. +The controller module is implemented as [middleware](https://github.com/cosmos/ibc/tree/master/spec/app/ics-030-middleware) and the authentication module is connected to the controller module as the base application of the middleware stack. +To implement an authentication module, the `IBCModule` interface must be fulfilled. +By implementing the controller module as middleware, any amount of authentication modules can be created and connected to the controller module without writing redundant code. + +The authentication module must: + +- Authenticate interchain account owners +- Track the associated interchain account address for an owner +- Claim the channel capability in `OnChanOpenInit` +- Send packets on behalf of an owner (after authentication) + +## IBCModule implementation + +The following `IBCModule` callbacks must be implemented with appropriate custom logic: + +```go +// OnChanOpenInit implements the IBCModule interface +func (im IBCModule) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + // the authentication module *must* claim the channel capability on OnChanOpenInit + if err := im.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return version, err + } + + // perform custom logic + + return version, nil +} + +// OnChanOpenAck implements the IBCModule interface +func (im IBCModule) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyVersion string, +) error { + // perform custom logic + + return nil +} + +// OnChanCloseConfirm implements the IBCModule interface +func (im IBCModule) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // perform custom logic + + return nil +} + +// OnAcknowledgementPacket implements the IBCModule interface +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + // perform custom logic + + return nil +} + +// OnTimeoutPacket implements the IBCModule interface. +func (im IBCModule) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + // perform custom logic + + return nil +} +``` + +**Note**: The channel capability must be claimed by the authentication module in `OnChanOpenInit` otherwise the authentication module will not be able to send packets on the channel created for the associated interchain account. + +The following functions must be defined to fulfill the `IBCModule` interface, but they will never be called by the controller module so they may error or panic. + +```go +// OnChanOpenTry implements the IBCModule interface +func (im IBCModule) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + panic("UNIMPLEMENTED") +} + +// OnChanOpenConfirm implements the IBCModule interface +func (im IBCModule) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + panic("UNIMPLEMENTED") +} + +// OnChanCloseInit implements the IBCModule interface +func (im IBCModule) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + panic("UNIMPLEMENTED") +} + +// OnRecvPacket implements the IBCModule interface. A successful acknowledgement +// is returned if the packet data is successfully decoded and the receive application +// logic returns without error. +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) ibcexported.Acknowledgement { + panic("UNIMPLEMENTED") +} +``` + +## `RegisterInterchainAccount` + +The authentication module can begin registering interchain accounts by calling `RegisterInterchainAccount`: + +```go +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, connectionID, owner.String(), version); err != nil { + return err +} + +return nil +``` + +The `version` argument is used to support ICS29 fee middleware for relayer incentivization of ICS27 packets. Consumers of the `RegisterInterchainAccount` are expected to build the appropriate JSON encoded version string themselves and pass it accordingly. If an empty string is passed in the `version` argument, then the version will be initialized to a default value in the `OnChanOpenInit` callback of the controller's handler, so that channel handshake can proceed. + +The following code snippet illustrates how to construct an appropriate interchain accounts `Metadata` and encode it as a JSON bytestring: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, controllerConnectionID, owner.String(), string(appVersion)); err != nil { + return err +} +``` + +Similarly, if the application stack is configured to route through ICS29 fee middleware and a fee enabled channel is desired, construct the appropriate ICS29 `Metadata` type: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +feeMetadata := feetypes.Metadata{ + AppVersion: string(appVersion), + FeeVersion: feetypes.Version, +} + +feeEnabledVersion, err := feetypes.ModuleCdc.MarshalJSON(&feeMetadata) +if err != nil { + return err +} + +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, controllerConnectionID, owner.String(), string(feeEnabledVersion)); err != nil { + return err +} +``` + +## `SendTx` + +The authentication module can attempt to send a packet by calling `SendTx`: + +```go + +// Authenticate owner +// perform custom logic + +// Construct controller portID based on interchain account owner address +portID, err := icatypes.NewControllerPortID(owner.String()) +if err != nil { + return err +} + +channelID, found := keeper.icaControllerKeeper.GetActiveChannelID(ctx, portID) +if !found { + return sdkerrors.Wrapf(icatypes.ErrActiveChannelNotFound, "failed to retrieve active channel for port %s", portID) +} + +// Obtain the channel capability, claimed in OnChanOpenInit +chanCap, found := keeper.scopedKeeper.GetCapability(ctx, host.ChannelCapabilityPath(portID, channelID)) +if !found { + return sdkerrors.Wrap(channeltypes.ErrChannelCapabilityNotFound, "module does not own channel capability") +} + +// Obtain data to be sent to the host chain. +// In this example, the owner of the interchain account would like to send a bank MsgSend to the host chain. +// The appropriate serialization function should be called. The host chain must be able to deserialize the transaction. +// If the host chain is using the ibc-go host module, `SerializeCosmosTx` should be used. +msg := &banktypes.MsgSend{FromAddress: fromAddr, ToAddress: toAddr, Amount: amt} +data, err := icatypes.SerializeCosmosTx(keeper.cdc, []sdk.Msg{msg}) +if err != nil { + return err +} + +// Construct packet data +packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, +} + +// Obtain timeout timestamp +// An appropriate timeout timestamp must be determined based on the usage of the interchain account. +// If the packet times out, the channel will be closed requiring a new channel to be created +timeoutTimestamp := obtainTimeoutTimestamp() + +// Send the interchain accounts packet, returning the packet sequence +seq, err = keeper.icaControllerKeeper.SendTx(ctx, chanCap, portID, packetData, timeoutTimestamp) +``` + +The data within an `InterchainAccountPacketData` must be serialized using a format supported by the host chain. +If the host chain is using the ibc-go host chain submodule, `SerializeCosmosTx` should be used. If the `InterchainAccountPacketData.Data` is serialized using a format not support by the host chain, the packet will not be successfully received. + +## `OnAcknowledgementPacket` + +Controller chains will be able to access the acknowledgement written into the host chain state once a relayer relays the acknowledgement. +The acknowledgement bytes will be passed to the auth module via the `OnAcknowledgementPacket` callback. +Auth modules are expected to know how to decode the acknowledgement. + +If the controller chain is connected to a host chain using the host module on ibc-go, it may interpret the acknowledgement bytes as follows: + +Begin by unmarshaling the acknowledgement into sdk.TxMsgData: + +```go +var ack channeltypes.Acknowledgement +if err := channeltypes.SubModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { + return err +} + +txMsgData := &sdk.TxMsgData{} +if err := proto.Unmarshal(ack.GetResult(), txMsgData); err != nil { + return err +} +``` + +If the txMsgData.Data field is non nil, the host chain is using SDK version \<\= v0.45. +The auth module should interpret the txMsgData.Data as follows: + +```go +switch len(txMsgData.Data) { +case 0: + // see documentation below for SDK 0.46.x or greater +default: + for _, msgData := range txMsgData.Data { + if err := handler(msgData); err != nil { + return err + } + } +... +} +``` + +A handler will be needed to interpret what actions to perform based on the message type sent. +A router could be used, or more simply a switch statement. + +```go +func handler(msgData sdk.MsgData) error { +switch msgData.MsgType { +case sdk.MsgTypeURL(&banktypes.MsgSend{}): + msgResponse := &banktypes.MsgSendResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleBankSendMsg(msgResponse) + +case sdk.MsgTypeURL(&stakingtypes.MsgDelegate{}): + msgResponse := &stakingtypes.MsgDelegateResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleStakingDelegateMsg(msgResponse) + +case sdk.MsgTypeURL(&transfertypes.MsgTransfer{}): + msgResponse := &transfertypes.MsgTransferResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleIBCTransferMsg(msgResponse) + +default: + return +} +``` + +If the txMsgData.Data is empty, the host chain is using SDK version > v0.45. +The auth module should interpret the txMsgData.Responses as follows: + +```go +... +// switch statement from above +case 0: + for _, any := range txMsgData.MsgResponses { + if err := handleAny(any); err != nil { + return err + } + } +} +``` + +A handler will be needed to interpret what actions to perform based on the type url of the Any. +A router could be used, or more simply a switch statement. +It may be possible to deduplicate logic between `handler` and `handleAny`. + +```go +func handleAny(any *codectypes.Any) error { +switch any.TypeURL { +case banktypes.MsgSend: + msgResponse, err := unpackBankMsgSendResponse(any) + if err != nil { + return err + } + + handleBankSendMsg(msgResponse) + +case stakingtypes.MsgDelegate: + msgResponse, err := unpackStakingDelegateResponse(any) + if err != nil { + return err + } + + handleStakingDelegateMsg(msgResponse) + + case transfertypes.MsgTransfer: + msgResponse, err := unpackIBCTransferMsgResponse(any) + if err != nil { + return err + } + + handleIBCTransferMsg(msgResponse) + +default: + return +} +``` + +### Integration into `app.go` file + +To integrate the authentication module into your chain, please follow the steps outlined above in [app.go integration](04-integration.md#example-integration). diff --git a/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/03-active-channels.md b/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/03-active-channels.md new file mode 100644 index 0000000..500deb6 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/03-active-channels.md @@ -0,0 +1,28 @@ +--- +title: Active Channels +sidebar_label: Active Channels +sidebar_position: 3 +slug: /apps/interchain-accounts/active-channels +--- + + +# Understanding Active Channels + +The Interchain Accounts module uses [ORDERED channels](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#ordering) to maintain the order of transactions when sending packets from a controller to a host chain. A limitation when using ORDERED channels is that when a packet times out the channel will be closed. + +In the case of a channel closing, a controller chain needs to be able to regain access to the interchain account registered on this channel. `Active Channels` enable this functionality. Future versions of the ICS-27 protocol and the Interchain Accounts module will likely use a new +channel type that provides ordering of packets without the channel closing on timing out, thus removing the need for `Active Channels` entirely. + +When an Interchain Account is registered using the `RegisterInterchainAccount` API, a new channel is created on a particular port. During the `OnChanOpenAck` and `OnChanOpenConfirm` steps (controller & host chain) the `Active Channel` for this interchain account +is stored in state. + +It is possible to create a new channel using the same controller chain portID if the previously set `Active Channel` is now in a `CLOSED` state. This channel creation can be initialized programmatically by sending a new `MsgChannelOpenInit` message like so: + +```go +msg := channeltypes.NewMsgChannelOpenInit(portID, string(versionBytes), channeltypes.ORDERED, []string{connectionID}, icatypes.PortID, icatypes.ModuleName) +handler := k.msgRouter.Handler(msg) +``` + +Alternatively, any relayer operator may initiate a new channel handshake for this interchain account once the previously set `Active Channel` is in a `CLOSED` state. This is done by initiating the channel handshake on the controller chain using the same portID associated with the interchain account in question. + +It is important to note that once a channel has been opened for a given Interchain Account, new channels can not be opened for this account until the currently set `Active Channel` is set to `CLOSED`. diff --git a/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/04-integration.md b/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/04-integration.md new file mode 100644 index 0000000..20b47a7 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/04-integration.md @@ -0,0 +1,193 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 4 +slug: /apps/interchain-accounts/integration +--- + + +# Integration + +:::note Synopsis +Learn how to integrate Interchain Accounts host and controller functionality to your chain. The following document only applies for Cosmos SDK chains. +::: + +The Interchain Accounts module contains two submodules. Each submodule has its own IBC application. The Interchain Accounts module should be registered as an `AppModule` in the same way all SDK modules are registered on a chain, but each submodule should create its own `IBCModule` as necessary. A route should be added to the IBC router for each submodule which will be used. + +Chains who wish to support ICS27 may elect to act as a host chain, a controller chain or both. Disabling host or controller functionality may be done statically by excluding the host or controller module entirely from the `app.go` file or it may be done dynamically by taking advantage of the on-chain parameters which enable or disable the host or controller submodules. + +Interchain Account authentication modules are the base application of a middleware stack. The controller submodule is the middleware in this stack. + +## Example integration + +```go +// app.go + +// Register the AppModule for the Interchain Accounts module and the authentication module +// Note: No `icaauth` exists, this must be substituted with an actual Interchain Accounts authentication module +ModuleBasics = module.NewBasicManager( + ... + ica.AppModuleBasic{}, + icaauth.AppModuleBasic{}, + ... +) + +... + +// Add module account permissions for the Interchain Accounts module +// Only necessary for host chain functionality +// Each Interchain Account created on the host chain is derived from the module account created +maccPerms = map[string][]string{ + ... + icatypes.ModuleName: nil, +} + +... + +// Add Interchain Accounts Keepers for each submodule used and the authentication module +// If a submodule is being statically disabled, the associated Keeper does not need to be added. +type App struct { + ... + + ICAControllerKeeper icacontrollerkeeper.Keeper + ICAHostKeeper icahostkeeper.Keeper + ICAAuthKeeper icaauthkeeper.Keeper + + ... +} + +... + +// Create store keys for each submodule Keeper and the authentication module +keys := sdk.NewKVStoreKeys( + ... + icacontrollertypes.StoreKey, + icahosttypes.StoreKey, + icaauthtypes.StoreKey, + ... +) + +... + +// Create the scoped keepers for each submodule keeper and authentication keeper +scopedICAControllerKeeper := app.CapabilityKeeper.ScopeToModule(icacontrollertypes.SubModuleName) +scopedICAHostKeeper := app.CapabilityKeeper.ScopeToModule(icahosttypes.SubModuleName) +scopedICAAuthKeeper := app.CapabilityKeeper.ScopeToModule(icaauthtypes.ModuleName) + +... + +// Create the Keeper for each submodule +app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + scopedICAControllerKeeper, app.MsgServiceRouter(), +) +app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.AccountKeeper, scopedICAHostKeeper, app.MsgServiceRouter(), +) + +// Create Interchain Accounts AppModule +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper) + +// Create your Interchain Accounts authentication module +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.ICAControllerKeeper, scopedICAAuthKeeper) + +// ICA auth AppModule +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) + +// ICA auth IBC Module +icaAuthIBCModule := icaauth.NewIBCModule(app.ICAAuthKeeper) + +// Create controller IBC application stack and host IBC module as desired +icaControllerStack := icacontroller.NewIBCMiddleware(icaAuthIBCModule, app.ICAControllerKeeper) +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host and authentication routes +ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icahosttypes.SubModuleName, icaHostIBCModule). + AddRoute(icaauthtypes.ModuleName, icaControllerStack) // Note, the authentication module is routed to the top level of the middleware stack + +... + +// Register Interchain Accounts and authentication module AppModule's +app.moduleManager = module.NewManager( + ... + icaModule, + icaAuthModule, +) + +... + +// Add fee middleware to begin blocker logic +app.moduleManager.SetOrderBeginBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add fee middleware to end blocker logic +app.moduleManager.SetOrderEndBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add Interchain Accounts module InitGenesis logic +app.moduleManager.SetOrderInitGenesis( + ... + icatypes.ModuleName, + ... +) + +// initParamsKeeper init params keeper and its subspaces +func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey sdk.StoreKey) paramskeeper.Keeper { + ... + paramsKeeper.Subspace(icahosttypes.SubModuleName) + paramsKeeper.Subspace(icacontrollertypes.SubModuleName) + ... +``` + +### Using submodules exclusively + +As described above, the Interchain Accounts application module is structured to support the ability of exclusively enabling controller or host functionality. +This can be achieved by simply omitting either controller or host `Keeper` from the Interchain Accounts `NewAppModule` constructor function, and mounting only the desired submodule via the `IBCRouter`. +Alternatively, submodules can be enabled and disabled dynamically using [on-chain parameters](05-parameters.md). + +The following snippets show basic examples of statically disabling submodules using `app.go`. + +#### Disabling controller chain functionality + +```go +// Create Interchain Accounts AppModule omitting the controller keeper +icaModule := ica.NewAppModule(nil, &app.ICAHostKeeper) + +// Create host IBC Module +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host route +ibcRouter.AddRoute(icahosttypes.SubModuleName, icaHostIBCModule) +``` + +#### Disabling host chain functionality + +```go +// Create Interchain Accounts AppModule omitting the host keeper +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, nil) + +// Create your Interchain Accounts authentication module, setting up the Keeper, AppModule and IBCModule appropriately +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.ICAControllerKeeper, scopedICAAuthKeeper) +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) +icaAuthIBCModule := icaauth.NewIBCModule(app.ICAAuthKeeper) + +// Create controller IBC application stack +icaControllerStack := icacontroller.NewIBCMiddleware(icaAuthIBCModule, app.ICAControllerKeeper) + +// Register controller and authentication routes +ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icaauthtypes.ModuleName, icaControllerStack) // Note, the authentication module is routed to the top level of the middleware stack +``` diff --git a/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/05-parameters.md b/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/05-parameters.md new file mode 100644 index 0000000..39036c3 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/05-parameters.md @@ -0,0 +1,65 @@ +--- +title: Parameters +sidebar_label: Parameters +sidebar_position: 5 +slug: /apps/interchain-accounts/parameters +--- + + +# Parameters + +The Interchain Accounts module contains the following on-chain parameters, logically separated for each distinct submodule: + +## Controller Submodule Parameters + +| Key | Type | Default Value | +|------------------------|------|---------------| +| `ControllerEnabled` | bool | `true` | + +### ControllerEnabled + +The `ControllerEnabled` parameter controls a chains ability to service ICS-27 controller specific logic. This includes the sending of Interchain Accounts packet data as well as the following ICS-26 callback handlers: + +- `OnChanOpenInit` +- `OnChanOpenAck` +- `OnChanCloseConfirm` +- `OnAcknowledgementPacket` +- `OnTimeoutPacket` + +## Host Submodule Parameters + +| Key | Type | Default Value | +|------------------------|----------|---------------| +| `HostEnabled` | bool | `true` | +| `AllowMessages` | []string | `[]` | + +### HostEnabled + +The `HostEnabled` parameter controls a chains ability to service ICS27 host specific logic. This includes the following ICS-26 callback handlers: + +- `OnChanOpenTry` +- `OnChanOpenConfirm` +- `OnChanCloseConfirm` +- `OnRecvPacket` + +### AllowMessages + +The `AllowMessages` parameter provides the ability for a chain to limit the types of messages or transactions that hosted interchain accounts are authorized to execute by defining an allowlist using the Protobuf message TypeURL format. + +For example, a Cosmos SDK based chain that elects to provide hosted Interchain Accounts with the ability of governance voting and staking delegations will define its parameters as follows: + +```json +"params": { + "host_enabled": true, + "allow_messages": ["/cosmos.staking.v1beta1.MsgDelegate", "/cosmos.gov.v1beta1.MsgVote"] +} +``` + +There is also a special wildcard `"*"` message type which allows any type of message to be executed by the interchain account. This must be the only message in the `allow_messages` array. + +```json +"params": { + "host_enabled": true, + "allow_messages": ["*"] +} +``` diff --git a/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/06-transactions.md b/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/06-transactions.md new file mode 100644 index 0000000..1d53027 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/06-transactions.md @@ -0,0 +1,27 @@ +--- +title: Transactions +sidebar_label: Transactions +sidebar_position: 6 +slug: /apps/interchain-accounts/transactions +--- + + +# Transactions + +:::note Synopsis +Learn about Interchain Accounts transaction execution +::: + +## Executing a transaction + +As described in [Authentication Modules](02-auth-modules.md#trysendtx) transactions are executed using the interchain accounts controller API and require a `Base Application` as outlined in [ICS30 IBC Middleware](https://github.com/cosmos/ibc/tree/master/spec/app/ics-030-middleware) to facilitate authentication. The method of authentication remains unspecified to provide flexibility for the authentication module developer. + +Transactions are executed via the ICS27 [`SendTx` API](02-auth-modules.md#trysendtx). This must be invoked through an Interchain Accounts authentication module and follows the outlined path of execution below. Packet relaying semantics provided by the IBC core transport, authentication, and ordering (IBC/TAO) layer are omitted for brevity. + +![send-interchain-tx.png](./images/send-interchain-tx.png) + +## Atomicity + +As the Interchain Accounts module supports the execution of multiple transactions using the Cosmos SDK `Msg` interface, it provides the same atomicity guarantees as Cosmos SDK-based applications, leveraging the [`CacheMultiStore`](https://docs.cosmos.network/main/learn/advanced/store#cachemultistore) architecture provided by the [`Context`](https://docs.cosmos.network/main/learn/advanced/context.html) type. + +This provides atomic execution of transactions when using Interchain Accounts, where state changes are only committed if all `Msg`s succeed. diff --git a/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/_category_.json b/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/_category_.json new file mode 100644 index 0000000..41e3ac2 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Interchain Accounts", + "position": 2, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/images/send-interchain-tx.png b/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/images/send-interchain-tx.png new file mode 100644 index 0000000..37a6576 Binary files /dev/null and b/docs/versioned_docs/version-v4.6.x/02-apps/02-interchain-accounts/images/send-interchain-tx.png differ diff --git a/docs/versioned_docs/version-v4.6.x/02-apps/_category_.json b/docs/versioned_docs/version-v4.6.x/02-apps/_category_.json new file mode 100644 index 0000000..83a389b --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/02-apps/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "IBC Application Modules", + "position": 2, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/01-overview.md b/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/01-overview.md new file mode 100644 index 0000000..81d7e9e --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/01-overview.md @@ -0,0 +1,54 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /middleware/ics29-fee/overview +--- + +# Overview + +:::note Synopsis +Learn about what the Fee Middleware module is, and how to build custom modules that utilize the Fee Middleware functionality +::: + +## What is the Fee Middleware module? + +IBC does not depend on relayer operators for transaction verification. However, the relayer infrastructure ensures liveness of the Interchain network — operators listen for packets sent through channels opened between chains, and perform the vital service of ferrying these packets (and proof of the transaction on the sending chain/receipt on the receiving chain) to the clients on each side of the channel. + +Though relaying is permissionless and completely decentralized and accessible, it does come with operational costs. Running full nodes to query transaction proofs and paying for transaction fees associated with IBC packets are two of the primary cost burdens which have driven the overall discussion on **a general, in-protocol incentivization mechanism for relayers**. + +Initially, a [simple proposal](https://github.com/cosmos/ibc/pull/577/files) was created to incentivize relaying on ICS20 token transfers on the destination chain. However, the proposal was specific to ICS20 token transfers and would have to be reimplemented in this format on every other IBC application module. + +After much discussion, the proposal was expanded to a [general incentivisation design](https://github.com/cosmos/ibc/tree/master/spec/app/ics-029-fee-payment) that can be adopted by any ICS application protocol as [middleware](../../01-ibc/04-middleware/01-develop.md). + +## Concepts + +ICS29 fee payments in this middleware design are built on the assumption that sender chains are the source of incentives — the chain on which packets are incentivized is the chain that distributes fees to relayer operators. However, as part of the IBC packet flow, messages have to be submitted on both sender and destination chains. This introduces the requirement of a mapping of relayer operator's addresses on both chains. + +To achieve the stated requirements, the **fee middleware module has two main groups of functionality**: + +- Registering of relayer addresses associated with each party involved in relaying the packet on the source chain. This registration process can be automated on start up of relayer infrastructure and happens only once, not every packet flow. + + This is described in the [Fee distribution section](04-fee-distribution.md). + +- Escrowing fees by any party which will be paid out to each rightful party on completion of the packet lifecycle. + + This is described in the [Fee messages section](03-msgs.md). + +We complete the introduction by giving a list of definitions of relevant terminology. + +`Forward relayer`: The relayer that submits the `MsgRecvPacket` message for a given packet (on the destination chain). + +`Reverse relayer`: The relayer that submits the `MsgAcknowledgement` message for a given packet (on the source chain). + +`Timeout relayer`: The relayer that submits the `MsgTimeout` or `MsgTimeoutOnClose` messages for a given packet (on the source chain). + +`Payee`: The account address on the source chain to be paid on completion of the packet lifecycle. The packet lifecycle on the source chain completes with the receipt of a `MsgTimeout`/`MsgTimeoutOnClose` or a `MsgAcknowledgement`. + +`Counterparty payee`: The account address to be paid on completion of the packet lifecycle on the destination chain. The package lifecycle on the destination chain completes with a successful `MsgRecvPacket`. + +`Refund address`: The address of the account paying for the incentivization of packet relaying. The account is refunded timeout fees upon successful acknowledgement. In the event of a packet timeout, both acknowledgement and receive fees are refunded. + +## Known Limitations + +The first version of fee payments middleware will only support incentivisation of new channels, however, channel upgradeability will enable incentivisation of all existing channels. diff --git a/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/02-integration.md b/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/02-integration.md new file mode 100644 index 0000000..0cac856 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/02-integration.md @@ -0,0 +1,174 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /middleware/ics29-fee/integration +--- + + +# Integration + +:::note Synopsis +Learn how to configure the Fee Middleware module with IBC applications. The following document is intended for developers building on top of the Cosmos SDK and only applies for Cosmos SDK chains. +::: + +## Pre-requisite Readings + +- [IBC middleware development](../../01-ibc/04-middleware/01-develop.md) +- [IBC middleware integration](../../01-ibc/04-middleware/02-integration.md) + +The Fee Middleware module, as the name suggests, plays the role of an IBC middleware and as such must be configured by chain developers to route and handle IBC messages correctly. +For Cosmos SDK chains this setup is done via the `app/app.go` file, where modules are constructed and configured in order to bootstrap the blockchain application. + +## Example integration of the Fee Middleware module + +```go +// app.go + +// Register the AppModule for the fee middleware module +ModuleBasics = module.NewBasicManager( + ... + ibcfee.AppModuleBasic{}, + ... +) + +... + +// Add module account permissions for the fee middleware module +maccPerms = map[string][]string{ + ... + ibcfeetypes.ModuleName: nil, +} + +... + +// Add fee middleware Keeper +type App struct { + ... + + IBCFeeKeeper ibcfeekeeper.Keeper + + ... +} + +... + +// Create store keys +keys := sdk.NewKVStoreKeys( + ... + ibcfeetypes.StoreKey, + ... +) + +... + +app.IBCFeeKeeper = ibcfeekeeper.NewKeeper( + appCodec, keys[ibcfeetypes.StoreKey], + app.IBCKeeper.ChannelKeeper, // may be replaced with IBC middleware + app.IBCKeeper.ChannelKeeper, + &app.IBCKeeper.PortKeeper, app.AccountKeeper, app.BankKeeper, +) + + +// See the section below for configuring an application stack with the fee middleware module + +... + +// Register fee middleware AppModule +app.moduleManager = module.NewManager( + ... + ibcfee.NewAppModule(app.IBCFeeKeeper), +) + +... + +// Add fee middleware to begin blocker logic +app.moduleManager.SetOrderBeginBlockers( + ... + ibcfeetypes.ModuleName, + ... +) + +// Add fee middleware to end blocker logic +app.moduleManager.SetOrderEndBlockers( + ... + ibcfeetypes.ModuleName, + ... +) + +// Add fee middleware to init genesis logic +app.moduleManager.SetOrderInitGenesis( + ... + ibcfeetypes.ModuleName, + ... +) +``` + +## Configuring an application stack with Fee Middleware + +As mentioned in [IBC middleware development](../../01-ibc/04-middleware/01-develop.md) an application stack may be composed of many or no middlewares that nest a base application. +These layers form the complete set of application logic that enable developers to build composable and flexible IBC application stacks. +For example, an application stack may be just a single base application like `transfer`, however, the same application stack composed with `29-fee` will nest the `transfer` base application +by wrapping it with the Fee Middleware module. + +### Transfer + +See below for an example of how to create an application stack using `transfer` and `29-fee`. +The following `transferStack` is configured in `app/app.go` and added to the IBC `Router`. +The in-line comments describe the execution flow of packets between the application stack and IBC core. + +```go +// Create Transfer Stack +// SendPacket, since it is originating from the application to core IBC: +// transferKeeper.SendPacket -> fee.SendPacket -> channel.SendPacket + +// RecvPacket, message that originates from core IBC and goes down to app, the flow is the other way +// channel.RecvPacket -> fee.OnRecvPacket -> transfer.OnRecvPacket + +// transfer stack contains (from top to bottom): +// - IBC Fee Middleware +// - Transfer + +// create IBC module from bottom to top of stack +var transferStack porttypes.IBCModule +transferStack = transfer.NewIBCModule(app.TransferKeeper) +transferStack = ibcfee.NewIBCMiddleware(transferStack, app.IBCFeeKeeper) + +// Add transfer stack to IBC Router +ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) +``` + +### Interchain Accounts + +See below for an example of how to create an application stack using `27-interchain-accounts` and `29-fee`. +The following `icaControllerStack` and `icaHostStack` are configured in `app/app.go` and added to the IBC `Router` with the associated authentication module. +The in-line comments describe the execution flow of packets between the application stack and IBC core. + +```go +// Create Interchain Accounts Stack +// SendPacket, since it is originating from the application to core IBC: +// icaAuthModuleKeeper.SendTx -> icaController.SendPacket -> fee.SendPacket -> channel.SendPacket + +// initialize ICA module with mock module as the authentication module on the controller side +var icaControllerStack porttypes.IBCModule +icaControllerStack = ibcmock.NewIBCModule(&mockModule, ibcmock.NewMockIBCApp("", scopedICAMockKeeper)) +app.ICAAuthModule = icaControllerStack.(ibcmock.IBCModule) +icaControllerStack = icacontroller.NewIBCMiddleware(icaControllerStack, app.ICAControllerKeeper) +icaControllerStack = ibcfee.NewIBCMiddleware(icaControllerStack, app.IBCFeeKeeper) + +// RecvPacket, message that originates from core IBC and goes down to app, the flow is: +// channel.RecvPacket -> fee.OnRecvPacket -> icaHost.OnRecvPacket + +var icaHostStack porttypes.IBCModule +icaHostStack = icahost.NewIBCModule(app.ICAHostKeeper) +icaHostStack = ibcfee.NewIBCMiddleware(icaHostStack, app.IBCFeeKeeper) + +// Add authentication module, controller and host to IBC router +ibcRouter. + // the ICA Controller middleware needs to be explicitly added to the IBC Router because the + // ICA controller module owns the port capability for ICA. The ICA authentication module + // owns the channel capability. + AddRoute(ibcmock.ModuleName+icacontrollertypes.SubModuleName, icaControllerStack) // ica with mock auth module stack route to ica (top level of middleware stack) + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icahosttypes.SubModuleName, icaHostStack). +``` diff --git a/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/03-msgs.md b/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/03-msgs.md new file mode 100644 index 0000000..706706e --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/03-msgs.md @@ -0,0 +1,95 @@ +--- +title: Fee Messages +sidebar_label: Fee Messages +sidebar_position: 3 +slug: /middleware/ics29-fee/msgs +--- + +# Fee messages + +:::note Synopsis +Learn about the different ways to pay for fees, how the fees are paid out and what happens when not enough escrowed fees are available for payout +::: + +## Escrowing fees + +The fee middleware module exposes two different ways to pay fees for relaying IBC packets: + +1. `MsgPayPacketFee`, which enables the escrowing of fees for a packet at the next sequence send and should be combined into one `MultiMsgTx` with the message that will be paid for. + + Note that the `Relayers` field has been set up to allow for an optional whitelist of relayers permitted to receive this fee, however, this feature has not yet been enabled at this time. + + ```go + type MsgPayPacketFee struct{ + // fee encapsulates the recv, ack and timeout fees associated with an IBC packet + Fee Fee + // the source port unique identifier + SourcePortId string + // the source channel unique identifier + SourceChannelId string + // account address to refund fee if necessary + Signer string + // optional list of relayers permitted to the receive packet fee + Relayers []string + } + ``` + + The `Fee` message contained in this synchronous fee payment method configures different fees which will be paid out for `MsgRecvPacket`, `MsgAcknowledgement`, and `MsgTimeout`/`MsgTimeoutOnClose`. + + ```go + type Fee struct { + RecvFee types.Coins + AckFee types.Coins + TimeoutFee types.Coins + } + ``` + + The diagram below shows the `MultiMsgTx` with the `MsgTransfer` coming from a token transfer message, along with `MsgPayPacketFee`. + + ![msgpaypacket.png](./images/msgpaypacket.png) + +2. `MsgPayPacketFeeAsync`, which enables the asynchronous escrowing of fees for a specified packet: + + Note that a packet can be 'topped up' multiple times with additional fees of any coin denomination by broadcasting multiple `MsgPayPacketFeeAsync` messages. + + ```go + type MsgPayPacketFeeAsync struct { + // unique packet identifier comprised of the channel ID, port ID and sequence + PacketId channeltypes.PacketId + // the packet fee associated with a particular IBC packet + PacketFee PacketFee + } + ``` + + where the `PacketFee` also specifies the `Fee` to be paid as well as the refund address for fees which are not paid out + + ```go + type PacketFee struct { + Fee Fee + RefundAddress string + Relayers []string + } + ``` + +The diagram below shows how multiple `MsgPayPacketFeeAsync` can be broadcasted asynchronously. Escrowing of the fee associated with a packet can be carried out by any party because ICS-29 does not dictate a particular fee payer. In fact, chains can choose to simply not expose this fee payment to end users at all and rely on a different module account or even the community pool as the source of relayer incentives. + +![paypacketfeeasync.png](./images/paypacketfeeasync.png) + +Please see our [wiki](https://github.com/cosmos/ibc-go/wiki/Fee-enabled-fungible-token-transfers) for example flows on how to use these messages to incentivise a token transfer channel using a CLI. + +## Paying out the escrowed fees + +Following diagram takes a look at the packet flow for an incentivized token transfer and investigates the several scenario's for paying out the escrowed fees. We assume that the relayers have registered their counterparty address, detailed in the [Fee distribution section](04-fee-distribution.md). + +![feeflow.png](./images/feeflow.png) + +- In the case of a successful transaction, `RecvFee` will be paid out to the designated counterparty payee address which has been registered on the receiver chain and sent back with the `MsgAcknowledgement`, `AckFee` will be paid out to the relayer address which has submitted the `MsgAcknowledgement` on the sending chain (or the registered payee in case one has been registered for the relayer address), and `TimeoutFee` will be reimbursed to the account which escrowed the fee. +- In case of a timeout transaction, `RecvFee` and `AckFee` will be reimbursed. The `TimeoutFee` will be paid to the `Timeout Relayer` (who submits the timeout message to the source chain). + +> Please note that fee payments are built on the assumption that sender chains are the source of incentives — the chain that sends the packets is the same chain where fee payments will occur -- please see the [Fee distribution section](04-fee-distribution.md) to understand the flow for registering payee and counterparty payee (fee receiving) addresses. + +## A locked fee middleware module + +The fee middleware module can become locked if the situation arises that the escrow account for the fees does not have sufficient funds to pay out the fees which have been escrowed for each packet. *This situation indicates a severe bug.* In this case, the fee module will be locked until manual intervention fixes the issue. + +> A locked fee module will simply skip fee logic and continue on to the underlying packet flow. A channel with a locked fee module will temporarily function as a fee disabled channel, and the locking of a fee module will not affect the continued flow of packets over the channel. diff --git a/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/04-fee-distribution.md b/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/04-fee-distribution.md new file mode 100644 index 0000000..48f0434 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/04-fee-distribution.md @@ -0,0 +1,114 @@ +--- +title: Fee Distribution +sidebar_label: Fee Distribution +sidebar_position: 4 +slug: /middleware/ics29-fee/fee-distribution +--- + + +# Fee distribution + +:::note Synopsis +Learn about payee registration for the distribution of packet fees. The following document is intended for relayer operators. +::: + +## Pre-requisite readings + +- [Fee Middleware](01-overview.md) + +Packet fees are divided into 3 distinct amounts in order to compensate relayer operators for packet relaying on fee enabled IBC channels. + +- `RecvFee`: The sum of all packet receive fees distributed to a payee for successful execution of `MsgRecvPacket`. +- `AckFee`: The sum of all packet acknowledgement fees distributed to a payee for successful execution of `MsgAcknowledgement`. +- `TimeoutFee`: The sum of all packet timeout fees distributed to a payee for successful execution of `MsgTimeout`. + +## Register a counterparty payee address for forward relaying + +As mentioned in [ICS29 Concepts](01-overview.md#concepts), the forward relayer describes the actor who performs the submission of `MsgRecvPacket` on the destination chain. +Fee distribution for incentivized packet relays takes place on the packet source chain. + +> Relayer operators are expected to register a counterparty payee address, in order to be compensated accordingly with `RecvFee`s upon completion of a packet lifecycle. + +The counterparty payee address registered on the destination chain is encoded into the packet acknowledgement and communicated as such to the source chain for fee distribution. +**If a counterparty payee is not registered for the forward relayer on the destination chain, the escrowed fees will be refunded upon fee distribution.** + +### Relayer operator actions? + +A transaction must be submitted **to the destination chain** including a `CounterpartyPayee` address of an account on the source chain. +The transaction must be signed by the `Relayer`. + +Note: If a module account address is used as the `CounterpartyPayee` but the module has been set as a blocked address in the `BankKeeper`, the refunding to the module account will fail. This is because many modules use invariants to compare internal tracking of module account balances against the actual balance of the account stored in the `BankKeeper`. If a token transfer to the module account occurs without going through this module and updating the account balance of the module on the `BankKeeper`, then invariants may break and unknown behaviour could occur depending on the module implementation. Therefore, if it is desirable to use a module account that is currently blocked, the module developers should be consulted to gauge to possibility of removing the module account from the blocked list. + +```go +type MsgRegisterCounterpartyPayee struct { + // unique port identifier + PortId string + // unique channel identifier + ChannelId string + // the relayer address + Relayer string + // the counterparty payee address + CounterpartyPayee string +} +``` + +> This message is expected to fail if: +> +> - `PortId` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators). +> - `ChannelId` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +> - `Relayer` is an invalid address. +> - `CounterpartyPayee` is empty. + +See below for an example CLI command: + +```bash +simd tx ibc-fee register-counterparty-payee transfer channel-0 \ +cosmos1rsp837a4kvtgp2m4uqzdge0zzu6efqgucm0qdh \ +osmo1v5y0tz01llxzf4c2afml8s3awue0ymju22wxx2 \ +--from cosmos1rsp837a4kvtgp2m4uqzdge0zzu6efqgucm0qdh +``` + +## Register an alternative payee address for reverse and timeout relaying + +As mentioned in [ICS29 Concepts](01-overview.md#concepts), the reverse relayer describes the actor who performs the submission of `MsgAcknowledgement` on the source chain. +Similarly the timeout relayer describes the actor who performs the submission of `MsgTimeout` (or `MsgTimeoutOnClose`) on the source chain. + +> Relayer operators **may choose** to register an optional payee address, in order to be compensated accordingly with `AckFee`s and `TimeoutFee`s upon completion of a packet life cycle. + +If a payee is not registered for the reverse or timeout relayer on the source chain, then fee distribution assumes the default behaviour, where fees are paid out to the relayer account which delivers `MsgAcknowledgement` or `MsgTimeout`/`MsgTimeoutOnClose`. + +### Relayer operator actions + +A transaction must be submitted **to the source chain** including a `Payee` address of an account on the source chain. +The transaction must be signed by the `Relayer`. + +Note: If a module account address is used as the `Payee` it is recommended to [turn off invariant checks](https://github.com/cosmos/ibc-go/blob/71d7480c923f4227453e8a80f51be01ae7ee845e/testing/simapp/app.go#L659) for that module. + +```go +type MsgRegisterPayee struct { + // unique port identifier + PortId string + // unique channel identifier + ChannelId string + // the relayer address + Relayer string + // the payee address + Payee string +} +``` + +> This message is expected to fail if: +> +> - `PortId` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators). +> - `ChannelId` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +> - `Relayer` is an invalid address. +> - `Payee` is an invalid address. + +See below for an example CLI command: + +```bash +simd tx ibc-fee register-payee transfer channel-0 \ +cosmos1rsp837a4kvtgp2m4uqzdge0zzu6efqgucm0qdh \ +cosmos153lf4zntqt33a4v0sm5cytrxyqn78q7kz8j8x5 \ +--from cosmos1rsp837a4kvtgp2m4uqzdge0zzu6efqgucm0qdh +``` diff --git a/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/05-events.md b/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/05-events.md new file mode 100644 index 0000000..dbda482 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/05-events.md @@ -0,0 +1,43 @@ +--- +title: Events +sidebar_label: Events +sidebar_position: 5 +slug: /middleware/ics29-fee/events +--- + + +# Events + +:::note Synopsis +An overview of all events related to ICS-29 +::: + +## `MsgPayPacketFee`, `MsgPayPacketFeeAsync` + +| Type | Attribute Key | Attribute Value | +| ----------------------- | --------------- | --------------- | +| incentivized_ibc_packet | port_id | \{portID\} | +| incentivized_ibc_packet | channel_id | \{channelID\} | +| incentivized_ibc_packet | packet_sequence | \{sequence\} | +| incentivized_ibc_packet | recv_fee | \{recvFee\} | +| incentivized_ibc_packet | ack_fee | \{ackFee\} | +| incentivized_ibc_packet | timeout_fee | \{timeoutFee\} | +| message | module | fee-ibc | + +## `RegisterPayee` + +| Type | Attribute Key | Attribute Value | +| -------------- | ------------- | --------------- | +| register_payee | relayer | \{relayer\} | +| register_payee | payee | \{payee\} | +| register_payee | channel_id | \{channelID\} | +| message | module | fee-ibc | + +## `RegisterCounterpartyPayee` + +| Type | Attribute Key | Attribute Value | +| --------------------------- | ------------------ | --------------------- | +| register_counterparty_payee | relayer | \{relayer\} | +| register_counterparty_payee | counterparty_payee | \{counterpartyPayee\} | +| register_counterparty_payee | channel_id | \{channelID\} | +| message | module | fee-ibc | diff --git a/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/06-end-users.md b/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/06-end-users.md new file mode 100644 index 0000000..7419d03 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/06-end-users.md @@ -0,0 +1,36 @@ +--- +title: End Users +sidebar_label: End Users +sidebar_position: 6 +slug: /middleware/ics29-fee/end-users +--- + + +# For end users + +:::note Synopsis +Learn how to incentivize IBC packets using the ICS29 Fee Middleware module. +::: + +## Pre-requisite readings + +- [Fee Middleware](01-overview.md) + +## Summary + +Different types of end users: + +- CLI users who want to manually incentivize IBC packets +- Client developers + +The Fee Middleware module allows end users to add a 'tip' to each IBC packet which will incentivize relayer operators to relay packets between chains. gRPC endpoints are exposed for client developers as well as a simple CLI for manually incentivizing IBC packets. + +## CLI Users + +For an in depth guide on how to use the ICS29 Fee Middleware module using the CLI please take a look at the [wiki](https://github.com/cosmos/ibc-go/wiki/Fee-enabled-fungible-token-transfers#asynchronous-incentivization-of-a-fungible-token-transfer) on the `ibc-go` repo. + +## Client developers + +Client developers can read more about the relevant ICS29 message types in the [Fee messages section](03-msgs.md). + +[CosmJS](https://github.com/cosmos/cosmjs) is a useful client library for signing and broadcasting Cosmos SDK messages. diff --git a/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/_category_.json b/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/_category_.json new file mode 100644 index 0000000..639bd0e --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Fee Middleware", + "position": 1, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/images/feeflow.png b/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/images/feeflow.png new file mode 100644 index 0000000..4e77529 Binary files /dev/null and b/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/images/feeflow.png differ diff --git a/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/images/msgpaypacket.png b/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/images/msgpaypacket.png new file mode 100644 index 0000000..a454f52 Binary files /dev/null and b/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/images/msgpaypacket.png differ diff --git a/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/images/paypacketfeeasync.png b/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/images/paypacketfeeasync.png new file mode 100644 index 0000000..73d1e04 Binary files /dev/null and b/docs/versioned_docs/version-v4.6.x/03-middleware/01-ics29-fee/images/paypacketfeeasync.png differ diff --git a/docs/versioned_docs/version-v4.6.x/03-middleware/_category_.json b/docs/versioned_docs/version-v4.6.x/03-middleware/_category_.json new file mode 100644 index 0000000..13f4809 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/03-middleware/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "IBC Middleware Modules", + "position": 3, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v4.6.x/04-migrations/01-support-denoms-with-slashes.md b/docs/versioned_docs/version-v4.6.x/04-migrations/01-support-denoms-with-slashes.md new file mode 100644 index 0000000..df8912d --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/04-migrations/01-support-denoms-with-slashes.md @@ -0,0 +1,88 @@ +--- +title: Support transfer of coins whose base denom contains slashes +sidebar_label: Support transfer of coins whose base denom contains slashes +sidebar_position: 1 +slug: /migrations/support-denoms-with-slashes +--- +# Migrating from not supporting base denoms with slashes to supporting base denoms with slashes + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +This document is necessary when chains are upgrading from a version that does not support base denoms with slashes (e.g. v3.0.0) to a version that does (e.g. v3.2.0). All versions of ibc-go smaller than v1.5.0 for the v1.x release line, v2.3.0 for the v2.x release line, and v3.1.0 for the v3.x release line do **NOT** support IBC token transfers of coins whose base denoms contain slashes. Therefore the in-place of genesis migration described in this document are required when upgrading. + +If a chain receives coins of a base denom with slashes before it upgrades to supporting it, the receive may pass however the trace information will be incorrect. + +E.g. If a base denom of `testcoin/testcoin/testcoin` is sent to a chain that does not support slashes in the base denom, the receive will be successful. However, the trace information stored on the receiving chain will be: `Trace: "transfer/{channel-id}/testcoin/testcoin", BaseDenom: "testcoin"`. + +This incorrect trace information must be corrected when the chain does upgrade to fully supporting denominations with slashes. + +To do so, chain binaries should include a migration script that will run when the chain upgrades from not supporting base denominations with slashes to supporting base denominations with slashes. + +## Chains + +### ICS20 - Transfer + +The transfer module will now support slashes in base denoms, so we must iterate over current traces to check if any of them are incorrectly formed and correct the trace information. + +### Upgrade Proposal + +```go +app.UpgradeKeeper.SetUpgradeHandler("MigrateTraces", + func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + // transfer module consensus version has been bumped to 2 + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) + +``` + +This is only necessary if there are denom traces in the store with incorrect trace information from previously received coins that had a slash in the base denom. However, it is recommended that any chain upgrading to support base denominations with slashes runs this code for safety. + +For a more detailed sample, please check out the code changes in [this pull request](https://github.com/cosmos/ibc-go/pull/1680). + +### Genesis Migration + +If the chain chooses to add support for slashes in base denoms via genesis export, then the trace information must be corrected during genesis migration. + +The migration code required may look like: + +```go +func migrateGenesisSlashedDenomsUpgrade(appState genutiltypes.AppMap, clientCtx client.Context, genDoc *tmtypes.GenesisDoc) (genutiltypes.AppMap, error) { + if appState[ibctransfertypes.ModuleName] != nil { + transferGenState := &ibctransfertypes.GenesisState{} + clientCtx.Codec.MustUnmarshalJSON(appState[ibctransfertypes.ModuleName], transferGenState) + + substituteTraces := make([]ibctransfertypes.DenomTrace, len(transferGenState.DenomTraces)) + for i, dt := range transferGenState.DenomTraces { + // replace all previous traces with the latest trace if validation passes + // note most traces will have same value + newTrace := ibctransfertypes.ParseDenomTrace(dt.GetFullDenomPath()) + + if err := newTrace.Validate(); err != nil { + substituteTraces[i] = dt + } else { + substituteTraces[i] = newTrace + } + } + + transferGenState.DenomTraces = substituteTraces + + // delete old genesis state + delete(appState, ibctransfertypes.ModuleName) + + // set new ibc transfer genesis state + appState[ibctransfertypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(transferGenState) + } + + return appState, nil +} +``` + +For a more detailed sample, please check out the code changes in [this pull request](https://github.com/cosmos/ibc-go/pull/1528). diff --git a/docs/versioned_docs/version-v4.6.x/04-migrations/02-sdk-to-v1.md b/docs/versioned_docs/version-v4.6.x/04-migrations/02-sdk-to-v1.md new file mode 100644 index 0000000..28b41ab --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/04-migrations/02-sdk-to-v1.md @@ -0,0 +1,195 @@ +--- +title: SDK v0.43 to IBC-Go v1 +sidebar_label: SDK v0.43 to IBC-Go v1 +sidebar_position: 2 +slug: /migrations/sdk-to-v1 +--- +# Migrating to ibc-go + +This file contains information on how to migrate from the IBC module contained in the SDK 0.41.x and 0.42.x lines to the IBC module in the ibc-go repository based on the 0.44 SDK version. + +## Import Changes + +The most obvious changes is import name changes. We need to change: + +- applications -> apps +- cosmos-sdk/x/ibc -> ibc-go + +On my GNU/Linux based machine I used the following commands, executed in order: + +```bash +grep -RiIl 'cosmos-sdk\/x\/ibc\/applications' | xargs sed -i 's/cosmos-sdk\/x\/ibc\/applications/ibc-go\/modules\/apps/g' +``` + +```bash +grep -RiIl 'cosmos-sdk\/x\/ibc' | xargs sed -i 's/cosmos-sdk\/x\/ibc/ibc-go\/modules/g' +``` + +ref: [explanation of the above commands](https://www.internalpointers.com/post/linux-find-and-replace-text-multiple-files) + +Executing these commands out of order will cause issues. + +Feel free to use your own method for modifying import names. + +NOTE: Updating to the `v0.44.0` SDK release and then running `go mod tidy` will cause a downgrade to `v0.42.0` in order to support the old IBC import paths. +Update the import paths before running `go mod tidy`. + +## Chain Upgrades + +Chains may choose to upgrade via an upgrade proposal or genesis upgrades. Both in-place store migrations and genesis migrations are supported. + +**WARNING**: Please read at least the quick guide for [IBC client upgrades](../01-ibc/05-upgrades/00-intro.md) before upgrading your chain. It is highly recommended you do not change the chain-ID during an upgrade, otherwise you must follow the IBC client upgrade instructions. + +Both in-place store migrations and genesis migrations will: + +- migrate the solo machine client state from v1 to v2 protobuf definitions +- prune all solo machine consensus states +- prune all expired tendermint consensus states + +Chains must set a new connection parameter during either in place store migrations or genesis migration. The new parameter, max expected block time, is used to enforce packet processing delays on the receiving end of an IBC packet flow. Checkout the [docs](https://github.com/cosmos/ibc-go/blob/release/v1.0.x/docs/ibc/proto-docs.md#params-2) for more information. + +### In-Place Store Migrations + +The new chain binary will need to run migrations in the upgrade handler. The fromVM (previous module version) for the IBC module should be 1. This will allow migrations to be run for IBC updating the version from 1 to 2. + +Ex: + +```go +app.UpgradeKeeper.SetUpgradeHandler("my-upgrade-proposal", + func(ctx sdk.Context, _ upgradetypes.Plan, _ module.VersionMap) (module.VersionMap, error) { + // set max expected block time parameter. Replace the default with your expected value + // https://github.com/cosmos/ibc-go/blob/release/v1.0.x/docs/ibc/proto-docs.md#params-2 + app.IBCKeeper.ConnectionKeeper.SetParams(ctx, ibcconnectiontypes.DefaultParams()) + + fromVM := map[string]uint64{ + ... // other modules + "ibc": 1, + ... + } + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) + +``` + +### Genesis Migrations + +To perform genesis migrations, the following code must be added to your existing migration code. + +```go +// add imports as necessary +import ( + ibcv100 "github.com/cosmos/ibc-go/modules/core/legacy/v100" + ibchost "github.com/cosmos/ibc-go/modules/core/24-host" +) + +... + +// add in migrate cmd function +// expectedTimePerBlock is a new connection parameter +// https://github.com/cosmos/ibc-go/blob/release/v1.0.x/docs/ibc/proto-docs.md#params-2 +newGenState, err = ibcv100.MigrateGenesis(newGenState, clientCtx, *genDoc, expectedTimePerBlock) +if err != nil { + return err +} +``` + +**NOTE:** The genesis chain-id, time and height MUST be updated before migrating IBC, otherwise the tendermint consensus state will not be pruned. + +## IBC Keeper Changes + +The IBC Keeper now takes in the Upgrade Keeper. Please add the chains' Upgrade Keeper after the Staking Keeper: + +```diff + // Create IBC Keeper + app.IBCKeeper = ibckeeper.NewKeeper( +- appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, scopedIBCKeeper, ++ appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, + ) + +``` + +## Proposals + +### UpdateClientProposal + +The `UpdateClient` has been modified to take in two client-identifiers and one initial height. Please see the [documentation](../01-ibc/06-proposals.md) for more information. + +### UpgradeProposal + +A new IBC proposal type has been added, `UpgradeProposal`. This handles an IBC (breaking) Upgrade. +The previous `UpgradedClientState` field in an Upgrade `Plan` has been deprecated in favor of this new proposal type. + +### Proposal Handler Registration + +The `ClientUpdateProposalHandler` has been renamed to `ClientProposalHandler`. +It handles both `UpdateClientProposal`s and `UpgradeProposal`s. + +Add this import: + +```diff ++ ibcclienttypes "github.com/cosmos/ibc-go/modules/core/02-client/types" +``` + +Please ensure the governance module adds the correct route: + +```diff +- AddRoute(ibchost.RouterKey, ibcclient.NewClientUpdateProposalHandler(app.IBCKeeper.ClientKeeper)) ++ AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)) +``` + +NOTE: Simapp registration was incorrect in the 0.41.x releases. The `UpdateClient` proposal handler should be registered with the router key belonging to `ibc-go/core/02-client/types` +as shown in the diffs above. + +### Proposal CLI Registration + +Please ensure both proposal type CLI commands are registered on the governance module by adding the following arguments to `gov.NewAppModuleBasic()`: + +Add the following import: + +```diff ++ ibcclientclient "github.com/cosmos/ibc-go/modules/core/02-client/client" +``` + +Register the cli commands: + +```diff + gov.NewAppModuleBasic( + paramsclient.ProposalHandler, distrclient.ProposalHandler, upgradeclient.ProposalHandler, upgradeclient.CancelProposalHandler, ++ ibcclientclient.UpdateClientProposalHandler, ibcclientclient.UpgradeProposalHandler, + ), +``` + +REST routes are not supported for these proposals. + +## Proto file changes + +The gRPC querier service endpoints have changed slightly. The previous files used `v1beta1` gRPC route, this has been updated to `v1`. + +The solo machine has replaced the FrozenSequence uint64 field with a IsFrozen boolean field. The package has been bumped from `v1` to `v2` + +## IBC callback changes + +### OnRecvPacket + +Application developers need to update their `OnRecvPacket` callback logic. + +The `OnRecvPacket` callback has been modified to only return the acknowledgement. The acknowledgement returned must implement the `Acknowledgement` interface. The acknowledgement should indicate if it represents a successful processing of a packet by returning true on `Success()` and false in all other cases. A return value of false on `Success()` will result in all state changes which occurred in the callback being discarded. More information can be found in the [documentation](../01-ibc/03-apps/02-ibcmodule.md#receiving-packets). + +The `OnRecvPacket`, `OnAcknowledgementPacket`, and `OnTimeoutPacket` callbacks are now passed the `sdk.AccAddress` of the relayer who relayed the IBC packet. Applications may use or ignore this information. + +## IBC Event changes + +The `packet_data` attribute has been deprecated in favor of `packet_data_hex`, in order to provide standardized encoding/decoding of packet data in events. While the `packet_data` event still exists, all relayers and IBC Event consumers are strongly encouraged to switch over to using `packet_data_hex` as soon as possible. + +The `packet_ack` attribute has also been deprecated in favor of `packet_ack_hex` for the same reason stated above. All relayers and IBC Event consumers are strongly encouraged to switch over to using `packet_ack_hex` as soon as possible. + +The `consensus_height` attribute has been removed in the Misbehaviour event emitted. IBC clients no longer have a frozen height and misbehaviour does not necessarily have an associated height. + +## Relevant SDK changes + +- (codec) [\#9226](https://github.com/cosmos/cosmos-sdk/pull/9226) Rename codec interfaces and methods, to follow a general Go interfaces: + - `codec.Marshaler` → `codec.Codec` (this defines objects which serialize other objects) + - `codec.BinaryMarshaler` → `codec.BinaryCodec` + - `codec.JSONMarshaler` → `codec.JSONCodec` + - Removed `BinaryBare` suffix from `BinaryCodec` methods (`MarshalBinaryBare`, `UnmarshalBinaryBare`, ...) + - Removed `Binary` infix from `BinaryCodec` methods (`MarshalBinaryLengthPrefixed`, `UnmarshalBinaryLengthPrefixed`, ...) diff --git a/docs/versioned_docs/version-v4.6.x/04-migrations/03-v1-to-v2.md b/docs/versioned_docs/version-v4.6.x/04-migrations/03-v1-to-v2.md new file mode 100644 index 0000000..39fa738 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/04-migrations/03-v1-to-v2.md @@ -0,0 +1,60 @@ +--- +title: IBC-Go v1 to v2 +sidebar_label: IBC-Go v1 to v2 +sidebar_position: 3 +slug: /migrations/v1-to-v2 +--- +# Migrating from ibc-go v1 to v2 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go -> github.com/cosmos/ibc-go/v2 +``` + +## Chains + +- No relevant changes were made in this release. + +## IBC Apps + +A new function has been added to the app module interface: + +```go +// NegotiateAppVersion performs application version negotiation given the provided channel ordering, connectionID, portID, counterparty and proposed version. + // An error is returned if version negotiation cannot be performed. For example, an application module implementing this interface + // may decide to return an error in the event of the proposed version being incompatible with it's own + NegotiateAppVersion( + ctx sdk.Context, + order channeltypes.Order, + connectionID string, + portID string, + counterparty channeltypes.Counterparty, + proposedVersion string, + ) (version string, err error) +} +``` + +This function should perform application version negotiation and return the negotiated version. If the version cannot be negotiated, an error should be returned. This function is only used on the client side. + +### sdk.Result removed + +sdk.Result has been removed as a return value in the application callbacks. Previously it was being discarded by core IBC and was thus unused. + +## Relayers + +A new gRPC has been added to 05-port, `AppVersion`. It returns the negotiated app version. This function should be used for the `ChanOpenTry` channel handshake step to decide upon the application version which should be set in the channel. + +## IBC Light Clients + +- No relevant changes were made in this release. diff --git a/docs/versioned_docs/version-v4.6.x/04-migrations/04-v2-to-v3.md b/docs/versioned_docs/version-v4.6.x/04-migrations/04-v2-to-v3.md new file mode 100644 index 0000000..2efc9fa --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/04-migrations/04-v2-to-v3.md @@ -0,0 +1,186 @@ +--- +title: IBC-Go v2 to v3 +sidebar_label: IBC-Go v2 to v3 +sidebar_position: 4 +slug: /migrations/v2-to-v3 +--- +# Migrating from ibc-go v2 to v3 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go/v2 -> github.com/cosmos/ibc-go/v3 +``` + +No genesis or in-place migrations are required when upgrading from v1 or v2 of ibc-go. + +## Chains + +### ICS20 + +The `transferkeeper.NewKeeper(...)` now takes in an ICS4Wrapper. +The ICS4Wrapper should be the IBC Channel Keeper unless ICS 20 is being connected to a middleware application. + +### ICS27 + +ICS27 Interchain Accounts has been added as a supported IBC application of ibc-go. +Please see the [ICS27 documentation](../02-apps/02-interchain-accounts/01-overview.md) for more information. + +### Upgrade Proposal + +If the chain will adopt ICS27, it must set the appropriate params during the execution of the upgrade handler in `app.go`: + +```go +app.UpgradeKeeper.SetUpgradeHandler("v3", + func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + // set the ICS27 consensus version so InitGenesis is not run + fromVM[icatypes.ModuleName] = icamodule.ConsensusVersion() + + // create ICS27 Controller submodule params + controllerParams := icacontrollertypes.Params{ + ControllerEnabled: true, + } + + // create ICS27 Host submodule params + hostParams := icahosttypes.Params{ + HostEnabled: true, + AllowMessages: []string{"/cosmos.bank.v1beta1.MsgSend", ...}, + } + + // initialize ICS27 module + icamodule.InitModule(ctx, controllerParams, hostParams) + + ... + + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) + +``` + +The host and controller submodule params only need to be set if the chain integrates those submodules. +For example, if a chain chooses not to integrate a controller submodule, it may pass empty params into `InitModule`. + +#### Add `StoreUpgrades` for ICS27 module + +For ICS27 it is also necessary to [manually add store upgrades](https://docs.cosmos.network/main/learn/advanced/upgrade#add-storeupgrades-for-new-modules) for the new ICS27 module and then configure the store loader to apply those upgrades in `app.go`: + +```go +if upgradeInfo.Name == "v3" && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { + storeUpgrades := store.StoreUpgrades{ + Added: []string{icacontrollertypes.StoreKey, icahosttypes.StoreKey}, + } + + app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) +} +``` + +This ensures that the new module's stores are added to the multistore before the migrations begin. +The host and controller submodule keys only need to be added if the chain integrates those submodules. +For example, if a chain chooses not to integrate a controller submodule, it does not need to add the controller key to the `Added` field. + +### Genesis migrations + +If the chain will adopt ICS27 and chooses to upgrade via a genesis export, then the ICS27 parameters must be set during genesis migration. + +The migration code required may look like: + +```go + controllerGenesisState := icatypes.DefaultControllerGenesis() + // overwrite parameters as desired + controllerGenesisState.Params = icacontrollertypes.Params{ + ControllerEnabled: true, + } + + hostGenesisState := icatypes.DefaultHostGenesis() + // overwrite parameters as desired + hostGenesisState.Params = icahosttypes.Params{ + HostEnabled: true, + AllowMessages: []string{"/cosmos.bank.v1beta1.MsgSend", ...}, + } + + icaGenesisState := icatypes.NewGenesisState(controllerGenesisState, hostGenesisState) + + // set new ics27 genesis state + appState[icatypes.ModuleName] = clientCtx.JSONCodec.MustMarshalJSON(icaGenesisState) +``` + +### Ante decorator + +The field of type `channelkeeper.Keeper` in the `AnteDecorator` structure has been replaced with a field of type `*keeper.Keeper`: + +```diff +type AnteDecorator struct { +- k channelkeeper.Keeper ++ k *keeper.Keeper +} + +- func NewAnteDecorator(k channelkeeper.Keeper) AnteDecorator { ++ func NewAnteDecorator(k *keeper.Keeper) AnteDecorator { + return AnteDecorator{k: k} +} +``` + +## IBC Apps + +### `OnChanOpenTry` must return negotiated application version + +The `OnChanOpenTry` application callback has been modified. +The return signature now includes the application version. +IBC applications must perform application version negotiation in `OnChanOpenTry` using the counterparty version. +The negotiated application version then must be returned in `OnChanOpenTry` to core IBC. +Core IBC will set this version in the TRYOPEN channel. + +### `OnChanOpenAck` will take additional `counterpartyChannelID` argument + +The `OnChanOpenAck` application callback has been modified. +The arguments now include the counterparty channel id. + +### `NegotiateAppVersion` removed from `IBCModule` interface + +Previously this logic was handled by the `NegotiateAppVersion` function. +Relayers would query this function before calling `ChanOpenTry`. +Applications would then need to verify that the passed in version was correct. +Now applications will perform this version negotiation during the channel handshake, thus removing the need for `NegotiateAppVersion`. + +### Channel state will not be set before application callback + +The channel handshake logic has been reorganized within core IBC. +Channel state will not be set in state after the application callback is performed. +Applications must rely only on the passed in channel parameters instead of querying the channel keeper for channel state. + +### IBC application callbacks moved from `AppModule` to `IBCModule` + +Previously, IBC module callbacks were apart of the `AppModule` type. +The recommended approach is to create an `IBCModule` type and move the IBC module callbacks from `AppModule` to `IBCModule` in a separate file `ibc_module.go`. + +The mock module go API has been broken in this release by applying the above format. +The IBC module callbacks have been moved from the mock modules `AppModule` into a new type `IBCModule`. + +As apart of this release, the mock module now supports middleware testing. Please see the [README](https://github.com/cosmos/ibc-go/blob/v4.4.0/testing/README.md#middleware-testing) for more information. + +Please review the [mock](https://github.com/cosmos/ibc-go/blob/v4.4.0/testing/mock/ibc_module.go) and [transfer](https://github.com/cosmos/ibc-go/blob/v4.4.0/modules/apps/transfer/ibc_module.go) modules as examples. Additionally, [simapp](https://github.com/cosmos/ibc-go/blob/v4.4.0/testing/simapp/app.go) provides an example of how `IBCModule` types should now be added to the IBC router in favour of `AppModule`. + +### IBC testing package + +`TestChain`s are now created with chainID's beginning from an index of 1. Any calls to `GetChainID(0)` will now fail. Please increment all calls to `GetChainID` by 1. + +## Relayers + +`AppVersion` gRPC has been removed. +The `version` string in `MsgChanOpenTry` has been deprecated and will be ignored by core IBC. +Relayers no longer need to determine the version to use on the `ChanOpenTry` step. +IBC applications will determine the correct version using the counterparty version. + +## IBC Light Clients + +The `GetProofSpecs` function has been removed from the `ClientState` interface. This function was previously unused by core IBC. Light clients which don't use this function may remove it. diff --git a/docs/versioned_docs/version-v4.6.x/04-migrations/05-v3-to-v4.md b/docs/versioned_docs/version-v4.6.x/04-migrations/05-v3-to-v4.md new file mode 100644 index 0000000..15b65af --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/04-migrations/05-v3-to-v4.md @@ -0,0 +1,160 @@ +--- +title: IBC-Go v3 to v4 +sidebar_label: IBC-Go v3 to v4 +sidebar_position: 5 +slug: /migrations/v3-to-v4 +--- +# Migrating from ibc-go v3 to v4 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go/v3 -> github.com/cosmos/ibc-go/v4 +``` + +No genesis or in-place migrations required when upgrading from v1 or v2 of ibc-go. + +## Chains + +### ICS27 - Interchain Accounts + +The controller submodule implements now the 05-port `Middleware` interface instead of the 05-port `IBCModule` interface. Chains that integrate the controller submodule, need to create it with the `NewIBCMiddleware` constructor function. For example: + +```diff +- icacontroller.NewIBCModule(app.ICAControllerKeeper, icaAuthIBCModule) ++ icacontroller.NewIBCMiddleware(icaAuthIBCModule, app.ICAControllerKeeper) +``` + +where `icaAuthIBCModule` is the Interchain Accounts authentication IBC Module. + +### ICS29 - Fee Middleware + +The Fee Middleware module, as the name suggests, plays the role of an IBC middleware and as such must be configured by chain developers to route and handle IBC messages correctly. + +Please read the Fee Middleware [integration documentation](../03-middleware/01-ics29-fee/02-integration.md) for an in depth guide on how to configure the module correctly in order to incentivize IBC packets. + +Take a look at the following diff for an [example setup](https://github.com/cosmos/ibc-go/pull/1432/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08aL366) of how to incentivize ics27 channels. + +### Migration to fix support for base denoms with slashes + +As part of [v1.5.0](https://github.com/cosmos/ibc-go/releases/tag/v1.5.0), [v2.3.0](https://github.com/cosmos/ibc-go/releases/tag/v2.3.0) and [v3.1.0](https://github.com/cosmos/ibc-go/releases/tag/v3.1.0) some [migration handler code sample was documented](./01-support-denoms-with-slashes.md#upgrade-proposal) that needs to run in order to correct the trace information of coins transferred using ICS20 whose base denom contains slashes. + +Based on feedback from the community we add now an improved solution to run the same migration that does not require copying a large piece of code over from the migration document, but instead requires only adding a one-line upgrade handler. + +If the chain will migrate to supporting base denoms with slashes, it must set the appropriate params during the execution of the upgrade handler in `app.go`: + +```go +app.UpgradeKeeper.SetUpgradeHandler("MigrateTraces", + func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + // transfer module consensus version has been bumped to 2 + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) + +``` + +If a chain receives coins of a base denom with slashes before it upgrades to supporting it, the receive may pass however the trace information will be incorrect. + +E.g. If a base denom of `testcoin/testcoin/testcoin` is sent to a chain that does not support slashes in the base denom, the receive will be successful. However, the trace information stored on the receiving chain will be: `Trace: "transfer/{channel-id}/testcoin/testcoin", BaseDenom: "testcoin"`. + +This incorrect trace information must be corrected when the chain does upgrade to fully supporting denominations with slashes. + +## IBC Apps + +### ICS03 - Connection + +Crossing hellos have been removed from 03-connection handshake negotiation. +`PreviousConnectionId` in `MsgConnectionOpenTry` has been deprecated and is no longer used by core IBC. + +`NewMsgConnectionOpenTry` no longer takes in the `PreviousConnectionId` as crossing hellos are no longer supported. A non-empty `PreviousConnectionId` will fail basic validation for this message. + +### ICS04 - Channel + +The `WriteAcknowledgement` API now takes the `exported.Acknowledgement` type instead of passing in the acknowledgement byte array directly. +This is an API breaking change and as such IBC application developers will have to update any calls to `WriteAcknowledgement`. + +The `OnChanOpenInit` application callback has been modified. +The return signature now includes the application version as detailed in the latest IBC [spec changes](https://github.com/cosmos/ibc/pull/629). + +The `NewErrorAcknowledgement` method signature has changed. +It now accepts an `error` rather than a `string`. This was done in order to prevent accidental state changes. +All error acknowledgements now contain a deterministic ABCI code and error message. It is the responsibility of the application developer to emit error details in events. + +Crossing hellos have been removed from 04-channel handshake negotiation. +IBC Applications no longer need to account from already claimed capabilities in the `OnChanOpenTry` callback. The capability provided by core IBC must be able to be claimed with error. +`PreviousChannelId` in `MsgChannelOpenTry` has been deprecated and is no longer used by core IBC. + +`NewMsgChannelOpenTry` no longer takes in the `PreviousChannelId` as crossing hellos are no longer supported. A non-empty `PreviousChannelId` will fail basic validation for this message. + +### ICS27 - Interchain Accounts + +The `RegisterInterchainAccount` API has been modified to include an additional `version` argument. This change has been made in order to support ICS29 fee middleware, for relayer incentivization of ICS27 packets. +Consumers of the `RegisterInterchainAccount` are now expected to build the appropriate JSON encoded version string themselves and pass it accordingly. +This should be constructed within the interchain accounts authentication module which leverages the APIs exposed via the interchain accounts `controllerKeeper`. If an empty string is passed in the `version` argument, then the version will be initialized to a default value in the `OnChanOpenInit` callback of the controller's handler, so that channel handshake can proceed. + +The following code snippet illustrates how to construct an appropriate interchain accounts `Metadata` and encode it as a JSON bytestring: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +if err := k.icaControllerKeeper.RegisterInterchainAccount(ctx, msg.ConnectionId, msg.Owner, string(appVersion)); err != nil { + return err +} +``` + +Similarly, if the application stack is configured to route through ICS29 fee middleware and a fee enabled channel is desired, construct the appropriate ICS29 `Metadata` type: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +feeMetadata := feetypes.Metadata{ + AppVersion: string(appVersion), + FeeVersion: feetypes.Version, +} + +feeEnabledVersion, err := feetypes.ModuleCdc.MarshalJSON(&feeMetadata) +if err != nil { + return err +} + +if err := k.icaControllerKeeper.RegisterInterchainAccount(ctx, msg.ConnectionId, msg.Owner, string(feeEnabledVersion)); err != nil { + return err +} +``` + +## Relayers + +When using the `DenomTrace` gRPC, the full IBC denomination with the `ibc/` prefix may now be passed in. + +Crossing hellos are no longer supported by core IBC for 03-connection and 04-channel. The handshake should be completed in the logical 4 step process (INIT, TRY, ACK, CONFIRM). diff --git a/docs/versioned_docs/version-v4.6.x/04-migrations/_category_.json b/docs/versioned_docs/version-v4.6.x/04-migrations/_category_.json new file mode 100644 index 0000000..617def6 --- /dev/null +++ b/docs/versioned_docs/version-v4.6.x/04-migrations/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Migrations", + "position": 4, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v5.4.x/00-intro.md b/docs/versioned_docs/version-v5.4.x/00-intro.md new file mode 100644 index 0000000..e053110 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/00-intro.md @@ -0,0 +1,20 @@ +--- +slug: / +sidebar_position: 0 +--- + +:::danger +This version of ibc-go is not supported anymore. Please upgrade to the latest version. +::: + +# IBC-Go Documentation + +Welcome to the IBC-Go documentation! + +The Inter-Blockchain Communication protocol (IBC) is an end-to-end, connection-oriented, stateful protocol for reliable, ordered, and authenticated communication between heterogeneous blockchains arranged in an unknown and dynamic topology. + +IBC is a protocol that allows blockchains to talk to each other. + +The protocol realizes this interoperability by specifying a set of data structures, abstractions, and semantics that can be implemented by any distributed ledger that satisfies a small set of requirements. + +IBC can be used to build a wide range of cross-chain applications that include token transfers, atomic swaps, multi-chain smart contracts (with or without mutually comprehensible VMs), and data and code sharding of various kinds. diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/01-overview.md b/docs/versioned_docs/version-v5.4.x/01-ibc/01-overview.md new file mode 100644 index 0000000..4c3c0b1 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/01-ibc/01-overview.md @@ -0,0 +1,297 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /ibc/overview +--- + + +# Overview + +:::note Synopsis +Learn about IBC, its components, and IBC use cases. +::: + +## What is the Inter-Blockchain Communication Protocol (IBC)? + +This document serves as a guide for developers who want to write their own Inter-Blockchain +Communication protocol (IBC) applications for custom use cases. + +> IBC applications must be written as self-contained modules. + +Due to the modular design of the IBC protocol, IBC +application developers do not need to be concerned with the low-level details of clients, +connections, and proof verification. + +This brief explanation of the lower levels of the +stack gives application developers a broad understanding of the IBC +protocol. Abstraction layer details for channels and ports are most relevant for application developers and describe how to define custom packets and `IBCModule` callbacks. + +The requirements to have your module interact over IBC are: + +- Bind to a port or ports. +- Define your packet data. +- Use the default acknowledgment struct provided by core IBC or optionally define a custom acknowledgment struct. +- Standardize an encoding of the packet data. +- Implement the `IBCModule` interface. + +Read on for a detailed explanation of how to write a self-contained IBC application module. + +## Components Overview + +### [Clients](https://github.com/cosmos/ibc-go/blob/main/modules/core/02-client) + +IBC clients are on-chain light clients. Each light client is identified by a unique client-id. +IBC clients track the consensus states of other blockchains, along with the proof spec necessary to +properly verify proofs against the client's consensus state. A client can be associated with any number +of connections to the counterparty chain. The client identifier is auto generated using the client type +and the global client counter appended in the format: `{client-type}-{N}`. + +A `ClientState` should contain chain specific and light client specific information necessary for verifying updates +and upgrades to the IBC client. The `ClientState` may contain information such as chain-id, latest height, proof specs, +unbonding periods or the status of the light client. The `ClientState` should not contain information that +is specific to a given block at a certain height, this is the function of the `ConsensusState`. Each `ConsensusState` +should be associated with a unique block and should be referenced using a height. IBC clients are given a +client identifier prefixed store to store their associated client state and consensus states along with +any metadata associated with the consensus states. Consensus states are stored using their associated height. + +The supported IBC clients are: + +- [Solo Machine light client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/06-solomachine): Devices such as phones, browsers, or laptops. +- [Tendermint light client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/07-tendermint): The default for Cosmos SDK-based chains. +- [Localhost (loopback) client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/09-localhost): Useful for +testing, simulation, and relaying packets to modules on the same application. + +### IBC Client Heights + +IBC Client Heights are represented by the struct: + +```go +type Height struct { + RevisionNumber uint64 + RevisionHeight uint64 +} +``` + +The `RevisionNumber` represents the revision of the chain that the height is representing. +A revision typically represents a continuous, monotonically increasing range of block-heights. +The `RevisionHeight` represents the height of the chain within the given revision. + +On any reset of the `RevisionHeight`—for example, when hard-forking a Tendermint chain— +the `RevisionNumber` will get incremented. This allows IBC clients to distinguish between a +block-height `n` of a previous revision of the chain (at revision `p`) and block-height `n` of the current +revision of the chain (at revision `e`). + +`Height`s that share the same revision number can be compared by simply comparing their respective `RevisionHeight`s. +`Height`s that do not share the same revision number will only be compared using their respective `RevisionNumber`s. +Thus a height `h` with revision number `e+1` will always be greater than a height `g` with revision number `e`, +**REGARDLESS** of the difference in revision heights. + +Ex: + +```go +Height{RevisionNumber: 3, RevisionHeight: 0} > Height{RevisionNumber: 2, RevisionHeight: 100000000000} +``` + +When a Tendermint chain is running a particular revision, relayers can simply submit headers and proofs with the revision number +given by the chain's `chainID`, and the revision height given by the Tendermint block height. When a chain updates using a hard-fork +and resets its block-height, it is responsible for updating its `chainID` to increment the revision number. +IBC Tendermint clients then verifies the revision number against their `chainID` and treat the `RevisionHeight` as the Tendermint block-height. + +Tendermint chains wishing to use revisions to maintain persistent IBC connections even across height-resetting upgrades must format their `chainID`s +in the following manner: `{chainID}-{revision_number}`. On any height-resetting upgrade, the `chainID` **MUST** be updated with a higher revision number +than the previous value. + +Ex: + +- Before upgrade `chainID`: `gaiamainnet-3` +- After upgrade `chainID`: `gaiamainnet-4` + +Clients that do not require revisions, such as the solo-machine client, simply hardcode `0` into the revision number whenever they +need to return an IBC height when implementing IBC interfaces and use the `RevisionHeight` exclusively. + +Other client-types can implement their own logic to verify the IBC heights that relayers provide in their `Update`, `Misbehavior`, and +`Verify` functions respectively. + +The IBC interfaces expect an `ibcexported.Height` interface, however all clients must use the concrete implementation provided in +`02-client/types` and reproduced above. + +### [Connections](https://github.com/cosmos/ibc-go/blob/main/modules/core/03-connection) + +Connections encapsulate two `ConnectionEnd` objects on two separate blockchains. Each +`ConnectionEnd` is associated with a client of the other blockchain (for example, the counterparty blockchain). +The connection handshake is responsible for verifying that the light clients on each chain are +correct for their respective counterparties. Connections, once established, are responsible for +facilitating all cross-chain verifications of IBC state. A connection can be associated with any +number of channels. + +### [Proofs](https://github.com/cosmos/ibc-go/blob/main/modules/core/23-commitment) and [Paths](https://github.com/cosmos/ibc-go/blob/main/modules/core/24-host) + +In IBC, blockchains do not directly pass messages to each other over the network. Instead, to +communicate, a blockchain commits some state to a specifically defined path that is reserved for a +specific message type and a specific counterparty. For example, for storing a specific connectionEnd as part +of a handshake or a packet intended to be relayed to a module on the counterparty chain. A relayer +process monitors for updates to these paths and relays messages by submitting the data stored +under the path and a proof to the counterparty chain. + +Proofs are passed from core IBC to light-clients as bytes. It is up to light client implementation to interpret these bytes appropriately. + +- The paths that all IBC implementations must use for committing IBC messages is defined in +[ICS-24 Host State Machine Requirements](https://github.com/cosmos/ibc/tree/master/spec/core/ics-024-host-requirements). +- The proof format that all implementations must be able to produce and verify is defined in [ICS-23 Proofs](https://github.com/confio/ics23) implementation. + +### Capabilities + +IBC is intended to work in execution environments where modules do not necessarily trust each +other. Thus, IBC must authenticate module actions on ports and channels so that only modules with the +appropriate permissions can use them. + +This module authentication is accomplished using a [dynamic +capability store](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-003-dynamic-capability-store.md). Upon binding to a port or +creating a channel for a module, IBC returns a dynamic capability that the module must claim in +order to use that port or channel. The dynamic capability module prevents other modules from using that port or channel since +they do not own the appropriate capability. + +While this background information is useful, IBC modules do not need to interact at all with +these lower-level abstractions. The relevant abstraction layer for IBC application developers is +that of channels and ports. IBC applications must be written as self-contained **modules**. + +A module on one blockchain can communicate with other modules on other blockchains by sending, +receiving, and acknowledging packets through channels that are uniquely identified by the +`(channelID, portID)` tuple. + +A useful analogy is to consider IBC modules as internet applications on +a computer. A channel can then be conceptualized as an IP connection, with the IBC portID being +analogous to an IP port and the IBC channelID being analogous to an IP address. Thus, a single +instance of an IBC module can communicate on the same port with any number of other modules and +IBC correctly routes all packets to the relevant module using the (channelID, portID tuple). An +IBC module can also communicate with another IBC module over multiple ports, with each +`(portID<->portID)` packet stream being sent on a different unique channel. + +### [Ports](https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port) + +An IBC module can bind to any number of ports. Each port must be identified by a unique `portID`. +Since IBC is designed to be secure with mutually distrusted modules operating on the same ledger, +binding a port returns a dynamic object capability. In order to take action on a particular port +(for example, an open channel with its portID), a module must provide the dynamic object capability to the IBC +handler. This requirement prevents a malicious module from opening channels with ports it does not own. Thus, +IBC modules are responsible for claiming the capability that is returned on `BindPort`. + +### [Channels](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +An IBC channel can be established between two IBC ports. Currently, a port is exclusively owned by a +single module. IBC packets are sent over channels. Just as IP packets contain the destination IP +address and IP port, and the source IP address and source IP port, IBC packets contain +the destination portID and channelID, and the source portID and channelID. This packet structure enables IBC to +correctly route packets to the destination module while allowing modules receiving packets to +know the sender module. + +A channel can be `ORDERED`, where packets from a sending module must be processed by the +receiving module in the order they were sent. Or a channel can be `UNORDERED`, where packets +from a sending module are processed in the order they arrive (might be in a different order than they were sent). + +Modules can choose which channels they wish to communicate over with, thus IBC expects modules to +implement callbacks that are called during the channel handshake. These callbacks can do custom +channel initialization logic. If any callback returns an error, the channel handshake fails. Thus, by +returning errors on callbacks, modules can programmatically reject and accept channels. + +The channel handshake is a 4-step handshake. Briefly, if a given chain A wants to open a channel with +chain B using an already established connection: + +1. chain A sends a `ChanOpenInit` message to signal a channel initialization attempt with chain B. +2. chain B sends a `ChanOpenTry` message to try opening the channel on chain A. +3. chain A sends a `ChanOpenAck` message to mark its channel end status as open. +4. chain B sends a `ChanOpenConfirm` message to mark its channel end status as open. + +If all handshake steps are successful, the channel is opened on both sides. At each step in the handshake, the module +associated with the `ChannelEnd` executes its callback. So +on `ChanOpenInit`, the module on chain A executes its callback `OnChanOpenInit`. + +The channel identifier is auto derived in the format: `channel-{N}` where N is the next sequence to be used. + +Just as ports came with dynamic capabilities, channel initialization returns a dynamic capability +that the module **must** claim so that they can pass in a capability to authenticate channel actions +like sending packets. The channel capability is passed into the callback on the first parts of the +handshake; either `OnChanOpenInit` on the initializing chain or `OnChanOpenTry` on the other chain. + +#### Closing channels + +Closing a channel occurs in 2 handshake steps as defined in [ICS 04](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics). + +`ChanCloseInit` closes a channel on the executing chain if the channel exists, it is not +already closed and the connection it exists upon is OPEN. Channels can only be closed by a +calling module or in the case of a packet timeout on an ORDERED channel. + +`ChanCloseConfirm` is a response to a counterparty channel executing `ChanCloseInit`. The channel +on the executing chain closes if the channel exists, the channel is not already closed, +the connection the channel exists upon is OPEN and the executing chain successfully verifies +that the counterparty channel has been closed. + +### [Packets](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +Modules communicate with each other by sending packets over IBC channels. All +IBC packets contain the destination `portID` and `channelID` along with the source `portID` and +`channelID`. This packet structure allows modules to know the sender module of a given packet. IBC packets +contain a sequence to optionally enforce ordering. + +IBC packets also contain a `TimeoutHeight` and a `TimeoutTimestamp` that determine the deadline before the receiving module must process a packet. + +Modules send custom application data to each other inside the `Data []byte` field of the IBC packet. +Thus, packet data is opaque to IBC handlers. It is incumbent on a sender module to encode +their application-specific packet information into the `Data` field of packets. The receiver +module must decode that `Data` back to the original application data. + +### [Receipts and Timeouts](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +Since IBC works over a distributed network and relies on potentially faulty relayers to relay messages between ledgers, +IBC must handle the case where a packet does not get sent to its destination in a timely manner or at all. Packets must +specify a non-zero value for timeout height (`TimeoutHeight`) or timeout timestamp (`TimeoutTimestamp` ) after which a packet can no longer be successfully received on the destination chain. + +- The `timeoutHeight` indicates a consensus height on the destination chain after which the packet is no longer be processed, and instead counts as having timed-out. +- The `timeoutTimestamp` indicates a timestamp on the destination chain after which the packet is no longer be processed, and instead counts as having timed-out. + +If the timeout passes without the packet being successfully received, the packet can no longer be +received on the destination chain. The sending module can timeout the packet and take appropriate actions. + +If the timeout is reached, then a proof of packet timeout can be submitted to the original chain. The original chain can then perform +application-specific logic to timeout the packet, perhaps by rolling back the packet send changes (refunding senders any locked funds, etc.). + +- In ORDERED channels, a timeout of a single packet in the channel causes the channel to close. + + - If packet sequence `n` times out, then a packet at sequence `k > n` cannot be received without violating the contract of ORDERED channels that packets are processed in the order that they are sent. + - Since ORDERED channels enforce this invariant, a proof that sequence `n` has not been received on the destination chain by the specified timeout of packet `n` is sufficient to timeout packet `n` and close the channel. + +- In UNORDERED channels, the application-specific timeout logic for that packet is applied and the channel is not closed. + + - Packets can be received in any order. + + - IBC writes a packet receipt for each sequence receives in the UNORDERED channel. This receipt does not contain information; it is simply a marker intended to signify that the UNORDERED channel has received a packet at the specified sequence. + + - To timeout a packet on an UNORDERED channel, a proof is required that a packet receipt **does not exist** for the packet's sequence by the specified timeout. + +For this reason, most modules should use UNORDERED channels as they require fewer liveness guarantees to function effectively for users of that channel. + +### [Acknowledgments](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +Modules can also choose to write application-specific acknowledgments upon processing a packet. Acknowledgments can be done: + +- Synchronously on `OnRecvPacket` if the module processes packets as soon as they are received from IBC module. +- Asynchronously if module processes packets at some later point after receiving the packet. + +This acknowledgment data is opaque to IBC much like the packet `Data` and is treated by IBC as a simple byte string `[]byte`. Receiver modules must encode their acknowledgment so that the sender module can decode it correctly. The encoding must be negotiated between the two parties during version negotiation in the channel handshake. + +The acknowledgment can encode whether the packet processing succeeded or failed, along with additional information that allows the sender module to take appropriate action. + +After the acknowledgment has been written by the receiving chain, a relayer relays the acknowledgment back to the original sender module. + +The original sender module then executes application-specific acknowledgment logic using the contents of the acknowledgment. + +- After an acknowledgement fails, packet-send changes can be rolled back (for example, refunding senders in ICS20). + +- After an acknowledgment is received successfully on the original sender on the chain, the corresponding packet commitment is deleted since it is no longer needed. + +## Further Readings and Specs + +If you want to learn more about IBC, check the following specifications: + +- [IBC specification overview](https://github.com/cosmos/ibc/blob/master/README.md) diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/02-integration.md b/docs/versioned_docs/version-v5.4.x/01-ibc/02-integration.md new file mode 100644 index 0000000..9148547 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/01-ibc/02-integration.md @@ -0,0 +1,222 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /ibc/integration +--- + +# Integration + +:::note Synopsis +Learn how to integrate IBC to your application and send data packets to other chains. +::: + +This document outlines the required steps to integrate and configure the [IBC +module](https://github.com/cosmos/ibc-go/tree/main/modules/core) to your Cosmos SDK application and +send fungible token transfers to other chains. + +## Integrating the IBC module + +Integrating the IBC module to your SDK-based application is straightforward. The general changes can be summarized in the following steps: + +- Add required modules to the `module.BasicManager` +- Define additional `Keeper` fields for the new modules on the `App` type +- Add the module's `StoreKeys` and initialize their `Keepers` +- Set up corresponding routers and routes for the `ibc` module +- Add the modules to the module `Manager` +- Add modules to `Begin/EndBlockers` and `InitGenesis` +- Update the module `SimulationManager` to enable simulations + +### Module `BasicManager` and `ModuleAccount` permissions + +The first step is to add the following modules to the `BasicManager`: `x/capability`, `x/ibc`, +and `x/ibc-transfer`. After that, we need to grant `Minter` and `Burner` permissions to +the `ibc-transfer` `ModuleAccount` to mint and burn relayed tokens. + +```go +// app.go +var ( + + ModuleBasics = module.NewBasicManager( + // ... + capability.AppModuleBasic{}, + ibc.AppModuleBasic{}, + transfer.AppModuleBasic{}, // i.e ibc-transfer module + ) + + // module account permissions + maccPerms = map[string][]string{ + // other module accounts permissions + // ... + ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner}, +) +``` + +### Application fields + +Then, we need to register the `Keepers` as follows: + +```go +// app.go +type App struct { + // baseapp, keys and subspaces definitions + + // other keepers + // ... + IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly + TransferKeeper ibctransferkeeper.Keeper // for cross-chain fungible token transfers + + // make scoped keepers public for test purposes + ScopedIBCKeeper capabilitykeeper.ScopedKeeper + ScopedTransferKeeper capabilitykeeper.ScopedKeeper + + /// ... + /// module and simulation manager definitions +} +``` + +### Configure the `Keepers` + +During initialization, besides initializing the IBC `Keepers` (for the `x/ibc`, and +`x/ibc-transfer` modules), we need to grant specific capabilities through the capability module +`ScopedKeepers` so that we can authenticate the object-capability permissions for each of the IBC +channels. + +```go +func NewApp(...args) *App { + // define codecs and baseapp + + // add capability keeper and ScopeToModule for ibc module + app.CapabilityKeeper = capabilitykeeper.NewKeeper(appCodec, keys[capabilitytypes.StoreKey], memKeys[capabilitytypes.MemStoreKey]) + + // grant capabilities for the ibc and ibc-transfer modules + scopedIBCKeeper := app.CapabilityKeeper.ScopeToModule(ibchost.ModuleName) + scopedTransferKeeper := app.CapabilityKeeper.ScopeToModule(ibctransfertypes.ModuleName) + + // ... other modules keepers + + // Create IBC Keeper + app.IBCKeeper = ibckeeper.NewKeeper( + appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, + ) + + // Create Transfer Keepers + app.TransferKeeper = ibctransferkeeper.NewKeeper( + appCodec, keys[ibctransfertypes.StoreKey], app.GetSubspace(ibctransfertypes.ModuleName), + app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.AccountKeeper, app.BankKeeper, scopedTransferKeeper, + ) + transferModule := transfer.NewAppModule(app.TransferKeeper) + + // .. continues +} +``` + +### Register `Routers` + +IBC needs to know which module is bound to which port so that it can route packets to the +appropriate module and call the appropriate callbacks. The port to module name mapping is handled by +IBC's port `Keeper`. However, the mapping from module name to the relevant callbacks is accomplished +by the port +[`Router`](https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port/types/router.go) on the +IBC module. + +Adding the module routes allows the IBC handler to call the appropriate callback when processing a +channel handshake or a packet. + +Currently, a `Router` is static so it must be initialized and set correctly on app initialization. +Once the `Router` has been set, no new routes can be added. + +```go +// app.go +func NewApp(...args) *App { + // .. continuation from above + + // Create static IBC router, add ibc-transfer module route, then set and seal it + ibcRouter := port.NewRouter() + ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferModule) + // Setting Router will finalize all routes by sealing router + // No more routes can be added + app.IBCKeeper.SetRouter(ibcRouter) + + // .. continues +``` + +### Module Managers + +In order to use IBC, we need to add the new modules to the module `Manager` and to the `SimulationManager` in case your application supports simulations. + +```go +// app.go +func NewApp(...args) *App { + // .. continuation from above + + app.mm = module.NewManager( + // other modules + // ... + capability.NewAppModule(appCodec, *app.CapabilityKeeper), + ibc.NewAppModule(app.IBCKeeper), + transferModule, + ) + + // ... + + app.sm = module.NewSimulationManager( + // other modules + // ... + capability.NewAppModule(appCodec, *app.CapabilityKeeper), + ibc.NewAppModule(app.IBCKeeper), + transferModule, + ) + + // .. continues +``` + +### Application ABCI Ordering + +One addition from IBC is the concept of `HistoricalEntries` which are stored on the staking module. +Each entry contains the historical information for the `Header` and `ValidatorSet` of this chain which is stored +at each height during the `BeginBlock` call. The historical info is required to introspect the +past historical info at any given height in order to verify the light client `ConsensusState` during the +connection handshake. + +The IBC module also has +[`BeginBlock`](https://github.com/cosmos/ibc-go/blob/main/modules/core/02-client/abci.go) logic as well. This is optional as it is only required if your application uses the localhost client to connect two different modules from the same chain. + +:::tip +Only register the ibc module to the `SetOrderBeginBlockers` if your application will use the +localhost (*aka* loopback) client. +::: + +```go +// app.go +func NewApp(...args) *App { + // .. continuation from above + + // add staking and ibc modules to BeginBlockers + app.mm.SetOrderBeginBlockers( + // other modules ... + stakingtypes.ModuleName, ibchost.ModuleName, + ) + + // ... + + // NOTE: Capability module must occur first so that it can initialize any capabilities + // so that other modules that want to create or claim capabilities afterwards in InitChain + // can do so safely. + app.mm.SetOrderInitGenesis( + capabilitytypes.ModuleName, + // other modules ... + ibchost.ModuleName, ibctransfertypes.ModuleName, + ) + + // .. continues +``` + +:::warning +**IMPORTANT**: The capability module **must** be declared first in `SetOrderInitGenesis` +::: + +That's it! You have now wired up the IBC module and are now able to send fungible tokens across +different chains. If you want to have a broader view of the changes take a look into the SDK's +[`SimApp`](https://github.com/cosmos/ibc-go/blob/main/testing/simapp/app.go). diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/01-apps.md b/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/01-apps.md new file mode 100644 index 0000000..6f1881d --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/01-apps.md @@ -0,0 +1,56 @@ +--- +title: IBC Applications +sidebar_label: IBC Applications +sidebar_position: 1 +slug: /ibc/apps/apps +--- + +# IBC Applications + +:::note Synopsis +Learn how to build custom IBC application modules that enable packets to be sent to and received from other IBC-enabled chains. +::: + +This document serves as a guide for developers who want to write their own Inter-blockchain Communication Protocol (IBC) applications for custom use cases. + +Due to the modular design of the IBC protocol, IBC application developers do not need to concern themselves with the low-level details of clients, connections, and proof verification. Nevertheless, an overview of these low-level concepts can be found in [the Overview section](../01-overview.md). +The document goes into detail on the abstraction layer most relevant for application developers (channels and ports), and describes how to define your own custom packets, `IBCModule` callbacks and more to make an application module IBC ready. + +**To have your module interact over IBC you must:** + +- implement the `IBCModule` interface, i.e.: + - channel (opening) handshake callbacks + - channel closing handshake callbacks + - packet callbacks +- bind to a port(s) +- add keeper methods +- define your own packet data and acknowledgement structs as well as how to encode/decode them +- add a route to the IBC router + +The following sections provide a more detailed explanation of how to write an IBC application +module correctly corresponding to the listed steps. + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: + +## Working example + +For a real working example of an IBC application, you can look through the `ibc-transfer` module +which implements everything discussed in this section. + +Here are the useful parts of the module to look at: + +[Binding to transfer +port](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/genesis.go) + +[Sending transfer +packets](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/relay.go) + +[Implementing IBC +callbacks](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/ibc_module.go) diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/02-ibcmodule.md b/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/02-ibcmodule.md new file mode 100644 index 0000000..c3cb0a8 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/02-ibcmodule.md @@ -0,0 +1,351 @@ +--- +title: Implement `IBCModule` interface and callbacks +sidebar_label: Implement `IBCModule` interface and callbacks +sidebar_position: 2 +slug: /ibc/apps/ibcmodule +--- + +# Implement `IBCModule` interface and callbacks + +:::note Synopsis +Learn how to implement the `IBCModule` interface and all of the callbacks it requires. +::: + +The Cosmos SDK expects all IBC modules to implement the [`IBCModule` +interface](https://github.com/cosmos/ibc-go/tree/main/modules/core/05-port/types/module.go). This interface contains all of the callbacks IBC expects modules to implement. They include callbacks related to channel handshake, closing and packet callbacks (`OnRecvPacket`, `OnAcknowledgementPacket` and `OnTimeoutPacket`). + +```go +// IBCModule implements the ICS26 interface for given the keeper. +// The implementation of the IBCModule interface could for example be in a file called ibc_module.go, +// but ultimately file structure is up to the developer +type IBCModule struct { + keeper keeper.Keeper +} +``` + +Additionally, in the `module.go` file, add the following line: + +```go +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + // Add this line + _ porttypes.IBCModule = IBCModule{} +) +``` + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: + +## Channel handshake callbacks + +This section will describe the callbacks that are called during channel handshake execution. Among other things, it will claim channel capabilities passed on from core IBC. For a refresher on capabilities, check [the Overview section](../01-overview.md#capabilities). + +Here are the channel handshake callbacks that modules are expected to implement: + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `checkArguments` and `negotiateAppVersion` functions. + +```go +// Called by IBC Handler on MsgOpenInit +func (im IBCModule) OnChanOpenInit(ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + // Examples: + // - Abort if order == UNORDERED, + // - Abort if version is unsupported + if err := checkArguments(args); err != nil { + return "", err + } + + // OpenInit must claim the channelCapability that IBC passes into the callback + if err := im.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return "", err + } + + return version, nil +} + +// Called by IBC Handler on MsgOpenTry +func (im IBCModule) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + if err := checkArguments(args); err != nil { + return "", err + } + + // OpenTry must claim the channelCapability that IBC passes into the callback + if err := im.keeper.scopedKeeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return err + } + + // Construct application version + // IBC applications must return the appropriate application version + // This can be a simple string or it can be a complex version constructed + // from the counterpartyVersion and other arguments. + // The version returned will be the channel version used for both channel ends. + appVersion := negotiateAppVersion(counterpartyVersion, args) + + return appVersion, nil +} + +// Called by IBC Handler on MsgOpenAck +func (im IBCModule) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyVersion string, +) error { + if counterpartyVersion != types.Version { + return sdkerrors.Wrapf(types.ErrInvalidVersion, "invalid counterparty version: %s, expected %s", counterpartyVersion, types.Version) + } + + // do custom logic + + return nil +} + +// Called by IBC Handler on MsgOpenConfirm +func (im IBCModule) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // do custom logic + + return nil +} +``` + +The channel closing handshake will also invoke module callbacks that can return errors to abort the closing handshake. Closing a channel is a 2-step handshake, the initiating chain calls `ChanCloseInit` and the finalizing chain calls `ChanCloseConfirm`. + +```go +// Called by IBC Handler on MsgCloseInit +func (im IBCModule) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + // ... do custom finalization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} + +// Called by IBC Handler on MsgCloseConfirm +func (im IBCModule) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // ... do custom finalization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} +``` + +### Channel handshake version negotiation + +Application modules are expected to verify versioning used during the channel handshake procedure. + +- `OnChanOpenInit` will verify that the relayer-chosen parameters + are valid and perform any custom `INIT` logic. + It may return an error if the chosen parameters are invalid + in which case the handshake is aborted. + If the provided version string is non-empty, `OnChanOpenInit` should return + the version string if valid or an error if the provided version is invalid. + **If the version string is empty, `OnChanOpenInit` is expected to + return a default version string representing the version(s) + it supports.** + If there is no default version string for the application, + it should return an error if the provided version is an empty string. +- `OnChanOpenTry` will verify the relayer-chosen parameters along with the + counterparty-chosen version string and perform custom `TRY` logic. + If the relayer-chosen parameters + are invalid, the callback must return an error to abort the handshake. + If the counterparty-chosen version is not compatible with this module's + supported versions, the callback must return an error to abort the handshake. + If the versions are compatible, the try callback must select the final version + string and return it to core IBC. + `OnChanOpenTry` may also perform custom initialization logic. +- `OnChanOpenAck` will error if the counterparty selected version string + is invalid and abort the handshake. It may also perform custom ACK logic. + +Versions must be strings but can implement any versioning structure. If your application plans to +have linear releases then semantic versioning is recommended. If your application plans to release +various features in between major releases then it is advised to use the same versioning scheme +as IBC. This versioning scheme specifies a version identifier and compatible feature set with +that identifier. Valid version selection includes selecting a compatible version identifier with +a subset of features supported by your application for that version. The struct used for this +scheme can be found in [03-connection/types](https://github.com/cosmos/ibc-go/blob/main/modules/core/03-connection/types/version.go#L16). + +Since the version type is a string, applications have the ability to do simple version verification +via string matching or they can use the already implemented versioning system and pass the proto +encoded version into each handhshake call as necessary. + +ICS20 currently implements basic string matching with a single supported version. + +## Packet callbacks + +Just as IBC expects modules to implement callbacks for channel handshakes, it also expects modules to implement callbacks for handling the packet flow through a channel, as defined in the `IBCModule` interface. + +Once a module A and module B are connected to each other, relayers can start relaying packets and acknowledgements back and forth on the channel. + +![IBC packet flow diagram](./images/packet_flow.png) + +Briefly, a successful packet flow works as follows: + +1. module A sends a packet through the IBC module +2. the packet is received by module B +3. if module B writes an acknowledgement of the packet then module A will process the + acknowledgement +4. if the packet is not successfully received before the timeout, then module A processes the + packet's timeout. + +### Sending packets + +Modules **do not send packets through callbacks**, since the modules initiate the action of sending packets to the IBC module, as opposed to other parts of the packet flow where messages sent to the IBC +module must trigger execution on the port-bound module through the use of callbacks. Thus, to send a packet a module simply needs to call `SendPacket` on the `IBCChannelKeeper`. + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `EncodePacketData(customPacketData)` function. + +```go +// retrieve the dynamic capability for this channel +channelCap := scopedKeeper.GetCapability(ctx, channelCapName) +// Sending custom application packet data +data := EncodePacketData(customPacketData) +packet.Data = data +// Send packet to IBC, authenticating with channelCap +IBCChannelKeeper.SendPacket(ctx, channelCap, packet) +``` + +:::warning +In order to prevent modules from sending packets on channels they do not own, IBC expects +modules to pass in the correct channel capability for the packet's source channel. +::: + +### Receiving packets + +To handle receiving packets, the module must implement the `OnRecvPacket` callback. This gets +invoked by the IBC module after the packet has been proved valid and correctly processed by the IBC +keepers. Thus, the `OnRecvPacket` callback only needs to worry about making the appropriate state +changes given the packet data without worrying about whether the packet is valid or not. + +Modules may return to the IBC handler an acknowledgement which implements the `Acknowledgement` interface. +The IBC handler will then commit this acknowledgement of the packet so that a relayer may relay the +acknowledgement back to the sender module. + +The state changes that occurred during this callback will only be written if: + +- the acknowledgement was successful as indicated by the `Success()` function of the acknowledgement +- if the acknowledgement returned is nil indicating that an asynchronous process is occurring + +NOTE: Applications which process asynchronous acknowledgements must handle reverting state changes +when appropriate. Any state changes that occurred during the `OnRecvPacket` callback will be written +for asynchronous acknowledgements. + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `DecodePacketData(packet.Data)` function. + +```go +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, +) ibcexported.Acknowledgement { + // Decode the packet data + packetData := DecodePacketData(packet.Data) + + // do application state changes based on packet data and return the acknowledgement + // NOTE: The acknowledgement will indicate to the IBC handler if the application + // state changes should be written via the `Success()` function. Application state + // changes are only written if the acknowledgement is successful or the acknowledgement + // returned is nil indicating that an asynchronous acknowledgement will occur. + ack := processPacket(ctx, packet, packetData) + + return ack +} +``` + +Reminder, the `Acknowledgement` interface: + +```go +// Acknowledgement defines the interface used to return +// acknowledgements in the OnRecvPacket callback. +type Acknowledgement interface { + Success() bool + Acknowledgement() []byte +} +``` + +### Acknowledging packets + +After a module writes an acknowledgement, a relayer can relay back the acknowledgement to the sender module. The sender module can +then process the acknowledgement using the `OnAcknowledgementPacket` callback. The contents of the +acknowledgement is entirely up to the modules on the channel (just like the packet data); however, it +may often contain information on whether the packet was successfully processed along +with some additional data that could be useful for remediation if the packet processing failed. + +Since the modules are responsible for agreeing on an encoding/decoding standard for packet data and +acknowledgements, IBC will pass in the acknowledgements as `[]byte` to this callback. The callback +is responsible for decoding the acknowledgement and processing it. + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `DecodeAcknowledgement(acknowledgments)` and `processAck(ack)` functions. + +```go +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, +) (*sdk.Result, error) { + // Decode acknowledgement + ack := DecodeAcknowledgement(acknowledgement) + + // process ack + res, err := processAck(ack) + return res, err +} +``` + +### Timeout packets + +If the timeout for a packet is reached before the packet is successfully received or the +counterparty channel end is closed before the packet is successfully received, then the receiving +chain can no longer process it. Thus, the sending chain must process the timeout using +`OnTimeoutPacket` to handle this situation. Again the IBC module will verify that the timeout is +indeed valid, so our module only needs to implement the state machine logic for what to do once a +timeout is reached and the packet can no longer be received. + +```go +func (im IBCModule) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, +) (*sdk.Result, error) { + // do custom timeout logic +} +``` diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/03-bindports.md b/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/03-bindports.md new file mode 100644 index 0000000..303168d --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/03-bindports.md @@ -0,0 +1,122 @@ +--- +title: Bind ports +sidebar_label: Bind ports +sidebar_position: 3 +slug: /ibc/apps/bindports +--- + +# Bind ports + +:::note Synopsis +Learn what changes to make to bind modules to their ports on initialization. +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: +Currently, ports must be bound on app initialization. In order to bind modules to their respective ports on initialization, the following needs to be implemented: + +> Note that `portID` does not refer to a certain numerical ID, like `localhost:8080` with a `portID` 8080. Rather it refers to the application module the port binds. For IBC Modules built with the Cosmos SDK, it defaults to the module's name and for Cosmwasm contracts it defaults to the contract address. + +1. Add port ID to the `GenesisState` proto definition: + + ```protobuf + message GenesisState { + string port_id = 1; + // other fields + } + ``` + +1. Add port ID as a key to the module store: + + ```go + // x//types/keys.go + const ( + // ModuleName defines the IBC Module name + ModuleName = "moduleName" + + // Version defines the current version the IBC + // module supports + Version = "moduleVersion-1" + + // PortID is the default port id that module binds to + PortID = "portID" + + // ... + ) + ``` + +1. Add port ID to `x//types/genesis.go`: + + ```go + // in x//types/genesis.go + + // DefaultGenesisState returns a GenesisState with "transfer" as the default PortID. + func DefaultGenesisState() *GenesisState { + return &GenesisState{ + PortId: PortID, + // additional k-v fields + } + } + + // Validate performs basic genesis state validation returning an error upon any + // failure. + func (gs GenesisState) Validate() error { + if err := host.PortIdentifierValidator(gs.PortId); err != nil { + return err + } + //additional validations + + return gs.Params.Validate() + } + ``` + +1. Bind to port(s) in the module keeper's `InitGenesis`: + + ```go + // InitGenesis initializes the ibc-module state and binds to PortID. + func (k Keeper) InitGenesis(ctx sdk.Context, state types.GenesisState) { + k.SetPort(ctx, state.PortId) + + // ... + + // Only try to bind to port if it is not already bound, since we may already own + // port capability from capability InitGenesis + if !k.IsBound(ctx, state.PortId) { + // transfer module binds to the transfer port on InitChain + // and claims the returned capability + err := k.BindPort(ctx, state.PortId) + if err != nil { + panic(fmt.Sprintf("could not claim port capability: %v", err)) + } + } + + // ... + } + ``` + + With: + + ```go + // IsBound checks if the module is already bound to the desired port + func (k Keeper) IsBound(ctx sdk.Context, portID string) bool { + _, ok := k.scopedKeeper.GetCapability(ctx, host.PortPath(portID)) + return ok + } + + // BindPort defines a wrapper function for the port Keeper's function in + // order to expose it to module's InitGenesis function + func (k Keeper) BindPort(ctx sdk.Context, portID string) error { + cap := k.portKeeper.BindPort(ctx, portID) + return k.ClaimCapability(ctx, cap, host.PortPath(portID)) + } + ``` + + The module binds to the desired port(s) and returns the capabilities. + + In the above we find reference to keeper methods that wrap other keeper functionality, in the next section the keeper methods that need to be implemented will be defined. diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/04-keeper.md b/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/04-keeper.md new file mode 100644 index 0000000..cf4fac6 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/04-keeper.md @@ -0,0 +1,96 @@ +--- +title: Keeper +sidebar_label: Keeper +sidebar_position: 4 +slug: /ibc/apps/keeper +--- + +# Keeper + +:::note Synopsis +Learn how to implement the IBC Module keeper. +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: +In the previous sections, on channel handshake callbacks and port binding in `InitGenesis`, a reference was made to keeper methods that need to be implemented when creating a custom IBC module. Below is an overview of how to define an IBC module's keeper. + +> Note that some code has been left out for clarity, to get a full code overview, please refer to [the transfer module's keeper in the ibc-go repo](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/keeper.go). + +```go +// Keeper defines the IBC app module keeper +type Keeper struct { + storeKey sdk.StoreKey + cdc codec.BinaryCodec + paramSpace paramtypes.Subspace + + channelKeeper types.ChannelKeeper + portKeeper types.PortKeeper + scopedKeeper capabilitykeeper.ScopedKeeper + + // ... additional according to custom logic +} + +// NewKeeper creates a new IBC app module Keeper instance +func NewKeeper( + // args +) Keeper { + // ... + + return Keeper{ + cdc: cdc, + storeKey: key, + paramSpace: paramSpace, + + channelKeeper: channelKeeper, + portKeeper: portKeeper, + scopedKeeper: scopedKeeper, + + // ... additional according to custom logic + } +} + +// IsBound checks if the IBC app module is already bound to the desired port +func (k Keeper) IsBound(ctx sdk.Context, portID string) bool { + _, ok := k.scopedKeeper.GetCapability(ctx, host.PortPath(portID)) + return ok +} + +// BindPort defines a wrapper function for the port Keeper's function in +// order to expose it to module's InitGenesis function +func (k Keeper) BindPort(ctx sdk.Context, portID string) error { + cap := k.portKeeper.BindPort(ctx, portID) + return k.ClaimCapability(ctx, cap, host.PortPath(portID)) +} + +// GetPort returns the portID for the IBC app module. Used in ExportGenesis +func (k Keeper) GetPort(ctx sdk.Context) string { + store := ctx.KVStore(k.storeKey) + return string(store.Get(types.PortKey)) +} + +// SetPort sets the portID for the IBC app module. Used in InitGenesis +func (k Keeper) SetPort(ctx sdk.Context, portID string) { + store := ctx.KVStore(k.storeKey) + store.Set(types.PortKey, []byte(portID)) +} + +// AuthenticateCapability wraps the scopedKeeper's AuthenticateCapability function +func (k Keeper) AuthenticateCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) bool { + return k.scopedKeeper.AuthenticateCapability(ctx, cap, name) +} + +// ClaimCapability allows the IBC app module to claim a capability that core IBC +// passes to it +func (k Keeper) ClaimCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) error { + return k.scopedKeeper.ClaimCapability(ctx, cap, name) +} + +// ... additional according to custom logic +``` diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/05-packets_acks.md b/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/05-packets_acks.md new file mode 100644 index 0000000..e1f8dc1 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/05-packets_acks.md @@ -0,0 +1,108 @@ +--- +title: Define packets and acks +sidebar_label: Define packets and acks +sidebar_position: 5 +slug: /ibc/apps/packets_acks +--- + +# Define packets and acks + +:::note Synopsis +Learn how to define custom packet and acknowledgement structs and how to encode and decode them. +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: + +## Custom packets + +Modules connected by a channel must agree on what application data they are sending over the +channel, as well as how they will encode/decode it. This process is not specified by IBC as it is up +to each application module to determine how to implement this agreement. However, for most +applications this will happen as a version negotiation during the channel handshake. While more +complex version negotiation is possible to implement inside the channel opening handshake, a very +simple version negotiation is implemented in the [ibc-transfer module](https://github.com/cosmos/ibc-go/tree/main/modules/apps/transfer/module.go). + +Thus, a module must define its custom packet data structure, along with a well-defined way to +encode and decode it to and from `[]byte`. + +```go +// Custom packet data defined in application module +type CustomPacketData struct { + // Custom fields ... +} + +EncodePacketData(packetData CustomPacketData) []byte { + // encode packetData to bytes +} + +DecodePacketData(encoded []byte) (CustomPacketData) { + // decode from bytes to packet data +} +``` + +> Note that the `CustomPacketData` struct is defined in the proto definition and then compiled by the protobuf compiler. + +Then a module must encode its packet data before sending it through IBC. + +```go +// Sending custom application packet data +data := EncodePacketData(customPacketData) +packet.Data = data +IBCChannelKeeper.SendPacket(ctx, packet) +``` + +A module receiving a packet must decode the `PacketData` into a structure it expects so that it can +act on it. + +```go +// Receiving custom application packet data (in OnRecvPacket) +packetData := DecodePacketData(packet.Data) +// handle received custom packet data +``` + +## Acknowledgements + +Modules may commit an acknowledgement upon receiving and processing a packet in the case of synchronous packet processing. +In the case where a packet is processed at some later point after the packet has been received (asynchronous execution), the acknowledgement +will be written once the packet has been processed by the application which may be well after the packet receipt. + +NOTE: Most blockchain modules will want to use the synchronous execution model in which the module processes and writes the acknowledgement +for a packet as soon as it has been received from the IBC module. + +This acknowledgement can then be relayed back to the original sender chain, which can take action +depending on the contents of the acknowledgement. + +Just as packet data was opaque to IBC, acknowledgements are similarly opaque. Modules must pass and +receive acknowledegments with the IBC modules as byte strings. + +Thus, modules must agree on how to encode/decode acknowledgements. The process of creating an +acknowledgement struct along with encoding and decoding it, is very similar to the packet data +example above. [ICS 04](https://github.com/cosmos/ibc/blob/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope) +specifies a recommended format for acknowledgements. This acknowledgement type can be imported from +[channel types](https://github.com/cosmos/ibc-go/tree/main/modules/core/04-channel/types). + +While modules may choose arbitrary acknowledgement structs, a default acknowledgement types is provided by IBC [here](https://github.com/cosmos/ibc-go/blob/main/proto/ibc/core/channel/v1/channel.proto): + +```protobuf +// Acknowledgement is the recommended acknowledgement format to be used by +// app-specific protocols. +// NOTE: The field numbers 21 and 22 were explicitly chosen to avoid accidental +// conflicts with other protobuf message formats used for acknowledgements. +// The first byte of any message with this format will be the non-ASCII values +// `0xaa` (result) or `0xb2` (error). Implemented as defined by ICS: +// https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope +message Acknowledgement { + // response contains either a result or an error and must be non-empty + oneof response { + bytes result = 21; + string error = 22; + } +} +``` diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/06-routing.md b/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/06-routing.md new file mode 100644 index 0000000..7e6c205 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/06-routing.md @@ -0,0 +1,44 @@ +--- +title: Routing +sidebar_label: Routing +sidebar_position: 6 +slug: /ibc/apps/routing +--- + +# Routing + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: +:::note Synopsis +Learn how to hook a route to the IBC router for the custom IBC module. +::: + +As mentioned above, modules must implement the `IBCModule` interface (which contains both channel +handshake callbacks and packet handling callbacks). The concrete implementation of this interface +must be registered with the module name as a route on the IBC `Router`. + +```go +// app.go +func NewApp(...args) *App { +// ... + +// Create static IBC router, add module routes, then set and seal it +ibcRouter := port.NewRouter() + +ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferModule) +// Note: moduleCallbacks must implement IBCModule interface +ibcRouter.AddRoute(moduleName, moduleCallbacks) + +// Setting Router will finalize all routes by sealing router +// No more routes can be added +app.IBCKeeper.SetRouter(ibcRouter) + +// ... +} +``` diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/_category_.json b/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/_category_.json new file mode 100644 index 0000000..1c34da9 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Applications", + "position": 3, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/images/packet_flow.png b/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/images/packet_flow.png new file mode 100644 index 0000000..e5bae3f Binary files /dev/null and b/docs/versioned_docs/version-v5.4.x/01-ibc/03-apps/images/packet_flow.png differ diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/04-middleware/01-develop.md b/docs/versioned_docs/version-v5.4.x/01-ibc/04-middleware/01-develop.md new file mode 100644 index 0000000..5a0ccce --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/01-ibc/04-middleware/01-develop.md @@ -0,0 +1,422 @@ +--- +title: IBC middleware +sidebar_label: IBC middleware +sidebar_position: 1 +slug: /ibc/middleware/develop +--- + +# IBC middleware + +:::note Synopsis +Learn how to write your own custom middleware to wrap an IBC application, and understand how to hook different middleware to IBC base applications to form different IBC application stacks +:::. + +This document serves as a guide for middleware developers who want to write their own middleware and for chain developers who want to use IBC middleware on their chains. + +IBC applications are designed to be self-contained modules that implement their own application-specific logic through a set of interfaces with the core IBC handlers. These core IBC handlers, in turn, are designed to enforce the correctness properties of IBC (transport, authentication, ordering) while delegating all application-specific handling to the IBC application modules. However, there are cases where some functionality may be desired by many applications, yet not appropriate to place in core IBC. + +Middleware allows developers to define the extensions as separate modules that can wrap over the base application. This middleware can thus perform its own custom logic, and pass data into the application so that it may run its logic without being aware of the middleware's existence. This allows both the application and the middleware to implement its own isolated logic while still being able to run as part of a single packet flow. + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC Integration](../02-integration.md) +- [IBC Application Developer Guide](../03-apps/01-apps.md) + +::: + +## Definitions + +`Middleware`: A self-contained module that sits between core IBC and an underlying IBC application during packet execution. All messages between core IBC and underlying application must flow through middleware, which may perform its own custom logic. + +`Underlying Application`: An underlying application is the application that is directly connected to the middleware in question. This underlying application may itself be middleware that is chained to a base application. + +`Base Application`: A base application is an IBC application that does not contain any middleware. It may be nested by 0 or multiple middleware to form an application stack. + +`Application Stack (or stack)`: A stack is the complete set of application logic (middleware(s) + base application) that gets connected to core IBC. A stack may be just a base application, or it may be a series of middlewares that nest a base application. + +## Create a custom IBC middleware + +IBC middleware will wrap over an underlying IBC application and sits between core IBC and the application. It has complete control in modifying any message coming from IBC to the application, and any message coming from the application to core IBC. Thus, middleware must be completely trusted by chain developers who wish to integrate them, however this gives them complete flexibility in modifying the application(s) they wrap. + +### Interfaces + +```go +// Middleware implements the ICS26 Module interface +type Middleware interface { + porttypes.IBCModule // middleware has access to an underlying application which may be wrapped by more middleware + ics4Wrapper: ICS4Wrapper // middleware has access to ICS4Wrapper which may be core IBC Channel Handler or a higher-level middleware that wraps this middleware. +} +``` + +```typescript +// This is implemented by ICS4 and all middleware that are wrapping base application. +// The base application will call `sendPacket` or `writeAcknowledgement` of the middleware directly above them +// which will call the next middleware until it reaches the core IBC handler. +type ICS4Wrapper interface { + SendPacket(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet exported.Packet) error + WriteAcknowledgement(ctx sdk.Context, chanCap *capabilitytypes.Capability, packet exported.Packet, ack exported.Acknowledgement) error + GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) +} +``` + +### Implement `IBCModule` interface and callbacks + +The `IBCModule` is a struct that implements the [ICS-26 interface (`porttypes.IBCModule`)](https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port/types/module.go#L11-L106). It is recommended to separate these callbacks into a separate file `ibc_module.go`. As will be mentioned in the [integration section](02-integration.md), this struct should be different than the struct that implements `AppModule` in case the middleware maintains its own internal state and processes separate SDK messages. + +The middleware must have access to the underlying application, and be called before during all ICS-26 callbacks. It may execute custom logic during these callbacks, and then call the underlying application's callback. Middleware **may** choose not to call the underlying application's callback at all. Though these should generally be limited to error cases. + +In the case where the IBC middleware expects to speak to a compatible IBC middleware on the counterparty chain, they must use the channel handshake to negotiate the middleware version without interfering in the version negotiation of the underlying application. + +Middleware accomplishes this by formatting the version in a JSON-encoded string containing the middleware version and the application version. The application version may as well be a JSON-encoded string, possibly including further middleware and app versions, if the application stack consists of multiple milddlewares wrapping a base application. The format of the version is specified in ICS-30 as the following: + +```json +{ + "": "", + "app_version": "" +} +``` + +The `` key in the JSON struct should be replaced by the actual name of the key for the corresponding middleware (e.g. `fee_version`). + +During the handshake callbacks, the middleware can unmarshal the version string and retrieve the middleware and application versions. It can do its negotiation logic on ``, and pass the `` to the underlying application. + +The middleware should simply pass the capability in the callback arguments along to the underlying application so that it may be claimed by the base application. The base application will then pass the capability up the stack in order to authenticate an outgoing packet/acknowledgement. + +In the case where the middleware wishes to send a packet or acknowledgment without the involvement of the underlying application, it should be given access to the same `scopedKeeper` as the base application so that it can retrieve the capabilities by itself. + +### Handshake callbacks + +#### `OnChanOpenInit` + +```go +func (im IBCModule) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + if version != "" { + // try to unmarshal JSON-encoded version string and pass + // the app-specific version to app callback. + // otherwise, pass version directly to app callback. + metadata, err := Unmarshal(version) + if err != nil { + // Since it is valid for fee version to not be specified, + // the above middleware version may be for another middleware. + // Pass the entire version string onto the underlying application. + return im.app.OnChanOpenInit( + ctx, + order, + connectionHops, + portID, + channelID, + channelCap, + counterparty, + version, + ) + } + else { + metadata = { + // set middleware version to default value + MiddlewareVersion: defaultMiddlewareVersion, + // allow application to return its default version + AppVersion: "", + } + } + + doCustomLogic() + + // if the version string is empty, OnChanOpenInit is expected to return + // a default version string representing the version(s) it supports + appVersion, err := im.app.OnChanOpenInit( + ctx, + order, + connectionHops, + portID, + channelID, + channelCap, + counterparty, + metadata.AppVersion, // note we only pass app version here + ) + if err != nil { + return "", err + } + + version := constructVersion(metadata.MiddlewareVersion, appVersion) + + return version, nil +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L34-L82) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnChanOpenTry` + +```go +func OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + // try to unmarshal JSON-encoded version string and pass + // the app-specific version to app callback. + // otherwise, pass version directly to app callback. + cpMetadata, err := Unmarshal(counterpartyVersion) + if err != nil { + return app.OnChanOpenTry( + ctx, + order, + connectionHops, + portID, + channelID, + channelCap, + counterparty, + counterpartyVersion, + ) + } + + doCustomLogic() + + // Call the underlying application's OnChanOpenTry callback. + // The try callback must select the final app-specific version string and return it. + appVersion, err := app.OnChanOpenTry( + ctx, + order, + connectionHops, + portID, + channelID, + channelCap, + counterparty, + cpMetadata.AppVersion, // note we only pass counterparty app version here + ) + if err != nil { + return "", err + } + + // negotiate final middleware version + middlewareVersion := negotiateMiddlewareVersion(cpMetadata.MiddlewareVersion) + version := constructVersion(middlewareVersion, appVersion) + + return version, nil +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L84-L124) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnChanOpenAck` + +```go +func OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyChannelID string, + counterpartyVersion string, +) error { + // try to unmarshal JSON-encoded version string and pass + // the app-specific version to app callback. + // otherwise, pass version directly to app callback. + cpMetadata, err = UnmarshalJSON(counterpartyVersion) + if err != nil { + return app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) + } + + if !isCompatible(cpMetadata.MiddlewareVersion) { + return error + } + doCustomLogic() + + // call the underlying application's OnChanOpenTry callback + return app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, cpMetadata.AppVersion) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L126-L152) an example implementation of this callback for the ICS29 Fee Middleware module. + +### `OnChanOpenConfirm` + +```go +func OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + doCustomLogic() + + return app.OnChanOpenConfirm(ctx, portID, channelID) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L154-L162) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnChanCloseInit` + +```go +func OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + doCustomLogic() + + return app.OnChanCloseInit(ctx, portID, channelID) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L164-L187) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnChanCloseConfirm` + +```go +func OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + doCustomLogic() + + return app.OnChanCloseConfirm(ctx, portID, channelID) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L189-L212) an example implementation of this callback for the ICS29 Fee Middleware module. + +**NOTE**: Middleware that does not need to negotiate with a counterparty middleware on the remote stack will not implement the version unmarshalling and negotiation, and will simply perform its own custom logic on the callbacks without relying on the counterparty behaving similarly. + +### Packet callbacks + +The packet callbacks just like the handshake callbacks wrap the application's packet callbacks. The packet callbacks are where the middleware performs most of its custom logic. The middleware may read the packet flow data and perform some additional packet handling, or it may modify the incoming data before it reaches the underlying application. This enables a wide degree of usecases, as a simple base application like token-transfer can be transformed for a variety of usecases by combining it with custom middleware. + +#### `OnRecvPacket` + +```go +func OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) ibcexported.Acknowledgement { + doCustomLogic(packet) + + ack := app.OnRecvPacket(ctx, packet, relayer) + + doCustomLogic(ack) // middleware may modify outgoing ack + return ack +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L214-L237) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnAcknowledgementPacket` + +```go +func OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + doCustomLogic(packet, ack) + + return app.OnAcknowledgementPacket(ctx, packet, ack, relayer) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L239-L292) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnTimeoutPacket` + +```go +func OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + doCustomLogic(packet) + + return app.OnTimeoutPacket(ctx, packet, relayer) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L294-L334) an example implementation of this callback for the ICS29 Fee Middleware module. + +### ICS-4 wrappers + +Middleware must also wrap ICS-4 so that any communication from the application to the `channelKeeper` goes through the middleware first. Similar to the packet callbacks, the middleware may modify outgoing acknowledgements and packets in any way it wishes. + +#### `SendPacket` + +```go +func SendPacket( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + appPacket exported.PacketI, +) { + // middleware may modify packet + packet = doCustomLogic(appPacket) + + return ics4Keeper.SendPacket(ctx, chanCap, packet) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L336-L343) an example implementation of this function for the ICS29 Fee Middleware module. + +#### `WriteAcknowledgement` + +```go +// only called for async acks +func WriteAcknowledgement( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + packet exported.PacketI, + ack exported.Acknowledgement, +) { + // middleware may modify acknowledgement + ack_bytes = doCustomLogic(ack) + + return ics4Keeper.WriteAcknowledgement(packet, ack_bytes) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L345-L353) an example implementation of this function for the ICS29 Fee Middleware module. + +#### `GetAppVersion` + +```go +// middleware must return the underlying application version +func GetAppVersion( + ctx sdk.Context, + portID, + channelID string, +) (string, bool) { + version, found := ics4Keeper.GetAppVersion(ctx, portID, channelID) + if !found { + return "", false + } + + if !MiddlewareEnabled { + return version, true + } + + // unwrap channel version + metadata, err := Unmarshal(version) + if err != nil { + panic(fmt.Errof("unable to unmarshal version: %w", err)) + } + + return metadata.AppVersion, true +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L355-L358) an example implementation of this function for the ICS29 Fee Middleware module. diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/04-middleware/02-integration.md b/docs/versioned_docs/version-v5.4.x/01-ibc/04-middleware/02-integration.md new file mode 100644 index 0000000..d721023 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/01-ibc/04-middleware/02-integration.md @@ -0,0 +1,72 @@ +--- +title: Integrating IBC middleware into a chain +sidebar_label: Integrating IBC middleware into a chain +sidebar_position: 2 +slug: /ibc/middleware/integration +--- + + +# Integrating IBC middleware into a chain + +Learn how to integrate IBC middleware(s) with a base application to your chain. The following document only applies for Cosmos SDK chains. + +If the middleware is maintaining its own state and/or processing SDK messages, then it should create and register its SDK module **only once** with the module manager in `app.go`. + +All middleware must be connected to the IBC router and wrap over an underlying base IBC application. An IBC application may be wrapped by many layers of middleware, only the top layer middleware should be hooked to the IBC router, with all underlying middlewares and application getting wrapped by it. + +The order of middleware **matters**, function calls from IBC to the application travel from top-level middleware to the bottom middleware and then to the application. Function calls from the application to IBC goes through the bottom middleware in order to the top middleware and then to core IBC handlers. Thus the same set of middleware put in different orders may produce different effects. + +## Example integration + +```go +// app.go + +// middleware 1 and middleware 3 are stateful middleware, +// perhaps implementing separate sdk.Msg and Handlers +mw1Keeper := mw1.NewKeeper(storeKey1) +mw3Keeper := mw3.NewKeeper(storeKey3) + +// Only create App Module **once** and register in app module +// if the module maintains independent state and/or processes sdk.Msgs +app.moduleManager = module.NewManager( + ... + mw1.NewAppModule(mw1Keeper), + mw3.NewAppModule(mw3Keeper), + transfer.NewAppModule(transferKeeper), + custom.NewAppModule(customKeeper) +) + +mw1IBCModule := mw1.NewIBCModule(mw1Keeper) +mw2IBCModule := mw2.NewIBCModule() // middleware2 is stateless middleware +mw3IBCModule := mw3.NewIBCModule(mw3Keeper) + +scopedKeeperTransfer := capabilityKeeper.NewScopedKeeper("transfer") +scopedKeeperCustom1 := capabilityKeeper.NewScopedKeeper("custom1") +scopedKeeperCustom2 := capabilityKeeper.NewScopedKeeper("custom2") + +// NOTE: IBC Modules may be initialized any number of times provided they use a separate +// scopedKeeper and underlying port. + +// initialize base IBC applications +// if you want to create two different stacks with the same base application, +// they must be given different scopedKeepers and assigned different ports. +transferIBCModule := transfer.NewIBCModule(transferKeeper) +customIBCModule1 := custom.NewIBCModule(customKeeper, "portCustom1") +customIBCModule2 := custom.NewIBCModule(customKeeper, "portCustom2") + +// create IBC stacks by combining middleware with base application +// NOTE: since middleware2 is stateless it does not require a Keeper +// stack 1 contains mw1 -> mw3 -> transfer +stack1 := mw1.NewIBCMiddleware(mw3.NewIBCMiddleware(transferIBCModule, mw3Keeper), mw1Keeper) +// stack 2 contains mw3 -> mw2 -> custom1 +stack2 := mw3.NewIBCMiddleware(mw2.NewIBCMiddleware(customIBCModule1), mw3Keeper) +// stack 3 contains mw2 -> mw1 -> custom2 +stack3 := mw2.NewIBCMiddleware(mw1.NewIBCMiddleware(customIBCModule2, mw1Keeper)) + +// associate each stack with the moduleName provided by the underlying scopedKeeper +ibcRouter := porttypes.NewRouter() +ibcRouter.AddRoute("transfer", stack1) +ibcRouter.AddRoute("custom1", stack2) +ibcRouter.AddRoute("custom2", stack3) +app.IBCKeeper.SetRouter(ibcRouter) +``` diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/04-middleware/_category_.json b/docs/versioned_docs/version-v5.4.x/01-ibc/04-middleware/_category_.json new file mode 100644 index 0000000..ec27d4e --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/01-ibc/04-middleware/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Middleware", + "position": 4, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/05-upgrades/00-intro.md b/docs/versioned_docs/version-v5.4.x/01-ibc/05-upgrades/00-intro.md new file mode 100644 index 0000000..e821ad3 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/01-ibc/05-upgrades/00-intro.md @@ -0,0 +1,8 @@ +### Upgrading IBC Chains Overview + +This directory contains information on how to upgrade an IBC chain without breaking counterparty clients and connections. + +IBC-connected chains must be able to upgrade without breaking connections to other chains. Otherwise there would be a massive disincentive towards upgrading and disrupting high-value IBC connections, thus preventing chains in the IBC ecosystem from evolving and improving. Many chain upgrades may be irrelevant to IBC, however some upgrades could potentially break counterparty clients if not handled correctly. Thus, any IBC chain that wishes to perform an IBC-client-breaking upgrade must perform an IBC upgrade in order to allow counterparty clients to securely upgrade to the new light client. + +1. The [quick-guide](./01-quick-guide.md) describes how IBC-connected chains can perform client-breaking upgrades and how relayers can securely upgrade counterparty clients using the SDK. +2. The [developer-guide](./02-developer-guide.md) is a guide for developers intending to develop IBC client implementations with upgrade functionality. diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/05-upgrades/01-quick-guide.md b/docs/versioned_docs/version-v5.4.x/01-ibc/05-upgrades/01-quick-guide.md new file mode 100644 index 0000000..85e329e --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/01-ibc/05-upgrades/01-quick-guide.md @@ -0,0 +1,60 @@ +--- +title: How to Upgrade IBC Chains and their Clients +sidebar_label: How to Upgrade IBC Chains and their Clients +sidebar_position: 1 +slug: /ibc/upgrades/quick-guide +--- + + +# How to Upgrade IBC Chains and their Clients + +:::note Synopsis +Learn how to upgrade your chain and counterparty clients. +::: + +The information in this doc for upgrading chains is relevant to SDK chains. However, the guide for counterparty clients is relevant to any Tendermint client that enables upgrades. + +## IBC Client Breaking Upgrades + +IBC-connected chains must perform an IBC upgrade if their upgrade will break counterparty IBC clients. The current IBC protocol supports upgrading tendermint chains for a specific subset of IBC-client-breaking upgrades. Here is the exhaustive list of IBC client-breaking upgrades and whether the IBC protocol currently supports such upgrades. + +IBC currently does **NOT** support unplanned upgrades. All of the following upgrades must be planned and committed to in advance by the upgrading chain, in order for counterparty clients to maintain their connections securely. + +Note: Since upgrades are only implemented for Tendermint clients, this doc only discusses upgrades on Tendermint chains that would break counterparty IBC Tendermint Clients. + +1. Changing the Chain-ID: **Supported** +2. Changing the UnbondingPeriod: **Partially Supported**, chains may increase the unbonding period with no issues. However, decreasing the unbonding period may irreversibly break some counterparty clients. Thus, it is **not recommended** that chains reduce the unbonding period. +3. Changing the height (resetting to 0): **Supported**, so long as chains remember to increment the revision number in their chain-id. +4. Changing the ProofSpecs: **Supported**, this should be changed if the proof structure needed to verify IBC proofs is changed across the upgrade. Ex: Switching from an IAVL store, to a SimpleTree Store +5. Changing the UpgradePath: **Supported**, this might involve changing the key under which upgraded clients and consensus states are stored in the upgrade store, or even migrating the upgrade store itself. +6. Migrating the IBC store: **Unsupported**, as the IBC store location is negotiated by the connection. +7. Upgrading to a backwards compatible version of IBC: Supported +8. Upgrading to a non-backwards compatible version of IBC: **Unsupported**, as IBC version is negotiated on connection handshake. +9. Changing the Tendermint LightClient algorithm: **Partially Supported**. Changes to the light client algorithm that do not change the ClientState or ConsensusState struct may be supported, provided that the counterparty is also upgraded to support the new light client algorithm. Changes that require updating the ClientState and ConsensusState structs themselves are theoretically possible by providing a path to translate an older ClientState struct into the new ClientState struct; however this is not currently implemented. + +### Step-by-Step Upgrade Process for SDK chains + +If the IBC-connected chain is conducting an upgrade that will break counterparty clients, it must ensure that the upgrade is first supported by IBC using the list above and then execute the upgrade process described below in order to prevent counterparty clients from breaking. + +1. Create a 02-client [`UpgradeProposal`](https://github.com/cosmos/ibc-go/blob/v5.3.1/proto/ibc/core/client/v1/client.proto#L58-L77) with an `UpgradePlan` and a new IBC ClientState in the `UpgradedClientState` field. Note that the `UpgradePlan` must specify an upgrade height **only** (no upgrade time), and the `ClientState` should only include the fields common to all valid clients and zero out any client-customizable fields (such as TrustingPeriod). +2. Vote on and pass the `UpgradeProposal` + +Upon the `UpgradeProposal` passing, the upgrade module will commit the UpgradedClient under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedClient`. On the block right before the upgrade height, the upgrade module will also commit an initial consensus state for the next chain under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedConsState`. + +Once the chain reaches the upgrade height and halts, a relayer can upgrade the counterparty clients to the last block of the old chain. They can then submit the proofs of the `UpgradedClient` and `UpgradedConsensusState` against this last block and upgrade the counterparty client. + +### Step-by-Step Upgrade Process for Relayers Upgrading Counterparty Clients + +Once the upgrading chain has committed to upgrading, relayers must wait till the chain halts at the upgrade height before upgrading counterparty clients. This is because chains may reschedule or cancel upgrade plans before they occur. Thus, relayers must wait till the chain reaches the upgrade height and halts before they can be sure the upgrade will take place. + +Thus, the upgrade process for relayers trying to upgrade the counterparty clients is as follows: + +1. Wait for the upgrading chain to reach the upgrade height and halt +2. Query a full node for the proofs of `UpgradedClient` and `UpgradedConsensusState` at the last height of the old chain. +3. Update the counterparty client to the last height of the old chain using the `UpdateClient` msg. +4. Submit an `UpgradeClient` msg to the counterparty chain with the `UpgradedClient`, `UpgradedConsensusState` and their respective proofs. +5. Submit an `UpdateClient` msg to the counterparty chain with a header from the new upgraded chain. + +The Tendermint client on the counterparty chain will verify that the upgrading chain did indeed commit to the upgraded client and upgraded consensus state at the upgrade height (since the upgrade height is included in the key). If the proofs are verified against the upgrade height, then the client will upgrade to the new client while retaining all of its client-customized fields. Thus, it will retain its old TrustingPeriod, TrustLevel, MaxClockDrift, etc; while adopting the new chain-specified fields such as UnbondingPeriod, ChainId, UpgradePath, etc. Note, this can lead to an invalid client since the old client-chosen fields may no longer be valid given the new chain-chosen fields. Upgrading chains should try to avoid these situations by not altering parameters that can break old clients. For an example, see the UnbondingPeriod example in the supported upgrades section. + +The upgraded consensus state will serve purely as a basis of trust for future `UpdateClientMsgs` and will not contain a consensus root to perform proof verification against. Thus, relayers must submit an `UpdateClientMsg` with a header from the new chain so that the connection can be used for proof verification again. diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/05-upgrades/02-developer-guide.md b/docs/versioned_docs/version-v5.4.x/01-ibc/05-upgrades/02-developer-guide.md new file mode 100644 index 0000000..98f1d42 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/01-ibc/05-upgrades/02-developer-guide.md @@ -0,0 +1,56 @@ +--- +title: IBC Client Developer Guide to Upgrades +sidebar_label: IBC Client Developer Guide to Upgrades +sidebar_position: 2 +slug: /ibc/upgrades/developer-guide +--- + + +# IBC Client Developer Guide to Upgrades + +:::note Synopsis +Learn how to implement upgrade functionality for your custom IBC client. +::: + +As mentioned in the [README](./00-intro.md), it is vital that high-value IBC clients can upgrade along with their underlying chains to avoid disruption to the IBC ecosystem. Thus, IBC client developers will want to implement upgrade functionality to enable clients to maintain connections and channels even across chain upgrades. + +The IBC protocol allows client implementations to provide a path to upgrading clients given the upgraded client state, upgraded consensus state and proofs for each. + +```go +// Upgrade functions +// NOTE: proof heights are not included as upgrade to a new revision is expected to pass only on the last +// height committed by the current revision. Clients are responsible for ensuring that the planned last +// height of the current revision is somehow encoded in the proof verification process. +// This is to ensure that no premature upgrades occur, since upgrade plans committed to by the counterparty +// may be cancelled or modified before the last planned height. +VerifyUpgradeAndUpdateState( + ctx sdk.Context, + cdc codec.BinaryCodec, + store sdk.KVStore, + newClient ClientState, + newConsState ConsensusState, + proofUpgradeClient, + proofUpgradeConsState []byte, +) (upgradedClient ClientState, upgradedConsensus ConsensusState, err error) +``` + +Note that the clients should have prior knowledge of the merkle path that the upgraded client and upgraded consensus states will use. The height at which the upgrade has occurred should also be encoded in the proof. The Tendermint client implementation accomplishes this by including an `UpgradePath` in the ClientState itself, which is used along with the upgrade height to construct the merkle path under which the client state and consensus state are committed. + +Developers must ensure that the `UpgradeClientMsg` does not pass until the last height of the old chain has been committed, and after the chain upgrades, the `UpgradeClientMsg` should pass once and only once on all counterparty clients. + +Developers must ensure that the new client adopts all of the new Client parameters that must be uniform across every valid light client of a chain (chain-chosen parameters), while maintaining the Client parameters that are customizable by each individual client (client-chosen parameters) from the previous version of the client. + +Upgrades must adhere to the IBC Security Model. IBC does not rely on the assumption of honest relayers for correctness. Thus users should not have to rely on relayers to maintain client correctness and security (though honest relayers must exist to maintain relayer liveness). While relayers may choose any set of client parameters while creating a new `ClientState`, this still holds under the security model since users can always choose a relayer-created client that suits their security and correctness needs or create a Client with their desired parameters if no such client exists. + +However, when upgrading an existing client, one must keep in mind that there are already many users who depend on this client's particular parameters. We cannot give the upgrading relayer free choice over these parameters once they have already been chosen. This would violate the security model since users who rely on the client would have to rely on the upgrading relayer to maintain the same level of security. Thus, developers must make sure that their upgrade mechanism allows clients to upgrade the chain-specified parameters whenever a chain upgrade changes these parameters (examples in the Tendermint client include `UnbondingPeriod`, `TrustingPeriod`, `ChainID`, `UpgradePath`, etc.), while ensuring that the relayer submitting the `UpgradeClientMsg` cannot alter the client-chosen parameters that the users are relying upon (examples in Tendermint client include `TrustLevel`, `MaxClockDrift`, etc). + +Developers should maintain the distinction between Client parameters that are uniform across every valid light client of a chain (chain-chosen parameters), and Client parameters that are customizable by each individual client (client-chosen parameters); since this distinction is necessary to implement the `ZeroCustomFields` method in the `ClientState` interface: + +```go +// Utility function that zeroes out any client customizable fields in client state +// Ledger enforced fields are maintained while all custom fields are zero values +// Used to verify upgrades +ZeroCustomFields() ClientState +``` + +Counterparty clients can upgrade securely by using all of the chain-chosen parameters from the chain-committed `UpgradedClient` and preserving all of the old client-chosen parameters. This enables chains to securely upgrade without relying on an honest relayer, however it can in some cases lead to an invalid final `ClientState` if the new chain-chosen parameters clash with the old client-chosen parameter. This can happen in the Tendermint client case if the upgrading chain lowers the `UnbondingPeriod` (chain-chosen) to a duration below that of a counterparty client's `TrustingPeriod` (client-chosen). Such cases should be clearly documented by developers, so that chains know which upgrades should be avoided to prevent this problem. The final upgraded client should also be validated in `VerifyUpgradeAndUpdateState` before returning to ensure that the client does not upgrade to an invalid `ClientState`. diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/05-upgrades/03-genesis-restart.md b/docs/versioned_docs/version-v5.4.x/01-ibc/05-upgrades/03-genesis-restart.md new file mode 100644 index 0000000..872227f --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/01-ibc/05-upgrades/03-genesis-restart.md @@ -0,0 +1,52 @@ +--- +title: Genesis Restart Upgrades +sidebar_label: Genesis Restart Upgrades +sidebar_position: 3 +slug: /ibc/upgrades/genesis-restart +--- + + +# Genesis Restart Upgrades + +:::note Synopsis +Learn how to upgrade your chain and counterparty clients using genesis restarts. +::: + +**NOTE**: Regular genesis restarts are currently unsupported by relayers! + +## IBC Client Breaking Upgrades + +IBC client breaking upgrades are possible using genesis restarts. +It is highly recommended to use the in-place migrations instead of a genesis restart. +Genesis restarts should be used sparingly and as backup plans. + +Genesis restarts still require the usage of an IBC upgrade proposal in order to correctly upgrade counterparty clients. + +### Step-by-Step Upgrade Process for SDK Chains + +If the IBC-connected chain is conducting an upgrade that will break counterparty clients, it must ensure that the upgrade is first supported by IBC using the [IBC Client Breaking Upgrade List](./01-quick-guide.md#ibc-client-breaking-upgrades) and then execute the upgrade process described below in order to prevent counterparty clients from breaking. + +1. Create a 02-client [`UpgradeProposal`](https://github.com/cosmos/ibc-go/blob/v5.3.1/proto/ibc/core/client/v1/client.proto#L58-L77) with an `UpgradePlan` and a new IBC ClientState in the `UpgradedClientState` field. Note that the `UpgradePlan` must specify an upgrade height **only** (no upgrade time), and the `ClientState` should only include the fields common to all valid clients and zero out any client-customizable fields (such as TrustingPeriod). +2. Vote on and pass the `UpgradeProposal` +3. Halt the node after successful upgrade. +4. Export the genesis file. +5. Swap to the new binary. +6. Run migrations on the genesis file. +7. Remove the `UpgradeProposal` plan from the genesis file. This may be done by migrations. +8. Change desired chain-specific fields (chain id, unbonding period, etc). This may be done by migrations. +8. Reset the node's data. +9. Start the chain. + +Upon the `UpgradeProposal` passing, the upgrade module will commit the UpgradedClient under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedClient`. On the block right before the upgrade height, the upgrade module will also commit an initial consensus state for the next chain under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedConsState`. + +Once the chain reaches the upgrade height and halts, a relayer can upgrade the counterparty clients to the last block of the old chain. They can then submit the proofs of the `UpgradedClient` and `UpgradedConsensusState` against this last block and upgrade the counterparty client. + +#### Step-by-Step Upgrade Process for Relayers Upgrading Counterparty Clients + +These steps are identical to the regular [IBC client breaking upgrade process](./01-quick-guide.md#step-by-step-upgrade-process-for-relayers-upgrading-counterparty-clients). + +### Non-IBC Client Breaking Upgrades + +While ibc-go supports genesis restarts which do not break IBC clients, relayers do not support this upgrade path. +Here is a tracking issue on [Hermes](https://github.com/informalsystems/ibc-rs/issues/1152). +Please do not attempt a regular genesis restarts unless you have a tool to update counterparty clients correctly. diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/05-upgrades/_category_.json b/docs/versioned_docs/version-v5.4.x/01-ibc/05-upgrades/_category_.json new file mode 100644 index 0000000..439f7ee --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/01-ibc/05-upgrades/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Upgrades", + "position": 5, + "link": { "type": "doc", "id": "intro" } +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/06-proposals.md b/docs/versioned_docs/version-v5.4.x/01-ibc/06-proposals.md new file mode 100644 index 0000000..1b4f964 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/01-ibc/06-proposals.md @@ -0,0 +1,130 @@ +--- +title: Governance Proposals +sidebar_label: Governance Proposals +sidebar_position: 6 +slug: /ibc/proposals +--- + +# Governance Proposals + +In uncommon situations, a highly valued client may become frozen due to uncontrollable +circumstances. A highly valued client might have hundreds of channels being actively used. +Some of those channels might have a significant amount of locked tokens used for ICS 20. + +If the one third of the validator set of the chain the client represents decides to collude, +they can sign off on two valid but conflicting headers each signed by the other one third +of the honest validator set. The light client can now be updated with two valid, but conflicting +headers at the same height. The light client cannot know which header is trustworthy and therefore +evidence of such misbehaviour is likely to be submitted resulting in a frozen light client. + +Frozen light clients cannot be updated under any circumstance except via a governance proposal. +Since a quorum of validators can sign arbitrary state roots which may not be valid executions +of the state machine, a governance proposal has been added to ease the complexity of unfreezing +or updating clients which have become "stuck". Without this mechanism, validator sets would need +to construct a state root to unfreeze the client. Unfreezing clients, re-enables all of the channels +built upon that client. This may result in recovery of otherwise lost funds. + +Tendermint light clients may become expired if the trusting period has passed since their +last update. This may occur if relayers stop submitting headers to update the clients. + +An unplanned upgrade by the counterparty chain may also result in expired clients. If the counterparty +chain undergoes an unplanned upgrade, there may be no commitment to that upgrade signed by the validator +set before the chain-id changes. In this situation, the validator set of the last valid update for the +light client is never expected to produce another valid header since the chain-id has changed, which will +ultimately lead the on-chain light client to become expired. + +In the case that a highly valued light client is frozen, expired, or rendered non-updateable, a +governance proposal may be submitted to update this client, known as the subject client. The +proposal includes the client identifier for the subject and the client identifier for a substitute +client. Light client implementations may implement custom updating logic, but in most cases, +the subject will be updated to the latest consensus state of the substitute client, if the proposal passes. +The substitute client is used as a "stand in" while the subject is on trial. It is best practice to create +a substitute client *after* the subject has become frozen to avoid the substitute from also becoming frozen. +An active substitute client allows headers to be submitted during the voting period to prevent accidental expiry +once the proposal passes. + +## How to recover an expired client with a governance proposal + +See also the relevant documentation: [ADR-026, IBC client recovery mechanisms](/architecture/adr-026-ibc-client-recovery-mechanisms) + +> **Who is this information for?** +> Although technically anyone can submit the governance proposal to recover an expired client, often it will be **relayer operators** (at least coordinating the submission). + +### Preconditions + +- The chain is updated with ibc-go >= v1.1.0. +- There exists an active client (with a known client identifier) for the same counterparty chain as the expired client. +- The governance deposit. + +## Steps + +### Step 1 + +Check if the client is attached to the expected `chain-id`. For example, for an expired Tendermint client representing the Akash chain the client state looks like this on querying the client state: + +```text +{ + client_id: 07-tendermint-146 + client_state: + '@type': /ibc.lightclients.tendermint.v1.ClientState + allow_update_after_expiry: true + allow_update_after_misbehaviour: true + chain_id: akashnet-2 +} +``` + +The client is attached to the expected Akash `chain-id`. Note that although the parameters (`allow_update_after_expiry` and `allow_update_after_misbehaviour`) exist to signal intent, these parameters have been deprecated and will not enforce any checks on the revival of client. See ADR-026 for more context on this deprecation. + +### Step 2 + +If the chain has been updated to ibc-go >= v1.1.0, anyone can submit the governance proposal to recover the client by executing this via CLI. + +> Note that the Cosmos SDK has updated how governance proposals are submitted in SDK v0.46, now requiring to pass a .json proposal file + +- From SDK v0.46.x onwards + + ```bash + tx gov submit-proposal [path-to-proposal-json] + ``` + + where `proposal.json` contains: + + ```json + { + "messages": [ + { + "@type": "/ibc.core.client.v1.ClientUpdateProposal", + "title": "title_string", + "description": "description_string", + "subject_client_id": "expired_client_id_string", + "substitute_client_id": "active_client_id_string" + } + ], + "metadata": "", + "deposit": "10stake" + } + ``` + + Alternatively there's a legacy command (that is no longer recommended though): + + ```bash + tx gov submit-legacy-proposal update-client + ``` + +- Until SDK v0.45.x + + ```bash + tx gov submit-proposal update-client + ``` + +The `` identifier is the proposed client to be updated. This client must be either frozen or expired. + +The `` represents a substitute client. It carries all the state for the client which may be updated. It must have identical client and chain parameters to the client which may be updated (except for latest height, frozen height, and chain ID). It should be continually updated during the voting period. + +After this, all that remains is deciding who funds the governance deposit and ensuring the governance proposal passes. If it does, the client on trial will be updated to the latest state of the substitute. + +## Important considerations + +Please note that from v1.0.0 of ibc-go it will not be allowed for transactions to go to expired clients anymore, so please update to at least this version to prevent similar issues in the future. + +Please also note that if the client on the other end of the transaction is also expired, that client will also need to update. This process updates only one client. diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/07-relayer.md b/docs/versioned_docs/version-v5.4.x/01-ibc/07-relayer.md new file mode 100644 index 0000000..3d63e97 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/01-ibc/07-relayer.md @@ -0,0 +1,53 @@ +--- +title: Relayer +sidebar_label: Relayer +sidebar_position: 7 +slug: /ibc/relayer +--- + +# Relayer + +:::note + +## Pre-requisite readings + +- [IBC Overview](01-overview.md) +- Events + +::: + +## Events + +Events are emitted for every transaction processed by the base application to indicate the execution +of some logic clients may want to be aware of. This is extremely useful when relaying IBC packets. +Any message that uses IBC will emit events for the corresponding TAO logic executed as defined in +the [IBC events document](/events/events). + +In the SDK, it can be assumed that for every message there is an event emitted with the type `message`, +attribute key `action`, and an attribute value representing the type of message sent +(`channel_open_init` would be the attribute value for `MsgChannelOpenInit`). If a relayer queries +for transaction events, it can split message events using this event Type/Attribute Key pair. + +The Event Type `message` with the Attribute Key `module` may be emitted multiple times for a single +message due to application callbacks. It can be assumed that any TAO logic executed will result in +a module event emission with the attribute value `ibc_` (02-client emits `ibc_client`). + +### Subscribing with Tendermint + +Calling the Tendermint RPC method `Subscribe` via Tendermint's Websocket will return events using +Tendermint's internal representation of them. Instead of receiving back a list of events as they +were emitted, Tendermint will return the type `map[string][]string` which maps a string in the +form `.` to `attribute_value`. This causes extraction of the event +ordering to be non-trivial, but still possible. + +A relayer should use the `message.action` key to extract the number of messages in the transaction +and the type of IBC transactions sent. For every IBC transaction within the string array for +`message.action`, the necessary information should be extracted from the other event fields. If +`send_packet` appears at index 2 in the value for `message.action`, a relayer will need to use the +value at index 2 of the key `send_packet.packet_sequence`. This process should be repeated for each +piece of information needed to relay a packet. + +## Example Implementations + +- [Golang Relayer](https://github.com/cosmos/relayer) +- [Hermes](https://github.com/informalsystems/hermes) diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/08-proto-docs.md b/docs/versioned_docs/version-v5.4.x/01-ibc/08-proto-docs.md new file mode 100644 index 0000000..fd4a9cc --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/01-ibc/08-proto-docs.md @@ -0,0 +1,10 @@ +--- +title: Protobuf Documentation +sidebar_label: Protobuf Documentation +sidebar_position: 8 +slug: /ibc/proto-docs +--- + +# Protobuf documentation + +See [ibc-go v5.3.x Buf Protobuf documentation](https://github.com/cosmos/ibc-go/blob/release/v5.3.x/docs/ibc/proto-docs.md). diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/09-roadmap.md b/docs/versioned_docs/version-v5.4.x/01-ibc/09-roadmap.md new file mode 100644 index 0000000..86ffb8a --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/01-ibc/09-roadmap.md @@ -0,0 +1,60 @@ +--- +title: Roadmap +sidebar_label: Roadmap +sidebar_position: 9 +slug: /roadmap/roadmap +--- + +# Roadmap ibc-go + +*Latest update: July 7, 2022* + +This document endeavours to inform the wider IBC community about plans and priorities for work on ibc-go by the team at Interchain GmbH. It is intended to broadly inform all users of ibc-go, including developers and operators of IBC, relayer, chain and wallet applications. + +This roadmap should be read as a high-level guide, rather than a commitment to schedules and deliverables. The degree of specificity is inversely proportional to the timeline. We will update this document periodically to reflect the status and plans. + +## Q3 - 2022 + +At a high level we will focus on: + +### Features + +- Releasing [v4.0.0](https://github.com/cosmos/ibc-go/milestone/26), which includes the ICS-29 Fee Middleware module. +- Finishing and releasing the [refactoring of 02-client](https://github.com/cosmos/ibc-go/milestone/16). This refactor will make the development of light clients easier. +- Starting the implementation of channel upgradability (see [epic](https://github.com/cosmos/ibc-go/issues/1599) and [alpha milestone](https://github.com/cosmos/ibc-go/milestone/29)) with the goal of cutting an alpha1 pre-release by the end of the quarter. Channel upgradability will allow chains to renegotiate an existing channel to take advantage of new features without having to create a new channel, thus preserving all existing packet state processed on the channel. +- Implementing the new [`ORDERED_ALLOW_TIMEOUT` channel type](https://github.com/cosmos/ibc-go/milestone/31) and hopefully releasing it as well. This new channel type will allow packets on an ordered channel to timeout without causing the closure of the channel. + +### Testing and infrastructure + +- Adding [automated e2e tests](https://github.com/cosmos/ibc-go/milestone/32) to the repo's CI. + +### Documentation and backlog + +- Finishing and releasing the upgrade to Cosmos SDK v0.46. +- Writing the [light client implementation guide](https://github.com/cosmos/ibc-go/issues/59). +- Working on [core backlog issues](https://github.com/cosmos/ibc-go/milestone/28). +- Depending on the timeline of the Cosmos SDK, implementing and testing the changes needed to support the [transition to SMT storage](https://github.com/cosmos/ibc-go/milestone/21). + +We have also received a lot of feedback to improve Interchain Accounts and we might also work on a few things, but will depend on priorities and availability. + +For a detail view of each iteration's planned work, please check out our [project board](https://github.com/orgs/cosmos/projects/7). + +### Release schedule + +#### **July** + +We will probably cut at least one more release candidate of v4.0.0 before the final release, which should happen around the end of the month. + +For the Rho upgrade of the Cosmos Hub we will also release a new minor version of v3 with SDK 0.46. + +#### **August** + +In the first half we will probably start cutting release candidates for the 02-client refactor. Final release would most likely come out at the end of the month or beginning of September. + +#### **September** + +We might cut some pre-releases for the new channel type, and by the end of the month we expect to cut the first alpha pre-release for channel upgradability. + +## Q4 - 2022 + +We will continue the implementation and cut the final release of [channel upgradability](https://github.com/cosmos/ibc/blob/master/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md). At the end of Q3 or maybe beginning of Q4 we might also work on designing the implementation and scoping the engineering work to add support for [multihop channels](https://github.com/cosmos/ibc/pull/741/files), so that we could start the implementation of this feature during Q4 (but this is still be decided). diff --git a/docs/versioned_docs/version-v5.4.x/01-ibc/_category_.json b/docs/versioned_docs/version-v5.4.x/01-ibc/_category_.json new file mode 100644 index 0000000..afc6d18 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/01-ibc/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Using IBC-Go", + "position": 1, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/01-overview.md b/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/01-overview.md new file mode 100644 index 0000000..ed696b3 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/01-overview.md @@ -0,0 +1,128 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /apps/transfer/overview +--- + +# Overview + +:::note Synopsis +Learn about what the token Transfer module is +::: + +## What is the Transfer module? + +Transfer is the Cosmos SDK implementation of the [ICS-20](https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer) protocol, which enables cross-chain fungible token transfers. + +## Concepts + +### Acknowledgements + +ICS20 uses the recommended acknowledgement format as specified by [ICS 04](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope). + +A successful receive of a transfer packet will result in a Result Acknowledgement being written +with the value `[]byte{byte(1)}` in the `Response` field. + +An unsuccessful receive of a transfer packet will result in an Error Acknowledgement being written +with the error message in the `Response` field. + +### Denomination trace + +The denomination trace corresponds to the information that allows a token to be traced back to its +origin chain. It contains a sequence of port and channel identifiers ordered from the most recent to +the oldest in the timeline of transfers. + +This information is included on the token denomination field in the form of a hash to prevent an +unbounded denomination length. For example, the token `transfer/channelToA/uatom` will be displayed +as `ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2`. + +Each send to any chain other than the one it was previously received from is a movement forwards in +the token's timeline. This causes trace to be added to the token's history and the destination port +and destination channel to be prefixed to the denomination. In these instances the sender chain is +acting as the "source zone". When the token is sent back to the chain it previously received from, the +prefix is removed. This is a backwards movement in the token's timeline and the sender chain is +acting as the "sink zone". + +It is strongly recommended to read the full details of [ADR 001: Coin Source Tracing](/architecture/adr-001-coin-source-tracing) to understand the implications and context of the IBC token representations. + +## UX suggestions for clients + +For clients (wallets, exchanges, applications, block explorers, etc) that want to display the source of the token, it is recommended to use the following alternatives for each of the cases below: + +### Direct connection + +If the denomination trace contains a single identifier prefix pair (as in the example above), then +the easiest way to retrieve the chain and light client identifier is to map the trace information +directly. In summary, this requires querying the channel from the denomination trace identifiers, +and then the counterparty client state using the counterparty port and channel identifiers from the +retrieved channel. + +A general pseudo algorithm would look like the following: + +1. Query the full denomination trace. +2. Query the channel with the `portID/channelID` pair, which corresponds to the first destination of the + token. +3. Query the client state using the identifiers pair. Note that this query will return a `"Not +Found"` response if the current chain is not connected to this channel. +4. Retrieve the client identifier or chain identifier from the client state (eg: on + Tendermint clients) and store it locally. + +Using the gRPC gateway client service the steps above would be, with a given IBC token `ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2` stored on `chainB`: + +1. `GET /ibc/apps/transfer/v1/denom_traces/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2` -> `{"path": "transfer/channelToA", "base_denom": "uatom"}` +2. `GET /ibc/apps/transfer/v1/channels/channelToA/ports/transfer/client_state"` -> `{"client_id": "clientA", "chain-id": "chainA", ...}` +3. `GET /ibc/apps/transfer/v1/channels/channelToA/ports/transfer"` -> `{"channel_id": "channelToA", port_id": "transfer", counterparty: {"channel_id": "channelToB", port_id": "transfer"}, ...}` +4. `GET /ibc/apps/transfer/v1/channels/channelToB/ports/transfer/client_state" -> {"client_id": "clientB", "chain-id": "chainB", ...}` + +Then, the token transfer chain path for the `uatom` denomination would be: `chainA` -> `chainB`. + +### Multiple hops + +The multiple channel hops case applies when the token has passed through multiple chains between the original source and final destination chains. + +The IBC protocol doesn't know the topology of the overall network (i.e connections between chains and identifier names between them). For this reason, in the multiple hops case, a particular chain in the timeline of the individual transfers can't query the chain and client identifiers of the other chains. + +Take for example the following sequence of transfers `A -> B -> C` for an IBC token, with a final prefix path (trace info) of `transfer/channelChainC/transfer/channelChainB`. What the paragraph above means is that even in the case that chain `C` is directly connected to chain `A`, querying the port and channel identifiers that chain `B` uses to connect to chain `A` (eg: `transfer/channelChainA`) can be completely different from the one that chain `C` uses to connect to chain `A` (eg: `transfer/channelToChainA`). + +Thus the proposed solution for clients that the IBC team recommends are the following: + +- **Connect to all chains**: Connecting to all the chains in the timeline would allow clients to + perform the queries outlined in the [direct connection](#direct-connection) section to each + relevant chain. By repeatedly following the port and channel denomination trace transfer timeline, + clients should always be able to find all the relevant identifiers. This comes at the tradeoff + that the client must connect to nodes on each of the chains in order to perform the queries. +- **Relayer as a Service (RaaS)**: A longer term solution is to use/create a relayer service that + could map the denomination trace to the chain path timeline for each token (i.e `origin chain -> +chain #1 -> ... -> chain #(n-1) -> final chain`). These services could provide merkle proofs in + order to allow clients to optionally verify the path timeline correctness for themselves by + running light clients. If the proofs are not verified, they should be considered as trusted third + parties services. Additionally, client would be advised in the future to use RaaS that support the + largest number of connections between chains in the ecosystem. Unfortunately, none of the existing + public relayers (in [Golang](https://github.com/cosmos/relayer) and + [Rust](https://github.com/informalsystems/ibc-rs)), provide this service to clients. + +:::tip +The only viable alternative for clients (at the time of writing) to tokens with multiple connection hops, is to connect to all chains directly and perform relevant queries to each of them in the sequence. +::: + +## Locked funds + +In some [exceptional cases](/architecture/adr-026-ibc-client-recovery-mechanisms#exceptional-cases), a client state associated with a given channel cannot be updated. This causes that funds from fungible tokens in that channel will be permanently locked and thus can no longer be transferred. + +To mitigate this, a client update governance proposal can be submitted to update the frozen client +with a new valid header. Once the proposal passes the client state will be unfrozen and the funds +from the associated channels will then be unlocked. This mechanism only applies to clients that +allow updates via governance, such as Tendermint clients. + +In addition to this, it's important to mention that a token must be sent back along the exact route +that it took originally in order to return it to its original form on the source chain (eg: the +Cosmos Hub for the `uatom`). Sending a token back to the same chain across a different channel will +**not** move the token back across its timeline. If a channel in the chain history closes before the +token can be sent back across that channel, then the token will not be returnable to its original +form. + +## Security considerations + +For safety, no other module must be capable of minting tokens with the `ibc/` prefix. The IBC +transfer module needs a subset of the denomination space that only it can create tokens in. diff --git a/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/02-state.md b/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/02-state.md new file mode 100644 index 0000000..17f48a2 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/02-state.md @@ -0,0 +1,13 @@ +--- +title: State +sidebar_label: State +sidebar_position: 2 +slug: /apps/transfer/state +--- + +# State + +The IBC transfer application module keeps state of the port to which the module is binded and the denomination trace information as outlined in [ADR 001](/architecture/adr-001-coin-source-tracing). + +- `Port`: `0x01 -> ProtocolBuffer(string)` +- `DenomTrace`: `0x02 | []bytes(traceHash) -> ProtocolBuffer(DenomTrace)` diff --git a/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/03-state-transitions.md b/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/03-state-transitions.md new file mode 100644 index 0000000..23bf1bf --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/03-state-transitions.md @@ -0,0 +1,37 @@ +--- +title: State Transitions +sidebar_label: State Transitions +sidebar_position: 3 +slug: /apps/transfer/state-transitions +--- + +# State transitions + +## Send fungible tokens + +A successful fungible token send has two state transitions depending if the transfer is a movement forward or backwards in the token's timeline: + +1. Sender chain is the source chain, *i.e* a transfer to any chain other than the one it was previously received from is a movement forwards in the token's timeline. This results in the following state transitions: + + - The coins are transferred to an escrow address (i.e locked) on the sender chain. + - The coins are transferred to the receiving chain through IBC TAO logic. + +2. Sender chain is the sink chain, *i.e* the token is sent back to the chain it previously received from. This is a backwards movement in the token's timeline. This results in the following state transitions: + + - The coins (vouchers) are burned on the sender chain. + - The coins are transferred to the receiving chain through IBC TAO logic. + +## Receive fungible tokens + +A successful fungible token receive has two state transitions depending if the transfer is a movement forward or backwards in the token's timeline: + +1. Receiver chain is the source chain. This is a backwards movement in the token's timeline. This results in the following state transitions: + + - The leftmost port and channel identifier pair is removed from the token denomination prefix. + - The tokens are unescrowed and sent to the receiving address. + +2. Receiver chain is the sink chain. This is a movement forwards in the token's timeline. This results in the following state transitions: + + - Token vouchers are minted by prefixing the destination port and channel identifiers to the trace information. + - The receiving chain stores the new trace information in the store (if not set already). + - The vouchers are sent to the receiving address. diff --git a/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/04-messages.md b/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/04-messages.md new file mode 100644 index 0000000..1e27c60 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/04-messages.md @@ -0,0 +1,46 @@ +--- +title: Messages +sidebar_label: Messages +sidebar_position: 4 +slug: /apps/transfer/messages +--- + +# Messages + +## `MsgTransfer` + +A fungible token cross chain transfer is achieved by using the `MsgTransfer`: + +```go +type MsgTransfer struct { + SourcePort string + SourceChannel string + Token sdk.Coin + Sender string + Receiver string + TimeoutHeight ibcexported.Height + TimeoutTimestamp uint64 + Memo string +} +``` + +This message is expected to fail if: + +- `SourcePort` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators). +- `SourceChannel` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +- `Token` is invalid (denom is invalid or amount is negative) + - `Token.Amount` is not positive. + - `Token.Denom` is not a valid IBC denomination as per [ADR 001 - Coin Source Tracing](/architecture/adr-001-coin-source-tracing). +- `Sender` is empty. +- `Receiver` is empty. +- `TimeoutHeight` and `TimeoutTimestamp` are both zero. + +This message will send a fungible token to the counterparty chain represented by the counterparty Channel End connected to the Channel End with the identifiers `SourcePort` and `SourceChannel`. + +The denomination provided for transfer should correspond to the same denomination represented on this chain. The prefixes will be added as necessary upon by the receiving chain. + +### Memo + +The memo field was added to allow applications and users to attach metadata to transfer packets. The field is optional and may be left empty. When it is used to attach metadata for a particular middleware, the memo field should be represented as a json object where different middlewares use different json keys. + +You can find more information about applications that use the memo field in the [chain registry](https://github.com/cosmos/chain-registry/blob/master/_memo_keys/ICS20_memo_keys.json). diff --git a/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/05-events.md b/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/05-events.md new file mode 100644 index 0000000..68eb4a4 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/05-events.md @@ -0,0 +1,55 @@ +--- +title: Events +sidebar_label: Events +sidebar_position: 5 +slug: /apps/transfer/events +--- + + +# Events + +## `MsgTransfer` + +| Type | Attribute Key | Attribute Value | +|--------------|---------------|-----------------| +| ibc_transfer | sender | \{sender\} | +| ibc_transfer | receiver | \{receiver\} | +| message | module | transfer | + +## `OnRecvPacket` callback + +| Type | Attribute Key | Attribute Value | +|-----------------------|---------------|-----------------| +| fungible_token_packet | module | transfer | +| fungible_token_packet | sender | \{sender\} | +| fungible_token_packet | receiver | \{receiver\} | +| fungible_token_packet | denom | \{denom\} | +| fungible_token_packet | amount | \{amount\} | +| fungible_token_packet | memo | \{memo\} | +| fungible_token_packet | success | \{ackSuccess\} | +| fungible_token_packet | error | \{ackError\} | +| denomination_trace | trace_hash | \{hex_hash\} | +| denomination_trace | denom | \{voucherDenom\}| + +## `OnAcknowledgePacket` callback + +| Type | Attribute Key | Attribute Value | +|-----------------------|-----------------|------------------| +| fungible_token_packet | module | transfer | +| fungible_token_packet | sender | \{sender\} | +| fungible_token_packet | receiver | \{receiver\} | +| fungible_token_packet | denom | \{denom\} | +| fungible_token_packet | amount | \{amount\} | +| fungible_token_packet | memo | \{memo\} | +| fungible_token_packet | acknowledgement | \{ack.String()\} | +| fungible_token_packet | success / error | \{ack.Response\} | + +## `OnTimeoutPacket` callback + +| Type | Attribute Key | Attribute Value | +|-----------------------|-----------------|-----------------| +| fungible_token_packet | module | transfer | +| fungible_token_packet | refund_receiver | \{receiver\} | +| fungible_token_packet | denom | \{denom\} | +| fungible_token_packet | amount | \{amount\} | +| fungible_token_packet | memo | \{memo\} | diff --git a/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/06-metrics.md b/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/06-metrics.md new file mode 100644 index 0000000..7f6087e --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/06-metrics.md @@ -0,0 +1,18 @@ +--- +title: Metrics +sidebar_label: Metrics +sidebar_position: 6 +slug: /apps/transfer/metrics +--- + + +# Metrics + +The IBC transfer application module exposes the following set of metrics. + +| Metric | Description | Unit | Type | +|:--------------------------------|:------------------------------------------------------------------------------------------|:----------------|:--------| +| `tx_msg_ibc_transfer` | The total amount of tokens transferred via IBC in a `MsgTransfer` (source or sink chain) | token | gauge | +| `ibc_transfer_packet_receive` | The total amount of tokens received in a `FungibleTokenPacketData` (source or sink chain) | token | gauge | +| `ibc_transfer_send` | Total number of IBC transfers sent from a chain (source or sink) | transfer | counter | +| `ibc_transfer_receive` | Total number of IBC transfers received to a chain (source or sink) | transfer | counter | diff --git a/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/07-params.md b/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/07-params.md new file mode 100644 index 0000000..c4c9330 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/07-params.md @@ -0,0 +1,34 @@ +--- +title: Params +sidebar_label: Params +sidebar_position: 7 +slug: /apps/transfer/params +--- + + +# Parameters + +The IBC transfer application module contains the following parameters: + +| Key | Type | Default Value | +|------------------|------|---------------| +| `SendEnabled` | bool | `true` | +| `ReceiveEnabled` | bool | `true` | + +## `SendEnabled` + +The transfers enabled parameter controls send cross-chain transfer capabilities for all fungible tokens. + +To prevent a single token from being transferred from the chain, set the `SendEnabled` parameter to `true` and then, depending on the Cosmos SDK version, do one of the following: + +- For Cosmos SDK v0.46.x or earlier, set the bank module's [`SendEnabled` parameter](https://github.com/cosmos/cosmos-sdk/blob/release/v0.46.x/x/bank/spec/05_params.md#sendenabled) for the denomination to `false`. +- For Cosmos SDK versions above v0.46.x, set the bank module's `SendEnabled` entry for the denomination to `false` using `MsgSetSendEnabled` as a governance proposal. + +## `ReceiveEnabled` + +The transfers enabled parameter controls receive cross-chain transfer capabilities for all fungible tokens. + +To prevent a single token from being transferred to the chain, set the `ReceiveEnabled` parameter to `true` and then, depending on the Cosmos SDK version, do one of the following: + +- For Cosmos SDK v0.46.x or earlier, set the bank module's [`SendEnabled` parameter](https://github.com/cosmos/cosmos-sdk/blob/release/v0.46.x/x/bank/spec/05_params.md#sendenabled) for the denomination to `false`. +- For Cosmos SDK versions above v0.46.x, set the bank module's `SendEnabled` entry for the denomination to `false` using `MsgSetSendEnabled` as a governance proposal. diff --git a/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/_category_.json b/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/_category_.json new file mode 100644 index 0000000..50d492b --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/02-apps/01-transfer/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Transfer", + "position": 1, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/01-overview.md b/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/01-overview.md new file mode 100644 index 0000000..3f3ab23 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/01-overview.md @@ -0,0 +1,43 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /apps/interchain-accounts/overview +--- + + +# Overview + +:::note Synopsis +Learn about what the Interchain Accounts module is, and how to build custom modules that utilize Interchain Accounts functionality +::: + +## What is the Interchain Accounts module? + +Interchain Accounts is the Cosmos SDK implementation of the ICS-27 protocol, which enables cross-chain account management built upon IBC. Chains using the Interchain Accounts module can programmatically create accounts on other chains and control these accounts via IBC transactions. + +Interchain Accounts exposes a simple-to-use API which means IBC application developers do not require an in-depth knowledge of the underlying low-level details of IBC or the ICS-27 protocol. + +Developers looking to build upon Interchain Accounts must write custom logic in their own IBC application module, called authentication modules. + +- How is an interchain account different than a regular account? + +Regular accounts use a private key to sign transactions on-chain. Interchain Accounts are instead controlled programmatically by separate chains via IBC transactions. Interchain Accounts are implemented as sub-accounts of the interchain accounts module account. + +## Concepts + +`Host Chain`: The chain where the interchain account is registered. The host chain listens for IBC packets from a controller chain which should contain instructions (e.g. cosmos SDK messages) for which the interchain account will execute. + +`Controller Chain`: The chain registering and controlling an account on a host chain. The controller chain sends IBC packets to the host chain to control the account. A controller chain must have at least one interchain accounts authentication module in order to act as a controller chain. + +`Authentication Module`: A custom IBC application module on the controller chain that uses the Interchain Accounts module API to build custom logic for the creation & management of interchain accounts. For a controller chain to utilize the interchain accounts module functionality, an authentication module is required. + +`Interchain Account`: An account on a host chain. An interchain account has all the capabilities of a normal account. However, rather than signing transactions with a private key, a controller chain's authentication module will send IBC packets to the host chain which signals what transactions the interchain account should execute. + +## SDK Security Model + +SDK modules on a chain are assumed to be trustworthy. For example, there are no checks to prevent an untrustworthy module from accessing the bank keeper. + +The implementation of ICS27 on ibc-go uses this assumption in its security considerations. The implementation assumes the authentication module will not try to open channels on owner addresses it does not control. + +The implementation assumes other IBC application modules will not bind to ports within the ICS27 namespace. diff --git a/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/02-auth-modules.md b/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/02-auth-modules.md new file mode 100644 index 0000000..27d4e59 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/02-auth-modules.md @@ -0,0 +1,402 @@ +--- +title: Authentication Modules +sidebar_label: Authentication Modules +sidebar_position: 2 +slug: /apps/interchain-accounts/auth-modules +--- + + +# Building an authentication module + +:::note Synopsis +Authentication modules play the role of the `Base Application` as described in [ICS30 IBC Middleware](https://github.com/cosmos/ibc/tree/master/spec/app/ics-030-middleware), and enable application developers to perform custom logic when working with the Interchain Accounts controller API. +::: + +The controller submodule is used for account registration and packet sending. +It executes only logic required of all controllers of interchain accounts. +The type of authentication used to manage the interchain accounts remains unspecified. +There may exist many different types of authentication which are desirable for different use cases. +Thus the purpose of the authentication module is to wrap the controller module with custom authentication logic. + +In ibc-go, authentication modules are connected to the controller chain via a middleware stack. +The controller module is implemented as [middleware](https://github.com/cosmos/ibc/tree/master/spec/app/ics-030-middleware) and the authentication module is connected to the controller module as the base application of the middleware stack. +To implement an authentication module, the `IBCModule` interface must be fulfilled. +By implementing the controller module as middleware, any amount of authentication modules can be created and connected to the controller module without writing redundant code. + +The authentication module must: + +- Authenticate interchain account owners +- Track the associated interchain account address for an owner +- Claim the channel capability in `OnChanOpenInit` +- Send packets on behalf of an owner (after authentication) + +## IBCModule implementation + +The following `IBCModule` callbacks must be implemented with appropriate custom logic: + +```go +// OnChanOpenInit implements the IBCModule interface +func (im IBCModule) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + // the authentication module *must* claim the channel capability on OnChanOpenInit + if err := im.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return version, err + } + + // perform custom logic + + return version, nil +} + +// OnChanOpenAck implements the IBCModule interface +func (im IBCModule) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyVersion string, +) error { + // perform custom logic + + return nil +} + +// OnChanCloseConfirm implements the IBCModule interface +func (im IBCModule) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // perform custom logic + + return nil +} + +// OnAcknowledgementPacket implements the IBCModule interface +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + // perform custom logic + + return nil +} + +// OnTimeoutPacket implements the IBCModule interface. +func (im IBCModule) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + // perform custom logic + + return nil +} +``` + +**Note**: The channel capability must be claimed by the authentication module in `OnChanOpenInit` otherwise the authentication module will not be able to send packets on the channel created for the associated interchain account. + +The following functions must be defined to fulfill the `IBCModule` interface, but they will never be called by the controller module so they may error or panic. + +```go +// OnChanOpenTry implements the IBCModule interface +func (im IBCModule) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + panic("UNIMPLEMENTED") +} + +// OnChanOpenConfirm implements the IBCModule interface +func (im IBCModule) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + panic("UNIMPLEMENTED") +} + +// OnChanCloseInit implements the IBCModule interface +func (im IBCModule) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + panic("UNIMPLEMENTED") +} + +// OnRecvPacket implements the IBCModule interface. A successful acknowledgement +// is returned if the packet data is successfully decoded and the receive application +// logic returns without error. +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) ibcexported.Acknowledgement { + panic("UNIMPLEMENTED") +} +``` + +## `RegisterInterchainAccount` + +The authentication module can begin registering interchain accounts by calling `RegisterInterchainAccount`: + +```go +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, connectionID, owner.String(), version); err != nil { + return err +} + +return nil +``` + +The `version` argument is used to support ICS29 fee middleware for relayer incentivization of ICS27 packets. Consumers of the `RegisterInterchainAccount` are expected to build the appropriate JSON encoded version string themselves and pass it accordingly. If an empty string is passed in the `version` argument, then the version will be initialized to a default value in the `OnChanOpenInit` callback of the controller's handler, so that channel handshake can proceed. + +The following code snippet illustrates how to construct an appropriate interchain accounts `Metadata` and encode it as a JSON bytestring: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, controllerConnectionID, owner.String(), string(appVersion)); err != nil { + return err +} +``` + +Similarly, if the application stack is configured to route through ICS29 fee middleware and a fee enabled channel is desired, construct the appropriate ICS29 `Metadata` type: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +feeMetadata := feetypes.Metadata{ + AppVersion: string(appVersion), + FeeVersion: feetypes.Version, +} + +feeEnabledVersion, err := feetypes.ModuleCdc.MarshalJSON(&feeMetadata) +if err != nil { + return err +} + +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, controllerConnectionID, owner.String(), string(feeEnabledVersion)); err != nil { + return err +} +``` + +## `SendTx` + +The authentication module can attempt to send a packet by calling `SendTx`: + +```go + +// Authenticate owner +// perform custom logic + +// Construct controller portID based on interchain account owner address +portID, err := icatypes.NewControllerPortID(owner.String()) +if err != nil { + return err +} + +channelID, found := keeper.icaControllerKeeper.GetActiveChannelID(ctx, portID) +if !found { + return sdkerrors.Wrapf(icatypes.ErrActiveChannelNotFound, "failed to retrieve active channel for port %s", portID) +} + +// Obtain the channel capability, claimed in OnChanOpenInit +chanCap, found := keeper.scopedKeeper.GetCapability(ctx, host.ChannelCapabilityPath(portID, channelID)) +if !found { + return sdkerrors.Wrap(channeltypes.ErrChannelCapabilityNotFound, "module does not own channel capability") +} + +// Obtain data to be sent to the host chain. +// In this example, the owner of the interchain account would like to send a bank MsgSend to the host chain. +// The appropriate serialization function should be called. The host chain must be able to deserialize the transaction. +// If the host chain is using the ibc-go host module, `SerializeCosmosTx` should be used. +msg := &banktypes.MsgSend{FromAddress: fromAddr, ToAddress: toAddr, Amount: amt} +data, err := icatypes.SerializeCosmosTx(keeper.cdc, []sdk.Msg{msg}) +if err != nil { + return err +} + +// Construct packet data +packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, +} + +// Obtain timeout timestamp +// An appropriate timeout timestamp must be determined based on the usage of the interchain account. +// If the packet times out, the channel will be closed requiring a new channel to be created +timeoutTimestamp := obtainTimeoutTimestamp() + +// Send the interchain accounts packet, returning the packet sequence +seq, err = keeper.icaControllerKeeper.SendTx(ctx, chanCap, portID, packetData, timeoutTimestamp) +``` + +The data within an `InterchainAccountPacketData` must be serialized using a format supported by the host chain. +If the host chain is using the ibc-go host chain submodule, `SerializeCosmosTx` should be used. If the `InterchainAccountPacketData.Data` is serialized using a format not support by the host chain, the packet will not be successfully received. + +## `OnAcknowledgementPacket` + +Controller chains will be able to access the acknowledgement written into the host chain state once a relayer relays the acknowledgement. +The acknowledgement bytes will be passed to the auth module via the `OnAcknowledgementPacket` callback. +Auth modules are expected to know how to decode the acknowledgement. + +If the controller chain is connected to a host chain using the host module on ibc-go, it may interpret the acknowledgement bytes as follows: + +Begin by unmarshaling the acknowledgement into `sdk.TxMsgData`: + +```go +var ack channeltypes.Acknowledgement +if err := channeltypes.SubModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { + return err +} + +txMsgData := &sdk.TxMsgData{} +if err := proto.Unmarshal(ack.GetResult(), txMsgData); err != nil { + return err +} +``` + +If the `txMsgData.Data` field is non nil, the host chain is using SDK version \<\= v0.45. +The auth module should interpret the `txMsgData.Data` as follows: + +```go +switch len(txMsgData.Data) { +case 0: + // see documentation below for SDK 0.46.x or greater +default: + for _, msgData := range txMsgData.Data { + if err := handler(msgData); err != nil { + return err + } + } +... +} +``` + +A handler will be needed to interpret what actions to perform based on the message type sent. +A router could be used, or more simply a switch statement. + +```go +func handler(msgData sdk.MsgData) error { +switch msgData.MsgType { +case sdk.MsgTypeURL(&banktypes.MsgSend{}): + msgResponse := &banktypes.MsgSendResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleBankSendMsg(msgResponse) + +case sdk.MsgTypeURL(&stakingtypes.MsgDelegate{}): + msgResponse := &stakingtypes.MsgDelegateResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleStakingDelegateMsg(msgResponse) + +case sdk.MsgTypeURL(&transfertypes.MsgTransfer{}): + msgResponse := &transfertypes.MsgTransferResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleIBCTransferMsg(msgResponse) + +default: + return +} +``` + +If the `txMsgData.Data` is empty, the host chain is using SDK version > v0.45. +The auth module should interpret the `txMsgData.Responses` as follows: + +```go +... +// switch statement from above +case 0: + for _, any := range txMsgData.MsgResponses { + if err := handleAny(any); err != nil { + return err + } + } +} +``` + +A handler will be needed to interpret what actions to perform based on the type url of the Any. +A router could be used, or more simply a switch statement. +It may be possible to deduplicate logic between `handler` and `handleAny`. + +```go +func handleAny(any *codectypes.Any) error { +switch any.TypeURL { +case banktypes.MsgSend: + msgResponse, err := unpackBankMsgSendResponse(any) + if err != nil { + return err + } + + handleBankSendMsg(msgResponse) + +case stakingtypes.MsgDelegate: + msgResponse, err := unpackStakingDelegateResponse(any) + if err != nil { + return err + } + + handleStakingDelegateMsg(msgResponse) + + case transfertypes.MsgTransfer: + msgResponse, err := unpackIBCTransferMsgResponse(any) + if err != nil { + return err + } + + handleIBCTransferMsg(msgResponse) + +default: + return +} +``` + +### Integration into `app.go` file + +To integrate the authentication module into your chain, please follow the steps outlined above in [app.go integration](04-integration.md#example-integration). diff --git a/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/03-active-channels.md b/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/03-active-channels.md new file mode 100644 index 0000000..500deb6 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/03-active-channels.md @@ -0,0 +1,28 @@ +--- +title: Active Channels +sidebar_label: Active Channels +sidebar_position: 3 +slug: /apps/interchain-accounts/active-channels +--- + + +# Understanding Active Channels + +The Interchain Accounts module uses [ORDERED channels](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#ordering) to maintain the order of transactions when sending packets from a controller to a host chain. A limitation when using ORDERED channels is that when a packet times out the channel will be closed. + +In the case of a channel closing, a controller chain needs to be able to regain access to the interchain account registered on this channel. `Active Channels` enable this functionality. Future versions of the ICS-27 protocol and the Interchain Accounts module will likely use a new +channel type that provides ordering of packets without the channel closing on timing out, thus removing the need for `Active Channels` entirely. + +When an Interchain Account is registered using the `RegisterInterchainAccount` API, a new channel is created on a particular port. During the `OnChanOpenAck` and `OnChanOpenConfirm` steps (controller & host chain) the `Active Channel` for this interchain account +is stored in state. + +It is possible to create a new channel using the same controller chain portID if the previously set `Active Channel` is now in a `CLOSED` state. This channel creation can be initialized programmatically by sending a new `MsgChannelOpenInit` message like so: + +```go +msg := channeltypes.NewMsgChannelOpenInit(portID, string(versionBytes), channeltypes.ORDERED, []string{connectionID}, icatypes.PortID, icatypes.ModuleName) +handler := k.msgRouter.Handler(msg) +``` + +Alternatively, any relayer operator may initiate a new channel handshake for this interchain account once the previously set `Active Channel` is in a `CLOSED` state. This is done by initiating the channel handshake on the controller chain using the same portID associated with the interchain account in question. + +It is important to note that once a channel has been opened for a given Interchain Account, new channels can not be opened for this account until the currently set `Active Channel` is set to `CLOSED`. diff --git a/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/04-integration.md b/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/04-integration.md new file mode 100644 index 0000000..880c695 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/04-integration.md @@ -0,0 +1,194 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 4 +slug: /apps/interchain-accounts/integration +--- + + +# Integration + +:::note Synopsis +Learn how to integrate Interchain Accounts host and controller functionality to your chain. The following document only applies for Cosmos SDK chains. +::: + +The Interchain Accounts module contains two submodules. Each submodule has its own IBC application. The Interchain Accounts module should be registered as an `AppModule` in the same way all SDK modules are registered on a chain, but each submodule should create its own `IBCModule` as necessary. A route should be added to the IBC router for each submodule which will be used. + +Chains who wish to support ICS27 may elect to act as a host chain, a controller chain or both. Disabling host or controller functionality may be done statically by excluding the host or controller module entirely from the `app.go` file or it may be done dynamically by taking advantage of the on-chain parameters which enable or disable the host or controller submodules. + +Interchain Account authentication modules are the base application of a middleware stack. The controller submodule is the middleware in this stack. + +## Example integration + +```go +// app.go + +// Register the AppModule for the Interchain Accounts module and the authentication module +// Note: No `icaauth` exists, this must be substituted with an actual Interchain Accounts authentication module +ModuleBasics = module.NewBasicManager( + ... + ica.AppModuleBasic{}, + icaauth.AppModuleBasic{}, + ... +) + +... + +// Add module account permissions for the Interchain Accounts module +// Only necessary for host chain functionality +// Each Interchain Account created on the host chain is derived from the module account created +maccPerms = map[string][]string{ + ... + icatypes.ModuleName: nil, +} + +... + +// Add Interchain Accounts Keepers for each submodule used and the authentication module +// If a submodule is being statically disabled, the associated Keeper does not need to be added. +type App struct { + ... + + ICAControllerKeeper icacontrollerkeeper.Keeper + ICAHostKeeper icahostkeeper.Keeper + ICAAuthKeeper icaauthkeeper.Keeper + + ... +} + +... + +// Create store keys for each submodule Keeper and the authentication module +keys := sdk.NewKVStoreKeys( + ... + icacontrollertypes.StoreKey, + icahosttypes.StoreKey, + icaauthtypes.StoreKey, + ... +) + +... + +// Create the scoped keepers for each submodule keeper and authentication keeper +scopedICAControllerKeeper := app.CapabilityKeeper.ScopeToModule(icacontrollertypes.SubModuleName) +scopedICAHostKeeper := app.CapabilityKeeper.ScopeToModule(icahosttypes.SubModuleName) +scopedICAAuthKeeper := app.CapabilityKeeper.ScopeToModule(icaauthtypes.ModuleName) + +... + +// Create the Keeper for each submodule +app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + scopedICAControllerKeeper, app.MsgServiceRouter(), +) +app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.AccountKeeper, scopedICAHostKeeper, app.MsgServiceRouter(), +) + +// Create Interchain Accounts AppModule +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper) + +// Create your Interchain Accounts authentication module +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.ICAControllerKeeper, scopedICAAuthKeeper) + +// ICA auth AppModule +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) + +// ICA auth IBC Module +icaAuthIBCModule := icaauth.NewIBCModule(app.ICAAuthKeeper) + +// Create controller IBC application stack and host IBC module as desired +icaControllerStack := icacontroller.NewIBCMiddleware(icaAuthIBCModule, app.ICAControllerKeeper) +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host and authentication routes +ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icahosttypes.SubModuleName, icaHostIBCModule). + AddRoute(icaauthtypes.ModuleName, icaControllerStack) // Note, the authentication module is routed to the top level of the middleware stack + +... + +// Register Interchain Accounts and authentication module AppModule's +app.moduleManager = module.NewManager( + ... + icaModule, + icaAuthModule, +) + +... + +// Add fee middleware to begin blocker logic +app.moduleManager.SetOrderBeginBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add fee middleware to end blocker logic +app.moduleManager.SetOrderEndBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add Interchain Accounts module InitGenesis logic +app.moduleManager.SetOrderInitGenesis( + ... + icatypes.ModuleName, + ... +) + +// initParamsKeeper init params keeper and its subspaces +func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey sdk.StoreKey) paramskeeper.Keeper { + ... + paramsKeeper.Subspace(icahosttypes.SubModuleName) + paramsKeeper.Subspace(icacontrollertypes.SubModuleName) + ... +``` + +### Using submodules exclusively + +As described above, the Interchain Accounts application module is structured to support the ability of exclusively enabling controller or host functionality. +This can be achieved by simply omitting either controller or host `Keeper` from the Interchain Accounts `NewAppModule` constructor function, and mounting only the desired submodule via the `IBCRouter`. +Alternatively, submodules can be enabled and disabled dynamically using [on-chain parameters](05-parameters.md). + +The following snippets show basic examples of statically disabling submodules using `app.go`. + +#### Disabling controller chain functionality + +```go +// Create Interchain Accounts AppModule omitting the controller keeper +icaModule := ica.NewAppModule(nil, &app.ICAHostKeeper) + +// Create host IBC Module +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host route +ibcRouter.AddRoute(icahosttypes.SubModuleName, icaHostIBCModule) +``` + +#### Disabling host chain functionality + +```go +// Create Interchain Accounts AppModule omitting the host keeper +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, nil) + +// Create your Interchain Accounts authentication module, setting up the Keeper, AppModule and IBCModule appropriately +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.ICAControllerKeeper, scopedICAAuthKeeper) +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) +icaAuthIBCModule := icaauth.NewIBCModule(app.ICAAuthKeeper) + +// Create controller IBC application stack +icaControllerStack := icacontroller.NewIBCMiddleware(icaAuthIBCModule, app.ICAControllerKeeper) + +// Register controller and authentication routes +ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icaauthtypes.ModuleName, icaControllerStack) // Note, the authentication module is routed to the top level of the middleware stack +``` diff --git a/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/05-parameters.md b/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/05-parameters.md new file mode 100644 index 0000000..39036c3 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/05-parameters.md @@ -0,0 +1,65 @@ +--- +title: Parameters +sidebar_label: Parameters +sidebar_position: 5 +slug: /apps/interchain-accounts/parameters +--- + + +# Parameters + +The Interchain Accounts module contains the following on-chain parameters, logically separated for each distinct submodule: + +## Controller Submodule Parameters + +| Key | Type | Default Value | +|------------------------|------|---------------| +| `ControllerEnabled` | bool | `true` | + +### ControllerEnabled + +The `ControllerEnabled` parameter controls a chains ability to service ICS-27 controller specific logic. This includes the sending of Interchain Accounts packet data as well as the following ICS-26 callback handlers: + +- `OnChanOpenInit` +- `OnChanOpenAck` +- `OnChanCloseConfirm` +- `OnAcknowledgementPacket` +- `OnTimeoutPacket` + +## Host Submodule Parameters + +| Key | Type | Default Value | +|------------------------|----------|---------------| +| `HostEnabled` | bool | `true` | +| `AllowMessages` | []string | `[]` | + +### HostEnabled + +The `HostEnabled` parameter controls a chains ability to service ICS27 host specific logic. This includes the following ICS-26 callback handlers: + +- `OnChanOpenTry` +- `OnChanOpenConfirm` +- `OnChanCloseConfirm` +- `OnRecvPacket` + +### AllowMessages + +The `AllowMessages` parameter provides the ability for a chain to limit the types of messages or transactions that hosted interchain accounts are authorized to execute by defining an allowlist using the Protobuf message TypeURL format. + +For example, a Cosmos SDK based chain that elects to provide hosted Interchain Accounts with the ability of governance voting and staking delegations will define its parameters as follows: + +```json +"params": { + "host_enabled": true, + "allow_messages": ["/cosmos.staking.v1beta1.MsgDelegate", "/cosmos.gov.v1beta1.MsgVote"] +} +``` + +There is also a special wildcard `"*"` message type which allows any type of message to be executed by the interchain account. This must be the only message in the `allow_messages` array. + +```json +"params": { + "host_enabled": true, + "allow_messages": ["*"] +} +``` diff --git a/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/06-transactions.md b/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/06-transactions.md new file mode 100644 index 0000000..1d53027 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/06-transactions.md @@ -0,0 +1,27 @@ +--- +title: Transactions +sidebar_label: Transactions +sidebar_position: 6 +slug: /apps/interchain-accounts/transactions +--- + + +# Transactions + +:::note Synopsis +Learn about Interchain Accounts transaction execution +::: + +## Executing a transaction + +As described in [Authentication Modules](02-auth-modules.md#trysendtx) transactions are executed using the interchain accounts controller API and require a `Base Application` as outlined in [ICS30 IBC Middleware](https://github.com/cosmos/ibc/tree/master/spec/app/ics-030-middleware) to facilitate authentication. The method of authentication remains unspecified to provide flexibility for the authentication module developer. + +Transactions are executed via the ICS27 [`SendTx` API](02-auth-modules.md#trysendtx). This must be invoked through an Interchain Accounts authentication module and follows the outlined path of execution below. Packet relaying semantics provided by the IBC core transport, authentication, and ordering (IBC/TAO) layer are omitted for brevity. + +![send-interchain-tx.png](./images/send-interchain-tx.png) + +## Atomicity + +As the Interchain Accounts module supports the execution of multiple transactions using the Cosmos SDK `Msg` interface, it provides the same atomicity guarantees as Cosmos SDK-based applications, leveraging the [`CacheMultiStore`](https://docs.cosmos.network/main/learn/advanced/store#cachemultistore) architecture provided by the [`Context`](https://docs.cosmos.network/main/learn/advanced/context.html) type. + +This provides atomic execution of transactions when using Interchain Accounts, where state changes are only committed if all `Msg`s succeed. diff --git a/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/_category_.json b/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/_category_.json new file mode 100644 index 0000000..41e3ac2 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Interchain Accounts", + "position": 2, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/images/send-interchain-tx.png b/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/images/send-interchain-tx.png new file mode 100644 index 0000000..37a6576 Binary files /dev/null and b/docs/versioned_docs/version-v5.4.x/02-apps/02-interchain-accounts/images/send-interchain-tx.png differ diff --git a/docs/versioned_docs/version-v5.4.x/02-apps/_category_.json b/docs/versioned_docs/version-v5.4.x/02-apps/_category_.json new file mode 100644 index 0000000..83a389b --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/02-apps/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "IBC Application Modules", + "position": 2, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/01-overview.md b/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/01-overview.md new file mode 100644 index 0000000..0861286 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/01-overview.md @@ -0,0 +1,55 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /middleware/ics29-fee/overview +--- + + +# Overview + +:::note Synopsis +Learn about what the Fee Middleware module is, and how to build custom modules that utilize the Fee Middleware functionality +::: + +## What is the Fee Middleware module? + +IBC does not depend on relayer operators for transaction verification. However, the relayer infrastructure ensures liveness of the Interchain network — operators listen for packets sent through channels opened between chains, and perform the vital service of ferrying these packets (and proof of the transaction on the sending chain/receipt on the receiving chain) to the clients on each side of the channel. + +Though relaying is permissionless and completely decentralized and accessible, it does come with operational costs. Running full nodes to query transaction proofs and paying for transaction fees associated with IBC packets are two of the primary cost burdens which have driven the overall discussion on **a general, in-protocol incentivization mechanism for relayers**. + +Initially, a [simple proposal](https://github.com/cosmos/ibc/pull/577/files) was created to incentivize relaying on ICS20 token transfers on the destination chain. However, the proposal was specific to ICS20 token transfers and would have to be reimplemented in this format on every other IBC application module. + +After much discussion, the proposal was expanded to a [general incentivisation design](https://github.com/cosmos/ibc/tree/master/spec/app/ics-029-fee-payment) that can be adopted by any ICS application protocol as [middleware](../../01-ibc/04-middleware/01-develop.md). + +## Concepts + +ICS29 fee payments in this middleware design are built on the assumption that sender chains are the source of incentives — the chain on which packets are incentivized is the chain that distributes fees to relayer operators. However, as part of the IBC packet flow, messages have to be submitted on both sender and destination chains. This introduces the requirement of a mapping of relayer operator's addresses on both chains. + +To achieve the stated requirements, the **fee middleware module has two main groups of functionality**: + +- Registering of relayer addresses associated with each party involved in relaying the packet on the source chain. This registration process can be automated on start up of relayer infrastructure and happens only once, not every packet flow. + + This is described in the [Fee distribution section](04-fee-distribution.md). + +- Escrowing fees by any party which will be paid out to each rightful party on completion of the packet lifecycle. + + This is described in the [Fee messages section](03-msgs.md). + +We complete the introduction by giving a list of definitions of relevant terminology. + +`Forward relayer`: The relayer that submits the `MsgRecvPacket` message for a given packet (on the destination chain). + +`Reverse relayer`: The relayer that submits the `MsgAcknowledgement` message for a given packet (on the source chain). + +`Timeout relayer`: The relayer that submits the `MsgTimeout` or `MsgTimeoutOnClose` messages for a given packet (on the source chain). + +`Payee`: The account address on the source chain to be paid on completion of the packet lifecycle. The packet lifecycle on the source chain completes with the receipt of a `MsgTimeout`/`MsgTimeoutOnClose` or a `MsgAcknowledgement`. + +`Counterparty payee`: The account address to be paid on completion of the packet lifecycle on the destination chain. The package lifecycle on the destination chain completes with a successful `MsgRecvPacket`. + +`Refund address`: The address of the account paying for the incentivization of packet relaying. The account is refunded timeout fees upon successful acknowledgement. In the event of a packet timeout, both acknowledgement and receive fees are refunded. + +## Known Limitations + +The first version of fee payments middleware will only support incentivisation of new channels, however, channel upgradeability will enable incentivisation of all existing channels. diff --git a/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/02-integration.md b/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/02-integration.md new file mode 100644 index 0000000..0cac856 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/02-integration.md @@ -0,0 +1,174 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /middleware/ics29-fee/integration +--- + + +# Integration + +:::note Synopsis +Learn how to configure the Fee Middleware module with IBC applications. The following document is intended for developers building on top of the Cosmos SDK and only applies for Cosmos SDK chains. +::: + +## Pre-requisite Readings + +- [IBC middleware development](../../01-ibc/04-middleware/01-develop.md) +- [IBC middleware integration](../../01-ibc/04-middleware/02-integration.md) + +The Fee Middleware module, as the name suggests, plays the role of an IBC middleware and as such must be configured by chain developers to route and handle IBC messages correctly. +For Cosmos SDK chains this setup is done via the `app/app.go` file, where modules are constructed and configured in order to bootstrap the blockchain application. + +## Example integration of the Fee Middleware module + +```go +// app.go + +// Register the AppModule for the fee middleware module +ModuleBasics = module.NewBasicManager( + ... + ibcfee.AppModuleBasic{}, + ... +) + +... + +// Add module account permissions for the fee middleware module +maccPerms = map[string][]string{ + ... + ibcfeetypes.ModuleName: nil, +} + +... + +// Add fee middleware Keeper +type App struct { + ... + + IBCFeeKeeper ibcfeekeeper.Keeper + + ... +} + +... + +// Create store keys +keys := sdk.NewKVStoreKeys( + ... + ibcfeetypes.StoreKey, + ... +) + +... + +app.IBCFeeKeeper = ibcfeekeeper.NewKeeper( + appCodec, keys[ibcfeetypes.StoreKey], + app.IBCKeeper.ChannelKeeper, // may be replaced with IBC middleware + app.IBCKeeper.ChannelKeeper, + &app.IBCKeeper.PortKeeper, app.AccountKeeper, app.BankKeeper, +) + + +// See the section below for configuring an application stack with the fee middleware module + +... + +// Register fee middleware AppModule +app.moduleManager = module.NewManager( + ... + ibcfee.NewAppModule(app.IBCFeeKeeper), +) + +... + +// Add fee middleware to begin blocker logic +app.moduleManager.SetOrderBeginBlockers( + ... + ibcfeetypes.ModuleName, + ... +) + +// Add fee middleware to end blocker logic +app.moduleManager.SetOrderEndBlockers( + ... + ibcfeetypes.ModuleName, + ... +) + +// Add fee middleware to init genesis logic +app.moduleManager.SetOrderInitGenesis( + ... + ibcfeetypes.ModuleName, + ... +) +``` + +## Configuring an application stack with Fee Middleware + +As mentioned in [IBC middleware development](../../01-ibc/04-middleware/01-develop.md) an application stack may be composed of many or no middlewares that nest a base application. +These layers form the complete set of application logic that enable developers to build composable and flexible IBC application stacks. +For example, an application stack may be just a single base application like `transfer`, however, the same application stack composed with `29-fee` will nest the `transfer` base application +by wrapping it with the Fee Middleware module. + +### Transfer + +See below for an example of how to create an application stack using `transfer` and `29-fee`. +The following `transferStack` is configured in `app/app.go` and added to the IBC `Router`. +The in-line comments describe the execution flow of packets between the application stack and IBC core. + +```go +// Create Transfer Stack +// SendPacket, since it is originating from the application to core IBC: +// transferKeeper.SendPacket -> fee.SendPacket -> channel.SendPacket + +// RecvPacket, message that originates from core IBC and goes down to app, the flow is the other way +// channel.RecvPacket -> fee.OnRecvPacket -> transfer.OnRecvPacket + +// transfer stack contains (from top to bottom): +// - IBC Fee Middleware +// - Transfer + +// create IBC module from bottom to top of stack +var transferStack porttypes.IBCModule +transferStack = transfer.NewIBCModule(app.TransferKeeper) +transferStack = ibcfee.NewIBCMiddleware(transferStack, app.IBCFeeKeeper) + +// Add transfer stack to IBC Router +ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) +``` + +### Interchain Accounts + +See below for an example of how to create an application stack using `27-interchain-accounts` and `29-fee`. +The following `icaControllerStack` and `icaHostStack` are configured in `app/app.go` and added to the IBC `Router` with the associated authentication module. +The in-line comments describe the execution flow of packets between the application stack and IBC core. + +```go +// Create Interchain Accounts Stack +// SendPacket, since it is originating from the application to core IBC: +// icaAuthModuleKeeper.SendTx -> icaController.SendPacket -> fee.SendPacket -> channel.SendPacket + +// initialize ICA module with mock module as the authentication module on the controller side +var icaControllerStack porttypes.IBCModule +icaControllerStack = ibcmock.NewIBCModule(&mockModule, ibcmock.NewMockIBCApp("", scopedICAMockKeeper)) +app.ICAAuthModule = icaControllerStack.(ibcmock.IBCModule) +icaControllerStack = icacontroller.NewIBCMiddleware(icaControllerStack, app.ICAControllerKeeper) +icaControllerStack = ibcfee.NewIBCMiddleware(icaControllerStack, app.IBCFeeKeeper) + +// RecvPacket, message that originates from core IBC and goes down to app, the flow is: +// channel.RecvPacket -> fee.OnRecvPacket -> icaHost.OnRecvPacket + +var icaHostStack porttypes.IBCModule +icaHostStack = icahost.NewIBCModule(app.ICAHostKeeper) +icaHostStack = ibcfee.NewIBCMiddleware(icaHostStack, app.IBCFeeKeeper) + +// Add authentication module, controller and host to IBC router +ibcRouter. + // the ICA Controller middleware needs to be explicitly added to the IBC Router because the + // ICA controller module owns the port capability for ICA. The ICA authentication module + // owns the channel capability. + AddRoute(ibcmock.ModuleName+icacontrollertypes.SubModuleName, icaControllerStack) // ica with mock auth module stack route to ica (top level of middleware stack) + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icahosttypes.SubModuleName, icaHostStack). +``` diff --git a/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/03-msgs.md b/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/03-msgs.md new file mode 100644 index 0000000..f2215f9 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/03-msgs.md @@ -0,0 +1,96 @@ +--- +title: Fee Messages +sidebar_label: Fee Messages +sidebar_position: 3 +slug: /middleware/ics29-fee/msgs +--- + + +# Fee messages + +:::note Synopsis +Learn about the different ways to pay for fees, how the fees are paid out and what happens when not enough escrowed fees are available for payout +::: + +## Escrowing fees + +The fee middleware module exposes two different ways to pay fees for relaying IBC packets: + +1. `MsgPayPacketFee`, which enables the escrowing of fees for a packet at the next sequence send and should be combined into one `MultiMsgTx` with the message that will be paid for. + + Note that the `Relayers` field has been set up to allow for an optional whitelist of relayers permitted to receive this fee, however, this feature has not yet been enabled at this time. + + ```go + type MsgPayPacketFee struct{ + // fee encapsulates the recv, ack and timeout fees associated with an IBC packet + Fee Fee + // the source port unique identifier + SourcePortId string + // the source channel unique identifier + SourceChannelId string + // account address to refund fee if necessary + Signer string + // optional list of relayers permitted to the receive packet fee + Relayers []string + } + ``` + + The `Fee` message contained in this synchronous fee payment method configures different fees which will be paid out for `MsgRecvPacket`, `MsgAcknowledgement`, and `MsgTimeout`/`MsgTimeoutOnClose`. + + ```go + type Fee struct { + RecvFee types.Coins + AckFee types.Coins + TimeoutFee types.Coins + } + ``` + + The diagram below shows the `MultiMsgTx` with the `MsgTransfer` coming from a token transfer message, along with `MsgPayPacketFee`. + + ![msgpaypacket.png](./images/msgpaypacket.png) + +2. `MsgPayPacketFeeAsync`, which enables the asynchronous escrowing of fees for a specified packet: + + Note that a packet can be 'topped up' multiple times with additional fees of any coin denomination by broadcasting multiple `MsgPayPacketFeeAsync` messages. + + ```go + type MsgPayPacketFeeAsync struct { + // unique packet identifier comprised of the channel ID, port ID and sequence + PacketId channeltypes.PacketId + // the packet fee associated with a particular IBC packet + PacketFee PacketFee + } + ``` + + where the `PacketFee` also specifies the `Fee` to be paid as well as the refund address for fees which are not paid out + + ```go + type PacketFee struct { + Fee Fee + RefundAddress string + Relayers []string + } + ``` + +The diagram below shows how multiple `MsgPayPacketFeeAsync` can be broadcasted asynchronously. Escrowing of the fee associated with a packet can be carried out by any party because ICS-29 does not dictate a particular fee payer. In fact, chains can choose to simply not expose this fee payment to end users at all and rely on a different module account or even the community pool as the source of relayer incentives. + +![paypacketfeeasync.png](./images/paypacketfeeasync.png) + +Please see our [wiki](https://github.com/cosmos/ibc-go/wiki/Fee-enabled-fungible-token-transfers) for example flows on how to use these messages to incentivise a token transfer channel using a CLI. + +## Paying out the escrowed fees + +Following diagram takes a look at the packet flow for an incentivized token transfer and investigates the several scenario's for paying out the escrowed fees. We assume that the relayers have registered their counterparty address, detailed in the [Fee distribution section](04-fee-distribution.md). + +![feeflow.png](./images/feeflow.png) + +- In the case of a successful transaction, `RecvFee` will be paid out to the designated counterparty payee address which has been registered on the receiver chain and sent back with the `MsgAcknowledgement`, `AckFee` will be paid out to the relayer address which has submitted the `MsgAcknowledgement` on the sending chain (or the registered payee in case one has been registered for the relayer address), and `TimeoutFee` will be reimbursed to the account which escrowed the fee. +- In case of a timeout transaction, `RecvFee` and `AckFee` will be reimbursed. The `TimeoutFee` will be paid to the `Timeout Relayer` (who submits the timeout message to the source chain). + +> Please note that fee payments are built on the assumption that sender chains are the source of incentives — the chain that sends the packets is the same chain where fee payments will occur -- please see the [Fee distribution section](04-fee-distribution.md) to understand the flow for registering payee and counterparty payee (fee receiving) addresses. + +## A locked fee middleware module + +The fee middleware module can become locked if the situation arises that the escrow account for the fees does not have sufficient funds to pay out the fees which have been escrowed for each packet. *This situation indicates a severe bug.* In this case, the fee module will be locked until manual intervention fixes the issue. + +> A locked fee module will simply skip fee logic and continue on to the underlying packet flow. A channel with a locked fee module will temporarily function as a fee disabled channel, and the locking of a fee module will not affect the continued flow of packets over the channel. diff --git a/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/04-fee-distribution.md b/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/04-fee-distribution.md new file mode 100644 index 0000000..48f0434 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/04-fee-distribution.md @@ -0,0 +1,114 @@ +--- +title: Fee Distribution +sidebar_label: Fee Distribution +sidebar_position: 4 +slug: /middleware/ics29-fee/fee-distribution +--- + + +# Fee distribution + +:::note Synopsis +Learn about payee registration for the distribution of packet fees. The following document is intended for relayer operators. +::: + +## Pre-requisite readings + +- [Fee Middleware](01-overview.md) + +Packet fees are divided into 3 distinct amounts in order to compensate relayer operators for packet relaying on fee enabled IBC channels. + +- `RecvFee`: The sum of all packet receive fees distributed to a payee for successful execution of `MsgRecvPacket`. +- `AckFee`: The sum of all packet acknowledgement fees distributed to a payee for successful execution of `MsgAcknowledgement`. +- `TimeoutFee`: The sum of all packet timeout fees distributed to a payee for successful execution of `MsgTimeout`. + +## Register a counterparty payee address for forward relaying + +As mentioned in [ICS29 Concepts](01-overview.md#concepts), the forward relayer describes the actor who performs the submission of `MsgRecvPacket` on the destination chain. +Fee distribution for incentivized packet relays takes place on the packet source chain. + +> Relayer operators are expected to register a counterparty payee address, in order to be compensated accordingly with `RecvFee`s upon completion of a packet lifecycle. + +The counterparty payee address registered on the destination chain is encoded into the packet acknowledgement and communicated as such to the source chain for fee distribution. +**If a counterparty payee is not registered for the forward relayer on the destination chain, the escrowed fees will be refunded upon fee distribution.** + +### Relayer operator actions? + +A transaction must be submitted **to the destination chain** including a `CounterpartyPayee` address of an account on the source chain. +The transaction must be signed by the `Relayer`. + +Note: If a module account address is used as the `CounterpartyPayee` but the module has been set as a blocked address in the `BankKeeper`, the refunding to the module account will fail. This is because many modules use invariants to compare internal tracking of module account balances against the actual balance of the account stored in the `BankKeeper`. If a token transfer to the module account occurs without going through this module and updating the account balance of the module on the `BankKeeper`, then invariants may break and unknown behaviour could occur depending on the module implementation. Therefore, if it is desirable to use a module account that is currently blocked, the module developers should be consulted to gauge to possibility of removing the module account from the blocked list. + +```go +type MsgRegisterCounterpartyPayee struct { + // unique port identifier + PortId string + // unique channel identifier + ChannelId string + // the relayer address + Relayer string + // the counterparty payee address + CounterpartyPayee string +} +``` + +> This message is expected to fail if: +> +> - `PortId` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators). +> - `ChannelId` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +> - `Relayer` is an invalid address. +> - `CounterpartyPayee` is empty. + +See below for an example CLI command: + +```bash +simd tx ibc-fee register-counterparty-payee transfer channel-0 \ +cosmos1rsp837a4kvtgp2m4uqzdge0zzu6efqgucm0qdh \ +osmo1v5y0tz01llxzf4c2afml8s3awue0ymju22wxx2 \ +--from cosmos1rsp837a4kvtgp2m4uqzdge0zzu6efqgucm0qdh +``` + +## Register an alternative payee address for reverse and timeout relaying + +As mentioned in [ICS29 Concepts](01-overview.md#concepts), the reverse relayer describes the actor who performs the submission of `MsgAcknowledgement` on the source chain. +Similarly the timeout relayer describes the actor who performs the submission of `MsgTimeout` (or `MsgTimeoutOnClose`) on the source chain. + +> Relayer operators **may choose** to register an optional payee address, in order to be compensated accordingly with `AckFee`s and `TimeoutFee`s upon completion of a packet life cycle. + +If a payee is not registered for the reverse or timeout relayer on the source chain, then fee distribution assumes the default behaviour, where fees are paid out to the relayer account which delivers `MsgAcknowledgement` or `MsgTimeout`/`MsgTimeoutOnClose`. + +### Relayer operator actions + +A transaction must be submitted **to the source chain** including a `Payee` address of an account on the source chain. +The transaction must be signed by the `Relayer`. + +Note: If a module account address is used as the `Payee` it is recommended to [turn off invariant checks](https://github.com/cosmos/ibc-go/blob/71d7480c923f4227453e8a80f51be01ae7ee845e/testing/simapp/app.go#L659) for that module. + +```go +type MsgRegisterPayee struct { + // unique port identifier + PortId string + // unique channel identifier + ChannelId string + // the relayer address + Relayer string + // the payee address + Payee string +} +``` + +> This message is expected to fail if: +> +> - `PortId` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators). +> - `ChannelId` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +> - `Relayer` is an invalid address. +> - `Payee` is an invalid address. + +See below for an example CLI command: + +```bash +simd tx ibc-fee register-payee transfer channel-0 \ +cosmos1rsp837a4kvtgp2m4uqzdge0zzu6efqgucm0qdh \ +cosmos153lf4zntqt33a4v0sm5cytrxyqn78q7kz8j8x5 \ +--from cosmos1rsp837a4kvtgp2m4uqzdge0zzu6efqgucm0qdh +``` diff --git a/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/05-events.md b/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/05-events.md new file mode 100644 index 0000000..dbda482 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/05-events.md @@ -0,0 +1,43 @@ +--- +title: Events +sidebar_label: Events +sidebar_position: 5 +slug: /middleware/ics29-fee/events +--- + + +# Events + +:::note Synopsis +An overview of all events related to ICS-29 +::: + +## `MsgPayPacketFee`, `MsgPayPacketFeeAsync` + +| Type | Attribute Key | Attribute Value | +| ----------------------- | --------------- | --------------- | +| incentivized_ibc_packet | port_id | \{portID\} | +| incentivized_ibc_packet | channel_id | \{channelID\} | +| incentivized_ibc_packet | packet_sequence | \{sequence\} | +| incentivized_ibc_packet | recv_fee | \{recvFee\} | +| incentivized_ibc_packet | ack_fee | \{ackFee\} | +| incentivized_ibc_packet | timeout_fee | \{timeoutFee\} | +| message | module | fee-ibc | + +## `RegisterPayee` + +| Type | Attribute Key | Attribute Value | +| -------------- | ------------- | --------------- | +| register_payee | relayer | \{relayer\} | +| register_payee | payee | \{payee\} | +| register_payee | channel_id | \{channelID\} | +| message | module | fee-ibc | + +## `RegisterCounterpartyPayee` + +| Type | Attribute Key | Attribute Value | +| --------------------------- | ------------------ | --------------------- | +| register_counterparty_payee | relayer | \{relayer\} | +| register_counterparty_payee | counterparty_payee | \{counterpartyPayee\} | +| register_counterparty_payee | channel_id | \{channelID\} | +| message | module | fee-ibc | diff --git a/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/06-end-users.md b/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/06-end-users.md new file mode 100644 index 0000000..7419d03 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/06-end-users.md @@ -0,0 +1,36 @@ +--- +title: End Users +sidebar_label: End Users +sidebar_position: 6 +slug: /middleware/ics29-fee/end-users +--- + + +# For end users + +:::note Synopsis +Learn how to incentivize IBC packets using the ICS29 Fee Middleware module. +::: + +## Pre-requisite readings + +- [Fee Middleware](01-overview.md) + +## Summary + +Different types of end users: + +- CLI users who want to manually incentivize IBC packets +- Client developers + +The Fee Middleware module allows end users to add a 'tip' to each IBC packet which will incentivize relayer operators to relay packets between chains. gRPC endpoints are exposed for client developers as well as a simple CLI for manually incentivizing IBC packets. + +## CLI Users + +For an in depth guide on how to use the ICS29 Fee Middleware module using the CLI please take a look at the [wiki](https://github.com/cosmos/ibc-go/wiki/Fee-enabled-fungible-token-transfers#asynchronous-incentivization-of-a-fungible-token-transfer) on the `ibc-go` repo. + +## Client developers + +Client developers can read more about the relevant ICS29 message types in the [Fee messages section](03-msgs.md). + +[CosmJS](https://github.com/cosmos/cosmjs) is a useful client library for signing and broadcasting Cosmos SDK messages. diff --git a/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/_category_.json b/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/_category_.json new file mode 100644 index 0000000..639bd0e --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Fee Middleware", + "position": 1, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/images/feeflow.png b/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/images/feeflow.png new file mode 100644 index 0000000..4e77529 Binary files /dev/null and b/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/images/feeflow.png differ diff --git a/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/images/msgpaypacket.png b/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/images/msgpaypacket.png new file mode 100644 index 0000000..a454f52 Binary files /dev/null and b/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/images/msgpaypacket.png differ diff --git a/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/images/paypacketfeeasync.png b/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/images/paypacketfeeasync.png new file mode 100644 index 0000000..73d1e04 Binary files /dev/null and b/docs/versioned_docs/version-v5.4.x/03-middleware/01-ics29-fee/images/paypacketfeeasync.png differ diff --git a/docs/versioned_docs/version-v5.4.x/03-middleware/_category_.json b/docs/versioned_docs/version-v5.4.x/03-middleware/_category_.json new file mode 100644 index 0000000..13f4809 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/03-middleware/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "IBC Middleware Modules", + "position": 3, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v5.4.x/04-migrations/01-support-denoms-with-slashes.md b/docs/versioned_docs/version-v5.4.x/04-migrations/01-support-denoms-with-slashes.md new file mode 100644 index 0000000..df8912d --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/04-migrations/01-support-denoms-with-slashes.md @@ -0,0 +1,88 @@ +--- +title: Support transfer of coins whose base denom contains slashes +sidebar_label: Support transfer of coins whose base denom contains slashes +sidebar_position: 1 +slug: /migrations/support-denoms-with-slashes +--- +# Migrating from not supporting base denoms with slashes to supporting base denoms with slashes + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +This document is necessary when chains are upgrading from a version that does not support base denoms with slashes (e.g. v3.0.0) to a version that does (e.g. v3.2.0). All versions of ibc-go smaller than v1.5.0 for the v1.x release line, v2.3.0 for the v2.x release line, and v3.1.0 for the v3.x release line do **NOT** support IBC token transfers of coins whose base denoms contain slashes. Therefore the in-place of genesis migration described in this document are required when upgrading. + +If a chain receives coins of a base denom with slashes before it upgrades to supporting it, the receive may pass however the trace information will be incorrect. + +E.g. If a base denom of `testcoin/testcoin/testcoin` is sent to a chain that does not support slashes in the base denom, the receive will be successful. However, the trace information stored on the receiving chain will be: `Trace: "transfer/{channel-id}/testcoin/testcoin", BaseDenom: "testcoin"`. + +This incorrect trace information must be corrected when the chain does upgrade to fully supporting denominations with slashes. + +To do so, chain binaries should include a migration script that will run when the chain upgrades from not supporting base denominations with slashes to supporting base denominations with slashes. + +## Chains + +### ICS20 - Transfer + +The transfer module will now support slashes in base denoms, so we must iterate over current traces to check if any of them are incorrectly formed and correct the trace information. + +### Upgrade Proposal + +```go +app.UpgradeKeeper.SetUpgradeHandler("MigrateTraces", + func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + // transfer module consensus version has been bumped to 2 + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) + +``` + +This is only necessary if there are denom traces in the store with incorrect trace information from previously received coins that had a slash in the base denom. However, it is recommended that any chain upgrading to support base denominations with slashes runs this code for safety. + +For a more detailed sample, please check out the code changes in [this pull request](https://github.com/cosmos/ibc-go/pull/1680). + +### Genesis Migration + +If the chain chooses to add support for slashes in base denoms via genesis export, then the trace information must be corrected during genesis migration. + +The migration code required may look like: + +```go +func migrateGenesisSlashedDenomsUpgrade(appState genutiltypes.AppMap, clientCtx client.Context, genDoc *tmtypes.GenesisDoc) (genutiltypes.AppMap, error) { + if appState[ibctransfertypes.ModuleName] != nil { + transferGenState := &ibctransfertypes.GenesisState{} + clientCtx.Codec.MustUnmarshalJSON(appState[ibctransfertypes.ModuleName], transferGenState) + + substituteTraces := make([]ibctransfertypes.DenomTrace, len(transferGenState.DenomTraces)) + for i, dt := range transferGenState.DenomTraces { + // replace all previous traces with the latest trace if validation passes + // note most traces will have same value + newTrace := ibctransfertypes.ParseDenomTrace(dt.GetFullDenomPath()) + + if err := newTrace.Validate(); err != nil { + substituteTraces[i] = dt + } else { + substituteTraces[i] = newTrace + } + } + + transferGenState.DenomTraces = substituteTraces + + // delete old genesis state + delete(appState, ibctransfertypes.ModuleName) + + // set new ibc transfer genesis state + appState[ibctransfertypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(transferGenState) + } + + return appState, nil +} +``` + +For a more detailed sample, please check out the code changes in [this pull request](https://github.com/cosmos/ibc-go/pull/1528). diff --git a/docs/versioned_docs/version-v5.4.x/04-migrations/02-sdk-to-v1.md b/docs/versioned_docs/version-v5.4.x/04-migrations/02-sdk-to-v1.md new file mode 100644 index 0000000..28b41ab --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/04-migrations/02-sdk-to-v1.md @@ -0,0 +1,195 @@ +--- +title: SDK v0.43 to IBC-Go v1 +sidebar_label: SDK v0.43 to IBC-Go v1 +sidebar_position: 2 +slug: /migrations/sdk-to-v1 +--- +# Migrating to ibc-go + +This file contains information on how to migrate from the IBC module contained in the SDK 0.41.x and 0.42.x lines to the IBC module in the ibc-go repository based on the 0.44 SDK version. + +## Import Changes + +The most obvious changes is import name changes. We need to change: + +- applications -> apps +- cosmos-sdk/x/ibc -> ibc-go + +On my GNU/Linux based machine I used the following commands, executed in order: + +```bash +grep -RiIl 'cosmos-sdk\/x\/ibc\/applications' | xargs sed -i 's/cosmos-sdk\/x\/ibc\/applications/ibc-go\/modules\/apps/g' +``` + +```bash +grep -RiIl 'cosmos-sdk\/x\/ibc' | xargs sed -i 's/cosmos-sdk\/x\/ibc/ibc-go\/modules/g' +``` + +ref: [explanation of the above commands](https://www.internalpointers.com/post/linux-find-and-replace-text-multiple-files) + +Executing these commands out of order will cause issues. + +Feel free to use your own method for modifying import names. + +NOTE: Updating to the `v0.44.0` SDK release and then running `go mod tidy` will cause a downgrade to `v0.42.0` in order to support the old IBC import paths. +Update the import paths before running `go mod tidy`. + +## Chain Upgrades + +Chains may choose to upgrade via an upgrade proposal or genesis upgrades. Both in-place store migrations and genesis migrations are supported. + +**WARNING**: Please read at least the quick guide for [IBC client upgrades](../01-ibc/05-upgrades/00-intro.md) before upgrading your chain. It is highly recommended you do not change the chain-ID during an upgrade, otherwise you must follow the IBC client upgrade instructions. + +Both in-place store migrations and genesis migrations will: + +- migrate the solo machine client state from v1 to v2 protobuf definitions +- prune all solo machine consensus states +- prune all expired tendermint consensus states + +Chains must set a new connection parameter during either in place store migrations or genesis migration. The new parameter, max expected block time, is used to enforce packet processing delays on the receiving end of an IBC packet flow. Checkout the [docs](https://github.com/cosmos/ibc-go/blob/release/v1.0.x/docs/ibc/proto-docs.md#params-2) for more information. + +### In-Place Store Migrations + +The new chain binary will need to run migrations in the upgrade handler. The fromVM (previous module version) for the IBC module should be 1. This will allow migrations to be run for IBC updating the version from 1 to 2. + +Ex: + +```go +app.UpgradeKeeper.SetUpgradeHandler("my-upgrade-proposal", + func(ctx sdk.Context, _ upgradetypes.Plan, _ module.VersionMap) (module.VersionMap, error) { + // set max expected block time parameter. Replace the default with your expected value + // https://github.com/cosmos/ibc-go/blob/release/v1.0.x/docs/ibc/proto-docs.md#params-2 + app.IBCKeeper.ConnectionKeeper.SetParams(ctx, ibcconnectiontypes.DefaultParams()) + + fromVM := map[string]uint64{ + ... // other modules + "ibc": 1, + ... + } + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) + +``` + +### Genesis Migrations + +To perform genesis migrations, the following code must be added to your existing migration code. + +```go +// add imports as necessary +import ( + ibcv100 "github.com/cosmos/ibc-go/modules/core/legacy/v100" + ibchost "github.com/cosmos/ibc-go/modules/core/24-host" +) + +... + +// add in migrate cmd function +// expectedTimePerBlock is a new connection parameter +// https://github.com/cosmos/ibc-go/blob/release/v1.0.x/docs/ibc/proto-docs.md#params-2 +newGenState, err = ibcv100.MigrateGenesis(newGenState, clientCtx, *genDoc, expectedTimePerBlock) +if err != nil { + return err +} +``` + +**NOTE:** The genesis chain-id, time and height MUST be updated before migrating IBC, otherwise the tendermint consensus state will not be pruned. + +## IBC Keeper Changes + +The IBC Keeper now takes in the Upgrade Keeper. Please add the chains' Upgrade Keeper after the Staking Keeper: + +```diff + // Create IBC Keeper + app.IBCKeeper = ibckeeper.NewKeeper( +- appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, scopedIBCKeeper, ++ appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, + ) + +``` + +## Proposals + +### UpdateClientProposal + +The `UpdateClient` has been modified to take in two client-identifiers and one initial height. Please see the [documentation](../01-ibc/06-proposals.md) for more information. + +### UpgradeProposal + +A new IBC proposal type has been added, `UpgradeProposal`. This handles an IBC (breaking) Upgrade. +The previous `UpgradedClientState` field in an Upgrade `Plan` has been deprecated in favor of this new proposal type. + +### Proposal Handler Registration + +The `ClientUpdateProposalHandler` has been renamed to `ClientProposalHandler`. +It handles both `UpdateClientProposal`s and `UpgradeProposal`s. + +Add this import: + +```diff ++ ibcclienttypes "github.com/cosmos/ibc-go/modules/core/02-client/types" +``` + +Please ensure the governance module adds the correct route: + +```diff +- AddRoute(ibchost.RouterKey, ibcclient.NewClientUpdateProposalHandler(app.IBCKeeper.ClientKeeper)) ++ AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)) +``` + +NOTE: Simapp registration was incorrect in the 0.41.x releases. The `UpdateClient` proposal handler should be registered with the router key belonging to `ibc-go/core/02-client/types` +as shown in the diffs above. + +### Proposal CLI Registration + +Please ensure both proposal type CLI commands are registered on the governance module by adding the following arguments to `gov.NewAppModuleBasic()`: + +Add the following import: + +```diff ++ ibcclientclient "github.com/cosmos/ibc-go/modules/core/02-client/client" +``` + +Register the cli commands: + +```diff + gov.NewAppModuleBasic( + paramsclient.ProposalHandler, distrclient.ProposalHandler, upgradeclient.ProposalHandler, upgradeclient.CancelProposalHandler, ++ ibcclientclient.UpdateClientProposalHandler, ibcclientclient.UpgradeProposalHandler, + ), +``` + +REST routes are not supported for these proposals. + +## Proto file changes + +The gRPC querier service endpoints have changed slightly. The previous files used `v1beta1` gRPC route, this has been updated to `v1`. + +The solo machine has replaced the FrozenSequence uint64 field with a IsFrozen boolean field. The package has been bumped from `v1` to `v2` + +## IBC callback changes + +### OnRecvPacket + +Application developers need to update their `OnRecvPacket` callback logic. + +The `OnRecvPacket` callback has been modified to only return the acknowledgement. The acknowledgement returned must implement the `Acknowledgement` interface. The acknowledgement should indicate if it represents a successful processing of a packet by returning true on `Success()` and false in all other cases. A return value of false on `Success()` will result in all state changes which occurred in the callback being discarded. More information can be found in the [documentation](../01-ibc/03-apps/02-ibcmodule.md#receiving-packets). + +The `OnRecvPacket`, `OnAcknowledgementPacket`, and `OnTimeoutPacket` callbacks are now passed the `sdk.AccAddress` of the relayer who relayed the IBC packet. Applications may use or ignore this information. + +## IBC Event changes + +The `packet_data` attribute has been deprecated in favor of `packet_data_hex`, in order to provide standardized encoding/decoding of packet data in events. While the `packet_data` event still exists, all relayers and IBC Event consumers are strongly encouraged to switch over to using `packet_data_hex` as soon as possible. + +The `packet_ack` attribute has also been deprecated in favor of `packet_ack_hex` for the same reason stated above. All relayers and IBC Event consumers are strongly encouraged to switch over to using `packet_ack_hex` as soon as possible. + +The `consensus_height` attribute has been removed in the Misbehaviour event emitted. IBC clients no longer have a frozen height and misbehaviour does not necessarily have an associated height. + +## Relevant SDK changes + +- (codec) [\#9226](https://github.com/cosmos/cosmos-sdk/pull/9226) Rename codec interfaces and methods, to follow a general Go interfaces: + - `codec.Marshaler` → `codec.Codec` (this defines objects which serialize other objects) + - `codec.BinaryMarshaler` → `codec.BinaryCodec` + - `codec.JSONMarshaler` → `codec.JSONCodec` + - Removed `BinaryBare` suffix from `BinaryCodec` methods (`MarshalBinaryBare`, `UnmarshalBinaryBare`, ...) + - Removed `Binary` infix from `BinaryCodec` methods (`MarshalBinaryLengthPrefixed`, `UnmarshalBinaryLengthPrefixed`, ...) diff --git a/docs/versioned_docs/version-v5.4.x/04-migrations/03-v1-to-v2.md b/docs/versioned_docs/version-v5.4.x/04-migrations/03-v1-to-v2.md new file mode 100644 index 0000000..39fa738 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/04-migrations/03-v1-to-v2.md @@ -0,0 +1,60 @@ +--- +title: IBC-Go v1 to v2 +sidebar_label: IBC-Go v1 to v2 +sidebar_position: 3 +slug: /migrations/v1-to-v2 +--- +# Migrating from ibc-go v1 to v2 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go -> github.com/cosmos/ibc-go/v2 +``` + +## Chains + +- No relevant changes were made in this release. + +## IBC Apps + +A new function has been added to the app module interface: + +```go +// NegotiateAppVersion performs application version negotiation given the provided channel ordering, connectionID, portID, counterparty and proposed version. + // An error is returned if version negotiation cannot be performed. For example, an application module implementing this interface + // may decide to return an error in the event of the proposed version being incompatible with it's own + NegotiateAppVersion( + ctx sdk.Context, + order channeltypes.Order, + connectionID string, + portID string, + counterparty channeltypes.Counterparty, + proposedVersion string, + ) (version string, err error) +} +``` + +This function should perform application version negotiation and return the negotiated version. If the version cannot be negotiated, an error should be returned. This function is only used on the client side. + +### sdk.Result removed + +sdk.Result has been removed as a return value in the application callbacks. Previously it was being discarded by core IBC and was thus unused. + +## Relayers + +A new gRPC has been added to 05-port, `AppVersion`. It returns the negotiated app version. This function should be used for the `ChanOpenTry` channel handshake step to decide upon the application version which should be set in the channel. + +## IBC Light Clients + +- No relevant changes were made in this release. diff --git a/docs/versioned_docs/version-v5.4.x/04-migrations/04-v2-to-v3.md b/docs/versioned_docs/version-v5.4.x/04-migrations/04-v2-to-v3.md new file mode 100644 index 0000000..813cb58 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/04-migrations/04-v2-to-v3.md @@ -0,0 +1,186 @@ +--- +title: IBC-Go v2 to v3 +sidebar_label: IBC-Go v2 to v3 +sidebar_position: 4 +slug: /migrations/v2-to-v3 +--- +# Migrating from ibc-go v2 to v3 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go/v2 -> github.com/cosmos/ibc-go/v3 +``` + +No genesis or in-place migrations are required when upgrading from v1 or v2 of ibc-go. + +## Chains + +### ICS20 + +The `transferkeeper.NewKeeper(...)` now takes in an ICS4Wrapper. +The ICS4Wrapper should be the IBC Channel Keeper unless ICS 20 is being connected to a middleware application. + +### ICS27 + +ICS27 Interchain Accounts has been added as a supported IBC application of ibc-go. +Please see the [ICS27 documentation](../02-apps/02-interchain-accounts/01-overview.md) for more information. + +### Upgrade Proposal + +If the chain will adopt ICS27, it must set the appropriate params during the execution of the upgrade handler in `app.go`: + +```go +app.UpgradeKeeper.SetUpgradeHandler("v3", + func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + // set the ICS27 consensus version so InitGenesis is not run + fromVM[icatypes.ModuleName] = icamodule.ConsensusVersion() + + // create ICS27 Controller submodule params + controllerParams := icacontrollertypes.Params{ + ControllerEnabled: true, + } + + // create ICS27 Host submodule params + hostParams := icahosttypes.Params{ + HostEnabled: true, + AllowMessages: []string{"/cosmos.bank.v1beta1.MsgSend", ...}, + } + + // initialize ICS27 module + icamodule.InitModule(ctx, controllerParams, hostParams) + + ... + + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) + +``` + +The host and controller submodule params only need to be set if the chain integrates those submodules. +For example, if a chain chooses not to integrate a controller submodule, it may pass empty params into `InitModule`. + +#### Add `StoreUpgrades` for ICS27 module + +For ICS27 it is also necessary to [manually add store upgrades](https://docs.cosmos.network/main/learn/advanced/upgrade#add-storeupgrades-for-new-modules) for the new ICS27 module and then configure the store loader to apply those upgrades in `app.go`: + +```go +if upgradeInfo.Name == "v3" && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { + storeUpgrades := store.StoreUpgrades{ + Added: []string{icacontrollertypes.StoreKey, icahosttypes.StoreKey}, + } + + app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) +} +``` + +This ensures that the new module's stores are added to the multistore before the migrations begin. +The host and controller submodule keys only need to be added if the chain integrates those submodules. +For example, if a chain chooses not to integrate a controller submodule, it does not need to add the controller key to the `Added` field. + +### Genesis migrations + +If the chain will adopt ICS27 and chooses to upgrade via a genesis export, then the ICS27 parameters must be set during genesis migration. + +The migration code required may look like: + +```go + controllerGenesisState := icatypes.DefaultControllerGenesis() + // overwrite parameters as desired + controllerGenesisState.Params = icacontrollertypes.Params{ + ControllerEnabled: true, + } + + hostGenesisState := icatypes.DefaultHostGenesis() + // overwrite parameters as desired + hostGenesisState.Params = icahosttypes.Params{ + HostEnabled: true, + AllowMessages: []string{"/cosmos.bank.v1beta1.MsgSend", ...}, + } + + icaGenesisState := icatypes.NewGenesisState(controllerGenesisState, hostGenesisState) + + // set new ics27 genesis state + appState[icatypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(icaGenesisState) +``` + +### Ante decorator + +The field of type `channelkeeper.Keeper` in the `AnteDecorator` structure has been replaced with a field of type `*keeper.Keeper`: + +```diff +type AnteDecorator struct { +- k channelkeeper.Keeper ++ k *keeper.Keeper +} + +- func NewAnteDecorator(k channelkeeper.Keeper) AnteDecorator { ++ func NewAnteDecorator(k *keeper.Keeper) AnteDecorator { + return AnteDecorator{k: k} +} +``` + +## IBC Apps + +### `OnChanOpenTry` must return negotiated application version + +The `OnChanOpenTry` application callback has been modified. +The return signature now includes the application version. +IBC applications must perform application version negotiation in `OnChanOpenTry` using the counterparty version. +The negotiated application version then must be returned in `OnChanOpenTry` to core IBC. +Core IBC will set this version in the TRYOPEN channel. + +### `OnChanOpenAck` will take additional `counterpartyChannelID` argument + +The `OnChanOpenAck` application callback has been modified. +The arguments now include the counterparty channel id. + +### `NegotiateAppVersion` removed from `IBCModule` interface + +Previously this logic was handled by the `NegotiateAppVersion` function. +Relayers would query this function before calling `ChanOpenTry`. +Applications would then need to verify that the passed in version was correct. +Now applications will perform this version negotiation during the channel handshake, thus removing the need for `NegotiateAppVersion`. + +### Channel state will not be set before application callback + +The channel handshake logic has been reorganized within core IBC. +Channel state will not be set in state after the application callback is performed. +Applications must rely only on the passed in channel parameters instead of querying the channel keeper for channel state. + +### IBC application callbacks moved from `AppModule` to `IBCModule` + +Previously, IBC module callbacks were apart of the `AppModule` type. +The recommended approach is to create an `IBCModule` type and move the IBC module callbacks from `AppModule` to `IBCModule` in a separate file `ibc_module.go`. + +The mock module go API has been broken in this release by applying the above format. +The IBC module callbacks have been moved from the mock modules `AppModule` into a new type `IBCModule`. + +As apart of this release, the mock module now supports middleware testing. Please see the [README](https://github.com/cosmos/ibc-go/blob/v5.3.0/testing/README.md#middleware-testing) for more information. + +Please review the [mock](https://github.com/cosmos/ibc-go/blob/v5.3.0/testing/mock/ibc_module.go) and [transfer](https://github.com/cosmos/ibc-go/blob/v5.3.0/modules/apps/transfer/ibc_module.go) modules as examples. Additionally, [simapp](https://github.com/cosmos/ibc-go/blob/v5.3.0/testing/simapp/app.go) provides an example of how `IBCModule` types should now be added to the IBC router in favour of `AppModule`. + +### IBC testing package + +`TestChain`s are now created with chainID's beginning from an index of 1. Any calls to `GetChainID(0)` will now fail. Please increment all calls to `GetChainID` by 1. + +## Relayers + +`AppVersion` gRPC has been removed. +The `version` string in `MsgChanOpenTry` has been deprecated and will be ignored by core IBC. +Relayers no longer need to determine the version to use on the `ChanOpenTry` step. +IBC applications will determine the correct version using the counterparty version. + +## IBC Light Clients + +The `GetProofSpecs` function has been removed from the `ClientState` interface. This function was previously unused by core IBC. Light clients which don't use this function may remove it. diff --git a/docs/versioned_docs/version-v5.4.x/04-migrations/05-v3-to-v4.md b/docs/versioned_docs/version-v5.4.x/04-migrations/05-v3-to-v4.md new file mode 100644 index 0000000..15b65af --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/04-migrations/05-v3-to-v4.md @@ -0,0 +1,160 @@ +--- +title: IBC-Go v3 to v4 +sidebar_label: IBC-Go v3 to v4 +sidebar_position: 5 +slug: /migrations/v3-to-v4 +--- +# Migrating from ibc-go v3 to v4 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go/v3 -> github.com/cosmos/ibc-go/v4 +``` + +No genesis or in-place migrations required when upgrading from v1 or v2 of ibc-go. + +## Chains + +### ICS27 - Interchain Accounts + +The controller submodule implements now the 05-port `Middleware` interface instead of the 05-port `IBCModule` interface. Chains that integrate the controller submodule, need to create it with the `NewIBCMiddleware` constructor function. For example: + +```diff +- icacontroller.NewIBCModule(app.ICAControllerKeeper, icaAuthIBCModule) ++ icacontroller.NewIBCMiddleware(icaAuthIBCModule, app.ICAControllerKeeper) +``` + +where `icaAuthIBCModule` is the Interchain Accounts authentication IBC Module. + +### ICS29 - Fee Middleware + +The Fee Middleware module, as the name suggests, plays the role of an IBC middleware and as such must be configured by chain developers to route and handle IBC messages correctly. + +Please read the Fee Middleware [integration documentation](../03-middleware/01-ics29-fee/02-integration.md) for an in depth guide on how to configure the module correctly in order to incentivize IBC packets. + +Take a look at the following diff for an [example setup](https://github.com/cosmos/ibc-go/pull/1432/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08aL366) of how to incentivize ics27 channels. + +### Migration to fix support for base denoms with slashes + +As part of [v1.5.0](https://github.com/cosmos/ibc-go/releases/tag/v1.5.0), [v2.3.0](https://github.com/cosmos/ibc-go/releases/tag/v2.3.0) and [v3.1.0](https://github.com/cosmos/ibc-go/releases/tag/v3.1.0) some [migration handler code sample was documented](./01-support-denoms-with-slashes.md#upgrade-proposal) that needs to run in order to correct the trace information of coins transferred using ICS20 whose base denom contains slashes. + +Based on feedback from the community we add now an improved solution to run the same migration that does not require copying a large piece of code over from the migration document, but instead requires only adding a one-line upgrade handler. + +If the chain will migrate to supporting base denoms with slashes, it must set the appropriate params during the execution of the upgrade handler in `app.go`: + +```go +app.UpgradeKeeper.SetUpgradeHandler("MigrateTraces", + func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + // transfer module consensus version has been bumped to 2 + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) + +``` + +If a chain receives coins of a base denom with slashes before it upgrades to supporting it, the receive may pass however the trace information will be incorrect. + +E.g. If a base denom of `testcoin/testcoin/testcoin` is sent to a chain that does not support slashes in the base denom, the receive will be successful. However, the trace information stored on the receiving chain will be: `Trace: "transfer/{channel-id}/testcoin/testcoin", BaseDenom: "testcoin"`. + +This incorrect trace information must be corrected when the chain does upgrade to fully supporting denominations with slashes. + +## IBC Apps + +### ICS03 - Connection + +Crossing hellos have been removed from 03-connection handshake negotiation. +`PreviousConnectionId` in `MsgConnectionOpenTry` has been deprecated and is no longer used by core IBC. + +`NewMsgConnectionOpenTry` no longer takes in the `PreviousConnectionId` as crossing hellos are no longer supported. A non-empty `PreviousConnectionId` will fail basic validation for this message. + +### ICS04 - Channel + +The `WriteAcknowledgement` API now takes the `exported.Acknowledgement` type instead of passing in the acknowledgement byte array directly. +This is an API breaking change and as such IBC application developers will have to update any calls to `WriteAcknowledgement`. + +The `OnChanOpenInit` application callback has been modified. +The return signature now includes the application version as detailed in the latest IBC [spec changes](https://github.com/cosmos/ibc/pull/629). + +The `NewErrorAcknowledgement` method signature has changed. +It now accepts an `error` rather than a `string`. This was done in order to prevent accidental state changes. +All error acknowledgements now contain a deterministic ABCI code and error message. It is the responsibility of the application developer to emit error details in events. + +Crossing hellos have been removed from 04-channel handshake negotiation. +IBC Applications no longer need to account from already claimed capabilities in the `OnChanOpenTry` callback. The capability provided by core IBC must be able to be claimed with error. +`PreviousChannelId` in `MsgChannelOpenTry` has been deprecated and is no longer used by core IBC. + +`NewMsgChannelOpenTry` no longer takes in the `PreviousChannelId` as crossing hellos are no longer supported. A non-empty `PreviousChannelId` will fail basic validation for this message. + +### ICS27 - Interchain Accounts + +The `RegisterInterchainAccount` API has been modified to include an additional `version` argument. This change has been made in order to support ICS29 fee middleware, for relayer incentivization of ICS27 packets. +Consumers of the `RegisterInterchainAccount` are now expected to build the appropriate JSON encoded version string themselves and pass it accordingly. +This should be constructed within the interchain accounts authentication module which leverages the APIs exposed via the interchain accounts `controllerKeeper`. If an empty string is passed in the `version` argument, then the version will be initialized to a default value in the `OnChanOpenInit` callback of the controller's handler, so that channel handshake can proceed. + +The following code snippet illustrates how to construct an appropriate interchain accounts `Metadata` and encode it as a JSON bytestring: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +if err := k.icaControllerKeeper.RegisterInterchainAccount(ctx, msg.ConnectionId, msg.Owner, string(appVersion)); err != nil { + return err +} +``` + +Similarly, if the application stack is configured to route through ICS29 fee middleware and a fee enabled channel is desired, construct the appropriate ICS29 `Metadata` type: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +feeMetadata := feetypes.Metadata{ + AppVersion: string(appVersion), + FeeVersion: feetypes.Version, +} + +feeEnabledVersion, err := feetypes.ModuleCdc.MarshalJSON(&feeMetadata) +if err != nil { + return err +} + +if err := k.icaControllerKeeper.RegisterInterchainAccount(ctx, msg.ConnectionId, msg.Owner, string(feeEnabledVersion)); err != nil { + return err +} +``` + +## Relayers + +When using the `DenomTrace` gRPC, the full IBC denomination with the `ibc/` prefix may now be passed in. + +Crossing hellos are no longer supported by core IBC for 03-connection and 04-channel. The handshake should be completed in the logical 4 step process (INIT, TRY, ACK, CONFIRM). diff --git a/docs/versioned_docs/version-v5.4.x/04-migrations/_category_.json b/docs/versioned_docs/version-v5.4.x/04-migrations/_category_.json new file mode 100644 index 0000000..617def6 --- /dev/null +++ b/docs/versioned_docs/version-v5.4.x/04-migrations/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Migrations", + "position": 4, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v6.3.x/00-intro.md b/docs/versioned_docs/version-v6.3.x/00-intro.md new file mode 100644 index 0000000..e053110 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/00-intro.md @@ -0,0 +1,20 @@ +--- +slug: / +sidebar_position: 0 +--- + +:::danger +This version of ibc-go is not supported anymore. Please upgrade to the latest version. +::: + +# IBC-Go Documentation + +Welcome to the IBC-Go documentation! + +The Inter-Blockchain Communication protocol (IBC) is an end-to-end, connection-oriented, stateful protocol for reliable, ordered, and authenticated communication between heterogeneous blockchains arranged in an unknown and dynamic topology. + +IBC is a protocol that allows blockchains to talk to each other. + +The protocol realizes this interoperability by specifying a set of data structures, abstractions, and semantics that can be implemented by any distributed ledger that satisfies a small set of requirements. + +IBC can be used to build a wide range of cross-chain applications that include token transfers, atomic swaps, multi-chain smart contracts (with or without mutually comprehensible VMs), and data and code sharding of various kinds. diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/01-overview.md b/docs/versioned_docs/version-v6.3.x/01-ibc/01-overview.md new file mode 100644 index 0000000..4c3c0b1 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/01-ibc/01-overview.md @@ -0,0 +1,297 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /ibc/overview +--- + + +# Overview + +:::note Synopsis +Learn about IBC, its components, and IBC use cases. +::: + +## What is the Inter-Blockchain Communication Protocol (IBC)? + +This document serves as a guide for developers who want to write their own Inter-Blockchain +Communication protocol (IBC) applications for custom use cases. + +> IBC applications must be written as self-contained modules. + +Due to the modular design of the IBC protocol, IBC +application developers do not need to be concerned with the low-level details of clients, +connections, and proof verification. + +This brief explanation of the lower levels of the +stack gives application developers a broad understanding of the IBC +protocol. Abstraction layer details for channels and ports are most relevant for application developers and describe how to define custom packets and `IBCModule` callbacks. + +The requirements to have your module interact over IBC are: + +- Bind to a port or ports. +- Define your packet data. +- Use the default acknowledgment struct provided by core IBC or optionally define a custom acknowledgment struct. +- Standardize an encoding of the packet data. +- Implement the `IBCModule` interface. + +Read on for a detailed explanation of how to write a self-contained IBC application module. + +## Components Overview + +### [Clients](https://github.com/cosmos/ibc-go/blob/main/modules/core/02-client) + +IBC clients are on-chain light clients. Each light client is identified by a unique client-id. +IBC clients track the consensus states of other blockchains, along with the proof spec necessary to +properly verify proofs against the client's consensus state. A client can be associated with any number +of connections to the counterparty chain. The client identifier is auto generated using the client type +and the global client counter appended in the format: `{client-type}-{N}`. + +A `ClientState` should contain chain specific and light client specific information necessary for verifying updates +and upgrades to the IBC client. The `ClientState` may contain information such as chain-id, latest height, proof specs, +unbonding periods or the status of the light client. The `ClientState` should not contain information that +is specific to a given block at a certain height, this is the function of the `ConsensusState`. Each `ConsensusState` +should be associated with a unique block and should be referenced using a height. IBC clients are given a +client identifier prefixed store to store their associated client state and consensus states along with +any metadata associated with the consensus states. Consensus states are stored using their associated height. + +The supported IBC clients are: + +- [Solo Machine light client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/06-solomachine): Devices such as phones, browsers, or laptops. +- [Tendermint light client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/07-tendermint): The default for Cosmos SDK-based chains. +- [Localhost (loopback) client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/09-localhost): Useful for +testing, simulation, and relaying packets to modules on the same application. + +### IBC Client Heights + +IBC Client Heights are represented by the struct: + +```go +type Height struct { + RevisionNumber uint64 + RevisionHeight uint64 +} +``` + +The `RevisionNumber` represents the revision of the chain that the height is representing. +A revision typically represents a continuous, monotonically increasing range of block-heights. +The `RevisionHeight` represents the height of the chain within the given revision. + +On any reset of the `RevisionHeight`—for example, when hard-forking a Tendermint chain— +the `RevisionNumber` will get incremented. This allows IBC clients to distinguish between a +block-height `n` of a previous revision of the chain (at revision `p`) and block-height `n` of the current +revision of the chain (at revision `e`). + +`Height`s that share the same revision number can be compared by simply comparing their respective `RevisionHeight`s. +`Height`s that do not share the same revision number will only be compared using their respective `RevisionNumber`s. +Thus a height `h` with revision number `e+1` will always be greater than a height `g` with revision number `e`, +**REGARDLESS** of the difference in revision heights. + +Ex: + +```go +Height{RevisionNumber: 3, RevisionHeight: 0} > Height{RevisionNumber: 2, RevisionHeight: 100000000000} +``` + +When a Tendermint chain is running a particular revision, relayers can simply submit headers and proofs with the revision number +given by the chain's `chainID`, and the revision height given by the Tendermint block height. When a chain updates using a hard-fork +and resets its block-height, it is responsible for updating its `chainID` to increment the revision number. +IBC Tendermint clients then verifies the revision number against their `chainID` and treat the `RevisionHeight` as the Tendermint block-height. + +Tendermint chains wishing to use revisions to maintain persistent IBC connections even across height-resetting upgrades must format their `chainID`s +in the following manner: `{chainID}-{revision_number}`. On any height-resetting upgrade, the `chainID` **MUST** be updated with a higher revision number +than the previous value. + +Ex: + +- Before upgrade `chainID`: `gaiamainnet-3` +- After upgrade `chainID`: `gaiamainnet-4` + +Clients that do not require revisions, such as the solo-machine client, simply hardcode `0` into the revision number whenever they +need to return an IBC height when implementing IBC interfaces and use the `RevisionHeight` exclusively. + +Other client-types can implement their own logic to verify the IBC heights that relayers provide in their `Update`, `Misbehavior`, and +`Verify` functions respectively. + +The IBC interfaces expect an `ibcexported.Height` interface, however all clients must use the concrete implementation provided in +`02-client/types` and reproduced above. + +### [Connections](https://github.com/cosmos/ibc-go/blob/main/modules/core/03-connection) + +Connections encapsulate two `ConnectionEnd` objects on two separate blockchains. Each +`ConnectionEnd` is associated with a client of the other blockchain (for example, the counterparty blockchain). +The connection handshake is responsible for verifying that the light clients on each chain are +correct for their respective counterparties. Connections, once established, are responsible for +facilitating all cross-chain verifications of IBC state. A connection can be associated with any +number of channels. + +### [Proofs](https://github.com/cosmos/ibc-go/blob/main/modules/core/23-commitment) and [Paths](https://github.com/cosmos/ibc-go/blob/main/modules/core/24-host) + +In IBC, blockchains do not directly pass messages to each other over the network. Instead, to +communicate, a blockchain commits some state to a specifically defined path that is reserved for a +specific message type and a specific counterparty. For example, for storing a specific connectionEnd as part +of a handshake or a packet intended to be relayed to a module on the counterparty chain. A relayer +process monitors for updates to these paths and relays messages by submitting the data stored +under the path and a proof to the counterparty chain. + +Proofs are passed from core IBC to light-clients as bytes. It is up to light client implementation to interpret these bytes appropriately. + +- The paths that all IBC implementations must use for committing IBC messages is defined in +[ICS-24 Host State Machine Requirements](https://github.com/cosmos/ibc/tree/master/spec/core/ics-024-host-requirements). +- The proof format that all implementations must be able to produce and verify is defined in [ICS-23 Proofs](https://github.com/confio/ics23) implementation. + +### Capabilities + +IBC is intended to work in execution environments where modules do not necessarily trust each +other. Thus, IBC must authenticate module actions on ports and channels so that only modules with the +appropriate permissions can use them. + +This module authentication is accomplished using a [dynamic +capability store](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-003-dynamic-capability-store.md). Upon binding to a port or +creating a channel for a module, IBC returns a dynamic capability that the module must claim in +order to use that port or channel. The dynamic capability module prevents other modules from using that port or channel since +they do not own the appropriate capability. + +While this background information is useful, IBC modules do not need to interact at all with +these lower-level abstractions. The relevant abstraction layer for IBC application developers is +that of channels and ports. IBC applications must be written as self-contained **modules**. + +A module on one blockchain can communicate with other modules on other blockchains by sending, +receiving, and acknowledging packets through channels that are uniquely identified by the +`(channelID, portID)` tuple. + +A useful analogy is to consider IBC modules as internet applications on +a computer. A channel can then be conceptualized as an IP connection, with the IBC portID being +analogous to an IP port and the IBC channelID being analogous to an IP address. Thus, a single +instance of an IBC module can communicate on the same port with any number of other modules and +IBC correctly routes all packets to the relevant module using the (channelID, portID tuple). An +IBC module can also communicate with another IBC module over multiple ports, with each +`(portID<->portID)` packet stream being sent on a different unique channel. + +### [Ports](https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port) + +An IBC module can bind to any number of ports. Each port must be identified by a unique `portID`. +Since IBC is designed to be secure with mutually distrusted modules operating on the same ledger, +binding a port returns a dynamic object capability. In order to take action on a particular port +(for example, an open channel with its portID), a module must provide the dynamic object capability to the IBC +handler. This requirement prevents a malicious module from opening channels with ports it does not own. Thus, +IBC modules are responsible for claiming the capability that is returned on `BindPort`. + +### [Channels](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +An IBC channel can be established between two IBC ports. Currently, a port is exclusively owned by a +single module. IBC packets are sent over channels. Just as IP packets contain the destination IP +address and IP port, and the source IP address and source IP port, IBC packets contain +the destination portID and channelID, and the source portID and channelID. This packet structure enables IBC to +correctly route packets to the destination module while allowing modules receiving packets to +know the sender module. + +A channel can be `ORDERED`, where packets from a sending module must be processed by the +receiving module in the order they were sent. Or a channel can be `UNORDERED`, where packets +from a sending module are processed in the order they arrive (might be in a different order than they were sent). + +Modules can choose which channels they wish to communicate over with, thus IBC expects modules to +implement callbacks that are called during the channel handshake. These callbacks can do custom +channel initialization logic. If any callback returns an error, the channel handshake fails. Thus, by +returning errors on callbacks, modules can programmatically reject and accept channels. + +The channel handshake is a 4-step handshake. Briefly, if a given chain A wants to open a channel with +chain B using an already established connection: + +1. chain A sends a `ChanOpenInit` message to signal a channel initialization attempt with chain B. +2. chain B sends a `ChanOpenTry` message to try opening the channel on chain A. +3. chain A sends a `ChanOpenAck` message to mark its channel end status as open. +4. chain B sends a `ChanOpenConfirm` message to mark its channel end status as open. + +If all handshake steps are successful, the channel is opened on both sides. At each step in the handshake, the module +associated with the `ChannelEnd` executes its callback. So +on `ChanOpenInit`, the module on chain A executes its callback `OnChanOpenInit`. + +The channel identifier is auto derived in the format: `channel-{N}` where N is the next sequence to be used. + +Just as ports came with dynamic capabilities, channel initialization returns a dynamic capability +that the module **must** claim so that they can pass in a capability to authenticate channel actions +like sending packets. The channel capability is passed into the callback on the first parts of the +handshake; either `OnChanOpenInit` on the initializing chain or `OnChanOpenTry` on the other chain. + +#### Closing channels + +Closing a channel occurs in 2 handshake steps as defined in [ICS 04](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics). + +`ChanCloseInit` closes a channel on the executing chain if the channel exists, it is not +already closed and the connection it exists upon is OPEN. Channels can only be closed by a +calling module or in the case of a packet timeout on an ORDERED channel. + +`ChanCloseConfirm` is a response to a counterparty channel executing `ChanCloseInit`. The channel +on the executing chain closes if the channel exists, the channel is not already closed, +the connection the channel exists upon is OPEN and the executing chain successfully verifies +that the counterparty channel has been closed. + +### [Packets](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +Modules communicate with each other by sending packets over IBC channels. All +IBC packets contain the destination `portID` and `channelID` along with the source `portID` and +`channelID`. This packet structure allows modules to know the sender module of a given packet. IBC packets +contain a sequence to optionally enforce ordering. + +IBC packets also contain a `TimeoutHeight` and a `TimeoutTimestamp` that determine the deadline before the receiving module must process a packet. + +Modules send custom application data to each other inside the `Data []byte` field of the IBC packet. +Thus, packet data is opaque to IBC handlers. It is incumbent on a sender module to encode +their application-specific packet information into the `Data` field of packets. The receiver +module must decode that `Data` back to the original application data. + +### [Receipts and Timeouts](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +Since IBC works over a distributed network and relies on potentially faulty relayers to relay messages between ledgers, +IBC must handle the case where a packet does not get sent to its destination in a timely manner or at all. Packets must +specify a non-zero value for timeout height (`TimeoutHeight`) or timeout timestamp (`TimeoutTimestamp` ) after which a packet can no longer be successfully received on the destination chain. + +- The `timeoutHeight` indicates a consensus height on the destination chain after which the packet is no longer be processed, and instead counts as having timed-out. +- The `timeoutTimestamp` indicates a timestamp on the destination chain after which the packet is no longer be processed, and instead counts as having timed-out. + +If the timeout passes without the packet being successfully received, the packet can no longer be +received on the destination chain. The sending module can timeout the packet and take appropriate actions. + +If the timeout is reached, then a proof of packet timeout can be submitted to the original chain. The original chain can then perform +application-specific logic to timeout the packet, perhaps by rolling back the packet send changes (refunding senders any locked funds, etc.). + +- In ORDERED channels, a timeout of a single packet in the channel causes the channel to close. + + - If packet sequence `n` times out, then a packet at sequence `k > n` cannot be received without violating the contract of ORDERED channels that packets are processed in the order that they are sent. + - Since ORDERED channels enforce this invariant, a proof that sequence `n` has not been received on the destination chain by the specified timeout of packet `n` is sufficient to timeout packet `n` and close the channel. + +- In UNORDERED channels, the application-specific timeout logic for that packet is applied and the channel is not closed. + + - Packets can be received in any order. + + - IBC writes a packet receipt for each sequence receives in the UNORDERED channel. This receipt does not contain information; it is simply a marker intended to signify that the UNORDERED channel has received a packet at the specified sequence. + + - To timeout a packet on an UNORDERED channel, a proof is required that a packet receipt **does not exist** for the packet's sequence by the specified timeout. + +For this reason, most modules should use UNORDERED channels as they require fewer liveness guarantees to function effectively for users of that channel. + +### [Acknowledgments](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +Modules can also choose to write application-specific acknowledgments upon processing a packet. Acknowledgments can be done: + +- Synchronously on `OnRecvPacket` if the module processes packets as soon as they are received from IBC module. +- Asynchronously if module processes packets at some later point after receiving the packet. + +This acknowledgment data is opaque to IBC much like the packet `Data` and is treated by IBC as a simple byte string `[]byte`. Receiver modules must encode their acknowledgment so that the sender module can decode it correctly. The encoding must be negotiated between the two parties during version negotiation in the channel handshake. + +The acknowledgment can encode whether the packet processing succeeded or failed, along with additional information that allows the sender module to take appropriate action. + +After the acknowledgment has been written by the receiving chain, a relayer relays the acknowledgment back to the original sender module. + +The original sender module then executes application-specific acknowledgment logic using the contents of the acknowledgment. + +- After an acknowledgement fails, packet-send changes can be rolled back (for example, refunding senders in ICS20). + +- After an acknowledgment is received successfully on the original sender on the chain, the corresponding packet commitment is deleted since it is no longer needed. + +## Further Readings and Specs + +If you want to learn more about IBC, check the following specifications: + +- [IBC specification overview](https://github.com/cosmos/ibc/blob/master/README.md) diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/02-integration.md b/docs/versioned_docs/version-v6.3.x/01-ibc/02-integration.md new file mode 100644 index 0000000..9148547 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/01-ibc/02-integration.md @@ -0,0 +1,222 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /ibc/integration +--- + +# Integration + +:::note Synopsis +Learn how to integrate IBC to your application and send data packets to other chains. +::: + +This document outlines the required steps to integrate and configure the [IBC +module](https://github.com/cosmos/ibc-go/tree/main/modules/core) to your Cosmos SDK application and +send fungible token transfers to other chains. + +## Integrating the IBC module + +Integrating the IBC module to your SDK-based application is straightforward. The general changes can be summarized in the following steps: + +- Add required modules to the `module.BasicManager` +- Define additional `Keeper` fields for the new modules on the `App` type +- Add the module's `StoreKeys` and initialize their `Keepers` +- Set up corresponding routers and routes for the `ibc` module +- Add the modules to the module `Manager` +- Add modules to `Begin/EndBlockers` and `InitGenesis` +- Update the module `SimulationManager` to enable simulations + +### Module `BasicManager` and `ModuleAccount` permissions + +The first step is to add the following modules to the `BasicManager`: `x/capability`, `x/ibc`, +and `x/ibc-transfer`. After that, we need to grant `Minter` and `Burner` permissions to +the `ibc-transfer` `ModuleAccount` to mint and burn relayed tokens. + +```go +// app.go +var ( + + ModuleBasics = module.NewBasicManager( + // ... + capability.AppModuleBasic{}, + ibc.AppModuleBasic{}, + transfer.AppModuleBasic{}, // i.e ibc-transfer module + ) + + // module account permissions + maccPerms = map[string][]string{ + // other module accounts permissions + // ... + ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner}, +) +``` + +### Application fields + +Then, we need to register the `Keepers` as follows: + +```go +// app.go +type App struct { + // baseapp, keys and subspaces definitions + + // other keepers + // ... + IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly + TransferKeeper ibctransferkeeper.Keeper // for cross-chain fungible token transfers + + // make scoped keepers public for test purposes + ScopedIBCKeeper capabilitykeeper.ScopedKeeper + ScopedTransferKeeper capabilitykeeper.ScopedKeeper + + /// ... + /// module and simulation manager definitions +} +``` + +### Configure the `Keepers` + +During initialization, besides initializing the IBC `Keepers` (for the `x/ibc`, and +`x/ibc-transfer` modules), we need to grant specific capabilities through the capability module +`ScopedKeepers` so that we can authenticate the object-capability permissions for each of the IBC +channels. + +```go +func NewApp(...args) *App { + // define codecs and baseapp + + // add capability keeper and ScopeToModule for ibc module + app.CapabilityKeeper = capabilitykeeper.NewKeeper(appCodec, keys[capabilitytypes.StoreKey], memKeys[capabilitytypes.MemStoreKey]) + + // grant capabilities for the ibc and ibc-transfer modules + scopedIBCKeeper := app.CapabilityKeeper.ScopeToModule(ibchost.ModuleName) + scopedTransferKeeper := app.CapabilityKeeper.ScopeToModule(ibctransfertypes.ModuleName) + + // ... other modules keepers + + // Create IBC Keeper + app.IBCKeeper = ibckeeper.NewKeeper( + appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, + ) + + // Create Transfer Keepers + app.TransferKeeper = ibctransferkeeper.NewKeeper( + appCodec, keys[ibctransfertypes.StoreKey], app.GetSubspace(ibctransfertypes.ModuleName), + app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.AccountKeeper, app.BankKeeper, scopedTransferKeeper, + ) + transferModule := transfer.NewAppModule(app.TransferKeeper) + + // .. continues +} +``` + +### Register `Routers` + +IBC needs to know which module is bound to which port so that it can route packets to the +appropriate module and call the appropriate callbacks. The port to module name mapping is handled by +IBC's port `Keeper`. However, the mapping from module name to the relevant callbacks is accomplished +by the port +[`Router`](https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port/types/router.go) on the +IBC module. + +Adding the module routes allows the IBC handler to call the appropriate callback when processing a +channel handshake or a packet. + +Currently, a `Router` is static so it must be initialized and set correctly on app initialization. +Once the `Router` has been set, no new routes can be added. + +```go +// app.go +func NewApp(...args) *App { + // .. continuation from above + + // Create static IBC router, add ibc-transfer module route, then set and seal it + ibcRouter := port.NewRouter() + ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferModule) + // Setting Router will finalize all routes by sealing router + // No more routes can be added + app.IBCKeeper.SetRouter(ibcRouter) + + // .. continues +``` + +### Module Managers + +In order to use IBC, we need to add the new modules to the module `Manager` and to the `SimulationManager` in case your application supports simulations. + +```go +// app.go +func NewApp(...args) *App { + // .. continuation from above + + app.mm = module.NewManager( + // other modules + // ... + capability.NewAppModule(appCodec, *app.CapabilityKeeper), + ibc.NewAppModule(app.IBCKeeper), + transferModule, + ) + + // ... + + app.sm = module.NewSimulationManager( + // other modules + // ... + capability.NewAppModule(appCodec, *app.CapabilityKeeper), + ibc.NewAppModule(app.IBCKeeper), + transferModule, + ) + + // .. continues +``` + +### Application ABCI Ordering + +One addition from IBC is the concept of `HistoricalEntries` which are stored on the staking module. +Each entry contains the historical information for the `Header` and `ValidatorSet` of this chain which is stored +at each height during the `BeginBlock` call. The historical info is required to introspect the +past historical info at any given height in order to verify the light client `ConsensusState` during the +connection handshake. + +The IBC module also has +[`BeginBlock`](https://github.com/cosmos/ibc-go/blob/main/modules/core/02-client/abci.go) logic as well. This is optional as it is only required if your application uses the localhost client to connect two different modules from the same chain. + +:::tip +Only register the ibc module to the `SetOrderBeginBlockers` if your application will use the +localhost (*aka* loopback) client. +::: + +```go +// app.go +func NewApp(...args) *App { + // .. continuation from above + + // add staking and ibc modules to BeginBlockers + app.mm.SetOrderBeginBlockers( + // other modules ... + stakingtypes.ModuleName, ibchost.ModuleName, + ) + + // ... + + // NOTE: Capability module must occur first so that it can initialize any capabilities + // so that other modules that want to create or claim capabilities afterwards in InitChain + // can do so safely. + app.mm.SetOrderInitGenesis( + capabilitytypes.ModuleName, + // other modules ... + ibchost.ModuleName, ibctransfertypes.ModuleName, + ) + + // .. continues +``` + +:::warning +**IMPORTANT**: The capability module **must** be declared first in `SetOrderInitGenesis` +::: + +That's it! You have now wired up the IBC module and are now able to send fungible tokens across +different chains. If you want to have a broader view of the changes take a look into the SDK's +[`SimApp`](https://github.com/cosmos/ibc-go/blob/main/testing/simapp/app.go). diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/01-apps.md b/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/01-apps.md new file mode 100644 index 0000000..6f1881d --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/01-apps.md @@ -0,0 +1,56 @@ +--- +title: IBC Applications +sidebar_label: IBC Applications +sidebar_position: 1 +slug: /ibc/apps/apps +--- + +# IBC Applications + +:::note Synopsis +Learn how to build custom IBC application modules that enable packets to be sent to and received from other IBC-enabled chains. +::: + +This document serves as a guide for developers who want to write their own Inter-blockchain Communication Protocol (IBC) applications for custom use cases. + +Due to the modular design of the IBC protocol, IBC application developers do not need to concern themselves with the low-level details of clients, connections, and proof verification. Nevertheless, an overview of these low-level concepts can be found in [the Overview section](../01-overview.md). +The document goes into detail on the abstraction layer most relevant for application developers (channels and ports), and describes how to define your own custom packets, `IBCModule` callbacks and more to make an application module IBC ready. + +**To have your module interact over IBC you must:** + +- implement the `IBCModule` interface, i.e.: + - channel (opening) handshake callbacks + - channel closing handshake callbacks + - packet callbacks +- bind to a port(s) +- add keeper methods +- define your own packet data and acknowledgement structs as well as how to encode/decode them +- add a route to the IBC router + +The following sections provide a more detailed explanation of how to write an IBC application +module correctly corresponding to the listed steps. + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: + +## Working example + +For a real working example of an IBC application, you can look through the `ibc-transfer` module +which implements everything discussed in this section. + +Here are the useful parts of the module to look at: + +[Binding to transfer +port](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/genesis.go) + +[Sending transfer +packets](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/relay.go) + +[Implementing IBC +callbacks](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/ibc_module.go) diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/02-ibcmodule.md b/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/02-ibcmodule.md new file mode 100644 index 0000000..0f1cf8f --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/02-ibcmodule.md @@ -0,0 +1,358 @@ +--- +title: Implement `IBCModule` interface and callbacks +sidebar_label: Implement `IBCModule` interface and callbacks +sidebar_position: 2 +slug: /ibc/apps/ibcmodule +--- + +# Implement `IBCModule` interface and callbacks + +:::note Synopsis +Learn how to implement the `IBCModule` interface and all of the callbacks it requires. +::: + +The Cosmos SDK expects all IBC modules to implement the [`IBCModule` +interface](https://github.com/cosmos/ibc-go/tree/main/modules/core/05-port/types/module.go). This interface contains all of the callbacks IBC expects modules to implement. They include callbacks related to channel handshake, closing and packet callbacks (`OnRecvPacket`, `OnAcknowledgementPacket` and `OnTimeoutPacket`). + +```go +// IBCModule implements the ICS26 interface for given the keeper. +// The implementation of the IBCModule interface could for example be in a file called ibc_module.go, +// but ultimately file structure is up to the developer +type IBCModule struct { + keeper keeper.Keeper +} +``` + +Additionally, in the `module.go` file, add the following line: + +```go +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + // Add this line + _ porttypes.IBCModule = IBCModule{} +) +``` + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: + +## Channel handshake callbacks + +This section will describe the callbacks that are called during channel handshake execution. Among other things, it will claim channel capabilities passed on from core IBC. For a refresher on capabilities, check [the Overview section](../01-overview.md#capabilities). + +Here are the channel handshake callbacks that modules are expected to implement: + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `checkArguments` and `negotiateAppVersion` functions. + +```go +// Called by IBC Handler on MsgOpenInit +func (im IBCModule) OnChanOpenInit(ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + // Examples: + // - Abort if order == UNORDERED, + // - Abort if version is unsupported + if err := checkArguments(args); err != nil { + return "", err + } + + // OpenInit must claim the channelCapability that IBC passes into the callback + if err := im.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return "", err + } + + return version, nil +} + +// Called by IBC Handler on MsgOpenTry +func (im IBCModule) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + if err := checkArguments(args); err != nil { + return "", err + } + + // OpenTry must claim the channelCapability that IBC passes into the callback + if err := im.keeper.scopedKeeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return err + } + + // Construct application version + // IBC applications must return the appropriate application version + // This can be a simple string or it can be a complex version constructed + // from the counterpartyVersion and other arguments. + // The version returned will be the channel version used for both channel ends. + appVersion := negotiateAppVersion(counterpartyVersion, args) + + return appVersion, nil +} + +// Called by IBC Handler on MsgOpenAck +func (im IBCModule) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyVersion string, +) error { + if counterpartyVersion != types.Version { + return sdkerrors.Wrapf(types.ErrInvalidVersion, "invalid counterparty version: %s, expected %s", counterpartyVersion, types.Version) + } + + // do custom logic + + return nil +} + +// Called by IBC Handler on MsgOpenConfirm +func (im IBCModule) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // do custom logic + + return nil +} +``` + +The channel closing handshake will also invoke module callbacks that can return errors to abort the closing handshake. Closing a channel is a 2-step handshake, the initiating chain calls `ChanCloseInit` and the finalizing chain calls `ChanCloseConfirm`. + +```go +// Called by IBC Handler on MsgCloseInit +func (im IBCModule) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + // ... do custom finalization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} + +// Called by IBC Handler on MsgCloseConfirm +func (im IBCModule) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // ... do custom finalization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} +``` + +### Channel handshake version negotiation + +Application modules are expected to verify versioning used during the channel handshake procedure. + +- `OnChanOpenInit` will verify that the relayer-chosen parameters + are valid and perform any custom `INIT` logic. + It may return an error if the chosen parameters are invalid + in which case the handshake is aborted. + If the provided version string is non-empty, `OnChanOpenInit` should return + the version string if valid or an error if the provided version is invalid. + **If the version string is empty, `OnChanOpenInit` is expected to + return a default version string representing the version(s) + it supports.** + If there is no default version string for the application, + it should return an error if the provided version is an empty string. +- `OnChanOpenTry` will verify the relayer-chosen parameters along with the + counterparty-chosen version string and perform custom `TRY` logic. + If the relayer-chosen parameters + are invalid, the callback must return an error to abort the handshake. + If the counterparty-chosen version is not compatible with this module's + supported versions, the callback must return an error to abort the handshake. + If the versions are compatible, the try callback must select the final version + string and return it to core IBC. + `OnChanOpenTry` may also perform custom initialization logic. +- `OnChanOpenAck` will error if the counterparty selected version string + is invalid and abort the handshake. It may also perform custom ACK logic. + +Versions must be strings but can implement any versioning structure. If your application plans to +have linear releases then semantic versioning is recommended. If your application plans to release +various features in between major releases then it is advised to use the same versioning scheme +as IBC. This versioning scheme specifies a version identifier and compatible feature set with +that identifier. Valid version selection includes selecting a compatible version identifier with +a subset of features supported by your application for that version. The struct used for this +scheme can be found in [03-connection/types](https://github.com/cosmos/ibc-go/blob/main/modules/core/03-connection/types/version.go#L16). + +Since the version type is a string, applications have the ability to do simple version verification +via string matching or they can use the already implemented versioning system and pass the proto +encoded version into each handhshake call as necessary. + +ICS20 currently implements basic string matching with a single supported version. + +## Packet callbacks + +Just as IBC expects modules to implement callbacks for channel handshakes, it also expects modules to implement callbacks for handling the packet flow through a channel, as defined in the `IBCModule` interface. + +Once a module A and module B are connected to each other, relayers can start relaying packets and acknowledgements back and forth on the channel. + +![IBC packet flow diagram](./images/packet_flow.png) + +Briefly, a successful packet flow works as follows: + +1. module A sends a packet through the IBC module +2. the packet is received by module B +3. if module B writes an acknowledgement of the packet then module A will process the + acknowledgement +4. if the packet is not successfully received before the timeout, then module A processes the + packet's timeout. + +### Sending packets + +Modules **do not send packets through callbacks**, since the modules initiate the action of sending packets to the IBC module, as opposed to other parts of the packet flow where messages sent to the IBC +module must trigger execution on the port-bound module through the use of callbacks. Thus, to send a packet a module simply needs to call `SendPacket` on the `IBCChannelKeeper`. + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `EncodePacketData(customPacketData)` function. + +```go +// retrieve the dynamic capability for this channel +channelCap := scopedKeeper.GetCapability(ctx, channelCapName) +// Sending custom application packet data +data := EncodePacketData(customPacketData) +// Send packet to IBC, authenticating with channelCap +sequence, err := IBCChannelKeeper.SendPacket( + ctx, + channelCap, + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + data, +) +``` + +:::warning +In order to prevent modules from sending packets on channels they do not own, IBC expects +modules to pass in the correct channel capability for the packet's source channel. +::: + +### Receiving packets + +To handle receiving packets, the module must implement the `OnRecvPacket` callback. This gets +invoked by the IBC module after the packet has been proved valid and correctly processed by the IBC +keepers. Thus, the `OnRecvPacket` callback only needs to worry about making the appropriate state +changes given the packet data without worrying about whether the packet is valid or not. + +Modules may return to the IBC handler an acknowledgement which implements the `Acknowledgement` interface. +The IBC handler will then commit this acknowledgement of the packet so that a relayer may relay the +acknowledgement back to the sender module. + +The state changes that occurred during this callback will only be written if: + +- the acknowledgement was successful as indicated by the `Success()` function of the acknowledgement +- if the acknowledgement returned is nil indicating that an asynchronous process is occurring + +NOTE: Applications which process asynchronous acknowledgements must handle reverting state changes +when appropriate. Any state changes that occurred during the `OnRecvPacket` callback will be written +for asynchronous acknowledgements. + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `DecodePacketData(packet.Data)` function. + +```go +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, +) ibcexported.Acknowledgement { + // Decode the packet data + packetData := DecodePacketData(packet.Data) + + // do application state changes based on packet data and return the acknowledgement + // NOTE: The acknowledgement will indicate to the IBC handler if the application + // state changes should be written via the `Success()` function. Application state + // changes are only written if the acknowledgement is successful or the acknowledgement + // returned is nil indicating that an asynchronous acknowledgement will occur. + ack := processPacket(ctx, packet, packetData) + + return ack +} +``` + +Reminder, the `Acknowledgement` interface: + +```go +// Acknowledgement defines the interface used to return +// acknowledgements in the OnRecvPacket callback. +type Acknowledgement interface { + Success() bool + Acknowledgement() []byte +} +``` + +### Acknowledging packets + +After a module writes an acknowledgement, a relayer can relay back the acknowledgement to the sender module. The sender module can +then process the acknowledgement using the `OnAcknowledgementPacket` callback. The contents of the +acknowledgement is entirely up to the modules on the channel (just like the packet data); however, it +may often contain information on whether the packet was successfully processed along +with some additional data that could be useful for remediation if the packet processing failed. + +Since the modules are responsible for agreeing on an encoding/decoding standard for packet data and +acknowledgements, IBC will pass in the acknowledgements as `[]byte` to this callback. The callback +is responsible for decoding the acknowledgement and processing it. + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `DecodeAcknowledgement(acknowledgments)` and `processAck(ack)` functions. + +```go +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, +) (*sdk.Result, error) { + // Decode acknowledgement + ack := DecodeAcknowledgement(acknowledgement) + + // process ack + res, err := processAck(ack) + return res, err +} +``` + +### Timeout packets + +If the timeout for a packet is reached before the packet is successfully received or the +counterparty channel end is closed before the packet is successfully received, then the receiving +chain can no longer process it. Thus, the sending chain must process the timeout using +`OnTimeoutPacket` to handle this situation. Again the IBC module will verify that the timeout is +indeed valid, so our module only needs to implement the state machine logic for what to do once a +timeout is reached and the packet can no longer be received. + +```go +func (im IBCModule) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, +) (*sdk.Result, error) { + // do custom timeout logic +} +``` diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/03-bindports.md b/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/03-bindports.md new file mode 100644 index 0000000..303168d --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/03-bindports.md @@ -0,0 +1,122 @@ +--- +title: Bind ports +sidebar_label: Bind ports +sidebar_position: 3 +slug: /ibc/apps/bindports +--- + +# Bind ports + +:::note Synopsis +Learn what changes to make to bind modules to their ports on initialization. +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: +Currently, ports must be bound on app initialization. In order to bind modules to their respective ports on initialization, the following needs to be implemented: + +> Note that `portID` does not refer to a certain numerical ID, like `localhost:8080` with a `portID` 8080. Rather it refers to the application module the port binds. For IBC Modules built with the Cosmos SDK, it defaults to the module's name and for Cosmwasm contracts it defaults to the contract address. + +1. Add port ID to the `GenesisState` proto definition: + + ```protobuf + message GenesisState { + string port_id = 1; + // other fields + } + ``` + +1. Add port ID as a key to the module store: + + ```go + // x//types/keys.go + const ( + // ModuleName defines the IBC Module name + ModuleName = "moduleName" + + // Version defines the current version the IBC + // module supports + Version = "moduleVersion-1" + + // PortID is the default port id that module binds to + PortID = "portID" + + // ... + ) + ``` + +1. Add port ID to `x//types/genesis.go`: + + ```go + // in x//types/genesis.go + + // DefaultGenesisState returns a GenesisState with "transfer" as the default PortID. + func DefaultGenesisState() *GenesisState { + return &GenesisState{ + PortId: PortID, + // additional k-v fields + } + } + + // Validate performs basic genesis state validation returning an error upon any + // failure. + func (gs GenesisState) Validate() error { + if err := host.PortIdentifierValidator(gs.PortId); err != nil { + return err + } + //additional validations + + return gs.Params.Validate() + } + ``` + +1. Bind to port(s) in the module keeper's `InitGenesis`: + + ```go + // InitGenesis initializes the ibc-module state and binds to PortID. + func (k Keeper) InitGenesis(ctx sdk.Context, state types.GenesisState) { + k.SetPort(ctx, state.PortId) + + // ... + + // Only try to bind to port if it is not already bound, since we may already own + // port capability from capability InitGenesis + if !k.IsBound(ctx, state.PortId) { + // transfer module binds to the transfer port on InitChain + // and claims the returned capability + err := k.BindPort(ctx, state.PortId) + if err != nil { + panic(fmt.Sprintf("could not claim port capability: %v", err)) + } + } + + // ... + } + ``` + + With: + + ```go + // IsBound checks if the module is already bound to the desired port + func (k Keeper) IsBound(ctx sdk.Context, portID string) bool { + _, ok := k.scopedKeeper.GetCapability(ctx, host.PortPath(portID)) + return ok + } + + // BindPort defines a wrapper function for the port Keeper's function in + // order to expose it to module's InitGenesis function + func (k Keeper) BindPort(ctx sdk.Context, portID string) error { + cap := k.portKeeper.BindPort(ctx, portID) + return k.ClaimCapability(ctx, cap, host.PortPath(portID)) + } + ``` + + The module binds to the desired port(s) and returns the capabilities. + + In the above we find reference to keeper methods that wrap other keeper functionality, in the next section the keeper methods that need to be implemented will be defined. diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/04-keeper.md b/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/04-keeper.md new file mode 100644 index 0000000..cf4fac6 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/04-keeper.md @@ -0,0 +1,96 @@ +--- +title: Keeper +sidebar_label: Keeper +sidebar_position: 4 +slug: /ibc/apps/keeper +--- + +# Keeper + +:::note Synopsis +Learn how to implement the IBC Module keeper. +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: +In the previous sections, on channel handshake callbacks and port binding in `InitGenesis`, a reference was made to keeper methods that need to be implemented when creating a custom IBC module. Below is an overview of how to define an IBC module's keeper. + +> Note that some code has been left out for clarity, to get a full code overview, please refer to [the transfer module's keeper in the ibc-go repo](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/keeper.go). + +```go +// Keeper defines the IBC app module keeper +type Keeper struct { + storeKey sdk.StoreKey + cdc codec.BinaryCodec + paramSpace paramtypes.Subspace + + channelKeeper types.ChannelKeeper + portKeeper types.PortKeeper + scopedKeeper capabilitykeeper.ScopedKeeper + + // ... additional according to custom logic +} + +// NewKeeper creates a new IBC app module Keeper instance +func NewKeeper( + // args +) Keeper { + // ... + + return Keeper{ + cdc: cdc, + storeKey: key, + paramSpace: paramSpace, + + channelKeeper: channelKeeper, + portKeeper: portKeeper, + scopedKeeper: scopedKeeper, + + // ... additional according to custom logic + } +} + +// IsBound checks if the IBC app module is already bound to the desired port +func (k Keeper) IsBound(ctx sdk.Context, portID string) bool { + _, ok := k.scopedKeeper.GetCapability(ctx, host.PortPath(portID)) + return ok +} + +// BindPort defines a wrapper function for the port Keeper's function in +// order to expose it to module's InitGenesis function +func (k Keeper) BindPort(ctx sdk.Context, portID string) error { + cap := k.portKeeper.BindPort(ctx, portID) + return k.ClaimCapability(ctx, cap, host.PortPath(portID)) +} + +// GetPort returns the portID for the IBC app module. Used in ExportGenesis +func (k Keeper) GetPort(ctx sdk.Context) string { + store := ctx.KVStore(k.storeKey) + return string(store.Get(types.PortKey)) +} + +// SetPort sets the portID for the IBC app module. Used in InitGenesis +func (k Keeper) SetPort(ctx sdk.Context, portID string) { + store := ctx.KVStore(k.storeKey) + store.Set(types.PortKey, []byte(portID)) +} + +// AuthenticateCapability wraps the scopedKeeper's AuthenticateCapability function +func (k Keeper) AuthenticateCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) bool { + return k.scopedKeeper.AuthenticateCapability(ctx, cap, name) +} + +// ClaimCapability allows the IBC app module to claim a capability that core IBC +// passes to it +func (k Keeper) ClaimCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) error { + return k.scopedKeeper.ClaimCapability(ctx, cap, name) +} + +// ... additional according to custom logic +``` diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/05-packets_acks.md b/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/05-packets_acks.md new file mode 100644 index 0000000..9d40a45 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/05-packets_acks.md @@ -0,0 +1,118 @@ +--- +title: Define packets and acks +sidebar_label: Define packets and acks +sidebar_position: 5 +slug: /ibc/apps/packets_acks +--- + +# Define packets and acks + +:::note Synopsis +Learn how to define custom packet and acknowledgement structs and how to encode and decode them. +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: + +## Custom packets + +Modules connected by a channel must agree on what application data they are sending over the +channel, as well as how they will encode/decode it. This process is not specified by IBC as it is up +to each application module to determine how to implement this agreement. However, for most +applications this will happen as a version negotiation during the channel handshake. While more +complex version negotiation is possible to implement inside the channel opening handshake, a very +simple version negotiation is implemented in the [ibc-transfer module](https://github.com/cosmos/ibc-go/tree/main/modules/apps/transfer/module.go). + +Thus, a module must define its custom packet data structure, along with a well-defined way to +encode and decode it to and from `[]byte`. + +```go +// Custom packet data defined in application module +type CustomPacketData struct { + // Custom fields ... +} + +EncodePacketData(packetData CustomPacketData) []byte { + // encode packetData to bytes +} + +DecodePacketData(encoded []byte) (CustomPacketData) { + // decode from bytes to packet data +} +``` + +> Note that the `CustomPacketData` struct is defined in the proto definition and then compiled by the protobuf compiler. + +Then a module must encode its packet data before sending it through IBC. + +```go +// retrieve the dynamic capability for this channel +channelCap := scopedKeeper.GetCapability(ctx, channelCapName) +// Sending custom application packet data +data := EncodePacketData(customPacketData) +// Send packet to IBC, authenticating with channelCap +sequence, err := IBCChannelKeeper.SendPacket( + ctx, + channelCap, + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + data, +) +``` + +A module receiving a packet must decode the `PacketData` into a structure it expects so that it can +act on it. + +```go +// Receiving custom application packet data (in OnRecvPacket) +packetData := DecodePacketData(packet.Data) +// handle received custom packet data +``` + +## Acknowledgements + +Modules may commit an acknowledgement upon receiving and processing a packet in the case of synchronous packet processing. +In the case where a packet is processed at some later point after the packet has been received (asynchronous execution), the acknowledgement +will be written once the packet has been processed by the application which may be well after the packet receipt. + +NOTE: Most blockchain modules will want to use the synchronous execution model in which the module processes and writes the acknowledgement +for a packet as soon as it has been received from the IBC module. + +This acknowledgement can then be relayed back to the original sender chain, which can take action +depending on the contents of the acknowledgement. + +Just as packet data was opaque to IBC, acknowledgements are similarly opaque. Modules must pass and +receive acknowledegments with the IBC modules as byte strings. + +Thus, modules must agree on how to encode/decode acknowledgements. The process of creating an +acknowledgement struct along with encoding and decoding it, is very similar to the packet data +example above. [ICS 04](https://github.com/cosmos/ibc/blob/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope) +specifies a recommended format for acknowledgements. This acknowledgement type can be imported from +[channel types](https://github.com/cosmos/ibc-go/tree/main/modules/core/04-channel/types). + +While modules may choose arbitrary acknowledgement structs, a default acknowledgement types is provided by IBC [here](https://github.com/cosmos/ibc-go/blob/main/proto/ibc/core/channel/v1/channel.proto): + +```protobuf +// Acknowledgement is the recommended acknowledgement format to be used by +// app-specific protocols. +// NOTE: The field numbers 21 and 22 were explicitly chosen to avoid accidental +// conflicts with other protobuf message formats used for acknowledgements. +// The first byte of any message with this format will be the non-ASCII values +// `0xaa` (result) or `0xb2` (error). Implemented as defined by ICS: +// https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope +message Acknowledgement { + // response contains either a result or an error and must be non-empty + oneof response { + bytes result = 21; + string error = 22; + } +} +``` diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/06-routing.md b/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/06-routing.md new file mode 100644 index 0000000..7e6c205 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/06-routing.md @@ -0,0 +1,44 @@ +--- +title: Routing +sidebar_label: Routing +sidebar_position: 6 +slug: /ibc/apps/routing +--- + +# Routing + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: +:::note Synopsis +Learn how to hook a route to the IBC router for the custom IBC module. +::: + +As mentioned above, modules must implement the `IBCModule` interface (which contains both channel +handshake callbacks and packet handling callbacks). The concrete implementation of this interface +must be registered with the module name as a route on the IBC `Router`. + +```go +// app.go +func NewApp(...args) *App { +// ... + +// Create static IBC router, add module routes, then set and seal it +ibcRouter := port.NewRouter() + +ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferModule) +// Note: moduleCallbacks must implement IBCModule interface +ibcRouter.AddRoute(moduleName, moduleCallbacks) + +// Setting Router will finalize all routes by sealing router +// No more routes can be added +app.IBCKeeper.SetRouter(ibcRouter) + +// ... +} +``` diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/_category_.json b/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/_category_.json new file mode 100644 index 0000000..1c34da9 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Applications", + "position": 3, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/images/packet_flow.png b/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/images/packet_flow.png new file mode 100644 index 0000000..e5bae3f Binary files /dev/null and b/docs/versioned_docs/version-v6.3.x/01-ibc/03-apps/images/packet_flow.png differ diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/04-middleware/01-develop.md b/docs/versioned_docs/version-v6.3.x/01-ibc/04-middleware/01-develop.md new file mode 100644 index 0000000..e537add --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/01-ibc/04-middleware/01-develop.md @@ -0,0 +1,453 @@ +--- +title: IBC middleware +sidebar_label: IBC middleware +sidebar_position: 1 +slug: /ibc/middleware/develop +--- + +# IBC middleware + +:::note Synopsis +Learn how to write your own custom middleware to wrap an IBC application, and understand how to hook different middleware to IBC base applications to form different IBC application stacks +:::. + +This document serves as a guide for middleware developers who want to write their own middleware and for chain developers who want to use IBC middleware on their chains. + +IBC applications are designed to be self-contained modules that implement their own application-specific logic through a set of interfaces with the core IBC handlers. These core IBC handlers, in turn, are designed to enforce the correctness properties of IBC (transport, authentication, ordering) while delegating all application-specific handling to the IBC application modules. However, there are cases where some functionality may be desired by many applications, yet not appropriate to place in core IBC. + +Middleware allows developers to define the extensions as separate modules that can wrap over the base application. This middleware can thus perform its own custom logic, and pass data into the application so that it may run its logic without being aware of the middleware's existence. This allows both the application and the middleware to implement its own isolated logic while still being able to run as part of a single packet flow. + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC Integration](../02-integration.md) +- [IBC Application Developer Guide](../03-apps/01-apps.md) + +::: + +## Definitions + +`Middleware`: A self-contained module that sits between core IBC and an underlying IBC application during packet execution. All messages between core IBC and underlying application must flow through middleware, which may perform its own custom logic. + +`Underlying Application`: An underlying application is the application that is directly connected to the middleware in question. This underlying application may itself be middleware that is chained to a base application. + +`Base Application`: A base application is an IBC application that does not contain any middleware. It may be nested by 0 or multiple middleware to form an application stack. + +`Application Stack (or stack)`: A stack is the complete set of application logic (middleware(s) + base application) that gets connected to core IBC. A stack may be just a base application, or it may be a series of middlewares that nest a base application. + +## Create a custom IBC middleware + +IBC middleware will wrap over an underlying IBC application and sits between core IBC and the application. It has complete control in modifying any message coming from IBC to the application, and any message coming from the application to core IBC. Thus, middleware must be completely trusted by chain developers who wish to integrate them, however this gives them complete flexibility in modifying the application(s) they wrap. + +### Interfaces + +```go +// Middleware implements the ICS26 Module interface +type Middleware interface { + porttypes.IBCModule // middleware has access to an underlying application which may be wrapped by more middleware + ics4Wrapper: ICS4Wrapper // middleware has access to ICS4Wrapper which may be core IBC Channel Handler or a higher-level middleware that wraps this middleware. +} +``` + +```typescript +// This is implemented by ICS4 and all middleware that are wrapping base application. +// The base application will call `sendPacket` or `writeAcknowledgement` of the middleware directly above them +// which will call the next middleware until it reaches the core IBC handler. +type ICS4Wrapper interface { + SendPacket( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + data []byte, + ) (sequence uint64, err error) + + WriteAcknowledgement( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + packet exported.PacketI, + ack exported.Acknowledgement, + ) error + + GetAppVersion( + ctx sdk.Context, + portID, + channelID string, + ) (string, bool) +} +``` + +### Implement `IBCModule` interface and callbacks + +The `IBCModule` is a struct that implements the [ICS-26 interface (`porttypes.IBCModule`)](https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port/types/module.go#L11-L106). It is recommended to separate these callbacks into a separate file `ibc_module.go`. As will be mentioned in the [integration section](02-integration.md), this struct should be different than the struct that implements `AppModule` in case the middleware maintains its own internal state and processes separate SDK messages. + +The middleware must have access to the underlying application, and be called before during all ICS-26 callbacks. It may execute custom logic during these callbacks, and then call the underlying application's callback. Middleware **may** choose not to call the underlying application's callback at all. Though these should generally be limited to error cases. + +In the case where the IBC middleware expects to speak to a compatible IBC middleware on the counterparty chain, they must use the channel handshake to negotiate the middleware version without interfering in the version negotiation of the underlying application. + +Middleware accomplishes this by formatting the version in a JSON-encoded string containing the middleware version and the application version. The application version may as well be a JSON-encoded string, possibly including further middleware and app versions, if the application stack consists of multiple milddlewares wrapping a base application. The format of the version is specified in ICS-30 as the following: + +```json +{ + "": "", + "app_version": "" +} +``` + +The `` key in the JSON struct should be replaced by the actual name of the key for the corresponding middleware (e.g. `fee_version`). + +During the handshake callbacks, the middleware can unmarshal the version string and retrieve the middleware and application versions. It can do its negotiation logic on ``, and pass the `` to the underlying application. + +The middleware should simply pass the capability in the callback arguments along to the underlying application so that it may be claimed by the base application. The base application will then pass the capability up the stack in order to authenticate an outgoing packet/acknowledgement. + +In the case where the middleware wishes to send a packet or acknowledgment without the involvement of the underlying application, it should be given access to the same `scopedKeeper` as the base application so that it can retrieve the capabilities by itself. + +### Handshake callbacks + +#### `OnChanOpenInit` + +```go +func (im IBCModule) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + if version != "" { + // try to unmarshal JSON-encoded version string and pass + // the app-specific version to app callback. + // otherwise, pass version directly to app callback. + metadata, err := Unmarshal(version) + if err != nil { + // Since it is valid for fee version to not be specified, + // the above middleware version may be for another middleware. + // Pass the entire version string onto the underlying application. + return im.app.OnChanOpenInit( + ctx, + order, + connectionHops, + portID, + channelID, + channelCap, + counterparty, + version, + ) + } + else { + metadata = { + // set middleware version to default value + MiddlewareVersion: defaultMiddlewareVersion, + // allow application to return its default version + AppVersion: "", + } + } + + doCustomLogic() + + // if the version string is empty, OnChanOpenInit is expected to return + // a default version string representing the version(s) it supports + appVersion, err := im.app.OnChanOpenInit( + ctx, + order, + connectionHops, + portID, + channelID, + channelCap, + counterparty, + metadata.AppVersion, // note we only pass app version here + ) + if err != nil { + return "", err + } + + version := constructVersion(metadata.MiddlewareVersion, appVersion) + + return version, nil +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L34-L82) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnChanOpenTry` + +```go +func OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + // try to unmarshal JSON-encoded version string and pass + // the app-specific version to app callback. + // otherwise, pass version directly to app callback. + cpMetadata, err := Unmarshal(counterpartyVersion) + if err != nil { + return app.OnChanOpenTry( + ctx, + order, + connectionHops, + portID, + channelID, + channelCap, + counterparty, + counterpartyVersion, + ) + } + + doCustomLogic() + + // Call the underlying application's OnChanOpenTry callback. + // The try callback must select the final app-specific version string and return it. + appVersion, err := app.OnChanOpenTry( + ctx, + order, + connectionHops, + portID, + channelID, + channelCap, + counterparty, + cpMetadata.AppVersion, // note we only pass counterparty app version here + ) + if err != nil { + return "", err + } + + // negotiate final middleware version + middlewareVersion := negotiateMiddlewareVersion(cpMetadata.MiddlewareVersion) + version := constructVersion(middlewareVersion, appVersion) + + return version, nil +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L84-L124) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnChanOpenAck` + +```go +func OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyChannelID string, + counterpartyVersion string, +) error { + // try to unmarshal JSON-encoded version string and pass + // the app-specific version to app callback. + // otherwise, pass version directly to app callback. + cpMetadata, err = UnmarshalJSON(counterpartyVersion) + if err != nil { + return app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) + } + + if !isCompatible(cpMetadata.MiddlewareVersion) { + return error + } + doCustomLogic() + + // call the underlying application's OnChanOpenTry callback + return app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, cpMetadata.AppVersion) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L126-L152) an example implementation of this callback for the ICS29 Fee Middleware module. + +### `OnChanOpenConfirm` + +```go +func OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + doCustomLogic() + + return app.OnChanOpenConfirm(ctx, portID, channelID) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L154-L162) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnChanCloseInit` + +```go +func OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + doCustomLogic() + + return app.OnChanCloseInit(ctx, portID, channelID) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L164-L187) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnChanCloseConfirm` + +```go +func OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + doCustomLogic() + + return app.OnChanCloseConfirm(ctx, portID, channelID) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L189-L212) an example implementation of this callback for the ICS29 Fee Middleware module. + +**NOTE**: Middleware that does not need to negotiate with a counterparty middleware on the remote stack will not implement the version unmarshalling and negotiation, and will simply perform its own custom logic on the callbacks without relying on the counterparty behaving similarly. + +### Packet callbacks + +The packet callbacks just like the handshake callbacks wrap the application's packet callbacks. The packet callbacks are where the middleware performs most of its custom logic. The middleware may read the packet flow data and perform some additional packet handling, or it may modify the incoming data before it reaches the underlying application. This enables a wide degree of usecases, as a simple base application like token-transfer can be transformed for a variety of usecases by combining it with custom middleware. + +#### `OnRecvPacket` + +```go +func OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) ibcexported.Acknowledgement { + doCustomLogic(packet) + + ack := app.OnRecvPacket(ctx, packet, relayer) + + doCustomLogic(ack) // middleware may modify outgoing ack + return ack +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L214-L237) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnAcknowledgementPacket` + +```go +func OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + doCustomLogic(packet, ack) + + return app.OnAcknowledgementPacket(ctx, packet, ack, relayer) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L239-L292) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnTimeoutPacket` + +```go +func OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + doCustomLogic(packet) + + return app.OnTimeoutPacket(ctx, packet, relayer) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L294-L334) an example implementation of this callback for the ICS29 Fee Middleware module. + +### ICS-4 wrappers + +Middleware must also wrap ICS-4 so that any communication from the application to the `channelKeeper` goes through the middleware first. Similar to the packet callbacks, the middleware may modify outgoing acknowledgements and packets in any way it wishes. + +#### `SendPacket` + +```go +func SendPacket( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + appData []byte, +) { + // middleware may modify data + data = doCustomLogic(appData) + + return ics4Keeper.SendPacket( + ctx, + chanCap, + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + data, + ) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L336-L343) an example implementation of this function for the ICS29 Fee Middleware module. + +#### `WriteAcknowledgement` + +```go +// only called for async acks +func WriteAcknowledgement( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + packet exported.PacketI, + ack exported.Acknowledgement, +) { + // middleware may modify acknowledgement + ack_bytes = doCustomLogic(ack) + + return ics4Keeper.WriteAcknowledgement(packet, ack_bytes) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L345-L353) an example implementation of this function for the ICS29 Fee Middleware module. + +#### `GetAppVersion` + +```go +// middleware must return the underlying application version +func GetAppVersion( + ctx sdk.Context, + portID, + channelID string, +) (string, bool) { + version, found := ics4Keeper.GetAppVersion(ctx, portID, channelID) + if !found { + return "", false + } + + if !MiddlewareEnabled { + return version, true + } + + // unwrap channel version + metadata, err := Unmarshal(version) + if err != nil { + panic(fmt.Errof("unable to unmarshal version: %w", err)) + } + + return metadata.AppVersion, true +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L355-L358) an example implementation of this function for the ICS29 Fee Middleware module. diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/04-middleware/02-integration.md b/docs/versioned_docs/version-v6.3.x/01-ibc/04-middleware/02-integration.md new file mode 100644 index 0000000..d721023 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/01-ibc/04-middleware/02-integration.md @@ -0,0 +1,72 @@ +--- +title: Integrating IBC middleware into a chain +sidebar_label: Integrating IBC middleware into a chain +sidebar_position: 2 +slug: /ibc/middleware/integration +--- + + +# Integrating IBC middleware into a chain + +Learn how to integrate IBC middleware(s) with a base application to your chain. The following document only applies for Cosmos SDK chains. + +If the middleware is maintaining its own state and/or processing SDK messages, then it should create and register its SDK module **only once** with the module manager in `app.go`. + +All middleware must be connected to the IBC router and wrap over an underlying base IBC application. An IBC application may be wrapped by many layers of middleware, only the top layer middleware should be hooked to the IBC router, with all underlying middlewares and application getting wrapped by it. + +The order of middleware **matters**, function calls from IBC to the application travel from top-level middleware to the bottom middleware and then to the application. Function calls from the application to IBC goes through the bottom middleware in order to the top middleware and then to core IBC handlers. Thus the same set of middleware put in different orders may produce different effects. + +## Example integration + +```go +// app.go + +// middleware 1 and middleware 3 are stateful middleware, +// perhaps implementing separate sdk.Msg and Handlers +mw1Keeper := mw1.NewKeeper(storeKey1) +mw3Keeper := mw3.NewKeeper(storeKey3) + +// Only create App Module **once** and register in app module +// if the module maintains independent state and/or processes sdk.Msgs +app.moduleManager = module.NewManager( + ... + mw1.NewAppModule(mw1Keeper), + mw3.NewAppModule(mw3Keeper), + transfer.NewAppModule(transferKeeper), + custom.NewAppModule(customKeeper) +) + +mw1IBCModule := mw1.NewIBCModule(mw1Keeper) +mw2IBCModule := mw2.NewIBCModule() // middleware2 is stateless middleware +mw3IBCModule := mw3.NewIBCModule(mw3Keeper) + +scopedKeeperTransfer := capabilityKeeper.NewScopedKeeper("transfer") +scopedKeeperCustom1 := capabilityKeeper.NewScopedKeeper("custom1") +scopedKeeperCustom2 := capabilityKeeper.NewScopedKeeper("custom2") + +// NOTE: IBC Modules may be initialized any number of times provided they use a separate +// scopedKeeper and underlying port. + +// initialize base IBC applications +// if you want to create two different stacks with the same base application, +// they must be given different scopedKeepers and assigned different ports. +transferIBCModule := transfer.NewIBCModule(transferKeeper) +customIBCModule1 := custom.NewIBCModule(customKeeper, "portCustom1") +customIBCModule2 := custom.NewIBCModule(customKeeper, "portCustom2") + +// create IBC stacks by combining middleware with base application +// NOTE: since middleware2 is stateless it does not require a Keeper +// stack 1 contains mw1 -> mw3 -> transfer +stack1 := mw1.NewIBCMiddleware(mw3.NewIBCMiddleware(transferIBCModule, mw3Keeper), mw1Keeper) +// stack 2 contains mw3 -> mw2 -> custom1 +stack2 := mw3.NewIBCMiddleware(mw2.NewIBCMiddleware(customIBCModule1), mw3Keeper) +// stack 3 contains mw2 -> mw1 -> custom2 +stack3 := mw2.NewIBCMiddleware(mw1.NewIBCMiddleware(customIBCModule2, mw1Keeper)) + +// associate each stack with the moduleName provided by the underlying scopedKeeper +ibcRouter := porttypes.NewRouter() +ibcRouter.AddRoute("transfer", stack1) +ibcRouter.AddRoute("custom1", stack2) +ibcRouter.AddRoute("custom2", stack3) +app.IBCKeeper.SetRouter(ibcRouter) +``` diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/04-middleware/_category_.json b/docs/versioned_docs/version-v6.3.x/01-ibc/04-middleware/_category_.json new file mode 100644 index 0000000..ec27d4e --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/01-ibc/04-middleware/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Middleware", + "position": 4, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/05-upgrades/00-intro.md b/docs/versioned_docs/version-v6.3.x/01-ibc/05-upgrades/00-intro.md new file mode 100644 index 0000000..eff2de6 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/01-ibc/05-upgrades/00-intro.md @@ -0,0 +1,15 @@ +--- +title: Upgrading IBC Chains Overview +sidebar_label: Overview +sidebar_position: 0 +slug: /ibc/upgrades/intro +--- + +### Upgrading IBC Chains Overview + +This directory contains information on how to upgrade an IBC chain without breaking counterparty clients and connections. + +IBC-connected chains must be able to upgrade without breaking connections to other chains. Otherwise there would be a massive disincentive towards upgrading and disrupting high-value IBC connections, thus preventing chains in the IBC ecosystem from evolving and improving. Many chain upgrades may be irrelevant to IBC, however some upgrades could potentially break counterparty clients if not handled correctly. Thus, any IBC chain that wishes to perform an IBC-client-breaking upgrade must perform an IBC upgrade in order to allow counterparty clients to securely upgrade to the new light client. + +1. The [quick-guide](./01-quick-guide.md) describes how IBC-connected chains can perform client-breaking upgrades and how relayers can securely upgrade counterparty clients using the SDK. +2. The [developer-guide](./02-developer-guide.md) is a guide for developers intending to develop IBC client implementations with upgrade functionality. diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/05-upgrades/01-quick-guide.md b/docs/versioned_docs/version-v6.3.x/01-ibc/05-upgrades/01-quick-guide.md new file mode 100644 index 0000000..f757e90 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/01-ibc/05-upgrades/01-quick-guide.md @@ -0,0 +1,60 @@ +--- +title: How to Upgrade IBC Chains and their Clients +sidebar_label: How to Upgrade IBC Chains and their Clients +sidebar_position: 1 +slug: /ibc/upgrades/quick-guide +--- + + +# How to Upgrade IBC Chains and their Clients + +:::note Synopsis +Learn how to upgrade your chain and counterparty clients. +::: + +The information in this doc for upgrading chains is relevant to SDK chains. However, the guide for counterparty clients is relevant to any Tendermint client that enables upgrades. + +## IBC Client Breaking Upgrades + +IBC-connected chains must perform an IBC upgrade if their upgrade will break counterparty IBC clients. The current IBC protocol supports upgrading tendermint chains for a specific subset of IBC-client-breaking upgrades. Here is the exhaustive list of IBC client-breaking upgrades and whether the IBC protocol currently supports such upgrades. + +IBC currently does **NOT** support unplanned upgrades. All of the following upgrades must be planned and committed to in advance by the upgrading chain, in order for counterparty clients to maintain their connections securely. + +Note: Since upgrades are only implemented for Tendermint clients, this doc only discusses upgrades on Tendermint chains that would break counterparty IBC Tendermint Clients. + +1. Changing the Chain-ID: **Supported** +2. Changing the UnbondingPeriod: **Partially Supported**, chains may increase the unbonding period with no issues. However, decreasing the unbonding period may irreversibly break some counterparty clients. Thus, it is **not recommended** that chains reduce the unbonding period. +3. Changing the height (resetting to 0): **Supported**, so long as chains remember to increment the revision number in their chain-id. +4. Changing the ProofSpecs: **Supported**, this should be changed if the proof structure needed to verify IBC proofs is changed across the upgrade. Ex: Switching from an IAVL store, to a SimpleTree Store +5. Changing the UpgradePath: **Supported**, this might involve changing the key under which upgraded clients and consensus states are stored in the upgrade store, or even migrating the upgrade store itself. +6. Migrating the IBC store: **Unsupported**, as the IBC store location is negotiated by the connection. +7. Upgrading to a backwards compatible version of IBC: Supported +8. Upgrading to a non-backwards compatible version of IBC: **Unsupported**, as IBC version is negotiated on connection handshake. +9. Changing the Tendermint LightClient algorithm: **Partially Supported**. Changes to the light client algorithm that do not change the ClientState or ConsensusState struct may be supported, provided that the counterparty is also upgraded to support the new light client algorithm. Changes that require updating the ClientState and ConsensusState structs themselves are theoretically possible by providing a path to translate an older ClientState struct into the new ClientState struct; however this is not currently implemented. + +### Step-by-Step Upgrade Process for SDK chains + +If the IBC-connected chain is conducting an upgrade that will break counterparty clients, it must ensure that the upgrade is first supported by IBC using the list above and then execute the upgrade process described below in order to prevent counterparty clients from breaking. + +1. Create a 02-client [`UpgradeProposal`](https://github.com/cosmos/ibc-go/blob/v6.2.0/proto/ibc/core/client/v1/client.proto#L58-L77) with an `UpgradePlan` and a new IBC ClientState in the `UpgradedClientState` field. Note that the `UpgradePlan` must specify an upgrade height **only** (no upgrade time), and the `ClientState` should only include the fields common to all valid clients and zero out any client-customizable fields (such as TrustingPeriod). +2. Vote on and pass the `UpgradeProposal` + +Upon the `UpgradeProposal` passing, the upgrade module will commit the UpgradedClient under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedClient`. On the block right before the upgrade height, the upgrade module will also commit an initial consensus state for the next chain under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedConsState`. + +Once the chain reaches the upgrade height and halts, a relayer can upgrade the counterparty clients to the last block of the old chain. They can then submit the proofs of the `UpgradedClient` and `UpgradedConsensusState` against this last block and upgrade the counterparty client. + +### Step-by-Step Upgrade Process for Relayers Upgrading Counterparty Clients + +Once the upgrading chain has committed to upgrading, relayers must wait till the chain halts at the upgrade height before upgrading counterparty clients. This is because chains may reschedule or cancel upgrade plans before they occur. Thus, relayers must wait till the chain reaches the upgrade height and halts before they can be sure the upgrade will take place. + +Thus, the upgrade process for relayers trying to upgrade the counterparty clients is as follows: + +1. Wait for the upgrading chain to reach the upgrade height and halt +2. Query a full node for the proofs of `UpgradedClient` and `UpgradedConsensusState` at the last height of the old chain. +3. Update the counterparty client to the last height of the old chain using the `UpdateClient` msg. +4. Submit an `UpgradeClient` msg to the counterparty chain with the `UpgradedClient`, `UpgradedConsensusState` and their respective proofs. +5. Submit an `UpdateClient` msg to the counterparty chain with a header from the new upgraded chain. + +The Tendermint client on the counterparty chain will verify that the upgrading chain did indeed commit to the upgraded client and upgraded consensus state at the upgrade height (since the upgrade height is included in the key). If the proofs are verified against the upgrade height, then the client will upgrade to the new client while retaining all of its client-customized fields. Thus, it will retain its old TrustingPeriod, TrustLevel, MaxClockDrift, etc; while adopting the new chain-specified fields such as UnbondingPeriod, ChainId, UpgradePath, etc. Note, this can lead to an invalid client since the old client-chosen fields may no longer be valid given the new chain-chosen fields. Upgrading chains should try to avoid these situations by not altering parameters that can break old clients. For an example, see the UnbondingPeriod example in the supported upgrades section. + +The upgraded consensus state will serve purely as a basis of trust for future `UpdateClientMsgs` and will not contain a consensus root to perform proof verification against. Thus, relayers must submit an `UpdateClientMsg` with a header from the new chain so that the connection can be used for proof verification again. diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/05-upgrades/02-developer-guide.md b/docs/versioned_docs/version-v6.3.x/01-ibc/05-upgrades/02-developer-guide.md new file mode 100644 index 0000000..98f1d42 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/01-ibc/05-upgrades/02-developer-guide.md @@ -0,0 +1,56 @@ +--- +title: IBC Client Developer Guide to Upgrades +sidebar_label: IBC Client Developer Guide to Upgrades +sidebar_position: 2 +slug: /ibc/upgrades/developer-guide +--- + + +# IBC Client Developer Guide to Upgrades + +:::note Synopsis +Learn how to implement upgrade functionality for your custom IBC client. +::: + +As mentioned in the [README](./00-intro.md), it is vital that high-value IBC clients can upgrade along with their underlying chains to avoid disruption to the IBC ecosystem. Thus, IBC client developers will want to implement upgrade functionality to enable clients to maintain connections and channels even across chain upgrades. + +The IBC protocol allows client implementations to provide a path to upgrading clients given the upgraded client state, upgraded consensus state and proofs for each. + +```go +// Upgrade functions +// NOTE: proof heights are not included as upgrade to a new revision is expected to pass only on the last +// height committed by the current revision. Clients are responsible for ensuring that the planned last +// height of the current revision is somehow encoded in the proof verification process. +// This is to ensure that no premature upgrades occur, since upgrade plans committed to by the counterparty +// may be cancelled or modified before the last planned height. +VerifyUpgradeAndUpdateState( + ctx sdk.Context, + cdc codec.BinaryCodec, + store sdk.KVStore, + newClient ClientState, + newConsState ConsensusState, + proofUpgradeClient, + proofUpgradeConsState []byte, +) (upgradedClient ClientState, upgradedConsensus ConsensusState, err error) +``` + +Note that the clients should have prior knowledge of the merkle path that the upgraded client and upgraded consensus states will use. The height at which the upgrade has occurred should also be encoded in the proof. The Tendermint client implementation accomplishes this by including an `UpgradePath` in the ClientState itself, which is used along with the upgrade height to construct the merkle path under which the client state and consensus state are committed. + +Developers must ensure that the `UpgradeClientMsg` does not pass until the last height of the old chain has been committed, and after the chain upgrades, the `UpgradeClientMsg` should pass once and only once on all counterparty clients. + +Developers must ensure that the new client adopts all of the new Client parameters that must be uniform across every valid light client of a chain (chain-chosen parameters), while maintaining the Client parameters that are customizable by each individual client (client-chosen parameters) from the previous version of the client. + +Upgrades must adhere to the IBC Security Model. IBC does not rely on the assumption of honest relayers for correctness. Thus users should not have to rely on relayers to maintain client correctness and security (though honest relayers must exist to maintain relayer liveness). While relayers may choose any set of client parameters while creating a new `ClientState`, this still holds under the security model since users can always choose a relayer-created client that suits their security and correctness needs or create a Client with their desired parameters if no such client exists. + +However, when upgrading an existing client, one must keep in mind that there are already many users who depend on this client's particular parameters. We cannot give the upgrading relayer free choice over these parameters once they have already been chosen. This would violate the security model since users who rely on the client would have to rely on the upgrading relayer to maintain the same level of security. Thus, developers must make sure that their upgrade mechanism allows clients to upgrade the chain-specified parameters whenever a chain upgrade changes these parameters (examples in the Tendermint client include `UnbondingPeriod`, `TrustingPeriod`, `ChainID`, `UpgradePath`, etc.), while ensuring that the relayer submitting the `UpgradeClientMsg` cannot alter the client-chosen parameters that the users are relying upon (examples in Tendermint client include `TrustLevel`, `MaxClockDrift`, etc). + +Developers should maintain the distinction between Client parameters that are uniform across every valid light client of a chain (chain-chosen parameters), and Client parameters that are customizable by each individual client (client-chosen parameters); since this distinction is necessary to implement the `ZeroCustomFields` method in the `ClientState` interface: + +```go +// Utility function that zeroes out any client customizable fields in client state +// Ledger enforced fields are maintained while all custom fields are zero values +// Used to verify upgrades +ZeroCustomFields() ClientState +``` + +Counterparty clients can upgrade securely by using all of the chain-chosen parameters from the chain-committed `UpgradedClient` and preserving all of the old client-chosen parameters. This enables chains to securely upgrade without relying on an honest relayer, however it can in some cases lead to an invalid final `ClientState` if the new chain-chosen parameters clash with the old client-chosen parameter. This can happen in the Tendermint client case if the upgrading chain lowers the `UnbondingPeriod` (chain-chosen) to a duration below that of a counterparty client's `TrustingPeriod` (client-chosen). Such cases should be clearly documented by developers, so that chains know which upgrades should be avoided to prevent this problem. The final upgraded client should also be validated in `VerifyUpgradeAndUpdateState` before returning to ensure that the client does not upgrade to an invalid `ClientState`. diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/05-upgrades/03-genesis-restart.md b/docs/versioned_docs/version-v6.3.x/01-ibc/05-upgrades/03-genesis-restart.md new file mode 100644 index 0000000..73147f6 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/01-ibc/05-upgrades/03-genesis-restart.md @@ -0,0 +1,52 @@ +--- +title: Genesis Restart Upgrades +sidebar_label: Genesis Restart Upgrades +sidebar_position: 3 +slug: /ibc/upgrades/genesis-restart +--- + + +# Genesis Restart Upgrades + +:::note Synopsis +Learn how to upgrade your chain and counterparty clients using genesis restarts. +::: + +**NOTE**: Regular genesis restarts are currently unsupported by relayers! + +## IBC Client Breaking Upgrades + +IBC client breaking upgrades are possible using genesis restarts. +It is highly recommended to use the in-place migrations instead of a genesis restart. +Genesis restarts should be used sparingly and as backup plans. + +Genesis restarts still require the usage of an IBC upgrade proposal in order to correctly upgrade counterparty clients. + +### Step-by-Step Upgrade Process for SDK Chains + +If the IBC-connected chain is conducting an upgrade that will break counterparty clients, it must ensure that the upgrade is first supported by IBC using the [IBC Client Breaking Upgrade List](./01-quick-guide.md#ibc-client-breaking-upgrades) and then execute the upgrade process described below in order to prevent counterparty clients from breaking. + +1. Create a 02-client [`UpgradeProposal`](https://github.com/cosmos/ibc-go/blob/v6.2.0/proto/ibc/core/client/v1/client.proto#L58-L77) with an `UpgradePlan` and a new IBC ClientState in the `UpgradedClientState` field. Note that the `UpgradePlan` must specify an upgrade height **only** (no upgrade time), and the `ClientState` should only include the fields common to all valid clients and zero out any client-customizable fields (such as TrustingPeriod). +2. Vote on and pass the `UpgradeProposal` +3. Halt the node after successful upgrade. +4. Export the genesis file. +5. Swap to the new binary. +6. Run migrations on the genesis file. +7. Remove the `UpgradeProposal` plan from the genesis file. This may be done by migrations. +8. Change desired chain-specific fields (chain id, unbonding period, etc). This may be done by migrations. +8. Reset the node's data. +9. Start the chain. + +Upon the `UpgradeProposal` passing, the upgrade module will commit the UpgradedClient under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedClient`. On the block right before the upgrade height, the upgrade module will also commit an initial consensus state for the next chain under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedConsState`. + +Once the chain reaches the upgrade height and halts, a relayer can upgrade the counterparty clients to the last block of the old chain. They can then submit the proofs of the `UpgradedClient` and `UpgradedConsensusState` against this last block and upgrade the counterparty client. + +#### Step-by-Step Upgrade Process for Relayers Upgrading Counterparty Clients + +These steps are identical to the regular [IBC client breaking upgrade process](./01-quick-guide.md#step-by-step-upgrade-process-for-relayers-upgrading-counterparty-clients). + +### Non-IBC Client Breaking Upgrades + +While ibc-go supports genesis restarts which do not break IBC clients, relayers do not support this upgrade path. +Here is a tracking issue on [Hermes](https://github.com/informalsystems/ibc-rs/issues/1152). +Please do not attempt a regular genesis restarts unless you have a tool to update counterparty clients correctly. diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/05-upgrades/_category_.json b/docs/versioned_docs/version-v6.3.x/01-ibc/05-upgrades/_category_.json new file mode 100644 index 0000000..439f7ee --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/01-ibc/05-upgrades/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Upgrades", + "position": 5, + "link": { "type": "doc", "id": "intro" } +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/06-proposals.md b/docs/versioned_docs/version-v6.3.x/01-ibc/06-proposals.md new file mode 100644 index 0000000..1b4f964 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/01-ibc/06-proposals.md @@ -0,0 +1,130 @@ +--- +title: Governance Proposals +sidebar_label: Governance Proposals +sidebar_position: 6 +slug: /ibc/proposals +--- + +# Governance Proposals + +In uncommon situations, a highly valued client may become frozen due to uncontrollable +circumstances. A highly valued client might have hundreds of channels being actively used. +Some of those channels might have a significant amount of locked tokens used for ICS 20. + +If the one third of the validator set of the chain the client represents decides to collude, +they can sign off on two valid but conflicting headers each signed by the other one third +of the honest validator set. The light client can now be updated with two valid, but conflicting +headers at the same height. The light client cannot know which header is trustworthy and therefore +evidence of such misbehaviour is likely to be submitted resulting in a frozen light client. + +Frozen light clients cannot be updated under any circumstance except via a governance proposal. +Since a quorum of validators can sign arbitrary state roots which may not be valid executions +of the state machine, a governance proposal has been added to ease the complexity of unfreezing +or updating clients which have become "stuck". Without this mechanism, validator sets would need +to construct a state root to unfreeze the client. Unfreezing clients, re-enables all of the channels +built upon that client. This may result in recovery of otherwise lost funds. + +Tendermint light clients may become expired if the trusting period has passed since their +last update. This may occur if relayers stop submitting headers to update the clients. + +An unplanned upgrade by the counterparty chain may also result in expired clients. If the counterparty +chain undergoes an unplanned upgrade, there may be no commitment to that upgrade signed by the validator +set before the chain-id changes. In this situation, the validator set of the last valid update for the +light client is never expected to produce another valid header since the chain-id has changed, which will +ultimately lead the on-chain light client to become expired. + +In the case that a highly valued light client is frozen, expired, or rendered non-updateable, a +governance proposal may be submitted to update this client, known as the subject client. The +proposal includes the client identifier for the subject and the client identifier for a substitute +client. Light client implementations may implement custom updating logic, but in most cases, +the subject will be updated to the latest consensus state of the substitute client, if the proposal passes. +The substitute client is used as a "stand in" while the subject is on trial. It is best practice to create +a substitute client *after* the subject has become frozen to avoid the substitute from also becoming frozen. +An active substitute client allows headers to be submitted during the voting period to prevent accidental expiry +once the proposal passes. + +## How to recover an expired client with a governance proposal + +See also the relevant documentation: [ADR-026, IBC client recovery mechanisms](/architecture/adr-026-ibc-client-recovery-mechanisms) + +> **Who is this information for?** +> Although technically anyone can submit the governance proposal to recover an expired client, often it will be **relayer operators** (at least coordinating the submission). + +### Preconditions + +- The chain is updated with ibc-go >= v1.1.0. +- There exists an active client (with a known client identifier) for the same counterparty chain as the expired client. +- The governance deposit. + +## Steps + +### Step 1 + +Check if the client is attached to the expected `chain-id`. For example, for an expired Tendermint client representing the Akash chain the client state looks like this on querying the client state: + +```text +{ + client_id: 07-tendermint-146 + client_state: + '@type': /ibc.lightclients.tendermint.v1.ClientState + allow_update_after_expiry: true + allow_update_after_misbehaviour: true + chain_id: akashnet-2 +} +``` + +The client is attached to the expected Akash `chain-id`. Note that although the parameters (`allow_update_after_expiry` and `allow_update_after_misbehaviour`) exist to signal intent, these parameters have been deprecated and will not enforce any checks on the revival of client. See ADR-026 for more context on this deprecation. + +### Step 2 + +If the chain has been updated to ibc-go >= v1.1.0, anyone can submit the governance proposal to recover the client by executing this via CLI. + +> Note that the Cosmos SDK has updated how governance proposals are submitted in SDK v0.46, now requiring to pass a .json proposal file + +- From SDK v0.46.x onwards + + ```bash + tx gov submit-proposal [path-to-proposal-json] + ``` + + where `proposal.json` contains: + + ```json + { + "messages": [ + { + "@type": "/ibc.core.client.v1.ClientUpdateProposal", + "title": "title_string", + "description": "description_string", + "subject_client_id": "expired_client_id_string", + "substitute_client_id": "active_client_id_string" + } + ], + "metadata": "", + "deposit": "10stake" + } + ``` + + Alternatively there's a legacy command (that is no longer recommended though): + + ```bash + tx gov submit-legacy-proposal update-client + ``` + +- Until SDK v0.45.x + + ```bash + tx gov submit-proposal update-client + ``` + +The `` identifier is the proposed client to be updated. This client must be either frozen or expired. + +The `` represents a substitute client. It carries all the state for the client which may be updated. It must have identical client and chain parameters to the client which may be updated (except for latest height, frozen height, and chain ID). It should be continually updated during the voting period. + +After this, all that remains is deciding who funds the governance deposit and ensuring the governance proposal passes. If it does, the client on trial will be updated to the latest state of the substitute. + +## Important considerations + +Please note that from v1.0.0 of ibc-go it will not be allowed for transactions to go to expired clients anymore, so please update to at least this version to prevent similar issues in the future. + +Please also note that if the client on the other end of the transaction is also expired, that client will also need to update. This process updates only one client. diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/07-relayer.md b/docs/versioned_docs/version-v6.3.x/01-ibc/07-relayer.md new file mode 100644 index 0000000..3d63e97 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/01-ibc/07-relayer.md @@ -0,0 +1,53 @@ +--- +title: Relayer +sidebar_label: Relayer +sidebar_position: 7 +slug: /ibc/relayer +--- + +# Relayer + +:::note + +## Pre-requisite readings + +- [IBC Overview](01-overview.md) +- Events + +::: + +## Events + +Events are emitted for every transaction processed by the base application to indicate the execution +of some logic clients may want to be aware of. This is extremely useful when relaying IBC packets. +Any message that uses IBC will emit events for the corresponding TAO logic executed as defined in +the [IBC events document](/events/events). + +In the SDK, it can be assumed that for every message there is an event emitted with the type `message`, +attribute key `action`, and an attribute value representing the type of message sent +(`channel_open_init` would be the attribute value for `MsgChannelOpenInit`). If a relayer queries +for transaction events, it can split message events using this event Type/Attribute Key pair. + +The Event Type `message` with the Attribute Key `module` may be emitted multiple times for a single +message due to application callbacks. It can be assumed that any TAO logic executed will result in +a module event emission with the attribute value `ibc_` (02-client emits `ibc_client`). + +### Subscribing with Tendermint + +Calling the Tendermint RPC method `Subscribe` via Tendermint's Websocket will return events using +Tendermint's internal representation of them. Instead of receiving back a list of events as they +were emitted, Tendermint will return the type `map[string][]string` which maps a string in the +form `.` to `attribute_value`. This causes extraction of the event +ordering to be non-trivial, but still possible. + +A relayer should use the `message.action` key to extract the number of messages in the transaction +and the type of IBC transactions sent. For every IBC transaction within the string array for +`message.action`, the necessary information should be extracted from the other event fields. If +`send_packet` appears at index 2 in the value for `message.action`, a relayer will need to use the +value at index 2 of the key `send_packet.packet_sequence`. This process should be repeated for each +piece of information needed to relay a packet. + +## Example Implementations + +- [Golang Relayer](https://github.com/cosmos/relayer) +- [Hermes](https://github.com/informalsystems/hermes) diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/08-proto-docs.md b/docs/versioned_docs/version-v6.3.x/01-ibc/08-proto-docs.md new file mode 100644 index 0000000..9b3f475 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/01-ibc/08-proto-docs.md @@ -0,0 +1,10 @@ +--- +title: Protobuf Documentation +sidebar_label: Protobuf Documentation +sidebar_position: 8 +slug: /ibc/proto-docs +--- + +# Protobuf documentation + +See [ibc-go v6.2.x Buf Protobuf documentation](https://github.com/cosmos/ibc-go/blob/release/v6.2.x/docs/ibc/proto-docs.md). diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/09-roadmap.md b/docs/versioned_docs/version-v6.3.x/01-ibc/09-roadmap.md new file mode 100644 index 0000000..86ffb8a --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/01-ibc/09-roadmap.md @@ -0,0 +1,60 @@ +--- +title: Roadmap +sidebar_label: Roadmap +sidebar_position: 9 +slug: /roadmap/roadmap +--- + +# Roadmap ibc-go + +*Latest update: July 7, 2022* + +This document endeavours to inform the wider IBC community about plans and priorities for work on ibc-go by the team at Interchain GmbH. It is intended to broadly inform all users of ibc-go, including developers and operators of IBC, relayer, chain and wallet applications. + +This roadmap should be read as a high-level guide, rather than a commitment to schedules and deliverables. The degree of specificity is inversely proportional to the timeline. We will update this document periodically to reflect the status and plans. + +## Q3 - 2022 + +At a high level we will focus on: + +### Features + +- Releasing [v4.0.0](https://github.com/cosmos/ibc-go/milestone/26), which includes the ICS-29 Fee Middleware module. +- Finishing and releasing the [refactoring of 02-client](https://github.com/cosmos/ibc-go/milestone/16). This refactor will make the development of light clients easier. +- Starting the implementation of channel upgradability (see [epic](https://github.com/cosmos/ibc-go/issues/1599) and [alpha milestone](https://github.com/cosmos/ibc-go/milestone/29)) with the goal of cutting an alpha1 pre-release by the end of the quarter. Channel upgradability will allow chains to renegotiate an existing channel to take advantage of new features without having to create a new channel, thus preserving all existing packet state processed on the channel. +- Implementing the new [`ORDERED_ALLOW_TIMEOUT` channel type](https://github.com/cosmos/ibc-go/milestone/31) and hopefully releasing it as well. This new channel type will allow packets on an ordered channel to timeout without causing the closure of the channel. + +### Testing and infrastructure + +- Adding [automated e2e tests](https://github.com/cosmos/ibc-go/milestone/32) to the repo's CI. + +### Documentation and backlog + +- Finishing and releasing the upgrade to Cosmos SDK v0.46. +- Writing the [light client implementation guide](https://github.com/cosmos/ibc-go/issues/59). +- Working on [core backlog issues](https://github.com/cosmos/ibc-go/milestone/28). +- Depending on the timeline of the Cosmos SDK, implementing and testing the changes needed to support the [transition to SMT storage](https://github.com/cosmos/ibc-go/milestone/21). + +We have also received a lot of feedback to improve Interchain Accounts and we might also work on a few things, but will depend on priorities and availability. + +For a detail view of each iteration's planned work, please check out our [project board](https://github.com/orgs/cosmos/projects/7). + +### Release schedule + +#### **July** + +We will probably cut at least one more release candidate of v4.0.0 before the final release, which should happen around the end of the month. + +For the Rho upgrade of the Cosmos Hub we will also release a new minor version of v3 with SDK 0.46. + +#### **August** + +In the first half we will probably start cutting release candidates for the 02-client refactor. Final release would most likely come out at the end of the month or beginning of September. + +#### **September** + +We might cut some pre-releases for the new channel type, and by the end of the month we expect to cut the first alpha pre-release for channel upgradability. + +## Q4 - 2022 + +We will continue the implementation and cut the final release of [channel upgradability](https://github.com/cosmos/ibc/blob/master/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.md). At the end of Q3 or maybe beginning of Q4 we might also work on designing the implementation and scoping the engineering work to add support for [multihop channels](https://github.com/cosmos/ibc/pull/741/files), so that we could start the implementation of this feature during Q4 (but this is still be decided). diff --git a/docs/versioned_docs/version-v6.3.x/01-ibc/_category_.json b/docs/versioned_docs/version-v6.3.x/01-ibc/_category_.json new file mode 100644 index 0000000..afc6d18 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/01-ibc/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Using IBC-Go", + "position": 1, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/01-overview.md b/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/01-overview.md new file mode 100644 index 0000000..ed696b3 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/01-overview.md @@ -0,0 +1,128 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /apps/transfer/overview +--- + +# Overview + +:::note Synopsis +Learn about what the token Transfer module is +::: + +## What is the Transfer module? + +Transfer is the Cosmos SDK implementation of the [ICS-20](https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer) protocol, which enables cross-chain fungible token transfers. + +## Concepts + +### Acknowledgements + +ICS20 uses the recommended acknowledgement format as specified by [ICS 04](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope). + +A successful receive of a transfer packet will result in a Result Acknowledgement being written +with the value `[]byte{byte(1)}` in the `Response` field. + +An unsuccessful receive of a transfer packet will result in an Error Acknowledgement being written +with the error message in the `Response` field. + +### Denomination trace + +The denomination trace corresponds to the information that allows a token to be traced back to its +origin chain. It contains a sequence of port and channel identifiers ordered from the most recent to +the oldest in the timeline of transfers. + +This information is included on the token denomination field in the form of a hash to prevent an +unbounded denomination length. For example, the token `transfer/channelToA/uatom` will be displayed +as `ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2`. + +Each send to any chain other than the one it was previously received from is a movement forwards in +the token's timeline. This causes trace to be added to the token's history and the destination port +and destination channel to be prefixed to the denomination. In these instances the sender chain is +acting as the "source zone". When the token is sent back to the chain it previously received from, the +prefix is removed. This is a backwards movement in the token's timeline and the sender chain is +acting as the "sink zone". + +It is strongly recommended to read the full details of [ADR 001: Coin Source Tracing](/architecture/adr-001-coin-source-tracing) to understand the implications and context of the IBC token representations. + +## UX suggestions for clients + +For clients (wallets, exchanges, applications, block explorers, etc) that want to display the source of the token, it is recommended to use the following alternatives for each of the cases below: + +### Direct connection + +If the denomination trace contains a single identifier prefix pair (as in the example above), then +the easiest way to retrieve the chain and light client identifier is to map the trace information +directly. In summary, this requires querying the channel from the denomination trace identifiers, +and then the counterparty client state using the counterparty port and channel identifiers from the +retrieved channel. + +A general pseudo algorithm would look like the following: + +1. Query the full denomination trace. +2. Query the channel with the `portID/channelID` pair, which corresponds to the first destination of the + token. +3. Query the client state using the identifiers pair. Note that this query will return a `"Not +Found"` response if the current chain is not connected to this channel. +4. Retrieve the client identifier or chain identifier from the client state (eg: on + Tendermint clients) and store it locally. + +Using the gRPC gateway client service the steps above would be, with a given IBC token `ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2` stored on `chainB`: + +1. `GET /ibc/apps/transfer/v1/denom_traces/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2` -> `{"path": "transfer/channelToA", "base_denom": "uatom"}` +2. `GET /ibc/apps/transfer/v1/channels/channelToA/ports/transfer/client_state"` -> `{"client_id": "clientA", "chain-id": "chainA", ...}` +3. `GET /ibc/apps/transfer/v1/channels/channelToA/ports/transfer"` -> `{"channel_id": "channelToA", port_id": "transfer", counterparty: {"channel_id": "channelToB", port_id": "transfer"}, ...}` +4. `GET /ibc/apps/transfer/v1/channels/channelToB/ports/transfer/client_state" -> {"client_id": "clientB", "chain-id": "chainB", ...}` + +Then, the token transfer chain path for the `uatom` denomination would be: `chainA` -> `chainB`. + +### Multiple hops + +The multiple channel hops case applies when the token has passed through multiple chains between the original source and final destination chains. + +The IBC protocol doesn't know the topology of the overall network (i.e connections between chains and identifier names between them). For this reason, in the multiple hops case, a particular chain in the timeline of the individual transfers can't query the chain and client identifiers of the other chains. + +Take for example the following sequence of transfers `A -> B -> C` for an IBC token, with a final prefix path (trace info) of `transfer/channelChainC/transfer/channelChainB`. What the paragraph above means is that even in the case that chain `C` is directly connected to chain `A`, querying the port and channel identifiers that chain `B` uses to connect to chain `A` (eg: `transfer/channelChainA`) can be completely different from the one that chain `C` uses to connect to chain `A` (eg: `transfer/channelToChainA`). + +Thus the proposed solution for clients that the IBC team recommends are the following: + +- **Connect to all chains**: Connecting to all the chains in the timeline would allow clients to + perform the queries outlined in the [direct connection](#direct-connection) section to each + relevant chain. By repeatedly following the port and channel denomination trace transfer timeline, + clients should always be able to find all the relevant identifiers. This comes at the tradeoff + that the client must connect to nodes on each of the chains in order to perform the queries. +- **Relayer as a Service (RaaS)**: A longer term solution is to use/create a relayer service that + could map the denomination trace to the chain path timeline for each token (i.e `origin chain -> +chain #1 -> ... -> chain #(n-1) -> final chain`). These services could provide merkle proofs in + order to allow clients to optionally verify the path timeline correctness for themselves by + running light clients. If the proofs are not verified, they should be considered as trusted third + parties services. Additionally, client would be advised in the future to use RaaS that support the + largest number of connections between chains in the ecosystem. Unfortunately, none of the existing + public relayers (in [Golang](https://github.com/cosmos/relayer) and + [Rust](https://github.com/informalsystems/ibc-rs)), provide this service to clients. + +:::tip +The only viable alternative for clients (at the time of writing) to tokens with multiple connection hops, is to connect to all chains directly and perform relevant queries to each of them in the sequence. +::: + +## Locked funds + +In some [exceptional cases](/architecture/adr-026-ibc-client-recovery-mechanisms#exceptional-cases), a client state associated with a given channel cannot be updated. This causes that funds from fungible tokens in that channel will be permanently locked and thus can no longer be transferred. + +To mitigate this, a client update governance proposal can be submitted to update the frozen client +with a new valid header. Once the proposal passes the client state will be unfrozen and the funds +from the associated channels will then be unlocked. This mechanism only applies to clients that +allow updates via governance, such as Tendermint clients. + +In addition to this, it's important to mention that a token must be sent back along the exact route +that it took originally in order to return it to its original form on the source chain (eg: the +Cosmos Hub for the `uatom`). Sending a token back to the same chain across a different channel will +**not** move the token back across its timeline. If a channel in the chain history closes before the +token can be sent back across that channel, then the token will not be returnable to its original +form. + +## Security considerations + +For safety, no other module must be capable of minting tokens with the `ibc/` prefix. The IBC +transfer module needs a subset of the denomination space that only it can create tokens in. diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/02-state.md b/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/02-state.md new file mode 100644 index 0000000..17f48a2 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/02-state.md @@ -0,0 +1,13 @@ +--- +title: State +sidebar_label: State +sidebar_position: 2 +slug: /apps/transfer/state +--- + +# State + +The IBC transfer application module keeps state of the port to which the module is binded and the denomination trace information as outlined in [ADR 001](/architecture/adr-001-coin-source-tracing). + +- `Port`: `0x01 -> ProtocolBuffer(string)` +- `DenomTrace`: `0x02 | []bytes(traceHash) -> ProtocolBuffer(DenomTrace)` diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/03-state-transitions.md b/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/03-state-transitions.md new file mode 100644 index 0000000..23bf1bf --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/03-state-transitions.md @@ -0,0 +1,37 @@ +--- +title: State Transitions +sidebar_label: State Transitions +sidebar_position: 3 +slug: /apps/transfer/state-transitions +--- + +# State transitions + +## Send fungible tokens + +A successful fungible token send has two state transitions depending if the transfer is a movement forward or backwards in the token's timeline: + +1. Sender chain is the source chain, *i.e* a transfer to any chain other than the one it was previously received from is a movement forwards in the token's timeline. This results in the following state transitions: + + - The coins are transferred to an escrow address (i.e locked) on the sender chain. + - The coins are transferred to the receiving chain through IBC TAO logic. + +2. Sender chain is the sink chain, *i.e* the token is sent back to the chain it previously received from. This is a backwards movement in the token's timeline. This results in the following state transitions: + + - The coins (vouchers) are burned on the sender chain. + - The coins are transferred to the receiving chain through IBC TAO logic. + +## Receive fungible tokens + +A successful fungible token receive has two state transitions depending if the transfer is a movement forward or backwards in the token's timeline: + +1. Receiver chain is the source chain. This is a backwards movement in the token's timeline. This results in the following state transitions: + + - The leftmost port and channel identifier pair is removed from the token denomination prefix. + - The tokens are unescrowed and sent to the receiving address. + +2. Receiver chain is the sink chain. This is a movement forwards in the token's timeline. This results in the following state transitions: + + - Token vouchers are minted by prefixing the destination port and channel identifiers to the trace information. + - The receiving chain stores the new trace information in the store (if not set already). + - The vouchers are sent to the receiving address. diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/04-messages.md b/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/04-messages.md new file mode 100644 index 0000000..1e27c60 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/04-messages.md @@ -0,0 +1,46 @@ +--- +title: Messages +sidebar_label: Messages +sidebar_position: 4 +slug: /apps/transfer/messages +--- + +# Messages + +## `MsgTransfer` + +A fungible token cross chain transfer is achieved by using the `MsgTransfer`: + +```go +type MsgTransfer struct { + SourcePort string + SourceChannel string + Token sdk.Coin + Sender string + Receiver string + TimeoutHeight ibcexported.Height + TimeoutTimestamp uint64 + Memo string +} +``` + +This message is expected to fail if: + +- `SourcePort` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators). +- `SourceChannel` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +- `Token` is invalid (denom is invalid or amount is negative) + - `Token.Amount` is not positive. + - `Token.Denom` is not a valid IBC denomination as per [ADR 001 - Coin Source Tracing](/architecture/adr-001-coin-source-tracing). +- `Sender` is empty. +- `Receiver` is empty. +- `TimeoutHeight` and `TimeoutTimestamp` are both zero. + +This message will send a fungible token to the counterparty chain represented by the counterparty Channel End connected to the Channel End with the identifiers `SourcePort` and `SourceChannel`. + +The denomination provided for transfer should correspond to the same denomination represented on this chain. The prefixes will be added as necessary upon by the receiving chain. + +### Memo + +The memo field was added to allow applications and users to attach metadata to transfer packets. The field is optional and may be left empty. When it is used to attach metadata for a particular middleware, the memo field should be represented as a json object where different middlewares use different json keys. + +You can find more information about applications that use the memo field in the [chain registry](https://github.com/cosmos/chain-registry/blob/master/_memo_keys/ICS20_memo_keys.json). diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/05-events.md b/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/05-events.md new file mode 100644 index 0000000..68eb4a4 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/05-events.md @@ -0,0 +1,55 @@ +--- +title: Events +sidebar_label: Events +sidebar_position: 5 +slug: /apps/transfer/events +--- + + +# Events + +## `MsgTransfer` + +| Type | Attribute Key | Attribute Value | +|--------------|---------------|-----------------| +| ibc_transfer | sender | \{sender\} | +| ibc_transfer | receiver | \{receiver\} | +| message | module | transfer | + +## `OnRecvPacket` callback + +| Type | Attribute Key | Attribute Value | +|-----------------------|---------------|-----------------| +| fungible_token_packet | module | transfer | +| fungible_token_packet | sender | \{sender\} | +| fungible_token_packet | receiver | \{receiver\} | +| fungible_token_packet | denom | \{denom\} | +| fungible_token_packet | amount | \{amount\} | +| fungible_token_packet | memo | \{memo\} | +| fungible_token_packet | success | \{ackSuccess\} | +| fungible_token_packet | error | \{ackError\} | +| denomination_trace | trace_hash | \{hex_hash\} | +| denomination_trace | denom | \{voucherDenom\}| + +## `OnAcknowledgePacket` callback + +| Type | Attribute Key | Attribute Value | +|-----------------------|-----------------|------------------| +| fungible_token_packet | module | transfer | +| fungible_token_packet | sender | \{sender\} | +| fungible_token_packet | receiver | \{receiver\} | +| fungible_token_packet | denom | \{denom\} | +| fungible_token_packet | amount | \{amount\} | +| fungible_token_packet | memo | \{memo\} | +| fungible_token_packet | acknowledgement | \{ack.String()\} | +| fungible_token_packet | success / error | \{ack.Response\} | + +## `OnTimeoutPacket` callback + +| Type | Attribute Key | Attribute Value | +|-----------------------|-----------------|-----------------| +| fungible_token_packet | module | transfer | +| fungible_token_packet | refund_receiver | \{receiver\} | +| fungible_token_packet | denom | \{denom\} | +| fungible_token_packet | amount | \{amount\} | +| fungible_token_packet | memo | \{memo\} | diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/06-metrics.md b/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/06-metrics.md new file mode 100644 index 0000000..7f6087e --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/06-metrics.md @@ -0,0 +1,18 @@ +--- +title: Metrics +sidebar_label: Metrics +sidebar_position: 6 +slug: /apps/transfer/metrics +--- + + +# Metrics + +The IBC transfer application module exposes the following set of metrics. + +| Metric | Description | Unit | Type | +|:--------------------------------|:------------------------------------------------------------------------------------------|:----------------|:--------| +| `tx_msg_ibc_transfer` | The total amount of tokens transferred via IBC in a `MsgTransfer` (source or sink chain) | token | gauge | +| `ibc_transfer_packet_receive` | The total amount of tokens received in a `FungibleTokenPacketData` (source or sink chain) | token | gauge | +| `ibc_transfer_send` | Total number of IBC transfers sent from a chain (source or sink) | transfer | counter | +| `ibc_transfer_receive` | Total number of IBC transfers received to a chain (source or sink) | transfer | counter | diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/07-params.md b/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/07-params.md new file mode 100644 index 0000000..c4c9330 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/07-params.md @@ -0,0 +1,34 @@ +--- +title: Params +sidebar_label: Params +sidebar_position: 7 +slug: /apps/transfer/params +--- + + +# Parameters + +The IBC transfer application module contains the following parameters: + +| Key | Type | Default Value | +|------------------|------|---------------| +| `SendEnabled` | bool | `true` | +| `ReceiveEnabled` | bool | `true` | + +## `SendEnabled` + +The transfers enabled parameter controls send cross-chain transfer capabilities for all fungible tokens. + +To prevent a single token from being transferred from the chain, set the `SendEnabled` parameter to `true` and then, depending on the Cosmos SDK version, do one of the following: + +- For Cosmos SDK v0.46.x or earlier, set the bank module's [`SendEnabled` parameter](https://github.com/cosmos/cosmos-sdk/blob/release/v0.46.x/x/bank/spec/05_params.md#sendenabled) for the denomination to `false`. +- For Cosmos SDK versions above v0.46.x, set the bank module's `SendEnabled` entry for the denomination to `false` using `MsgSetSendEnabled` as a governance proposal. + +## `ReceiveEnabled` + +The transfers enabled parameter controls receive cross-chain transfer capabilities for all fungible tokens. + +To prevent a single token from being transferred to the chain, set the `ReceiveEnabled` parameter to `true` and then, depending on the Cosmos SDK version, do one of the following: + +- For Cosmos SDK v0.46.x or earlier, set the bank module's [`SendEnabled` parameter](https://github.com/cosmos/cosmos-sdk/blob/release/v0.46.x/x/bank/spec/05_params.md#sendenabled) for the denomination to `false`. +- For Cosmos SDK versions above v0.46.x, set the bank module's `SendEnabled` entry for the denomination to `false` using `MsgSetSendEnabled` as a governance proposal. diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/08-authorizations.md b/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/08-authorizations.md new file mode 100644 index 0000000..cb72789 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/08-authorizations.md @@ -0,0 +1,54 @@ +--- +title: Authorizations +sidebar_label: Authorizations +sidebar_position: 8 +slug: /apps/transfer/authorizations +--- + +# `TransferAuthorization` + +`TransferAuthorization` implements the `Authorization` interface for `ibc.applications.transfer.v1.MsgTransfer`. It allows a granter to grant a grantee the privilege to submit `MsgTransfer` on its behalf. Please see the [Cosmos SDK docs](https://docs.cosmos.network/v0.47/modules/authz) for more details on granting privileges via the `x/authz` module. + +More specifically, the granter allows the grantee to transfer funds that belong to the granter over a specified channel. + +For the specified channel, the granter must be able to specify a spend limit of a specific denomination they wish to allow the grantee to be able to transfer. + +The granter may be able to specify the list of addresses that they allow to receive funds. If empty, then all addresses are allowed. + +It takes: + +- a `SourcePort` and a `SourceChannel` which together comprise the unique transfer channel identifier over which authorized funds can be transferred. + +- a `SpendLimit` that specifies the maximum amount of tokens the grantee can transfer. The `SpendLimit` is updated as the tokens are transferred, unless the sentinel value of the maximum value for a 256-bit unsigned integer (i.e. 2^256 - 1) is used for the amount, in which case the `SpendLimit` will not be updated (please be aware that using this sentinel value will grant the grantee the privilege to transfer **all** the tokens of a given denomination available at the granter's account). The helper function `UnboundedSpendLimit` in the `types` package of the `transfer` module provides the sentinel value that can be used. This `SpendLimit` may also be updated to increase or decrease the limit as the granter wishes. + +- an `AllowList` list that specifies the list of addresses that are allowed to receive funds. If this list is empty, then all addresses are allowed to receive funds from the `TransferAuthorization`. + +Setting a `TransferAuthorization` is expected to fail if: + +- the spend limit is nil +- the denomination of the spend limit is an invalid coin type +- the source port ID is invalid +- the source channel ID is invalid +- there are duplicate entries in the `AllowList` + +Below is the `TransferAuthorization` message: + +```golang +func NewTransferAuthorization(allocations ...Allocation) *TransferAuthorization { + return &TransferAuthorization{ + Allocations: allocations, + } +} + +type Allocation struct { + // the port on which the packet will be sent + SourcePort string + // the channel by which the packet will be sent + SourceChannel string + // spend limitation on the channel + SpendLimit sdk.Coins + // allow list of receivers, an empty allow list permits any receiver address + AllowList []string +} + +``` diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/_category_.json b/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/_category_.json new file mode 100644 index 0000000..50d492b --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/02-apps/01-transfer/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Transfer", + "position": 1, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/01-overview.md b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/01-overview.md new file mode 100644 index 0000000..fafe184 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/01-overview.md @@ -0,0 +1,39 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /apps/interchain-accounts/overview +--- + + +# Overview + +:::note Synopsis +Learn about what the Interchain Accounts module is +::: + +## What is the Interchain Accounts module? + +Interchain Accounts is the Cosmos SDK implementation of the ICS-27 protocol, which enables cross-chain account management built upon IBC. + +- How does an interchain account differ from a regular account? + +Regular accounts use a private key to sign transactions. Interchain Accounts are instead controlled programmatically by counterparty chains via IBC packets. + +## Concepts + +`Host Chain`: The chain where the interchain account is registered. The host chain listens for IBC packets from a controller chain which should contain instructions (e.g. Cosmos SDK messages) for which the interchain account will execute. + +`Controller Chain`: The chain registering and controlling an account on a host chain. The controller chain sends IBC packets to the host chain to control the account. + +`Interchain Account`: An account on a host chain created using the ICS-27 protocol. An interchain account has all the capabilities of a normal account. However, rather than signing transactions with a private key, a controller chain will send IBC packets to the host chain which signals what transactions the interchain account should execute. + +`Authentication Module`: A custom application module on the controller chain that uses the Interchain Accounts module to build custom logic for the creation & management of interchain accounts. It can be either an IBC application module using the [legacy API](09-legacy/03-keeper-api.md), or a regular Cosmos SDK application module sending messages to the controller submodule's `MsgServer` (this is the recommended approach from ibc-go v6 if access to packet callbacks is not needed). Please note that the legacy API will eventually be removed and IBC applications will not be able to use them in later releases. + +## SDK security model + +SDK modules on a chain are assumed to be trustworthy. For example, there are no checks to prevent an untrustworthy module from accessing the bank keeper. + +The implementation of ICS-27 in ibc-go uses this assumption in its security considerations. + +The implementation assumes other IBC application modules will not bind to ports within the ICS-27 namespace. diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/02-development.md b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/02-development.md new file mode 100644 index 0000000..33acc42 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/02-development.md @@ -0,0 +1,40 @@ +--- +title: Development Use Cases +sidebar_label: Development Use Cases +sidebar_position: 2 +slug: /apps/interchain-accounts/development +--- + + +# Development use cases + +The initial version of Interchain Accounts allowed for the controller submodule to be extended by providing it with an underlying application which would handle all packet callbacks. +That functionality is now being deprecated in favor of alternative approaches. +This document will outline potential use cases and redirect each use case to the appropriate documentation. + +## Custom authentication + +Interchain accounts may be associated with alternative types of authentication relative to the traditional public/private key signing. +If you wish to develop or use Interchain Accounts with a custom authentication module and do not need to execute custom logic on the packet callbacks, we recommend you use ibc-go v6 or greater and that your custom authentication module interacts with the controller submodule via the [`MsgServer`](05-messages.md). + +If you wish to consume and execute custom logic in the packet callbacks, then please read the section [Packet callbacks](#packet-callbacks) below. + +## Redirection to a smart contract + +It may be desirable to allow smart contracts to control an interchain account. +To facilitate such an action, the controller submodule may be provided an underlying application which redirects to smart contract callers. +An improved design has been suggested in [ADR 008](https://github.com/cosmos/ibc-go/pull/1976) which performs this action via middleware. + +Implementers of this use case are recommended to follow the ADR 008 approach. +The underlying application may continue to be used as a short term solution for ADR 008 and the [legacy API](./09-legacy/01-auth-modules.md) should continue to be utilized in such situations. + +## Packet callbacks + +If a developer requires access to packet callbacks for their use case, then they have the following options: + +1. Write a smart contract which is connected via an ADR 008 or equivalent IBC application (recommended). +2. Use the controller's underlying application to implement packet callback logic. + +In the first case, the smart contract should use the [`MsgServer`](05-messages.md). + +In the second case, the underlying application should use the [legacy API](09-legacy/03-keeper-api.md). diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/03-auth-modules.md b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/03-auth-modules.md new file mode 100644 index 0000000..3f82bce --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/03-auth-modules.md @@ -0,0 +1,27 @@ +--- +title: Authentication Modules +sidebar_label: Authentication Modules +sidebar_position: 3 +slug: /apps/interchain-accounts/auth-modules +--- + + +# Building an authentication module + +:::note Synopsis +Authentication modules enable application developers to perform custom logic when interacting with the Interchain Accounts controller sumbmodule's `MsgServer`. +::: + +The controller submodule is used for account registration and packet sending. It executes only logic required of all controllers of interchain accounts. The type of authentication used to manage the interchain accounts remains unspecified. There may exist many different types of authentication which are desirable for different use cases. Thus the purpose of the authentication module is to wrap the controller submodule with custom authentication logic. + +In ibc-go, authentication modules can communicate with the controller submodule by passing messages through `baseapp`'s `MsgServiceRouter`. To implement an authentication module, the `IBCModule` interface need not be fulfilled; it is only required to fulfill Cosmos SDK's `AppModuleBasic` interface, just like any regular Cosmos SDK application module. + +The authentication module must: + +- Authenticate interchain account owners. +- Track the associated interchain account address for an owner. +- Send packets on behalf of an owner (after authentication). + +## Integration into `app.go` file + +To integrate the authentication module into your chain, please follow the steps outlined in [`app.go` integration](04-integration.md#example-integration). diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/04-integration.md b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/04-integration.md new file mode 100644 index 0000000..850d712 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/04-integration.md @@ -0,0 +1,199 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 4 +slug: /apps/interchain-accounts/integration +--- + + +# Integration + +:::note Synopsis +Learn how to integrate Interchain Accounts host and controller functionality to your chain. The following document only applies for Cosmos SDK chains. +::: + +The Interchain Accounts module contains two submodules. Each submodule has its own IBC application. The Interchain Accounts module should be registered as an `AppModule` in the same way all SDK modules are registered on a chain, but each submodule should create its own `IBCModule` as necessary. A route should be added to the IBC router for each submodule which will be used. + +Chains who wish to support ICS-27 may elect to act as a host chain, a controller chain or both. Disabling host or controller functionality may be done statically by excluding the host or controller submodule entirely from the `app.go` file or it may be done dynamically by taking advantage of the on-chain parameters which enable or disable the host or controller submodules. + +Interchain Account authentication modules (both custom or generic, such as the `x/gov`, `x/group` or `x/auth` Cosmos SDK modules) can send messages to the controller submodule's [`MsgServer`](05-messages.md) to register interchain accounts and send packets to the interchain account. To accomplish this, the authentication module needs to be composed with `baseapp`'s `MsgServiceRouter`. + +![ica-v6.png](./images/ica-v6.png) + +## Example integration + +```go +// app.go + +// Register the AppModule for the Interchain Accounts module and the authentication module +// Note: No `icaauth` exists, this must be substituted with an actual Interchain Accounts authentication module +ModuleBasics = module.NewBasicManager( + ... + ica.AppModuleBasic{}, + icaauth.AppModuleBasic{}, + ... +) + +... + +// Add module account permissions for the Interchain Accounts module +// Only necessary for host chain functionality +// Each Interchain Account created on the host chain is derived from the module account created +maccPerms = map[string][]string{ + ... + icatypes.ModuleName: nil, +} + +... + +// Add Interchain Accounts Keepers for each submodule used and the authentication module +// If a submodule is being statically disabled, the associated Keeper does not need to be added. +type App struct { + ... + + ICAControllerKeeper icacontrollerkeeper.Keeper + ICAHostKeeper icahostkeeper.Keeper + ICAAuthKeeper icaauthkeeper.Keeper + + ... +} + +... + +// Create store keys for each submodule Keeper and the authentication module +keys := sdk.NewKVStoreKeys( + ... + icacontrollertypes.StoreKey, + icahosttypes.StoreKey, + icaauthtypes.StoreKey, + ... +) + +... + +// Create the scoped keepers for each submodule keeper and authentication keeper +scopedICAControllerKeeper := app.CapabilityKeeper.ScopeToModule(icacontrollertypes.SubModuleName) +scopedICAHostKeeper := app.CapabilityKeeper.ScopeToModule(icahosttypes.SubModuleName) +scopedICAAuthKeeper := app.CapabilityKeeper.ScopeToModule(icaauthtypes.ModuleName) + +... + +// Create the Keeper for each submodule +app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + scopedICAControllerKeeper, app.MsgServiceRouter(), +) +app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.AccountKeeper, scopedICAHostKeeper, app.MsgServiceRouter(), +) + +// Create Interchain Accounts AppModule +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper) + +// Create your Interchain Accounts authentication module +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.MsgServiceRouter()) + +// ICA auth AppModule +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) + +// Create controller IBC application stack and host IBC module as desired +icaControllerStack := icacontroller.NewIBCMiddleware(nil, app.ICAControllerKeeper) +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host and authentication routes +ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icahosttypes.SubModuleName, icaHostIBCModule) +... + +// Register Interchain Accounts and authentication module AppModule's +app.moduleManager = module.NewManager( + ... + icaModule, + icaAuthModule, +) + +... + +// Add Interchain Accounts to begin blocker logic +app.moduleManager.SetOrderBeginBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add Interchain Accounts to end blocker logic +app.moduleManager.SetOrderEndBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add Interchain Accounts module InitGenesis logic +app.moduleManager.SetOrderInitGenesis( + ... + icatypes.ModuleName, + ... +) + +// initParamsKeeper init params keeper and its subspaces +func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey sdk.StoreKey) paramskeeper.Keeper { + ... + paramsKeeper.Subspace(icahosttypes.SubModuleName) + paramsKeeper.Subspace(icacontrollertypes.SubModuleName) + ... +} +``` + +If no custom athentication module is needed and a generic Cosmos SDK authentication module can be used, then from the sample integration code above all references to `ICAAuthKeeper` and `icaAuthModule` can be removed. That's it, the following code would not be needed: + +```go +// Create your Interchain Accounts authentication module +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.MsgServiceRouter()) + +// ICA auth AppModule +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) +``` + +### Using submodules exclusively + +As described above, the Interchain Accounts application module is structured to support the ability of exclusively enabling controller or host functionality. +This can be achieved by simply omitting either controller or host `Keeper` from the Interchain Accounts `NewAppModule` constructor function, and mounting only the desired submodule via the `IBCRouter`. +Alternatively, submodules can be enabled and disabled dynamically using [on-chain parameters](06-parameters.md). + +The following snippets show basic examples of statically disabling submodules using `app.go`. + +#### Disabling controller chain functionality + +```go +// Create Interchain Accounts AppModule omitting the controller keeper +icaModule := ica.NewAppModule(nil, &app.ICAHostKeeper) + +// Create host IBC Module +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host route +ibcRouter.AddRoute(icahosttypes.SubModuleName, icaHostIBCModule) +``` + +#### Disabling host chain functionality + +```go +// Create Interchain Accounts AppModule omitting the host keeper +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, nil) + + +// Optionally instantiate your custom authentication module if needed, or not otherwise +... + +// Create controller IBC application stack +icaControllerStack := icacontroller.NewIBCMiddleware(nil, app.ICAControllerKeeper) + +// Register controller route +ibcRouter.AddRoute(icacontrollertypes.SubModuleName, icaControllerStack) +``` diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/05-messages.md b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/05-messages.md new file mode 100644 index 0000000..7e1643e --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/05-messages.md @@ -0,0 +1,77 @@ +--- +title: Messages +sidebar_label: Messages +sidebar_position: 5 +slug: /apps/interchain-accounts/messages +--- + + +# Messages + +## `MsgRegisterInterchainAccount` + +An Interchain Accounts channel handshake can be initiated using `MsgRegisterInterchainAccount`: + +```go +type MsgRegisterInterchainAccount struct { + Owner string + ConnectionID string + Version string +} +``` + +This message is expected to fail if: + +- `Owner` is an empty string. +- `ConnectionID` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). + +This message will construct a new `MsgChannelOpenInit` on chain and route it to the core IBC message server to initiate the opening step of the channel handshake. + +The controller submodule will generate a new port identifier and claim the associated port capability. The caller is expected to provide an appropriate application version string. For example, this may be an ICS-27 JSON encoded [`Metadata`](https://github.com/cosmos/ibc-go/blob/v6.0.0-rc0/proto/ibc/applications/interchain_accounts/v1/metadata.proto#L11) type or an ICS-29 JSON encoded [`Metadata`](https://github.com/cosmos/ibc-go/blob/v6.0.0-rc0/proto/ibc/applications/fee/v1/metadata.proto#L11) type with a nested application version. +If the `Version` string is omitted, the controller submodule will construct a default version string in the `OnChanOpenInit` handshake callback. + +```go +type MsgRegisterInterchainAccountResponse struct { + ChannelID string +} +``` + +The `ChannelID` is returned in the message response. + +## `MsgSendTx` + +An Interchain Accounts transaction can be executed on a remote host chain by sending a `MsgSendTx` from the corresponding controller chain: + +```go +type MsgSendTx struct { + Owner string + ConnectionID string + PacketData InterchainAccountPacketData + RelativeTimeout uint64 +} +``` + +This message is expected to fail if: + +- `Owner` is an empty string. +- `ConnectionID` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +- `PacketData` contains an `UNSPECIFIED` type enum, the length of `Data` bytes is zero or the `Memo` field exceeds 256 characters in length. +- `RelativeTimeout` is zero. + +This message will create a new IBC packet with the provided `PacketData` and send it via the channel associated with the `Owner` and `ConnectionID`. +The `PacketData` is expected to contain a list of serialized `[]sdk.Msg` in the form of `CosmosTx`. Please note the signer field of each `sdk.Msg` must be the interchain account address. +When the packet is relayed to the host chain, the `PacketData` is unmarshalled and the messages are authenticated and executed. + +```go +type MsgSendTxResponse struct { + Sequence uint64 +} +``` + +The packet `Sequence` is returned in the message response. + +## Atomicity + +As the Interchain Accounts module supports the execution of multiple transactions using the Cosmos SDK `Msg` interface, it provides the same atomicity guarantees as Cosmos SDK-based applications, leveraging the [`CacheMultiStore`](https://docs.cosmos.network/main/learn/advanced/store#cachemultistore) architecture provided by the [`Context`](https://docs.cosmos.network/main/learn/advanced/context.html) type. + +This provides atomic execution of transactions when using Interchain Accounts, where state changes are only committed if all `Msg`s succeed. diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/06-parameters.md b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/06-parameters.md new file mode 100644 index 0000000..4d579e4 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/06-parameters.md @@ -0,0 +1,65 @@ +--- +title: Parameters +sidebar_label: Parameters +sidebar_position: 6 +slug: /apps/interchain-accounts/parameters +--- + + +# Parameters + +The Interchain Accounts module contains the following on-chain parameters, logically separated for each distinct submodule: + +## Controller Submodule Parameters + +| Key | Type | Default Value | +|------------------------|------|---------------| +| `ControllerEnabled` | bool | `true` | + +### ControllerEnabled + +The `ControllerEnabled` parameter controls a chains ability to service ICS-27 controller specific logic. This includes the sending of Interchain Accounts packet data as well as the following ICS-26 callback handlers: + +- `OnChanOpenInit` +- `OnChanOpenAck` +- `OnChanCloseConfirm` +- `OnAcknowledgementPacket` +- `OnTimeoutPacket` + +## Host Submodule Parameters + +| Key | Type | Default Value | +|------------------------|----------|---------------| +| `HostEnabled` | bool | `true` | +| `AllowMessages` | []string | `["*"]` | + +### HostEnabled + +The `HostEnabled` parameter controls a chains ability to service ICS-27 host specific logic. This includes the following ICS-26 callback handlers: + +- `OnChanOpenTry` +- `OnChanOpenConfirm` +- `OnChanCloseConfirm` +- `OnRecvPacket` + +### AllowMessages + +The `AllowMessages` parameter provides the ability for a chain to limit the types of messages or transactions that hosted interchain accounts are authorized to execute by defining an allowlist using the Protobuf message type URL format. + +For example, a Cosmos SDK-based chain that elects to provide hosted Interchain Accounts with the ability of governance voting and staking delegations will define its parameters as follows: + +```json +"params": { + "host_enabled": true, + "allow_messages": ["/cosmos.staking.v1beta1.MsgDelegate", "/cosmos.gov.v1beta1.MsgVote"] +} +``` + +There is also a special wildcard `"*"` value which allows any type of message to be executed by the interchain account. This must be the only value in the `allow_messages` array. + +```json +"params": { + "host_enabled": true, + "allow_messages": ["*"] +} +``` diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/07-client.md b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/07-client.md new file mode 100644 index 0000000..fde1bc6 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/07-client.md @@ -0,0 +1,184 @@ +--- +title: Client +sidebar_label: Client +sidebar_position: 7 +slug: /apps/interchain-accounts/client +--- + + +# Client + +## CLI + +A user can query and interact with the Interchain Accounts module using the CLI. Use the `--help` flag to discover the available commands: + +```shell +simd query interchain-accounts --help +``` + +> Please not that this section does not document all the available commands, but only the ones that deserved extra documentation that was not possible to fit in the command line documentation. + +### Controller + +A user can query and interact with the controller submodule. + +#### Query + +The `query` commands allow users to query the controller submodule. + +```shell +simd query interchain-accounts controller --help +``` + +#### Transactions + +The `tx` commands allow users to interact with the controller submodule. + +```shell +simd tx interchain-accounts controller --help +``` + +#### `send-tx` + +The `send-tx` command allows users to send a transaction on the provided connection to be executed using an interchain account on the host chain. + +```shell +simd tx interchain-accounts controller send-tx [connection-id] [path/to/packet_msg.json] +``` + +Example: + +```shell +simd tx interchain-accounts controller send-tx connection-0 packet-data.json --from cosmos1.. +``` + +See below for example contents of `packet-data.json`. The CLI handler will unmarshal the following into `InterchainAccountPacketData` appropriately. + +```json +{ + "type":"TYPE_EXECUTE_TX", + "data":"CqIBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEoEBCkFjb3Ntb3MxNWNjc2hobXAwZ3N4MjlxcHFxNmc0em1sdG5udmdteXU5dWV1YWRoOXkybmM1emowc3psczVndGRkehItY29zbW9zMTBoOXN0YzV2Nm50Z2V5Z2Y1eGY5NDVuanFxNWgzMnI1M3VxdXZ3Gg0KBXN0YWtlEgQxMDAw", + "memo":"" +} +``` + +Note the `data` field is a base64 encoded byte string as per the [proto3 JSON encoding specification](https://developers.google.com/protocol-buffers/docs/proto3#json). + +A helper CLI is provided in the host submodule which can be used to generate the packet data JSON using the counterparty chain's binary. See the [`generate-packet-data` command](#generate-packet-data) for an example. + +### Host + +A user can query and interact with the host submodule. + +#### Query + +The `query` commands allow users to query the host submodule. + +```shell +simd query interchain-accounts host --help +``` + +#### Transactions + +The `tx` commands allow users to interact with the controller submodule. + +```shell +simd tx interchain-accounts host --help +``` + +##### `generate-packet-data` + +The `generate-packet-data` command allows users to generate interchain accounts packet data for input message(s). The packet data can then be used with the controller submodule's [`send-tx` command](#send-tx). + +```shell +simd tx interchain-accounts host generate-packet-data [message] +``` + +Example: + +```shell +simd tx interchain-accounts host generate-packet-data '[{ + "@type":"/cosmos.bank.v1beta1.MsgSend", + "from_address":"cosmos15ccshhmp0gsx29qpqq6g4zmltnnvgmyu9ueuadh9y2nc5zj0szls5gtddz", + "to_address":"cosmos10h9stc5v6ntgeygf5xf945njqq5h32r53uquvw", + "amount": [ + { + "denom": "stake", + "amount": "1000" + } + ] +}]' --memo memo +``` + +The command accepts a single `sdk.Msg` or a list of `sdk.Msg`s that will be encoded into the outputs `data` field. + +Example output: + +```json +{ + "type":"TYPE_EXECUTE_TX", + "data":"CqIBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEoEBCkFjb3Ntb3MxNWNjc2hobXAwZ3N4MjlxcHFxNmc0em1sdG5udmdteXU5dWV1YWRoOXkybmM1emowc3psczVndGRkehItY29zbW9zMTBoOXN0YzV2Nm50Z2V5Z2Y1eGY5NDVuanFxNWgzMnI1M3VxdXZ3Gg0KBXN0YWtlEgQxMDAw", + "memo":"memo" +} +``` + +## gRPC + +A user can query the interchain account module using gRPC endpoints. + +### Controller + +A user can query the controller submodule using gRPC endpoints. + +#### `InterchainAccount` + +The `InterchainAccount` endpoint allows users to query the controller submodule for the interchain account address for a given owner on a particular connection. + +```shell +ibc.applications.interchain_accounts.controller.v1.Query/InterchainAccount +``` + +Example: + +```shell +grpcurl -plaintext \ + -d '{"owner":"cosmos1..","connection_id":"connection-0"}' \ + localhost:9090 \ + ibc.applications.interchain_accounts.controller.v1.Query/InterchainAccount +``` + +#### `Params` + +The `Params` endpoint users to query the current controller submodule parameters. + +```shell +ibc.applications.interchain_accounts.controller.v1.Query/Params +``` + +Example: + +```shell +grpcurl -plaintext \ + localhost:9090 \ + ibc.applications.interchain_accounts.controller.v1.Query/Params +``` + +### Host + +A user can query the host submodule using gRPC endpoints. + +#### `Params` + +The `Params` endpoint users to query the current host submodule parameters. + +```shell +ibc.applications.interchain_accounts.host.v1.Query/Params +``` + +Example: + +```shell +grpcurl -plaintext \ + localhost:9090 \ + ibc.applications.interchain_accounts.host.v1.Query/Params +``` diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/08-active-channels.md b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/08-active-channels.md new file mode 100644 index 0000000..4dd09cc --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/08-active-channels.md @@ -0,0 +1,40 @@ +--- +title: Active Channels +sidebar_label: Active Channels +sidebar_position: 8 +slug: /apps/interchain-accounts/active-channels +--- + + +# Understanding Active Channels + +The Interchain Accounts module uses [ORDERED channels](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#ordering) to maintain the order of transactions when sending packets from a controller to a host chain. A limitation when using ORDERED channels is that when a packet times out the channel will be closed. + +In the case of a channel closing, a controller chain needs to be able to regain access to the interchain account registered on this channel. `Active Channels` enable this functionality. + +When an Interchain Account is registered using `MsgRegisterInterchainAccount`, a new channel is created on a particular port. During the `OnChanOpenAck` and `OnChanOpenConfirm` steps (on controller & host chain respectively) the `Active Channel` for this interchain account is stored in state. + +It is possible to create a new channel using the same controller chain portID if the previously set `Active Channel` is now in a `CLOSED` state. This channel creation can be initialized programmatically by sending a new `MsgChannelOpenInit` message like so: + +```go +msg := channeltypes.NewMsgChannelOpenInit(portID, string(versionBytes), channeltypes.ORDERED, []string{connectionID}, icatypes.HostPortID, authtypes.NewModuleAddress(icatypes.ModuleName).String()) +handler := keeper.msgRouter.Handler(msg) +res, err := handler(ctx, msg) +if err != nil { + return err +} +``` + +Alternatively, any relayer operator may initiate a new channel handshake for this interchain account once the previously set `Active Channel` is in a `CLOSED` state. This is done by initiating the channel handshake on the controller chain using the same portID associated with the interchain account in question. + +It is important to note that once a channel has been opened for a given interchain account, new channels can not be opened for this account until the currently set `Active Channel` is set to `CLOSED`. + +## Future improvements + +Future versions of the ICS-27 protocol and the Interchain Accounts module will likely use a new channel type that provides ordering of packets without the channel closing in the event of a packet timing out, thus removing the need for `Active Channels` entirely. +The following is a list of issues which will provide the infrastructure to make this possible: + +- [IBC Channel Upgrades](https://github.com/cosmos/ibc-go/issues/1599) +- [Implement ORDERED_ALLOW_TIMEOUT logic in 04-channel](https://github.com/cosmos/ibc-go/issues/1661) +- [Add ORDERED_ALLOW_TIMEOUT as supported ordering in 03-connection](https://github.com/cosmos/ibc-go/issues/1662) +- [Allow ICA channels to be opened as ORDERED_ALLOW_TIMEOUT](https://github.com/cosmos/ibc-go/issues/1663) diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/09-legacy/01-auth-modules.md b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/09-legacy/01-auth-modules.md new file mode 100644 index 0000000..777c262 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/09-legacy/01-auth-modules.md @@ -0,0 +1,274 @@ +--- +title: Authentication Modules +sidebar_label: Authentication Modules +sidebar_position: 1 +slug: /apps/interchain-accounts/legacy/auth-modules +--- + + +# Building an authentication module + +## Deprecation Notice + +**This document is deprecated and will be removed in future releases**. + +:::note Synopsis +Authentication modules play the role of the `Base Application` as described in [ICS-30 IBC Middleware](https://github.com/cosmos/ibc/tree/master/spec/app/ics-030-middleware), and enable application developers to perform custom logic when working with the Interchain Accounts controller API. +::: + +The controller submodule is used for account registration and packet sending. It executes only logic required of all controllers of interchain accounts. The type of authentication used to manage the interchain accounts remains unspecified. There may exist many different types of authentication which are desirable for different use cases. Thus the purpose of the authentication module is to wrap the controller submodule with custom authentication logic. + +In ibc-go, authentication modules are connected to the controller chain via a middleware stack. The controller submodule is implemented as [middleware](https://github.com/cosmos/ibc/tree/master/spec/app/ics-030-middleware) and the authentication module is connected to the controller submodule as the base application of the middleware stack. To implement an authentication module, the `IBCModule` interface must be fulfilled. By implementing the controller submodule as middleware, any amount of authentication modules can be created and connected to the controller submodule without writing redundant code. + +The authentication module must: + +- Authenticate interchain account owners. +- Track the associated interchain account address for an owner. +- Send packets on behalf of an owner (after authentication). + +> Please note that since ibc-go v6 the channel capability is claimed by the controller submodule and therefore it is not required for authentication modules to claim the capability in the `OnChanOpenInit` callback. When the authentication module sends packets on the channel created for the associated interchain account it can pass a `nil` capability to the legacy function `SendTx` of the controller keeper (see [section `SendTx`](./03-keeper-api.md#sendtx) below for mode information). + +## `IBCModule` implementation + +The following `IBCModule` callbacks must be implemented with appropriate custom logic: + +```go +// OnChanOpenInit implements the IBCModule interface +func (im IBCModule) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + // since ibc-go v6 the authentication module *must not* claim the channel capability on OnChanOpenInit + + // perform custom logic + + return version, nil +} + +// OnChanOpenAck implements the IBCModule interface +func (im IBCModule) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyVersion string, +) error { + // perform custom logic + + return nil +} + +// OnChanCloseConfirm implements the IBCModule interface +func (im IBCModule) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // perform custom logic + + return nil +} + +// OnAcknowledgementPacket implements the IBCModule interface +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + // perform custom logic + + return nil +} + +// OnTimeoutPacket implements the IBCModule interface. +func (im IBCModule) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + // perform custom logic + + return nil +} +``` + +The following functions must be defined to fulfill the `IBCModule` interface, but they will never be called by the controller submodule so they may error or panic. That is because in Interchain Accounts, the channel handshake is always initiated on the controller chain and packets are always sent to the host chain and never to the controller chain. + +```go +// OnChanOpenTry implements the IBCModule interface +func (im IBCModule) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + panic("UNIMPLEMENTED") +} + +// OnChanOpenConfirm implements the IBCModule interface +func (im IBCModule) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + panic("UNIMPLEMENTED") +} + +// OnChanCloseInit implements the IBCModule interface +func (im IBCModule) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + panic("UNIMPLEMENTED") +} + +// OnRecvPacket implements the IBCModule interface. A successful acknowledgement +// is returned if the packet data is successfully decoded and the receive application +// logic returns without error. +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) ibcexported.Acknowledgement { + panic("UNIMPLEMENTED") +} +``` + +## `OnAcknowledgementPacket` + +Controller chains will be able to access the acknowledgement written into the host chain state once a relayer relays the acknowledgement. +The acknowledgement bytes contain either the response of the execution of the message(s) on the host chain or an error. They will be passed to the auth module via the `OnAcknowledgementPacket` callback. Auth modules are expected to know how to decode the acknowledgement. + +If the controller chain is connected to a host chain using the host module on ibc-go, it may interpret the acknowledgement bytes as follows: + +Begin by unmarshaling the acknowledgement into `sdk.TxMsgData`: + +```go +var ack channeltypes.Acknowledgement +if err := channeltypes.SubModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { + return err +} + +txMsgData := &sdk.TxMsgData{} +if err := proto.Unmarshal(ack.GetResult(), txMsgData); err != nil { + return err +} +``` + +If the `txMsgData.Data` field is non nil, the host chain is using SDK version \<\= v0.45. +The auth module should interpret the `txMsgData.Data` as follows: + +```go +switch len(txMsgData.Data) { +case 0: + // see documentation below for SDK 0.46.x or greater +default: + for _, msgData := range txMsgData.Data { + if err := handler(msgData); err != nil { + return err + } + } +... +} +``` + +A handler will be needed to interpret what actions to perform based on the message type sent. +A router could be used, or more simply a switch statement. + +```go +func handler(msgData sdk.MsgData) error { +switch msgData.MsgType { +case sdk.MsgTypeURL(&banktypes.MsgSend{}): + msgResponse := &banktypes.MsgSendResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleBankSendMsg(msgResponse) + +case sdk.MsgTypeURL(&stakingtypes.MsgDelegate{}): + msgResponse := &stakingtypes.MsgDelegateResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleStakingDelegateMsg(msgResponse) + +case sdk.MsgTypeURL(&transfertypes.MsgTransfer{}): + msgResponse := &transfertypes.MsgTransferResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleIBCTransferMsg(msgResponse) + +default: + return +} +``` + +If the `txMsgData.Data` is empty, the host chain is using SDK version > v0.45. +The auth module should interpret the `txMsgData.Responses` as follows: + +```go +... +// switch statement from above +case 0: + for _, any := range txMsgData.MsgResponses { + if err := handleAny(any); err != nil { + return err + } + } +} +``` + +A handler will be needed to interpret what actions to perform based on the type URL of the Any. +A router could be used, or more simply a switch statement. +It may be possible to deduplicate logic between `handler` and `handleAny`. + +```go +func handleAny(any *codectypes.Any) error { +switch any.TypeURL { +case banktypes.MsgSend: + msgResponse, err := unpackBankMsgSendResponse(any) + if err != nil { + return err + } + + handleBankSendMsg(msgResponse) + +case stakingtypes.MsgDelegate: + msgResponse, err := unpackStakingDelegateResponse(any) + if err != nil { + return err + } + + handleStakingDelegateMsg(msgResponse) + + case transfertypes.MsgTransfer: + msgResponse, err := unpackIBCTransferMsgResponse(any) + if err != nil { + return err + } + + handleIBCTransferMsg(msgResponse) + +default: + return +} +``` + +## Integration into `app.go` file + +To integrate the authentication module into your chain, please follow the steps outlined in [`app.go` integration](02-integration.md#example-integration). diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/09-legacy/02-integration.md b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/09-legacy/02-integration.md new file mode 100644 index 0000000..9f14f45 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/09-legacy/02-integration.md @@ -0,0 +1,201 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /apps/interchain-accounts/legacy/integration +--- + + +# Integration + +## Deprecation Notice + +**This document is deprecated and will be removed in future releases**. + +:::note Synopsis +Learn how to integrate Interchain Accounts host and controller functionality to your chain. The following document only applies for Cosmos SDK chains. +::: + +The Interchain Accounts module contains two submodules. Each submodule has its own IBC application. The Interchain Accounts module should be registered as an `AppModule` in the same way all SDK modules are registered on a chain, but each submodule should create its own `IBCModule` as necessary. A route should be added to the IBC router for each submodule which will be used. + +Chains who wish to support ICS-27 may elect to act as a host chain, a controller chain or both. Disabling host or controller functionality may be done statically by excluding the host or controller module entirely from the `app.go` file or it may be done dynamically by taking advantage of the on-chain parameters which enable or disable the host or controller submodules. + +Interchain Account authentication modules are the base application of a middleware stack. The controller submodule is the middleware in this stack. + +![ica-pre-v6.png](./images/ica-pre-v6.png) + +> Please note that since ibc-go v6 the channel capability is claimed by the controller submodule and therefore it is not required for authentication modules to claim the capability in the `OnChanOpenInit` callback. Therefore the custom authentication module does not need a scoped keeper anymore. + +## Example integration + +```go +// app.go + +// Register the AppModule for the Interchain Accounts module and the authentication module +// Note: No `icaauth` exists, this must be substituted with an actual Interchain Accounts authentication module +ModuleBasics = module.NewBasicManager( + ... + ica.AppModuleBasic{}, + icaauth.AppModuleBasic{}, + ... +) + +... + +// Add module account permissions for the Interchain Accounts module +// Only necessary for host chain functionality +// Each Interchain Account created on the host chain is derived from the module account created +maccPerms = map[string][]string{ + ... + icatypes.ModuleName: nil, +} + +... + +// Add Interchain Accounts Keepers for each submodule used and the authentication module +// If a submodule is being statically disabled, the associated Keeper does not need to be added. +type App struct { + ... + + ICAControllerKeeper icacontrollerkeeper.Keeper + ICAHostKeeper icahostkeeper.Keeper + ICAAuthKeeper icaauthkeeper.Keeper + + ... +} + +... + +// Create store keys for each submodule Keeper and the authentication module +keys := sdk.NewKVStoreKeys( + ... + icacontrollertypes.StoreKey, + icahosttypes.StoreKey, + icaauthtypes.StoreKey, + ... +) + +... + +// Create the scoped keepers for each submodule keeper and authentication keeper +scopedICAControllerKeeper := app.CapabilityKeeper.ScopeToModule(icacontrollertypes.SubModuleName) +scopedICAHostKeeper := app.CapabilityKeeper.ScopeToModule(icahosttypes.SubModuleName) + +... + +// Create the Keeper for each submodule +app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + scopedICAControllerKeeper, app.MsgServiceRouter(), +) +app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.AccountKeeper, scopedICAHostKeeper, app.MsgServiceRouter(), +) + +// Create Interchain Accounts AppModule +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper) + +// Create your Interchain Accounts authentication module +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.ICAControllerKeeper) + +// ICA auth AppModule +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) + +// ICA auth IBC Module +icaAuthIBCModule := icaauth.NewIBCModule(app.ICAAuthKeeper) + +// Create controller IBC application stack and host IBC module as desired +icaControllerStack := icacontroller.NewIBCMiddleware(icaAuthIBCModule, app.ICAControllerKeeper) +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host and authentication routes +ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icahosttypes.SubModuleName, icaHostIBCModule). + AddRoute(icaauthtypes.ModuleName, icaControllerStack) // Note, the authentication module is routed to the top level of the middleware stack + +... + +// Register Interchain Accounts and authentication module AppModule's +app.moduleManager = module.NewManager( + ... + icaModule, + icaAuthModule, +) + +... + +// Add fee middleware to begin blocker logic +app.moduleManager.SetOrderBeginBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add fee middleware to end blocker logic +app.moduleManager.SetOrderEndBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add Interchain Accounts module InitGenesis logic +app.moduleManager.SetOrderInitGenesis( + ... + icatypes.ModuleName, + ... +) + +// initParamsKeeper init params keeper and its subspaces +func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey sdk.StoreKey) paramskeeper.Keeper { + ... + paramsKeeper.Subspace(icahosttypes.SubModuleName) + paramsKeeper.Subspace(icacontrollertypes.SubModuleName) + ... +``` + +## Using submodules exclusively + +As described above, the Interchain Accounts application module is structured to support the ability of exclusively enabling controller or host functionality. +This can be achieved by simply omitting either controller or host `Keeper` from the Interchain Accounts `NewAppModule` constructor function, and mounting only the desired submodule via the `IBCRouter`. +Alternatively, submodules can be enabled and disabled dynamically using [on-chain parameters](../06-parameters.md). + +The following snippets show basic examples of statically disabling submodules using `app.go`. + +### Disabling controller chain functionality + +```go +// Create Interchain Accounts AppModule omitting the controller keeper +icaModule := ica.NewAppModule(nil, &app.ICAHostKeeper) + +// Create host IBC Module +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host route +ibcRouter.AddRoute(icahosttypes.SubModuleName, icaHostIBCModule) +``` + +### Disabling host chain functionality + +```go +// Create Interchain Accounts AppModule omitting the host keeper +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, nil) + +// Create your Interchain Accounts authentication module, setting up the Keeper, AppModule and IBCModule appropriately +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.ICAControllerKeeper) +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) +icaAuthIBCModule := icaauth.NewIBCModule(app.ICAAuthKeeper) + +// Create controller IBC application stack +icaControllerStack := icacontroller.NewIBCMiddleware(icaAuthIBCModule, app.ICAControllerKeeper) + +// Register controller and authentication routes +ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icaauthtypes.ModuleName, icaControllerStack) // Note, the authentication module is routed to the top level of the middleware stack +``` diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/09-legacy/03-keeper-api.md b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/09-legacy/03-keeper-api.md new file mode 100644 index 0000000..91d77ea --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/09-legacy/03-keeper-api.md @@ -0,0 +1,125 @@ +--- +title: Keeper API +sidebar_label: Keeper API +sidebar_position: 3 +slug: /apps/interchain-accounts/legacy/keeper-api +--- + + +# Keeper API + +## Deprecation Notice + +**This document is deprecated and will be removed in future releases**. + +The controller submodule keeper exposes two legacy functions that allow respectively for custom authentication modules to register interchain accounts and send packets to the interchain account. + +## `RegisterInterchainAccount` + +The authentication module can begin registering interchain accounts by calling `RegisterInterchainAccount`: + +```go +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, connectionID, owner.String(), version); err != nil { + return err +} + +return nil +``` + +The `version` argument is used to support ICS-29 fee middleware for relayer incentivization of ICS-27 packets. Consumers of the `RegisterInterchainAccount` are expected to build the appropriate JSON encoded version string themselves and pass it accordingly. If an empty string is passed in the `version` argument, then the version will be initialized to a default value in the `OnChanOpenInit` callback of the controller's handler, so that channel handshake can proceed. + +The following code snippet illustrates how to construct an appropriate interchain accounts `Metadata` and encode it as a JSON bytestring: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, controllerConnectionID, owner.String(), string(appVersion)); err != nil { + return err +} +``` + +Similarly, if the application stack is configured to route through ICS-29 fee middleware and a fee enabled channel is desired, construct the appropriate ICS-29 `Metadata` type: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +feeMetadata := feetypes.Metadata{ + AppVersion: string(appVersion), + FeeVersion: feetypes.Version, +} + +feeEnabledVersion, err := feetypes.ModuleCdc.MarshalJSON(&feeMetadata) +if err != nil { + return err +} + +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, controllerConnectionID, owner.String(), string(feeEnabledVersion)); err != nil { + return err +} +``` + +## `SendTx` + +The authentication module can attempt to send a packet by calling `SendTx`: + +```go +// Authenticate owner +// perform custom logic + +// Construct controller portID based on interchain account owner address +portID, err := icatypes.NewControllerPortID(owner.String()) +if err != nil { + return err +} + +// Obtain data to be sent to the host chain. +// In this example, the owner of the interchain account would like to send a bank MsgSend to the host chain. +// The appropriate serialization function should be called. The host chain must be able to deserialize the transaction. +// If the host chain is using the ibc-go host module, `SerializeCosmosTx` should be used. +msg := &banktypes.MsgSend{FromAddress: fromAddr, ToAddress: toAddr, Amount: amt} +data, err := icatypes.SerializeCosmosTx(keeper.cdc, []proto.Message{msg}) +if err != nil { + return err +} + +// Construct packet data +packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, +} + +// Obtain timeout timestamp +// An appropriate timeout timestamp must be determined based on the usage of the interchain account. +// If the packet times out, the channel will be closed requiring a new channel to be created. +timeoutTimestamp := obtainTimeoutTimestamp() + +// Send the interchain accounts packet, returning the packet sequence +// A nil channel capability can be passed, since the controller submodule (and not the authentication module) +// claims the channel capability since ibc-go v6. +seq, err = keeper.icaControllerKeeper.SendTx(ctx, nil, portID, packetData, timeoutTimestamp) +``` + +The data within an `InterchainAccountPacketData` must be serialized using a format supported by the host chain. +If the host chain is using the ibc-go host chain submodule, `SerializeCosmosTx` should be used. If the `InterchainAccountPacketData.Data` is serialized using a format not supported by the host chain, the packet will not be successfully received. diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/09-legacy/_category_.json b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/09-legacy/_category_.json new file mode 100644 index 0000000..8339982 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/09-legacy/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Legacy", + "position": 9, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/09-legacy/images/ica-pre-v6.png b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/09-legacy/images/ica-pre-v6.png new file mode 100644 index 0000000..4529b23 Binary files /dev/null and b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/09-legacy/images/ica-pre-v6.png differ diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/_category_.json b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/_category_.json new file mode 100644 index 0000000..41e3ac2 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Interchain Accounts", + "position": 2, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/images/ica-v6.png b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/images/ica-v6.png new file mode 100644 index 0000000..abe3eba Binary files /dev/null and b/docs/versioned_docs/version-v6.3.x/02-apps/02-interchain-accounts/images/ica-v6.png differ diff --git a/docs/versioned_docs/version-v6.3.x/02-apps/_category_.json b/docs/versioned_docs/version-v6.3.x/02-apps/_category_.json new file mode 100644 index 0000000..83a389b --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/02-apps/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "IBC Application Modules", + "position": 2, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/01-overview.md b/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/01-overview.md new file mode 100644 index 0000000..0861286 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/01-overview.md @@ -0,0 +1,55 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /middleware/ics29-fee/overview +--- + + +# Overview + +:::note Synopsis +Learn about what the Fee Middleware module is, and how to build custom modules that utilize the Fee Middleware functionality +::: + +## What is the Fee Middleware module? + +IBC does not depend on relayer operators for transaction verification. However, the relayer infrastructure ensures liveness of the Interchain network — operators listen for packets sent through channels opened between chains, and perform the vital service of ferrying these packets (and proof of the transaction on the sending chain/receipt on the receiving chain) to the clients on each side of the channel. + +Though relaying is permissionless and completely decentralized and accessible, it does come with operational costs. Running full nodes to query transaction proofs and paying for transaction fees associated with IBC packets are two of the primary cost burdens which have driven the overall discussion on **a general, in-protocol incentivization mechanism for relayers**. + +Initially, a [simple proposal](https://github.com/cosmos/ibc/pull/577/files) was created to incentivize relaying on ICS20 token transfers on the destination chain. However, the proposal was specific to ICS20 token transfers and would have to be reimplemented in this format on every other IBC application module. + +After much discussion, the proposal was expanded to a [general incentivisation design](https://github.com/cosmos/ibc/tree/master/spec/app/ics-029-fee-payment) that can be adopted by any ICS application protocol as [middleware](../../01-ibc/04-middleware/01-develop.md). + +## Concepts + +ICS29 fee payments in this middleware design are built on the assumption that sender chains are the source of incentives — the chain on which packets are incentivized is the chain that distributes fees to relayer operators. However, as part of the IBC packet flow, messages have to be submitted on both sender and destination chains. This introduces the requirement of a mapping of relayer operator's addresses on both chains. + +To achieve the stated requirements, the **fee middleware module has two main groups of functionality**: + +- Registering of relayer addresses associated with each party involved in relaying the packet on the source chain. This registration process can be automated on start up of relayer infrastructure and happens only once, not every packet flow. + + This is described in the [Fee distribution section](04-fee-distribution.md). + +- Escrowing fees by any party which will be paid out to each rightful party on completion of the packet lifecycle. + + This is described in the [Fee messages section](03-msgs.md). + +We complete the introduction by giving a list of definitions of relevant terminology. + +`Forward relayer`: The relayer that submits the `MsgRecvPacket` message for a given packet (on the destination chain). + +`Reverse relayer`: The relayer that submits the `MsgAcknowledgement` message for a given packet (on the source chain). + +`Timeout relayer`: The relayer that submits the `MsgTimeout` or `MsgTimeoutOnClose` messages for a given packet (on the source chain). + +`Payee`: The account address on the source chain to be paid on completion of the packet lifecycle. The packet lifecycle on the source chain completes with the receipt of a `MsgTimeout`/`MsgTimeoutOnClose` or a `MsgAcknowledgement`. + +`Counterparty payee`: The account address to be paid on completion of the packet lifecycle on the destination chain. The package lifecycle on the destination chain completes with a successful `MsgRecvPacket`. + +`Refund address`: The address of the account paying for the incentivization of packet relaying. The account is refunded timeout fees upon successful acknowledgement. In the event of a packet timeout, both acknowledgement and receive fees are refunded. + +## Known Limitations + +The first version of fee payments middleware will only support incentivisation of new channels, however, channel upgradeability will enable incentivisation of all existing channels. diff --git a/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/02-integration.md b/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/02-integration.md new file mode 100644 index 0000000..0cac856 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/02-integration.md @@ -0,0 +1,174 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /middleware/ics29-fee/integration +--- + + +# Integration + +:::note Synopsis +Learn how to configure the Fee Middleware module with IBC applications. The following document is intended for developers building on top of the Cosmos SDK and only applies for Cosmos SDK chains. +::: + +## Pre-requisite Readings + +- [IBC middleware development](../../01-ibc/04-middleware/01-develop.md) +- [IBC middleware integration](../../01-ibc/04-middleware/02-integration.md) + +The Fee Middleware module, as the name suggests, plays the role of an IBC middleware and as such must be configured by chain developers to route and handle IBC messages correctly. +For Cosmos SDK chains this setup is done via the `app/app.go` file, where modules are constructed and configured in order to bootstrap the blockchain application. + +## Example integration of the Fee Middleware module + +```go +// app.go + +// Register the AppModule for the fee middleware module +ModuleBasics = module.NewBasicManager( + ... + ibcfee.AppModuleBasic{}, + ... +) + +... + +// Add module account permissions for the fee middleware module +maccPerms = map[string][]string{ + ... + ibcfeetypes.ModuleName: nil, +} + +... + +// Add fee middleware Keeper +type App struct { + ... + + IBCFeeKeeper ibcfeekeeper.Keeper + + ... +} + +... + +// Create store keys +keys := sdk.NewKVStoreKeys( + ... + ibcfeetypes.StoreKey, + ... +) + +... + +app.IBCFeeKeeper = ibcfeekeeper.NewKeeper( + appCodec, keys[ibcfeetypes.StoreKey], + app.IBCKeeper.ChannelKeeper, // may be replaced with IBC middleware + app.IBCKeeper.ChannelKeeper, + &app.IBCKeeper.PortKeeper, app.AccountKeeper, app.BankKeeper, +) + + +// See the section below for configuring an application stack with the fee middleware module + +... + +// Register fee middleware AppModule +app.moduleManager = module.NewManager( + ... + ibcfee.NewAppModule(app.IBCFeeKeeper), +) + +... + +// Add fee middleware to begin blocker logic +app.moduleManager.SetOrderBeginBlockers( + ... + ibcfeetypes.ModuleName, + ... +) + +// Add fee middleware to end blocker logic +app.moduleManager.SetOrderEndBlockers( + ... + ibcfeetypes.ModuleName, + ... +) + +// Add fee middleware to init genesis logic +app.moduleManager.SetOrderInitGenesis( + ... + ibcfeetypes.ModuleName, + ... +) +``` + +## Configuring an application stack with Fee Middleware + +As mentioned in [IBC middleware development](../../01-ibc/04-middleware/01-develop.md) an application stack may be composed of many or no middlewares that nest a base application. +These layers form the complete set of application logic that enable developers to build composable and flexible IBC application stacks. +For example, an application stack may be just a single base application like `transfer`, however, the same application stack composed with `29-fee` will nest the `transfer` base application +by wrapping it with the Fee Middleware module. + +### Transfer + +See below for an example of how to create an application stack using `transfer` and `29-fee`. +The following `transferStack` is configured in `app/app.go` and added to the IBC `Router`. +The in-line comments describe the execution flow of packets between the application stack and IBC core. + +```go +// Create Transfer Stack +// SendPacket, since it is originating from the application to core IBC: +// transferKeeper.SendPacket -> fee.SendPacket -> channel.SendPacket + +// RecvPacket, message that originates from core IBC and goes down to app, the flow is the other way +// channel.RecvPacket -> fee.OnRecvPacket -> transfer.OnRecvPacket + +// transfer stack contains (from top to bottom): +// - IBC Fee Middleware +// - Transfer + +// create IBC module from bottom to top of stack +var transferStack porttypes.IBCModule +transferStack = transfer.NewIBCModule(app.TransferKeeper) +transferStack = ibcfee.NewIBCMiddleware(transferStack, app.IBCFeeKeeper) + +// Add transfer stack to IBC Router +ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) +``` + +### Interchain Accounts + +See below for an example of how to create an application stack using `27-interchain-accounts` and `29-fee`. +The following `icaControllerStack` and `icaHostStack` are configured in `app/app.go` and added to the IBC `Router` with the associated authentication module. +The in-line comments describe the execution flow of packets between the application stack and IBC core. + +```go +// Create Interchain Accounts Stack +// SendPacket, since it is originating from the application to core IBC: +// icaAuthModuleKeeper.SendTx -> icaController.SendPacket -> fee.SendPacket -> channel.SendPacket + +// initialize ICA module with mock module as the authentication module on the controller side +var icaControllerStack porttypes.IBCModule +icaControllerStack = ibcmock.NewIBCModule(&mockModule, ibcmock.NewMockIBCApp("", scopedICAMockKeeper)) +app.ICAAuthModule = icaControllerStack.(ibcmock.IBCModule) +icaControllerStack = icacontroller.NewIBCMiddleware(icaControllerStack, app.ICAControllerKeeper) +icaControllerStack = ibcfee.NewIBCMiddleware(icaControllerStack, app.IBCFeeKeeper) + +// RecvPacket, message that originates from core IBC and goes down to app, the flow is: +// channel.RecvPacket -> fee.OnRecvPacket -> icaHost.OnRecvPacket + +var icaHostStack porttypes.IBCModule +icaHostStack = icahost.NewIBCModule(app.ICAHostKeeper) +icaHostStack = ibcfee.NewIBCMiddleware(icaHostStack, app.IBCFeeKeeper) + +// Add authentication module, controller and host to IBC router +ibcRouter. + // the ICA Controller middleware needs to be explicitly added to the IBC Router because the + // ICA controller module owns the port capability for ICA. The ICA authentication module + // owns the channel capability. + AddRoute(ibcmock.ModuleName+icacontrollertypes.SubModuleName, icaControllerStack) // ica with mock auth module stack route to ica (top level of middleware stack) + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icahosttypes.SubModuleName, icaHostStack). +``` diff --git a/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/03-msgs.md b/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/03-msgs.md new file mode 100644 index 0000000..8357982 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/03-msgs.md @@ -0,0 +1,95 @@ +--- +title: Fee Messages +sidebar_label: Fee Messages +sidebar_position: 3 +slug: /middleware/ics29-fee/msgs +--- + +# Fee messages + +:::note Synopsis +Learn about the different ways to pay for fees, how the fees are paid out and what happens when not enough escrowed fees are available for payout +::: + +## Escrowing fees + +The fee middleware module exposes two different ways to pay fees for relaying IBC packets: + +1. `MsgPayPacketFee`, which enables the escrowing of fees for a packet at the next sequence send and should be combined into one `MultiMsgTx` with the message that will be paid for. + + Note that the `Relayers` field has been set up to allow for an optional whitelist of relayers permitted to receive this fee, however, this feature has not yet been enabled at this time. + + ```go + type MsgPayPacketFee struct{ + // fee encapsulates the recv, ack and timeout fees associated with an IBC packet + Fee Fee + // the source port unique identifier + SourcePortId string + // the source channel unique identifier + SourceChannelId string + // account address to refund fee if necessary + Signer string + // optional list of relayers permitted to the receive packet fee + Relayers []string + } + ``` + + The `Fee` message contained in this synchronous fee payment method configures different fees which will be paid out for `MsgRecvPacket`, `MsgAcknowledgement`, and `MsgTimeout`/`MsgTimeoutOnClose`. + + ```go + type Fee struct { + RecvFee types.Coins + AckFee types.Coins + TimeoutFee types.Coins + } + ``` + + The diagram below shows the `MultiMsgTx` with the `MsgTransfer` coming from a token transfer message, along with `MsgPayPacketFee`. + + ![msgpaypacket.png](./images/msgpaypacket.png) + +2. `MsgPayPacketFeeAsync`, which enables the asynchronous escrowing of fees for a specified packet: + + Note that a packet can be 'topped up' multiple times with additional fees of any coin denomination by broadcasting multiple `MsgPayPacketFeeAsync` messages. + + ```go + type MsgPayPacketFeeAsync struct { + // unique packet identifier comprised of the channel ID, port ID and sequence + PacketId channeltypes.PacketId + // the packet fee associated with a particular IBC packet + PacketFee PacketFee + } + ``` + + where the `PacketFee` also specifies the `Fee` to be paid as well as the refund address for fees which are not paid out + + ```go + type PacketFee struct { + Fee Fee + RefundAddress string + Relayers []string + } + ``` + +The diagram below shows how multiple `MsgPayPacketFeeAsync` can be broadcasted asynchronously. Escrowing of the fee associated with a packet can be carried out by any party because ICS-29 does not dictate a particular fee payer. In fact, chains can choose to simply not expose this fee payment to end users at all and rely on a different module account or even the community pool as the source of relayer incentives. + +![paypacketfeeasync.png](./images/paypacketfeeasync.png) + +Please see our [wiki](https://github.com/cosmos/ibc-go/wiki/Fee-enabled-fungible-token-transfers) for example flows on how to use these messages to incentivise a token transfer channel using a CLI. + +## Paying out the escrowed fees + +Following diagram takes a look at the packet flow for an incentivized token transfer and investigates the several scenario's for paying out the escrowed fees. We assume that the relayers have registered their counterparty address, detailed in the [Fee distribution section](04-fee-distribution.md). + +![feeflow.png](./images/feeflow.png) + +- In the case of a successful transaction, `RecvFee` will be paid out to the designated counterparty payee address which has been registered on the receiver chain and sent back with the `MsgAcknowledgement`, `AckFee` will be paid out to the relayer address which has submitted the `MsgAcknowledgement` on the sending chain (or the registered payee in case one has been registered for the relayer address), and `TimeoutFee` will be reimbursed to the account which escrowed the fee. +- In case of a timeout transaction, `RecvFee` and `AckFee` will be reimbursed. The `TimeoutFee` will be paid to the `Timeout Relayer` (who submits the timeout message to the source chain). + +> Please note that fee payments are built on the assumption that sender chains are the source of incentives — the chain that sends the packets is the same chain where fee payments will occur -- please see the [Fee distribution section](04-fee-distribution.md) to understand the flow for registering payee and counterparty payee (fee receiving) addresses. + +## A locked fee middleware module + +The fee middleware module can become locked if the situation arises that the escrow account for the fees does not have sufficient funds to pay out the fees which have been escrowed for each packet. *This situation indicates a severe bug.* In this case, the fee module will be locked until manual intervention fixes the issue. + +> A locked fee module will simply skip fee logic and continue on to the underlying packet flow. A channel with a locked fee module will temporarily function as a fee disabled channel, and the locking of a fee module will not affect the continued flow of packets over the channel. diff --git a/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/04-fee-distribution.md b/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/04-fee-distribution.md new file mode 100644 index 0000000..48f0434 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/04-fee-distribution.md @@ -0,0 +1,114 @@ +--- +title: Fee Distribution +sidebar_label: Fee Distribution +sidebar_position: 4 +slug: /middleware/ics29-fee/fee-distribution +--- + + +# Fee distribution + +:::note Synopsis +Learn about payee registration for the distribution of packet fees. The following document is intended for relayer operators. +::: + +## Pre-requisite readings + +- [Fee Middleware](01-overview.md) + +Packet fees are divided into 3 distinct amounts in order to compensate relayer operators for packet relaying on fee enabled IBC channels. + +- `RecvFee`: The sum of all packet receive fees distributed to a payee for successful execution of `MsgRecvPacket`. +- `AckFee`: The sum of all packet acknowledgement fees distributed to a payee for successful execution of `MsgAcknowledgement`. +- `TimeoutFee`: The sum of all packet timeout fees distributed to a payee for successful execution of `MsgTimeout`. + +## Register a counterparty payee address for forward relaying + +As mentioned in [ICS29 Concepts](01-overview.md#concepts), the forward relayer describes the actor who performs the submission of `MsgRecvPacket` on the destination chain. +Fee distribution for incentivized packet relays takes place on the packet source chain. + +> Relayer operators are expected to register a counterparty payee address, in order to be compensated accordingly with `RecvFee`s upon completion of a packet lifecycle. + +The counterparty payee address registered on the destination chain is encoded into the packet acknowledgement and communicated as such to the source chain for fee distribution. +**If a counterparty payee is not registered for the forward relayer on the destination chain, the escrowed fees will be refunded upon fee distribution.** + +### Relayer operator actions? + +A transaction must be submitted **to the destination chain** including a `CounterpartyPayee` address of an account on the source chain. +The transaction must be signed by the `Relayer`. + +Note: If a module account address is used as the `CounterpartyPayee` but the module has been set as a blocked address in the `BankKeeper`, the refunding to the module account will fail. This is because many modules use invariants to compare internal tracking of module account balances against the actual balance of the account stored in the `BankKeeper`. If a token transfer to the module account occurs without going through this module and updating the account balance of the module on the `BankKeeper`, then invariants may break and unknown behaviour could occur depending on the module implementation. Therefore, if it is desirable to use a module account that is currently blocked, the module developers should be consulted to gauge to possibility of removing the module account from the blocked list. + +```go +type MsgRegisterCounterpartyPayee struct { + // unique port identifier + PortId string + // unique channel identifier + ChannelId string + // the relayer address + Relayer string + // the counterparty payee address + CounterpartyPayee string +} +``` + +> This message is expected to fail if: +> +> - `PortId` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators). +> - `ChannelId` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +> - `Relayer` is an invalid address. +> - `CounterpartyPayee` is empty. + +See below for an example CLI command: + +```bash +simd tx ibc-fee register-counterparty-payee transfer channel-0 \ +cosmos1rsp837a4kvtgp2m4uqzdge0zzu6efqgucm0qdh \ +osmo1v5y0tz01llxzf4c2afml8s3awue0ymju22wxx2 \ +--from cosmos1rsp837a4kvtgp2m4uqzdge0zzu6efqgucm0qdh +``` + +## Register an alternative payee address for reverse and timeout relaying + +As mentioned in [ICS29 Concepts](01-overview.md#concepts), the reverse relayer describes the actor who performs the submission of `MsgAcknowledgement` on the source chain. +Similarly the timeout relayer describes the actor who performs the submission of `MsgTimeout` (or `MsgTimeoutOnClose`) on the source chain. + +> Relayer operators **may choose** to register an optional payee address, in order to be compensated accordingly with `AckFee`s and `TimeoutFee`s upon completion of a packet life cycle. + +If a payee is not registered for the reverse or timeout relayer on the source chain, then fee distribution assumes the default behaviour, where fees are paid out to the relayer account which delivers `MsgAcknowledgement` or `MsgTimeout`/`MsgTimeoutOnClose`. + +### Relayer operator actions + +A transaction must be submitted **to the source chain** including a `Payee` address of an account on the source chain. +The transaction must be signed by the `Relayer`. + +Note: If a module account address is used as the `Payee` it is recommended to [turn off invariant checks](https://github.com/cosmos/ibc-go/blob/71d7480c923f4227453e8a80f51be01ae7ee845e/testing/simapp/app.go#L659) for that module. + +```go +type MsgRegisterPayee struct { + // unique port identifier + PortId string + // unique channel identifier + ChannelId string + // the relayer address + Relayer string + // the payee address + Payee string +} +``` + +> This message is expected to fail if: +> +> - `PortId` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators). +> - `ChannelId` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +> - `Relayer` is an invalid address. +> - `Payee` is an invalid address. + +See below for an example CLI command: + +```bash +simd tx ibc-fee register-payee transfer channel-0 \ +cosmos1rsp837a4kvtgp2m4uqzdge0zzu6efqgucm0qdh \ +cosmos153lf4zntqt33a4v0sm5cytrxyqn78q7kz8j8x5 \ +--from cosmos1rsp837a4kvtgp2m4uqzdge0zzu6efqgucm0qdh +``` diff --git a/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/05-events.md b/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/05-events.md new file mode 100644 index 0000000..dbda482 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/05-events.md @@ -0,0 +1,43 @@ +--- +title: Events +sidebar_label: Events +sidebar_position: 5 +slug: /middleware/ics29-fee/events +--- + + +# Events + +:::note Synopsis +An overview of all events related to ICS-29 +::: + +## `MsgPayPacketFee`, `MsgPayPacketFeeAsync` + +| Type | Attribute Key | Attribute Value | +| ----------------------- | --------------- | --------------- | +| incentivized_ibc_packet | port_id | \{portID\} | +| incentivized_ibc_packet | channel_id | \{channelID\} | +| incentivized_ibc_packet | packet_sequence | \{sequence\} | +| incentivized_ibc_packet | recv_fee | \{recvFee\} | +| incentivized_ibc_packet | ack_fee | \{ackFee\} | +| incentivized_ibc_packet | timeout_fee | \{timeoutFee\} | +| message | module | fee-ibc | + +## `RegisterPayee` + +| Type | Attribute Key | Attribute Value | +| -------------- | ------------- | --------------- | +| register_payee | relayer | \{relayer\} | +| register_payee | payee | \{payee\} | +| register_payee | channel_id | \{channelID\} | +| message | module | fee-ibc | + +## `RegisterCounterpartyPayee` + +| Type | Attribute Key | Attribute Value | +| --------------------------- | ------------------ | --------------------- | +| register_counterparty_payee | relayer | \{relayer\} | +| register_counterparty_payee | counterparty_payee | \{counterpartyPayee\} | +| register_counterparty_payee | channel_id | \{channelID\} | +| message | module | fee-ibc | diff --git a/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/06-end-users.md b/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/06-end-users.md new file mode 100644 index 0000000..7419d03 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/06-end-users.md @@ -0,0 +1,36 @@ +--- +title: End Users +sidebar_label: End Users +sidebar_position: 6 +slug: /middleware/ics29-fee/end-users +--- + + +# For end users + +:::note Synopsis +Learn how to incentivize IBC packets using the ICS29 Fee Middleware module. +::: + +## Pre-requisite readings + +- [Fee Middleware](01-overview.md) + +## Summary + +Different types of end users: + +- CLI users who want to manually incentivize IBC packets +- Client developers + +The Fee Middleware module allows end users to add a 'tip' to each IBC packet which will incentivize relayer operators to relay packets between chains. gRPC endpoints are exposed for client developers as well as a simple CLI for manually incentivizing IBC packets. + +## CLI Users + +For an in depth guide on how to use the ICS29 Fee Middleware module using the CLI please take a look at the [wiki](https://github.com/cosmos/ibc-go/wiki/Fee-enabled-fungible-token-transfers#asynchronous-incentivization-of-a-fungible-token-transfer) on the `ibc-go` repo. + +## Client developers + +Client developers can read more about the relevant ICS29 message types in the [Fee messages section](03-msgs.md). + +[CosmJS](https://github.com/cosmos/cosmjs) is a useful client library for signing and broadcasting Cosmos SDK messages. diff --git a/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/_category_.json b/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/_category_.json new file mode 100644 index 0000000..639bd0e --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Fee Middleware", + "position": 1, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/images/feeflow.png b/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/images/feeflow.png new file mode 100644 index 0000000..4e77529 Binary files /dev/null and b/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/images/feeflow.png differ diff --git a/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/images/msgpaypacket.png b/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/images/msgpaypacket.png new file mode 100644 index 0000000..a454f52 Binary files /dev/null and b/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/images/msgpaypacket.png differ diff --git a/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/images/paypacketfeeasync.png b/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/images/paypacketfeeasync.png new file mode 100644 index 0000000..73d1e04 Binary files /dev/null and b/docs/versioned_docs/version-v6.3.x/03-middleware/01-ics29-fee/images/paypacketfeeasync.png differ diff --git a/docs/versioned_docs/version-v6.3.x/03-middleware/_category_.json b/docs/versioned_docs/version-v6.3.x/03-middleware/_category_.json new file mode 100644 index 0000000..13f4809 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/03-middleware/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "IBC Middleware Modules", + "position": 3, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v6.3.x/04-migrations/01-support-denoms-with-slashes.md b/docs/versioned_docs/version-v6.3.x/04-migrations/01-support-denoms-with-slashes.md new file mode 100644 index 0000000..df8912d --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/04-migrations/01-support-denoms-with-slashes.md @@ -0,0 +1,88 @@ +--- +title: Support transfer of coins whose base denom contains slashes +sidebar_label: Support transfer of coins whose base denom contains slashes +sidebar_position: 1 +slug: /migrations/support-denoms-with-slashes +--- +# Migrating from not supporting base denoms with slashes to supporting base denoms with slashes + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +This document is necessary when chains are upgrading from a version that does not support base denoms with slashes (e.g. v3.0.0) to a version that does (e.g. v3.2.0). All versions of ibc-go smaller than v1.5.0 for the v1.x release line, v2.3.0 for the v2.x release line, and v3.1.0 for the v3.x release line do **NOT** support IBC token transfers of coins whose base denoms contain slashes. Therefore the in-place of genesis migration described in this document are required when upgrading. + +If a chain receives coins of a base denom with slashes before it upgrades to supporting it, the receive may pass however the trace information will be incorrect. + +E.g. If a base denom of `testcoin/testcoin/testcoin` is sent to a chain that does not support slashes in the base denom, the receive will be successful. However, the trace information stored on the receiving chain will be: `Trace: "transfer/{channel-id}/testcoin/testcoin", BaseDenom: "testcoin"`. + +This incorrect trace information must be corrected when the chain does upgrade to fully supporting denominations with slashes. + +To do so, chain binaries should include a migration script that will run when the chain upgrades from not supporting base denominations with slashes to supporting base denominations with slashes. + +## Chains + +### ICS20 - Transfer + +The transfer module will now support slashes in base denoms, so we must iterate over current traces to check if any of them are incorrectly formed and correct the trace information. + +### Upgrade Proposal + +```go +app.UpgradeKeeper.SetUpgradeHandler("MigrateTraces", + func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + // transfer module consensus version has been bumped to 2 + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) + +``` + +This is only necessary if there are denom traces in the store with incorrect trace information from previously received coins that had a slash in the base denom. However, it is recommended that any chain upgrading to support base denominations with slashes runs this code for safety. + +For a more detailed sample, please check out the code changes in [this pull request](https://github.com/cosmos/ibc-go/pull/1680). + +### Genesis Migration + +If the chain chooses to add support for slashes in base denoms via genesis export, then the trace information must be corrected during genesis migration. + +The migration code required may look like: + +```go +func migrateGenesisSlashedDenomsUpgrade(appState genutiltypes.AppMap, clientCtx client.Context, genDoc *tmtypes.GenesisDoc) (genutiltypes.AppMap, error) { + if appState[ibctransfertypes.ModuleName] != nil { + transferGenState := &ibctransfertypes.GenesisState{} + clientCtx.Codec.MustUnmarshalJSON(appState[ibctransfertypes.ModuleName], transferGenState) + + substituteTraces := make([]ibctransfertypes.DenomTrace, len(transferGenState.DenomTraces)) + for i, dt := range transferGenState.DenomTraces { + // replace all previous traces with the latest trace if validation passes + // note most traces will have same value + newTrace := ibctransfertypes.ParseDenomTrace(dt.GetFullDenomPath()) + + if err := newTrace.Validate(); err != nil { + substituteTraces[i] = dt + } else { + substituteTraces[i] = newTrace + } + } + + transferGenState.DenomTraces = substituteTraces + + // delete old genesis state + delete(appState, ibctransfertypes.ModuleName) + + // set new ibc transfer genesis state + appState[ibctransfertypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(transferGenState) + } + + return appState, nil +} +``` + +For a more detailed sample, please check out the code changes in [this pull request](https://github.com/cosmos/ibc-go/pull/1528). diff --git a/docs/versioned_docs/version-v6.3.x/04-migrations/02-sdk-to-v1.md b/docs/versioned_docs/version-v6.3.x/04-migrations/02-sdk-to-v1.md new file mode 100644 index 0000000..28b41ab --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/04-migrations/02-sdk-to-v1.md @@ -0,0 +1,195 @@ +--- +title: SDK v0.43 to IBC-Go v1 +sidebar_label: SDK v0.43 to IBC-Go v1 +sidebar_position: 2 +slug: /migrations/sdk-to-v1 +--- +# Migrating to ibc-go + +This file contains information on how to migrate from the IBC module contained in the SDK 0.41.x and 0.42.x lines to the IBC module in the ibc-go repository based on the 0.44 SDK version. + +## Import Changes + +The most obvious changes is import name changes. We need to change: + +- applications -> apps +- cosmos-sdk/x/ibc -> ibc-go + +On my GNU/Linux based machine I used the following commands, executed in order: + +```bash +grep -RiIl 'cosmos-sdk\/x\/ibc\/applications' | xargs sed -i 's/cosmos-sdk\/x\/ibc\/applications/ibc-go\/modules\/apps/g' +``` + +```bash +grep -RiIl 'cosmos-sdk\/x\/ibc' | xargs sed -i 's/cosmos-sdk\/x\/ibc/ibc-go\/modules/g' +``` + +ref: [explanation of the above commands](https://www.internalpointers.com/post/linux-find-and-replace-text-multiple-files) + +Executing these commands out of order will cause issues. + +Feel free to use your own method for modifying import names. + +NOTE: Updating to the `v0.44.0` SDK release and then running `go mod tidy` will cause a downgrade to `v0.42.0` in order to support the old IBC import paths. +Update the import paths before running `go mod tidy`. + +## Chain Upgrades + +Chains may choose to upgrade via an upgrade proposal or genesis upgrades. Both in-place store migrations and genesis migrations are supported. + +**WARNING**: Please read at least the quick guide for [IBC client upgrades](../01-ibc/05-upgrades/00-intro.md) before upgrading your chain. It is highly recommended you do not change the chain-ID during an upgrade, otherwise you must follow the IBC client upgrade instructions. + +Both in-place store migrations and genesis migrations will: + +- migrate the solo machine client state from v1 to v2 protobuf definitions +- prune all solo machine consensus states +- prune all expired tendermint consensus states + +Chains must set a new connection parameter during either in place store migrations or genesis migration. The new parameter, max expected block time, is used to enforce packet processing delays on the receiving end of an IBC packet flow. Checkout the [docs](https://github.com/cosmos/ibc-go/blob/release/v1.0.x/docs/ibc/proto-docs.md#params-2) for more information. + +### In-Place Store Migrations + +The new chain binary will need to run migrations in the upgrade handler. The fromVM (previous module version) for the IBC module should be 1. This will allow migrations to be run for IBC updating the version from 1 to 2. + +Ex: + +```go +app.UpgradeKeeper.SetUpgradeHandler("my-upgrade-proposal", + func(ctx sdk.Context, _ upgradetypes.Plan, _ module.VersionMap) (module.VersionMap, error) { + // set max expected block time parameter. Replace the default with your expected value + // https://github.com/cosmos/ibc-go/blob/release/v1.0.x/docs/ibc/proto-docs.md#params-2 + app.IBCKeeper.ConnectionKeeper.SetParams(ctx, ibcconnectiontypes.DefaultParams()) + + fromVM := map[string]uint64{ + ... // other modules + "ibc": 1, + ... + } + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) + +``` + +### Genesis Migrations + +To perform genesis migrations, the following code must be added to your existing migration code. + +```go +// add imports as necessary +import ( + ibcv100 "github.com/cosmos/ibc-go/modules/core/legacy/v100" + ibchost "github.com/cosmos/ibc-go/modules/core/24-host" +) + +... + +// add in migrate cmd function +// expectedTimePerBlock is a new connection parameter +// https://github.com/cosmos/ibc-go/blob/release/v1.0.x/docs/ibc/proto-docs.md#params-2 +newGenState, err = ibcv100.MigrateGenesis(newGenState, clientCtx, *genDoc, expectedTimePerBlock) +if err != nil { + return err +} +``` + +**NOTE:** The genesis chain-id, time and height MUST be updated before migrating IBC, otherwise the tendermint consensus state will not be pruned. + +## IBC Keeper Changes + +The IBC Keeper now takes in the Upgrade Keeper. Please add the chains' Upgrade Keeper after the Staking Keeper: + +```diff + // Create IBC Keeper + app.IBCKeeper = ibckeeper.NewKeeper( +- appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, scopedIBCKeeper, ++ appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, + ) + +``` + +## Proposals + +### UpdateClientProposal + +The `UpdateClient` has been modified to take in two client-identifiers and one initial height. Please see the [documentation](../01-ibc/06-proposals.md) for more information. + +### UpgradeProposal + +A new IBC proposal type has been added, `UpgradeProposal`. This handles an IBC (breaking) Upgrade. +The previous `UpgradedClientState` field in an Upgrade `Plan` has been deprecated in favor of this new proposal type. + +### Proposal Handler Registration + +The `ClientUpdateProposalHandler` has been renamed to `ClientProposalHandler`. +It handles both `UpdateClientProposal`s and `UpgradeProposal`s. + +Add this import: + +```diff ++ ibcclienttypes "github.com/cosmos/ibc-go/modules/core/02-client/types" +``` + +Please ensure the governance module adds the correct route: + +```diff +- AddRoute(ibchost.RouterKey, ibcclient.NewClientUpdateProposalHandler(app.IBCKeeper.ClientKeeper)) ++ AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)) +``` + +NOTE: Simapp registration was incorrect in the 0.41.x releases. The `UpdateClient` proposal handler should be registered with the router key belonging to `ibc-go/core/02-client/types` +as shown in the diffs above. + +### Proposal CLI Registration + +Please ensure both proposal type CLI commands are registered on the governance module by adding the following arguments to `gov.NewAppModuleBasic()`: + +Add the following import: + +```diff ++ ibcclientclient "github.com/cosmos/ibc-go/modules/core/02-client/client" +``` + +Register the cli commands: + +```diff + gov.NewAppModuleBasic( + paramsclient.ProposalHandler, distrclient.ProposalHandler, upgradeclient.ProposalHandler, upgradeclient.CancelProposalHandler, ++ ibcclientclient.UpdateClientProposalHandler, ibcclientclient.UpgradeProposalHandler, + ), +``` + +REST routes are not supported for these proposals. + +## Proto file changes + +The gRPC querier service endpoints have changed slightly. The previous files used `v1beta1` gRPC route, this has been updated to `v1`. + +The solo machine has replaced the FrozenSequence uint64 field with a IsFrozen boolean field. The package has been bumped from `v1` to `v2` + +## IBC callback changes + +### OnRecvPacket + +Application developers need to update their `OnRecvPacket` callback logic. + +The `OnRecvPacket` callback has been modified to only return the acknowledgement. The acknowledgement returned must implement the `Acknowledgement` interface. The acknowledgement should indicate if it represents a successful processing of a packet by returning true on `Success()` and false in all other cases. A return value of false on `Success()` will result in all state changes which occurred in the callback being discarded. More information can be found in the [documentation](../01-ibc/03-apps/02-ibcmodule.md#receiving-packets). + +The `OnRecvPacket`, `OnAcknowledgementPacket`, and `OnTimeoutPacket` callbacks are now passed the `sdk.AccAddress` of the relayer who relayed the IBC packet. Applications may use or ignore this information. + +## IBC Event changes + +The `packet_data` attribute has been deprecated in favor of `packet_data_hex`, in order to provide standardized encoding/decoding of packet data in events. While the `packet_data` event still exists, all relayers and IBC Event consumers are strongly encouraged to switch over to using `packet_data_hex` as soon as possible. + +The `packet_ack` attribute has also been deprecated in favor of `packet_ack_hex` for the same reason stated above. All relayers and IBC Event consumers are strongly encouraged to switch over to using `packet_ack_hex` as soon as possible. + +The `consensus_height` attribute has been removed in the Misbehaviour event emitted. IBC clients no longer have a frozen height and misbehaviour does not necessarily have an associated height. + +## Relevant SDK changes + +- (codec) [\#9226](https://github.com/cosmos/cosmos-sdk/pull/9226) Rename codec interfaces and methods, to follow a general Go interfaces: + - `codec.Marshaler` → `codec.Codec` (this defines objects which serialize other objects) + - `codec.BinaryMarshaler` → `codec.BinaryCodec` + - `codec.JSONMarshaler` → `codec.JSONCodec` + - Removed `BinaryBare` suffix from `BinaryCodec` methods (`MarshalBinaryBare`, `UnmarshalBinaryBare`, ...) + - Removed `Binary` infix from `BinaryCodec` methods (`MarshalBinaryLengthPrefixed`, `UnmarshalBinaryLengthPrefixed`, ...) diff --git a/docs/versioned_docs/version-v6.3.x/04-migrations/03-v1-to-v2.md b/docs/versioned_docs/version-v6.3.x/04-migrations/03-v1-to-v2.md new file mode 100644 index 0000000..39fa738 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/04-migrations/03-v1-to-v2.md @@ -0,0 +1,60 @@ +--- +title: IBC-Go v1 to v2 +sidebar_label: IBC-Go v1 to v2 +sidebar_position: 3 +slug: /migrations/v1-to-v2 +--- +# Migrating from ibc-go v1 to v2 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go -> github.com/cosmos/ibc-go/v2 +``` + +## Chains + +- No relevant changes were made in this release. + +## IBC Apps + +A new function has been added to the app module interface: + +```go +// NegotiateAppVersion performs application version negotiation given the provided channel ordering, connectionID, portID, counterparty and proposed version. + // An error is returned if version negotiation cannot be performed. For example, an application module implementing this interface + // may decide to return an error in the event of the proposed version being incompatible with it's own + NegotiateAppVersion( + ctx sdk.Context, + order channeltypes.Order, + connectionID string, + portID string, + counterparty channeltypes.Counterparty, + proposedVersion string, + ) (version string, err error) +} +``` + +This function should perform application version negotiation and return the negotiated version. If the version cannot be negotiated, an error should be returned. This function is only used on the client side. + +### sdk.Result removed + +sdk.Result has been removed as a return value in the application callbacks. Previously it was being discarded by core IBC and was thus unused. + +## Relayers + +A new gRPC has been added to 05-port, `AppVersion`. It returns the negotiated app version. This function should be used for the `ChanOpenTry` channel handshake step to decide upon the application version which should be set in the channel. + +## IBC Light Clients + +- No relevant changes were made in this release. diff --git a/docs/versioned_docs/version-v6.3.x/04-migrations/04-v2-to-v3.md b/docs/versioned_docs/version-v6.3.x/04-migrations/04-v2-to-v3.md new file mode 100644 index 0000000..d171c01 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/04-migrations/04-v2-to-v3.md @@ -0,0 +1,186 @@ +--- +title: IBC-Go v2 to v3 +sidebar_label: IBC-Go v2 to v3 +sidebar_position: 4 +slug: /migrations/v2-to-v3 +--- +# Migrating from ibc-go v2 to v3 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go/v2 -> github.com/cosmos/ibc-go/v3 +``` + +No genesis or in-place migrations are required when upgrading from v1 or v2 of ibc-go. + +## Chains + +### ICS20 + +The `transferkeeper.NewKeeper(...)` now takes in an ICS4Wrapper. +The ICS4Wrapper should be the IBC Channel Keeper unless ICS 20 is being connected to a middleware application. + +### ICS27 + +ICS27 Interchain Accounts has been added as a supported IBC application of ibc-go. +Please see the [ICS27 documentation](../02-apps/02-interchain-accounts/01-overview.md) for more information. + +### Upgrade Proposal + +If the chain will adopt ICS27, it must set the appropriate params during the execution of the upgrade handler in `app.go`: + +```go +app.UpgradeKeeper.SetUpgradeHandler("v3", + func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + // set the ICS27 consensus version so InitGenesis is not run + fromVM[icatypes.ModuleName] = icamodule.ConsensusVersion() + + // create ICS27 Controller submodule params + controllerParams := icacontrollertypes.Params{ + ControllerEnabled: true, + } + + // create ICS27 Host submodule params + hostParams := icahosttypes.Params{ + HostEnabled: true, + AllowMessages: []string{"/cosmos.bank.v1beta1.MsgSend", ...}, + } + + // initialize ICS27 module + icamodule.InitModule(ctx, controllerParams, hostParams) + + ... + + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) + +``` + +The host and controller submodule params only need to be set if the chain integrates those submodules. +For example, if a chain chooses not to integrate a controller submodule, it may pass empty params into `InitModule`. + +#### Add `StoreUpgrades` for ICS27 module + +For ICS27 it is also necessary to [manually add store upgrades](https://docs.cosmos.network/main/learn/advanced/upgrade#add-storeupgrades-for-new-modules) for the new ICS27 module and then configure the store loader to apply those upgrades in `app.go`: + +```go +if upgradeInfo.Name == "v3" && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { + storeUpgrades := store.StoreUpgrades{ + Added: []string{icacontrollertypes.StoreKey, icahosttypes.StoreKey}, + } + + app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) +} +``` + +This ensures that the new module's stores are added to the multistore before the migrations begin. +The host and controller submodule keys only need to be added if the chain integrates those submodules. +For example, if a chain chooses not to integrate a controller submodule, it does not need to add the controller key to the `Added` field. + +### Genesis migrations + +If the chain will adopt ICS27 and chooses to upgrade via a genesis export, then the ICS27 parameters must be set during genesis migration. + +The migration code required may look like: + +```go + controllerGenesisState := icatypes.DefaultControllerGenesis() + // overwrite parameters as desired + controllerGenesisState.Params = icacontrollertypes.Params{ + ControllerEnabled: true, + } + + hostGenesisState := icatypes.DefaultHostGenesis() + // overwrite parameters as desired + hostGenesisState.Params = icahosttypes.Params{ + HostEnabled: true, + AllowMessages: []string{"/cosmos.bank.v1beta1.MsgSend", ...}, + } + + icaGenesisState := icatypes.NewGenesisState(controllerGenesisState, hostGenesisState) + + // set new ics27 genesis state + appState[icatypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(icaGenesisState) +``` + +### Ante decorator + +The field of type `channelkeeper.Keeper` in the `AnteDecorator` structure has been replaced with a field of type `*keeper.Keeper`: + +```diff +type AnteDecorator struct { +- k channelkeeper.Keeper ++ k *keeper.Keeper +} + +- func NewAnteDecorator(k channelkeeper.Keeper) AnteDecorator { ++ func NewAnteDecorator(k *keeper.Keeper) AnteDecorator { + return AnteDecorator{k: k} +} +``` + +## IBC Apps + +### `OnChanOpenTry` must return negotiated application version + +The `OnChanOpenTry` application callback has been modified. +The return signature now includes the application version. +IBC applications must perform application version negotiation in `OnChanOpenTry` using the counterparty version. +The negotiated application version then must be returned in `OnChanOpenTry` to core IBC. +Core IBC will set this version in the TRYOPEN channel. + +### `OnChanOpenAck` will take additional `counterpartyChannelID` argument + +The `OnChanOpenAck` application callback has been modified. +The arguments now include the counterparty channel id. + +### `NegotiateAppVersion` removed from `IBCModule` interface + +Previously this logic was handled by the `NegotiateAppVersion` function. +Relayers would query this function before calling `ChanOpenTry`. +Applications would then need to verify that the passed in version was correct. +Now applications will perform this version negotiation during the channel handshake, thus removing the need for `NegotiateAppVersion`. + +### Channel state will not be set before application callback + +The channel handshake logic has been reorganized within core IBC. +Channel state will not be set in state after the application callback is performed. +Applications must rely only on the passed in channel parameters instead of querying the channel keeper for channel state. + +### IBC application callbacks moved from `AppModule` to `IBCModule` + +Previously, IBC module callbacks were apart of the `AppModule` type. +The recommended approach is to create an `IBCModule` type and move the IBC module callbacks from `AppModule` to `IBCModule` in a separate file `ibc_module.go`. + +The mock module go API has been broken in this release by applying the above format. +The IBC module callbacks have been moved from the mock modules `AppModule` into a new type `IBCModule`. + +As apart of this release, the mock module now supports middleware testing. Please see the [README](https://github.com/cosmos/ibc-go/blob/v6.1.0/testing/README.md#middleware-testing) for more information. + +Please review the [mock](https://github.com/cosmos/ibc-go/blob/v6.1.0/testing/mock/ibc_module.go) and [transfer](https://github.com/cosmos/ibc-go/blob/v6.1.0/modules/apps/transfer/ibc_module.go) modules as examples. Additionally, [simapp](https://github.com/cosmos/ibc-go/blob/v6.1.0/testing/simapp/app.go) provides an example of how `IBCModule` types should now be added to the IBC router in favour of `AppModule`. + +### IBC testing package + +`TestChain`s are now created with chainID's beginning from an index of 1. Any calls to `GetChainID(0)` will now fail. Please increment all calls to `GetChainID` by 1. + +## Relayers + +`AppVersion` gRPC has been removed. +The `version` string in `MsgChanOpenTry` has been deprecated and will be ignored by core IBC. +Relayers no longer need to determine the version to use on the `ChanOpenTry` step. +IBC applications will determine the correct version using the counterparty version. + +## IBC Light Clients + +The `GetProofSpecs` function has been removed from the `ClientState` interface. This function was previously unused by core IBC. Light clients which don't use this function may remove it. diff --git a/docs/versioned_docs/version-v6.3.x/04-migrations/05-v3-to-v4.md b/docs/versioned_docs/version-v6.3.x/04-migrations/05-v3-to-v4.md new file mode 100644 index 0000000..6196d50 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/04-migrations/05-v3-to-v4.md @@ -0,0 +1,160 @@ +--- +title: IBC-Go v3 to v4 +sidebar_label: IBC-Go v3 to v4 +sidebar_position: 5 +slug: /migrations/v3-to-v4 +--- +# Migrating from ibc-go v3 to v4 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go/v3 -> github.com/cosmos/ibc-go/v4 +``` + +No genesis or in-place migrations required when upgrading from v1 or v2 of ibc-go. + +## Chains + +### ICS27 - Interchain Accounts + +The controller submodule implements now the 05-port `Middleware` interface instead of the 05-port `IBCModule` interface. Chains that integrate the controller submodule, need to create it with the `NewIBCMiddleware` constructor function. For example: + +```diff +- icacontroller.NewIBCModule(app.ICAControllerKeeper, icaAuthIBCModule) ++ icacontroller.NewIBCMiddleware(icaAuthIBCModule, app.ICAControllerKeeper) +``` + +where `icaAuthIBCModule` is the Interchain Accounts authentication IBC Module. + +### ICS29 - Fee Middleware + +The Fee Middleware module, as the name suggests, plays the role of an IBC middleware and as such must be configured by chain developers to route and handle IBC messages correctly. + +Please read the Fee Middleware [integration documentation](../03-middleware/01-ics29-fee/02-integration.md) for an in depth guide on how to configure the module correctly in order to incentivize IBC packets. + +Take a look at the following diff for an [example setup](https://github.com/cosmos/ibc-go/pull/1432/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08aL366) of how to incentivize ics27 channels. + +### Migration to fix support for base denoms with slashes + +As part of [v1.5.0](https://github.com/cosmos/ibc-go/releases/tag/v1.5.0), [v2.3.0](https://github.com/cosmos/ibc-go/releases/tag/v2.3.0) and [v3.1.0](https://github.com/cosmos/ibc-go/releases/tag/v3.1.0) some [migration handler code sample was documented](./01-support-denoms-with-slashes.md#upgrade-proposal) that needs to run in order to correct the trace information of coins transferred using ICS20 whose base denom contains slashes. + +Based on feedback from the community we add now an improved solution to run the same migration that does not require copying a large piece of code over from the migration document, but instead requires only adding a one-line upgrade handler. + +If the chain will migrate to supporting base denoms with slashes, it must set the appropriate params during the execution of the upgrade handler in `app.go`: + +```go +app.UpgradeKeeper.SetUpgradeHandler("MigrateTraces", + func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + // transfer module consensus version has been bumped to 2 + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) + +``` + +If a chain receives coins of a base denom with slashes before it upgrades to supporting it, the receive may pass however the trace information will be incorrect. + +E.g. If a base denom of `testcoin/testcoin/testcoin` is sent to a chain that does not support slashes in the base denom, the receive will be successful. However, the trace information stored on the receiving chain will be: `Trace: "transfer/{channel-id}/testcoin/testcoin", BaseDenom: "testcoin"`. + +This incorrect trace information must be corrected when the chain does upgrade to fully supporting denominations with slashes. + +## IBC Apps + +### ICS03 - Connection + +Crossing hellos have been removed from 03-connection handshake negotiation. +`PreviousConnectionId` in `MsgConnectionOpenTry` has been deprecated and is no longer used by core IBC. + +`NewMsgConnectionOpenTry` no longer takes in the `PreviousConnectionId` as crossing hellos are no longer supported. A non-empty `PreviousConnectionId` will fail basic validation for this message. + +### ICS04 - Channel + +The `WriteAcknowledgement` API now takes the `exported.Acknowledgement` type instead of passing in the acknowledgement byte array directly. +This is an API breaking change and as such IBC application developers will have to update any calls to `WriteAcknowledgement`. + +The `OnChanOpenInit` application callback has been modified. +The return signature now includes the application version as detailed in the latest IBC [spec changes](https://github.com/cosmos/ibc/pull/629). + +The `NewErrorAcknowledgement` method signature has changed. +It now accepts an `error` rather than a `string`. This was done in order to prevent accidental state changes. +All error acknowledgements now contain a deterministic ABCI code and error message. It is the responsibility of the application developer to emit error details in events. + +Crossing hellos have been removed from 04-channel handshake negotiation. +IBC Applications no longer need to account from already claimed capabilities in the `OnChanOpenTry` callback. The capability provided by core IBC must be able to be claimed with error. +`PreviousChannelId` in `MsgChannelOpenTry` has been deprecated and is no longer used by core IBC. + +`NewMsgChannelOpenTry` no longer takes in the `PreviousChannelId` as crossing hellos are no longer supported. A non-empty `PreviousChannelId` will fail basic validation for this message. + +### ICS27 - Interchain Accounts + +The `RegisterInterchainAccount` API has been modified to include an additional `version` argument. This change has been made in order to support ICS29 fee middleware, for relayer incentivization of ICS27 packets. +Consumers of the `RegisterInterchainAccount` are now expected to build the appropriate JSON encoded version string themselves and pass it accordingly. +This should be constructed within the interchain accounts authentication module which leverages the APIs exposed via the interchain accounts `controllerKeeper`. If an empty string is passed in the `version` argument, then the version will be initialized to a default value in the `OnChanOpenInit` callback of the controller's handler, so that channel handshake can proceed. + +The following code snippet illustrates how to construct an appropriate interchain accounts `Metadata` and encode it as a JSON bytestring: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +if err := k.icaControllerKeeper.RegisterInterchainAccount(ctx, msg.ConnectionId, msg.Owner, string(appVersion)); err != nil { + return err +} +``` + +Similarly, if the application stack is configured to route through ICS29 fee middleware and a fee enabled channel is desired, construct the appropriate ICS29 `Metadata` type: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +feeMetadata := feetypes.Metadata{ + AppVersion: string(appVersion), + FeeVersion: feetypes.Version, +} + +feeEnabledVersion, err := feetypes.ModuleCdc.MarshalJSON(&feeMetadata) +if err != nil { + return err +} + +if err := k.icaControllerKeeper.RegisterInterchainAccount(ctx, msg.ConnectionId, msg.Owner, string(feeEnabledVersion)); err != nil { + return err +} +``` + +## Relayers + +When using the `DenomTrace` gRPC, the full IBC denomination with the `ibc/` prefix may now be passed in. + +Crossing hellos are no longer supported by core IBC for 03-connection and 04-channel. The handshake should be completed in the logical 4-step process (INIT, TRY, ACK, CONFIRM). diff --git a/docs/versioned_docs/version-v6.3.x/04-migrations/06-v4-to-v5.md b/docs/versioned_docs/version-v6.3.x/04-migrations/06-v4-to-v5.md new file mode 100644 index 0000000..c7ae67f --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/04-migrations/06-v4-to-v5.md @@ -0,0 +1,441 @@ +--- +title: IBC-Go v4 to v5 +sidebar_label: IBC-Go v4 to v5 +sidebar_position: 6 +slug: /migrations/v4-to-v5 +--- + +# Migrating from v4 to v5 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- [Chains](#chains) +- [IBC Apps](#ibc-apps) +- [Relayers](#relayers) +- [IBC Light Clients](#relayers) + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go/v4 -> github.com/cosmos/ibc-go/v5 +``` + +## Chains + +### Ante decorator + +The `AnteDecorator` type in `core/ante` has been renamed to `RedundantRelayDecorator` (and the corresponding constructor function to `NewRedundantRelayDecorator`). Therefore in the function that creates the instance of the `sdk.AnteHandler` type (e.g. `NewAnteHandler`) the change would be like this: + +```diff +func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { + // parameter validation + + anteDecorators := []sdk.AnteDecorator{ + // other ante decorators +- ibcante.NewAnteDecorator(opts.IBCkeeper), ++ ibcante.NewRedundantRelayDecorator(options.IBCKeeper), + } + + return sdk.ChainAnteDecorators(anteDecorators...), nil +} +``` + +The `AnteDecorator` was actually renamed twice, but in [this PR](https://github.com/cosmos/ibc-go/pull/1820) you can see the changes made for the final rename. + +## IBC Apps + +### Core + +The `key` parameter of the `NewKeeper` function in `modules/core/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + stakingKeeper clienttypes.StakingKeeper, + upgradeKeeper clienttypes.UpgradeKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +) *Keeper +``` + +The `RegisterRESTRoutes` function in `modules/core` has been removed. + +### ICS03 - Connection + +The `key` parameter of the `NewKeeper` function in `modules/core/03-connection/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + ck types.ClientKeeper +) Keeper +``` + +### ICS04 - Channel + +The function `NewPacketId` in `modules/core/04-channel/types` has been renamed to `NewPacketID`: + +```diff +- func NewPacketId( ++ func NewPacketID( + portID, + channelID string, + seq uint64 +) PacketId +``` + +The `key` parameter of the `NewKeeper` function in `modules/core/04-channel/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + clientKeeper types.ClientKeeper, + connectionKeeper types.ConnectionKeeper, + portKeeper types.PortKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +) Keeper +``` + +### ICS20 - Transfer + +The `key` parameter of the `NewKeeper` function in `modules/apps/transfer/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + ics4Wrapper types.ICS4Wrapper, + channelKeeper types.ChannelKeeper, + portKeeper types.PortKeeper, + authKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +) Keeper +``` + +The `amount` parameter of function `GetTransferCoin` in `modules/apps/transfer/types` is now of type `math.Int` (`"cosmossdk.io/math"`): + +```diff +func GetTransferCoin( + portID, channelID, baseDenom string, +- amount sdk.Int ++ amount math.Int +) sdk.Coin +``` + +The `RegisterRESTRoutes` function in `modules/apps/transfer` has been removed. + +### ICS27 - Interchain Accounts + +The `key` and `msgRouter` parameters of the `NewKeeper` functions in + +- `modules/apps/27-interchain-accounts/controller/keeper` +- and `modules/apps/27-interchain-accounts/host/keeper` + +have changed type. The `key` parameter is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`), and the `msgRouter` parameter is now of type `*icatypes.MessageRouter` (where `icatypes` is an import alias for `"github.com/cosmos/ibc-go/v5/modules/apps/27-interchain-accounts/types"`): + +```diff +// NewKeeper creates a new interchain accounts controller Keeper instance +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + ics4Wrapper icatypes.ICS4Wrapper, + channelKeeper icatypes.ChannelKeeper, + portKeeper icatypes.PortKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +- msgRouter *baseapp.MsgServiceRouter, ++ msgRouter *icatypes.MessageRouter, +) Keeper +``` + +```diff +// NewKeeper creates a new interchain accounts host Keeper instance +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + channelKeeper icatypes.ChannelKeeper, + portKeeper icatypes.PortKeeper, + accountKeeper icatypes.AccountKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +- msgRouter *baseapp.MsgServiceRouter, ++ msgRouter *icatypes.MessageRouter, +) Keeper +``` + +The new `MessageRouter` interface is defined as: + +```go +type MessageRouter interface { + Handler(msg sdk.Msg) baseapp.MsgServiceHandler +} +``` + +The `RegisterRESTRoutes` function in `modules/apps/27-interchain-accounts` has been removed. + +An additional parameter, `ics4Wrapper` has been added to the `host` submodule `NewKeeper` function in `modules/apps/27-interchain-accounts/host/keeper`. +This allows the `host` submodule to correctly unwrap the channel version for channel reopening handshakes in the `OnChanOpenTry` callback. + +```diff +func NewKeeper( + cdc codec.BinaryCodec, + key storetypes.StoreKey, + paramSpace paramtypes.Subspace, ++ ics4Wrapper icatypes.ICS4Wrapper, + channelKeeper icatypes.ChannelKeeper, + portKeeper icatypes.PortKeeper, + accountKeeper icatypes.AccountKeeper, + scopedKeeper icatypes.ScopedKeeper, + msgRouter icatypes.MessageRouter, +) Keeper +``` + +#### Cosmos SDK message handler responses in packet acknowledgement + +The construction of the transaction response of a message execution on the host chain has changed. The `Data` field in the `sdk.TxMsgData` has been deprecated and since Cosmos SDK 0.46 the `MsgResponses` field contains the message handler responses packed into `Any`s. + +For chains on Cosmos SDK 0.45 and below, the message response was constructed like this: + +```go +txMsgData := &sdk.TxMsgData{ + Data: make([]*sdk.MsgData, len(msgs)), +} + +for i, msg := range msgs { + // message validation + + msgResponse, err := k.executeMsg(cacheCtx, msg) + // return if err != nil + + txMsgData.Data[i] = &sdk.MsgData{ + MsgType: sdk.MsgTypeURL(msg), + Data: msgResponse, + } +} + +// emit events + +txResponse, err := proto.Marshal(txMsgData) +// return if err != nil + +return txResponse, nil +``` + +And for chains on Cosmos SDK 0.46 and above, it is now done like this: + +```go +txMsgData := &sdk.TxMsgData{ + MsgResponses: make([]*codectypes.Any, len(msgs)), +} + +for i, msg := range msgs { + // message validation + + any, err := k.executeMsg(cacheCtx, msg) + // return if err != nil + + txMsgData.MsgResponses[i] = any +} + +// emit events + +txResponse, err := proto.Marshal(txMsgData) +// return if err != nil + +return txResponse, nil +``` + +When handling the acknowledgement in the `OnAcknowledgementPacket` callback of a custom ICA controller module, then depending on whether `txMsgData.Data` is empty or not, the logic to handle the message handler response will be different. **Only controller chains on Cosmos SDK 0.46 or above will be able to write the logic needed to handle the response from a host chain on Cosmos SDK 0.46 or above.** + +```go +var ack channeltypes.Acknowledgement +if err := channeltypes.SubModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { + return err +} + +var txMsgData sdk.TxMsgData +if err := proto.Unmarshal(ack.GetResult(), txMsgData); err != nil { + return err +} + +switch len(txMsgData.Data) { +case 0: // for SDK 0.46 and above + for _, msgResponse := range txMsgData.MsgResponses { + // unmarshall msgResponse and execute logic based on the response + } + return nil +default: // for SDK 0.45 and below + for _, msgData := range txMsgData.Data { + // unmarshall msgData and execute logic based on the response + } +} +``` + +See [ADR-03](/architecture/adr-003-ics27-acknowledgement#next-major-version-format) for more information or the [corresponding documentation about authentication modules](../02-apps/02-interchain-accounts/03-auth-modules.md#onacknowledgementpacket). + +### ICS29 - Fee Middleware + +The `key` parameter of the `NewKeeper` function in `modules/apps/29-fee` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + ics4Wrapper types.ICS4Wrapper, + channelKeeper types.ChannelKeeper, + portKeeper types.PortKeeper, + authKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, +) Keeper +``` + +The `RegisterRESTRoutes` function in `modules/apps/29-fee` has been removed. + +### IBC testing package + +The `MockIBCApp` type has been renamed to `IBCApp` (and the corresponding constructor function to `NewIBCApp`). This has resulted therefore in: + +- The `IBCApp` field of the `*IBCModule` in `testing/mock` to change its type as well to `*IBCApp`: + +```diff +type IBCModule struct { + appModule *AppModule +- IBCApp *MockIBCApp // base application of an IBC middleware stack ++ IBCApp *IBCApp // base application of an IBC middleware stack +} +``` + +- The `app` parameter to `*NewIBCModule` in `testing/mock` to change its type as well to `*IBCApp`: + +```diff +func NewIBCModule( + appModule *AppModule, +- app *MockIBCApp ++ app *IBCApp +) IBCModule +``` + +The `MockEmptyAcknowledgement` type has been renamed to `EmptyAcknowledgement` (and the corresponding constructor function to `NewEmptyAcknowledgement`). + +The `TestingApp` interface in `testing` has gone through some modifications: + +- The return type of the function `GetStakingKeeper` is not the concrete type `stakingkeeper.Keeper` anymore (where `stakingkeeper` is an import alias for `"github.com/cosmos/cosmos-sdk/x/staking/keeper"`), but it has been changed to the interface `ibctestingtypes.StakingKeeper` (where `ibctestingtypes` is an import alias for `""github.com/cosmos/ibc-go/v5/testing/types"`). See this [PR](https://github.com/cosmos/ibc-go/pull/2028) for more details. The `StakingKeeper` interface is defined as: + +```go +type StakingKeeper interface { + GetHistoricalInfo(ctx sdk.Context, height int64) (stakingtypes.HistoricalInfo, bool) +} +``` + +- The return type of the function `LastCommitID` has changed to `storetypes.CommitID` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`). + +See the following `git diff` for more details: + +```diff +type TestingApp interface { + abci.Application + + // ibc-go additions + GetBaseApp() *baseapp.BaseApp +- GetStakingKeeper() stakingkeeper.Keeper ++ GetStakingKeeper() ibctestingtypes.StakingKeeper + GetIBCKeeper() *keeper.Keeper + GetScopedIBCKeeper() capabilitykeeper.ScopedKeeper + GetTxConfig() client.TxConfig + + // Implemented by SimApp + AppCodec() codec.Codec + + // Implemented by BaseApp +- LastCommitID() sdk.CommitID ++ LastCommitID() storetypes.CommitID + LastBlockHeight() int64 +} +``` + +The `powerReduction` parameter of the function `SetupWithGenesisValSet` in `testing` is now of type `math.Int` (`"cosmossdk.io/math"`): + +```diff +func SetupWithGenesisValSet( + t *testing.T, + valSet *tmtypes.ValidatorSet, + genAccs []authtypes.GenesisAccount, + chainID string, +- powerReduction sdk.Int, ++ powerReduction math.Int, + balances ...banktypes.Balance +) TestingApp +``` + +The `accAmt` parameter of the functions + +- `AddTestAddrsFromPubKeys` , +- `AddTestAddrs` +- and `AddTestAddrsIncremental` + +in `testing/simapp` are now of type `math.Int` (`"cosmossdk.io/math"`): + +```diff +func AddTestAddrsFromPubKeys( + app *SimApp, + ctx sdk.Context, + pubKeys []cryptotypes.PubKey, +- accAmt sdk.Int, ++ accAmt math.Int +) +func addTestAddrs( + app *SimApp, + ctx sdk.Context, + accNum int, +- accAmt sdk.Int, ++ accAmt math.Int, + strategy GenerateAccountStrategy +) []sdk.AccAddress +func AddTestAddrsIncremental( + app *SimApp, + ctx sdk.Context, + accNum int, +- accAmt sdk.Int, ++ accAmt math.Int +) []sdk.AccAddress +``` + +The `RegisterRESTRoutes` function in `testing/mock` has been removed. + +## Relayers + +- No relevant changes were made in this release. + +## IBC Light Clients + +### ICS02 - Client + +The `key` parameter of the `NewKeeper` function in `modules/core/02-client/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + sk types.StakingKeeper, + uk types.UpgradeKeeper +) Keeper +``` diff --git a/docs/versioned_docs/version-v6.3.x/04-migrations/07-v5-to-v6.md b/docs/versioned_docs/version-v6.3.x/04-migrations/07-v5-to-v6.md new file mode 100644 index 0000000..f87a0e4 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/04-migrations/07-v5-to-v6.md @@ -0,0 +1,299 @@ +--- +title: IBC-Go v5 to v6 +sidebar_label: IBC-Go v5 to v6 +sidebar_position: 7 +slug: /migrations/v5-to-v6 +--- + +# Migrating from ibc-go v5 to v6 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +## Chains + +The `ibc-go/v6` release introduces a new set of migrations for `27-interchain-accounts`. Ownership of ICS27 channel capabilities is transferred from ICS27 authentication modules and will now reside with the ICS27 controller submodule moving forward. + +For chains which contain a custom authentication module using the ICS27 controller submodule this requires a migration function to be included in the chain upgrade handler. A subsequent migration handler is run automatically, asserting the ownership of ICS27 channel capabilities has been transferred successfully. + +This migration is not required for chains which *do not* contain a custom authentication module using the ICS27 controller submodule. + +This migration facilitates the addition of the ICS27 controller submodule `MsgServer` which provides a standardised approach to integrating existing forms of authentication such as `x/gov` and `x/group` provided by the Cosmos SDK. + +For more information please refer to [ADR 009](/architecture/adr-009-v6-ics27-msgserver). + +### Upgrade proposal + +Please refer to [PR #2383](https://github.com/cosmos/ibc-go/pull/2383) for integrating the ICS27 channel capability migration logic or follow the steps outlined below: + +1. Add the upgrade migration logic to chain distribution. This may be, for example, maintained under a package `app/upgrades/v6`. + +```go +package v6 + +import ( + "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + + v6 "github.com/cosmos/ibc-go/v6/modules/apps/27-interchain-accounts/controller/migrations/v6" +) + +const ( + UpgradeName = "v6" +) + +func CreateUpgradeHandler( + mm *module.Manager, + configurator module.Configurator, + cdc codec.BinaryCodec, + capabilityStoreKey *storetypes.KVStoreKey, + capabilityKeeper *capabilitykeeper.Keeper, + moduleName string, +) upgradetypes.UpgradeHandler { + return func(ctx sdk.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + if err := v6.MigrateICS27ChannelCapability(ctx, cdc, capabilityStoreKey, capabilityKeeper, moduleName); err != nil { + return nil, err + } + + return mm.RunMigrations(ctx, configurator, vm) + } +} +``` + +2. Set the upgrade handler in `app.go`. The `moduleName` parameter refers to the authentication module's `ScopedKeeper` name. This is the name provided upon instantiation in `app.go` via the [`x/capability` keeper `ScopeToModule(moduleName string)`](https://github.com/cosmos/cosmos-sdk/blob/v0.46.1/x/capability/keeper/keeper.go#L70) method. [See here for an example in `simapp`](https://github.com/cosmos/ibc-go/blob/v5.0.0/testing/simapp/app.go#L304). + +```go +app.UpgradeKeeper.SetUpgradeHandler( + v6.UpgradeName, + v6.CreateUpgradeHandler( + app.mm, + app.configurator, + app.appCodec, + app.keys[capabilitytypes.ModuleName], + app.CapabilityKeeper, + >>>> moduleName <<<<, + ), +) +``` + +## IBC Apps + +### ICS27 - Interchain Accounts + +#### Controller APIs + +In previous releases of ibc-go, chain developers integrating the ICS27 interchain accounts controller functionality were expected to create a custom `Base Application` referred to as an authentication module, see the section [Building an authentication module](../02-apps/02-interchain-accounts/03-auth-modules.md) from the documentation. + +The `Base Application` was intended to be composed with the ICS27 controller submodule `Keeper` and facilitate many forms of message authentication depending on a chain's particular use case. + +Prior to ibc-go v6 the controller submodule exposed only these two functions (to which we will refer as the legacy APIs): + +- [`RegisterInterchainAccount`](https://github.com/cosmos/ibc-go/blob/v5.0.0/modules/apps/27-interchain-accounts/controller/keeper/account.go#L19) +- [`SendTx`](https://github.com/cosmos/ibc-go/blob/v5.0.0/modules/apps/27-interchain-accounts/controller/keeper/relay.go#L18) + +However, these functions have now been deprecated in favour of the new controller submodule `MsgServer` and will be removed in later releases. + +Both APIs remain functional and maintain backwards compatibility in ibc-go v6, however consumers of these APIs are now recommended to follow the message passing paradigm outlined in Cosmos SDK [ADR 031](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-031-msg-service.md) and [ADR 033](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-033-protobuf-inter-module-comm.md). This is facilitated by the Cosmos SDK [`MsgServiceRouter`](https://github.com/cosmos/cosmos-sdk/blob/main/baseapp/msg_service_router.go#L17) and chain developers creating custom application logic can now omit the ICS27 controller submodule `Keeper` from their module and instead depend on message routing. + +Depending on the use case, developers of custom authentication modules face one of three scenarios: + +![auth-module-decision-tree.png](./images/auth-module-decision-tree.png) + +**My authentication module needs to access IBC packet callbacks** + +Application developers that wish to consume IBC packet callbacks and react upon packet acknowledgements **must** continue using the controller submodule's legacy APIs. The authentication modules will not need a `ScopedKeeper` anymore, though, because the channel capability will be claimed by the controller submodule. For example, given an Interchain Accounts authentication module keeper `ICAAuthKeeper`, the authentication module's `ScopedKeeper` (`scopedICAAuthKeeper`) is not needed anymore and can be removed for the argument list of the keeper constructor function, as shown here: + +```diff +app.ICAAuthKeeper = icaauthkeeper.NewKeeper( + appCodec, + keys[icaauthtypes.StoreKey], + app.ICAControllerKeeper, +- scopedICAAuthKeeper, +) +``` + +Please note that the authentication module's `ScopedKeeper` name is still needed as part of the channel capability migration described in section [Upgrade proposal](#upgrade-proposal) above. Therefore the authentication module's `ScopedKeeper` cannot be completely removed from the chain code until the migration has run. + +In the future, the use of the legacy APIs for accessing packet callbacks will be replaced by IBC Actor Callbacks (see [ADR 008](https://github.com/cosmos/ibc-go/pull/1976) for more details) and it will also be possible to access them with the `MsgServiceRouter`. + +**My authentication module does not need access to IBC packet callbacks** + +The authentication module can migrate from using the legacy APIs and it can be composed instead with the `MsgServiceRouter`, so that the authentication module is able to pass messages to the controller submodule's `MsgServer` to register interchain accounts and send packets to the interchain account. For example, given an Interchain Accounts authentication module keeper `ICAAuthKeeper`, the ICS27 controller submodule keeper (`ICAControllerKeeper`) and authentication module scoped keeper (`scopedICAAuthKeeper`) are not needed anymore and can be replaced with the `MsgServiceRouter`, as shown here: + +```diff +app.ICAAuthKeeper = icaauthkeeper.NewKeeper( + appCodec, + keys[icaauthtypes.StoreKey], +- app.ICAControllerKeeper, +- scopedICAAuthKeeper, ++ app.MsgServiceRouter(), +) +``` + +In your authentication module you can route messages to the controller submodule's `MsgServer` instead of using the legacy APIs. For example, for registering an interchain account: + +```diff +- if err := keeper.icaControllerKeeper.RegisterInterchainAccount( +- ctx, +- connectionID, +- owner.String(), +- version, +- ); err != nil { +- return err +- } ++ msg := controllertypes.NewMsgRegisterInterchainAccount( ++ connectionID, ++ owner.String(), ++ version, ++ ) ++ handler := keeper.msgRouter.Handler(msg) ++ res, err := handler(ctx, msg) ++ if err != nil { ++ return err ++ } +``` + +where `controllertypes` is an import alias for `"github.com/cosmos/ibc-go/v6/modules/apps/27-interchain-accounts/controller/types"`. + +In addition, in this use case the authentication module does not need to implement the `IBCModule` interface anymore. + +**I do not need a custom authentication module anymore** + +If your authentication module does not have any extra functionality compared to the default authentication module added in ibc-go v6 (the `MsgServer`), or if you can use a generic authentication module, such as the `x/auth`, `x/gov` or `x/group` modules from the Cosmos SDK (v0.46 and later), then you can remove your authentication module completely and use instead the gRPC endpoints of the `MsgServer` or the CLI added in ibc-go v6. + +Please remember that the authentication module's `ScopedKeeper` name is still needed as part of the channel capability migration described in section [Upgrade proposal](#upgrade-proposal) above. + +#### Host params + +The ICS27 host submodule default params have been updated to include the `AllowAllHostMsgs` wildcard `*`. +This enables execution of any `sdk.Msg` type for ICS27 registered on the host chain `InterfaceRegistry`. + +```diff +// AllowAllHostMsgs holds the string key that allows all message types on interchain accounts host module +const AllowAllHostMsgs = "*" + +... + +// DefaultParams is the default parameter configuration for the host submodule +func DefaultParams() Params { +- return NewParams(DefaultHostEnabled, nil) ++ return NewParams(DefaultHostEnabled, []string{AllowAllHostMsgs}) +} +``` + +#### API breaking changes + +`SerializeCosmosTx` takes in a `[]proto.Message` instead of `[]sdk.Message`. This allows for the serialization of proto messages without requiring the fulfillment of the `sdk.Msg` interface. + +The `27-interchain-accounts` genesis types have been moved to their own package: `modules/apps/27-interchain-acccounts/genesis/types`. +This change facilitates the addition of the ICS27 controller submodule `MsgServer` and avoids cyclic imports. This should have minimal disruption to chain developers integrating `27-interchain-accounts`. + +The ICS27 host submodule `NewKeeper` function in `modules/apps/27-interchain-acccounts/host/keeper` now includes an additional parameter of type `ICS4Wrapper`. +This provides the host submodule with the ability to correctly unwrap channel versions in the event of a channel reopening handshake. + +```diff +func NewKeeper( + cdc codec.BinaryCodec, key storetypes.StoreKey, paramSpace paramtypes.Subspace, +- channelKeeper icatypes.ChannelKeeper, portKeeper icatypes.PortKeeper, ++ ics4Wrapper icatypes.ICS4Wrapper, channelKeeper icatypes.ChannelKeeper, portKeeper icatypes.PortKeeper, + accountKeeper icatypes.AccountKeeper, scopedKeeper icatypes.ScopedKeeper, msgRouter icatypes.MessageRouter, +) Keeper +``` + +### ICS29 - `NewKeeper` API change + +The `NewKeeper` function of ICS29 has been updated to remove the `paramSpace` parameter as it was unused. + +```diff +func NewKeeper( +- cdc codec.BinaryCodec, key storetypes.StoreKey, paramSpace paramtypes.Subspace, +- ics4Wrapper types.ICS4Wrapper, channelKeeper types.ChannelKeeper, portKeeper types.PortKeeper, authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, ++ cdc codec.BinaryCodec, key storetypes.StoreKey, ++ ics4Wrapper types.ICS4Wrapper, channelKeeper types.ChannelKeeper, ++ portKeeper types.PortKeeper, authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, +) Keeper { +``` + +### ICS20 - `SendTransfer` is no longer exported + +The `SendTransfer` function of ICS20 has been removed. IBC transfers should now be initiated with `MsgTransfer` and routed to the ICS20 `MsgServer`. + +See below for example: + +```go +if handler := msgRouter.Handler(msgTransfer); handler != nil { + if err := msgTransfer.ValidateBasic(); err != nil { + return nil, err + } + + res, err := handler(ctx, msgTransfer) + if err != nil { + return nil, err + } +} +``` + +### ICS04 - `SendPacket` API change + +The `SendPacket` API has been simplified: + +```diff +// SendPacket is called by a module in order to send an IBC packet on a channel +func (k Keeper) SendPacket( + ctx sdk.Context, + channelCap *capabilitytypes.Capability, +- packet exported.PacketI, +-) error { ++ sourcePort string, ++ sourceChannel string, ++ timeoutHeight clienttypes.Height, ++ timeoutTimestamp uint64, ++ data []byte, ++) (uint64, error) { +``` + +Callers no longer need to pass in a pre-constructed packet. +The destination port/channel identifiers and the packet sequence will be determined by core IBC. +`SendPacket` will return the packet sequence. + +### IBC testing package + +The `SendPacket` API has been simplified: + +```diff +// SendPacket is called by a module in order to send an IBC packet on a channel +func (k Keeper) SendPacket( + ctx sdk.Context, + channelCap *capabilitytypes.Capability, +- packet exported.PacketI, +-) error { ++ sourcePort string, ++ sourceChannel string, ++ timeoutHeight clienttypes.Height, ++ timeoutTimestamp uint64, ++ data []byte, ++) (uint64, error) { +``` + +Callers no longer need to pass in a pre-constructed packet. `SendPacket` will return the packet sequence. + +## Relayers + +- No relevant changes were made in this release. + +## IBC Light Clients + +- No relevant changes were made in this release. diff --git a/docs/versioned_docs/version-v6.3.x/04-migrations/_category_.json b/docs/versioned_docs/version-v6.3.x/04-migrations/_category_.json new file mode 100644 index 0000000..617def6 --- /dev/null +++ b/docs/versioned_docs/version-v6.3.x/04-migrations/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Migrations", + "position": 4, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v6.3.x/04-migrations/images/auth-module-decision-tree.png b/docs/versioned_docs/version-v6.3.x/04-migrations/images/auth-module-decision-tree.png new file mode 100644 index 0000000..1122ddb Binary files /dev/null and b/docs/versioned_docs/version-v6.3.x/04-migrations/images/auth-module-decision-tree.png differ diff --git a/docs/versioned_docs/version-v7.8.x/00-intro.md b/docs/versioned_docs/version-v7.8.x/00-intro.md new file mode 100644 index 0000000..d572f63 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/00-intro.md @@ -0,0 +1,16 @@ +--- +slug: / +sidebar_position: 0 +--- + +# IBC-Go Documentation + +Welcome to the IBC-Go documentation! + +The Inter-Blockchain Communication protocol (IBC) is an end-to-end, connection-oriented, stateful protocol for reliable, ordered, and authenticated communication between heterogeneous blockchains arranged in an unknown and dynamic topology. + +IBC is a protocol that allows blockchains to talk to each other. + +The protocol realizes this interoperability by specifying a set of data structures, abstractions, and semantics that can be implemented by any distributed ledger that satisfies a small set of requirements. + +IBC can be used to build a wide range of cross-chain applications that include token transfers, atomic swaps, multi-chain smart contracts (with or without mutually comprehensible VMs), and data and code sharding of various kinds. diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/01-overview.md b/docs/versioned_docs/version-v7.8.x/01-ibc/01-overview.md new file mode 100644 index 0000000..2eb39d8 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/01-overview.md @@ -0,0 +1,297 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /ibc/overview +--- + + +# Overview + +:::note Synopsis +Learn about IBC, its components, and IBC use cases. +::: + +## What is the Inter-Blockchain Communication Protocol (IBC)? + +This document serves as a guide for developers who want to write their own Inter-Blockchain +Communication protocol (IBC) applications for custom use cases. + +> IBC applications must be written as self-contained modules. + +Due to the modular design of the IBC protocol, IBC +application developers do not need to be concerned with the low-level details of clients, +connections, and proof verification. + +This brief explanation of the lower levels of the +stack gives application developers a broad understanding of the IBC +protocol. Abstraction layer details for channels and ports are most relevant for application developers and describe how to define custom packets and `IBCModule` callbacks. + +The requirements to have your module interact over IBC are: + +- Bind to a port or ports. +- Define your packet data. +- Use the default acknowledgment struct provided by core IBC or optionally define a custom acknowledgment struct. +- Standardize an encoding of the packet data. +- Implement the `IBCModule` interface. + +Read on for a detailed explanation of how to write a self-contained IBC application module. + +## Components Overview + +### [Clients](https://github.com/cosmos/ibc-go/blob/main/modules/core/02-client) + +IBC clients are on-chain light clients. Each light client is identified by a unique client-id. +IBC clients track the consensus states of other blockchains, along with the proof spec necessary to +properly verify proofs against the client's consensus state. A client can be associated with any number +of connections to the counterparty chain. The client identifier is auto generated using the client type +and the global client counter appended in the format: `{client-type}-{N}`. + +A `ClientState` should contain chain specific and light client specific information necessary for verifying updates +and upgrades to the IBC client. The `ClientState` may contain information such as chain-id, latest height, proof specs, +unbonding periods or the status of the light client. The `ClientState` should not contain information that +is specific to a given block at a certain height, this is the function of the `ConsensusState`. Each `ConsensusState` +should be associated with a unique block and should be referenced using a height. IBC clients are given a +client identifier prefixed store to store their associated client state and consensus states along with +any metadata associated with the consensus states. Consensus states are stored using their associated height. + +The supported IBC clients are: + +- [Solo Machine light client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/06-solomachine): Devices such as phones, browsers, or laptops. +- [Tendermint light client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/07-tendermint): The default for Cosmos SDK-based chains. +- [Localhost (loopback) client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/09-localhost): Useful for +testing, simulation, and relaying packets to modules on the same application. + +### IBC Client Heights + +IBC Client Heights are represented by the struct: + +```go +type Height struct { + RevisionNumber uint64 + RevisionHeight uint64 +} +``` + +The `RevisionNumber` represents the revision of the chain that the height is representing. +A revision typically represents a continuous, monotonically increasing range of block-heights. +The `RevisionHeight` represents the height of the chain within the given revision. + +On any reset of the `RevisionHeight`—for example, when hard-forking a Tendermint chain— +the `RevisionNumber` will get incremented. This allows IBC clients to distinguish between a +block-height `n` of a previous revision of the chain (at revision `p`) and block-height `n` of the current +revision of the chain (at revision `e`). + +`Height`s that share the same revision number can be compared by simply comparing their respective `RevisionHeight`s. +`Height`s that do not share the same revision number will only be compared using their respective `RevisionNumber`s. +Thus a height `h` with revision number `e+1` will always be greater than a height `g` with revision number `e`, +**REGARDLESS** of the difference in revision heights. + +Ex: + +```go +Height{RevisionNumber: 3, RevisionHeight: 0} > Height{RevisionNumber: 2, RevisionHeight: 100000000000} +``` + +When a Tendermint chain is running a particular revision, relayers can simply submit headers and proofs with the revision number +given by the chain's `chainID`, and the revision height given by the Tendermint block height. When a chain updates using a hard-fork +and resets its block-height, it is responsible for updating its `chainID` to increment the revision number. +IBC Tendermint clients then verifies the revision number against their `chainID` and treat the `RevisionHeight` as the Tendermint block-height. + +Tendermint chains wishing to use revisions to maintain persistent IBC connections even across height-resetting upgrades must format their `chainID`s +in the following manner: `{chainID}-{revision_number}`. On any height-resetting upgrade, the `chainID` **MUST** be updated with a higher revision number +than the previous value. + +Ex: + +- Before upgrade `chainID`: `gaiamainnet-3` +- After upgrade `chainID`: `gaiamainnet-4` + +Clients that do not require revisions, such as the solo-machine client, simply hardcode `0` into the revision number whenever they +need to return an IBC height when implementing IBC interfaces and use the `RevisionHeight` exclusively. + +Other client-types can implement their own logic to verify the IBC heights that relayers provide in their `Update`, `Misbehavior`, and +`Verify` functions respectively. + +The IBC interfaces expect an `ibcexported.Height` interface, however all clients must use the concrete implementation provided in +`02-client/types` and reproduced above. + +### [Connections](https://github.com/cosmos/ibc-go/blob/main/modules/core/03-connection) + +Connections encapsulate two `ConnectionEnd` objects on two separate blockchains. Each +`ConnectionEnd` is associated with a client of the other blockchain (for example, the counterparty blockchain). +The connection handshake is responsible for verifying that the light clients on each chain are +correct for their respective counterparties. Connections, once established, are responsible for +facilitating all cross-chain verifications of IBC state. A connection can be associated with any +number of channels. + +### [Proofs](https://github.com/cosmos/ibc-go/blob/main/modules/core/23-commitment) and [Paths](https://github.com/cosmos/ibc-go/blob/main/modules/core/24-host) + +In IBC, blockchains do not directly pass messages to each other over the network. Instead, to +communicate, a blockchain commits some state to a specifically defined path that is reserved for a +specific message type and a specific counterparty. For example, for storing a specific connectionEnd as part +of a handshake or a packet intended to be relayed to a module on the counterparty chain. A relayer +process monitors for updates to these paths and relays messages by submitting the data stored +under the path and a proof to the counterparty chain. + +Proofs are passed from core IBC to light-clients as bytes. It is up to light client implementation to interpret these bytes appropriately. + +- The paths that all IBC implementations must use for committing IBC messages is defined in +[ICS-24 Host State Machine Requirements](https://github.com/cosmos/ibc/tree/master/spec/core/ics-024-host-requirements). +- The proof format that all implementations must be able to produce and verify is defined in [ICS-23 Proofs](https://github.com/cosmos/ics23) implementation. + +### Capabilities + +IBC is intended to work in execution environments where modules do not necessarily trust each +other. Thus, IBC must authenticate module actions on ports and channels so that only modules with the +appropriate permissions can use them. + +This module authentication is accomplished using a [dynamic +capability store](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-003-dynamic-capability-store.md). Upon binding to a port or +creating a channel for a module, IBC returns a dynamic capability that the module must claim in +order to use that port or channel. The dynamic capability module prevents other modules from using that port or channel since +they do not own the appropriate capability. + +While this background information is useful, IBC modules do not need to interact at all with +these lower-level abstractions. The relevant abstraction layer for IBC application developers is +that of channels and ports. IBC applications must be written as self-contained **modules**. + +A module on one blockchain can communicate with other modules on other blockchains by sending, +receiving, and acknowledging packets through channels that are uniquely identified by the +`(channelID, portID)` tuple. + +A useful analogy is to consider IBC modules as internet applications on +a computer. A channel can then be conceptualized as an IP connection, with the IBC portID being +analogous to an IP port and the IBC channelID being analogous to an IP address. Thus, a single +instance of an IBC module can communicate on the same port with any number of other modules and +IBC correctly routes all packets to the relevant module using the (channelID, portID tuple). An +IBC module can also communicate with another IBC module over multiple ports, with each +`(portID<->portID)` packet stream being sent on a different unique channel. + +### [Ports](https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port) + +An IBC module can bind to any number of ports. Each port must be identified by a unique `portID`. +Since IBC is designed to be secure with mutually distrusted modules operating on the same ledger, +binding a port returns a dynamic object capability. In order to take action on a particular port +(for example, an open channel with its portID), a module must provide the dynamic object capability to the IBC +handler. This requirement prevents a malicious module from opening channels with ports it does not own. Thus, +IBC modules are responsible for claiming the capability that is returned on `BindPort`. + +### [Channels](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +An IBC channel can be established between two IBC ports. Currently, a port is exclusively owned by a +single module. IBC packets are sent over channels. Just as IP packets contain the destination IP +address and IP port, and the source IP address and source IP port, IBC packets contain +the destination portID and channelID, and the source portID and channelID. This packet structure enables IBC to +correctly route packets to the destination module while allowing modules receiving packets to +know the sender module. + +A channel can be `ORDERED`, where packets from a sending module must be processed by the +receiving module in the order they were sent. Or a channel can be `UNORDERED`, where packets +from a sending module are processed in the order they arrive (might be in a different order than they were sent). + +Modules can choose which channels they wish to communicate over with, thus IBC expects modules to +implement callbacks that are called during the channel handshake. These callbacks can do custom +channel initialization logic. If any callback returns an error, the channel handshake fails. Thus, by +returning errors on callbacks, modules can programmatically reject and accept channels. + +The channel handshake is a 4-step handshake. Briefly, if a given chain A wants to open a channel with +chain B using an already established connection: + +1. chain A sends a `ChanOpenInit` message to signal a channel initialization attempt with chain B. +2. chain B sends a `ChanOpenTry` message to try opening the channel on chain A. +3. chain A sends a `ChanOpenAck` message to mark its channel end status as open. +4. chain B sends a `ChanOpenConfirm` message to mark its channel end status as open. + +If all handshake steps are successful, the channel is opened on both sides. At each step in the handshake, the module +associated with the `ChannelEnd` executes its callback. So +on `ChanOpenInit`, the module on chain A executes its callback `OnChanOpenInit`. + +The channel identifier is auto derived in the format: `channel-{N}` where N is the next sequence to be used. + +Just as ports came with dynamic capabilities, channel initialization returns a dynamic capability +that the module **must** claim so that they can pass in a capability to authenticate channel actions +like sending packets. The channel capability is passed into the callback on the first parts of the +handshake; either `OnChanOpenInit` on the initializing chain or `OnChanOpenTry` on the other chain. + +#### Closing channels + +Closing a channel occurs in 2 handshake steps as defined in [ICS 04](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics). + +`ChanCloseInit` closes a channel on the executing chain if the channel exists, it is not +already closed and the connection it exists upon is OPEN. Channels can only be closed by a +calling module or in the case of a packet timeout on an ORDERED channel. + +`ChanCloseConfirm` is a response to a counterparty channel executing `ChanCloseInit`. The channel +on the executing chain closes if the channel exists, the channel is not already closed, +the connection the channel exists upon is OPEN and the executing chain successfully verifies +that the counterparty channel has been closed. + +### [Packets](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +Modules communicate with each other by sending packets over IBC channels. All +IBC packets contain the destination `portID` and `channelID` along with the source `portID` and +`channelID`. This packet structure allows modules to know the sender module of a given packet. IBC packets +contain a sequence to optionally enforce ordering. + +IBC packets also contain a `TimeoutHeight` and a `TimeoutTimestamp` that determine the deadline before the receiving module must process a packet. + +Modules send custom application data to each other inside the `Data []byte` field of the IBC packet. +Thus, packet data is opaque to IBC handlers. It is incumbent on a sender module to encode +their application-specific packet information into the `Data` field of packets. The receiver +module must decode that `Data` back to the original application data. + +### [Receipts and Timeouts](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +Since IBC works over a distributed network and relies on potentially faulty relayers to relay messages between ledgers, +IBC must handle the case where a packet does not get sent to its destination in a timely manner or at all. Packets must +specify a non-zero value for timeout height (`TimeoutHeight`) or timeout timestamp (`TimeoutTimestamp` ) after which a packet can no longer be successfully received on the destination chain. + +- The `timeoutHeight` indicates a consensus height on the destination chain after which the packet is no longer be processed, and instead counts as having timed-out. +- The `timeoutTimestamp` indicates a timestamp on the destination chain after which the packet is no longer be processed, and instead counts as having timed-out. + +If the timeout passes without the packet being successfully received, the packet can no longer be +received on the destination chain. The sending module can timeout the packet and take appropriate actions. + +If the timeout is reached, then a proof of packet timeout can be submitted to the original chain. The original chain can then perform +application-specific logic to timeout the packet, perhaps by rolling back the packet send changes (refunding senders any locked funds, etc.). + +- In ORDERED channels, a timeout of a single packet in the channel causes the channel to close. + + - If packet sequence `n` times out, then a packet at sequence `k > n` cannot be received without violating the contract of ORDERED channels that packets are processed in the order that they are sent. + - Since ORDERED channels enforce this invariant, a proof that sequence `n` has not been received on the destination chain by the specified timeout of packet `n` is sufficient to timeout packet `n` and close the channel. + +- In UNORDERED channels, the application-specific timeout logic for that packet is applied and the channel is not closed. + + - Packets can be received in any order. + + - IBC writes a packet receipt for each sequence receives in the UNORDERED channel. This receipt does not contain information; it is simply a marker intended to signify that the UNORDERED channel has received a packet at the specified sequence. + + - To timeout a packet on an UNORDERED channel, a proof is required that a packet receipt **does not exist** for the packet's sequence by the specified timeout. + +For this reason, most modules should use UNORDERED channels as they require fewer liveness guarantees to function effectively for users of that channel. + +### [Acknowledgments](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +Modules can also choose to write application-specific acknowledgments upon processing a packet. Acknowledgments can be done: + +- Synchronously on `OnRecvPacket` if the module processes packets as soon as they are received from IBC module. +- Asynchronously if module processes packets at some later point after receiving the packet. + +This acknowledgment data is opaque to IBC much like the packet `Data` and is treated by IBC as a simple byte string `[]byte`. Receiver modules must encode their acknowledgment so that the sender module can decode it correctly. The encoding must be negotiated between the two parties during version negotiation in the channel handshake. + +The acknowledgment can encode whether the packet processing succeeded or failed, along with additional information that allows the sender module to take appropriate action. + +After the acknowledgment has been written by the receiving chain, a relayer relays the acknowledgment back to the original sender module. + +The original sender module then executes application-specific acknowledgment logic using the contents of the acknowledgment. + +- After an acknowledgement fails, packet-send changes can be rolled back (for example, refunding senders in ICS20). + +- After an acknowledgment is received successfully on the original sender on the chain, the corresponding packet commitment is deleted since it is no longer needed. + +## Further Readings and Specs + +If you want to learn more about IBC, check the following specifications: + +- [IBC specification overview](https://github.com/cosmos/ibc/blob/master/README.md) diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/02-integration.md b/docs/versioned_docs/version-v7.8.x/01-ibc/02-integration.md new file mode 100644 index 0000000..0e72f9e --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/02-integration.md @@ -0,0 +1,235 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /ibc/integration +--- + + +# Integration + +:::note Synopsis +Learn how to integrate IBC to your application and send data packets to other chains. +::: + +This document outlines the required steps to integrate and configure the [IBC +module](https://github.com/cosmos/ibc-go/tree/main/modules/core) to your Cosmos SDK application and +send fungible token transfers to other chains. + +## Integrating the IBC module + +Integrating the IBC module to your SDK-based application is straightforward. The general changes can be summarized in the following steps: + +- Add required modules to the `module.BasicManager` +- Define additional `Keeper` fields for the new modules on the `App` type +- Add the module's `StoreKey`s and initialize their `Keeper`s +- Set up corresponding routers and routes for the `ibc` module +- Add the modules to the module `Manager` +- Add modules to `Begin/EndBlockers` and `InitGenesis` +- Update the module `SimulationManager` to enable simulations + +### Module `BasicManager` and `ModuleAccount` permissions + +The first step is to add the following modules to the `BasicManager`: `x/capability`, `x/ibc`, +and `x/ibc-transfer`. After that, we need to grant `Minter` and `Burner` permissions to +the `ibc-transfer` `ModuleAccount` to mint and burn relayed tokens. + +### Integrating light clients + +> Note that from v7 onwards, all light clients have to be explicitly registered in a chain's app.go and follow the steps listed below. + This is in contrast to earlier versions of ibc-go when `07-tendermint` and `06-solomachine` were added out of the box. + +All light clients must be registered with `module.BasicManager` in a chain's app.go file. + +The following code example shows how to register the existing `ibctm.AppModuleBasic{}` light client implementation. + +```diff + +import ( + ... ++ ibctm "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" + ... +) + +// app.go +var ( + + ModuleBasics = module.NewBasicManager( + // ... + capability.AppModuleBasic{}, + ibc.AppModuleBasic{}, + transfer.AppModuleBasic{}, // i.e ibc-transfer module + + // register light clients on IBC ++ ibctm.AppModuleBasic{}, + ) + + // module account permissions + maccPerms = map[string][]string{ + // other module accounts permissions + // ... + ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + } +) +``` + +### Application fields + +Then, we need to register the `Keepers` as follows: + +```go +// app.go +type App struct { + // baseapp, keys and subspaces definitions + + // other keepers + // ... + IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly + TransferKeeper ibctransferkeeper.Keeper // for cross-chain fungible token transfers + + // make scoped keepers public for test purposes + ScopedIBCKeeper capabilitykeeper.ScopedKeeper + ScopedTransferKeeper capabilitykeeper.ScopedKeeper + + /// ... + /// module and simulation manager definitions +} +``` + +### Configure the `Keepers` + +During initialization, besides initializing the IBC `Keepers` (for the `x/ibc`, and +`x/ibc-transfer` modules), we need to grant specific capabilities through the capability module +`ScopedKeepers` so that we can authenticate the object-capability permissions for each of the IBC +channels. + +```go +func NewApp(...args) *App { + // define codecs and baseapp + + // add capability keeper and ScopeToModule for ibc module + app.CapabilityKeeper = capabilitykeeper.NewKeeper(appCodec, keys[capabilitytypes.StoreKey], memKeys[capabilitytypes.MemStoreKey]) + + // grant capabilities for the ibc and ibc-transfer modules + scopedIBCKeeper := app.CapabilityKeeper.ScopeToModule(ibcexported.ModuleName) + scopedTransferKeeper := app.CapabilityKeeper.ScopeToModule(ibctransfertypes.ModuleName) + + // ... other modules keepers + + // Create IBC Keeper + app.IBCKeeper = ibckeeper.NewKeeper( + appCodec, keys[ibcexported.StoreKey], app.GetSubspace(ibcexported.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, + ) + + // Create Transfer Keepers + app.TransferKeeper = ibctransferkeeper.NewKeeper( + appCodec, keys[ibctransfertypes.StoreKey], app.GetSubspace(ibctransfertypes.ModuleName), + app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.AccountKeeper, app.BankKeeper, scopedTransferKeeper, + ) + transferModule := transfer.NewAppModule(app.TransferKeeper) + + // .. continues +} +``` + +### Register `Routers` + +IBC needs to know which module is bound to which port so that it can route packets to the +appropriate module and call the appropriate callbacks. The port to module name mapping is handled by +IBC's port `Keeper`. However, the mapping from module name to the relevant callbacks is accomplished +by the port +[`Router`](https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port/types/router.go) on the +IBC module. + +Adding the module routes allows the IBC handler to call the appropriate callback when processing a +channel handshake or a packet. + +Currently, a `Router` is static so it must be initialized and set correctly on app initialization. +Once the `Router` has been set, no new routes can be added. + +```go +// app.go +func NewApp(...args) *App { + // .. continuation from above + + // Create static IBC router, add ibc-transfer module route, then set and seal it + ibcRouter := port.NewRouter() + ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferModule) + // Setting Router will finalize all routes by sealing router + // No more routes can be added + app.IBCKeeper.SetRouter(ibcRouter) + + // .. continues +``` + +### Module Managers + +In order to use IBC, we need to add the new modules to the module `Manager` and to the `SimulationManager` in case your application supports [simulations](https://docs.cosmos.network/main/learn/advanced/simulation). + +```go +// app.go +func NewApp(...args) *App { + // .. continuation from above + + app.mm = module.NewManager( + // other modules + // ... + capability.NewAppModule(appCodec, *app.CapabilityKeeper), + ibc.NewAppModule(app.IBCKeeper), + transferModule, + ) + + // ... + + app.sm = module.NewSimulationManager( + // other modules + // ... + capability.NewAppModule(appCodec, *app.CapabilityKeeper), + ibc.NewAppModule(app.IBCKeeper), + transferModule, + ) + + // .. continues +``` + +### Application ABCI Ordering + +One addition from IBC is the concept of `HistoricalEntries` which are stored on the staking module. +Each entry contains the historical information for the `Header` and `ValidatorSet` of this chain which is stored +at each height during the `BeginBlock` call. The historical info is required to introspect the +past historical info at any given height in order to verify the light client `ConsensusState` during the +connection handshake. + +```go +// app.go +func NewApp(...args) *App { + // .. continuation from above + + // add staking and ibc modules to BeginBlockers + app.mm.SetOrderBeginBlockers( + // other modules ... + stakingtypes.ModuleName, ibcexported.ModuleName, + ) + + // ... + + // NOTE: Capability module must occur first so that it can initialize any capabilities + // so that other modules that want to create or claim capabilities afterwards in InitChain + // can do so safely. + app.mm.SetOrderInitGenesis( + capabilitytypes.ModuleName, + // other modules ... + ibcexported.ModuleName, ibctransfertypes.ModuleName, + ) + + // .. continues +``` + +:::warning +**IMPORTANT**: The capability module **must** be declared first in `SetOrderInitGenesis` +::: + +That's it! You have now wired up the IBC module and are now able to send fungible tokens across +different chains. If you want to have a broader view of the changes take a look into the SDK's +[`SimApp`](https://github.com/cosmos/ibc-go/blob/main/testing/simapp/app.go). diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/01-apps.md b/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/01-apps.md new file mode 100644 index 0000000..b039253 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/01-apps.md @@ -0,0 +1,57 @@ +--- +title: IBC Applications +sidebar_label: IBC Applications +sidebar_position: 1 +slug: /ibc/apps/apps +--- + + +# IBC Applications + +:::note Synopsis +Learn how to build custom IBC application modules that enable packets to be sent to and received from other IBC-enabled chains. +::: + +This document serves as a guide for developers who want to write their own Inter-blockchain Communication Protocol (IBC) applications for custom use cases. + +Due to the modular design of the IBC protocol, IBC application developers do not need to concern themselves with the low-level details of clients, connections, and proof verification. Nevertheless, an overview of these low-level concepts can be found in [the Overview section](../01-overview.md). +The document goes into detail on the abstraction layer most relevant for application developers (channels and ports), and describes how to define your own custom packets, `IBCModule` callbacks and more to make an application module IBC ready. + +**To have your module interact over IBC you must:** + +- implement the `IBCModule` interface, i.e.: + - channel (opening) handshake callbacks + - channel closing handshake callbacks + - packet callbacks +- bind to a port(s) +- add keeper methods +- define your own packet data and acknowledgement structs as well as how to encode/decode them +- add a route to the IBC router + +The following sections provide a more detailed explanation of how to write an IBC application +module correctly corresponding to the listed steps. + +:::note + +## Pre-requisites Readings + +- [IBC Overview](../01-overview.md)) +- [IBC default integration](../02-integration.md) + +::: + +## Working example + +For a real working example of an IBC application, you can look through the `ibc-transfer` module +which implements everything discussed in this section. + +Here are the useful parts of the module to look at: + +[Binding to transfer +port](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/genesis.go) + +[Sending transfer +packets](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/relay.go) + +[Implementing IBC +callbacks](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/ibc_module.go) diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/02-ibcmodule.md b/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/02-ibcmodule.md new file mode 100644 index 0000000..c55661d --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/02-ibcmodule.md @@ -0,0 +1,382 @@ +--- +title: Implement `IBCModule` interface and callbacks +sidebar_label: Implement `IBCModule` interface and callbacks +sidebar_position: 2 +slug: /ibc/apps/ibcmodule +--- + + +# Implement `IBCModule` interface and callbacks + +:::note Synopsis +Learn how to implement the `IBCModule` interface and all of the callbacks it requires. +::: + +The Cosmos SDK expects all IBC modules to implement the [`IBCModule` +interface](https://github.com/cosmos/ibc-go/tree/main/modules/core/05-port/types/module.go). This interface contains all of the callbacks IBC expects modules to implement. They include callbacks related to channel handshake, closing and packet callbacks (`OnRecvPacket`, `OnAcknowledgementPacket` and `OnTimeoutPacket`). + +```go +// IBCModule implements the ICS26 interface for given the keeper. +// The implementation of the IBCModule interface could for example be in a file called ibc_module.go, +// but ultimately file structure is up to the developer +type IBCModule struct { + keeper keeper.Keeper +} +``` + +Additionally, in the `module.go` file, add the following line: + +```go +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + // Add this line + _ porttypes.IBCModule = IBCModule{} +) +``` + +:::note + +## Pre-requisites Readings + +- [IBC Overview](../01-overview.md)) +- [IBC default integration](../02-integration.md) + +::: + +## Channel handshake callbacks + +This section will describe the callbacks that are called during channel handshake execution. Among other things, it will claim channel capabilities passed on from core IBC. For a refresher on capabilities, check [the Overview section](../01-overview.md#capabilities). + +Here are the channel handshake callbacks that modules are expected to implement: + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `checkArguments` and `negotiateAppVersion` functions. + +```go +// Called by IBC Handler on MsgOpenInit +func (im IBCModule) OnChanOpenInit(ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + // Examples: + // - Abort if order == UNORDERED, + // - Abort if version is unsupported + if err := checkArguments(args); err != nil { + return "", err + } + + // OpenInit must claim the channelCapability that IBC passes into the callback + if err := im.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return "", err + } + + return version, nil +} + +// Called by IBC Handler on MsgOpenTry +func (im IBCModule) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + if err := checkArguments(args); err != nil { + return "", err + } + + // OpenTry must claim the channelCapability that IBC passes into the callback + if err := im.keeper.scopedKeeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return err + } + + // Construct application version + // IBC applications must return the appropriate application version + // This can be a simple string or it can be a complex version constructed + // from the counterpartyVersion and other arguments. + // The version returned will be the channel version used for both channel ends. + appVersion := negotiateAppVersion(counterpartyVersion, args) + + return appVersion, nil +} + +// Called by IBC Handler on MsgOpenAck +func (im IBCModule) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyVersion string, +) error { + if counterpartyVersion != types.Version { + return sdkerrors.Wrapf(types.ErrInvalidVersion, "invalid counterparty version: %s, expected %s", counterpartyVersion, types.Version) + } + + // do custom logic + + return nil +} + +// Called by IBC Handler on MsgOpenConfirm +func (im IBCModule) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // do custom logic + + return nil +} +``` + +The channel closing handshake will also invoke module callbacks that can return errors to abort the closing handshake. Closing a channel is a 2-step handshake, the initiating chain calls `ChanCloseInit` and the finalizing chain calls `ChanCloseConfirm`. + +```go +// Called by IBC Handler on MsgCloseInit +func (im IBCModule) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + // ... do custom finalization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} + +// Called by IBC Handler on MsgCloseConfirm +func (im IBCModule) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // ... do custom finalization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} +``` + +### Channel handshake version negotiation + +Application modules are expected to verify versioning used during the channel handshake procedure. + +- `OnChanOpenInit` will verify that the relayer-chosen parameters + are valid and perform any custom `INIT` logic. + It may return an error if the chosen parameters are invalid + in which case the handshake is aborted. + If the provided version string is non-empty, `OnChanOpenInit` should return + the version string if valid or an error if the provided version is invalid. + **If the version string is empty, `OnChanOpenInit` is expected to + return a default version string representing the version(s) + it supports.** + If there is no default version string for the application, + it should return an error if the provided version is an empty string. +- `OnChanOpenTry` will verify the relayer-chosen parameters along with the + counterparty-chosen version string and perform custom `TRY` logic. + If the relayer-chosen parameters + are invalid, the callback must return an error to abort the handshake. + If the counterparty-chosen version is not compatible with this module's + supported versions, the callback must return an error to abort the handshake. + If the versions are compatible, the try callback must select the final version + string and return it to core IBC. + `OnChanOpenTry` may also perform custom initialization logic. +- `OnChanOpenAck` will error if the counterparty selected version string + is invalid and abort the handshake. It may also perform custom ACK logic. + +Versions must be strings but can implement any versioning structure. If your application plans to +have linear releases then semantic versioning is recommended. If your application plans to release +various features in between major releases then it is advised to use the same versioning scheme +as IBC. This versioning scheme specifies a version identifier and compatible feature set with +that identifier. Valid version selection includes selecting a compatible version identifier with +a subset of features supported by your application for that version. The struct used for this +scheme can be found in [03-connection/types](https://github.com/cosmos/ibc-go/blob/main/modules/core/03-connection/types/version.go#L16). + +Since the version type is a string, applications have the ability to do simple version verification +via string matching or they can use the already implemented versioning system and pass the proto +encoded version into each handhshake call as necessary. + +ICS20 currently implements basic string matching with a single supported version. + +## Packet callbacks + +Just as IBC expects modules to implement callbacks for channel handshakes, it also expects modules to implement callbacks for handling the packet flow through a channel, as defined in the `IBCModule` interface. + +Once a module A and module B are connected to each other, relayers can start relaying packets and acknowledgements back and forth on the channel. + +![IBC packet flow diagram](./images/packet_flow.png) + +Briefly, a successful packet flow works as follows: + +1. module A sends a packet through the IBC module +2. the packet is received by module B +3. if module B writes an acknowledgement of the packet then module A will process the + acknowledgement +4. if the packet is not successfully received before the timeout, then module A processes the + packet's timeout. + +### Sending packets + +Modules **do not send packets through callbacks**, since the modules initiate the action of sending packets to the IBC module, as opposed to other parts of the packet flow where messages sent to the IBC +module must trigger execution on the port-bound module through the use of callbacks. Thus, to send a packet a module simply needs to call `SendPacket` on the `IBCChannelKeeper`. + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `EncodePacketData(customPacketData)` function. + +```go +// retrieve the dynamic capability for this channel +channelCap := scopedKeeper.GetCapability(ctx, channelCapName) +// Sending custom application packet data +data := EncodePacketData(customPacketData) +// Send packet to IBC, authenticating with channelCap +sequence, err := IBCChannelKeeper.SendPacket( + ctx, + channelCap, + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + data, +) +``` + +:::warning +In order to prevent modules from sending packets on channels they do not own, IBC expects +modules to pass in the correct channel capability for the packet's source channel. +::: + +### Receiving packets + +To handle receiving packets, the module must implement the `OnRecvPacket` callback. This gets +invoked by the IBC module after the packet has been proved valid and correctly processed by the IBC +keepers. Thus, the `OnRecvPacket` callback only needs to worry about making the appropriate state +changes given the packet data without worrying about whether the packet is valid or not. + +Modules may return to the IBC handler an acknowledgement which implements the `Acknowledgement` interface. +The IBC handler will then commit this acknowledgement of the packet so that a relayer may relay the +acknowledgement back to the sender module. + +The state changes that occurred during this callback will only be written if: + +- the acknowledgement was successful as indicated by the `Success()` function of the acknowledgement +- if the acknowledgement returned is nil indicating that an asynchronous process is occurring + +NOTE: Applications which process asynchronous acknowledgements must handle reverting state changes +when appropriate. Any state changes that occurred during the `OnRecvPacket` callback will be written +for asynchronous acknowledgements. + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `DecodePacketData(packet.Data)` function. + +```go +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, +) ibcexported.Acknowledgement { + // Decode the packet data + packetData := DecodePacketData(packet.Data) + + // do application state changes based on packet data and return the acknowledgement + // NOTE: The acknowledgement will indicate to the IBC handler if the application + // state changes should be written via the `Success()` function. Application state + // changes are only written if the acknowledgement is successful or the acknowledgement + // returned is nil indicating that an asynchronous acknowledgement will occur. + ack := processPacket(ctx, packet, packetData) + + return ack +} +``` + +Reminder, the `Acknowledgement` interface: + +```go +// Acknowledgement defines the interface used to return +// acknowledgements in the OnRecvPacket callback. +type Acknowledgement interface { + Success() bool + Acknowledgement() []byte +} +``` + +### Acknowledging packets + +After a module writes an acknowledgement, a relayer can relay back the acknowledgement to the sender module. The sender module can +then process the acknowledgement using the `OnAcknowledgementPacket` callback. The contents of the +acknowledgement is entirely up to the modules on the channel (just like the packet data); however, it +may often contain information on whether the packet was successfully processed along +with some additional data that could be useful for remediation if the packet processing failed. + +Since the modules are responsible for agreeing on an encoding/decoding standard for packet data and +acknowledgements, IBC will pass in the acknowledgements as `[]byte` to this callback. The callback +is responsible for decoding the acknowledgement and processing it. + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `DecodeAcknowledgement(acknowledgments)` and `processAck(ack)` functions. + +```go +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, +) (*sdk.Result, error) { + // Decode acknowledgement + ack := DecodeAcknowledgement(acknowledgement) + + // process ack + res, err := processAck(ack) + return res, err +} +``` + +### Timeout packets + +If the timeout for a packet is reached before the packet is successfully received or the +counterparty channel end is closed before the packet is successfully received, then the receiving +chain can no longer process it. Thus, the sending chain must process the timeout using +`OnTimeoutPacket` to handle this situation. Again the IBC module will verify that the timeout is +indeed valid, so our module only needs to implement the state machine logic for what to do once a +timeout is reached and the packet can no longer be received. + +```go +func (im IBCModule) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, +) (*sdk.Result, error) { + // do custom timeout logic +} +``` + +### Optional interfaces + +The following interface are optional and MAY be implemented by an IBCModule. + +#### PacketDataUnmarshaler + +The `PacketDataUnmarshaler` interface is defined as follows: + +```go +// PacketDataUnmarshaler defines an optional interface which allows a middleware to +// request the packet data to be unmarshaled by the base application. +type PacketDataUnmarshaler interface { + // UnmarshalPacketData unmarshals the packet data into a concrete type + UnmarshalPacketData([]byte) (interface{}, error) +} +``` + +The implementation of `UnmarshalPacketData` should unmarshal the bytes into the packet data type defined for an IBC stack. +The base application of an IBC stack should unmarshal the bytes into its packet data type, while a middleware may simply defer the call to the underlying application. + +This interface allows middlewares to unmarshal a packet data in order to make use of interfaces the packet data type implements. +For example, the callbacks middleware makes use of this function to access packet data types which implement the `PacketData` and `PacketDataProvider` interfaces. diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/03-bindports.md b/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/03-bindports.md new file mode 100644 index 0000000..303168d --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/03-bindports.md @@ -0,0 +1,122 @@ +--- +title: Bind ports +sidebar_label: Bind ports +sidebar_position: 3 +slug: /ibc/apps/bindports +--- + +# Bind ports + +:::note Synopsis +Learn what changes to make to bind modules to their ports on initialization. +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: +Currently, ports must be bound on app initialization. In order to bind modules to their respective ports on initialization, the following needs to be implemented: + +> Note that `portID` does not refer to a certain numerical ID, like `localhost:8080` with a `portID` 8080. Rather it refers to the application module the port binds. For IBC Modules built with the Cosmos SDK, it defaults to the module's name and for Cosmwasm contracts it defaults to the contract address. + +1. Add port ID to the `GenesisState` proto definition: + + ```protobuf + message GenesisState { + string port_id = 1; + // other fields + } + ``` + +1. Add port ID as a key to the module store: + + ```go + // x//types/keys.go + const ( + // ModuleName defines the IBC Module name + ModuleName = "moduleName" + + // Version defines the current version the IBC + // module supports + Version = "moduleVersion-1" + + // PortID is the default port id that module binds to + PortID = "portID" + + // ... + ) + ``` + +1. Add port ID to `x//types/genesis.go`: + + ```go + // in x//types/genesis.go + + // DefaultGenesisState returns a GenesisState with "transfer" as the default PortID. + func DefaultGenesisState() *GenesisState { + return &GenesisState{ + PortId: PortID, + // additional k-v fields + } + } + + // Validate performs basic genesis state validation returning an error upon any + // failure. + func (gs GenesisState) Validate() error { + if err := host.PortIdentifierValidator(gs.PortId); err != nil { + return err + } + //additional validations + + return gs.Params.Validate() + } + ``` + +1. Bind to port(s) in the module keeper's `InitGenesis`: + + ```go + // InitGenesis initializes the ibc-module state and binds to PortID. + func (k Keeper) InitGenesis(ctx sdk.Context, state types.GenesisState) { + k.SetPort(ctx, state.PortId) + + // ... + + // Only try to bind to port if it is not already bound, since we may already own + // port capability from capability InitGenesis + if !k.IsBound(ctx, state.PortId) { + // transfer module binds to the transfer port on InitChain + // and claims the returned capability + err := k.BindPort(ctx, state.PortId) + if err != nil { + panic(fmt.Sprintf("could not claim port capability: %v", err)) + } + } + + // ... + } + ``` + + With: + + ```go + // IsBound checks if the module is already bound to the desired port + func (k Keeper) IsBound(ctx sdk.Context, portID string) bool { + _, ok := k.scopedKeeper.GetCapability(ctx, host.PortPath(portID)) + return ok + } + + // BindPort defines a wrapper function for the port Keeper's function in + // order to expose it to module's InitGenesis function + func (k Keeper) BindPort(ctx sdk.Context, portID string) error { + cap := k.portKeeper.BindPort(ctx, portID) + return k.ClaimCapability(ctx, cap, host.PortPath(portID)) + } + ``` + + The module binds to the desired port(s) and returns the capabilities. + + In the above we find reference to keeper methods that wrap other keeper functionality, in the next section the keeper methods that need to be implemented will be defined. diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/04-keeper.md b/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/04-keeper.md new file mode 100644 index 0000000..cf4fac6 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/04-keeper.md @@ -0,0 +1,96 @@ +--- +title: Keeper +sidebar_label: Keeper +sidebar_position: 4 +slug: /ibc/apps/keeper +--- + +# Keeper + +:::note Synopsis +Learn how to implement the IBC Module keeper. +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: +In the previous sections, on channel handshake callbacks and port binding in `InitGenesis`, a reference was made to keeper methods that need to be implemented when creating a custom IBC module. Below is an overview of how to define an IBC module's keeper. + +> Note that some code has been left out for clarity, to get a full code overview, please refer to [the transfer module's keeper in the ibc-go repo](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/keeper.go). + +```go +// Keeper defines the IBC app module keeper +type Keeper struct { + storeKey sdk.StoreKey + cdc codec.BinaryCodec + paramSpace paramtypes.Subspace + + channelKeeper types.ChannelKeeper + portKeeper types.PortKeeper + scopedKeeper capabilitykeeper.ScopedKeeper + + // ... additional according to custom logic +} + +// NewKeeper creates a new IBC app module Keeper instance +func NewKeeper( + // args +) Keeper { + // ... + + return Keeper{ + cdc: cdc, + storeKey: key, + paramSpace: paramSpace, + + channelKeeper: channelKeeper, + portKeeper: portKeeper, + scopedKeeper: scopedKeeper, + + // ... additional according to custom logic + } +} + +// IsBound checks if the IBC app module is already bound to the desired port +func (k Keeper) IsBound(ctx sdk.Context, portID string) bool { + _, ok := k.scopedKeeper.GetCapability(ctx, host.PortPath(portID)) + return ok +} + +// BindPort defines a wrapper function for the port Keeper's function in +// order to expose it to module's InitGenesis function +func (k Keeper) BindPort(ctx sdk.Context, portID string) error { + cap := k.portKeeper.BindPort(ctx, portID) + return k.ClaimCapability(ctx, cap, host.PortPath(portID)) +} + +// GetPort returns the portID for the IBC app module. Used in ExportGenesis +func (k Keeper) GetPort(ctx sdk.Context) string { + store := ctx.KVStore(k.storeKey) + return string(store.Get(types.PortKey)) +} + +// SetPort sets the portID for the IBC app module. Used in InitGenesis +func (k Keeper) SetPort(ctx sdk.Context, portID string) { + store := ctx.KVStore(k.storeKey) + store.Set(types.PortKey, []byte(portID)) +} + +// AuthenticateCapability wraps the scopedKeeper's AuthenticateCapability function +func (k Keeper) AuthenticateCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) bool { + return k.scopedKeeper.AuthenticateCapability(ctx, cap, name) +} + +// ClaimCapability allows the IBC app module to claim a capability that core IBC +// passes to it +func (k Keeper) ClaimCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) error { + return k.scopedKeeper.ClaimCapability(ctx, cap, name) +} + +// ... additional according to custom logic +``` diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/05-packets_acks.md b/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/05-packets_acks.md new file mode 100644 index 0000000..6b1b822 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/05-packets_acks.md @@ -0,0 +1,167 @@ +--- +title: Define packets and acks +sidebar_label: Define packets and acks +sidebar_position: 5 +slug: /ibc/apps/packets_acks +--- + + +# Define packets and acks + +:::note Synopsis +Learn how to define custom packet and acknowledgement structs and how to encode and decode them. +::: + +:::note + +## Pre-requisites Readings + +- [IBC Overview](../01-overview.md)) +- [IBC default integration](../02-integration.md) + +::: + +## Custom packets + +Modules connected by a channel must agree on what application data they are sending over the +channel, as well as how they will encode/decode it. This process is not specified by IBC as it is up +to each application module to determine how to implement this agreement. However, for most +applications this will happen as a version negotiation during the channel handshake. While more +complex version negotiation is possible to implement inside the channel opening handshake, a very +simple version negotiation is implemented in the [ibc-transfer module](https://github.com/cosmos/ibc-go/tree/main/modules/apps/transfer/module.go). + +Thus, a module must define its custom packet data structure, along with a well-defined way to +encode and decode it to and from `[]byte`. + +```go +// Custom packet data defined in application module +type CustomPacketData struct { + // Custom fields ... +} + +EncodePacketData(packetData CustomPacketData) []byte { + // encode packetData to bytes +} + +DecodePacketData(encoded []byte) (CustomPacketData) { + // decode from bytes to packet data +} +``` + +> Note that the `CustomPacketData` struct is defined in the proto definition and then compiled by the protobuf compiler. + +Then a module must encode its packet data before sending it through IBC. + +```go +// retrieve the dynamic capability for this channel +channelCap := scopedKeeper.GetCapability(ctx, channelCapName) +// Sending custom application packet data +data := EncodePacketData(customPacketData) +// Send packet to IBC, authenticating with channelCap +sequence, err := IBCChannelKeeper.SendPacket( + ctx, + channelCap, + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + data, +) +``` + +A module receiving a packet must decode the `PacketData` into a structure it expects so that it can +act on it. + +```go +// Receiving custom application packet data (in OnRecvPacket) +packetData := DecodePacketData(packet.Data) +// handle received custom packet data +``` + +### Optional interfaces + +The following interfaces are optional and MAY be implemented by a custom packet type. +They allow middlewares such as callbacks to access information stored within the packet data. + +#### PacketData interface + +The `PacketData` interface is defined as follows: + +```go +// PacketData defines an optional interface which an application's packet data structure may implement. +type PacketData interface { + // GetPacketSender returns the sender address of the packet data. + // If the packet sender is unknown or undefined, an empty string should be returned. + GetPacketSender(sourcePortID string) string +} +``` + +The implementation of `GetPacketSender` should return the sender of the packet data. +If the packet sender is unknown or undefined, an empty string should be returned. + +This interface is intended to give IBC middlewares access to the packet sender of a packet data type. + +#### PacketDataProvider interface + +The `PacketDataProvider` interface is defined as follows: + +```go +// PacketDataProvider defines an optional interfaces for retrieving custom packet data stored on behalf of another application. +// An existing problem in the IBC middleware design is the inability for a middleware to define its own packet data type and insert packet sender provided information. +// A short term solution was introduced into several application's packet data to utilize a memo field to carry this information on behalf of another application. +// This interfaces standardizes that behaviour. Upon realization of the ability for middleware's to define their own packet data types, this interface will be deprecated and removed with time. +type PacketDataProvider interface { + // GetCustomPacketData returns the packet data held on behalf of another application. + // The name the information is stored under should be provided as the key. + // If no custom packet data exists for the key, nil should be returned. + GetCustomPacketData(key string) interface{} +} +``` + +The implementation of `GetCustomPacketData` should return packet data held on behalf of another application (if present and supported). +If this functionality is not supported, it should return nil. Otherwise it should return the packet data associated with the provided key. + +This interface gives IBC applications access to the packet data information embedded into the base packet data type. +Within transfer and interchain accounts, the embedded packet data is stored within the Memo field. + +Once all IBC applications within an IBC stack are capable of creating/maintaining their own packet data type's, this interface function will be deprecated and removed. + +## Acknowledgements + +Modules may commit an acknowledgement upon receiving and processing a packet in the case of synchronous packet processing. +In the case where a packet is processed at some later point after the packet has been received (asynchronous execution), the acknowledgement +will be written once the packet has been processed by the application which may be well after the packet receipt. + +NOTE: Most blockchain modules will want to use the synchronous execution model in which the module processes and writes the acknowledgement +for a packet as soon as it has been received from the IBC module. + +This acknowledgement can then be relayed back to the original sender chain, which can take action +depending on the contents of the acknowledgement. + +Just as packet data was opaque to IBC, acknowledgements are similarly opaque. Modules must pass and +receive acknowledegments with the IBC modules as byte strings. + +Thus, modules must agree on how to encode/decode acknowledgements. The process of creating an +acknowledgement struct along with encoding and decoding it, is very similar to the packet data +example above. [ICS 04](https://github.com/cosmos/ibc/blob/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope) +specifies a recommended format for acknowledgements. This acknowledgement type can be imported from +[channel types](https://github.com/cosmos/ibc-go/tree/main/modules/core/04-channel/types). + +While modules may choose arbitrary acknowledgement structs, a default acknowledgement types is provided by IBC [here](https://github.com/cosmos/ibc-go/blob/main/proto/ibc/core/channel/v1/channel.proto): + +```protobuf +// Acknowledgement is the recommended acknowledgement format to be used by +// app-specific protocols. +// NOTE: The field numbers 21 and 22 were explicitly chosen to avoid accidental +// conflicts with other protobuf message formats used for acknowledgements. +// The first byte of any message with this format will be the non-ASCII values +// `0xaa` (result) or `0xb2` (error). Implemented as defined by ICS: +// https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope +message Acknowledgement { + // response contains either a result or an error and must be non-empty + oneof response { + bytes result = 21; + string error = 22; + } +} +``` diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/06-routing.md b/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/06-routing.md new file mode 100644 index 0000000..7e6c205 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/06-routing.md @@ -0,0 +1,44 @@ +--- +title: Routing +sidebar_label: Routing +sidebar_position: 6 +slug: /ibc/apps/routing +--- + +# Routing + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: +:::note Synopsis +Learn how to hook a route to the IBC router for the custom IBC module. +::: + +As mentioned above, modules must implement the `IBCModule` interface (which contains both channel +handshake callbacks and packet handling callbacks). The concrete implementation of this interface +must be registered with the module name as a route on the IBC `Router`. + +```go +// app.go +func NewApp(...args) *App { +// ... + +// Create static IBC router, add module routes, then set and seal it +ibcRouter := port.NewRouter() + +ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferModule) +// Note: moduleCallbacks must implement IBCModule interface +ibcRouter.AddRoute(moduleName, moduleCallbacks) + +// Setting Router will finalize all routes by sealing router +// No more routes can be added +app.IBCKeeper.SetRouter(ibcRouter) + +// ... +} +``` diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/_category_.json b/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/_category_.json new file mode 100644 index 0000000..1c34da9 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Applications", + "position": 3, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/images/packet_flow.png b/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/images/packet_flow.png new file mode 100644 index 0000000..e5bae3f Binary files /dev/null and b/docs/versioned_docs/version-v7.8.x/01-ibc/03-apps/images/packet_flow.png differ diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/04-middleware/01-develop.md b/docs/versioned_docs/version-v7.8.x/01-ibc/04-middleware/01-develop.md new file mode 100644 index 0000000..8c11ca2 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/04-middleware/01-develop.md @@ -0,0 +1,487 @@ +--- +title: IBC middleware +sidebar_label: IBC middleware +sidebar_position: 1 +slug: /ibc/middleware/develop +--- + +# IBC middleware + +:::note Synopsis +Learn how to write your own custom middleware to wrap an IBC application, and understand how to hook different middleware to IBC base applications to form different IBC application stacks +:::. + +This document serves as a guide for middleware developers who want to write their own middleware and for chain developers who want to use IBC middleware on their chains. + +IBC applications are designed to be self-contained modules that implement their own application-specific logic through a set of interfaces with the core IBC handlers. These core IBC handlers, in turn, are designed to enforce the correctness properties of IBC (transport, authentication, ordering) while delegating all application-specific handling to the IBC application modules. However, there are cases where some functionality may be desired by many applications, yet not appropriate to place in core IBC. + +Middleware allows developers to define the extensions as separate modules that can wrap over the base application. This middleware can thus perform its own custom logic, and pass data into the application so that it may run its logic without being aware of the middleware's existence. This allows both the application and the middleware to implement its own isolated logic while still being able to run as part of a single packet flow. + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC Integration](../02-integration.md) +- [IBC Application Developer Guide](../03-apps/01-apps.md) + +::: + +## Definitions + +`Middleware`: A self-contained module that sits between core IBC and an underlying IBC application during packet execution. All messages between core IBC and underlying application must flow through middleware, which may perform its own custom logic. + +`Underlying Application`: An underlying application is the application that is directly connected to the middleware in question. This underlying application may itself be middleware that is chained to a base application. + +`Base Application`: A base application is an IBC application that does not contain any middleware. It may be nested by 0 or multiple middleware to form an application stack. + +`Application Stack (or stack)`: A stack is the complete set of application logic (middleware(s) + base application) that gets connected to core IBC. A stack may be just a base application, or it may be a series of middlewares that nest a base application. + +## Create a custom IBC middleware + +IBC middleware will wrap over an underlying IBC application and sits between core IBC and the application. It has complete control in modifying any message coming from IBC to the application, and any message coming from the application to core IBC. Thus, middleware must be completely trusted by chain developers who wish to integrate them, however this gives them complete flexibility in modifying the application(s) they wrap. + +:::warning +middleware developers must use the same serialization and deserialization method as in ibc-go's codec: transfertypes.ModuleCdc.[Must]MarshalJSON +::: + +For middleware builders this means: + +```go +import transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +transfertypes.ModuleCdc.[Must]MarshalJSON +func MarshalAsIBCDoes(ack channeltypes.Acknowledgement) ([]byte, error) { + return transfertypes.ModuleCdc.MarshalJSON(&ack) +} +``` + +### Interfaces + +```go +// Middleware implements the ICS26 Module interface +type Middleware interface { + porttypes.IBCModule // middleware has access to an underlying application which may be wrapped by more middleware + ics4Wrapper: ICS4Wrapper // middleware has access to ICS4Wrapper which may be core IBC Channel Handler or a higher-level middleware that wraps this middleware. +} +``` + +```typescript +// This is implemented by ICS4 and all middleware that are wrapping base application. +// The base application will call `sendPacket` or `writeAcknowledgement` of the middleware directly above them +// which will call the next middleware until it reaches the core IBC handler. +type ICS4Wrapper interface { + SendPacket( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + data []byte, + ) (sequence uint64, err error) + + WriteAcknowledgement( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + packet exported.PacketI, + ack exported.Acknowledgement, + ) error + + GetAppVersion( + ctx sdk.Context, + portID, + channelID string, + ) (string, bool) +} +``` + +### Implement `IBCModule` interface and callbacks + +The `IBCModule` is a struct that implements the [ICS-26 interface (`porttypes.IBCModule`)](https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port/types/module.go#L11-L106). It is recommended to separate these callbacks into a separate file `ibc_module.go`. As will be mentioned in the [integration section](02-integration.md), this struct should be different than the struct that implements `AppModule` in case the middleware maintains its own internal state and processes separate SDK messages. + +The middleware must have access to the underlying application, and be called before during all ICS-26 callbacks. It may execute custom logic during these callbacks, and then call the underlying application's callback. Middleware **may** choose not to call the underlying application's callback at all. Though these should generally be limited to error cases. + +In the case where the IBC middleware expects to speak to a compatible IBC middleware on the counterparty chain, they must use the channel handshake to negotiate the middleware version without interfering in the version negotiation of the underlying application. + +Middleware accomplishes this by formatting the version in a JSON-encoded string containing the middleware version and the application version. The application version may as well be a JSON-encoded string, possibly including further middleware and app versions, if the application stack consists of multiple milddlewares wrapping a base application. The format of the version is specified in ICS-30 as the following: + +```json +{ + "": "", + "app_version": "" +} +``` + +The `` key in the JSON struct should be replaced by the actual name of the key for the corresponding middleware (e.g. `fee_version`). + +During the handshake callbacks, the middleware can unmarshal the version string and retrieve the middleware and application versions. It can do its negotiation logic on ``, and pass the `` to the underlying application. + +The middleware should simply pass the capability in the callback arguments along to the underlying application so that it may be claimed by the base application. The base application will then pass the capability up the stack in order to authenticate an outgoing packet/acknowledgement. + +In the case where the middleware wishes to send a packet or acknowledgment without the involvement of the underlying application, it should be given access to the same `scopedKeeper` as the base application so that it can retrieve the capabilities by itself. + +### Handshake callbacks + +#### `OnChanOpenInit` + +```go +func (im IBCModule) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + if version != "" { + // try to unmarshal JSON-encoded version string and pass + // the app-specific version to app callback. + // otherwise, pass version directly to app callback. + metadata, err := Unmarshal(version) + if err != nil { + // Since it is valid for fee version to not be specified, + // the above middleware version may be for another middleware. + // Pass the entire version string onto the underlying application. + return im.app.OnChanOpenInit( + ctx, + order, + connectionHops, + portID, + channelID, + channelCap, + counterparty, + version, + ) + } + else { + metadata = { + // set middleware version to default value + MiddlewareVersion: defaultMiddlewareVersion, + // allow application to return its default version + AppVersion: "", + } + } + + doCustomLogic() + + // if the version string is empty, OnChanOpenInit is expected to return + // a default version string representing the version(s) it supports + appVersion, err := im.app.OnChanOpenInit( + ctx, + order, + connectionHops, + portID, + channelID, + channelCap, + counterparty, + metadata.AppVersion, // note we only pass app version here + ) + if err != nil { + return "", err + } + + version := constructVersion(metadata.MiddlewareVersion, appVersion) + + return version, nil +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L34-L82) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnChanOpenTry` + +```go +func OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + // try to unmarshal JSON-encoded version string and pass + // the app-specific version to app callback. + // otherwise, pass version directly to app callback. + cpMetadata, err := Unmarshal(counterpartyVersion) + if err != nil { + return app.OnChanOpenTry( + ctx, + order, + connectionHops, + portID, + channelID, + channelCap, + counterparty, + counterpartyVersion, + ) + } + + doCustomLogic() + + // Call the underlying application's OnChanOpenTry callback. + // The try callback must select the final app-specific version string and return it. + appVersion, err := app.OnChanOpenTry( + ctx, + order, + connectionHops, + portID, + channelID, + channelCap, + counterparty, + cpMetadata.AppVersion, // note we only pass counterparty app version here + ) + if err != nil { + return "", err + } + + // negotiate final middleware version + middlewareVersion := negotiateMiddlewareVersion(cpMetadata.MiddlewareVersion) + version := constructVersion(middlewareVersion, appVersion) + + return version, nil +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L84-L124) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnChanOpenAck` + +```go +func OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyChannelID string, + counterpartyVersion string, +) error { + // try to unmarshal JSON-encoded version string and pass + // the app-specific version to app callback. + // otherwise, pass version directly to app callback. + cpMetadata, err = UnmarshalJSON(counterpartyVersion) + if err != nil { + return app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) + } + + if !isCompatible(cpMetadata.MiddlewareVersion) { + return error + } + doCustomLogic() + + // call the underlying application's OnChanOpenTry callback + return app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, cpMetadata.AppVersion) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L126-L152) an example implementation of this callback for the ICS29 Fee Middleware module. + +### `OnChanOpenConfirm` + +```go +func OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + doCustomLogic() + + return app.OnChanOpenConfirm(ctx, portID, channelID) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L154-L162) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnChanCloseInit` + +```go +func OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + doCustomLogic() + + return app.OnChanCloseInit(ctx, portID, channelID) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L164-L187) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnChanCloseConfirm` + +```go +func OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + doCustomLogic() + + return app.OnChanCloseConfirm(ctx, portID, channelID) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L189-L212) an example implementation of this callback for the ICS29 Fee Middleware module. + +**NOTE**: Middleware that does not need to negotiate with a counterparty middleware on the remote stack will not implement the version unmarshalling and negotiation, and will simply perform its own custom logic on the callbacks without relying on the counterparty behaving similarly. + +### Packet callbacks + +The packet callbacks just like the handshake callbacks wrap the application's packet callbacks. The packet callbacks are where the middleware performs most of its custom logic. The middleware may read the packet flow data and perform some additional packet handling, or it may modify the incoming data before it reaches the underlying application. This enables a wide degree of usecases, as a simple base application like token-transfer can be transformed for a variety of usecases by combining it with custom middleware. + +#### `OnRecvPacket` + +```go +func OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) ibcexported.Acknowledgement { + doCustomLogic(packet) + + ack := app.OnRecvPacket(ctx, packet, relayer) + + doCustomLogic(ack) // middleware may modify outgoing ack + return ack +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L214-L237) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnAcknowledgementPacket` + +```go +func OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + doCustomLogic(packet, ack) + + return app.OnAcknowledgementPacket(ctx, packet, ack, relayer) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L239-L292) an example implementation of this callback for the ICS29 Fee Middleware module. + +#### `OnTimeoutPacket` + +```go +func OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + doCustomLogic(packet) + + return app.OnTimeoutPacket(ctx, packet, relayer) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L294-L334) an example implementation of this callback for the ICS29 Fee Middleware module. + +### ICS-4 wrappers + +Middleware must also wrap ICS-4 so that any communication from the application to the `channelKeeper` goes through the middleware first. Similar to the packet callbacks, the middleware may modify outgoing acknowledgements and packets in any way it wishes. + +#### `SendPacket` + +```go +func SendPacket( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + appData []byte, +) { + // middleware may modify data + data = doCustomLogic(appData) + + return ics4Keeper.SendPacket( + ctx, + chanCap, + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + data, + ) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L336-L343) an example implementation of this function for the ICS29 Fee Middleware module. + +#### `WriteAcknowledgement` + +```go +// only called for async acks +func WriteAcknowledgement( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + packet exported.PacketI, + ack exported.Acknowledgement, +) { + // middleware may modify acknowledgement + ack_bytes = doCustomLogic(ack) + + return ics4Keeper.WriteAcknowledgement(packet, ack_bytes) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L345-L353) an example implementation of this function for the ICS29 Fee Middleware module. + +#### `GetAppVersion` + +```go +// middleware must return the underlying application version +func GetAppVersion( + ctx sdk.Context, + portID, + channelID string, +) (string, bool) { + version, found := ics4Keeper.GetAppVersion(ctx, portID, channelID) + if !found { + return "", false + } + + if !MiddlewareEnabled { + return version, true + } + + // unwrap channel version + metadata, err := Unmarshal(version) + if err != nil { + panic(fmt.Errof("unable to unmarshal version: %w", err)) + } + + return metadata.AppVersion, true +} + +// middleware must return the underlying application version +func GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) { + version, found := ics4Keeper.GetAppVersion(ctx, portID, channelID) + if !found { + return "", false + } + + if !MiddlewareEnabled { + return version, true + } + + // unwrap channel version + metadata, err := Unmarshal(version) + if err != nil { + panic(fmt.Errof("unable to unmarshal version: %w", err)) + } + + return metadata.AppVersion, true +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/29-fee/ibc_middleware.go#L355-L358) an example implementation of this function for the ICS29 Fee Middleware module. diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/04-middleware/02-integration.md b/docs/versioned_docs/version-v7.8.x/01-ibc/04-middleware/02-integration.md new file mode 100644 index 0000000..d721023 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/04-middleware/02-integration.md @@ -0,0 +1,72 @@ +--- +title: Integrating IBC middleware into a chain +sidebar_label: Integrating IBC middleware into a chain +sidebar_position: 2 +slug: /ibc/middleware/integration +--- + + +# Integrating IBC middleware into a chain + +Learn how to integrate IBC middleware(s) with a base application to your chain. The following document only applies for Cosmos SDK chains. + +If the middleware is maintaining its own state and/or processing SDK messages, then it should create and register its SDK module **only once** with the module manager in `app.go`. + +All middleware must be connected to the IBC router and wrap over an underlying base IBC application. An IBC application may be wrapped by many layers of middleware, only the top layer middleware should be hooked to the IBC router, with all underlying middlewares and application getting wrapped by it. + +The order of middleware **matters**, function calls from IBC to the application travel from top-level middleware to the bottom middleware and then to the application. Function calls from the application to IBC goes through the bottom middleware in order to the top middleware and then to core IBC handlers. Thus the same set of middleware put in different orders may produce different effects. + +## Example integration + +```go +// app.go + +// middleware 1 and middleware 3 are stateful middleware, +// perhaps implementing separate sdk.Msg and Handlers +mw1Keeper := mw1.NewKeeper(storeKey1) +mw3Keeper := mw3.NewKeeper(storeKey3) + +// Only create App Module **once** and register in app module +// if the module maintains independent state and/or processes sdk.Msgs +app.moduleManager = module.NewManager( + ... + mw1.NewAppModule(mw1Keeper), + mw3.NewAppModule(mw3Keeper), + transfer.NewAppModule(transferKeeper), + custom.NewAppModule(customKeeper) +) + +mw1IBCModule := mw1.NewIBCModule(mw1Keeper) +mw2IBCModule := mw2.NewIBCModule() // middleware2 is stateless middleware +mw3IBCModule := mw3.NewIBCModule(mw3Keeper) + +scopedKeeperTransfer := capabilityKeeper.NewScopedKeeper("transfer") +scopedKeeperCustom1 := capabilityKeeper.NewScopedKeeper("custom1") +scopedKeeperCustom2 := capabilityKeeper.NewScopedKeeper("custom2") + +// NOTE: IBC Modules may be initialized any number of times provided they use a separate +// scopedKeeper and underlying port. + +// initialize base IBC applications +// if you want to create two different stacks with the same base application, +// they must be given different scopedKeepers and assigned different ports. +transferIBCModule := transfer.NewIBCModule(transferKeeper) +customIBCModule1 := custom.NewIBCModule(customKeeper, "portCustom1") +customIBCModule2 := custom.NewIBCModule(customKeeper, "portCustom2") + +// create IBC stacks by combining middleware with base application +// NOTE: since middleware2 is stateless it does not require a Keeper +// stack 1 contains mw1 -> mw3 -> transfer +stack1 := mw1.NewIBCMiddleware(mw3.NewIBCMiddleware(transferIBCModule, mw3Keeper), mw1Keeper) +// stack 2 contains mw3 -> mw2 -> custom1 +stack2 := mw3.NewIBCMiddleware(mw2.NewIBCMiddleware(customIBCModule1), mw3Keeper) +// stack 3 contains mw2 -> mw1 -> custom2 +stack3 := mw2.NewIBCMiddleware(mw1.NewIBCMiddleware(customIBCModule2, mw1Keeper)) + +// associate each stack with the moduleName provided by the underlying scopedKeeper +ibcRouter := porttypes.NewRouter() +ibcRouter.AddRoute("transfer", stack1) +ibcRouter.AddRoute("custom1", stack2) +ibcRouter.AddRoute("custom2", stack3) +app.IBCKeeper.SetRouter(ibcRouter) +``` diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/04-middleware/_category_.json b/docs/versioned_docs/version-v7.8.x/01-ibc/04-middleware/_category_.json new file mode 100644 index 0000000..ec27d4e --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/04-middleware/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Middleware", + "position": 4, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/05-upgrades/00-intro.md b/docs/versioned_docs/version-v7.8.x/01-ibc/05-upgrades/00-intro.md new file mode 100644 index 0000000..b959435 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/05-upgrades/00-intro.md @@ -0,0 +1,15 @@ +--- +title: Upgrading IBC Chains Overview +sidebar_label: Overview +sidebar_position: 0 +slug: /ibc/upgrades/intro +--- + +### Upgrading IBC Chains Overview + +This directory contains information on how to upgrade an IBC chain without breaking counterparty clients and connections. + +IBC-connected chains must be able to upgrade without breaking connections to other chains. Otherwise there would be a massive disincentive towards upgrading and disrupting high-value IBC connections, thus preventing chains in the IBC ecosystem from evolving and improving. Many chain upgrades may be irrelevant to IBC, however some upgrades could potentially break counterparty clients if not handled correctly. Thus, any IBC chain that wishes to perform an IBC-client-breaking upgrade must perform an IBC upgrade in order to allow counterparty clients to securely upgrade to the new light client. + +1. The [quick-guide](./01-quick-guide.md) describes how IBC-connected chains can perform client-breaking upgrades and how relayers can securely upgrade counterparty clients using the SDK. +2. The [developer-guide](./02-developer-guide.md) is a guide for developers intending to develop IBC client implementations with upgrade functionality. diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/05-upgrades/01-quick-guide.md b/docs/versioned_docs/version-v7.8.x/01-ibc/05-upgrades/01-quick-guide.md new file mode 100644 index 0000000..c24ae72 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/05-upgrades/01-quick-guide.md @@ -0,0 +1,60 @@ +--- +title: How to Upgrade IBC Chains and their Clients +sidebar_label: How to Upgrade IBC Chains and their Clients +sidebar_position: 1 +slug: /ibc/upgrades/quick-guide +--- + + +# How to Upgrade IBC Chains and their Clients + +:::note Synopsis +Learn how to upgrade your chain and counterparty clients. +::: + +The information in this doc for upgrading chains is relevant to SDK chains. However, the guide for counterparty clients is relevant to any Tendermint client that enables upgrades. + +## IBC Client Breaking Upgrades + +IBC-connected chains must perform an IBC upgrade if their upgrade will break counterparty IBC clients. The current IBC protocol supports upgrading tendermint chains for a specific subset of IBC-client-breaking upgrades. Here is the exhaustive list of IBC client-breaking upgrades and whether the IBC protocol currently supports such upgrades. + +IBC currently does **NOT** support unplanned upgrades. All of the following upgrades must be planned and committed to in advance by the upgrading chain, in order for counterparty clients to maintain their connections securely. + +Note: Since upgrades are only implemented for Tendermint clients, this doc only discusses upgrades on Tendermint chains that would break counterparty IBC Tendermint Clients. + +1. Changing the Chain-ID: **Supported** +2. Changing the UnbondingPeriod: **Partially Supported**, chains may increase the unbonding period with no issues. However, decreasing the unbonding period may irreversibly break some counterparty clients. Thus, it is **not recommended** that chains reduce the unbonding period. +3. Changing the height (resetting to 0): **Supported**, so long as chains remember to increment the revision number in their chain-id. +4. Changing the ProofSpecs: **Supported**, this should be changed if the proof structure needed to verify IBC proofs is changed across the upgrade. Ex: Switching from an IAVL store, to a SimpleTree Store +5. Changing the UpgradePath: **Supported**, this might involve changing the key under which upgraded clients and consensus states are stored in the upgrade store, or even migrating the upgrade store itself. +6. Migrating the IBC store: **Unsupported**, as the IBC store location is negotiated by the connection. +7. Upgrading to a backwards compatible version of IBC: Supported +8. Upgrading to a non-backwards compatible version of IBC: **Unsupported**, as IBC version is negotiated on connection handshake. +9. Changing the Tendermint LightClient algorithm: **Partially Supported**. Changes to the light client algorithm that do not change the ClientState or ConsensusState struct may be supported, provided that the counterparty is also upgraded to support the new light client algorithm. Changes that require updating the ClientState and ConsensusState structs themselves are theoretically possible by providing a path to translate an older ClientState struct into the new ClientState struct; however this is not currently implemented. + +### Step-by-Step Upgrade Process for SDK chains + +If the IBC-connected chain is conducting an upgrade that will break counterparty clients, it must ensure that the upgrade is first supported by IBC using the list above and then execute the upgrade process described below in order to prevent counterparty clients from breaking. + +1. Create a 02-client [`UpgradeProposal`](https://github.com/cosmos/ibc-go/blob/v7.3.0/proto/ibc/core/client/v1/client.proto#L58-L77) with an `UpgradePlan` and a new IBC ClientState in the `UpgradedClientState` field. Note that the `UpgradePlan` must specify an upgrade height **only** (no upgrade time), and the `ClientState` should only include the fields common to all valid clients and zero out any client-customizable fields (such as TrustingPeriod). +2. Vote on and pass the `UpgradeProposal` + +Upon the `UpgradeProposal` passing, the upgrade module will commit the UpgradedClient under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedClient`. On the block right before the upgrade height, the upgrade module will also commit an initial consensus state for the next chain under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedConsState`. + +Once the chain reaches the upgrade height and halts, a relayer can upgrade the counterparty clients to the last block of the old chain. They can then submit the proofs of the `UpgradedClient` and `UpgradedConsensusState` against this last block and upgrade the counterparty client. + +### Step-by-Step Upgrade Process for Relayers Upgrading Counterparty Clients + +Once the upgrading chain has committed to upgrading, relayers must wait till the chain halts at the upgrade height before upgrading counterparty clients. This is because chains may reschedule or cancel upgrade plans before they occur. Thus, relayers must wait till the chain reaches the upgrade height and halts before they can be sure the upgrade will take place. + +Thus, the upgrade process for relayers trying to upgrade the counterparty clients is as follows: + +1. Wait for the upgrading chain to reach the upgrade height and halt +2. Query a full node for the proofs of `UpgradedClient` and `UpgradedConsensusState` at the last height of the old chain. +3. Update the counterparty client to the last height of the old chain using the `UpdateClient` msg. +4. Submit an `UpgradeClient` msg to the counterparty chain with the `UpgradedClient`, `UpgradedConsensusState` and their respective proofs. +5. Submit an `UpdateClient` msg to the counterparty chain with a header from the new upgraded chain. + +The Tendermint client on the counterparty chain will verify that the upgrading chain did indeed commit to the upgraded client and upgraded consensus state at the upgrade height (since the upgrade height is included in the key). If the proofs are verified against the upgrade height, then the client will upgrade to the new client while retaining all of its client-customized fields. Thus, it will retain its old TrustingPeriod, TrustLevel, MaxClockDrift, etc; while adopting the new chain-specified fields such as UnbondingPeriod, ChainId, UpgradePath, etc. Note, this can lead to an invalid client since the old client-chosen fields may no longer be valid given the new chain-chosen fields. Upgrading chains should try to avoid these situations by not altering parameters that can break old clients. For an example, see the UnbondingPeriod example in the supported upgrades section. + +The upgraded consensus state will serve purely as a basis of trust for future `UpdateClientMsgs` and will not contain a consensus root to perform proof verification against. Thus, relayers must submit an `UpdateClientMsg` with a header from the new chain so that the connection can be used for proof verification again. diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/05-upgrades/02-developer-guide.md b/docs/versioned_docs/version-v7.8.x/01-ibc/05-upgrades/02-developer-guide.md new file mode 100644 index 0000000..57fc9bc --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/05-upgrades/02-developer-guide.md @@ -0,0 +1,15 @@ +--- +title: IBC Client Developer Guide to Upgrades +sidebar_label: IBC Client Developer Guide to Upgrades +sidebar_position: 2 +slug: /ibc/upgrades/developer-guide +--- + + +# IBC Client Developer Guide to Upgrades + +:::note Synopsis +Learn how to implement upgrade functionality for your custom IBC client. +::: + +Please see the section [Handling upgrades](../../03-light-clients/01-developer-guide/05-upgrades.md) from the light client developer guide for more information. diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/05-upgrades/03-genesis-restart.md b/docs/versioned_docs/version-v7.8.x/01-ibc/05-upgrades/03-genesis-restart.md new file mode 100644 index 0000000..b18c15a --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/05-upgrades/03-genesis-restart.md @@ -0,0 +1,52 @@ +--- +title: Genesis Restart Upgrades +sidebar_label: Genesis Restart Upgrades +sidebar_position: 3 +slug: /ibc/upgrades/genesis-restart +--- + + +# Genesis Restart Upgrades + +:::note Synopsis +Learn how to upgrade your chain and counterparty clients using genesis restarts. +::: + +**NOTE**: Regular genesis restarts are currently unsupported by relayers! + +## IBC Client Breaking Upgrades + +IBC client breaking upgrades are possible using genesis restarts. +It is highly recommended to use the in-place migrations instead of a genesis restart. +Genesis restarts should be used sparingly and as backup plans. + +Genesis restarts still require the usage of an IBC upgrade proposal in order to correctly upgrade counterparty clients. + +### Step-by-Step Upgrade Process for SDK Chains + +If the IBC-connected chain is conducting an upgrade that will break counterparty clients, it must ensure that the upgrade is first supported by IBC using the [IBC Client Breaking Upgrade List](./01-quick-guide.md#ibc-client-breaking-upgrades) and then execute the upgrade process described below in order to prevent counterparty clients from breaking. + +1. Create a 02-client [`UpgradeProposal`](https://github.com/cosmos/ibc-go/blob/v7.3.0/proto/ibc/core/client/v1/client.proto#L58-L77) with an `UpgradePlan` and a new IBC ClientState in the `UpgradedClientState` field. Note that the `UpgradePlan` must specify an upgrade height **only** (no upgrade time), and the `ClientState` should only include the fields common to all valid clients and zero out any client-customizable fields (such as TrustingPeriod). +2. Vote on and pass the `UpgradeProposal` +3. Halt the node after successful upgrade. +4. Export the genesis file. +5. Swap to the new binary. +6. Run migrations on the genesis file. +7. Remove the `UpgradeProposal` plan from the genesis file. This may be done by migrations. +8. Change desired chain-specific fields (chain id, unbonding period, etc). This may be done by migrations. +8. Reset the node's data. +9. Start the chain. + +Upon the `UpgradeProposal` passing, the upgrade module will commit the UpgradedClient under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedClient`. On the block right before the upgrade height, the upgrade module will also commit an initial consensus state for the next chain under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedConsState`. + +Once the chain reaches the upgrade height and halts, a relayer can upgrade the counterparty clients to the last block of the old chain. They can then submit the proofs of the `UpgradedClient` and `UpgradedConsensusState` against this last block and upgrade the counterparty client. + +#### Step-by-Step Upgrade Process for Relayers Upgrading Counterparty Clients + +These steps are identical to the regular [IBC client breaking upgrade process](./01-quick-guide.md#step-by-step-upgrade-process-for-relayers-upgrading-counterparty-clients). + +### Non-IBC Client Breaking Upgrades + +While ibc-go supports genesis restarts which do not break IBC clients, relayers do not support this upgrade path. +Here is a tracking issue on [Hermes](https://github.com/informalsystems/ibc-rs/issues/1152). +Please do not attempt a regular genesis restarts unless you have a tool to update counterparty clients correctly. diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/05-upgrades/_category_.json b/docs/versioned_docs/version-v7.8.x/01-ibc/05-upgrades/_category_.json new file mode 100644 index 0000000..439f7ee --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/05-upgrades/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Upgrades", + "position": 5, + "link": { "type": "doc", "id": "intro" } +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/06-proposals.md b/docs/versioned_docs/version-v7.8.x/01-ibc/06-proposals.md new file mode 100644 index 0000000..f143603 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/06-proposals.md @@ -0,0 +1,132 @@ +--- +title: Governance Proposals +sidebar_label: Governance Proposals +sidebar_position: 6 +slug: /ibc/proposals +--- + +# Governance Proposals + +In uncommon situations, a highly valued client may become frozen due to uncontrollable +circumstances. A highly valued client might have hundreds of channels being actively used. +Some of those channels might have a significant amount of locked tokens used for ICS 20. + +If the one third of the validator set of the chain the client represents decides to collude, +they can sign off on two valid but conflicting headers each signed by the other one third +of the honest validator set. The light client can now be updated with two valid, but conflicting +headers at the same height. The light client cannot know which header is trustworthy and therefore +evidence of such misbehaviour is likely to be submitted resulting in a frozen light client. + +Frozen light clients cannot be updated under any circumstance except via a governance proposal. +Since a quorum of validators can sign arbitrary state roots which may not be valid executions +of the state machine, a governance proposal has been added to ease the complexity of unfreezing +or updating clients which have become "stuck". Without this mechanism, validator sets would need +to construct a state root to unfreeze the client. Unfreezing clients, re-enables all of the channels +built upon that client. This may result in recovery of otherwise lost funds. + +Tendermint light clients may become expired if the trusting period has passed since their +last update. This may occur if relayers stop submitting headers to update the clients. + +An unplanned upgrade by the counterparty chain may also result in expired clients. If the counterparty +chain undergoes an unplanned upgrade, there may be no commitment to that upgrade signed by the validator +set before the chain-id changes. In this situation, the validator set of the last valid update for the +light client is never expected to produce another valid header since the chain-id has changed, which will +ultimately lead the on-chain light client to become expired. + +In the case that a highly valued light client is frozen, expired, or rendered non-updateable, a +governance proposal may be submitted to update this client, known as the subject client. The +proposal includes the client identifier for the subject and the client identifier for a substitute +client. Light client implementations may implement custom updating logic, but in most cases, +the subject will be updated to the latest consensus state of the substitute client, if the proposal passes. +The substitute client is used as a "stand in" while the subject is on trial. It is best practice to create +a substitute client *after* the subject has become frozen to avoid the substitute from also becoming frozen. +An active substitute client allows headers to be submitted during the voting period to prevent accidental expiry +once the proposal passes. + +*note* two of these parameters: `AllowUpdateAfterExpiry` and `AllowUpdateAfterMisbehavior` have been deprecated, and will both be set to `false` upon upgrades even if they were previously set to `true`. These parameters will no longer play a role in restricting a client upgrade. Please see ADR026 for more details. + +## How to recover an expired client with a governance proposal + +See also the relevant documentation: [ADR-026, IBC client recovery mechanisms](/architecture/adr-026-ibc-client-recovery-mechanisms) + +> **Who is this information for?** +> Although technically anyone can submit the governance proposal to recover an expired client, often it will be **relayer operators** (at least coordinating the submission). + +### Preconditions + +- The chain is updated with ibc-go >= v1.1.0. +- There exists an active client (with a known client identifier) for the same counterparty chain as the expired client. +- The governance deposit. + +## Steps + +### Step 1 + +Check if the client is attached to the expected `chain-id`. For example, for an expired Tendermint client representing the Akash chain the client state looks like this on querying the client state: + +```text +{ + client_id: 07-tendermint-146 + client_state: + '@type': /ibc.lightclients.tendermint.v1.ClientState + allow_update_after_expiry: true + allow_update_after_misbehaviour: true + chain_id: akashnet-2 +} +``` + +The client is attached to the expected Akash `chain-id`. Note that although the parameters (`allow_update_after_expiry` and `allow_update_after_misbehaviour`) exist to signal intent, these parameters have been deprecated and will not enforce any checks on the revival of client. See ADR-026 for more context on this deprecation. + +### Step 2 + +If the chain has been updated to ibc-go >= v1.1.0, anyone can submit the governance proposal to recover the client by executing this via CLI. + +> Note that the Cosmos SDK has updated how governance proposals are submitted in SDK v0.46, now requiring to pass a .json proposal file + +- From SDK v0.46.x onwards + + ```bash + tx gov submit-proposal [path-to-proposal-json] + ``` + + where `proposal.json` contains: + + ```json + { + "messages": [ + { + "@type": "/ibc.core.client.v1.ClientUpdateProposal", + "title": "title_string", + "description": "description_string", + "subject_client_id": "expired_client_id_string", + "substitute_client_id": "active_client_id_string" + } + ], + "metadata": "", + "deposit": "10stake" + } + ``` + + Alternatively there's a legacy command (that is no longer recommended though): + + ```bash + tx gov submit-legacy-proposal update-client + ``` + +- Until SDK v0.45.x + + ```bash + tx gov submit-proposal update-client + ``` + +The `` identifier is the proposed client to be updated. This client must be either frozen or expired. + +The `` represents a substitute client. It carries all the state for the client which may be updated. It must have identical client and chain parameters to the client which may be updated (except for latest height, frozen height, and chain ID). It should be continually updated during the voting period. + +After this, all that remains is deciding who funds the governance deposit and ensuring the governance proposal passes. If it does, the client on trial will be updated to the latest state of the substitute. + +## Important considerations + +Please note that from v1.0.0 of ibc-go it will not be allowed for transactions to go to expired clients anymore, so please update to at least this version to prevent similar issues in the future. + +Please also note that if the client on the other end of the transaction is also expired, that client will also need to update. This process updates only one client. diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/07-relayer.md b/docs/versioned_docs/version-v7.8.x/01-ibc/07-relayer.md new file mode 100644 index 0000000..3d63e97 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/07-relayer.md @@ -0,0 +1,53 @@ +--- +title: Relayer +sidebar_label: Relayer +sidebar_position: 7 +slug: /ibc/relayer +--- + +# Relayer + +:::note + +## Pre-requisite readings + +- [IBC Overview](01-overview.md) +- Events + +::: + +## Events + +Events are emitted for every transaction processed by the base application to indicate the execution +of some logic clients may want to be aware of. This is extremely useful when relaying IBC packets. +Any message that uses IBC will emit events for the corresponding TAO logic executed as defined in +the [IBC events document](/events/events). + +In the SDK, it can be assumed that for every message there is an event emitted with the type `message`, +attribute key `action`, and an attribute value representing the type of message sent +(`channel_open_init` would be the attribute value for `MsgChannelOpenInit`). If a relayer queries +for transaction events, it can split message events using this event Type/Attribute Key pair. + +The Event Type `message` with the Attribute Key `module` may be emitted multiple times for a single +message due to application callbacks. It can be assumed that any TAO logic executed will result in +a module event emission with the attribute value `ibc_` (02-client emits `ibc_client`). + +### Subscribing with Tendermint + +Calling the Tendermint RPC method `Subscribe` via Tendermint's Websocket will return events using +Tendermint's internal representation of them. Instead of receiving back a list of events as they +were emitted, Tendermint will return the type `map[string][]string` which maps a string in the +form `.` to `attribute_value`. This causes extraction of the event +ordering to be non-trivial, but still possible. + +A relayer should use the `message.action` key to extract the number of messages in the transaction +and the type of IBC transactions sent. For every IBC transaction within the string array for +`message.action`, the necessary information should be extracted from the other event fields. If +`send_packet` appears at index 2 in the value for `message.action`, a relayer will need to use the +value at index 2 of the key `send_packet.packet_sequence`. This process should be repeated for each +piece of information needed to relay a packet. + +## Example Implementations + +- [Golang Relayer](https://github.com/cosmos/relayer) +- [Hermes](https://github.com/informalsystems/hermes) diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/08-proto-docs.md b/docs/versioned_docs/version-v7.8.x/01-ibc/08-proto-docs.md new file mode 100644 index 0000000..52cc6b4 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/08-proto-docs.md @@ -0,0 +1,11 @@ +--- +title: Protobuf Documentation +sidebar_label: Protobuf Documentation +sidebar_position: 8 +slug: /ibc/proto-docs +--- + + +# Protobuf documentation + +See [ibc-go Buf Protobuf documentation](https://buf.build/cosmos/ibc/tags/main). diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/09-roadmap.md b/docs/versioned_docs/version-v7.8.x/01-ibc/09-roadmap.md new file mode 100644 index 0000000..e32c79c --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/09-roadmap.md @@ -0,0 +1,74 @@ +--- +title: Roadmap +sidebar_label: Roadmap +sidebar_position: 9 +slug: /roadmap/roadmap +--- + +# Roadmap ibc-go + +*Latest update: December 21st, 2022* + +This document endeavours to inform the wider IBC community about plans and priorities for work on ibc-go by the team at Interchain GmbH. It is intended to broadly inform all users of ibc-go, including developers and operators of IBC, relayer, chain and wallet applications. + +This roadmap should be read as a high-level guide, rather than a commitment to schedules and deliverables. The degree of specificity is inversely proportional to the timeline. We will update this document periodically to reflect the status and plans. For the latest expected release timelines, please check [here](https://github.com/cosmos/ibc-go/wiki/Release-timeline). + +## v7.0.0 + +### 02-client refactor + +This refactor will make the development of light clients easier. The ibc-go implementation will finally align with the spec and light clients will be required to set their own client and consensus states. This will allow more flexibility for light clients to manage their own internal storage and do batch updates. See [ADR 006](/architecture/adr-006-02-client-refactor) for more information. + +Follow the progress with the [beta](https://github.com/cosmos/ibc-go/milestone/25) and [RC](https://github.com/cosmos/ibc-go/milestone/27) milestones or in the [project board](https://github.com/orgs/cosmos/projects/7/views/14). + +### Upgrade Cosmos SDK v0.47 + +Follow the progress with the [milestone](https://github.com/cosmos/ibc-go/milestone/36). + +### Add `authz` support to 20-transfer + +Authz goes cross chain: users can grant permission for their tokens to be transferred to another chain on their behalf. See [this issue](https://github.com/cosmos/ibc-go/issues/2431) for more details. + +## v7.1.0 + +Because it is so important to have an ibc-go release compatible with the latest Cosmos SDK release, a couple of features will take a little longer and be released in [v7.1.0](https://github.com/cosmos/ibc-go/milestone/37). + +### Localhost connection + +This feature will add support for applications on a chain to communicate with applications on the same chain using the existing standard interface to communicate with applications on remote chains. This is a powerful UX improvement, particularly for those users interested in interacting with multiple smart contracts on a single chain through one interface. + +For more details, see the design proposal and discussion [here](https://github.com/cosmos/ibc-go/discussions/2191). + +A special shout out to Strangelove for their substantial contribution on this feature. + +### Support for Wasm light clients + +We will add support for Wasm light clients. The first Wasm client developed with ibc-go/v7 02-client refactor and stored as Wasm bytecode will be the GRANDPA light client used for Cosmos x Substrate IBC connections. This feature will be used also for a NEAR light client in the future. + +This feature was developed by Composable and Strangelove but will be upstreamed into ibc-go. + +## v8.0.0 + +### Channel upgradability + +Channel upgradability will allow chains to renegotiate an existing channel to take advantage of new features without having to create a new channel, thus preserving all existing packet state processed on the channel. + +Follow the progress with the [alpha milestone](https://github.com/cosmos/ibc-go/milestone/29) or the [project board](https://github.com/orgs/cosmos/projects/7/views/17). + +### Path unwinding + +This feature will allow tokens with non-native denoms to be sent back automatically to their native chains before being sent to a final destination chain. This will allow tokens to reach a final destination with the least amount possible of hops from their native chain. + +For more details, see this [discussion](https://github.com/cosmos/ibc/discussions/824). + +--- + +This roadmap is also available as a [project board](https://github.com/orgs/cosmos/projects/7/views/25). + +For the latest expected release timelines, please check [here](https://github.com/cosmos/ibc-go/wiki/Release-timeline). + +For the latest information on the progress of the work or the decisions made that might influence the roadmap, please follow our [engineering updates](https://github.com/cosmos/ibc-go/wiki/Engineering-updates). + +--- + +**Note**: release version numbers may be subject to change. diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/10-troubleshooting.md b/docs/versioned_docs/version-v7.8.x/01-ibc/10-troubleshooting.md new file mode 100644 index 0000000..ee6cbd8 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/10-troubleshooting.md @@ -0,0 +1,15 @@ +--- +title: Troubleshooting +sidebar_label: Troubleshooting +sidebar_position: 10 +slug: /ibc/troubleshooting +--- + +# Troubleshooting + +## Unauthorized client states + +If it is being reported that a client state is unauthorized, this is due to the client type not being present +in the [`AllowedClients`](https://github.com/cosmos/ibc-go/blob/v6.0.0/modules/core/02-client/types/client.pb.go#L345) array. + +Unless the client type is present in this array, all usage of clients of this type will be prevented. diff --git a/docs/versioned_docs/version-v7.8.x/01-ibc/_category_.json b/docs/versioned_docs/version-v7.8.x/01-ibc/_category_.json new file mode 100644 index 0000000..afc6d18 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/01-ibc/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Using IBC-Go", + "position": 1, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/01-overview.md b/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/01-overview.md new file mode 100644 index 0000000..ed696b3 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/01-overview.md @@ -0,0 +1,128 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /apps/transfer/overview +--- + +# Overview + +:::note Synopsis +Learn about what the token Transfer module is +::: + +## What is the Transfer module? + +Transfer is the Cosmos SDK implementation of the [ICS-20](https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer) protocol, which enables cross-chain fungible token transfers. + +## Concepts + +### Acknowledgements + +ICS20 uses the recommended acknowledgement format as specified by [ICS 04](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope). + +A successful receive of a transfer packet will result in a Result Acknowledgement being written +with the value `[]byte{byte(1)}` in the `Response` field. + +An unsuccessful receive of a transfer packet will result in an Error Acknowledgement being written +with the error message in the `Response` field. + +### Denomination trace + +The denomination trace corresponds to the information that allows a token to be traced back to its +origin chain. It contains a sequence of port and channel identifiers ordered from the most recent to +the oldest in the timeline of transfers. + +This information is included on the token denomination field in the form of a hash to prevent an +unbounded denomination length. For example, the token `transfer/channelToA/uatom` will be displayed +as `ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2`. + +Each send to any chain other than the one it was previously received from is a movement forwards in +the token's timeline. This causes trace to be added to the token's history and the destination port +and destination channel to be prefixed to the denomination. In these instances the sender chain is +acting as the "source zone". When the token is sent back to the chain it previously received from, the +prefix is removed. This is a backwards movement in the token's timeline and the sender chain is +acting as the "sink zone". + +It is strongly recommended to read the full details of [ADR 001: Coin Source Tracing](/architecture/adr-001-coin-source-tracing) to understand the implications and context of the IBC token representations. + +## UX suggestions for clients + +For clients (wallets, exchanges, applications, block explorers, etc) that want to display the source of the token, it is recommended to use the following alternatives for each of the cases below: + +### Direct connection + +If the denomination trace contains a single identifier prefix pair (as in the example above), then +the easiest way to retrieve the chain and light client identifier is to map the trace information +directly. In summary, this requires querying the channel from the denomination trace identifiers, +and then the counterparty client state using the counterparty port and channel identifiers from the +retrieved channel. + +A general pseudo algorithm would look like the following: + +1. Query the full denomination trace. +2. Query the channel with the `portID/channelID` pair, which corresponds to the first destination of the + token. +3. Query the client state using the identifiers pair. Note that this query will return a `"Not +Found"` response if the current chain is not connected to this channel. +4. Retrieve the client identifier or chain identifier from the client state (eg: on + Tendermint clients) and store it locally. + +Using the gRPC gateway client service the steps above would be, with a given IBC token `ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2` stored on `chainB`: + +1. `GET /ibc/apps/transfer/v1/denom_traces/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2` -> `{"path": "transfer/channelToA", "base_denom": "uatom"}` +2. `GET /ibc/apps/transfer/v1/channels/channelToA/ports/transfer/client_state"` -> `{"client_id": "clientA", "chain-id": "chainA", ...}` +3. `GET /ibc/apps/transfer/v1/channels/channelToA/ports/transfer"` -> `{"channel_id": "channelToA", port_id": "transfer", counterparty: {"channel_id": "channelToB", port_id": "transfer"}, ...}` +4. `GET /ibc/apps/transfer/v1/channels/channelToB/ports/transfer/client_state" -> {"client_id": "clientB", "chain-id": "chainB", ...}` + +Then, the token transfer chain path for the `uatom` denomination would be: `chainA` -> `chainB`. + +### Multiple hops + +The multiple channel hops case applies when the token has passed through multiple chains between the original source and final destination chains. + +The IBC protocol doesn't know the topology of the overall network (i.e connections between chains and identifier names between them). For this reason, in the multiple hops case, a particular chain in the timeline of the individual transfers can't query the chain and client identifiers of the other chains. + +Take for example the following sequence of transfers `A -> B -> C` for an IBC token, with a final prefix path (trace info) of `transfer/channelChainC/transfer/channelChainB`. What the paragraph above means is that even in the case that chain `C` is directly connected to chain `A`, querying the port and channel identifiers that chain `B` uses to connect to chain `A` (eg: `transfer/channelChainA`) can be completely different from the one that chain `C` uses to connect to chain `A` (eg: `transfer/channelToChainA`). + +Thus the proposed solution for clients that the IBC team recommends are the following: + +- **Connect to all chains**: Connecting to all the chains in the timeline would allow clients to + perform the queries outlined in the [direct connection](#direct-connection) section to each + relevant chain. By repeatedly following the port and channel denomination trace transfer timeline, + clients should always be able to find all the relevant identifiers. This comes at the tradeoff + that the client must connect to nodes on each of the chains in order to perform the queries. +- **Relayer as a Service (RaaS)**: A longer term solution is to use/create a relayer service that + could map the denomination trace to the chain path timeline for each token (i.e `origin chain -> +chain #1 -> ... -> chain #(n-1) -> final chain`). These services could provide merkle proofs in + order to allow clients to optionally verify the path timeline correctness for themselves by + running light clients. If the proofs are not verified, they should be considered as trusted third + parties services. Additionally, client would be advised in the future to use RaaS that support the + largest number of connections between chains in the ecosystem. Unfortunately, none of the existing + public relayers (in [Golang](https://github.com/cosmos/relayer) and + [Rust](https://github.com/informalsystems/ibc-rs)), provide this service to clients. + +:::tip +The only viable alternative for clients (at the time of writing) to tokens with multiple connection hops, is to connect to all chains directly and perform relevant queries to each of them in the sequence. +::: + +## Locked funds + +In some [exceptional cases](/architecture/adr-026-ibc-client-recovery-mechanisms#exceptional-cases), a client state associated with a given channel cannot be updated. This causes that funds from fungible tokens in that channel will be permanently locked and thus can no longer be transferred. + +To mitigate this, a client update governance proposal can be submitted to update the frozen client +with a new valid header. Once the proposal passes the client state will be unfrozen and the funds +from the associated channels will then be unlocked. This mechanism only applies to clients that +allow updates via governance, such as Tendermint clients. + +In addition to this, it's important to mention that a token must be sent back along the exact route +that it took originally in order to return it to its original form on the source chain (eg: the +Cosmos Hub for the `uatom`). Sending a token back to the same chain across a different channel will +**not** move the token back across its timeline. If a channel in the chain history closes before the +token can be sent back across that channel, then the token will not be returnable to its original +form. + +## Security considerations + +For safety, no other module must be capable of minting tokens with the `ibc/` prefix. The IBC +transfer module needs a subset of the denomination space that only it can create tokens in. diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/02-state.md b/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/02-state.md new file mode 100644 index 0000000..17f48a2 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/02-state.md @@ -0,0 +1,13 @@ +--- +title: State +sidebar_label: State +sidebar_position: 2 +slug: /apps/transfer/state +--- + +# State + +The IBC transfer application module keeps state of the port to which the module is binded and the denomination trace information as outlined in [ADR 001](/architecture/adr-001-coin-source-tracing). + +- `Port`: `0x01 -> ProtocolBuffer(string)` +- `DenomTrace`: `0x02 | []bytes(traceHash) -> ProtocolBuffer(DenomTrace)` diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/03-state-transitions.md b/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/03-state-transitions.md new file mode 100644 index 0000000..23bf1bf --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/03-state-transitions.md @@ -0,0 +1,37 @@ +--- +title: State Transitions +sidebar_label: State Transitions +sidebar_position: 3 +slug: /apps/transfer/state-transitions +--- + +# State transitions + +## Send fungible tokens + +A successful fungible token send has two state transitions depending if the transfer is a movement forward or backwards in the token's timeline: + +1. Sender chain is the source chain, *i.e* a transfer to any chain other than the one it was previously received from is a movement forwards in the token's timeline. This results in the following state transitions: + + - The coins are transferred to an escrow address (i.e locked) on the sender chain. + - The coins are transferred to the receiving chain through IBC TAO logic. + +2. Sender chain is the sink chain, *i.e* the token is sent back to the chain it previously received from. This is a backwards movement in the token's timeline. This results in the following state transitions: + + - The coins (vouchers) are burned on the sender chain. + - The coins are transferred to the receiving chain through IBC TAO logic. + +## Receive fungible tokens + +A successful fungible token receive has two state transitions depending if the transfer is a movement forward or backwards in the token's timeline: + +1. Receiver chain is the source chain. This is a backwards movement in the token's timeline. This results in the following state transitions: + + - The leftmost port and channel identifier pair is removed from the token denomination prefix. + - The tokens are unescrowed and sent to the receiving address. + +2. Receiver chain is the sink chain. This is a movement forwards in the token's timeline. This results in the following state transitions: + + - Token vouchers are minted by prefixing the destination port and channel identifiers to the trace information. + - The receiving chain stores the new trace information in the store (if not set already). + - The vouchers are sent to the receiving address. diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/04-messages.md b/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/04-messages.md new file mode 100644 index 0000000..ebf6854 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/04-messages.md @@ -0,0 +1,61 @@ +--- +title: Messages +sidebar_label: Messages +sidebar_position: 4 +slug: /apps/transfer/messages +--- + +# Messages + +## `MsgTransfer` + +A fungible token cross chain transfer is achieved by using the `MsgTransfer`: + +```go +type MsgTransfer struct { + SourcePort string + SourceChannel string + Token sdk.Coin + Sender string + Receiver string + TimeoutHeight ibcexported.Height + TimeoutTimestamp uint64 + Memo string +} +``` + +This message is expected to fail if: + +- `SourcePort` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators). +- `SourceChannel` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +- `Token` is invalid (denom is invalid or amount is negative) + - `Token.Amount` is not positive. + - `Token.Denom` is not a valid IBC denomination as per [ADR 001 - Coin Source Tracing](/architecture/adr-001-coin-source-tracing). +- `Sender` is empty. +- `Receiver` is empty or contains more than 2048 bytes. +- `Memo` contains more than 32768 bytes. +- `TimeoutHeight` and `TimeoutTimestamp` are both zero. + +This message will send a fungible token to the counterparty chain represented by the counterparty Channel End connected to the Channel End with the identifiers `SourcePort` and `SourceChannel`. + +The denomination provided for transfer should correspond to the same denomination represented on this chain. The prefixes will be added as necessary upon by the receiving chain. + +If the `Amount` is set to the maximum value for a 256-bit unsigned integer (i.e. 2^256 - 1), then the whole balance of the corresponding denomination will be transferred. The helper function `UnboundedSpendLimit` in the `types` package of the `transfer` module provides the sentinel value that can be used. + +### Memo + +The memo field was added to allow applications and users to attach metadata to transfer packets. The field is optional and may be left empty. When it is used to attach metadata for a particular middleware, the memo field should be represented as a json object where different middlewares use different json keys. + +For example, the following memo field is used by the [callbacks middleware](../../04-middleware/02-callbacks/01-overview.md) to attach a source callback to a transfer packet: + +```jsonc +{ + "src_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} +``` + +You can find more information about other applications that use the memo field in the [chain registry](https://github.com/cosmos/chain-registry/blob/master/_memo_keys/ICS20_memo_keys.json). diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/05-events.md b/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/05-events.md new file mode 100644 index 0000000..b4ee2f4 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/05-events.md @@ -0,0 +1,58 @@ +--- +title: Events +sidebar_label: Events +sidebar_position: 5 +slug: /apps/transfer/events +--- + + +# Events + +## `MsgTransfer` + +| Type | Attribute Key | Attribute Value | +|--------------|---------------|-----------------| +| ibc_transfer | sender | \{sender\} | +| ibc_transfer | receiver | \{receiver\} | +| ibc_transfer | amount | \{amount\} | +| ibc_transfer | denom | \{denom\} | +| ibc_transfer | memo | \{memo\} | +| message | module | transfer | + +## `OnRecvPacket` callback + +| Type | Attribute Key | Attribute Value | +|-----------------------|---------------|-----------------| +| fungible_token_packet | module | transfer | +| fungible_token_packet | sender | \{sender\} | +| fungible_token_packet | receiver | \{receiver\} | +| fungible_token_packet | denom | \{denom\} | +| fungible_token_packet | amount | \{amount\} | +| fungible_token_packet | memo | \{memo\} | +| fungible_token_packet | success | \{ackSuccess\} | +| fungible_token_packet | error | \{ackError\} | +| denomination_trace | trace_hash | \{hex_hash\} | +| denomination_trace | denom | \{voucherDenom\}| + +## `OnAcknowledgePacket` callback + +| Type | Attribute Key | Attribute Value | +|-----------------------|-----------------|------------------| +| fungible_token_packet | module | transfer | +| fungible_token_packet | sender | \{sender\} | +| fungible_token_packet | receiver | \{receiver\} | +| fungible_token_packet | denom | \{denom\} | +| fungible_token_packet | amount | \{amount\} | +| fungible_token_packet | memo | \{memo\} | +| fungible_token_packet | acknowledgement | \{ack.String()\} | +| fungible_token_packet | success / error | \{ack.Response\} | + +## `OnTimeoutPacket` callback + +| Type | Attribute Key | Attribute Value | +|-----------------------|-----------------|-----------------| +| fungible_token_packet | module | transfer | +| fungible_token_packet | refund_receiver | \{receiver\} | +| fungible_token_packet | denom | \{denom\} | +| fungible_token_packet | amount | \{amount\} | +| fungible_token_packet | memo | \{memo\} | diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/06-metrics.md b/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/06-metrics.md new file mode 100644 index 0000000..ae4906e --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/06-metrics.md @@ -0,0 +1,18 @@ +--- +title: Metrics +sidebar_label: Metrics +sidebar_position: 6 +slug: /apps/transfer/metrics +--- + + +# Metrics + +The IBC transfer application module exposes the following set of [metrics](https://docs.cosmos.network/main/learn/advanced/telemetry). + +| Metric | Description | Unit | Type | +|:--------------------------------|:------------------------------------------------------------------------------------------|:----------------|:--------| +| `tx_msg_ibc_transfer` | The total amount of tokens transferred via IBC in a `MsgTransfer` (source or sink chain) | token | gauge | +| `ibc_transfer_packet_receive` | The total amount of tokens received in a `FungibleTokenPacketData` (source or sink chain) | token | gauge | +| `ibc_transfer_send` | Total number of IBC transfers sent from a chain (source or sink) | transfer | counter | +| `ibc_transfer_receive` | Total number of IBC transfers received to a chain (source or sink) | transfer | counter | diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/07-params.md b/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/07-params.md new file mode 100644 index 0000000..c4c9330 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/07-params.md @@ -0,0 +1,34 @@ +--- +title: Params +sidebar_label: Params +sidebar_position: 7 +slug: /apps/transfer/params +--- + + +# Parameters + +The IBC transfer application module contains the following parameters: + +| Key | Type | Default Value | +|------------------|------|---------------| +| `SendEnabled` | bool | `true` | +| `ReceiveEnabled` | bool | `true` | + +## `SendEnabled` + +The transfers enabled parameter controls send cross-chain transfer capabilities for all fungible tokens. + +To prevent a single token from being transferred from the chain, set the `SendEnabled` parameter to `true` and then, depending on the Cosmos SDK version, do one of the following: + +- For Cosmos SDK v0.46.x or earlier, set the bank module's [`SendEnabled` parameter](https://github.com/cosmos/cosmos-sdk/blob/release/v0.46.x/x/bank/spec/05_params.md#sendenabled) for the denomination to `false`. +- For Cosmos SDK versions above v0.46.x, set the bank module's `SendEnabled` entry for the denomination to `false` using `MsgSetSendEnabled` as a governance proposal. + +## `ReceiveEnabled` + +The transfers enabled parameter controls receive cross-chain transfer capabilities for all fungible tokens. + +To prevent a single token from being transferred to the chain, set the `ReceiveEnabled` parameter to `true` and then, depending on the Cosmos SDK version, do one of the following: + +- For Cosmos SDK v0.46.x or earlier, set the bank module's [`SendEnabled` parameter](https://github.com/cosmos/cosmos-sdk/blob/release/v0.46.x/x/bank/spec/05_params.md#sendenabled) for the denomination to `false`. +- For Cosmos SDK versions above v0.46.x, set the bank module's `SendEnabled` entry for the denomination to `false` using `MsgSetSendEnabled` as a governance proposal. diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/08-authorizations.md b/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/08-authorizations.md new file mode 100644 index 0000000..fa8b480 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/08-authorizations.md @@ -0,0 +1,57 @@ +--- +title: Authorizations +sidebar_label: Authorizations +sidebar_position: 8 +slug: /apps/transfer/authorizations +--- + + +# `TransferAuthorization` + +`TransferAuthorization` implements the `Authorization` interface for `ibc.applications.transfer.v1.MsgTransfer`. It allows a granter to grant a grantee the privilege to submit `MsgTransfer` on its behalf. Please see the [Cosmos SDK docs](https://docs.cosmos.network/v0.47/modules/authz) for more details on granting privileges via the `x/authz` module. + +More specifically, the granter allows the grantee to transfer funds that belong to the granter over a specified channel. + +For the specified channel, the granter must be able to specify a spend limit of a specific denomination they wish to allow the grantee to be able to transfer. + +The granter may be able to specify the list of addresses that they allow to receive funds. If empty, then all addresses are allowed. + +It takes: + +- a `SourcePort` and a `SourceChannel` which together comprise the unique transfer channel identifier over which authorized funds can be transferred. + +- a `SpendLimit` that specifies the maximum amount of tokens the grantee can transfer. The `SpendLimit` is updated as the tokens are transferred, unless the sentinel value of the maximum value for a 256-bit unsigned integer (i.e. 2^256 - 1) is used for the amount, in which case the `SpendLimit` will not be updated (please be aware that using this sentinel value will grant the grantee the privilege to transfer **all** the tokens of a given denomination available at the granter's account). The helper function `UnboundedSpendLimit` in the `types` package of the `transfer` module provides the sentinel value that can be used. This `SpendLimit` may also be updated to increase or decrease the limit as the granter wishes. + +- an `AllowList` list that specifies the list of addresses that are allowed to receive funds. If this list is empty, then all addresses are allowed to receive funds from the `TransferAuthorization`. + +- an `AllowedPacketData` list that specifies the list of memo strings that are allowed to be included in the memo field of the packet. If this list is empty, then only an empty memo is allowed (a `memo` field with non-empty content will be denied). If this list includes a single element equal to `"*"`, then any content in the `memo` field will be allowed. + +Setting a `TransferAuthorization` is expected to fail if: + +- the spend limit is nil +- the denomination of the spend limit is an invalid coin type +- the source port ID is invalid +- the source channel ID is invalid +- there are duplicate entries in the `AllowList` + +Below is the `TransferAuthorization` message: + +```go +func NewTransferAuthorization(allocations ...Allocation) *TransferAuthorization { + return &TransferAuthorization{ + Allocations: allocations, + } +} + +type Allocation struct { + // the port on which the packet will be sent + SourcePort string + // the channel by which the packet will be sent + SourceChannel string + // spend limitation on the channel + SpendLimit sdk.Coins + // allow list of receivers, an empty allow list permits any receiver address + AllowList []string +} + +``` diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/09-client.md b/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/09-client.md new file mode 100644 index 0000000..a3d21f1 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/09-client.md @@ -0,0 +1,70 @@ +--- +title: Client +sidebar_label: Client +sidebar_position: 9 +slug: /apps/transfer/client +--- + + +# Client + +## CLI + +A user can query and interact with the `transfer` module using the CLI. Use the `--help` flag to discover the available commands: + +### Query + +The `query` commands allow users to query `transfer` state. + +```shell +simd query ibc-transfer --help +``` + +#### `total-escrow` + +The `total-escrow` command allows users to query the total amount in escrow for a particular coin denomination regardless of the transfer channel from where the coins were sent out. + +```shell +simd query ibc-transfer total-escrow [denom] [flags] +``` + +Example: + +```shell +simd query ibc-transfer total-escrow samoleans +``` + +Example Output: + +```shell +amount: "100" +``` + +## gRPC + +A user can query the `transfer` module using gRPC endpoints. + +### `TotalEscrowForDenom` + +The `TotalEscrowForDenom` endpoint allows users to query the total amount in escrow for a particular coin denomination regardless of the transfer channel from where the coins were sent out. + +```shell +ibc.applications.transfer.v1.Query/TotalEscrowForDenom +``` + +Example: + +```shell +grpcurl -plaintext \ + -d '{"denom":"samoleans"}' \ + localhost:9090 \ + ibc.applications.transfer.v1.Query/TotalEscrowForDenom +``` + +Example output: + +```shell +{ + "amount": "100" +} +``` diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/_category_.json b/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/_category_.json new file mode 100644 index 0000000..50d492b --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/01-transfer/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Transfer", + "position": 1, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/01-overview.md b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/01-overview.md new file mode 100644 index 0000000..26a5f53 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/01-overview.md @@ -0,0 +1,39 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /apps/interchain-accounts/overview +--- + + +# Overview + +:::note Synopsis +Learn about what the Interchain Accounts module is +::: + +## What is the Interchain Accounts module? + +Interchain Accounts is the Cosmos SDK implementation of the ICS-27 protocol, which enables cross-chain account management built upon IBC. + +- How does an interchain account differ from a regular account? + +Regular accounts use a private key to sign transactions. Interchain Accounts are instead controlled programmatically by counterparty chains via IBC packets. + +## Concepts + +`Host Chain`: The chain where the interchain account is registered. The host chain listens for IBC packets from a controller chain which should contain instructions (e.g. Cosmos SDK messages) for which the interchain account will execute. + +`Controller Chain`: The chain registering and controlling an account on a host chain. The controller chain sends IBC packets to the host chain to control the account. + +`Interchain Account`: An account on a host chain created using the ICS-27 protocol. An interchain account has all the capabilities of a normal account. However, rather than signing transactions with a private key, a controller chain will send IBC packets to the host chain which signals what transactions the interchain account should execute. + +`Authentication Module`: A custom application module on the controller chain that uses the Interchain Accounts module to build custom logic for the creation & management of interchain accounts. It can be either an IBC application module using the [legacy API](10-legacy/03-keeper-api.md), or a regular Cosmos SDK application module sending messages to the controller submodule's `MsgServer` (this is the recommended approach from ibc-go v6 if access to packet callbacks is not needed). Please note that the legacy API will eventually be removed and IBC applications will not be able to use them in later releases. + +## SDK security model + +SDK modules on a chain are assumed to be trustworthy. For example, there are no checks to prevent an untrustworthy module from accessing the bank keeper. + +The implementation of ICS-27 in ibc-go uses this assumption in its security considerations. + +The implementation assumes other IBC application modules will not bind to ports within the ICS-27 namespace. diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/02-development.md b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/02-development.md new file mode 100644 index 0000000..d5c507f --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/02-development.md @@ -0,0 +1,40 @@ +--- +title: Development Use Cases +sidebar_label: Development Use Cases +sidebar_position: 2 +slug: /apps/interchain-accounts/development +--- + + +# Development use cases + +The initial version of Interchain Accounts allowed for the controller submodule to be extended by providing it with an underlying application which would handle all packet callbacks. +That functionality is now being deprecated in favor of alternative approaches. +This document will outline potential use cases and redirect each use case to the appropriate documentation. + +## Custom authentication + +Interchain accounts may be associated with alternative types of authentication relative to the traditional public/private key signing. +If you wish to develop or use Interchain Accounts with a custom authentication module and do not need to execute custom logic on the packet callbacks, we recommend you use ibc-go v6 or greater and that your custom authentication module interacts with the controller submodule via the [`MsgServer`](05-messages.md). + +If you wish to consume and execute custom logic in the packet callbacks, then please read the section [Packet callbacks](#packet-callbacks) below. + +## Redirection to a smart contract + +It may be desirable to allow smart contracts to control an interchain account. +To facilitate such an action, the controller submodule may be provided an underlying application which redirects to smart contract callers. +An improved design has been suggested in [ADR 008](https://github.com/cosmos/ibc-go/pull/1976) which performs this action via middleware. + +Implementers of this use case are recommended to follow the ADR 008 approach. +The underlying application may continue to be used as a short term solution for ADR 008 and the [legacy API](./10-legacy/01-auth-modules.md) should continue to be utilized in such situations. + +## Packet callbacks + +If a developer requires access to packet callbacks for their use case, then they have the following options: + +1. Write a smart contract which is connected via an ADR 008 or equivalent IBC application (recommended). +2. Use the controller's underlying application to implement packet callback logic. + +In the first case, the smart contract should use the [`MsgServer`](05-messages.md). + +In the second case, the underlying application should use the [legacy API](10-legacy/03-keeper-api.md). diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/03-auth-modules.md b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/03-auth-modules.md new file mode 100644 index 0000000..3f82bce --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/03-auth-modules.md @@ -0,0 +1,27 @@ +--- +title: Authentication Modules +sidebar_label: Authentication Modules +sidebar_position: 3 +slug: /apps/interchain-accounts/auth-modules +--- + + +# Building an authentication module + +:::note Synopsis +Authentication modules enable application developers to perform custom logic when interacting with the Interchain Accounts controller sumbmodule's `MsgServer`. +::: + +The controller submodule is used for account registration and packet sending. It executes only logic required of all controllers of interchain accounts. The type of authentication used to manage the interchain accounts remains unspecified. There may exist many different types of authentication which are desirable for different use cases. Thus the purpose of the authentication module is to wrap the controller submodule with custom authentication logic. + +In ibc-go, authentication modules can communicate with the controller submodule by passing messages through `baseapp`'s `MsgServiceRouter`. To implement an authentication module, the `IBCModule` interface need not be fulfilled; it is only required to fulfill Cosmos SDK's `AppModuleBasic` interface, just like any regular Cosmos SDK application module. + +The authentication module must: + +- Authenticate interchain account owners. +- Track the associated interchain account address for an owner. +- Send packets on behalf of an owner (after authentication). + +## Integration into `app.go` file + +To integrate the authentication module into your chain, please follow the steps outlined in [`app.go` integration](04-integration.md#example-integration). diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/04-integration.md b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/04-integration.md new file mode 100644 index 0000000..bb110d0 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/04-integration.md @@ -0,0 +1,202 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 4 +slug: /apps/interchain-accounts/integration +--- + + +# Integration + +:::note Synopsis +Learn how to integrate Interchain Accounts host and controller functionality to your chain. The following document only applies for Cosmos SDK chains. +::: + +The Interchain Accounts module contains two submodules. Each submodule has its own IBC application. The Interchain Accounts module should be registered as an `AppModule` in the same way all SDK modules are registered on a chain, but each submodule should create its own `IBCModule` as necessary. A route should be added to the IBC router for each submodule which will be used. + +Chains who wish to support ICS-27 may elect to act as a host chain, a controller chain or both. Disabling host or controller functionality may be done statically by excluding the host or controller submodule entirely from the `app.go` file or it may be done dynamically by taking advantage of the on-chain parameters which enable or disable the host or controller submodules. + +Interchain Account authentication modules (both custom or generic, such as the `x/gov`, `x/group` or `x/auth` Cosmos SDK modules) can send messages to the controller submodule's [`MsgServer`](05-messages.md) to register interchain accounts and send packets to the interchain account. To accomplish this, the authentication module needs to be composed with `baseapp`'s `MsgServiceRouter`. + +![ica-v6.png](./images/ica-v6.png) + +> Please note that since ibc-go v7.5.0 it is mandatory to register the gRPC query router after the creation of the host submodule's keeper; otherwise, nodes will not start. The query router is used to execute on the host query messages encoded in the ICA packet data. Please check the sample integration code below for more details. + +## Example integration + +```go +// app.go + +// Register the AppModule for the Interchain Accounts module and the authentication module +// Note: No `icaauth` exists, this must be substituted with an actual Interchain Accounts authentication module +ModuleBasics = module.NewBasicManager( + ... + ica.AppModuleBasic{}, + icaauth.AppModuleBasic{}, + ... +) + +... + +// Add module account permissions for the Interchain Accounts module +// Only necessary for host chain functionality +// Each Interchain Account created on the host chain is derived from the module account created +maccPerms = map[string][]string{ + ... + icatypes.ModuleName: nil, +} + +... + +// Add Interchain Accounts Keepers for each submodule used and the authentication module +// If a submodule is being statically disabled, the associated Keeper does not need to be added. +type App struct { + ... + + ICAControllerKeeper icacontrollerkeeper.Keeper + ICAHostKeeper icahostkeeper.Keeper + ICAAuthKeeper icaauthkeeper.Keeper + + ... +} + +... + +// Create store keys for each submodule Keeper and the authentication module +keys := sdk.NewKVStoreKeys( + ... + icacontrollertypes.StoreKey, + icahosttypes.StoreKey, + icaauthtypes.StoreKey, + ... +) + +... + +// Create the scoped keepers for each submodule keeper and authentication keeper +scopedICAControllerKeeper := app.CapabilityKeeper.ScopeToModule(icacontrollertypes.SubModuleName) +scopedICAHostKeeper := app.CapabilityKeeper.ScopeToModule(icahosttypes.SubModuleName) +scopedICAAuthKeeper := app.CapabilityKeeper.ScopeToModule(icaauthtypes.ModuleName) + +... + +// Create the Keeper for each submodule +app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + scopedICAControllerKeeper, app.MsgServiceRouter(), +) +app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.AccountKeeper, scopedICAHostKeeper, app.MsgServiceRouter(), +) +app.ICAHostKeeper.WithQueryRouter(app.GRPCQueryRouter()) + +// Create Interchain Accounts AppModule +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper) + +// Create your Interchain Accounts authentication module +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.MsgServiceRouter()) + +// ICA auth AppModule +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) + +// Create controller IBC application stack and host IBC module as desired +icaControllerStack := icacontroller.NewIBCMiddleware(nil, app.ICAControllerKeeper) +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host and authentication routes +ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icahosttypes.SubModuleName, icaHostIBCModule) +... + +// Register Interchain Accounts and authentication module AppModule's +app.moduleManager = module.NewManager( + ... + icaModule, + icaAuthModule, +) + +... + +// Add Interchain Accounts to begin blocker logic +app.moduleManager.SetOrderBeginBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add Interchain Accounts to end blocker logic +app.moduleManager.SetOrderEndBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add Interchain Accounts module InitGenesis logic +app.moduleManager.SetOrderInitGenesis( + ... + icatypes.ModuleName, + ... +) + +// initParamsKeeper init params keeper and its subspaces +func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey sdk.StoreKey) paramskeeper.Keeper { + ... + paramsKeeper.Subspace(icahosttypes.SubModuleName) + paramsKeeper.Subspace(icacontrollertypes.SubModuleName) + ... +} +``` + +If no custom athentication module is needed and a generic Cosmos SDK authentication module can be used, then from the sample integration code above all references to `ICAAuthKeeper` and `icaAuthModule` can be removed. That's it, the following code would not be needed: + +```go +// Create your Interchain Accounts authentication module +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.MsgServiceRouter()) + +// ICA auth AppModule +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) +``` + +### Using submodules exclusively + +As described above, the Interchain Accounts application module is structured to support the ability of exclusively enabling controller or host functionality. +This can be achieved by simply omitting either controller or host `Keeper` from the Interchain Accounts `NewAppModule` constructor function, and mounting only the desired submodule via the `IBCRouter`. +Alternatively, submodules can be enabled and disabled dynamically using [on-chain parameters](06-parameters.md). + +The following snippets show basic examples of statically disabling submodules using `app.go`. + +#### Disabling controller chain functionality + +```go +// Create Interchain Accounts AppModule omitting the controller keeper +icaModule := ica.NewAppModule(nil, &app.ICAHostKeeper) + +// Create host IBC Module +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host route +ibcRouter.AddRoute(icahosttypes.SubModuleName, icaHostIBCModule) +``` + +#### Disabling host chain functionality + +```go +// Create Interchain Accounts AppModule omitting the host keeper +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, nil) + + +// Optionally instantiate your custom authentication module if needed, or not otherwise +... + +// Create controller IBC application stack +icaControllerStack := icacontroller.NewIBCMiddleware(nil, app.ICAControllerKeeper) + +// Register controller route +ibcRouter.AddRoute(icacontrollertypes.SubModuleName, icaControllerStack) +``` diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/05-messages.md b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/05-messages.md new file mode 100644 index 0000000..b766162 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/05-messages.md @@ -0,0 +1,143 @@ +--- +title: Messages +sidebar_label: Messages +sidebar_position: 5 +slug: /apps/interchain-accounts/messages +--- + + +# Messages + +## `MsgRegisterInterchainAccount` + +An Interchain Accounts channel handshake can be initiated using `MsgRegisterInterchainAccount`: + +```go +type MsgRegisterInterchainAccount struct { + Owner string + ConnectionID string + Version string + Ordering channeltypes.Order +} +``` + +This message is expected to fail if: + +- `Owner` is an empty string or contains more than 2048 bytes. +- `ConnectionID` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). + +This message will construct a new `MsgChannelOpenInit` on chain and route it to the core IBC message server to initiate the opening step of the channel handshake. + +The controller submodule will generate a new port identifier and claim the associated port capability. The caller is expected to provide an appropriate application version string. For example, this may be an ICS-27 JSON encoded [`Metadata`](https://github.com/cosmos/ibc-go/blob/v6.0.0/proto/ibc/applications/interchain_accounts/v1/metadata.proto#L11) type or an ICS-29 JSON encoded [`Metadata`](https://github.com/cosmos/ibc-go/blob/v6.0.0/proto/ibc/applications/fee/v1/metadata.proto#L11) type with a nested application version. +If the `Version` string is omitted, the controller submodule will construct a default version string in the `OnChanOpenInit` handshake callback. + +```go +type MsgRegisterInterchainAccountResponse struct { + ChannelID string + PortID string +} +``` + +The `ChannelID` and `PortID` are returned in the message response. + +## `MsgSendTx` + +An Interchain Accounts transaction can be executed on a remote host chain by sending a `MsgSendTx` from the corresponding controller chain: + +```go +type MsgSendTx struct { + Owner string + ConnectionID string + PacketData InterchainAccountPacketData + RelativeTimeout uint64 +} +``` + +This message is expected to fail if: + +- `Owner` is an empty string or contains more than 2048 bytes. +- `ConnectionID` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +- `PacketData` contains an `UNSPECIFIED` type enum, the length of `Data` bytes is zero or the `Memo` field exceeds 256 characters in length. +- `RelativeTimeout` is zero. + +This message will create a new IBC packet with the provided `PacketData` and send it via the channel associated with the `Owner` and `ConnectionID`. +The `PacketData` is expected to contain a list of serialized `[]sdk.Msg` in the form of `CosmosTx`. Please note the signer field of each `sdk.Msg` must be the interchain account address. +When the packet is relayed to the host chain, the `PacketData` is unmarshalled and the messages are authenticated and executed. + +```go +type MsgSendTxResponse struct { + Sequence uint64 +} +``` + +The packet `Sequence` is returned in the message response. + +### Queries + +It is possible to use [`MsgModuleQuerySafe`](https://github.com/cosmos/ibc-go/blob/v7.5.0/proto/ibc/applications/interchain_accounts/host/v1/tx.proto#L32-L39) to execute a list of queries on the host chain. This message can be included in the list of encoded `sdk.Msg`s of `InterchainPacketData`. The host chain will return on the acknowledgment the responses for all the queries. Please note that only module safe queries can be executed ([deterministic queries that are safe to be called from within the state machine](https://docs.cosmos.network/main/build/building-modules/query-services#calling-queries-from-the-state-machine)). + +The queries available from Cosmos SDK are: + +```plaintext +/cosmos.staking.v1beta1.Query/Validators, +/cosmos.staking.v1beta1.Query/Validator, +/cosmos.staking.v1beta1.Query/ValidatorDelegations", +/cosmos.staking.v1beta1.Query/ValidatorUnbondingDelegations +/cosmos.staking.v1beta1.Query/Delegation +/cosmos.staking.v1beta1.Query/UnbondingDelegation +/cosmos.staking.v1beta1.Query/DelegatorDelegations +/cosmos.staking.v1beta1.Query/DelegatorUnbondingDelegations +/cosmos.staking.v1beta1.Query/Redelegations +/cosmos.staking.v1beta1.Query/DelegatorValidators +/cosmos.staking.v1beta1.Query/DelegatorValidator +/cosmos.staking.v1beta1.Query/HistoricalInfo +/cosmos.staking.v1beta1.Query/Pool +/cosmos.staking.v1beta1.Query/Params +/cosmos.bank.v1beta1.Query/Balance +/cosmos.bank.v1beta1.Query/AllBalances +/cosmos.bank.v1beta1.Query/SpendableBalances +/cosmos.bank.v1beta1.Query/SpendableBalanceByDenom +/cosmos.bank.v1beta1.Query/TotalSupply +/cosmos.bank.v1beta1.Query/SupplyOf +/cosmos.bank.v1beta1.Query/Params +/cosmos.bank.v1beta1.Query/DenomMetadata +/cosmos.bank.v1beta1.Query/DenomsMetadata +/cosmos.bank.v1beta1.Query/DenomOwners +/cosmos.bank.v1beta1.Query/SendEnabled +/cosmos.auth.v1beta1.Query/Accounts +/cosmos.auth.v1beta1.Query/Account +/cosmos.auth.v1beta1.Query/AccountAddressByID +/cosmos.auth.v1beta1.Query/Params +/cosmos.auth.v1beta1.Query/ModuleAccounts +/cosmos.auth.v1beta1.Query/ModuleAccountByName +/cosmos.auth.v1beta1.Query/AccountInfo +``` + +The following code block shows an example of how `MsgModuleQuerySafe` can be used to query the account balance of an account on the host chain. The resulting packet data variable is used to set the `PacketData` of `MsgSendTx`. + +```go +balanceQuery := banktypes.NewQueryBalanceRequest("cosmos1...", "uatom") +queryBz, err := balanceQuery.Marshal() + +// signer of message must be the interchain account on the host +queryMsg := icahosttypes.NewMsgModuleQuerySafe("cosmos2...", []*icahosttypes.QueryRequest{ + { + Path: "/cosmos.bank.v1beta1.Query/Balance", + Data: queryBz, + }, +}) + +bz, err := icatypes.SerializeCosmosTx(cdc, []proto.Message{queryMsg}, icatypes.EncodingProtobuf) + +packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: bz, + Memo: "", +} +``` + +## Atomicity + +As the Interchain Accounts module supports the execution of multiple transactions using the Cosmos SDK `Msg` interface, it provides the same atomicity guarantees as Cosmos SDK-based applications, leveraging the [`CacheMultiStore`](https://docs.cosmos.network/main/learn/advanced/store#cachemultistore) architecture provided by the [`Context`](https://docs.cosmos.network/main/learn/advanced/context.html) type. + +This provides atomic execution of transactions when using Interchain Accounts, where state changes are only committed if all `Msg`s succeed. diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/06-parameters.md b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/06-parameters.md new file mode 100644 index 0000000..4d579e4 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/06-parameters.md @@ -0,0 +1,65 @@ +--- +title: Parameters +sidebar_label: Parameters +sidebar_position: 6 +slug: /apps/interchain-accounts/parameters +--- + + +# Parameters + +The Interchain Accounts module contains the following on-chain parameters, logically separated for each distinct submodule: + +## Controller Submodule Parameters + +| Key | Type | Default Value | +|------------------------|------|---------------| +| `ControllerEnabled` | bool | `true` | + +### ControllerEnabled + +The `ControllerEnabled` parameter controls a chains ability to service ICS-27 controller specific logic. This includes the sending of Interchain Accounts packet data as well as the following ICS-26 callback handlers: + +- `OnChanOpenInit` +- `OnChanOpenAck` +- `OnChanCloseConfirm` +- `OnAcknowledgementPacket` +- `OnTimeoutPacket` + +## Host Submodule Parameters + +| Key | Type | Default Value | +|------------------------|----------|---------------| +| `HostEnabled` | bool | `true` | +| `AllowMessages` | []string | `["*"]` | + +### HostEnabled + +The `HostEnabled` parameter controls a chains ability to service ICS-27 host specific logic. This includes the following ICS-26 callback handlers: + +- `OnChanOpenTry` +- `OnChanOpenConfirm` +- `OnChanCloseConfirm` +- `OnRecvPacket` + +### AllowMessages + +The `AllowMessages` parameter provides the ability for a chain to limit the types of messages or transactions that hosted interchain accounts are authorized to execute by defining an allowlist using the Protobuf message type URL format. + +For example, a Cosmos SDK-based chain that elects to provide hosted Interchain Accounts with the ability of governance voting and staking delegations will define its parameters as follows: + +```json +"params": { + "host_enabled": true, + "allow_messages": ["/cosmos.staking.v1beta1.MsgDelegate", "/cosmos.gov.v1beta1.MsgVote"] +} +``` + +There is also a special wildcard `"*"` value which allows any type of message to be executed by the interchain account. This must be the only value in the `allow_messages` array. + +```json +"params": { + "host_enabled": true, + "allow_messages": ["*"] +} +``` diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/07-tx-encoding.md b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/07-tx-encoding.md new file mode 100644 index 0000000..c30ee07 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/07-tx-encoding.md @@ -0,0 +1,58 @@ +--- +title: Transaction Encoding +sidebar_label: Transaction Encoding +sidebar_position: 7 +slug: /apps/interchain-accounts/tx-encoding +--- + +# Transaction Encoding + +When orchestrating an interchain account transaction, which comprises multiple `sdk.Msg` objects represented as `Any` types, the transactions must be encoded as bytes within [`InterchainAccountPacketData`](https://github.com/cosmos/ibc-go/blob/v7.2.0/proto/ibc/applications/interchain_accounts/v1/packet.proto#L21-L26). + +```protobuf +// InterchainAccountPacketData is comprised of a raw transaction, type of transaction and optional memo field. +message InterchainAccountPacketData { + Type type = 1; + bytes data = 2; + string memo = 3; +} +``` + +The `data` field must be encoded as a [`CosmosTx`](https://github.com/cosmos/ibc-go/blob/v7.2.0/proto/ibc/applications/interchain_accounts/v1/packet.proto#L28-L31). + +```protobuf +// CosmosTx contains a list of sdk.Msg's. It should be used when sending transactions to an SDK host chain. +message CosmosTx { + repeated google.protobuf.Any messages = 1; +} +``` + +The encoding method for `CosmosTx` is determined during the channel handshake process. If the channel version [metadata's `encoding` field](https://github.com/cosmos/ibc-go/blob/v7.2.0/proto/ibc/applications/interchain_accounts/v1/metadata.proto#L22) is marked as `proto3`, then `CosmosTx` undergoes protobuf encoding. Conversely, if the field is set to `proto3json`, then [proto3 json](https://protobuf.dev/programming-guides/proto3/#json) encoding takes place, which generates a JSON representation of the protobuf message. + +## Protobuf Encoding + +Protobuf encoding serves as the standard encoding process for `CosmosTx`. This occurs if the channel handshake initiates with an empty channel version metadata or if the `encoding` field explicitly denotes `proto3`. In Golang, the protobuf encoding procedure utilizes the `proto.Marshal` function. Every protobuf autogenerated Golang type comes equipped with a `Marshal` method that can be employed to encode the message. + +## (Protobuf) JSON Encoding + +The proto3 JSON encoding presents an alternative encoding technique for `CosmosTx`. It is selected if the channel handshake begins with the channel version metadata `encoding` field labeled as `proto3json`. In Golang, the Proto3 canonical encoding in JSON is implemented by the `"github.com/cosmos/gogoproto/jsonpb"` package. Within Cosmos SDK, the `ProtoCodec` structure implements the `JSONCodec` interface, leveraging the `jsonpb` package. This method generates a JSON format as follows: + +```json +{ + "messages": [ + { + "@type": "/cosmos.bank.v1beta1.MsgSend", + "from_address": "cosmos1...", + "to_address": "cosmos1...", + "amount": [ + { + "denom": "uatom", + "amount": "1000000" + } + ] + } + ] +} +``` + +Here, the `"messages"` array is populated with transactions. Each transaction is represented as a JSON object with the `@type` field denoting the transaction type and the remaining fields representing the transaction's attributes. diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/08-client.md b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/08-client.md new file mode 100644 index 0000000..65e1dc8 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/08-client.md @@ -0,0 +1,183 @@ +--- +title: Client +sidebar_label: Client +sidebar_position: 8 +slug: /apps/interchain-accounts/client +--- + +# Client + +## CLI + +A user can query and interact with the Interchain Accounts module using the CLI. Use the `--help` flag to discover the available commands: + +```shell +simd query interchain-accounts --help +``` + +> Please not that this section does not document all the available commands, but only the ones that deserved extra documentation that was not possible to fit in the command line documentation. + +### Controller + +A user can query and interact with the controller submodule. + +#### Query + +The `query` commands allow users to query the controller submodule. + +```shell +simd query interchain-accounts controller --help +``` + +#### Transactions + +The `tx` commands allow users to interact with the controller submodule. + +```shell +simd tx interchain-accounts controller --help +``` + +#### `send-tx` + +The `send-tx` command allows users to send a transaction on the provided connection to be executed using an interchain account on the host chain. + +```shell +simd tx interchain-accounts controller send-tx [connection-id] [path/to/packet_msg.json] +``` + +Example: + +```shell +simd tx interchain-accounts controller send-tx connection-0 packet-data.json --from cosmos1.. +``` + +See below for example contents of `packet-data.json`. The CLI handler will unmarshal the following into `InterchainAccountPacketData` appropriately. + +```json +{ + "type":"TYPE_EXECUTE_TX", + "data":"CqIBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEoEBCkFjb3Ntb3MxNWNjc2hobXAwZ3N4MjlxcHFxNmc0em1sdG5udmdteXU5dWV1YWRoOXkybmM1emowc3psczVndGRkehItY29zbW9zMTBoOXN0YzV2Nm50Z2V5Z2Y1eGY5NDVuanFxNWgzMnI1M3VxdXZ3Gg0KBXN0YWtlEgQxMDAw", + "memo":"" +} +``` + +Note the `data` field is a base64 encoded byte string as per the tx encoding agreed upon during the channel handshake. + +A helper CLI is provided in the host submodule which can be used to generate the packet data JSON using the counterparty chain's binary. See the [`generate-packet-data` command](#generate-packet-data) for an example. + +### Host + +A user can query and interact with the host submodule. + +#### Query + +The `query` commands allow users to query the host submodule. + +```shell +simd query interchain-accounts host --help +``` + +#### Transactions + +The `tx` commands allow users to interact with the controller submodule. + +```shell +simd tx interchain-accounts host --help +``` + +##### `generate-packet-data` + +The `generate-packet-data` command allows users to generate protobuf or proto3 JSON encoded interchain accounts packet data for input message(s). The packet data can then be used with the controller submodule's [`send-tx` command](#send-tx). The `--encoding` flag can be used to specify the encoding format (value must be either `proto3` or `proto3json`); if not specified, the default will be `proto3`. The `--memo` flag can be used to include a memo string in the interchain accounts packet data. + +```shell +simd tx interchain-accounts host generate-packet-data [message] +``` + +Example: + +```shell +simd tx interchain-accounts host generate-packet-data '[{ + "@type":"/cosmos.bank.v1beta1.MsgSend", + "from_address":"cosmos15ccshhmp0gsx29qpqq6g4zmltnnvgmyu9ueuadh9y2nc5zj0szls5gtddz", + "to_address":"cosmos10h9stc5v6ntgeygf5xf945njqq5h32r53uquvw", + "amount": [ + { + "denom": "stake", + "amount": "1000" + } + ] +}]' --memo memo +``` + +The command accepts a single `sdk.Msg` or a list of `sdk.Msg`s that will be encoded into the outputs `data` field. + +Example output: + +```json +{ + "type":"TYPE_EXECUTE_TX", + "data":"CqIBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEoEBCkFjb3Ntb3MxNWNjc2hobXAwZ3N4MjlxcHFxNmc0em1sdG5udmdteXU5dWV1YWRoOXkybmM1emowc3psczVndGRkehItY29zbW9zMTBoOXN0YzV2Nm50Z2V5Z2Y1eGY5NDVuanFxNWgzMnI1M3VxdXZ3Gg0KBXN0YWtlEgQxMDAw", + "memo":"memo" +} +``` + +## gRPC + +A user can query the interchain account module using gRPC endpoints. + +### Controller + +A user can query the controller submodule using gRPC endpoints. + +#### `InterchainAccount` + +The `InterchainAccount` endpoint allows users to query the controller submodule for the interchain account address for a given owner on a particular connection. + +```shell +ibc.applications.interchain_accounts.controller.v1.Query/InterchainAccount +``` + +Example: + +```shell +grpcurl -plaintext \ + -d '{"owner":"cosmos1..","connection_id":"connection-0"}' \ + localhost:9090 \ + ibc.applications.interchain_accounts.controller.v1.Query/InterchainAccount +``` + +#### `Params` + +The `Params` endpoint users to query the current controller submodule parameters. + +```shell +ibc.applications.interchain_accounts.controller.v1.Query/Params +``` + +Example: + +```shell +grpcurl -plaintext \ + localhost:9090 \ + ibc.applications.interchain_accounts.controller.v1.Query/Params +``` + +### Host + +A user can query the host submodule using gRPC endpoints. + +#### `Params` + +The `Params` endpoint users to query the current host submodule parameters. + +```shell +ibc.applications.interchain_accounts.host.v1.Query/Params +``` + +Example: + +```shell +grpcurl -plaintext \ + localhost:9090 \ + ibc.applications.interchain_accounts.host.v1.Query/Params +``` diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/09-active-channels.md b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/09-active-channels.md new file mode 100644 index 0000000..166530e --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/09-active-channels.md @@ -0,0 +1,45 @@ +--- +title: Active Channels +sidebar_label: Active Channels +sidebar_position: 9 +slug: /apps/interchain-accounts/active-channels +--- + +# Understanding Active Channels + +The Interchain Accounts module uses either [ORDERED or UNORDERED](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#ordering) channels. + +When using `ORDERED` channels, the order of transactions when sending packets from a controller to a host chain is maintained. + +When using `UNORDERED` channels, there is no guarantee that the order of transactions when sending packets from the controller to the host chain is maintained. Since ibc-go v7.5.0, the default ordering for new ICA channels is `UNORDERED`, if no ordering is specified in `MsgRegisterInterchainAccount` (previously the default ordering was `ORDERED`). + +> A limitation when using ORDERED channels is that when a packet times out the channel will be closed. + +In the case of a channel closing, a controller chain needs to be able to regain access to the interchain account registered on this channel. `Active Channels` enable this functionality. + +When an Interchain Account is registered using `MsgRegisterInterchainAccount`, a new channel is created on a particular port. During the `OnChanOpenAck` and `OnChanOpenConfirm` steps (on controller & host chain respectively) the `Active Channel` for this interchain account is stored in state. + +It is possible to create a new channel using the same controller chain portID if the previously set `Active Channel` is now in a `CLOSED` state. This channel creation can be initialized programmatically by sending a new `MsgChannelOpenInit` message like so: + +```go +msg := channeltypes.NewMsgChannelOpenInit(portID, string(versionBytes), channeltypes.ORDERED, []string{connectionID}, icatypes.HostPortID, authtypes.NewModuleAddress(icatypes.ModuleName).String()) +handler := keeper.msgRouter.Handler(msg) +res, err := handler(ctx, msg) +if err != nil { + return err +} +``` + +Alternatively, any relayer operator may initiate a new channel handshake for this interchain account once the previously set `Active Channel` is in a `CLOSED` state. This is done by initiating the channel handshake on the controller chain using the same portID associated with the interchain account in question. + +It is important to note that once a channel has been opened for a given interchain account, new channels can not be opened for this account until the currently set `Active Channel` is set to `CLOSED`. + +## Future improvements + +Future versions of the ICS-27 protocol and the Interchain Accounts module will likely use a new channel type that provides ordering of packets without the channel closing in the event of a packet timing out, thus removing the need for `Active Channels` entirely. +The following is a list of issues which will provide the infrastructure to make this possible: + +- [IBC Channel Upgrades](https://github.com/cosmos/ibc-go/issues/1599) +- [Implement ORDERED_ALLOW_TIMEOUT logic in 04-channel](https://github.com/cosmos/ibc-go/issues/1661) +- [Add ORDERED_ALLOW_TIMEOUT as supported ordering in 03-connection](https://github.com/cosmos/ibc-go/issues/1662) +- [Allow ICA channels to be opened as ORDERED_ALLOW_TIMEOUT](https://github.com/cosmos/ibc-go/issues/1663) diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/10-legacy/01-auth-modules.md b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/10-legacy/01-auth-modules.md new file mode 100644 index 0000000..2c8f5db --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/10-legacy/01-auth-modules.md @@ -0,0 +1,274 @@ +--- +title: Authentication Modules +sidebar_label: Authentication Modules +sidebar_position: 1 +slug: /apps/interchain-accounts/legacy/auth-modules +--- + + +# Building an authentication module + +## Deprecation Notice + +**This document is deprecated and will be removed in future releases**. + +:::note Synopsis +Authentication modules play the role of the `Base Application` as described in [ICS-30 IBC Middleware](https://github.com/cosmos/ibc/tree/master/spec/app/ics-030-middleware), and enable application developers to perform custom logic when working with the Interchain Accounts controller API. +::: + +The controller submodule is used for account registration and packet sending. It executes only logic required of all controllers of interchain accounts. The type of authentication used to manage the interchain accounts remains unspecified. There may exist many different types of authentication which are desirable for different use cases. Thus the purpose of the authentication module is to wrap the controller submodule with custom authentication logic. + +In ibc-go, authentication modules are connected to the controller chain via a middleware stack. The controller submodule is implemented as [middleware](https://github.com/cosmos/ibc/tree/master/spec/app/ics-030-middleware) and the authentication module is connected to the controller submodule as the base application of the middleware stack. To implement an authentication module, the `IBCModule` interface must be fulfilled. By implementing the controller submodule as middleware, any amount of authentication modules can be created and connected to the controller submodule without writing redundant code. + +The authentication module must: + +- Authenticate interchain account owners. +- Track the associated interchain account address for an owner. +- Send packets on behalf of an owner (after authentication). + +> Please note that since ibc-go v6 the channel capability is claimed by the controller submodule and therefore it is not required for authentication modules to claim the capability in the `OnChanOpenInit` callback. When the authentication module sends packets on the channel created for the associated interchain account it can pass a `nil` capability to the legacy function `SendTx` of the controller keeper (see section [`SendTx`](03-keeper-api.md#sendtx) for more information). + +## `IBCModule` implementation + +The following `IBCModule` callbacks must be implemented with appropriate custom logic: + +```go +// OnChanOpenInit implements the IBCModule interface +func (im IBCModule) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + // since ibc-go v6 the authentication module *must not* claim the channel capability on OnChanOpenInit + + // perform custom logic + + return version, nil +} + +// OnChanOpenAck implements the IBCModule interface +func (im IBCModule) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyVersion string, +) error { + // perform custom logic + + return nil +} + +// OnChanCloseConfirm implements the IBCModule interface +func (im IBCModule) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // perform custom logic + + return nil +} + +// OnAcknowledgementPacket implements the IBCModule interface +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + // perform custom logic + + return nil +} + +// OnTimeoutPacket implements the IBCModule interface. +func (im IBCModule) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + // perform custom logic + + return nil +} +``` + +The following functions must be defined to fulfill the `IBCModule` interface, but they will never be called by the controller submodule so they may error or panic. That is because in Interchain Accounts, the channel handshake is always initiated on the controller chain and packets are always sent to the host chain and never to the controller chain. + +```go +// OnChanOpenTry implements the IBCModule interface +func (im IBCModule) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + panic("UNIMPLEMENTED") +} + +// OnChanOpenConfirm implements the IBCModule interface +func (im IBCModule) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + panic("UNIMPLEMENTED") +} + +// OnChanCloseInit implements the IBCModule interface +func (im IBCModule) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + panic("UNIMPLEMENTED") +} + +// OnRecvPacket implements the IBCModule interface. A successful acknowledgement +// is returned if the packet data is successfully decoded and the receive application +// logic returns without error. +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) ibcexported.Acknowledgement { + panic("UNIMPLEMENTED") +} +``` + +## `OnAcknowledgementPacket` + +Controller chains will be able to access the acknowledgement written into the host chain state once a relayer relays the acknowledgement. +The acknowledgement bytes contain either the response of the execution of the message(s) on the host chain or an error. They will be passed to the auth module via the `OnAcknowledgementPacket` callback. Auth modules are expected to know how to decode the acknowledgement. + +If the controller chain is connected to a host chain using the host module on ibc-go, it may interpret the acknowledgement bytes as follows: + +Begin by unmarshaling the acknowledgement into `sdk.TxMsgData`: + +```go +var ack channeltypes.Acknowledgement +if err := channeltypes.SubModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { + return err +} + +txMsgData := &sdk.TxMsgData{} +if err := proto.Unmarshal(ack.GetResult(), txMsgData); err != nil { + return err +} +``` + +If the `txMsgData.Data` field is non nil, the host chain is using SDK version \<\= v0.45. +The auth module should interpret the `txMsgData.Data` as follows: + +```go +switch len(txMsgData.Data) { +case 0: + // see documentation below for SDK 0.46.x or greater +default: + for _, msgData := range txMsgData.Data { + if err := handler(msgData); err != nil { + return err + } + } +... +} +``` + +A handler will be needed to interpret what actions to perform based on the message type sent. +A router could be used, or more simply a switch statement. + +```go +func handler(msgData sdk.MsgData) error { +switch msgData.MsgType { +case sdk.MsgTypeURL(&banktypes.MsgSend{}): + msgResponse := &banktypes.MsgSendResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleBankSendMsg(msgResponse) + +case sdk.MsgTypeURL(&stakingtypes.MsgDelegate{}): + msgResponse := &stakingtypes.MsgDelegateResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleStakingDelegateMsg(msgResponse) + +case sdk.MsgTypeURL(&transfertypes.MsgTransfer{}): + msgResponse := &transfertypes.MsgTransferResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleIBCTransferMsg(msgResponse) + +default: + return +} +``` + +If the `txMsgData.Data` is empty, the host chain is using SDK version > v0.45. +The auth module should interpret the `txMsgData.Responses` as follows: + +```go +... +// switch statement from above +case 0: + for _, any := range txMsgData.MsgResponses { + if err := handleAny(any); err != nil { + return err + } + } +} +``` + +A handler will be needed to interpret what actions to perform based on the type URL of the Any. +A router could be used, or more simply a switch statement. +It may be possible to deduplicate logic between `handler` and `handleAny`. + +```go +func handleAny(any *codectypes.Any) error { +switch any.TypeURL { +case banktypes.MsgSend: + msgResponse, err := unpackBankMsgSendResponse(any) + if err != nil { + return err + } + + handleBankSendMsg(msgResponse) + +case stakingtypes.MsgDelegate: + msgResponse, err := unpackStakingDelegateResponse(any) + if err != nil { + return err + } + + handleStakingDelegateMsg(msgResponse) + + case transfertypes.MsgTransfer: + msgResponse, err := unpackIBCTransferMsgResponse(any) + if err != nil { + return err + } + + handleIBCTransferMsg(msgResponse) + +default: + return +} +``` + +## Integration into `app.go` file + +To integrate the authentication module into your chain, please follow the steps outlined in [`app.go` integration](02-integration.md#example-integration). diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/10-legacy/02-integration.md b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/10-legacy/02-integration.md new file mode 100644 index 0000000..ea2ce81 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/10-legacy/02-integration.md @@ -0,0 +1,203 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /apps/interchain-accounts/legacy/integration +--- + + +# Integration + +## Deprecation Notice + +**This document is deprecated and will be removed in future releases**. + +:::note Synopsis +Learn how to integrate Interchain Accounts host and controller functionality to your chain. The following document only applies for Cosmos SDK chains. +::: + +The Interchain Accounts module contains two submodules. Each submodule has its own IBC application. The Interchain Accounts module should be registered as an `AppModule` in the same way all SDK modules are registered on a chain, but each submodule should create its own `IBCModule` as necessary. A route should be added to the IBC router for each submodule which will be used. + +Chains who wish to support ICS-27 may elect to act as a host chain, a controller chain or both. Disabling host or controller functionality may be done statically by excluding the host or controller module entirely from the `app.go` file or it may be done dynamically by taking advantage of the on-chain parameters which enable or disable the host or controller submodules. + +Interchain Account authentication modules are the base application of a middleware stack. The controller submodule is the middleware in this stack. + +![ica-pre-v6.png](./images/ica-pre-v6.png) + +> Please note that since ibc-go v6 the channel capability is claimed by the controller submodule and therefore it is not required for authentication modules to claim the capability in the `OnChanOpenInit` callback. Therefore the custom authentication module does not need a scoped keeper anymore. +> Please note that since ibc-go v7.5.0 it is mandatory to register the gRPC query router after the creation of the host submodule's keeper; otherwise, nodes will not start. The query router is used to execute on the host query messages encoded in the ICA packet data. Please check the sample integration code below for more details. + +## Example integration + +```go +// app.go + +// Register the AppModule for the Interchain Accounts module and the authentication module +// Note: No `icaauth` exists, this must be substituted with an actual Interchain Accounts authentication module +ModuleBasics = module.NewBasicManager( + ... + ica.AppModuleBasic{}, + icaauth.AppModuleBasic{}, + ... +) + +... + +// Add module account permissions for the Interchain Accounts module +// Only necessary for host chain functionality +// Each Interchain Account created on the host chain is derived from the module account created +maccPerms = map[string][]string{ + ... + icatypes.ModuleName: nil, +} + +... + +// Add Interchain Accounts Keepers for each submodule used and the authentication module +// If a submodule is being statically disabled, the associated Keeper does not need to be added. +type App struct { + ... + + ICAControllerKeeper icacontrollerkeeper.Keeper + ICAHostKeeper icahostkeeper.Keeper + ICAAuthKeeper icaauthkeeper.Keeper + + ... +} + +... + +// Create store keys for each submodule Keeper and the authentication module +keys := sdk.NewKVStoreKeys( + ... + icacontrollertypes.StoreKey, + icahosttypes.StoreKey, + icaauthtypes.StoreKey, + ... +) + +... + +// Create the scoped keepers for each submodule keeper and authentication keeper +scopedICAControllerKeeper := app.CapabilityKeeper.ScopeToModule(icacontrollertypes.SubModuleName) +scopedICAHostKeeper := app.CapabilityKeeper.ScopeToModule(icahosttypes.SubModuleName) + +... + +// Create the Keeper for each submodule +app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + scopedICAControllerKeeper, app.MsgServiceRouter(), +) +app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.AccountKeeper, scopedICAHostKeeper, app.MsgServiceRouter(), +) +app.ICAHostKeeper.WithQueryRouter(app.GRPCQueryRouter()) + +// Create Interchain Accounts AppModule +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper) + +// Create your Interchain Accounts authentication module +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.ICAControllerKeeper) + +// ICA auth AppModule +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) + +// ICA auth IBC Module +icaAuthIBCModule := icaauth.NewIBCModule(app.ICAAuthKeeper) + +// Create controller IBC application stack and host IBC module as desired +icaControllerStack := icacontroller.NewIBCMiddleware(icaAuthIBCModule, app.ICAControllerKeeper) +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host and authentication routes +ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icahosttypes.SubModuleName, icaHostIBCModule). + AddRoute(icaauthtypes.ModuleName, icaControllerStack) // Note, the authentication module is routed to the top level of the middleware stack + +... + +// Register Interchain Accounts and authentication module AppModule's +app.moduleManager = module.NewManager( + ... + icaModule, + icaAuthModule, +) + +... + +// Add fee middleware to begin blocker logic +app.moduleManager.SetOrderBeginBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add fee middleware to end blocker logic +app.moduleManager.SetOrderEndBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add Interchain Accounts module InitGenesis logic +app.moduleManager.SetOrderInitGenesis( + ... + icatypes.ModuleName, + ... +) + +// initParamsKeeper init params keeper and its subspaces +func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey sdk.StoreKey) paramskeeper.Keeper { + ... + paramsKeeper.Subspace(icahosttypes.SubModuleName) + paramsKeeper.Subspace(icacontrollertypes.SubModuleName) + ... +``` + +## Using submodules exclusively + +As described above, the Interchain Accounts application module is structured to support the ability of exclusively enabling controller or host functionality. +This can be achieved by simply omitting either controller or host `Keeper` from the Interchain Accounts `NewAppModule` constructor function, and mounting only the desired submodule via the `IBCRouter`. +Alternatively, submodules can be enabled and disabled dynamically using [on-chain parameters](../06-parameters.md). + +The following snippets show basic examples of statically disabling submodules using `app.go`. + +### Disabling controller chain functionality + +```go +// Create Interchain Accounts AppModule omitting the controller keeper +icaModule := ica.NewAppModule(nil, &app.ICAHostKeeper) + +// Create host IBC Module +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host route +ibcRouter.AddRoute(icahosttypes.SubModuleName, icaHostIBCModule) +``` + +### Disabling host chain functionality + +```go +// Create Interchain Accounts AppModule omitting the host keeper +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, nil) + +// Create your Interchain Accounts authentication module, setting up the Keeper, AppModule and IBCModule appropriately +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.ICAControllerKeeper) +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) +icaAuthIBCModule := icaauth.NewIBCModule(app.ICAAuthKeeper) + +// Create controller IBC application stack +icaControllerStack := icacontroller.NewIBCMiddleware(icaAuthIBCModule, app.ICAControllerKeeper) + +// Register controller and authentication routes +ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icaauthtypes.ModuleName, icaControllerStack) // Note, the authentication module is routed to the top level of the middleware stack +``` diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/10-legacy/03-keeper-api.md b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/10-legacy/03-keeper-api.md new file mode 100644 index 0000000..e81f5dc --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/10-legacy/03-keeper-api.md @@ -0,0 +1,127 @@ +--- +title: Keeper API +sidebar_label: Keeper API +sidebar_position: 3 +slug: /apps/interchain-accounts/legacy/keeper-api +--- + + +# Keeper API + +## Deprecation Notice + +**This document is deprecated and will be removed in future releases**. + +The controller submodule keeper exposes two legacy functions that allow respectively for custom authentication modules to register interchain accounts and send packets to the interchain account. + +## `RegisterInterchainAccount` + +The authentication module can begin registering interchain accounts by calling `RegisterInterchainAccount`: + +```go +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, connectionID, owner.String(), version); err != nil { + return err +} + +return nil +``` + +The `version` argument is used to support ICS-29 fee middleware for relayer incentivization of ICS-27 packets. Consumers of the `RegisterInterchainAccount` are expected to build the appropriate JSON encoded version string themselves and pass it accordingly. If an empty string is passed in the `version` argument, then the version will be initialized to a default value in the `OnChanOpenInit` callback of the controller's handler, so that channel handshake can proceed. + +The following code snippet illustrates how to construct an appropriate interchain accounts `Metadata` and encode it as a JSON bytestring: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, controllerConnectionID, owner.String(), string(appVersion)); err != nil { + return err +} +``` + +Similarly, if the application stack is configured to route through ICS-29 fee middleware and a fee enabled channel is desired, construct the appropriate ICS-29 `Metadata` type: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +feeMetadata := feetypes.Metadata{ + AppVersion: string(appVersion), + FeeVersion: feetypes.Version, +} + +feeEnabledVersion, err := feetypes.ModuleCdc.MarshalJSON(&feeMetadata) +if err != nil { + return err +} + +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, controllerConnectionID, owner.String(), string(feeEnabledVersion)); err != nil { + return err +} +``` + +> Since ibc-go v7.5.0 the default ordering of new ICA channels created when invoking `RegisterInterchainAccount` has changed from `ORDERED` to `UNORDERED`. If this default behaviour does not meet your use case, please use the function `RegisterInterchainAccountWithOrdering` (available since ibc-go v7.5.0), which takes an extra parameter that can be used to specify the ordering of the channel. + +## `SendTx` + +The authentication module can attempt to send a packet by calling `SendTx`: + +```go +// Authenticate owner +// perform custom logic + +// Construct controller portID based on interchain account owner address +portID, err := icatypes.NewControllerPortID(owner.String()) +if err != nil { + return err +} + +// Obtain data to be sent to the host chain. +// In this example, the owner of the interchain account would like to send a bank MsgSend to the host chain. +// The appropriate serialization function should be called. The host chain must be able to deserialize the transaction. +// If the host chain is using the ibc-go host module, `SerializeCosmosTx` should be used. +msg := &banktypes.MsgSend{FromAddress: fromAddr, ToAddress: toAddr, Amount: amt} +data, err := icatypes.SerializeCosmosTx(keeper.cdc, []proto.Message{msg}) +if err != nil { + return err +} + +// Construct packet data +packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, +} + +// Obtain timeout timestamp +// An appropriate timeout timestamp must be determined based on the usage of the interchain account. +// If the packet times out, the channel will be closed requiring a new channel to be created. +timeoutTimestamp := obtainTimeoutTimestamp() + +// Send the interchain accounts packet, returning the packet sequence +// A nil channel capability can be passed, since the controller submodule (and not the authentication module) +// claims the channel capability since ibc-go v6. +seq, err = keeper.icaControllerKeeper.SendTx(ctx, nil, portID, packetData, timeoutTimestamp) +``` + +The data within an `InterchainAccountPacketData` must be serialized using a format supported by the host chain. +If the host chain is using the ibc-go host chain submodule, `SerializeCosmosTx` should be used. If the `InterchainAccountPacketData.Data` is serialized using a format not supported by the host chain, the packet will not be successfully received. diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/10-legacy/_category_.json b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/10-legacy/_category_.json new file mode 100644 index 0000000..da34288 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/10-legacy/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Legacy", + "position": 10, + "link": null +} diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/10-legacy/images/ica-pre-v6.png b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/10-legacy/images/ica-pre-v6.png new file mode 100644 index 0000000..4529b23 Binary files /dev/null and b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/10-legacy/images/ica-pre-v6.png differ diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/_category_.json b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/_category_.json new file mode 100644 index 0000000..41e3ac2 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Interchain Accounts", + "position": 2, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/images/ica-v6.png b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/images/ica-v6.png new file mode 100644 index 0000000..abe3eba Binary files /dev/null and b/docs/versioned_docs/version-v7.8.x/02-apps/02-interchain-accounts/images/ica-v6.png differ diff --git a/docs/versioned_docs/version-v7.8.x/02-apps/_category_.json b/docs/versioned_docs/version-v7.8.x/02-apps/_category_.json new file mode 100644 index 0000000..83a389b --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/02-apps/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "IBC Application Modules", + "position": 2, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/01-overview.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/01-overview.md new file mode 100644 index 0000000..2035663 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/01-overview.md @@ -0,0 +1,79 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /ibc/light-clients/overview +--- + +# Overview + +:::note Synopsis +Learn how to build IBC light client modules and fulfill the interfaces required to integrate with core IBC. +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../../01-ibc/01-overview.md) +- [IBC Transport, Authentication, and Ordering Layer - Clients](https://tutorials.cosmos.network/academy/3-ibc/4-clients.html) +- [ICS-002 Client Semantics](https://github.com/cosmos/ibc/tree/main/spec/core/ics-002-client-semantics) + +::: + +IBC uses light clients in order to provide trust-minimized interoperability between sovereign blockchains. Light clients operate under a strict set of rules which provide security guarantees for state updates and facilitate the ability to verify the state of a remote blockchain using merkle proofs. + +The following aims to provide a high level IBC light client module developer guide. Access to IBC light clients are gated by the core IBC `MsgServer` which utilizes the abstractions set by the `02-client` submodule to call into a light client module. A light client module developer is only required to implement a set interfaces as defined in the `modules/core/exported` package of ibc-go. + +A light client module developer should be concerned with three main interfaces: + +- [`ClientState`](#clientstate) encapsulates the light client implementation and its semantics. +- [`ConsensusState`](#consensusstate) tracks consensus data used for verification of client updates, misbehaviour detection and proof verification of counterparty state. +- [`ClientMessage`](#clientmessage) used for submitting block headers for client updates and submission of misbehaviour evidence using conflicting headers. + +Throughout this guide the `07-tendermint` light client module may be referred to as a reference example. + +## Concepts and vocabulary + +### `ClientState` + +`ClientState` is a term used to define the data structure which encapsulates opaque light client state. The `ClientState` contains all the information needed to verify a `ClientMessage` and perform membership and non-membership proof verification of counterparty state. This includes properties that refer to the remote state machine, the light client type and the specific light client instance. + +For example: + +- Constraints used for client updates. +- Constraints used for misbehaviour detection. +- Constraints used for state verification. +- Constraints used for client upgrades. + +The `ClientState` type maintained within the light client module *must* implement the [`ClientState`](https://github.com/cosmos/ibc-go/tree/02-client-refactor-beta1/modules/core/exported/client.go#L36) interface defined in `core/modules/exported/client.go`. +The methods which make up this interface are detailed at a more granular level in the [ClientState section of this guide](02-client-state.md). + +Please refer to the `07-tendermint` light client module's [`ClientState` definition](https://github.com/cosmos/ibc-go/tree/02-client-refactor-beta1/proto/ibc/lightclients/tendermint/v1/tendermint.proto#L18) containing information such as chain ID, status, latest height, unbonding period and proof specifications. + +### `ConsensusState` + +`ConsensusState` is a term used to define the data structure which encapsulates consensus data at a particular point in time, i.e. a unique height or sequence number of a state machine. There must exist a single trusted `ConsensusState` for each height. `ConsensusState` generally contains a trusted root, validator set information and timestamp. + +For example, the `ConsensusState` of the `07-tendermint` light client module defines a trusted root which is used by the `ClientState` to perform verification of membership and non-membership commitment proofs, as well as the next validator set hash used for verifying headers can be trusted in client updates. + +The `ConsensusState` type maintained within the light client module *must* implement the [`ConsensusState`](https://github.com/cosmos/ibc-go/tree/02-client-refactor-beta1/modules/core/exported/client.go#L134) interface defined in `modules/core/exported/client.go`. +The methods which make up this interface are detailed at a more granular level in the [`ConsensusState` section of this guide](03-consensus-state.md). + +### `Height` + +`Height` defines a monotonically increasing sequence number which provides ordering of consensus state data persisted through client updates. +IBC light client module developers are expected to use the [concrete type](https://github.com/cosmos/ibc-go/tree/02-client-refactor-beta1/proto/ibc/core/client/v1/client.proto#L89) provided by the `02-client` submodule. This implements the expectations required by the [`Height`](https://github.com/cosmos/ibc-go/blob/02-client-refactor-beta1/modules/core/exported/client.go#L157) interface defined in `modules/core/exported/client.go`. + +### `ClientMessage` + +`ClientMessage` refers to the interface type [`ClientMessage`](https://github.com/cosmos/ibc-go/tree/02-client-refactor-beta1/modules/core/exported/client.go#L148) used for performing updates to a `ClientState` stored on chain. +This may be any concrete type which produces a change in state to the IBC client when verified. + +The following are considered as valid update scenarios: + +- A block header which when verified inserts a new `ConsensusState` at a unique height. +- A batch of block headers which when verified inserts `N` `ConsensusState` instances for `N` unique heights. +- Evidence of misbehaviour provided by two conflicting block headers. + +Learn more in the [Handling update and misbehaviour](04-updates-and-misbehaviour.md) section. diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/02-client-state.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/02-client-state.md new file mode 100644 index 0000000..8e4eedf --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/02-client-state.md @@ -0,0 +1,79 @@ +--- +title: Client State interface +sidebar_label: Client State interface +sidebar_position: 2 +slug: /ibc/light-clients/client-state +--- + + +# Implementing the `ClientState` interface + +Learn how to implement the [`ClientState`](https://github.com/cosmos/ibc-go/blob/v6.0.0/modules/core/exported/client.go#L40) interface. This list of methods described here does not include all methods of the interface. Some methods are explained in detail in the relevant sections of the guide. + +## `ClientType` method + +`ClientType` should return a unique string identifier of the light client. This will be used when generating a client identifier. +The format is created as follows: `ClientType-{N}` where `{N}` is the unique global nonce associated with a specific client. + +## `GetLatestHeight` method + +`GetLatestHeight` should return the latest block height that the client state represents. + +## `Validate` method + +`Validate` should validate every client state field and should return an error if any value is invalid. The light client +implementer is in charge of determining which checks are required. See the [Tendermint light client implementation](https://github.com/cosmos/ibc-go/blob/v6.0.0/modules/light-clients/07-tendermint/types/client_state.go#L101) as a reference. + +## `Status` method + +`Status` must return the status of the client. + +- An `Active` status indicates that clients are allowed to process packets. +- A `Frozen` status indicates that misbehaviour was detected in the counterparty chain and the client is not allowed to be used. +- An `Expired` status indicates that a client is not allowed to be used because it was not updated for longer than the trusting period. +- An `Unknown` status indicates that there was an error in determining the status of a client. + +All possible `Status` types can be found [here](https://github.com/cosmos/ibc-go/blob/v6.0.0/modules/core/exported/client.go#L26-L36). + +This field is returned in the response of the gRPC [`ibc.core.client.v1.Query/ClientStatus`](https://github.com/cosmos/ibc-go/blob/v6.0.0/modules/core/02-client/types/query.pb.go#L665) endpoint. + +## `ZeroCustomFields` method + +`ZeroCustomFields` should return a copy of the light client with all client customizable fields with their zero value. It should not mutate the fields of the light client. +This method is used when [scheduling upgrades](https://github.com/cosmos/ibc-go/blob/v6.0.0/modules/core/02-client/keeper/proposal.go#L89). Upgrades are used to upgrade chain specific fields. +In the tendermint case, this may be the chain ID or the unbonding period. +For more information about client upgrades see the [Handling upgrades](05-upgrades.md) section. + +## `GetTimestampAtHeight` method + +`GetTimestampAtHeight` must return the timestamp for the consensus state associated with the provided height. +This value is used to facilitate timeouts by checking the packet timeout timestamp against the returned value. + +## `Initialize` method + +Clients must validate the initial consensus state, and set the initial client state and consensus state in the provided client store. +Clients may also store any necessary client-specific metadata. + +`Initialize` is called when a [client is created](https://github.com/cosmos/ibc-go/blob/main/modules/core/02-client/keeper/client.go#L32). + +## `VerifyMembership` method + +`VerifyMembership` must verify the existence of a value at a given commitment path at the specified height. For more information about membership proofs +see the [Existence and non-existence proofs section](06-proofs.md). + +## `VerifyNonMembership` method + +`VerifyNonMembership` must verify the absence of a value at a given commitment path at a specified height. For more information about non-membership proofs +see the [Existence and non-existence proofs section](06-proofs.md). + +## `VerifyClientMessage` method + +`VerifyClientMessage` must verify a `ClientMessage`. A `ClientMessage` could be a `Header`, `Misbehaviour`, or batch update. +It must handle each type of `ClientMessage` appropriately. Calls to `CheckForMisbehaviour`, `UpdateState`, and `UpdateStateOnMisbehaviour` +will assume that the content of the `ClientMessage` has been verified and can be trusted. An error should be returned +if the ClientMessage fails to verify. + +## `CheckForMisbehaviour` method + +Checks for evidence of a misbehaviour in `Header` or `Misbehaviour` type. It assumes the `ClientMessage` +has already been verified. diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/03-consensus-state.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/03-consensus-state.md new file mode 100644 index 0000000..c7fafb7 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/03-consensus-state.md @@ -0,0 +1,27 @@ +--- +title: Consensus State interface +sidebar_label: Consensus State interface +sidebar_position: 3 +slug: /ibc/light-clients/consensus-state +--- + + +# Implementing the `ConsensusState` interface + +A `ConsensusState` is the snapshot of the counterparty chain, that an IBC client uses to verify proofs (e.g. a block). + +The further development of multiple types of IBC light clients and the difficulties presented by this generalization problem (see [ADR-006](https://github.com/cosmos/ibc-go/blob/main/docs/architecture/adr-006-02-client-refactor.md) for more information about this historical context) led to the design decision of each client keeping track of and set its own `ClientState` and `ConsensusState`, as well as the simplification of client `ConsensusState` updates through the generalized `ClientMessage` interface. + +The below [`ConsensusState`](https://github.com/cosmos/ibc-go/blob/main/modules/core/exported/client.go#L134) interface is a generalized interface for the types of information a `ConsensusState` could contain. For a reference `ConsensusState` implementation, please see the [Tendermint light client `ConsensusState`](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/07-tendermint/consensus_state.go). + +## `ClientType` method + +This is the type of client consensus. It should be the same as the `ClientType` return value for the [corresponding `ClientState` implementation](02-client-state.md). + +## `GetTimestamp` method + +`GetTimestamp` should return the timestamp (in nanoseconds) of the consensus state snapshot. + +## `ValidateBasic` method + +`ValidateBasic` should validate every consensus state field and should return an error if any value is invalid. The light client implementer is in charge of determining which checks are required. diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/04-updates-and-misbehaviour.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/04-updates-and-misbehaviour.md new file mode 100644 index 0000000..320ae4d --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/04-updates-and-misbehaviour.md @@ -0,0 +1,99 @@ +--- +title: Handling Updates and Misbehaviour +sidebar_label: Handling Updates and Misbehaviour +sidebar_position: 4 +slug: /ibc/light-clients/updates-and-misbehaviour +--- + + +# Handling `ClientMessage`s: updates and misbehaviour + +As mentioned before in the documentation about [implementing the `ConsensusState` interface](03-consensus-state.md), [`ClientMessage`](https://github.com/cosmos/ibc-go/blob/main/modules/core/exported/client.go#L145) is an interface used to update an IBC client. This update may be performed by: + +- a single header +- a batch of headers +- evidence of misbehaviour, +- or any type which when verified produces a change to the consensus state of the IBC client. + +This interface has been purposefully kept generic in order to give the maximum amount of flexibility to the light client implementer. + +## Implementing the `ClientMessage` interface + +Find the `ClientMessage`interface in `modules/core/exported`: + +```go +type ClientMessage interface { + proto.Message + + ClientType() string + ValidateBasic() error +} +``` + +The `ClientMessage` will be passed to the client to be used in [`UpdateClient`](https://github.com/cosmos/ibc-go/blob/57da75a70145409247e85365b64a4b2fc6ddad2f/modules/core/02-client/keeper/client.go#L53), which retrieves the `ClientState` by client ID (available in `MsgUpdateClient`). This `ClientState` implements the [`ClientState` interface](02-client-state.md) for its specific consenus type (e.g. Tendermint). + +`UpdateClient` will then handle a number of cases including misbehaviour and/or updating the consensus state, utilizing the specific methods defined in the relevant `ClientState`. + +```go +VerifyClientMessage(ctx sdk.Context, cdc codec.BinaryCodec, clientStore sdk.KVStore, clientMsg ClientMessage) error +CheckForMisbehaviour(ctx sdk.Context, cdc codec.BinaryCodec, clientStore sdk.KVStore, clientMsg ClientMessage) bool +UpdateStateOnMisbehaviour(ctx sdk.Context, cdc codec.BinaryCodec, clientStore sdk.KVStore, clientMsg ClientMessage) +UpdateState(ctx sdk.Context, cdc codec.BinaryCodec, clientStore sdk.KVStore, clientMsg ClientMessage) []Height +``` + +## Handling updates and misbehaviour + +The functions for handling updates to a light client and evidence of misbehaviour are all found in the [`ClientState`](https://github.com/cosmos/ibc-go/blob/v6.0.0/modules/core/exported/client.go#L40) interface, and will be discussed below. + +> It is important to note that `Misbehaviour` in this particular context is referring to misbehaviour on the chain level intended to fool the light client. This will be defined by each light client. + +## `VerifyClientMessage` + +`VerifyClientMessage` must verify a `ClientMessage`. A `ClientMessage` could be a `Header`, `Misbehaviour`, or batch update. To understand how to implement a `ClientMessage`, please refer to the [Implementing the `ClientMessage` interface](#implementing-the-clientmessage-interface) section. + +It must handle each type of `ClientMessage` appropriately. Calls to `CheckForMisbehaviour`, `UpdateState`, and `UpdateStateOnMisbehaviour` will assume that the content of the `ClientMessage` has been verified and can be trusted. An error should be returned if the `ClientMessage` fails to verify. + +For an example of a `VerifyClientMessage` implementation, please check the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/07-tendermint/update.go#L20). + +## `CheckForMisbehaviour` + +Checks for evidence of a misbehaviour in `Header` or `Misbehaviour` type. It assumes the `ClientMessage` has already been verified. + +For an example of a `CheckForMisbehaviour` implementation, please check the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/07-tendermint/misbehaviour_handle.go#L18). + +> The Tendermint light client [defines `Misbehaviour`](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/07-tendermint/misbehaviour.go) as two different types of situations: a situation where two conflicting `Header`s with the same height have been submitted to update a client's `ConsensusState` within the same trusting period, or that the two conflicting `Header`s have been submitted at different heights but the consensus states are not in the correct monotonic time ordering (BFT time violation). More explicitly, updating to a new height must have a timestamp greater than the previous consensus state, or, if inserting a consensus at a past height, then time must be less than those heights which come after and greater than heights which come before. + +## `UpdateStateOnMisbehaviour` + +`UpdateStateOnMisbehaviour` should perform appropriate state changes on a client state given that misbehaviour has been detected and verified. This method should only be called when misbehaviour is detected, as it does not perform any misbehaviour checks. Notably, it should freeze the client so that calling the `Status` function on the associated client state no longer returns `Active`. + +For an example of a `UpdateStateOnMisbehaviour` implementation, please check the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/07-tendermint/update.go#L197). + +## `UpdateState` + +`UpdateState` updates and stores as necessary any associated information for an IBC client, such as the `ClientState` and corresponding `ConsensusState`. It should perform a no-op on duplicate updates. + +It assumes the `ClientMessage` has already been verified. + +For an example of a `UpdateState` implementation, please check the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/07-tendermint/update.go#L131). + +## Putting it all together + +The `02-client` `Keeper` module in ibc-go offers a reference as to how these functions will be used to [update the client](https://github.com/cosmos/ibc-go/blob/main/modules/core/02-client/keeper/client.go#L48). + +```go +if err := clientState.VerifyClientMessage(clientMessage); err != nil { + return err +} + +foundMisbehaviour := clientState.CheckForMisbehaviour(clientMessage) +if foundMisbehaviour { + clientState.UpdateStateOnMisbehaviour(clientMessage) + // emit misbehaviour event + return +} + +clientState.UpdateState(clientMessage) // expects no-op on duplicate header + // emit update event + return +} diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/05-upgrades.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/05-upgrades.md new file mode 100644 index 0000000..14ed52f --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/05-upgrades.md @@ -0,0 +1,66 @@ +--- +title: Handling Upgrades +sidebar_label: Handling Upgrades +sidebar_position: 5 +slug: /ibc/light-clients/upgrades +--- + + +# Handling upgrades + +It is vital that high-value IBC clients can upgrade along with their underlying chains to avoid disruption to the IBC ecosystem. Thus, IBC client developers will want to implement upgrade functionality to enable clients to maintain connections and channels even across chain upgrades. + +## Implementing `VerifyUpgradeAndUpdateState` + +The IBC protocol allows client implementations to provide a path to upgrading clients given the upgraded `ClientState`, upgraded `ConsensusState` and proofs for each. This path is provided in the `VerifyUpgradeAndUpdateState` method: + +```go +// NOTE: proof heights are not included as upgrade to a new revision is expected to pass only on the last height committed by the current revision. Clients are responsible for ensuring that the planned last height of the current revision is somehow encoded in the proof verification process. +// This is to ensure that no premature upgrades occur, since upgrade plans committed to by the counterparty may be cancelled or modified before the last planned height. +// If the upgrade is verified, the upgraded client and consensus states must be set in the client store. +func (cs ClientState) VerifyUpgradeAndUpdateState( + ctx sdk.Context, + cdc codec.BinaryCodec, + store sdk.KVStore, + newClient ClientState, + newConsState ConsensusState, + proofUpgradeClient, + proofUpgradeConsState []byte, +) error +``` + +> Please refer to the [Tendermint light client implementation](https://github.com/cosmos/ibc-go/blob/02-client-refactor-beta1/modules/light-clients/07-tendermint/upgrade.go#L27) as an example for implementation. + +It is important to note that light clients **must** handle all management of client and consensus states including the setting of updated `ClientState` and `ConsensusState` in the client store. This can include verifying that the submitted upgraded `ClientState` is of a valid `ClientState` type, that the height of the upgraded client is not greater than the height of the current client (in order to preserve BFT monotonic time), or that certain parameters which should not be changed have not been altered in the upgraded `ClientState`. + +Developers must ensure that the `MsgUpgradeClient` does not pass until the last height of the old chain has been committed, and after the chain upgrades, the `MsgUpgradeClient` should pass once and only once on all counterparty clients. + +### Upgrade path + +Clients should have **prior knowledge of the merkle path** that the upgraded client and upgraded consensus states will use. The height at which the upgrade has occurred should also be encoded in the proof. +> The Tendermint client implementation accomplishes this by including an `UpgradePath` in the `ClientState` itself, which is used along with the upgrade height to construct the merkle path under which the client state and consensus state are committed. + +## Chain specific vs client specific client parameters + +Developers should maintain the distinction between client parameters that are uniform across every valid light client of a chain (chain-chosen parameters), and client parameters that are customizable by each individual client (client-chosen parameters); since this distinction is necessary to implement the `ZeroCustomFields` method in the [`ClientState` interface](02-client-state.md): + +```go +// Utility function that zeroes out any client customizable fields in client state +// Ledger enforced fields are maintained while all custom fields are zero values +// Used to verify upgrades +func (cs ClientState) ZeroCustomFields() ClientState +``` + +Developers must ensure that the new client adopts all of the new client parameters that must be uniform across every valid light client of a chain (chain-chosen parameters), while maintaining the client parameters that are customizable by each individual client (client-chosen parameters) from the previous version of the client. `ZeroCustomFields` is a useful utility function to ensure only chain specific fields are updated during upgrades. + +## Security + +Upgrades must adhere to the IBC Security Model. IBC does not rely on the assumption of honest relayers for correctness. Thus users should not have to rely on relayers to maintain client correctness and security (though honest relayers must exist to maintain relayer liveness). While relayers may choose any set of client parameters while creating a new `ClientState`, this still holds under the security model since users can always choose a relayer-created client that suits their security and correctness needs or create a client with their desired parameters if no such client exists. + +However, when upgrading an existing client, one must keep in mind that there are already many users who depend on this client's particular parameters. **We cannot give the upgrading relayer free choice over these parameters once they have already been chosen. This would violate the security model** since users who rely on the client would have to rely on the upgrading relayer to maintain the same level of security. + +Thus, developers must make sure that their upgrade mechanism allows clients to upgrade the chain-specified parameters whenever a chain upgrade changes these parameters (examples in the Tendermint client include `UnbondingPeriod`, `TrustingPeriod`, `ChainID`, `UpgradePath`, etc), while ensuring that the relayer submitting the `MsgUpgradeClient` cannot alter the client-chosen parameters that the users are relying upon (examples in Tendermint client include `TrustLevel`, `MaxClockDrift`, etc). The previous paragraph discusses how `ZeroCustomFields` helps achieve this. + +### Document potential client parameter conflicts during upgrades + +Counterparty clients can upgrade securely by using all of the chain-chosen parameters from the chain-committed `UpgradedClient` and preserving all of the old client-chosen parameters. This enables chains to securely upgrade without relying on an honest relayer, however it can in some cases lead to an invalid final `ClientState` if the new chain-chosen parameters clash with the old client-chosen parameter. This can happen in the Tendermint client case if the upgrading chain lowers the `UnbondingPeriod` (chain-chosen) to a duration below that of a counterparty client's `TrustingPeriod` (client-chosen). Such cases should be clearly documented by developers, so that chains know which upgrades should be avoided to prevent this problem. The final upgraded client should also be validated in `VerifyUpgradeAndUpdateState` before returning to ensure that the client does not upgrade to an invalid `ClientState`. diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/06-proofs.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/06-proofs.md new file mode 100644 index 0000000..6aff8e6 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/06-proofs.md @@ -0,0 +1,66 @@ +--- +title: Existence/Non-Existence Proofs +sidebar_label: Existence/Non-Existence Proofs +sidebar_position: 6 +slug: /ibc/light-clients/proofs +--- + + +# Existence and non-existence proofs + +IBC uses merkle proofs in order to verify the state of a remote counterparty state machine given a trusted root, and [ICS-23](https://github.com/cosmos/ics23/tree/master/go) is a general approach for verifying merkle trees which is used in ibc-go. + +Currently, all Cosmos SDK modules contain their own stores, which maintain the state of the application module in an IAVL (immutable AVL) binary merkle tree format. Specifically with regard to IBC, core IBC maintains its own IAVL store, and IBC apps (e.g. transfer) maintain their own dedicated stores. The Cosmos SDK multistore therefore creates a simple merkle tree of all of these IAVL trees, and from each of these individual IAVL tree root hashes it derives a root hash for the application state tree as a whole (the `AppHash`). + +For the purposes of ibc-go, there are two types of proofs which are important: existence and non-existence proofs, terms which have been used interchangeably with membership and non-membership proofs. For the purposes of this guide, we will stick with "existence" and "non-existence". + +## Existence proofs + +Existence proofs are used in IBC transactions which involve verification of counterparty state for transactions which will result in the writing of provable state. For example, this includes verification of IBC store state for handshakes and packets. + +Put simply, existence proofs prove that a particular key and value exists in the tree. Under the hood, an IBC existence proof is comprised of two proofs: an IAVL proof that the key exists in IBC store/IBC root hash, and a proof that the IBC root hash exists in the multistore root hash. + +## Non-existence proofs + +Non-existence proofs verify the absence of data stored within counterparty state and are used to prove that a key does NOT exist in state. As stated above, these types of proofs can be used to timeout packets by proving that the counterparty has not written a packet receipt into the store, meaning that a token transfer has NOT successfully occurred. + +Some trees (e.g. SMT) may have a sentinel empty child for non-existent keys. In this case, the ICS-23 proof spec should include this `EmptyChild` so that ICS-23 handles the non-existence proof correctly. + +In some cases, there is a necessity to "mock" non-existence proofs if the counterparty does not have ability to prove absence. Since the verification method is designed to give complete control to client implementations, clients can support chains that do not provide absence proofs by verifying the existence of a non-empty sentinel `ABSENCE` value. In these special cases, the proof provided will be an ICS-23 `Existence` proof, and the client will verify that the `ABSENCE` value is stored under the given path for the given height. + +## State verification methods: `VerifyMembership` and `VerifyNonMembership` + +The state verification functions for all IBC data types have been consolidated into two generic methods, `VerifyMembership` and `VerifyNonMembership`. + +From the [`ClientState` interface definition](https://github.com/cosmos/ibc-go/blob/e650be91614ced7be687c30eb42714787a3bbc59/modules/core/exported/client.go#L68-L91), we find: + +```go +VerifyMembership( + ctx sdk.Context, + clientStore sdk.KVStore, + cdc codec.BinaryCodec, + height Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path Path, + value []byte, +) error + +// VerifyNonMembership is a generic proof verification method which verifies the absence of a given CommitmentPath at a specified height. +// The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). +VerifyNonMembership( + ctx sdk.Context, + clientStore sdk.KVStore, + cdc codec.BinaryCodec, + height Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path Path, +) error +``` + +Both are expected to be provided with a standardised key path, `exported.Path`, as defined in [ICS-24 host requirements](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements). Membership verification requires callers to provide the value marshalled as `[]byte`. Delay period values should be zero for non-packet processing verification. A zero proof height is now allowed by core IBC and may be passed into `VerifyMembership` and `VerifyNonMembership`. Light clients are responsible for returning an error if a zero proof height is invalid behaviour. + +Please refer to the [ICS-23 implementation](https://github.com/cosmos/ibc-go/blob/e093d85b533ab3572b32a7de60b88a0816bed4af/modules/core/23-commitment/types/merkle.go#L131-L205) for a concrete example. diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/07-proposals.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/07-proposals.md new file mode 100644 index 0000000..34b8266 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/07-proposals.md @@ -0,0 +1,36 @@ +--- +title: Handling Proposals +sidebar_label: Handling Proposals +sidebar_position: 7 +slug: /ibc/light-clients/proposals +--- + + +# Handling proposals + +It is possible to update the client with the state of the substitute client through a governance proposal. [This type of governance proposal](../../01-ibc/06-proposals.md) is typically used to recover an expired or frozen client, as it can recover the entire state and therefore all existing channels built on top of the client. `CheckSubstituteAndUpdateState` should be implemented to handle the proposal. + +## Implementing `CheckSubstituteAndUpdateState` + +In the [`ClientState`interface](https://github.com/cosmos/ibc-go/blob/e650be91614ced7be687c30eb42714787a3bbc59/modules/core/exported/client.go), we find: + +```go +// CheckSubstituteAndUpdateState must verify that the provided substitute may be used to update the subject client. +// The light client must set the updated client and consensus states within the clientStore for the subject client. +CheckSubstituteAndUpdateState( + ctx sdk.Context, + cdc codec.BinaryCodec, + subjectClientStore, + substituteClientStore sdk.KVStore, + substituteClient ClientState) + error +``` + +Prior to updating, this function must verify that: + +- the substitute client is the same type as the subject client. For a reference implementation, please see the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/02-client-refactor-beta1/modules/light-clients/07-tendermint/proposal_handle.go#L32). +- the provided substitute may be used to update the subject client. This may mean that certain parameters must remain unaltered. For example, a [valid substitute Tendermint light client](https://github.com/cosmos/ibc-go/blob/02-client-refactor-beta1/modules/light-clients/07-tendermint/proposal_handle.go#L84) must NOT change the chain ID, trust level, max clock drift, unbonding period, proof specs or upgrade path. Please note that `AllowUpdateAfterMisbehaviour` and `AllowUpdateAfterExpiry` have been deprecated (see ADR 026 for more information). + +After these checks are performed, the function must [set the updated client and consensus states](https://github.com/cosmos/ibc-go/blob/02-client-refactor-beta1/modules/light-clients/07-tendermint/proposal_handle.go#L77) within the client store for the subject client. + +Please refer to the [Tendermint light client implementation](https://github.com/cosmos/ibc-go/blob/02-client-refactor-beta1/modules/light-clients/07-tendermint/proposal_handle.go#L27) for reference. diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/08-genesis.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/08-genesis.md new file mode 100644 index 0000000..7b0e175 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/08-genesis.md @@ -0,0 +1,42 @@ +--- +title: Handling Genesis +sidebar_label: Handling Genesis +sidebar_position: 8 +slug: /ibc/light-clients/genesis +--- + + +# Genesis metadata + +:::note Synopsis +Learn how to implement the `ExportMetadata` interface +::: + +## Pre-requisite readings + +- [Cosmos SDK module genesis](https://docs.cosmos.network/v0.47/building-modules/genesis) + +`ClientState` instances are provided their own isolated and namespaced client store upon initialisation. `ClientState` implementations may choose to store any amount of arbitrary metadata in order to verify counterparty consensus state and perform light client updates correctly. + +The `ExportMetadata` method of the [`ClientState` interface](https://github.com/cosmos/ibc-go/blob/e650be91614ced7be687c30eb42714787a3bbc59/modules/core/exported/client.go) provides light client modules with the ability to persist metadata in genesis exports. + +```go +ExportMetadata(clientStore sdk.KVStore) []GenesisMetadata +``` + +`ExportMetadata` is provided the client store and returns an array of `GenesisMetadata`. For maximum flexibility, `GenesisMetadata` is defined as a simple interface containing two distinct `Key` and `Value` accessor methods. + +```go +type GenesisMetadata interface { + // return store key that contains metadata without clientID-prefix + GetKey() []byte + // returns metadata value + GetValue() []byte +} +``` + +This allows `ClientState` instances to retrieve and export any number of key-value pairs which are maintained within the store in their raw `[]byte` form. + +When a chain is started with a `genesis.json` file which contains `ClientState` metadata (for example, when performing manual upgrades using an exported `genesis.json`) the `02-client` submodule of core IBC will handle setting the key-value pairs within their respective client stores. [See `02-client` `InitGenesis`](https://github.com/cosmos/ibc-go/blob/02-client-refactor-beta1/modules/core/02-client/genesis.go#L18-L22). + +Please refer to the [Tendermint light client implementation](https://github.com/cosmos/ibc-go/blob/02-client-refactor-beta1/modules/light-clients/07-tendermint/genesis.go#L12) for an example. diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/09-setup.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/09-setup.md new file mode 100644 index 0000000..fa691e9 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/09-setup.md @@ -0,0 +1,135 @@ +--- +title: Setup +sidebar_label: Setup +sidebar_position: 9 +slug: /ibc/light-clients/setup +--- + + +# Setup + +:::note Synopsis +Learn how to configure light client modules and create clients using core IBC and the `02-client` submodule. +::: + +A last step to finish the development of the light client, is to implement the `AppModuleBasic` interface to allow it to be added to the chain's `app.go` alongside other light client types the chain enables. + +Finally, a succinct rundown is given of the remaining steps to make the light client operational, getting the light client type passed through governance and creating the clients. + +## Configuring a light client module + +An IBC light client module must implement the [`AppModuleBasic`](https://github.com/cosmos/cosmos-sdk/blob/main/types/module/module.go#L50) interface in order to register its concrete types against the core IBC interfaces defined in `modules/core/exported`. This is accomplished via the `RegisterInterfaces` method which provides the light client module with the opportunity to register codec types using the chain's `InterfaceRegistry`. Please refer to the [`07-tendermint` codec registration](https://github.com/cosmos/ibc-go/blob/02-client-refactor-beta1/modules/light-clients/07-tendermint/codec.go#L11). + +The `AppModuleBasic` interface may also be leveraged to install custom CLI handlers for light client module users. Light client modules can safely no-op for interface methods which it does not wish to implement. + +Please refer to the [core IBC documentation](../../01-ibc/02-integration.md#integrating-light-clients) for how to configure additional light client modules alongside `07-tendermint` in `app.go`. + +See below for an example of the `07-tendermint` implementation of `AppModuleBasic`. + +```go +var _ module.AppModuleBasic = AppModuleBasic{} + +// AppModuleBasic defines the basic application module used by the tendermint light client. +// Only the RegisterInterfaces function needs to be implemented. All other function perform +// a no-op. +type AppModuleBasic struct{} + +// Name returns the tendermint module name. +func (AppModuleBasic) Name() string { + return ModuleName +} + +// RegisterLegacyAminoCodec performs a no-op. The Tendermint client does not support amino. +func (AppModuleBasic) RegisterLegacyAminoCodec(*codec.LegacyAmino) {} + +// RegisterInterfaces registers module concrete types into protobuf Any. This allows core IBC +// to unmarshal tendermint light client types. +func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) { + RegisterInterfaces(registry) +} + +// DefaultGenesis performs a no-op. Genesis is not supported for the tendermint light client. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return nil +} + +// ValidateGenesis performs a no-op. Genesis is not supported for the tendermint light client. +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { + return nil +} + +// RegisterGRPCGatewayRoutes performs a no-op. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {} + +// GetTxCmd performs a no-op. Please see the 02-client cli commands. +func (AppModuleBasic) GetTxCmd() *cobra.Command { + return nil +} + +// GetQueryCmd performs a no-op. Please see the 02-client cli commands. +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return nil +} +``` + +## Creating clients + +A client is created by executing a new `MsgCreateClient` transaction composed with a valid `ClientState` and initial `ConsensusState` encoded as protobuf `Any`s. +Generally, this is performed by an off-chain process known as an [IBC relayer](https://github.com/cosmos/ibc/tree/main/spec/relayer/ics-018-relayer-algorithms) however, this is not a strict requirement. + +See below for a list of IBC relayer implementations: + +- [cosmos/relayer](https://github.com/cosmos/relayer) +- [informalsystems/hermes](https://github.com/informalsystems/hermes) +- [confio/ts-relayer](https://github.com/confio/ts-relayer) + +Stateless checks are performed within the [`ValidateBasic`](https://github.com/cosmos/ibc-go/blob/02-client-refactor-beta1/modules/core/02-client/types/msgs.go#L48) method of `MsgCreateClient`. + +```protobuf +// MsgCreateClient defines a message to create an IBC client +message MsgCreateClient { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // light client state + google.protobuf.Any client_state = 1 [(gogoproto.moretags) = "yaml:\"client_state\""]; + // consensus state associated with the client that corresponds to a given + // height. + google.protobuf.Any consensus_state = 2 [(gogoproto.moretags) = "yaml:\"consensus_state\""]; + // signer address + string signer = 3; +} +``` + +Leveraging protobuf `Any` encoding allows core IBC to [unpack](https://github.com/cosmos/ibc-go/blob/02-client-refactor-beta1/modules/core/keeper/msg_server.go#L28-L36) both the `ClientState` and `ConsensusState` into their respective interface types registered previously using the light client module's `RegisterInterfaces` method. + +Within the `02-client` submodule, the [`ClientState` is then initialized](https://github.com/cosmos/ibc-go/blob/02-client-refactor-beta1/modules/core/02-client/keeper/client.go#L30-L34) with its own isolated key-value store, namespaced using a unique client identifier. + +In order to successfully create an IBC client using a new client type, it [must be supported](https://github.com/cosmos/ibc-go/blob/02-client-refactor-beta1/modules/core/02-client/keeper/client.go#L18-L24). Light client support in IBC is gated by on-chain governance. The allow list may be updated by submitting a new governance proposal to update the `02-client` parameter `AllowedClients`. + + +See below for example: + +```shell +%s tx gov submit-proposal param-change --from= +``` + +where `proposal.json` contains: + +```json +{ + "title": "IBC Clients Param Change", + "description": "Update allowed clients", + "changes": [ + { + "subspace": "ibc", + "key": "AllowedClients", + "value": ["06-solomachine", "07-tendermint", "0x-new-client"] + } + ], + "deposit": "1000stake" +} +``` diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/_category_.json b/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/_category_.json new file mode 100644 index 0000000..e1c4f32 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/01-developer-guide/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Developer Guide", + "position": 1, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/02-solomachine/01-solomachine.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/02-solomachine/01-solomachine.md new file mode 100644 index 0000000..215db38 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/02-solomachine/01-solomachine.md @@ -0,0 +1,26 @@ +--- +title: Solomachine +sidebar_label: Solomachine +sidebar_position: 1 +slug: /ibc/light-clients/solomachine/solomachine +--- + + +# `solomachine` + +## Abstract + +This paper defines the implementation of the ICS06 protocol on the Cosmos SDK. For the general +specification please refer to the [ICS06 Specification](https://github.com/cosmos/ibc/tree/master/spec/client/ics-006-solo-machine-client). + +This implementation of a solo machine light client supports single and multi-signature public +keys. The client is capable of handling public key updates by header and governance proposals. +The light client is capable of processing client misbehaviour. Proofs of the counterparty state +are generated by the solo machine client by signing over the desired state with a certain sequence, +diversifier, and timestamp. + +## Contents + +1. **[Concepts](02-concepts.md)** +2. **[State](03-state.md)** +3. **[State Transitions](04-state_transitions.md)** diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/02-solomachine/02-concepts.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/02-solomachine/02-concepts.md new file mode 100644 index 0000000..eb68eb6 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/02-solomachine/02-concepts.md @@ -0,0 +1,168 @@ +--- +title: Concepts +sidebar_label: Concepts +sidebar_position: 2 +slug: /ibc/light-clients/solomachine/concepts +--- + + +# Concepts + +## Client State + +The `ClientState` for a solo machine light client stores the latest sequence, the frozen sequence, +the latest consensus state, and client flag indicating if the client should be allowed to be updated +after a governance proposal. + +If the client is not frozen then the frozen sequence is 0. + +## Consensus State + +The consensus states stores the public key, diversifier, and timestamp of the solo machine light client. + +The diversifier is used to prevent accidental misbehaviour if the same public key is used across +different chains with the same client identifier. It should be unique to the chain the light client +is used on. + +## Public Key + +The public key can be a single public key or a multi-signature public key. The public key type used +must fulfill the tendermint public key interface (this will become the SDK public key interface in the +near future). The public key must be registered on the application codec otherwise encoding/decoding +errors will arise. The public key stored in the consensus state is represented as a protobuf `Any`. +This allows for flexibility in what other public key types can be supported in the future. + +## Counterparty Verification + +The solo machine light client can verify counterparty client state, consensus state, connection state, +channel state, packet commitments, packet acknowledgements, packet receipt absence, +and the next sequence receive. At the end of each successful verification call the light +client sequence number will be incremented. + +Successful verification requires the current public key to sign over the proof. + +## Proofs + +A solo machine proof should verify that the solomachine public key signed +over some specified data. The format for generating marshaled proofs for +the SDK's implementation of solo machine is as follows: + +1. Construct the data using the associated protobuf definition and marshal it. + +For example: + +```go +data := &ClientStateData{ + Path: []byte(path.String()), + ClientState: any, +} + +dataBz, err := cdc.Marshal(data) +``` + +The helper functions `...DataBytes()` in [proof.go](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/06-solomachine/proof.go) handle this +functionality. + +2. Construct the `SignBytes` and marshal it. + +For example: + +```go +signBytes := &SignBytes{ + Sequence: sequence, + Timestamp: timestamp, + Diversifier: diversifier, + DataType: CLIENT, + Data: dataBz, +} + +signBz, err := cdc.Marshal(signBytes) +``` + +The helper functions `...SignBytes()` in [proof.go](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/06-solomachine/proof.go) handle this functionality. +The `DataType` field is used to disambiguate what type of data was signed to prevent potential +proto encoding overlap. + +3. Sign the sign bytes. Embed the signatures into either `SingleSignatureData` or `MultiSignatureData`. +Convert the `SignatureData` to proto and marshal it. + +For example: + +```go +sig, err := key.Sign(signBz) +sigData := &signing.SingleSignatureData{ + Signature: sig, +} + +protoSigData := signing.SignatureDataToProto(sigData) +bz, err := cdc.Marshal(protoSigData) +``` + +4. Construct a `TimestampedSignatureData` and marshal it. The marshaled result can be passed in +as the proof parameter to the verification functions. + +For example: + +```go +timestampedSignatureData := &solomachine.TimestampedSignatureData{ + SignatureData: sigData, + Timestamp: solomachine.Time, +} + +proof, err := cdc.Marshal(timestampedSignatureData) +``` + +NOTE: At the end of this process, the sequence associated with the key needs to be updated. +The sequence must be incremented each time proof is generated. + +## Updates By Header + +An update by a header will only succeed if: + +- the header provided is parseable to solo machine header +- the header sequence matches the current sequence +- the header timestamp is greater than or equal to the consensus state timestamp +- the currently registered public key generated the proof + +If the update is successful: + +- the public key is updated +- the diversifier is updated +- the timestamp is updated +- the sequence is incremented by 1 +- the new consensus state is set in the client state + +## Updates By Proposal + +An update by a governance proposal will only succeed if: + +- the substitute provided is parseable to solo machine client state +- the new consensus state public key does not equal the current consensus state public key + +If the update is successful: + +- the subject client state is updated to the substitute client state +- the subject consensus state is updated to the substitute consensus state +- the client is unfrozen (if it was previously frozen) + +NOTE: Previously, `AllowUpdateAfterProposal` was used to signal the update/recovery options for the solo machine client. However, this has now been deprecated because a code migration can overwrite the client and consensus states regardless of the value of this parameter. If governance would vote to overwrite a client or consensus state, it is likely that governance would also be willing to perform a code migration to do the same. + +## Misbehaviour + +Misbehaviour handling will only succeed if: + +- the misbehaviour provided is parseable to solo machine misbehaviour +- the client is not already frozen +- the current public key signed over two unique data messages at the same sequence and diversifier. + +If the misbehaviour is successfully processed: + +- the client is frozen by setting the frozen sequence to the misbehaviour sequence + +NOTE: Misbehaviour processing is data processing order dependent. A misbehaving solo machine +could update to a new public key to prevent being frozen before misbehaviour is submitted. + +## Upgrades + +Upgrades to solo machine light clients are not supported since an entirely different type of +public key can be set using normal client updates. diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/02-solomachine/03-state.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/02-solomachine/03-state.md new file mode 100644 index 0000000..bdb3102 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/02-solomachine/03-state.md @@ -0,0 +1,12 @@ +--- +title: State +sidebar_label: State +sidebar_position: 3 +slug: /ibc/light-clients/solomachine/state +--- + + +# State + +The solo machine light client will only store consensus states for each update by a header +or a governance proposal. The latest client state is also maintained in the store. diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/02-solomachine/04-state_transitions.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/02-solomachine/04-state_transitions.md new file mode 100644 index 0000000..e2053ac --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/02-solomachine/04-state_transitions.md @@ -0,0 +1,43 @@ +--- +title: State Transitions +sidebar_label: State Transitions +sidebar_position: 4 +slug: /ibc/light-clients/solomachine/state_transitions +--- + + +# State Transitions + +## Client State Verification Functions + +Successful state verification by a solo machine light client will result in: + +- the sequence being incremented by 1. + +## Update By Header + +A successful update of a solo machine light client by a header will result in: + +- the public key being updated to the new public key provided by the header. +- the diversifier being updated to the new diviersifier provided by the header. +- the timestamp being updated to the new timestamp provided by the header. +- the sequence being incremented by 1 +- the consensus state being updated (consensus state stores the public key, diversifier, and timestamp) + +## Update By Governance Proposal + +A successful update of a solo machine light client by a governance proposal will result in: + +- the client state being updated to the substitute client state +- the consensus state being updated to the substitute consensus state (consensus state stores the public key, diversifier, and timestamp) +- the frozen sequence being set to zero (client is unfrozen if it was previously frozen). + +## Upgrade + +Client udgrades are not supported for the solo machine light client. No state transition occurs. + +## Misbehaviour + +Successful misbehaviour processing of a solo machine light client will result in: + +- the frozen sequence being set to the sequence the misbehaviour occurred at diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/02-solomachine/_category_.json b/docs/versioned_docs/version-v7.8.x/03-light-clients/02-solomachine/_category_.json new file mode 100644 index 0000000..e2be8a6 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/02-solomachine/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Solomachine", + "position": 2, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/03-localhost/01-overview.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/03-localhost/01-overview.md new file mode 100644 index 0000000..977bc12 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/03-localhost/01-overview.md @@ -0,0 +1,46 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /ibc/light-clients/localhost/overview +--- + + +# `09-localhost` + +## Overview + +:::note Synopsis +Learn about the 09-localhost light client module. +::: + +The 09-localhost light client module implements a localhost loopback client with the ability to send and receive IBC packets to and from the same state machine. + +### Context + +In a multichain environment, application developers will be used to developing cross-chain applications through IBC. From their point of view, whether or not they are interacting with multiple modules on the same chain or on different chains should not matter. The localhost client module enables a unified interface to interact with different applications on a single chain, using the familiar IBC application layer semantics. + +### Implementation + +There exists a [single sentinel `ClientState`](03-client-state.md) instance with the client identifier `09-localhost`. + +To supplement this, a [sentinel `ConnectionEnd` is stored in core IBC](04-connection.md) state with the connection identifier `connection-localhost`. This enables IBC applications to create channels directly on top of the sentinel connection which leverage the 09-localhost loopback functionality. + +[State verification](05-state-verification.md) for channel state in handshakes or processing packets is reduced in complexity, the `09-localhost` client can simply compare bytes stored under the standardized key paths. + +### Localhost vs *regular* client + +The localhost client aims to provide a unified approach to interacting with applications on a single chain, as the IBC application layer provides for cross-chain interactions. To achieve this unified interface though, there are a number of differences under the hood compared to a 'regular' IBC client (excluding `06-solomachine` and `09-localhost` itself). + +The table below lists some important differences: + +| | Regular client | Localhost | +| -------------------------------------------- | --------------------------------------------------------------------------- | --------- | +| Number of clients | Many instances of a client *type* corresponding to different counterparties | A single sentinel client with the client identifier `09-localhost`| +| Client creation | Relayer (permissionless) | `ClientState` is instantiated in the `InitGenesis` handler of the 02-client submodule in core IBC | +| Client updates | Relayer submits headers using `MsgUpdateClient` | Latest height is updated periodically through the ABCI [`BeginBlock`](https://docs.cosmos.network/v0.47/building-modules/beginblock-endblock) interface of the 02-client submodule in core IBC | +| Number of connections | Many connections, 1 (or more) per client | A single sentinel connection with the connection identifier `connection-localhost` | +| Connection creation | Connection handshake, provided underlying client | Sentinel `ConnectionEnd` is created and set in store in the `InitGenesis` handler of the 03-connection submodule in core IBC | +| Counterparty | Underlying client, representing another chain | Client with identifier `09-localhost` in same chain | +| `VerifyMembership` and `VerifyNonMembership` | Performs proof verification using consensus state roots | Performs state verification using key-value lookups in the core IBC store | +| Integration | Expected to register codec types using the `AppModuleBasic` interface | Registers codec types within the core IBC module | diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/03-localhost/02-integration.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/03-localhost/02-integration.md new file mode 100644 index 0000000..a3ba04d --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/03-localhost/02-integration.md @@ -0,0 +1,20 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /ibc/light-clients/localhost/integration +--- + + +# Integration + +The 09-localhost light client module registers codec types within the core IBC module. This differs from other light client module implementations which are expected to register codec types using the `AppModuleBasic` interface. + +The localhost client is added to the 02-client submodule param [`allowed_clients`](https://github.com/cosmos/ibc-go/blob/v7.0.0-rc0/proto/ibc/core/client/v1/client.proto#L102) by default in ibc-go. + +```go +var ( + // DefaultAllowedClients are the default clients for the AllowedClients parameter. + DefaultAllowedClients = []string{exported.Solomachine, exported.Tendermint, exported.Localhost} +) +``` diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/03-localhost/03-client-state.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/03-localhost/03-client-state.md new file mode 100644 index 0000000..6db230b --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/03-localhost/03-client-state.md @@ -0,0 +1,64 @@ +--- +title: ClientState +sidebar_label: ClientState +sidebar_position: 3 +slug: /ibc/light-clients/localhost/client-state +--- + + +# `ClientState` + +The 09-localhost `ClientState` maintains a single field used to track the latest sequence of the state machine i.e. the height of the blockchain. + +```go +type ClientState struct { + // the latest height of the blockchain + LatestHeight clienttypes.Height +} +``` + +The 09-localhost `ClientState` is instantiated in the `InitGenesis` handler of the 02-client submodule in core IBC. +It calls `CreateLocalhostClient`, declaring a new `ClientState` and initializing it with its own client prefixed store. + +```go +func (k Keeper) CreateLocalhostClient(ctx sdk.Context) error { + var clientState localhost.ClientState + return clientState.Initialize(ctx, k.cdc, k.ClientStore(ctx, exported.LocalhostClientID), nil) +} +``` + +It is possible to disable the localhost client by removing the `09-localhost` entry from the `allowed_clients` list through governance. + +## Client updates + +The latest height is updated periodically through the ABCI [`BeginBlock`](https://docs.cosmos.network/v0.47/building-modules/beginblock-endblock) interface of the 02-client submodule in core IBC. + +[See `BeginBlocker` in abci.go.](https://github.com/cosmos/ibc-go/blob/v7.8.0/modules/core/02-client/abci.go#L12) + +```go +func BeginBlocker(ctx sdk.Context, k keeper.Keeper) { + // ... + + if clientState, found := k.GetClientState(ctx, exported.Localhost); found { + if k.GetClientStatus(ctx, clientState, exported.Localhost) == exported.Active { + k.UpdateLocalhostClient(ctx, clientState) + } + } +} +``` + +The above calls into the 09-localhost `UpdateState` method of the `ClientState` . +It retrieves the current block height from the application context and sets the `LatestHeight` of the 09-localhost client. + +```go +func (cs ClientState) UpdateState(ctx sdk.Context, cdc codec.BinaryCodec, clientStore sdk.KVStore, clientMsg exported.ClientMessage) []exported.Height { + height := clienttypes.GetSelfHeight(ctx) + cs.LatestHeight = height + + clientStore.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(cdc, &cs)) + + return []exported.Height{height} +} +``` + +Note that the 09-localhost `ClientState` is not updated through the 02-client interface leveraged by conventional IBC light clients. diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/03-localhost/04-connection.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/03-localhost/04-connection.md new file mode 100644 index 0000000..dbd5dab --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/03-localhost/04-connection.md @@ -0,0 +1,29 @@ +--- +title: Connection +sidebar_label: Connection +sidebar_position: 4 +slug: /ibc/light-clients/localhost/connection +--- + + +# Localhost connections + +The 09-localhost light client module integrates with core IBC through a single sentinel localhost connection. +The sentinel `ConnectionEnd` is stored by default in the core IBC store. + +This enables channel handshakes to be initiated out of the box by supplying the localhost connection identifier (`connection-localhost`) in the `connectionHops` parameter of `MsgChannelOpenInit`. + +The `ConnectionEnd` is created and set in store via the `InitGenesis` handler of the 03-connection submodule in core IBC. +The `ConnectionEnd` and its `Counterparty` both reference the `09-localhost` client identifier, and share the localhost connection identifier `connection-localhost`. + +```go +// CreateSentinelLocalhostConnection creates and sets the sentinel localhost connection end in the IBC store. +func (k Keeper) CreateSentinelLocalhostConnection(ctx sdk.Context) { + counterparty := types.NewCounterparty(exported.LocalhostClientID, exported.LocalhostConnectionID, commitmenttypes.NewMerklePrefix(k.GetCommitmentPrefix().Bytes())) + connectionEnd := types.NewConnectionEnd(types.OPEN, exported.LocalhostClientID, counterparty, types.ExportedVersionsToProto(types.GetCompatibleVersions()), 0) + + k.SetConnection(ctx, exported.LocalhostConnectionID, connectionEnd) +} +``` + +Note that connection handshakes are disallowed when using the `09-localhost` client type. diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/03-localhost/05-state-verification.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/03-localhost/05-state-verification.md new file mode 100644 index 0000000..1c6d426 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/03-localhost/05-state-verification.md @@ -0,0 +1,22 @@ +--- +title: State Verification +sidebar_label: State Verification +sidebar_position: 5 +slug: /ibc/light-clients/localhost/state-verification +--- + + +# State verification + +The localhost client handles state verification through the `ClientState` interface methods `VerifyMembership` and `VerifyNonMembership` by performing read-only operations directly on the core IBC store. + +When verifying channel state in handshakes or processing packets the `09-localhost` client can simply compare bytes stored under the standardized key paths defined by [ICS-24](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements). + +For existence proofs via `VerifyMembership` the 09-localhost client will retrieve the value stored under the provided key path and compare it against the value provided by the caller. In contrast, non-existence proofs via `VerifyNonMembership` assert the absence of a value at the provided key path. + +Relayers are expected to provide a sentinel proof when sending IBC messages. Submission of nil or empty proofs is disallowed in core IBC messaging. +The 09-localhost light client module defines a `SentinelProof` as a single byte. Localhost client state verification will fail if the sentintel proof value is not provided. + +```go +var SentinelProof = []byte{0x01} +``` diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/03-localhost/_category_.json b/docs/versioned_docs/version-v7.8.x/03-light-clients/03-localhost/_category_.json new file mode 100644 index 0000000..6d75e74 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/03-localhost/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Localhost", + "position": 3, + "link": null +} diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/01-overview.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/01-overview.md new file mode 100644 index 0000000..79e26a2 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/01-overview.md @@ -0,0 +1,26 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /ibc/light-clients/wasm/overview +--- + +# `08-wasm` + +## Overview + +Learn about the `08-wasm` light client proxy module. + +### Context + +Traditionally, light clients used by ibc-go have been implemented only in Go, and since ibc-go v7 (with the release of the 02-client refactor), they are [first-class Cosmos SDK modules](/architecture/adr-010-light-clients-as-sdk-modules). This means that updating existing light client implementations or adding support for new light clients is a multi-step, time-consuming process involving on-chain governance: it is necessary to modify the codebase of ibc-go (if the light client is part of its codebase), re-build chains' binaries, pass a governance proposal and have validators upgrade their nodes. + +### Motivation + +To break the limitation of being able to write light client implementations only in Go, the `08-wasm` adds support to run light clients written in a Wasm-compilable language. The light client byte code implements the entry points of a [CosmWasm](https://docs.cosmwasm.com/docs/) smart contract, and runs inside a Wasm VM. The `08-wasm` module exposes a proxy light client interface that routes incoming messages to the appropriate handler function, inside the Wasm VM, for execution. + +Adding a new light client to a chain is just as simple as submitting a governance proposal with the message that stores the byte code of the light client contract. No coordinated upgrade is needed. When the governance proposal passes and the message is executed, the contract is ready to be instantiated upon receiving a relayer-submitted `MsgCreateClient`. The process of creating a Wasm light client is the same as with a regular light client implemented in Go. + +### Use cases + +- Development of light clients for non-Cosmos ecosystem chains: state machines in other ecosystems are, in many cases, implemented in Rust, and thus there are probably libraries used in their light client implementations for which there is no equivalent in Go. This makes the development of a light client in Go very difficult, but relatively simple to do it in Rust. Therefore, writing a CosmWasm smart contract in Rust that implements the light client algorithm becomes a lower effort. diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/02-concepts.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/02-concepts.md new file mode 100644 index 0000000..e8c57d1 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/02-concepts.md @@ -0,0 +1,74 @@ +--- +title: Concepts +sidebar_label: Concepts +sidebar_position: 2 +slug: /ibc/light-clients/wasm/concepts +--- + +# Concepts + +Learn about the differences between a proxy light client and a Wasm light client. + +## Proxy light client + +The `08-wasm` module is not a regular light client in the same sense as, for example, the 07-tendermint light client. `08-wasm` is instead a *proxy* light client module, and this means that the module acts a proxy to the actual implementations of light clients. The module will act as a wrapper for the actual light clients uploaded as Wasm byte code and will delegate all operations to them (i.e. `08-wasm` just passes through the requests to the Wasm light clients). Still, the `08-wasm` module implements all the required interfaces necessary to integrate with core IBC, so that 02-client can call into it as it would for any other light client module. These interfaces are `ClientState`, `ConsensusState` and `ClientMessage`, and we will describe them in the context of `08-wasm` in the following sections. For more information about this set of interfaces, please read section [Overview of the light client module developer guide](../01-developer-guide/01-overview.md#overview). + +### `ClientState` + +The `08-wasm`'s `ClientState` data structure contains three fields: + +- `Data` contains the bytes of the Protobuf-encoded client state of the underlying light client implemented as a Wasm contract. For example, if the Wasm light client contract implements the GRANDPA light client algorithm, then `Data` will contain the bytes for a [GRANDPA client state](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L35-L60). +- `Checksum` is the sha256 hash of the Wasm contract's byte code. This hash is used as an identifier to call the right contract. +- `LatestHeight` is the latest height of the counterparty state machine (i.e. the height of the blockchain), whose consensus state the light client tracks. + +```go +type ClientState struct { + // bytes encoding the client state of the underlying + // light client implemented as a Wasm contract + Data []byte + // sha256 hash of Wasm contract byte code + Checksum []byte + // latest height of the counterparty ledger + LatestHeight types.Height +} +``` + +See section [`ClientState` of the light client module developer guide](../01-developer-guide/01-overview.md#clientstate) for more information about the `ClientState` interface. + +### `ConsensusState` + +The `08-wasm`'s `ConsensusState` data structure maintains one field: + +- `Data` contains the bytes of the Protobuf-encoded consensus state of the underlying light client implemented as a Wasm contract. For example, if the Wasm light client contract implements the GRANDPA light client algorithm, then `Data` will contain the bytes for a [GRANDPA consensus state](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L87-L94). + +```go +type ConsensusState struct { + // bytes encoding the consensus state of the underlying light client + // implemented as a Wasm contract. + Data []byte +} +``` + +See section [`ConsensusState` of the light client module developer guide](../01-developer-guide/01-overview.md#consensusstate) for more information about the `ConsensusState` interface. + +### `ClientMessage` + +`ClientMessage` is used for performing updates to a `ClientState` stored on chain. The `08-wasm`'s `ClientMessage` data structure maintains one field: + +- `Data` contains the bytes of the Protobuf-encoded header(s) or misbehaviour for the underlying light client implemented as a Wasm contract. For example, if the Wasm light client contract implements the GRANDPA light client algorithm, then `Data` will contain the bytes of either [header](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L96-L104) or [misbehaviour](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L106-L112) for a GRANDPA light client. + +```go +type ClientMessage struct { + // bytes encoding the header(s) or misbehaviour for the underlying light client + // implemented as a Wasm contract. + Data []byte +} +``` + +See section [`ClientMessage` of the light client module developer guide](../01-developer-guide/01-overview.md#clientmessage) for more information about the `ClientMessage` interface. + +## Wasm light client + +The actual light client can be implemented in any language that compiles to Wasm and implements the interfaces of a [CosmWasm](https://docs.cosmwasm.com/docs/) contract. Even though in theory other languages could be used, in practice (at least for the time being) the most suitable language to use would be Rust, since there is already good support for it for developing CosmWasm smart contracts. + +At the moment of writing there are two contracts available: one for [Tendermint](https://github.com/ComposableFi/composable-ibc/tree/master/light-clients/ics07-tendermint-cw) and one [GRANDPA](https://github.com/ComposableFi/composable-ibc/tree/master/light-clients/ics10-grandpa-cw) (which is being used in production in [Composable Finance's Centauri bridge](https://github.com/ComposableFi/composable-ibc)). And there are others in development (e.g. for Near). diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/03-integration.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/03-integration.md new file mode 100644 index 0000000..bb76543 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/03-integration.md @@ -0,0 +1,386 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 3 +slug: /ibc/light-clients/wasm/integration +--- + +# Integration + +Learn how to integrate the `08-wasm` module in a chain binary and about the recommended approaches depending on whether the [`x/wasm` module](https://github.com/CosmWasm/wasmd/tree/main/x/wasm) is already used in the chain. The following document only applies for Cosmos SDK chains. + +## Importing the `08-wasm` module + +`08-wasm` has no stable releases yet. To use it, you need to import the git commit that contains the module with the compatible versions of `ibc-go` and `wasmvm`. To do so, run the following command with the desired git commit in your project: + +```sh +go get github.com/cosmos/ibc-go/modules/light-clients/08-wasm@7ee2a2452b79d0bc8316dc622a1243afa058e8cb +``` + +You can find the version matrix in [here](../../../../docs/03-light-clients/04-wasm/03-integration.md#importing-the-08-wasm-module). + +## `app.go` setup + +The sample code below shows the relevant integration points in `app.go` required to setup the `08-wasm` module in a chain binary. Since `08-wasm` is a light client module itself, please check out as well the section [Integrating light clients](../../01-ibc/02-integration.md#integrating-light-clients) for more information: + +```go +// app.go +import ( + ... + "github.com/cosmos/cosmos-sdk/runtime" + + cmtos "github.com/cometbft/cometbft/libs/os" + + ibcwasm "github.com/cosmos/ibc-go/modules/light-clients/08-wasm" + ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" + ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + ... +) + +... + +// Register the AppModule for the 08-wasm module +ModuleBasics = module.NewBasicManager( + ... + ibcwasm.AppModuleBasic{}, + ... +) + +// Add 08-wasm Keeper +type SimApp struct { + ... + WasmClientKeeper ibcwasmkeeper.Keeper + ... +} + +func NewSimApp( + logger log.Logger, + db dbm.DB, + traceStore io.Writer, + loadLatest bool, + appOpts servertypes.AppOptions, + baseAppOptions ...func(*baseapp.BaseApp), +) *SimApp { + ... + keys := sdk.NewKVStoreKeys( + ... + ibcwasmtypes.StoreKey, + ) + + // Instantiate 08-wasm's keeper + // This sample code uses a constructor function that + // accepts a pointer to an existing instance of Wasm VM. + // This is the recommended approach when the chain + // also uses `x/wasm`, and then the Wasm VM instance + // can be shared. + app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM( + appCodec, + keys[wasmtypes.StoreKey], + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmVM, + app.GRPCQueryRouter(), + ) + app.ModuleManager = module.NewManager( + // SDK app modules + ... + ibcwasm.NewAppModule(app.WasmClientKeeper), + ) + app.ModuleManager.SetOrderBeginBlockers( + ... + ibcwasmtypes.ModuleName, + ... + ) + app.ModuleManager.SetOrderEndBlockers( + ... + ibcwasmtypes.ModuleName, + ... + ) + genesisModuleOrder := []string{ + ... + ibcwasmtypes.ModuleName, + ... + } + app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...) + app.ModuleManager.SetOrderExportGenesis(genesisModuleOrder...) + ... + + // initialize BaseApp + app.SetInitChainer(app.InitChainer) + ... + + // must be before Loading version + if manager := app.SnapshotManager(); manager != nil { + err := manager.RegisterExtensions( + ibcwasmkeeper.NewWasmSnapshotter(app.CommitMultiStore(), &app.WasmClientKeeper), + ) + if err != nil { + panic(fmt.Errorf("failed to register snapshot extension: %s", err)) + } + } + ... + + if loadLatest { + ... + + ctx := app.BaseApp.NewUncachedContext(true, cmtproto.Header{}) + + // Initialize pinned codes in wasmvm as they are not persisted there + if err := ibcwasmkeeper.InitializePinnedCodes(ctx, app.appCodec); err != nil { + cmtos.Exit(fmt.Sprintf("failed initialize pinned codes %s", err)) + } + } +} +``` + +## Keeper instantiation + +When it comes to instantiating `08-wasm`'s keeper there are two recommended ways of doing it. Choosing one or the other will depend on whether the chain already integrates [`x/wasm`](https://github.com/CosmWasm/wasmd/tree/main/x/wasm) or not. + +### If `x/wasm` is present + +If the chain where the module is integrated uses `x/wasm` then we recommend that both `08-wasm` and `x/wasm` share the same Wasm VM instance. Having two separate Wasm VM instances is still possible, but care should be taken to make sure that both instances do not share the directory when the VM stores blobs and various caches, otherwise unexpected behaviour is likely to happen. + +In order to share the Wasm VM instance please follow the guideline below. Please note that this requires `x/wasm`v0.41 or above. + +- Instantiate the Wasm VM in `app.go` with the parameters of your choice. +- [Create an `Option` with this Wasm VM instance](https://github.com/CosmWasm/wasmd/blob/db93d7b6c7bb6f4a340d74b96a02cec885729b59/x/wasm/keeper/options.go#L21-L25). +- Add the option created in the previous step to a slice and [pass it to the `x/wasm NewKeeper` constructor function](https://github.com/CosmWasm/wasmd/blob/db93d7b6c7bb6f4a340d74b96a02cec885729b59/x/wasm/keeper/keeper_cgo.go#L36). +- Pass the pointer to the Wasm VM instance to `08-wasm` [`NewKeeperWithVM` constructor function](https://github.com/cosmos/ibc-go/blob/b306e7a706e1f84a5e11af0540987bd68de9bae5/modules/light-clients/08-wasm/keeper/keeper.go#L38-L46). + +The code to set this up would look something like this: + +```go +// app.go +import ( + ... + "github.com/cosmos/cosmos-sdk/runtime" + + wasmvm "github.com/CosmWasm/wasmvm" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + + ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" + ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + ... +) + +... + +// instantiate the Wasm VM with the chosen parameters +wasmer, err := wasmvm.NewVM( + dataDir, + availableCapabilities, + contractMemoryLimit, + contractDebugMode, + memoryCacheSize, +) +if err != nil { + panic(err) +} + +// create an Option slice (or append to an existing one) +// with the option to use a custom Wasm VM instance +wasmOpts = []wasmkeeper.Option{ + wasmkeeper.WithWasmEngine(wasmer), +} + +// the keeper will use the provided Wasm VM instance, +// instead of instantiating a new one +app.WasmKeeper = wasmkeeper.NewKeeper( + appCodec, + keys[wasmtypes.StoreKey], + app.AccountKeeper, + app.BankKeeper, + app.StakingKeeper, + distrkeeper.NewQuerier(app.DistrKeeper), + app.IBCFeeKeeper, // ISC4 Wrapper: fee IBC middleware + app.IBCKeeper.ChannelKeeper, + &app.IBCKeeper.PortKeeper, + scopedWasmKeeper, + app.TransferKeeper, + app.MsgServiceRouter(), + app.GRPCQueryRouter(), + wasmDir, + wasmConfig, + availableCapabilities, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmOpts..., +) + +app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM( + appCodec, + keys[ibcwasmtypes.StoreKey], + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmer, // pass the Wasm VM instance to `08-wasm` keeper constructor + app.GRPCQueryRouter(), +) +... +``` + +### If `x/wasm` is not present + +If the chain does not use [`x/wasm`](https://github.com/CosmWasm/wasmd/tree/main/x/wasm), even though it is still possible to use the method above from the previous section +(e.g. instantiating a Wasm VM in app.go an pass it to 08-wasm's [`NewKeeperWithVM` constructor function](https://github.com/cosmos/ibc-go/blob/b306e7a706e1f84a5e11af0540987bd68de9bae5/modules/light-clients/08-wasm/keeper/keeper.go#L38-L46), since there would be no need in this case to share the Wasm VM instance with another module, you can use the [`NewKeeperWithConfig` constructor function](https://github.com/cosmos/ibc-go/blob/b306e7a706e1f84a5e11af0540987bd68de9bae5/modules/light-clients/08-wasm/keeper/keeper.go#L82-L90) and provide the Wasm VM configuration parameters of your choice instead. A Wasm VM instance will be created in `NewKeeperWithConfig`. The parameters that can set are: + +- `DataDir` is the [directory for Wasm blobs and various caches](https://github.com/CosmWasm/wasmvm/blob/1638725b25d799f078d053391945399cb35664b1/lib.go#L25). In `wasmd` this is set to the [`wasm` folder under the home directory](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/app/app.go#L578). +- `SupportedCapabilities` is a comma separated [list of capabilities supported by the chain](https://github.com/CosmWasm/wasmvm/blob/1638725b25d799f078d053391945399cb35664b1/lib.go#L26). [`wasmd` sets this to all the available capabilities](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/app/app.go#L586), but 08-wasm only requires `iterator`. +- `MemoryCacheSize` sets [the size in MiB of an in-memory cache for e.g. module caching](https://github.com/CosmWasm/wasmvm/blob/1638725b25d799f078d053391945399cb35664b1/lib.go#L29C16-L29C104). It is not consensus-critical and should be defined on a per-node basis, often in the range 100 to 1000 MB. [`wasmd` reads this value of](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/app/app.go#L579). Default value is 256. +- `ContractDebugMode` is a [flag to enable/disable printing debug logs from the contract to STDOUT](https://github.com/CosmWasm/wasmvm/blob/1638725b25d799f078d053391945399cb35664b1/lib.go#L28). This should be false in production environments. Default value is false. + +Another configuration parameter of the Wasm VM is the contract memory limit (in MiB), which is [set to 32](https://github.com/cosmos/ibc-go/blob/b306e7a706e1f84a5e11af0540987bd68de9bae5/modules/light-clients/08-wasm/types/config.go#L8), [following the example of `wasmd`](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/x/wasm/keeper/keeper.go#L32-L34). This parameter is not configurable by users of `08-wasm`. + +The following sample code shows how the keeper would be constructed using this method: + +```go +// app.go +import ( + ... + "github.com/cosmos/cosmos-sdk/runtime" + + ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" + ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + ... +) + +... + +// homePath is the path to the directory where the data +// directory for Wasm blobs and caches will be created +wasmConfig := ibcwasmtypes.WasmConfig{ + DataDir: filepath.Join(homePath, "ibc_08-wasm_client_data"), + SupportedCapabilities: "iterator", + ContractDebugMode: false, +} +app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithConfig( + appCodec, + keys[ibcwasmtypes.StoreKey], + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmConfig, + app.GRPCQueryRouter(), +) +``` + +Check out also the [`WasmConfig` type definition](https://github.com/cosmos/ibc-go/blob/b306e7a706e1f84a5e11af0540987bd68de9bae5/modules/light-clients/08-wasm/types/config.go#L21-L31) for more information on each of the configurable parameters. Some parameters allow node-level configurations. There is additionally the function [`DefaultWasmConfig`](https://github.com/cosmos/ibc-go/blob/b306e7a706e1f84a5e11af0540987bd68de9bae5/modules/light-clients/08-wasm/types/config.go#L36) available that returns a configuration with the default values. + +### Options + +The `08-wasm` module comes with an options API inspired by the one in `x/wasm`. +Currently the only option available is the `WithQueryPlugins` option, which allows registration of custom query plugins for the `08-wasm` module. The use of this API is optional and it is only required if the chain wants to register custom query plugins for the `08-wasm` module. + +#### `WithQueryPlugins` + +By default, the `08-wasm` module does not support any queries. However, it is possible to register custom query plugins for [`QueryRequest::Custom`](https://github.com/CosmWasm/cosmwasm/blob/v1.5.0/packages/std/src/query/mod.rs#L45) and [`QueryRequest::Stargate`](https://github.com/CosmWasm/cosmwasm/blob/v1.5.0/packages/std/src/query/mod.rs#L54-L61). + +Assuming that the keeper is not yet instantiated, the following sample code shows how to register query plugins for the `08-wasm` module. + +We first construct a [`QueryPlugins`](https://github.com/cosmos/ibc-go/blob/b306e7a706e1f84a5e11af0540987bd68de9bae5/modules/light-clients/08-wasm/types/querier.go#L78-L87) object with the desired query plugins: + +```go +queryPlugins := ibcwasmtypes.QueryPlugins { + Custom: MyCustomQueryPlugin(), + // `myAcceptList` is a `[]string` containing the list of gRPC query paths that the chain wants to allow for the `08-wasm` module to query. + // These queries must be registered in the chain's gRPC query router, be deterministic, and track their gas usage. + // The `AcceptListStargateQuerier` function will return a query plugin that will only allow queries for the paths in the `myAcceptList`. + // The query responses are encoded in protobuf unlike the implementation in `x/wasm`. + Stargate: ibcwasmtypes.AcceptListStargateQuerier(myAcceptList), +} +``` + +You may leave any of the fields in the `QueryPlugins` object as `nil` if you do not want to register a query plugin for that query type. + +Then, we pass the `QueryPlugins` object to the `WithQueryPlugins` option: + +```go +querierOption := ibcwasmtypes.WithQueryPlugins(&queryPlugins) +``` + +Finally, we pass the option to the `NewKeeperWithConfig` or `NewKeeperWithVM` constructor function during [Keeper instantiation](#keeper-instantiation): + +```diff +app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithConfig( + appCodec, + keys[ibcwasmtypes.StoreKey], + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmConfig, + app.GRPCQueryRouter(), ++ querierOption, +) +``` + +```diff +app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM( + appCodec, + keys[wasmtypes.StoreKey], + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmer, // pass the Wasm VM instance to `08-wasm` keeper constructor + app.GRPCQueryRouter(), ++ querierOption, +) +``` + +## Updating `AllowedClients` + +In order to use the `08-wasm` module chains must update the [`AllowedClients` parameter in the 02-client submodule](https://github.com/cosmos/ibc-go/blob/v7.3.0/proto/ibc/core/client/v1/client.proto#L104) of core IBC. This can be configured directly in the application upgrade handler with the sample code below: + +```go +import ( + ... + ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + ... +) + +... + +func CreateWasmUpgradeHandler( + mm *module.Manager, + configurator module.Configurator, + clientKeeper clientkeeper.Keeper, +) upgradetypes.UpgradeHandler { + return func(goCtx context.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + // explicitly update the IBC 02-client params, adding the wasm client type + params := clientKeeper.GetParams(ctx) + params.AllowedClients = append(params.AllowedClients, ibcwasmtypes.Wasm) + clientKeeper.SetParams(ctx, params) + + return mm.RunMigrations(goCtx, configurator, vm) + } +} +``` + +Or alternatively the parameter can be updated via a governance proposal (see at the bottom of section [`Creating clients`](../01-developer-guide/09-setup.md#creating-clients) for an example of how to do this). + +## Adding the module to the store + +As part of the upgrade migration you must also add the module to the upgrades store. + +```go +func (app SimApp) RegisterUpgradeHandlers() { + + ... + + if upgradeInfo.Name == UpgradeName && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { + storeUpgrades := storetypes.StoreUpgrades{ + Added: []string{ + ibcwasmtypes.ModuleName, + }, + } + + // configure store loader that checks if version == upgradeHeight and applies store upgrades + app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) + } +} +``` + +## Adding snapshot support + +In order to use the `08-wasm` module chains are required to register the `WasmSnapshotter` extension in the snapshot manager. This snapshotter takes care of persisting the external state, in the form of contract code, of the Wasm VM instance to disk when the chain is snapshotted. [This code](https://github.com/cosmos/ibc-go/blob/b306e7a706e1f84a5e11af0540987bd68de9bae5/modules/light-clients/08-wasm/testing/simapp/app.go#L747-L755) should be placed in `NewSimApp` function in `app.go`. + +## Pin byte codes at start + +Wasm byte codes should be pinned to the WasmVM cache on every application start, therefore [this code](https://github.com/cosmos/ibc-go/blob/b306e7a706e1f84a5e11af0540987bd68de9bae5/modules/light-clients/08-wasm/testing/simapp/app.go#L786-L791) should be placed in `NewSimApp` function in `app.go`. diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/04-messages.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/04-messages.md new file mode 100644 index 0000000..7fd06d1 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/04-messages.md @@ -0,0 +1,75 @@ +--- +title: Messages +sidebar_label: Messages +sidebar_position: 4 +slug: /ibc/light-clients/wasm/messages +--- + +# Messages + +## `MsgStoreCode` + +Uploading the Wasm light client contract to the Wasm VM storage is achieved by means of `MsgStoreCode`: + +```go +type MsgStoreCode struct { + // signer address + Signer string + // wasm byte code of light client contract. It can be raw or gzip compressed + WasmByteCode []byte +} +``` + +This message is expected to fail if: + +- `Signer` is an invalid Bech32 address, or it does not match the designated authority address. +- `WasmByteCode` is empty or it exceeds the maximum size, currently set to 3MB. + +Only light client contracts stored using `MsgStoreCode` are allowed to be instantiated. An attempt to create a light client from contracts uploaded via other means (e.g. through `x/wasm` if the module shares the same Wasm VM instance with 08-wasm) will fail. Due to the idempotent nature of the Wasm VM's `StoreCode` function, it is possible to store the same byte code multiple times. + +When execution of `MsgStoreCode` succeeds, the checksum of the contract (i.e. the sha256 hash of the contract's byte code) is stored in an allow list. When a relayer submits [`MsgCreateClient`](https://github.com/cosmos/ibc-go/blob/v7.3.0/proto/ibc/core/client/v1/tx.proto#L25-L37) with 08-wasm's `ClientState`, the client state includes the checksum of the Wasm byte code that should be called. Then 02-client calls [08-wasm's implementation of `Initialize` function](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/core/02-client/keeper/client.go#L34) (which is an interface function part of `ClientState`), and it will check that the checksum in the client state matches one of the checksums in the allow list. If a match is found, the light client is initialized; otherwise, the transaction is aborted. + +## `MsgMigrateContract` + +Migrating a contract to a new Wasm byte code is achieved by means of `MsgMigrateContract`: + +```go +type MsgMigrateContract struct { + // signer address + Signer string + // the client id of the contract + ClientId string + // the SHA-256 hash of the new wasm byte code for the contract + Checksum []byte + // the json-encoded migrate msg to be passed to the contract on migration + Msg []byte +} +``` + +This message is expected to fail if: + +- `Signer` is an invalid Bech32 address, or it does not match the designated authority address. +- `ClientId` is not a valid identifier prefixed by `08-wasm`. +- `Checksum` is not exactly 32 bytes long or it is not found in the list of allowed checksums (a new checksum is added to the list when executing `MsgStoreCode`), or it matches the current checksum of the contract. + +When a Wasm light client contract is migrated to a new Wasm byte code the checksum for the contract will be updated with the new checksum. + +## `MsgRemoveChecksum` + +Removing a checksum from the list of allowed checksums is achieved by means of `MsgRemoveChecksum`: + +```go +type MsgRemoveChecksum struct { + // signer address + Signer string + // Wasm byte code checksum to be removed from the store + Checksum []byte +} +``` + +This message is expected to fail if: + +- `Signer` is an invalid Bech32 address, or it does not match the designated authority address. +- `Checksum` is not exactly 32 bytes long or it is not found in the list of allowed checksums (a new checksum is added to the list when executing `MsgStoreCode`). + +When a checksum is removed from the list of allowed checksums, then the corresponding Wasm byte code will not be available for instantiation in [08-wasm's implementation of `Initialize` function](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/core/02-client/keeper/client.go#L34). diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/05-governance.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/05-governance.md new file mode 100644 index 0000000..2ff659a --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/05-governance.md @@ -0,0 +1,126 @@ +--- +title: Governance +sidebar_label: Governance +sidebar_position: 5 +slug: /ibc/light-clients/wasm/governance +--- + +# Governance + +Learn how to upload Wasm light client byte code on a chain, and how to migrate an existing Wasm light client contract. + +## Setting an authority + +Both the storage of Wasm light client byte code as well as the migration of an existing Wasm light client contract are permissioned (i.e. only allowed to an authority such as governance). The designated authority is specified when instantiating `08-wasm`'s keeper: both [`NewKeeperWithVM`](https://github.com/cosmos/ibc-go/blob/b306e7a706e1f84a5e11af0540987bd68de9bae5/modules/light-clients/08-wasm/keeper/keeper.go#L38-L46) and [`NewKeeperWithConfig`](https://github.com/cosmos/ibc-go/blob/b306e7a706e1f84a5e11af0540987bd68de9bae5/modules/light-clients/08-wasm/keeper/keeper.go#L82-L90) constructor functions accept an `authority` argument that must be the address of the authorized actor. For example, in `app.go`, when instantiating the keeper, you can pass the address of the governance module: + +```go +// app.go +import ( + ... + "github.com/cosmos/cosmos-sdk/runtime" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" + ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + ... +) + +// app.go +app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM( + appCodec, + keys[wasmtypes.StoreKey], + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), // authority + wasmVM, + app.GRPCQueryRouter(), +) +``` + +## Storing new Wasm light client byte code + + If governance is the allowed authority, the governance v1 proposal that needs to be submitted to upload a new light client contract should contain the message [`MsgStoreCode`](https://github.com/cosmos/ibc-go/blob/b306e7a706e1f84a5e11af0540987bd68de9bae5/proto/ibc/lightclients/wasm/v1/tx.proto#L22-L30) with the base64-encoded byte code of the Wasm contract. Use the following CLI command and JSON as an example: + +```shell +simd tx gov submit-proposal --from +``` + +where `proposal.json` contains: + +```json +{ + "title": "Upload IBC Wasm light client", + "summary": "Upload wasm client", + "messages": [ + { + "@type": "/ibc.lightclients.wasm.v1.MsgStoreCode", + "signer": "cosmos1...", // the authority address (e.g. the gov module account address) + "wasm_byte_code": "YWJ...PUB+" // standard base64 encoding of the Wasm contract byte code + } + ], + "metadata": "AQ==", + "deposit": "100stake" +} +``` + +To learn more about the `submit-proposal` CLI command, please check out [the relevant section in Cosmos SDK documentation](https://docs.cosmos.network/main/modules/gov#submit-proposal). + +Alternatively, the process of submitting the proposal may be simpler if you use the CLI command `store-code`. This CLI command accepts as argument the file of the Wasm light client contract and takes care of constructing the proposal message with `MsgStoreCode` and broadcasting it. See section [`store-code`](./08-client.md#store-code) for more information. + +## Migrating an existing Wasm light client contract + +If governance is the allowed authority, the governance v1 proposal that needs to be submitted to migrate an existing new Wasm light client contract should contain the message [`MsgMigrateContract`](https://github.com/cosmos/ibc-go/blob/b306e7a706e1f84a5e11af0540987bd68de9bae5/proto/ibc/lightclients/wasm/v1/tx.proto#L51-L63) with the checksum of the Wasm byte code to migrate to. Use the following CLI command and JSON as an example: + +```shell +simd tx gov submit-proposal --from +``` + +where `proposal.json` contains: + +```json +{ + "title": "Migrate IBC Wasm light client", + "summary": "Migrate wasm client", + "messages": [ + { + "@type": "/ibc.lightclients.wasm.v1.MsgMigrateContract", + "signer": "cosmos1...", // the authority address (e.g. the gov module account address) + "client_id": "08-wasm-1", // client identifier of the Wasm light client contract that will be migrated + "checksum": "a8ad...4dc0", // SHA-256 hash of the Wasm byte code to migrate to, previously stored with MsgStoreCode + "msg": "{}" // JSON-encoded message to be passed to the contract on migration + } + ], + "metadata": "AQ==", + "deposit": "100stake" +} +``` + +To learn more about the `submit-proposal` CLI command, please check out [the relevant section in Cosmos SDK documentation](https://docs.cosmos.network/main/modules/gov#submit-proposal). + +## Removing an existing checksum + +If governance is the allowed authority, the governance v1 proposal that needs to be submitted to remove a specific checksum from the list of allowed checksums should contain the message [`MsgRemoveChecksum`](https://github.com/cosmos/ibc-go/blob/b306e7a706e1f84a5e11af0540987bd68de9bae5/proto/ibc/lightclients/wasm/v1/tx.proto#L38-L46) with the checksum (of a corresponding Wasm byte code). Use the following CLI command and JSON as an example: + +```shell +simd tx gov submit-proposal --from +``` + +where `proposal.json` contains: + +```json +{ + "title": "Remove checksum of Wasm light client byte code", + "summary": "Remove checksum", + "messages": [ + { + "@type": "/ibc.lightclients.wasm.v1.MsgRemoveChecksum", + "signer": "cosmos1...", // the authority address (e.g. the gov module account address) + "checksum": "a8ad...4dc0", // SHA-256 hash of the Wasm byte code that should be removed from the list of allowed checksums + } + ], + "metadata": "AQ==", + "deposit": "100stake" +} +``` + +To learn more about the `submit-proposal` CLI command, please check out [the relevant section in Cosmos SDK documentation](https://docs.cosmos.network/main/modules/gov#submit-proposal). diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/06-events.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/06-events.md new file mode 100644 index 0000000..5d68ce9 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/06-events.md @@ -0,0 +1,26 @@ +--- +title: Events +sidebar_label: Events +sidebar_position: 6 +slug: /ibc/light-clients/wasm/events +--- + +# Events + +The `08-wasm` module emits the following events: + +## `MsgStoreCode` + +| Type | Attribute Key | Attribute Value | +|------------------|----------------|--------------------------| +| store_wasm_code | wasm_checksum | \{hex.Encode(checksum)\} | +| message | module | 08-wasm | + +## `MsgMigrateContract` + +| Type | Attribute Key | Attribute Value | +|------------------|----------------|-----------------------------| +| migrate_contract | client_id | \{clientId\} | +| migrate_contract | wasm_checksum | \{hex.Encode(checksum)\} | +| migrate_contract | new_checksum | \{hex.Encode(newChecksum)\} | +| message | module | 08-wasm | diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/07-contracts.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/07-contracts.md new file mode 100644 index 0000000..b5a2457 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/07-contracts.md @@ -0,0 +1,113 @@ +--- +title: Contracts +sidebar_label: Contracts +sidebar_position: 7 +slug: /ibc/light-clients/wasm/contracts +--- + +# Contracts + +Learn about the expected behaviour of Wasm light client contracts and the between with `08-wasm`. + +## API + +The `08-wasm` light client proxy performs calls to the Wasm light client via the Wasm VM. The calls require as input JSON-encoded payload messages that fall in the three categories described in the next sections. + +## `InstantiateMessage` + +This is the message sent to the contract's `instantiate` entry point. It contains the bytes of the protobuf-encoded client and consensus states of the underlying light client, both provided in [`MsgCreateClient`](https://github.com/cosmos/ibc-go/blob/v7.3.0/proto/ibc/core/client/v1/tx.proto#L44-L54). Please note that the bytes contained within the JSON message are represented as base64-encoded strings. + +```go +type InstantiateMessage struct { + ClientState []byte `json:"client_state"` + ConsensusState []byte `json:"consensus_state"` + Checksum []byte `json:"checksum" +} +``` + +The Wasm light client contract is expected to store the client and consensus state in the corresponding keys of the client-prefixed store. + +## `QueryMsg` + +`QueryMsg` acts as a discriminated union type that is used to encode the messages that are sent to the contract's `query` entry point. Only one of the fields of the type should be set at a time, so that the other fields are omitted in the encoded JSON and the payload can be correctly translated to the corresponding element of the enumeration in Rust. + +```go +type QueryMsg struct { + Status *StatusMsg `json:"status,omitempty"` + ExportMetadata *ExportMetadataMsg `json:"export_metadata,omitempty"` + TimestampAtHeight *TimestampAtHeightMsg `json:"timestamp_at_height,omitempty"` + VerifyClientMessage *VerifyClientMessageMsg `json:"verify_client_message,omitempty"` + CheckForMisbehaviour *CheckForMisbehaviourMsg `json:"check_for_misbehaviour,omitempty"` +} +``` + +```rust +#[cw_serde] +pub enum QueryMsg { + Status(StatusMsg), + ExportMetadata(ExportMetadataMsg), + TimestampAtHeight(TimestampAtHeightMsg), + VerifyClientMessage(VerifyClientMessageRaw), + CheckForMisbehaviour(CheckForMisbehaviourMsgRaw), +} +``` + +To learn what it is expected from the Wasm light client contract when processing each message, please read the corresponding section of the [Light client developer guide](../01-developer-guide/01-overview.md): + +- For `StatusMsg`, see the section [`Status` method](../01-developer-guide/02-client-state.md#status-method). +- For `ExportMetadataMsg`, see the section [Genesis metadata](../01-developer-guide/08-genesis.md#genesis-metadata). +- For `TimestampAtHeightMsg`, see the section [`GetTimestampAtHeight` method](../01-developer-guide/02-client-state.md#gettimestampatheight-method). +- For `VerifyClientMessageMsg`, see the section [`VerifyClientMessage`](../01-developer-guide/04-updates-and-misbehaviour.md#verifyclientmessage). +- For `CheckForMisbehaviourMsg`, see the section [`CheckForMisbehaviour` method](../01-developer-guide/02-client-state.md#checkformisbehaviour-method). + +## `SudoMsg` + +`SudoMsg` acts as a discriminated union type that is used to encode the messages that are sent to the contract's `sudo` entry point. Only one of the fields of the type should be set at a time, so that the other fields are omitted in the encoded JSON and the payload can be correctly translated to the corresponding element of the enumeration in Rust. + +The `sudo` entry point is able to perform state-changing writes in the client-prefixed store. + +```go +type SudoMsg struct { + UpdateState *UpdateStateMsg `json:"update_state,omitempty"` + UpdateStateOnMisbehaviour *UpdateStateOnMisbehaviourMsg `json:"update_state_on_misbehaviour,omitempty"` + VerifyUpgradeAndUpdateState *VerifyUpgradeAndUpdateStateMsg `json:"verify_upgrade_and_update_state,omitempty"` + VerifyMembership *VerifyMembershipMsg `json:"verify_membership,omitempty"` + VerifyNonMembership *VerifyNonMembershipMsg `json:"verify_non_membership,omitempty"` + MigrateClientStore *MigrateClientStoreMsg `json:"migrate_client_store,omitempty"` +} +``` + +```rust +#[cw_serde] +pub enum SudoMsg { + UpdateState(UpdateStateMsgRaw), + UpdateStateOnMisbehaviour(UpdateStateOnMisbehaviourMsgRaw), + VerifyUpgradeAndUpdateState(VerifyUpgradeAndUpdateStateMsgRaw), + VerifyMembership(VerifyMembershipMsgRaw), + VerifyNonMembership(VerifyNonMembershipMsgRaw), + MigrateClientStore(MigrateClientStoreMsgRaw), +} +``` + +To learn what it is expected from the Wasm light client contract when processing each message, please read the corresponding section of the [Light client developer guide](../01-developer-guide/01-overview.md): + +- For `UpdateStateMsg`, see the section [`UpdateState`](../01-developer-guide/04-updates-and-misbehaviour.md#updatestate). +- For `UpdateStateOnMisbehaviourMsg`, see the section [`UpdateStateOnMisbehaviour`](../01-developer-guide/04-updates-and-misbehaviour.md#updatestateonmisbehaviour). +- For `VerifyUpgradeAndUpdateStateMsg`, see the section [`GetTimestampAtHeight` method](../01-developer-guide/05-upgrades.md#implementing-verifyupgradeandupdatestate). +- For `VerifyMembershipMsg`, see the section [`VerifyMembership` method](../01-developer-guide/02-client-state.md#verifymembership-method). +- For `VerifyNonMembershipMsg`, see the section [`VerifyNonMembership` method](../01-developer-guide/02-client-state.md#verifynonmembership-method). +- For `MigrateClientStoreMsg`, see the section [Implementing `CheckSubstituteAndUpdateState`](../01-developer-guide/07-proposals.md#implementing-checksubstituteandupdatestate). + +### Migration + +The `08-wasm` proxy light client exposes the `MigrateContract` RPC endpoint that can be used to migrate a given Wasm light client contract (specified by the client identifier) to a new Wasm byte code (specified by the hash of the byte code). The expected use case for this RPC endpoint is to enable contracts to migrate to new byte code in case the current byte code is found to have a bug or vulnerability. The Wasm byte code that contracts are migrated have to be uploaded beforehand using `MsgStoreCode` and must implement the `migrate` entry point. See section[`MsgMigrateContract`](./04-messages.md#msgmigratecontract) for information about the request message for this RPC endpoint. + +## Expected behaviour + +The `08-wasm` proxy light client modules expects the following behaviour from the Wasm light client contracts when executing messages that perform state-changing writes: + +- The contract must not delete the client state from the store. +- The contract must not change the client state to a client state of another type. +- The contract must not change the checksum in the client state. + +Any violation of these rules will result in an error returned from `08-wasm` that will abort the transaction. diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/08-client.md b/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/08-client.md new file mode 100644 index 0000000..9f39830 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/08-client.md @@ -0,0 +1,151 @@ +--- +title: Client +sidebar_label: Client +sidebar_position: 7 +slug: /ibc/light-clients/wasm/client +--- + +# Client + +## CLI + +A user can query and interact with the `08-wasm` module using the CLI. Use the `--help` flag to discover the available commands: + +### Transactions + +The `tx` commands allow users to interact with the `08-wasm` submodule. + +```shell +simd tx ibc-wasm --help +``` + +#### `store-code` + +The `store-code` command allows users to submit a governance proposal with a `MsgStoreCode` to store the byte code of a Wasm light client contract. + +```shell +simd tx ibc-wasm store-code [path/to/wasm-file] [flags] +``` + +`path/to/wasm-file` is the path to the `.wasm` or `.wasm.gz` file. + +#### `migrate-contract` + +The `migrate-contract` command allows users to broadcast a transaction with a `MsgMigrateContract` to migrate the contract for a given light client to a new byte code denoted by the given checksum. + +```shell +simd tx ibc-wasm migrate-contract [client-id] [checksum] [migrate-msg] +``` + +The migrate message must not be emptied and is expected to be a JSON-encoded string. + +### Query + +The `query` commands allow users to query `08-wasm` state. + +```shell +simd query ibc-wasm --help +``` + +#### `checksums` + +The `checksums` command allows users to query the list of checksums of Wasm light client contracts stored in the Wasm VM via the `MsgStoreCode`. The checksums are hex-encoded. + +```shell +simd query ibc-wasm checksums [flags] +``` + +Example: + +```shell +simd query ibc-wasm checksums +``` + +Example Output: + +```shell +checksums: +- c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64 +pagination: + next_key: null + total: "1" +``` + +#### `code` + +The `code` command allows users to query the Wasm byte code of a light client contract given the provided input checksum. + +```shell +./simd q ibc-wasm code +``` + +Example: + +```shell +simd query ibc-wasm code c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64 +``` + +Example Output: + +```shell +code: AGFzb...AqBBE= +``` + +## gRPC + +A user can query the `08-wasm` module using gRPC endpoints. + +### `Checksums` + +The `Checksums` endpoint allows users to query the list of checksums of Wasm light client contracts stored in the Wasm VM via the `MsgStoreCode`. + +```shell +ibc.lightclients.wasm.v1.Query/Checksums +``` + +Example: + +```shell +grpcurl -plaintext \ + -d '{}' \ + localhost:9090 \ + ibc.lightclients.wasm.v1.Query/Checksums +``` + +Example output: + +```shell +{ + "checksums": [ + "c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64" + ], + "pagination": { + "total": "1" + } +} +``` + +### `Code` + +The `Code` endpoint allows users to query the Wasm byte code of a light client contract given the provided input checksum. + +```shell +ibc.lightclients.wasm.v1.Query/Code +``` + +Example: + +```shell +grpcurl -plaintext \ + -d '{"checksum":"c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64"}' \ + localhost:9090 \ + ibc.lightclients.wasm.v1.Query/Code +``` + +Example output: + +```shell +{ + "code": AGFzb...AqBBE= +} +``` diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/_category_.json b/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/_category_.json new file mode 100644 index 0000000..51c4eb7 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/04-wasm/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Wasm", + "position": 4, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v7.8.x/03-light-clients/_category_.json b/docs/versioned_docs/version-v7.8.x/03-light-clients/_category_.json new file mode 100644 index 0000000..e42f1b9 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/03-light-clients/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "IBC Light Clients", + "position": 3, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/01-overview.md b/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/01-overview.md new file mode 100644 index 0000000..81d7e9e --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/01-overview.md @@ -0,0 +1,54 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /middleware/ics29-fee/overview +--- + +# Overview + +:::note Synopsis +Learn about what the Fee Middleware module is, and how to build custom modules that utilize the Fee Middleware functionality +::: + +## What is the Fee Middleware module? + +IBC does not depend on relayer operators for transaction verification. However, the relayer infrastructure ensures liveness of the Interchain network — operators listen for packets sent through channels opened between chains, and perform the vital service of ferrying these packets (and proof of the transaction on the sending chain/receipt on the receiving chain) to the clients on each side of the channel. + +Though relaying is permissionless and completely decentralized and accessible, it does come with operational costs. Running full nodes to query transaction proofs and paying for transaction fees associated with IBC packets are two of the primary cost burdens which have driven the overall discussion on **a general, in-protocol incentivization mechanism for relayers**. + +Initially, a [simple proposal](https://github.com/cosmos/ibc/pull/577/files) was created to incentivize relaying on ICS20 token transfers on the destination chain. However, the proposal was specific to ICS20 token transfers and would have to be reimplemented in this format on every other IBC application module. + +After much discussion, the proposal was expanded to a [general incentivisation design](https://github.com/cosmos/ibc/tree/master/spec/app/ics-029-fee-payment) that can be adopted by any ICS application protocol as [middleware](../../01-ibc/04-middleware/01-develop.md). + +## Concepts + +ICS29 fee payments in this middleware design are built on the assumption that sender chains are the source of incentives — the chain on which packets are incentivized is the chain that distributes fees to relayer operators. However, as part of the IBC packet flow, messages have to be submitted on both sender and destination chains. This introduces the requirement of a mapping of relayer operator's addresses on both chains. + +To achieve the stated requirements, the **fee middleware module has two main groups of functionality**: + +- Registering of relayer addresses associated with each party involved in relaying the packet on the source chain. This registration process can be automated on start up of relayer infrastructure and happens only once, not every packet flow. + + This is described in the [Fee distribution section](04-fee-distribution.md). + +- Escrowing fees by any party which will be paid out to each rightful party on completion of the packet lifecycle. + + This is described in the [Fee messages section](03-msgs.md). + +We complete the introduction by giving a list of definitions of relevant terminology. + +`Forward relayer`: The relayer that submits the `MsgRecvPacket` message for a given packet (on the destination chain). + +`Reverse relayer`: The relayer that submits the `MsgAcknowledgement` message for a given packet (on the source chain). + +`Timeout relayer`: The relayer that submits the `MsgTimeout` or `MsgTimeoutOnClose` messages for a given packet (on the source chain). + +`Payee`: The account address on the source chain to be paid on completion of the packet lifecycle. The packet lifecycle on the source chain completes with the receipt of a `MsgTimeout`/`MsgTimeoutOnClose` or a `MsgAcknowledgement`. + +`Counterparty payee`: The account address to be paid on completion of the packet lifecycle on the destination chain. The package lifecycle on the destination chain completes with a successful `MsgRecvPacket`. + +`Refund address`: The address of the account paying for the incentivization of packet relaying. The account is refunded timeout fees upon successful acknowledgement. In the event of a packet timeout, both acknowledgement and receive fees are refunded. + +## Known Limitations + +The first version of fee payments middleware will only support incentivisation of new channels, however, channel upgradeability will enable incentivisation of all existing channels. diff --git a/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/02-integration.md b/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/02-integration.md new file mode 100644 index 0000000..0cac856 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/02-integration.md @@ -0,0 +1,174 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /middleware/ics29-fee/integration +--- + + +# Integration + +:::note Synopsis +Learn how to configure the Fee Middleware module with IBC applications. The following document is intended for developers building on top of the Cosmos SDK and only applies for Cosmos SDK chains. +::: + +## Pre-requisite Readings + +- [IBC middleware development](../../01-ibc/04-middleware/01-develop.md) +- [IBC middleware integration](../../01-ibc/04-middleware/02-integration.md) + +The Fee Middleware module, as the name suggests, plays the role of an IBC middleware and as such must be configured by chain developers to route and handle IBC messages correctly. +For Cosmos SDK chains this setup is done via the `app/app.go` file, where modules are constructed and configured in order to bootstrap the blockchain application. + +## Example integration of the Fee Middleware module + +```go +// app.go + +// Register the AppModule for the fee middleware module +ModuleBasics = module.NewBasicManager( + ... + ibcfee.AppModuleBasic{}, + ... +) + +... + +// Add module account permissions for the fee middleware module +maccPerms = map[string][]string{ + ... + ibcfeetypes.ModuleName: nil, +} + +... + +// Add fee middleware Keeper +type App struct { + ... + + IBCFeeKeeper ibcfeekeeper.Keeper + + ... +} + +... + +// Create store keys +keys := sdk.NewKVStoreKeys( + ... + ibcfeetypes.StoreKey, + ... +) + +... + +app.IBCFeeKeeper = ibcfeekeeper.NewKeeper( + appCodec, keys[ibcfeetypes.StoreKey], + app.IBCKeeper.ChannelKeeper, // may be replaced with IBC middleware + app.IBCKeeper.ChannelKeeper, + &app.IBCKeeper.PortKeeper, app.AccountKeeper, app.BankKeeper, +) + + +// See the section below for configuring an application stack with the fee middleware module + +... + +// Register fee middleware AppModule +app.moduleManager = module.NewManager( + ... + ibcfee.NewAppModule(app.IBCFeeKeeper), +) + +... + +// Add fee middleware to begin blocker logic +app.moduleManager.SetOrderBeginBlockers( + ... + ibcfeetypes.ModuleName, + ... +) + +// Add fee middleware to end blocker logic +app.moduleManager.SetOrderEndBlockers( + ... + ibcfeetypes.ModuleName, + ... +) + +// Add fee middleware to init genesis logic +app.moduleManager.SetOrderInitGenesis( + ... + ibcfeetypes.ModuleName, + ... +) +``` + +## Configuring an application stack with Fee Middleware + +As mentioned in [IBC middleware development](../../01-ibc/04-middleware/01-develop.md) an application stack may be composed of many or no middlewares that nest a base application. +These layers form the complete set of application logic that enable developers to build composable and flexible IBC application stacks. +For example, an application stack may be just a single base application like `transfer`, however, the same application stack composed with `29-fee` will nest the `transfer` base application +by wrapping it with the Fee Middleware module. + +### Transfer + +See below for an example of how to create an application stack using `transfer` and `29-fee`. +The following `transferStack` is configured in `app/app.go` and added to the IBC `Router`. +The in-line comments describe the execution flow of packets between the application stack and IBC core. + +```go +// Create Transfer Stack +// SendPacket, since it is originating from the application to core IBC: +// transferKeeper.SendPacket -> fee.SendPacket -> channel.SendPacket + +// RecvPacket, message that originates from core IBC and goes down to app, the flow is the other way +// channel.RecvPacket -> fee.OnRecvPacket -> transfer.OnRecvPacket + +// transfer stack contains (from top to bottom): +// - IBC Fee Middleware +// - Transfer + +// create IBC module from bottom to top of stack +var transferStack porttypes.IBCModule +transferStack = transfer.NewIBCModule(app.TransferKeeper) +transferStack = ibcfee.NewIBCMiddleware(transferStack, app.IBCFeeKeeper) + +// Add transfer stack to IBC Router +ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) +``` + +### Interchain Accounts + +See below for an example of how to create an application stack using `27-interchain-accounts` and `29-fee`. +The following `icaControllerStack` and `icaHostStack` are configured in `app/app.go` and added to the IBC `Router` with the associated authentication module. +The in-line comments describe the execution flow of packets between the application stack and IBC core. + +```go +// Create Interchain Accounts Stack +// SendPacket, since it is originating from the application to core IBC: +// icaAuthModuleKeeper.SendTx -> icaController.SendPacket -> fee.SendPacket -> channel.SendPacket + +// initialize ICA module with mock module as the authentication module on the controller side +var icaControllerStack porttypes.IBCModule +icaControllerStack = ibcmock.NewIBCModule(&mockModule, ibcmock.NewMockIBCApp("", scopedICAMockKeeper)) +app.ICAAuthModule = icaControllerStack.(ibcmock.IBCModule) +icaControllerStack = icacontroller.NewIBCMiddleware(icaControllerStack, app.ICAControllerKeeper) +icaControllerStack = ibcfee.NewIBCMiddleware(icaControllerStack, app.IBCFeeKeeper) + +// RecvPacket, message that originates from core IBC and goes down to app, the flow is: +// channel.RecvPacket -> fee.OnRecvPacket -> icaHost.OnRecvPacket + +var icaHostStack porttypes.IBCModule +icaHostStack = icahost.NewIBCModule(app.ICAHostKeeper) +icaHostStack = ibcfee.NewIBCMiddleware(icaHostStack, app.IBCFeeKeeper) + +// Add authentication module, controller and host to IBC router +ibcRouter. + // the ICA Controller middleware needs to be explicitly added to the IBC Router because the + // ICA controller module owns the port capability for ICA. The ICA authentication module + // owns the channel capability. + AddRoute(ibcmock.ModuleName+icacontrollertypes.SubModuleName, icaControllerStack) // ica with mock auth module stack route to ica (top level of middleware stack) + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icahosttypes.SubModuleName, icaHostStack). +``` diff --git a/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/03-msgs.md b/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/03-msgs.md new file mode 100644 index 0000000..692f749 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/03-msgs.md @@ -0,0 +1,96 @@ +--- +title: Fee Messages +sidebar_label: Fee Messages +sidebar_position: 3 +slug: /middleware/ics29-fee/msgs +--- + + +# Fee messages + +:::note Synopsis +Learn about the different ways to pay for fees, how the fees are paid out and what happens when not enough escrowed fees are available for payout +::: + +## Escrowing fees + +The fee middleware module exposes two different ways to pay fees for relaying IBC packets: + +1. `MsgPayPacketFee`, which enables the escrowing of fees for a packet at the next sequence send and should be combined into one `MultiMsgTx` with the message that will be paid for. + + Note that the `Relayers` field has been set up to allow for an optional whitelist of relayers permitted to receive this fee, however, this feature has not yet been enabled at this time. + + ```go + type MsgPayPacketFee struct{ + // fee encapsulates the recv, ack and timeout fees associated with an IBC packet + Fee Fee + // the source port unique identifier + SourcePortId string + // the source channel unique identifier + SourceChannelId string + // account address to refund fee if necessary + Signer string + // optional list of relayers permitted to the receive packet fee + Relayers []string + } + ``` + + The `Fee` message contained in this synchronous fee payment method configures different fees which will be paid out for `MsgRecvPacket`, `MsgAcknowledgement`, and `MsgTimeout`/`MsgTimeoutOnClose`. + + ```go + type Fee struct { + RecvFee types.Coins + AckFee types.Coins + TimeoutFee types.Coins + } + ``` + + The diagram below shows the `MultiMsgTx` with the `MsgTransfer` coming from a token transfer message, along with `MsgPayPacketFee`. + + ![msgpaypacket.png](./images/msgpaypacket.png) + +2. `MsgPayPacketFeeAsync`, which enables the asynchronous escrowing of fees for a specified packet: + + Note that a packet can be 'topped up' multiple times with additional fees of any coin denomination by broadcasting multiple `MsgPayPacketFeeAsync` messages. + + ```go + type MsgPayPacketFeeAsync struct { + // unique packet identifier comprised of the channel ID, port ID and sequence + PacketId channeltypes.PacketId + // the packet fee associated with a particular IBC packet + PacketFee PacketFee + } + ``` + + where the `PacketFee` also specifies the `Fee` to be paid as well as the refund address for fees which are not paid out + + ```go + type PacketFee struct { + Fee Fee + RefundAddress string + Relayers []string + } + ``` + +The diagram below shows how multiple `MsgPayPacketFeeAsync` can be broadcasted asynchronously. Escrowing of the fee associated with a packet can be carried out by any party because ICS-29 does not dictate a particular fee payer. In fact, chains can choose to simply not expose this fee payment to end users at all and rely on a different module account or even the community pool as the source of relayer incentives. + +![paypacketfeeasync.png](./images/paypacketfeeasync.png) + +Please see our [wiki](https://github.com/cosmos/ibc-go/wiki/Fee-enabled-fungible-token-transfers) for example flows on how to use these messages to incentivise a token transfer channel using a CLI. + +## Paying out the escrowed fees + +Following diagram takes a look at the packet flow for an incentivized token transfer and investigates the several scenario's for paying out the escrowed fees. We assume that the relayers have registered their counterparty address, detailed in the [Fee distribution section](04-fee-distribution.md). + +![feeflow.png](./images/feeflow.png) + +- In the case of a successful transaction, `RecvFee` will be paid out to the designated counterparty payee address which has been registered on the receiver chain and sent back with the `MsgAcknowledgement`, `AckFee` will be paid out to the relayer address which has submitted the `MsgAcknowledgement` on the sending chain (or the registered payee in case one has been registered for the relayer address), and `TimeoutFee` will be reimbursed to the account which escrowed the fee. +- In case of a timeout transaction, `RecvFee` and `AckFee` will be reimbursed. The `TimeoutFee` will be paid to the `Timeout Relayer` (who submits the timeout message to the source chain). + +> Please note that fee payments are built on the assumption that sender chains are the source of incentives — the chain that sends the packets is the same chain where fee payments will occur -- please see the [Fee distribution section](04-fee-distribution.md) to understand the flow for registering payee and counterparty payee (fee receiving) addresses. + +## A locked fee middleware module + +The fee middleware module can become locked if the situation arises that the escrow account for the fees does not have sufficient funds to pay out the fees which have been escrowed for each packet. *This situation indicates a severe bug.* In this case, the fee module will be locked until manual intervention fixes the issue. + +> A locked fee module will simply skip fee logic and continue on to the underlying packet flow. A channel with a locked fee module will temporarily function as a fee disabled channel, and the locking of a fee module will not affect the continued flow of packets over the channel. diff --git a/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/04-fee-distribution.md b/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/04-fee-distribution.md new file mode 100644 index 0000000..aba0bfc --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/04-fee-distribution.md @@ -0,0 +1,114 @@ +--- +title: Fee Distribution +sidebar_label: Fee Distribution +sidebar_position: 4 +slug: /middleware/ics29-fee/fee-distribution +--- + + +# Fee distribution + +:::note Synopsis +Learn about payee registration for the distribution of packet fees. The following document is intended for relayer operators. +::: + +## Pre-requisite readings + +- [Fee Middleware](01-overview.md) + +Packet fees are divided into 3 distinct amounts in order to compensate relayer operators for packet relaying on fee enabled IBC channels. + +- `RecvFee`: The sum of all packet receive fees distributed to a payee for successful execution of `MsgRecvPacket`. +- `AckFee`: The sum of all packet acknowledgement fees distributed to a payee for successful execution of `MsgAcknowledgement`. +- `TimeoutFee`: The sum of all packet timeout fees distributed to a payee for successful execution of `MsgTimeout`. + +## Register a counterparty payee address for forward relaying + +As mentioned in [ICS29 Concepts](01-overview.md#concepts), the forward relayer describes the actor who performs the submission of `MsgRecvPacket` on the destination chain. +Fee distribution for incentivized packet relays takes place on the packet source chain. + +> Relayer operators are expected to register a counterparty payee address, in order to be compensated accordingly with `RecvFee`s upon completion of a packet lifecycle. + +The counterparty payee address registered on the destination chain is encoded into the packet acknowledgement and communicated as such to the source chain for fee distribution. +**If a counterparty payee is not registered for the forward relayer on the destination chain, the escrowed fees will be refunded upon fee distribution.** + +### Relayer operator actions? + +A transaction must be submitted **to the destination chain** including a `CounterpartyPayee` address of an account on the source chain. +The transaction must be signed by the `Relayer`. + +Note: If a module account address is used as the `CounterpartyPayee` but the module has been set as a blocked address in the `BankKeeper`, the refunding to the module account will fail. This is because many modules use invariants to compare internal tracking of module account balances against the actual balance of the account stored in the `BankKeeper`. If a token transfer to the module account occurs without going through this module and updating the account balance of the module on the `BankKeeper`, then invariants may break and unknown behaviour could occur depending on the module implementation. Therefore, if it is desirable to use a module account that is currently blocked, the module developers should be consulted to gauge to possibility of removing the module account from the blocked list. + +```go +type MsgRegisterCounterpartyPayee struct { + // unique port identifier + PortId string + // unique channel identifier + ChannelId string + // the relayer address + Relayer string + // the counterparty payee address + CounterpartyPayee string +} +``` + +> This message is expected to fail if: +> +> - `PortId` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators). +> - `ChannelId` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +> - `Relayer` is an invalid address. +> - `CounterpartyPayee` is empty or contains more than 2048 bytes. + +See below for an example CLI command: + +```bash +simd tx ibc-fee register-counterparty-payee transfer channel-0 \ +cosmos1rsp837a4kvtgp2m4uqzdge0zzu6efqgucm0qdh \ +osmo1v5y0tz01llxzf4c2afml8s3awue0ymju22wxx2 \ +--from cosmos1rsp837a4kvtgp2m4uqzdge0zzu6efqgucm0qdh +``` + +## Register an alternative payee address for reverse and timeout relaying + +As mentioned in [ICS29 Concepts](01-overview.md#concepts), the reverse relayer describes the actor who performs the submission of `MsgAcknowledgement` on the source chain. +Similarly the timeout relayer describes the actor who performs the submission of `MsgTimeout` (or `MsgTimeoutOnClose`) on the source chain. + +> Relayer operators **may choose** to register an optional payee address, in order to be compensated accordingly with `AckFee`s and `TimeoutFee`s upon completion of a packet life cycle. + +If a payee is not registered for the reverse or timeout relayer on the source chain, then fee distribution assumes the default behaviour, where fees are paid out to the relayer account which delivers `MsgAcknowledgement` or `MsgTimeout`/`MsgTimeoutOnClose`. + +### Relayer operator actions + +A transaction must be submitted **to the source chain** including a `Payee` address of an account on the source chain. +The transaction must be signed by the `Relayer`. + +Note: If a module account address is used as the `Payee` it is recommended to [turn off invariant checks](https://github.com/cosmos/ibc-go/blob/71d7480c923f4227453e8a80f51be01ae7ee845e/testing/simapp/app.go#L659) for that module. + +```go +type MsgRegisterPayee struct { + // unique port identifier + PortId string + // unique channel identifier + ChannelId string + // the relayer address + Relayer string + // the payee address + Payee string +} +``` + +> This message is expected to fail if: +> +> - `PortId` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators). +> - `ChannelId` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +> - `Relayer` is an invalid address (see [Cosmos SDK Addresses](https://github.com/cosmos/cosmos-sdk/blob/main/docs/learn/beginner/03-accounts.md#addresses)). +> - `Payee` is an invalid address (see [Cosmos SDK Addresses](https://github.com/cosmos/cosmos-sdk/blob/main/docs/learn/beginner/03-accounts.md#addresses)). + +See below for an example CLI command: + +```bash +simd tx ibc-fee register-payee transfer channel-0 \ +cosmos1rsp837a4kvtgp2m4uqzdge0zzu6efqgucm0qdh \ +cosmos153lf4zntqt33a4v0sm5cytrxyqn78q7kz8j8x5 \ +--from cosmos1rsp837a4kvtgp2m4uqzdge0zzu6efqgucm0qdh +``` diff --git a/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/05-events.md b/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/05-events.md new file mode 100644 index 0000000..dbda482 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/05-events.md @@ -0,0 +1,43 @@ +--- +title: Events +sidebar_label: Events +sidebar_position: 5 +slug: /middleware/ics29-fee/events +--- + + +# Events + +:::note Synopsis +An overview of all events related to ICS-29 +::: + +## `MsgPayPacketFee`, `MsgPayPacketFeeAsync` + +| Type | Attribute Key | Attribute Value | +| ----------------------- | --------------- | --------------- | +| incentivized_ibc_packet | port_id | \{portID\} | +| incentivized_ibc_packet | channel_id | \{channelID\} | +| incentivized_ibc_packet | packet_sequence | \{sequence\} | +| incentivized_ibc_packet | recv_fee | \{recvFee\} | +| incentivized_ibc_packet | ack_fee | \{ackFee\} | +| incentivized_ibc_packet | timeout_fee | \{timeoutFee\} | +| message | module | fee-ibc | + +## `RegisterPayee` + +| Type | Attribute Key | Attribute Value | +| -------------- | ------------- | --------------- | +| register_payee | relayer | \{relayer\} | +| register_payee | payee | \{payee\} | +| register_payee | channel_id | \{channelID\} | +| message | module | fee-ibc | + +## `RegisterCounterpartyPayee` + +| Type | Attribute Key | Attribute Value | +| --------------------------- | ------------------ | --------------------- | +| register_counterparty_payee | relayer | \{relayer\} | +| register_counterparty_payee | counterparty_payee | \{counterpartyPayee\} | +| register_counterparty_payee | channel_id | \{channelID\} | +| message | module | fee-ibc | diff --git a/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/06-end-users.md b/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/06-end-users.md new file mode 100644 index 0000000..7419d03 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/06-end-users.md @@ -0,0 +1,36 @@ +--- +title: End Users +sidebar_label: End Users +sidebar_position: 6 +slug: /middleware/ics29-fee/end-users +--- + + +# For end users + +:::note Synopsis +Learn how to incentivize IBC packets using the ICS29 Fee Middleware module. +::: + +## Pre-requisite readings + +- [Fee Middleware](01-overview.md) + +## Summary + +Different types of end users: + +- CLI users who want to manually incentivize IBC packets +- Client developers + +The Fee Middleware module allows end users to add a 'tip' to each IBC packet which will incentivize relayer operators to relay packets between chains. gRPC endpoints are exposed for client developers as well as a simple CLI for manually incentivizing IBC packets. + +## CLI Users + +For an in depth guide on how to use the ICS29 Fee Middleware module using the CLI please take a look at the [wiki](https://github.com/cosmos/ibc-go/wiki/Fee-enabled-fungible-token-transfers#asynchronous-incentivization-of-a-fungible-token-transfer) on the `ibc-go` repo. + +## Client developers + +Client developers can read more about the relevant ICS29 message types in the [Fee messages section](03-msgs.md). + +[CosmJS](https://github.com/cosmos/cosmjs) is a useful client library for signing and broadcasting Cosmos SDK messages. diff --git a/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/_category_.json b/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/_category_.json new file mode 100644 index 0000000..639bd0e --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Fee Middleware", + "position": 1, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/images/feeflow.png b/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/images/feeflow.png new file mode 100644 index 0000000..4e77529 Binary files /dev/null and b/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/images/feeflow.png differ diff --git a/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/images/msgpaypacket.png b/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/images/msgpaypacket.png new file mode 100644 index 0000000..a454f52 Binary files /dev/null and b/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/images/msgpaypacket.png differ diff --git a/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/images/paypacketfeeasync.png b/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/images/paypacketfeeasync.png new file mode 100644 index 0000000..73d1e04 Binary files /dev/null and b/docs/versioned_docs/version-v7.8.x/04-middleware/01-ics29-fee/images/paypacketfeeasync.png differ diff --git a/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/01-overview.md b/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/01-overview.md new file mode 100644 index 0000000..a5abfd3 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/01-overview.md @@ -0,0 +1,51 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /middleware/callbacks/overview +--- + +# Overview + +Learn about what the Callbacks Middleware is, and how to build custom modules that utilize the Callbacks Middleware functionality + +## What is the Callbacks Middleware? + +IBC was designed with callbacks between core IBC and IBC applications. IBC apps would send a packet to core IBC, and receive a callback on every step of that packet's lifecycle. This allows IBC applications to be built on top of core IBC, and to be able to execute custom logic on packet lifecycle events (e.g. unescrow tokens for ICS-20). + +This setup worked well for off-chain users interacting with IBC applications. However, we are now seeing the desire for secondary applications (e.g. smart contracts, modules) to call into IBC apps as part of their state machine logic and then do some actions on packet lifecycle events. + +The Callbacks Middleware allows for this functionality by allowing the packets of the underlying IBC applications to register callbacks to secondary applications for lifecycle events. These callbacks are then executed by the Callbacks Middleware when the corresponding packet lifecycle event occurs. + +After much discussion, the design was expanded to [an ADR](/architecture/adr-008-app-caller-cbs), and the Callbacks Middleware is an implementation of that ADR. + +## Concepts + +Callbacks Middleware was built with smart contracts in mind, but can be used by any secondary application that wants to allow IBC packets to call into it. Think of the Callbacks Middleware as a bridge between core IBC and a secondary application. + +We have the following definitions: + +- `Underlying IBC application`: The IBC application that is wrapped by the Callbacks Middleware. This is the IBC application that is actually sending and receiving packet lifecycle events from core IBC. For example, the transfer module, or the ICA controller submodule. +- `IBC Actor`: IBC Actor is an on-chain or off-chain entity that can initiate a packet on the underlying IBC application. For example, a smart contract, an off-chain user, or a module that sends a transfer packet are all IBC Actors. +- `Secondary application`: The application that is being called into by the Callbacks Middleware for packet lifecycle events. This is the application that is receiving the callback directly from the Callbacks Middleware module. For example, the `x/wasm` module. +- `Callback Actor`: The on-chain smart contract or module that is registered to receive callbacks from the secondary application. For example, a Wasm smart contract (gatekeeped by the `x/wasm` module). Note that the Callback Actor is not necessarily the same as the IBC Actor. For example, an off-chain user can initiate a packet on the underlying IBC application, but the Callback Actor could be a smart contract. The secondary application may want to check that the IBC Actor is allowed to call into the Callback Actor, for example, by checking that the IBC Actor is the same as the Callback Actor. +- `Callback Address`: Address of the Callback Actor. This is the address that the secondary application will call into when a packet lifecycle event occurs. For example, the address of the Wasm smart contract. +- `Maximum gas limit`: The maximum amount of gas that the Callbacks Middleware will allow the secondary application to use when it executes its custom logic. +- `User defined gas limit`: The amount of gas that the IBC Actor wants to allow the secondary application to use when it executes its custom logic. This is the gas limit that the IBC Actor specifies when it sends a packet to the underlying IBC application. This cannot be greater than the maximum gas limit. + +Think of the secondary application as a bridge between the Callbacks Middleware and the Callback Actor. The secondary application is responsible for executing the custom logic of the Callback Actor when a packet lifecycle event occurs. The secondary application is also responsible for checking that the IBC Actor is allowed to call into the Callback Actor. + +Note that it is possible that the IBC Actor, Secondary Application, and Callback Actor are all the same entity. In which case, the Callback Address should be the secondary application's module address. + +The following diagram shows how a typical `RecvPacket`, `AcknowledgementPacket`, and `TimeoutPacket` execution flow would look like: +![callbacks-middleware](./images/callbackflow.svg) + +And the following diagram shows how a typical `SendPacket` and `WriteAcknowledgement` execution flow would look like: +![callbacks-middleware](./images/ics4-callbackflow.svg) + +## Known Limitations + +- Callbacks are always executed after the underlying IBC application has executed its logic. +- Maximum gas limit is hardcoded manually during wiring. It requires a coordinated upgrade to change the maximum gas limit. +- The receive packet callback does not pass the relayer address to the secondary application. This is so that we can use the same callback for both synchronous and asynchronous acknowledgements. +- The receive packet callback does not pass IBC Actor's address, this is because the IBC Actor lives in the counterparty chain and cannot be trusted. diff --git a/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/02-integration.md b/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/02-integration.md new file mode 100644 index 0000000..e698d59 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/02-integration.md @@ -0,0 +1,108 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /middleware/callbacks/integration +--- + +# Integration + +Learn how to integrate the callbacks middleware with IBC applications. The following document is intended for developers building on top of the Cosmos SDK and only applies for Cosmos SDK chains. + +The callbacks middleware is a minimal and stateless implementation of the IBC middleware interface. It does not have a keeper, nor does it store any state. It simply routes IBC middleware messages to the appropriate callback function, which is implemented by the secondary application. Therefore, it doesn't need to be registered as a module, nor does it need to be added to the module manager. It only needs to be added to the IBC application stack. + +## Pre-requisite Readings + +- [IBC middleware development](../../01-ibc/04-middleware/01-develop.md) +- [IBC middleware integration](../../01-ibc/04-middleware/02-integration.md) + +The callbacks middleware, as the name suggests, plays the role of an IBC middleware and as such must be configured by chain developers to route and handle IBC messages correctly. +For Cosmos SDK chains this setup is done via the `app/app.go` file, where modules are constructed and configured in order to bootstrap the blockchain application. + +## Importing the callbacks middleware + +The callbacks middleware has no stable releases yet. To use it, you need to import the git commit that contains the module with the compatible version of `ibc-go`. To do so, run the following command with the desired git commit in your project: + +```sh +go get github.com/cosmos/ibc-go/modules/apps/callbacks@17cf1260a9cdc5292512acc9bcf6336ef0d917f4 +``` + +You can find the version matrix in [here](../../../../docs/04-middleware/01-callbacks/02-integration.md#importing-the-callbacks-middleware). + +## Configuring an application stack with the callbacks middleware + +As mentioned in [IBC middleware development](../../01-ibc/04-middleware/01-develop.md) an application stack may be composed of many or no middlewares that nest a base application. +These layers form the complete set of application logic that enable developers to build composable and flexible IBC application stacks. +For example, an application stack may just be a single base application like `transfer`, however, the same application stack composed with `29-fee` and `callbacks` will nest the `transfer` base application twice by wrapping it with the Fee Middleware module and then callbacks middleware. + +The callbacks middleware also **requires** a secondary application that will receive the callbacks to implement the [`ContractKeeper`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/callbacks/types/expected_keepers.go#L11-L83). Since the wasm module does not yet support the callbacks middleware, we will use the `mockContractKeeper` module in the examples below. You should replace this with a module that implements `ContractKeeper`. + +### Transfer + +See below for an example of how to create an application stack using `transfer`, `29-fee`, and `callbacks`. Feel free to omit the `29-fee` middleware if you do not want to use it. +The following `transferStack` is configured in `app/app.go` and added to the IBC `Router`. +The in-line comments describe the execution flow of packets between the application stack and IBC core. + +```go +// Create Transfer Stack +// SendPacket, since it is originating from the application to core IBC: +// transferKeeper.SendPacket -> callbacks.SendPacket -> feeKeeper.SendPacket -> channel.SendPacket + +// RecvPacket, message that originates from core IBC and goes down to app, the flow is the other way +// channel.RecvPacket -> fee.OnRecvPacket -> callbacks.OnRecvPacket -> transfer.OnRecvPacket + +// transfer stack contains (from top to bottom): +// - IBC Fee Middleware +// - IBC Callbacks Middleware +// - Transfer + +// create IBC module from bottom to top of stack +var transferStack porttypes.IBCModule +transferStack = transfer.NewIBCModule(app.TransferKeeper) +transferStack = ibccallbacks.NewIBCMiddleware(transferStack, app.IBCFeeKeeper, app.MockContractKeeper, maxCallbackGas) +transferICS4Wrapper := transferStack.(porttypes.ICS4Wrapper) +transferStack = ibcfee.NewIBCMiddleware(transferStack, app.IBCFeeKeeper) +// Since the callbacks middleware itself is an ics4wrapper, it needs to be passed to the transfer keeper +app.TransferKeeper.WithICS4Wrapper(transferICS4Wrapper) + +// Add transfer stack to IBC Router +ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) +``` + +:::warning +The usage of `WithICS4Wrapper` after `transferStack`'s configuration is critical! It allows the callbacks middleware to do `SendPacket` callbacks and asynchronous `ReceivePacket` callbacks. You must do this regardless of whether you are using the `29-fee` middleware or not. +::: + +### Interchain Accounts Controller + +```go +// Create Interchain Accounts Stack +// SendPacket, since it is originating from the application to core IBC: +// icaControllerKeeper.SendTx -> callbacks.SendPacket -> fee.SendPacket -> channel.SendPacket + +// initialize ICA module with mock module as the authentication module on the controller side +var icaControllerStack porttypes.IBCModule +icaControllerStack = ibcmock.NewIBCModule(&mockModule, ibcmock.NewIBCApp("", scopedICAMockKeeper)) +app.ICAAuthModule = icaControllerStack.(ibcmock.IBCModule) +icaControllerStack = icacontroller.NewIBCMiddleware(icaControllerStack, app.ICAControllerKeeper) +icaControllerStack = ibccallbacks.NewIBCMiddleware(icaControllerStack, app.IBCFeeKeeper, app.MockContractKeeper, maxCallbackGas) +icaICS4Wrapper := icaControllerStack.(porttypes.ICS4Wrapper) +icaControllerStack = ibcfee.NewIBCMiddleware(icaControllerStack, app.IBCFeeKeeper) +// Since the callbacks middleware itself is an ics4wrapper, it needs to be passed to the ica controller keeper +app.ICAControllerKeeper.WithICS4Wrapper(icaICS4Wrapper) + +// RecvPacket, message that originates from core IBC and goes down to app, the flow is: +// channel.RecvPacket -> fee.OnRecvPacket -> icaHost.OnRecvPacket + +var icaHostStack porttypes.IBCModule +icaHostStack = icahost.NewIBCModule(app.ICAHostKeeper) +icaHostStack = ibcfee.NewIBCMiddleware(icaHostStack, app.IBCFeeKeeper) + +// Add ICA host and controller to IBC router ibcRouter. +AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). +AddRoute(icahosttypes.SubModuleName, icaHostStack). +``` + +:::warning +The usage of `WithICS4Wrapper` here is also critical! +::: diff --git a/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/03-interfaces.md b/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/03-interfaces.md new file mode 100644 index 0000000..b2c4d84 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/03-interfaces.md @@ -0,0 +1,151 @@ +--- +title: Interfaces +sidebar_label: Interfaces +sidebar_position: 3 +slug: /middleware/callbacks/interfaces +--- + +# Interfaces + +The callbacks middleware requires certain interfaces to be implemented by the underlying IBC applications and the secondary application. If you're simply wiring up the callbacks middleware to an existing IBC application stack and a secondary application such as `icacontroller` and `x/wasm`, you can skip this section. + +## Interfaces for developing the Underlying IBC Application + +### `PacketDataUnmarshaler` + +```go +// PacketDataUnmarshaler defines an optional interface which allows a middleware to +// request the packet data to be unmarshaled by the base application. +type PacketDataUnmarshaler interface { + // UnmarshalPacketData unmarshals the packet data into a concrete type + UnmarshalPacketData([]byte) (interface{}, error) +} +``` + +The callbacks middleware **requires** the underlying ibc application to implement the [`PacketDataUnmarshaler`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/core/05-port/types/module.go#L142-L147) interface so that it can unmarshal the packet data bytes into the appropriate packet data type. This allows usage of interface functions implemented by the packet data type. The packet data type is expected to implement the `PacketDataProvider` interface (see section below), which is used to parse the callback data that is currently stored in the packet memo field for `transfer` and `ica` packets as a JSON string. See its implementation in the [`transfer`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/transfer/ibc_module.go#L303-L313) and [`icacontroller`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/27-interchain-accounts/controller/ibc_middleware.go#L258-L268) modules for reference. + +If the underlying application is a middleware itself, then it can implement this interface by simply passing the function call to its underlying application. See its implementation in the [`fee middleware`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/29-fee/ibc_middleware.go#L368-L378) for reference. + +### `PacketDataProvider` + +```go +// PacketDataProvider defines an optional interfaces for retrieving custom packet data stored on behalf of another application. +// An existing problem in the IBC middleware design is the inability for a middleware to define its own packet data type and insert packet sender provided information. +// A short term solution was introduced into several application's packet data to utilize a memo field to carry this information on behalf of another application. +// This interfaces standardizes that behaviour. Upon realization of the ability for middleware's to define their own packet data types, this interface will be deprecated and removed with time. +type PacketDataProvider interface { + // GetCustomPacketData returns the packet data held on behalf of another application. + // The name the information is stored under should be provided as the key. + // If no custom packet data exists for the key, nil should be returned. + GetCustomPacketData(key string) interface{} +} +``` + +The callbacks middleware also **requires** the underlying ibc application's packet data type to implement the [`PacketDataProvider`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/core/exported/packet.go#L43-L52) interface. This interface is used to retrieve the callback data from the packet data (using the memo field in the case of `transfer` and `ica`). For example, see its implementation in the [`transfer`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/transfer/types/packet.go#L85-L105) module. + +Since middlewares do not have packet types, they do not need to implement this interface. + +### `PacketData` + +```go +// PacketData defines an optional interface which an application's packet data structure may implement. +type PacketData interface { + // GetPacketSender returns the sender address of the packet data. + // If the packet sender is unknown or undefined, an empty string should be returned. + GetPacketSender(sourcePortID string) string +} +``` + +[`PacketData`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/core/exported/packet.go#L36-L41) is an optional interface that can be implemented by the underlying ibc application's packet data type. It is used to retrieve the packet sender address from the packet data. The callbacks middleware uses this interface to retrieve the packet sender address and pass it to the callback function during a source callback. If this interface is not implemented, then the callbacks middleware passes and empty string as the sender address. For example, see its implementation in the [`transfer`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/transfer/types/packet.go#L74-L83) and [`ica`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/27-interchain-accounts/types/packet.go#L78-L92) module. + +This interface was added so that secondary applications can retrieve the packet sender address to perform custom authorization logic if needed. + +Since middlewares do not have packet types, they do not need to implement this interface. + +## Interfaces for developing the Secondary Application + +### `ContractKeeper` + +The callbacks middleware requires the secondary application to implement the [`ContractKeeper`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/callbacks/types/expected_keepers.go#L11-L83) interface. The contract keeper will be invoked at each step of the packet lifecycle. When a packet is sent, if callback information is provided, the contract keeper will be invoked via the `IBCSendPacketCallback`. This allows the contract keeper to prevent packet sends when callback information is provided, for example if the sender is unauthorized to perform callbacks on the given information. If the packet send is successful, the contract keeper on the destination (if present) will be invoked when a packet has been received and the acknowledgement is written, this will occur via `IBCReceivePacketCallback`. At the end of the packet lifecycle, when processing acknowledgements or timeouts, the source contract keeper will be invoked either via `IBCOnAcknowledgementPacket` or `IBCOnTimeoutPacket`. Once a packet has been sent, each step of the packet lifecycle can be processed given that a relayer sets the gas limit to be more than or equal to the required `CommitGasLimit`. State changes performed in the callback will only be committed upon successful execution. + +```go +// ContractKeeper defines the entry points exposed to the VM module which invokes a smart contract +type ContractKeeper interface { + // IBCSendPacketCallback is called in the source chain when a PacketSend is executed. The + // packetSenderAddress is determined by the underlying module, and may be empty if the sender is + // unknown or undefined. The contract is expected to handle the callback within the user defined + // gas limit, and handle any errors, or panics gracefully. + // This entry point is called with a cached context. If an error is returned, then the changes in + // this context will not be persisted, and the error will be propagated to the underlying IBC + // application, resulting in a packet send failure. + // + // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform + // validation on the origin of a given packet. It is recommended to perform the same validation + // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This + // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. + IBCSendPacketCallback( + cachedCtx sdk.Context, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + packetData []byte, + contractAddress, + packetSenderAddress string, + ) error + // IBCOnAcknowledgementPacketCallback is called in the source chain when a packet acknowledgement + // is received. The packetSenderAddress is determined by the underlying module, and may be empty if + // the sender is unknown or undefined. The contract is expected to handle the callback within the + // user defined gas limit, and handle any errors, or panics gracefully. + // This entry point is called with a cached context. If an error is returned, then the changes in + // this context will not be persisted, but the packet lifecycle will not be blocked. + // + // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform + // validation on the origin of a given packet. It is recommended to perform the same validation + // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This + // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. + IBCOnAcknowledgementPacketCallback( + cachedCtx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, + contractAddress, + packetSenderAddress string, + ) error + // IBCOnTimeoutPacketCallback is called in the source chain when a packet is not received before + // the timeout height. The packetSenderAddress is determined by the underlying module, and may be + // empty if the sender is unknown or undefined. The contract is expected to handle the callback + // within the user defined gas limit, and handle any error, out of gas, or panics gracefully. + // This entry point is called with a cached context. If an error is returned, then the changes in + // this context will not be persisted, but the packet lifecycle will not be blocked. + // + // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform + // validation on the origin of a given packet. It is recommended to perform the same validation + // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This + // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. + IBCOnTimeoutPacketCallback( + cachedCtx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, + contractAddress, + packetSenderAddress string, + ) error + // IBCReceivePacketCallback is called in the destination chain when a packet acknowledgement is written. + // The contract is expected to handle the callback within the user defined gas limit, and handle any errors, + // out of gas, or panics gracefully. + // This entry point is called with a cached context. If an error is returned, then the changes in + // this context will not be persisted, but the packet lifecycle will not be blocked. + IBCReceivePacketCallback( + cachedCtx sdk.Context, + packet ibcexported.PacketI, + ack ibcexported.Acknowledgement, + contractAddress string, + ) error +} +``` + +These are the callback entry points exposed to the secondary application. The secondary application is expected to execute its custom logic within these entry points. The callbacks middleware will handle the execution of these callbacks and revert the state if needed. + +:::tip +Note that the source callback entry points are provided with the `packetSenderAddress` and MAY choose to use this to perform validation on the origin of a given packet. It is recommended to perform the same validation on all source chain callbacks (SendPacket, AcknowledgePacket, TimeoutPacket). This defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. +::: diff --git a/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/04-events.md b/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/04-events.md new file mode 100644 index 0000000..448a56f --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/04-events.md @@ -0,0 +1,39 @@ +--- +title: Events +sidebar_label: Events +sidebar_position: 4 +slug: /middleware/callbacks/events +--- + +# Events + +An overview of all events related to the callbacks middleware. There are two types of events, `"ibc_src_callback"` and `"ibc_dest_callback"`. + +## Shared Attributes + +Both of these event types share the following attributes: + +| **Attribute Key** | **Attribute Values** | **Optional** | +|:-------------------------:|:---------------------------------------------------------------------------------------:|:------------------:| +| module | "ibccallbacks" | | +| callback_type | **One of**: "send_packet", "acknowledgement_packet", "timeout_packet", "receive_packet" | | +| callback_address | string | | +| callback_exec_gas_limit | string (parsed from uint64) | | +| callback_commit_gas_limit | string (parsed from uint64) | | +| packet_sequence | string (parsed from uint64) | | +| callback_result | **One of**: "success", "failure" | | +| callback_error | string (parsed from callback err) | Yes, if err != nil | + +## `ibc_src_callback` Attributes + +| **Attribute Key** | **Attribute Values** | +|:------------------:|:------------------------:| +| packet_src_port | string (sourcePortID) | +| packet_src_channel | string (sourceChannelID) | + +## `ibc_dest_callback` Attributes + +| **Attribute Key** | **Attribute Values** | +|:-------------------:|:------------------------:| +| packet_dest_port | string (destPortID) | +| packet_dest_channel | string (destChannelID) | diff --git a/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/05-end-users.md b/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/05-end-users.md new file mode 100644 index 0000000..2142168 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/05-end-users.md @@ -0,0 +1,96 @@ +--- +title: End Users +sidebar_label: End Users +sidebar_position: 5 +slug: /middleware/callbacks/end-users +--- + +# Usage + +This section explains how to use the callbacks middleware from the perspective of an IBC Actor. Callbacks middleware provides two types of callbacks: + +- Source callbacks: + - `SendPacket` callback + - `OnAcknowledgementPacket` callback + - `OnTimeoutPacket` callback +- Destination callbacks: + - `ReceivePacket` callback + +For a given channel, the source callbacks are supported if the source chain has the callbacks middleware wired up in the channel's IBC stack. Similarly, the destination callbacks are supported if the destination chain has the callbacks middleware wired up in the channel's IBC stack. + +:::tip +Callbacks are always executed after the packet has been processed by the underlying IBC module. +::: + +:::warning +If the underlying application module is doing an asynchronous acknowledgement on packet receive (for example, if the [packet forward middleware](https://github.com/cosmos/ibc-apps/tree/main/middleware/packet-forward-middleware) is in the stack, and is being used by this packet), then the callbacks middleware will execute the `ReceivePacket` callback after the acknowledgement has been received. +::: + +## Source Callbacks + +Source callbacks are natively supported in the following ibc modules (if they are wrapped by the callbacks middleware): + +- `transfer` +- `icacontroller` + +To have your source callbacks be processed by the callbacks middleware, you must set the memo in the application's packet data to the following format: + +```jsonc +{ + "src_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} +``` + +## Destination Callbacks + +Destination callbacks are natively only supported in the transfer module. Note that wrapping icahost is not supported. This is because icahost should be able to execute an arbitrary transaction anyway, and can call contracts or modules directly. + +To have your destination callbacks processed by the callbacks middleware, you must set the memo in the application's packet data to the following format: + +```jsonc +{ + "dest_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} +``` + +Note that a packet can have both a source and destination callback. + +```jsonc +{ + "src_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + }, + "dest_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} +``` + +# User Defined Gas Limit + +User defined gas limit was added for the following reasons: + +- To prevent callbacks from blocking packet lifecycle. +- To prevent relayers from being able to DOS the callback execution by sending a packet with a low amount of gas. + +:::tip +There is a chain wide parameter that sets the maximum gas limit that a user can set for a callback. This is to prevent a user from setting a gas limit that is too high for relayers. If the `"gas_limit"` is not set in the packet memo, then the maximum gas limit is used. +::: + +These goals are achieved by creating a minimum gas amount required for callback execution. If the relayer provides at least the minimum gas limit for the callback execution, then the packet lifecycle will not be blocked if the callback runs out of gas during execution, and the callback cannot be retried. If the relayer does not provided the minimum amount of gas and the callback executions runs out of gas, the entire tx is reverted and it may be executed again. + +:::tip +`SendPacket` callback is always reverted if the callback execution fails or returns an error for any reason. This is so that the packet is not sent if the callback execution fails. +::: diff --git a/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/06-gas.md b/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/06-gas.md new file mode 100644 index 0000000..27cbf0f --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/06-gas.md @@ -0,0 +1,77 @@ +--- +title: Gas Management +sidebar_label: Gas Management +sidebar_position: 6 +slug: /middleware/callbacks/gas +--- + +# Gas Management + +## Overview + +Executing arbitrary code on a chain can be arbitrarily expensive. In general, a callback may consume infinite gas (think of a callback that loops forever). This is problematic for a few reasons: + +- It can block the packet lifecycle. +- It can be used to consume all of the relayer's funds and gas. +- A relayer can DOS the callback execution by sending a packet with a low amount of gas. + +To prevent these, the callbacks middleware introduces two gas limits: a chain wide gas limit (`maxCallbackGas`) and a user defined gas limit. + +### Chain Wide Gas Limit + +Since the callbacks middleware does not have a keeper, it does not use a governance parameter to set the chain wide gas limit. Instead, the chain wide gas limit is passed in as a parameter to the callbacks middleware during initialization. + +```go +// app.go + +maxCallbackGas := uint64(1_000_000) + +var transferStack porttypes.IBCModule +transferStack = transfer.NewIBCModule(app.TransferKeeper) +transferStack = ibcfee.NewIBCMiddleware(transferStack, app.IBCFeeKeeper) +transferStack = ibccallbacks.NewIBCMiddleware(transferStack, app.IBCFeeKeeper, app.MockContractKeeper, maxCallbackGas) +// Since the callbacks middleware itself is an ics4wrapper, it needs to be passed to the transfer keeper +app.TransferKeeper.WithICS4Wrapper(transferStack.(porttypes.ICS4Wrapper)) + +// Add transfer stack to IBC Router +ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) +``` + +### User Defined Gas Limit + +The user defined gas limit is set by the IBC Actor during packet creation. The user defined gas limit is set in the packet memo. If the user defined gas limit is not set or if the user defined gas limit is greater than the chain wide gas limit, then the chain wide gas limit is used as the user defined gas limit. + +```jsonc +{ + "src_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + }, + "dest_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} +``` + +## Gas Limit Enforcement + +During a callback execution, there are three types of gas limits that are enforced: + +- User defined gas limit +- Chain wide gas limit +- Context gas limit (amount of gas that the relayer has left for this execution) + +Chain wide gas limit is used as a maximum to the user defined gas limit as explained in the [previous section](#user-defined-gas-limit). It may also be used as a default value if no user gas limit is provided. Therefore, we can ignore the chain wide gas limit for the rest of this section and work with the minimum of the chain wide gas limit and user defined gas limit. This minimum is called the commit gas limit. + +The gas limit enforcement is done by executing the callback inside a cached context with a new gas meter. The gas meter is initialized with the minimum of the commit gas limit and the context gas limit. This minimum is called the execution gas limit. We say that retries are allowed if `context gas limit < commit gas limit`. Otherwise, we say that retries are not allowed. + +If the callback execution fails due to an out of gas error, then the middleware checks if retries are allowed. If retries are not allowed, then it recovers from the out of gas error, consumes execution gas limit from the original context, and continues with the packet life cycle. If retries are allowed, then it panics with an out of gas error to revert the entire tx. The packet can then be submitted again with a higher gas limit. The out of gas panic descriptor is shown below. + +```go +fmt.Sprintf("ibc %s callback out of gas; commitGasLimit: %d", callbackType, callbackData.CommitGasLimit)} +``` + +If the callback execution does not fail due to an out of gas error then the callbacks middleware does not block the packet life cycle regardless of whether retries are allowed or not. diff --git a/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/_category_.json b/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/_category_.json new file mode 100644 index 0000000..4c2654b --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Callbacks Middleware", + "position": 2, + "link": null +} diff --git a/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/images/callbackflow.svg b/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/images/callbackflow.svg new file mode 100644 index 0000000..df58e93 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/images/callbackflow.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/images/ics4-callbackflow.svg b/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/images/ics4-callbackflow.svg new file mode 100644 index 0000000..4b42440 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/04-middleware/02-callbacks/images/ics4-callbackflow.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/versioned_docs/version-v7.8.x/04-middleware/_category_.json b/docs/versioned_docs/version-v7.8.x/04-middleware/_category_.json new file mode 100644 index 0000000..cc8fa26 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/04-middleware/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "IBC Middleware Modules", + "position": 4, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v7.8.x/05-migrations/01-support-denoms-with-slashes.md b/docs/versioned_docs/version-v7.8.x/05-migrations/01-support-denoms-with-slashes.md new file mode 100644 index 0000000..df8912d --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/05-migrations/01-support-denoms-with-slashes.md @@ -0,0 +1,88 @@ +--- +title: Support transfer of coins whose base denom contains slashes +sidebar_label: Support transfer of coins whose base denom contains slashes +sidebar_position: 1 +slug: /migrations/support-denoms-with-slashes +--- +# Migrating from not supporting base denoms with slashes to supporting base denoms with slashes + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +This document is necessary when chains are upgrading from a version that does not support base denoms with slashes (e.g. v3.0.0) to a version that does (e.g. v3.2.0). All versions of ibc-go smaller than v1.5.0 for the v1.x release line, v2.3.0 for the v2.x release line, and v3.1.0 for the v3.x release line do **NOT** support IBC token transfers of coins whose base denoms contain slashes. Therefore the in-place of genesis migration described in this document are required when upgrading. + +If a chain receives coins of a base denom with slashes before it upgrades to supporting it, the receive may pass however the trace information will be incorrect. + +E.g. If a base denom of `testcoin/testcoin/testcoin` is sent to a chain that does not support slashes in the base denom, the receive will be successful. However, the trace information stored on the receiving chain will be: `Trace: "transfer/{channel-id}/testcoin/testcoin", BaseDenom: "testcoin"`. + +This incorrect trace information must be corrected when the chain does upgrade to fully supporting denominations with slashes. + +To do so, chain binaries should include a migration script that will run when the chain upgrades from not supporting base denominations with slashes to supporting base denominations with slashes. + +## Chains + +### ICS20 - Transfer + +The transfer module will now support slashes in base denoms, so we must iterate over current traces to check if any of them are incorrectly formed and correct the trace information. + +### Upgrade Proposal + +```go +app.UpgradeKeeper.SetUpgradeHandler("MigrateTraces", + func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + // transfer module consensus version has been bumped to 2 + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) + +``` + +This is only necessary if there are denom traces in the store with incorrect trace information from previously received coins that had a slash in the base denom. However, it is recommended that any chain upgrading to support base denominations with slashes runs this code for safety. + +For a more detailed sample, please check out the code changes in [this pull request](https://github.com/cosmos/ibc-go/pull/1680). + +### Genesis Migration + +If the chain chooses to add support for slashes in base denoms via genesis export, then the trace information must be corrected during genesis migration. + +The migration code required may look like: + +```go +func migrateGenesisSlashedDenomsUpgrade(appState genutiltypes.AppMap, clientCtx client.Context, genDoc *tmtypes.GenesisDoc) (genutiltypes.AppMap, error) { + if appState[ibctransfertypes.ModuleName] != nil { + transferGenState := &ibctransfertypes.GenesisState{} + clientCtx.Codec.MustUnmarshalJSON(appState[ibctransfertypes.ModuleName], transferGenState) + + substituteTraces := make([]ibctransfertypes.DenomTrace, len(transferGenState.DenomTraces)) + for i, dt := range transferGenState.DenomTraces { + // replace all previous traces with the latest trace if validation passes + // note most traces will have same value + newTrace := ibctransfertypes.ParseDenomTrace(dt.GetFullDenomPath()) + + if err := newTrace.Validate(); err != nil { + substituteTraces[i] = dt + } else { + substituteTraces[i] = newTrace + } + } + + transferGenState.DenomTraces = substituteTraces + + // delete old genesis state + delete(appState, ibctransfertypes.ModuleName) + + // set new ibc transfer genesis state + appState[ibctransfertypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(transferGenState) + } + + return appState, nil +} +``` + +For a more detailed sample, please check out the code changes in [this pull request](https://github.com/cosmos/ibc-go/pull/1528). diff --git a/docs/versioned_docs/version-v7.8.x/05-migrations/02-sdk-to-v1.md b/docs/versioned_docs/version-v7.8.x/05-migrations/02-sdk-to-v1.md new file mode 100644 index 0000000..28b41ab --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/05-migrations/02-sdk-to-v1.md @@ -0,0 +1,195 @@ +--- +title: SDK v0.43 to IBC-Go v1 +sidebar_label: SDK v0.43 to IBC-Go v1 +sidebar_position: 2 +slug: /migrations/sdk-to-v1 +--- +# Migrating to ibc-go + +This file contains information on how to migrate from the IBC module contained in the SDK 0.41.x and 0.42.x lines to the IBC module in the ibc-go repository based on the 0.44 SDK version. + +## Import Changes + +The most obvious changes is import name changes. We need to change: + +- applications -> apps +- cosmos-sdk/x/ibc -> ibc-go + +On my GNU/Linux based machine I used the following commands, executed in order: + +```bash +grep -RiIl 'cosmos-sdk\/x\/ibc\/applications' | xargs sed -i 's/cosmos-sdk\/x\/ibc\/applications/ibc-go\/modules\/apps/g' +``` + +```bash +grep -RiIl 'cosmos-sdk\/x\/ibc' | xargs sed -i 's/cosmos-sdk\/x\/ibc/ibc-go\/modules/g' +``` + +ref: [explanation of the above commands](https://www.internalpointers.com/post/linux-find-and-replace-text-multiple-files) + +Executing these commands out of order will cause issues. + +Feel free to use your own method for modifying import names. + +NOTE: Updating to the `v0.44.0` SDK release and then running `go mod tidy` will cause a downgrade to `v0.42.0` in order to support the old IBC import paths. +Update the import paths before running `go mod tidy`. + +## Chain Upgrades + +Chains may choose to upgrade via an upgrade proposal or genesis upgrades. Both in-place store migrations and genesis migrations are supported. + +**WARNING**: Please read at least the quick guide for [IBC client upgrades](../01-ibc/05-upgrades/00-intro.md) before upgrading your chain. It is highly recommended you do not change the chain-ID during an upgrade, otherwise you must follow the IBC client upgrade instructions. + +Both in-place store migrations and genesis migrations will: + +- migrate the solo machine client state from v1 to v2 protobuf definitions +- prune all solo machine consensus states +- prune all expired tendermint consensus states + +Chains must set a new connection parameter during either in place store migrations or genesis migration. The new parameter, max expected block time, is used to enforce packet processing delays on the receiving end of an IBC packet flow. Checkout the [docs](https://github.com/cosmos/ibc-go/blob/release/v1.0.x/docs/ibc/proto-docs.md#params-2) for more information. + +### In-Place Store Migrations + +The new chain binary will need to run migrations in the upgrade handler. The fromVM (previous module version) for the IBC module should be 1. This will allow migrations to be run for IBC updating the version from 1 to 2. + +Ex: + +```go +app.UpgradeKeeper.SetUpgradeHandler("my-upgrade-proposal", + func(ctx sdk.Context, _ upgradetypes.Plan, _ module.VersionMap) (module.VersionMap, error) { + // set max expected block time parameter. Replace the default with your expected value + // https://github.com/cosmos/ibc-go/blob/release/v1.0.x/docs/ibc/proto-docs.md#params-2 + app.IBCKeeper.ConnectionKeeper.SetParams(ctx, ibcconnectiontypes.DefaultParams()) + + fromVM := map[string]uint64{ + ... // other modules + "ibc": 1, + ... + } + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) + +``` + +### Genesis Migrations + +To perform genesis migrations, the following code must be added to your existing migration code. + +```go +// add imports as necessary +import ( + ibcv100 "github.com/cosmos/ibc-go/modules/core/legacy/v100" + ibchost "github.com/cosmos/ibc-go/modules/core/24-host" +) + +... + +// add in migrate cmd function +// expectedTimePerBlock is a new connection parameter +// https://github.com/cosmos/ibc-go/blob/release/v1.0.x/docs/ibc/proto-docs.md#params-2 +newGenState, err = ibcv100.MigrateGenesis(newGenState, clientCtx, *genDoc, expectedTimePerBlock) +if err != nil { + return err +} +``` + +**NOTE:** The genesis chain-id, time and height MUST be updated before migrating IBC, otherwise the tendermint consensus state will not be pruned. + +## IBC Keeper Changes + +The IBC Keeper now takes in the Upgrade Keeper. Please add the chains' Upgrade Keeper after the Staking Keeper: + +```diff + // Create IBC Keeper + app.IBCKeeper = ibckeeper.NewKeeper( +- appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, scopedIBCKeeper, ++ appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, + ) + +``` + +## Proposals + +### UpdateClientProposal + +The `UpdateClient` has been modified to take in two client-identifiers and one initial height. Please see the [documentation](../01-ibc/06-proposals.md) for more information. + +### UpgradeProposal + +A new IBC proposal type has been added, `UpgradeProposal`. This handles an IBC (breaking) Upgrade. +The previous `UpgradedClientState` field in an Upgrade `Plan` has been deprecated in favor of this new proposal type. + +### Proposal Handler Registration + +The `ClientUpdateProposalHandler` has been renamed to `ClientProposalHandler`. +It handles both `UpdateClientProposal`s and `UpgradeProposal`s. + +Add this import: + +```diff ++ ibcclienttypes "github.com/cosmos/ibc-go/modules/core/02-client/types" +``` + +Please ensure the governance module adds the correct route: + +```diff +- AddRoute(ibchost.RouterKey, ibcclient.NewClientUpdateProposalHandler(app.IBCKeeper.ClientKeeper)) ++ AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)) +``` + +NOTE: Simapp registration was incorrect in the 0.41.x releases. The `UpdateClient` proposal handler should be registered with the router key belonging to `ibc-go/core/02-client/types` +as shown in the diffs above. + +### Proposal CLI Registration + +Please ensure both proposal type CLI commands are registered on the governance module by adding the following arguments to `gov.NewAppModuleBasic()`: + +Add the following import: + +```diff ++ ibcclientclient "github.com/cosmos/ibc-go/modules/core/02-client/client" +``` + +Register the cli commands: + +```diff + gov.NewAppModuleBasic( + paramsclient.ProposalHandler, distrclient.ProposalHandler, upgradeclient.ProposalHandler, upgradeclient.CancelProposalHandler, ++ ibcclientclient.UpdateClientProposalHandler, ibcclientclient.UpgradeProposalHandler, + ), +``` + +REST routes are not supported for these proposals. + +## Proto file changes + +The gRPC querier service endpoints have changed slightly. The previous files used `v1beta1` gRPC route, this has been updated to `v1`. + +The solo machine has replaced the FrozenSequence uint64 field with a IsFrozen boolean field. The package has been bumped from `v1` to `v2` + +## IBC callback changes + +### OnRecvPacket + +Application developers need to update their `OnRecvPacket` callback logic. + +The `OnRecvPacket` callback has been modified to only return the acknowledgement. The acknowledgement returned must implement the `Acknowledgement` interface. The acknowledgement should indicate if it represents a successful processing of a packet by returning true on `Success()` and false in all other cases. A return value of false on `Success()` will result in all state changes which occurred in the callback being discarded. More information can be found in the [documentation](../01-ibc/03-apps/02-ibcmodule.md#receiving-packets). + +The `OnRecvPacket`, `OnAcknowledgementPacket`, and `OnTimeoutPacket` callbacks are now passed the `sdk.AccAddress` of the relayer who relayed the IBC packet. Applications may use or ignore this information. + +## IBC Event changes + +The `packet_data` attribute has been deprecated in favor of `packet_data_hex`, in order to provide standardized encoding/decoding of packet data in events. While the `packet_data` event still exists, all relayers and IBC Event consumers are strongly encouraged to switch over to using `packet_data_hex` as soon as possible. + +The `packet_ack` attribute has also been deprecated in favor of `packet_ack_hex` for the same reason stated above. All relayers and IBC Event consumers are strongly encouraged to switch over to using `packet_ack_hex` as soon as possible. + +The `consensus_height` attribute has been removed in the Misbehaviour event emitted. IBC clients no longer have a frozen height and misbehaviour does not necessarily have an associated height. + +## Relevant SDK changes + +- (codec) [\#9226](https://github.com/cosmos/cosmos-sdk/pull/9226) Rename codec interfaces and methods, to follow a general Go interfaces: + - `codec.Marshaler` → `codec.Codec` (this defines objects which serialize other objects) + - `codec.BinaryMarshaler` → `codec.BinaryCodec` + - `codec.JSONMarshaler` → `codec.JSONCodec` + - Removed `BinaryBare` suffix from `BinaryCodec` methods (`MarshalBinaryBare`, `UnmarshalBinaryBare`, ...) + - Removed `Binary` infix from `BinaryCodec` methods (`MarshalBinaryLengthPrefixed`, `UnmarshalBinaryLengthPrefixed`, ...) diff --git a/docs/versioned_docs/version-v7.8.x/05-migrations/03-v1-to-v2.md b/docs/versioned_docs/version-v7.8.x/05-migrations/03-v1-to-v2.md new file mode 100644 index 0000000..39fa738 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/05-migrations/03-v1-to-v2.md @@ -0,0 +1,60 @@ +--- +title: IBC-Go v1 to v2 +sidebar_label: IBC-Go v1 to v2 +sidebar_position: 3 +slug: /migrations/v1-to-v2 +--- +# Migrating from ibc-go v1 to v2 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go -> github.com/cosmos/ibc-go/v2 +``` + +## Chains + +- No relevant changes were made in this release. + +## IBC Apps + +A new function has been added to the app module interface: + +```go +// NegotiateAppVersion performs application version negotiation given the provided channel ordering, connectionID, portID, counterparty and proposed version. + // An error is returned if version negotiation cannot be performed. For example, an application module implementing this interface + // may decide to return an error in the event of the proposed version being incompatible with it's own + NegotiateAppVersion( + ctx sdk.Context, + order channeltypes.Order, + connectionID string, + portID string, + counterparty channeltypes.Counterparty, + proposedVersion string, + ) (version string, err error) +} +``` + +This function should perform application version negotiation and return the negotiated version. If the version cannot be negotiated, an error should be returned. This function is only used on the client side. + +### sdk.Result removed + +sdk.Result has been removed as a return value in the application callbacks. Previously it was being discarded by core IBC and was thus unused. + +## Relayers + +A new gRPC has been added to 05-port, `AppVersion`. It returns the negotiated app version. This function should be used for the `ChanOpenTry` channel handshake step to decide upon the application version which should be set in the channel. + +## IBC Light Clients + +- No relevant changes were made in this release. diff --git a/docs/versioned_docs/version-v7.8.x/05-migrations/04-v2-to-v3.md b/docs/versioned_docs/version-v7.8.x/05-migrations/04-v2-to-v3.md new file mode 100644 index 0000000..6eb64c2 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/05-migrations/04-v2-to-v3.md @@ -0,0 +1,186 @@ +--- +title: IBC-Go v2 to v3 +sidebar_label: IBC-Go v2 to v3 +sidebar_position: 4 +slug: /migrations/v2-to-v3 +--- +# Migrating from ibc-go v2 to v3 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go/v2 -> github.com/cosmos/ibc-go/v3 +``` + +No genesis or in-place migrations are required when upgrading from v1 or v2 of ibc-go. + +## Chains + +### ICS20 + +The `transferkeeper.NewKeeper(...)` now takes in an ICS4Wrapper. +The ICS4Wrapper should be the IBC Channel Keeper unless ICS 20 is being connected to a middleware application. + +### ICS27 + +ICS27 Interchain Accounts has been added as a supported IBC application of ibc-go. +Please see the [ICS27 documentation](../02-apps/02-interchain-accounts/01-overview.md) for more information. + +### Upgrade Proposal + +If the chain will adopt ICS27, it must set the appropriate params during the execution of the upgrade handler in `app.go`: + +```go +app.UpgradeKeeper.SetUpgradeHandler("v3", + func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + // set the ICS27 consensus version so InitGenesis is not run + fromVM[icatypes.ModuleName] = icamodule.ConsensusVersion() + + // create ICS27 Controller submodule params + controllerParams := icacontrollertypes.Params{ + ControllerEnabled: true, + } + + // create ICS27 Host submodule params + hostParams := icahosttypes.Params{ + HostEnabled: true, + AllowMessages: []string{"/cosmos.bank.v1beta1.MsgSend", ...}, + } + + // initialize ICS27 module + icamodule.InitModule(ctx, controllerParams, hostParams) + + ... + + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) + +``` + +The host and controller submodule params only need to be set if the chain integrates those submodules. +For example, if a chain chooses not to integrate a controller submodule, it may pass empty params into `InitModule`. + +#### Add `StoreUpgrades` for ICS27 module + +For ICS27 it is also necessary to [manually add store upgrades](https://docs.cosmos.network/main/learn/advanced/upgrade#add-storeupgrades-for-new-modules) for the new ICS27 module and then configure the store loader to apply those upgrades in `app.go`: + +```go +if upgradeInfo.Name == "v3" && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { + storeUpgrades := store.StoreUpgrades{ + Added: []string{icacontrollertypes.StoreKey, icahosttypes.StoreKey}, + } + + app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) +} +``` + +This ensures that the new module's stores are added to the multistore before the migrations begin. +The host and controller submodule keys only need to be added if the chain integrates those submodules. +For example, if a chain chooses not to integrate a controller submodule, it does not need to add the controller key to the `Added` field. + +### Genesis migrations + +If the chain will adopt ICS27 and chooses to upgrade via a genesis export, then the ICS27 parameters must be set during genesis migration. + +The migration code required may look like: + +```go + controllerGenesisState := icatypes.DefaultControllerGenesis() + // overwrite parameters as desired + controllerGenesisState.Params = icacontrollertypes.Params{ + ControllerEnabled: true, + } + + hostGenesisState := icatypes.DefaultHostGenesis() + // overwrite parameters as desired + hostGenesisState.Params = icahosttypes.Params{ + HostEnabled: true, + AllowMessages: []string{"/cosmos.bank.v1beta1.MsgSend", ...}, + } + + icaGenesisState := icatypes.NewGenesisState(controllerGenesisState, hostGenesisState) + + // set new ics27 genesis state + appState[icatypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(icaGenesisState) +``` + +### Ante decorator + +The field of type `channelkeeper.Keeper` in the `AnteDecorator` structure has been replaced with a field of type `*keeper.Keeper`: + +```diff +type AnteDecorator struct { +- k channelkeeper.Keeper ++ k *keeper.Keeper +} + +- func NewAnteDecorator(k channelkeeper.Keeper) AnteDecorator { ++ func NewAnteDecorator(k *keeper.Keeper) AnteDecorator { + return AnteDecorator{k: k} +} +``` + +## IBC Apps + +### `OnChanOpenTry` must return negotiated application version + +The `OnChanOpenTry` application callback has been modified. +The return signature now includes the application version. +IBC applications must perform application version negotiation in `OnChanOpenTry` using the counterparty version. +The negotiated application version then must be returned in `OnChanOpenTry` to core IBC. +Core IBC will set this version in the TRYOPEN channel. + +### `OnChanOpenAck` will take additional `counterpartyChannelID` argument + +The `OnChanOpenAck` application callback has been modified. +The arguments now include the counterparty channel id. + +### `NegotiateAppVersion` removed from `IBCModule` interface + +Previously this logic was handled by the `NegotiateAppVersion` function. +Relayers would query this function before calling `ChanOpenTry`. +Applications would then need to verify that the passed in version was correct. +Now applications will perform this version negotiation during the channel handshake, thus removing the need for `NegotiateAppVersion`. + +### Channel state will not be set before application callback + +The channel handshake logic has been reorganized within core IBC. +Channel state will not be set in state after the application callback is performed. +Applications must rely only on the passed in channel parameters instead of querying the channel keeper for channel state. + +### IBC application callbacks moved from `AppModule` to `IBCModule` + +Previously, IBC module callbacks were apart of the `AppModule` type. +The recommended approach is to create an `IBCModule` type and move the IBC module callbacks from `AppModule` to `IBCModule` in a separate file `ibc_module.go`. + +The mock module go API has been broken in this release by applying the above format. +The IBC module callbacks have been moved from the mock modules `AppModule` into a new type `IBCModule`. + +As apart of this release, the mock module now supports middleware testing. Please see the [README](https://github.com/cosmos/ibc-go/blob/v7.0.0/testing/README.md#middleware-testing) for more information. + +Please review the [mock](https://github.com/cosmos/ibc-go/blob/v7.0.0/testing/mock/ibc_module.go) and [transfer](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/transfer/ibc_module.go) modules as examples. Additionally, [simapp](https://github.com/cosmos/ibc-go/blob/v7.0.0/testing/simapp/app.go) provides an example of how `IBCModule` types should now be added to the IBC router in favour of `AppModule`. + +### IBC testing package + +`TestChain`s are now created with chainID's beginning from an index of 1. Any calls to `GetChainID(0)` will now fail. Please increment all calls to `GetChainID` by 1. + +## Relayers + +`AppVersion` gRPC has been removed. +The `version` string in `MsgChanOpenTry` has been deprecated and will be ignored by core IBC. +Relayers no longer need to determine the version to use on the `ChanOpenTry` step. +IBC applications will determine the correct version using the counterparty version. + +## IBC Light Clients + +The `GetProofSpecs` function has been removed from the `ClientState` interface. This function was previously unused by core IBC. Light clients which don't use this function may remove it. diff --git a/docs/versioned_docs/version-v7.8.x/05-migrations/05-v3-to-v4.md b/docs/versioned_docs/version-v7.8.x/05-migrations/05-v3-to-v4.md new file mode 100644 index 0000000..ff71213 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/05-migrations/05-v3-to-v4.md @@ -0,0 +1,160 @@ +--- +title: IBC-Go v3 to v4 +sidebar_label: IBC-Go v3 to v4 +sidebar_position: 5 +slug: /migrations/v3-to-v4 +--- +# Migrating from ibc-go v3 to v4 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go/v3 -> github.com/cosmos/ibc-go/v4 +``` + +No genesis or in-place migrations required when upgrading from v1 or v2 of ibc-go. + +## Chains + +### ICS27 - Interchain Accounts + +The controller submodule implements now the 05-port `Middleware` interface instead of the 05-port `IBCModule` interface. Chains that integrate the controller submodule, need to create it with the `NewIBCMiddleware` constructor function. For example: + +```diff +- icacontroller.NewIBCModule(app.ICAControllerKeeper, icaAuthIBCModule) ++ icacontroller.NewIBCMiddleware(icaAuthIBCModule, app.ICAControllerKeeper) +``` + +where `icaAuthIBCModule` is the Interchain Accounts authentication IBC Module. + +### ICS29 - Fee Middleware + +The Fee Middleware module, as the name suggests, plays the role of an IBC middleware and as such must be configured by chain developers to route and handle IBC messages correctly. + +Please read the Fee Middleware [integration documentation](../04-middleware/01-ics29-fee/02-integration.md) for an in depth guide on how to configure the module correctly in order to incentivize IBC packets. + +Take a look at the following diff for an [example setup](https://github.com/cosmos/ibc-go/pull/1432/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08aL366) of how to incentivize ics27 channels. + +### Migration to fix support for base denoms with slashes + +As part of [v1.5.0](https://github.com/cosmos/ibc-go/releases/tag/v1.5.0), [v2.3.0](https://github.com/cosmos/ibc-go/releases/tag/v2.3.0) and [v3.1.0](https://github.com/cosmos/ibc-go/releases/tag/v3.1.0) some [migration handler code sample was documented](./01-support-denoms-with-slashes.md#upgrade-proposal) that needs to run in order to correct the trace information of coins transferred using ICS20 whose base denom contains slashes. + +Based on feedback from the community we add now an improved solution to run the same migration that does not require copying a large piece of code over from the migration document, but instead requires only adding a one-line upgrade handler. + +If the chain will migrate to supporting base denoms with slashes, it must set the appropriate params during the execution of the upgrade handler in `app.go`: + +```go +app.UpgradeKeeper.SetUpgradeHandler("MigrateTraces", + func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + // transfer module consensus version has been bumped to 2 + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) + +``` + +If a chain receives coins of a base denom with slashes before it upgrades to supporting it, the receive may pass however the trace information will be incorrect. + +E.g. If a base denom of `testcoin/testcoin/testcoin` is sent to a chain that does not support slashes in the base denom, the receive will be successful. However, the trace information stored on the receiving chain will be: `Trace: "transfer/{channel-id}/testcoin/testcoin", BaseDenom: "testcoin"`. + +This incorrect trace information must be corrected when the chain does upgrade to fully supporting denominations with slashes. + +## IBC Apps + +### ICS03 - Connection + +Crossing hellos have been removed from 03-connection handshake negotiation. +`PreviousConnectionId` in `MsgConnectionOpenTry` has been deprecated and is no longer used by core IBC. + +`NewMsgConnectionOpenTry` no longer takes in the `PreviousConnectionId` as crossing hellos are no longer supported. A non-empty `PreviousConnectionId` will fail basic validation for this message. + +### ICS04 - Channel + +The `WriteAcknowledgement` API now takes the `exported.Acknowledgement` type instead of passing in the acknowledgement byte array directly. +This is an API breaking change and as such IBC application developers will have to update any calls to `WriteAcknowledgement`. + +The `OnChanOpenInit` application callback has been modified. +The return signature now includes the application version as detailed in the latest IBC [spec changes](https://github.com/cosmos/ibc/pull/629). + +The `NewErrorAcknowledgement` method signature has changed. +It now accepts an `error` rather than a `string`. This was done in order to prevent accidental state changes. +All error acknowledgements now contain a deterministic ABCI code and error message. It is the responsibility of the application developer to emit error details in events. + +Crossing hellos have been removed from 04-channel handshake negotiation. +IBC Applications no longer need to account from already claimed capabilities in the `OnChanOpenTry` callback. The capability provided by core IBC must be able to be claimed with error. +`PreviousChannelId` in `MsgChannelOpenTry` has been deprecated and is no longer used by core IBC. + +`NewMsgChannelOpenTry` no longer takes in the `PreviousChannelId` as crossing hellos are no longer supported. A non-empty `PreviousChannelId` will fail basic validation for this message. + +### ICS27 - Interchain Accounts + +The `RegisterInterchainAccount` API has been modified to include an additional `version` argument. This change has been made in order to support ICS29 fee middleware, for relayer incentivization of ICS27 packets. +Consumers of the `RegisterInterchainAccount` are now expected to build the appropriate JSON encoded version string themselves and pass it accordingly. +This should be constructed within the interchain accounts authentication module which leverages the APIs exposed via the interchain accounts `controllerKeeper`. If an empty string is passed in the `version` argument, then the version will be initialized to a default value in the `OnChanOpenInit` callback of the controller's handler, so that channel handshake can proceed. + +The following code snippet illustrates how to construct an appropriate interchain accounts `Metadata` and encode it as a JSON bytestring: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +if err := k.icaControllerKeeper.RegisterInterchainAccount(ctx, msg.ConnectionId, msg.Owner, string(appVersion)); err != nil { + return err +} +``` + +Similarly, if the application stack is configured to route through ICS29 fee middleware and a fee enabled channel is desired, construct the appropriate ICS29 `Metadata` type: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +feeMetadata := feetypes.Metadata{ + AppVersion: string(appVersion), + FeeVersion: feetypes.Version, +} + +feeEnabledVersion, err := feetypes.ModuleCdc.MarshalJSON(&feeMetadata) +if err != nil { + return err +} + +if err := k.icaControllerKeeper.RegisterInterchainAccount(ctx, msg.ConnectionId, msg.Owner, string(feeEnabledVersion)); err != nil { + return err +} +``` + +## Relayers + +When using the `DenomTrace` gRPC, the full IBC denomination with the `ibc/` prefix may now be passed in. + +Crossing hellos are no longer supported by core IBC for 03-connection and 04-channel. The handshake should be completed in the logical 4 step process (INIT, TRY, ACK, CONFIRM). diff --git a/docs/versioned_docs/version-v7.8.x/05-migrations/06-v4-to-v5.md b/docs/versioned_docs/version-v7.8.x/05-migrations/06-v4-to-v5.md new file mode 100644 index 0000000..35fd948 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/05-migrations/06-v4-to-v5.md @@ -0,0 +1,441 @@ +--- +title: IBC-Go v4 to v5 +sidebar_label: IBC-Go v4 to v5 +sidebar_position: 6 +slug: /migrations/v4-to-v5 +--- + +# Migrating from v4 to v5 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- [Chains](#chains) +- [IBC Apps](#ibc-apps) +- [Relayers](#relayers) +- [IBC Light Clients](#ibc-light-clients) + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go/v4 -> github.com/cosmos/ibc-go/v5 +``` + +## Chains + +### Ante decorator + +The `AnteDecorator` type in `core/ante` has been renamed to `RedundantRelayDecorator` (and the corresponding constructor function to `NewRedundantRelayDecorator`). Therefore in the function that creates the instance of the `sdk.AnteHandler` type (e.g. `NewAnteHandler`) the change would be like this: + +```diff +func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { + // parameter validation + + anteDecorators := []sdk.AnteDecorator{ + // other ante decorators +- ibcante.NewAnteDecorator(opts.IBCkeeper), ++ ibcante.NewRedundantRelayDecorator(options.IBCKeeper), + } + + return sdk.ChainAnteDecorators(anteDecorators...), nil +} +``` + +The `AnteDecorator` was actually renamed twice, but in [this PR](https://github.com/cosmos/ibc-go/pull/1820) you can see the changes made for the final rename. + +## IBC Apps + +### Core + +The `key` parameter of the `NewKeeper` function in `modules/core/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + stakingKeeper clienttypes.StakingKeeper, + upgradeKeeper clienttypes.UpgradeKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +) *Keeper +``` + +The `RegisterRESTRoutes` function in `modules/core` has been removed. + +### ICS03 - Connection + +The `key` parameter of the `NewKeeper` function in `modules/core/03-connection/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + ck types.ClientKeeper +) Keeper +``` + +### ICS04 - Channel + +The function `NewPacketId` in `modules/core/04-channel/types` has been renamed to `NewPacketID`: + +```diff +- func NewPacketId( ++ func NewPacketID( + portID, + channelID string, + seq uint64 +) PacketId +``` + +The `key` parameter of the `NewKeeper` function in `modules/core/04-channel/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + clientKeeper types.ClientKeeper, + connectionKeeper types.ConnectionKeeper, + portKeeper types.PortKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +) Keeper +``` + +### ICS20 - Transfer + +The `key` parameter of the `NewKeeper` function in `modules/apps/transfer/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + ics4Wrapper types.ICS4Wrapper, + channelKeeper types.ChannelKeeper, + portKeeper types.PortKeeper, + authKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +) Keeper +``` + +The `amount` parameter of function `GetTransferCoin` in `modules/apps/transfer/types` is now of type `math.Int` (`"cosmossdk.io/math"`): + +```diff +func GetTransferCoin( + portID, channelID, baseDenom string, +- amount sdk.Int ++ amount math.Int +) sdk.Coin +``` + +The `RegisterRESTRoutes` function in `modules/apps/transfer` has been removed. + +### ICS27 - Interchain Accounts + +The `key` and `msgRouter` parameters of the `NewKeeper` functions in + +- `modules/apps/27-interchain-accounts/controller/keeper` +- and `modules/apps/27-interchain-accounts/host/keeper` + +have changed type. The `key` parameter is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`), and the `msgRouter` parameter is now of type `*icatypes.MessageRouter` (where `icatypes` is an import alias for `"github.com/cosmos/ibc-go/v5/modules/apps/27-interchain-accounts/types"`): + +```diff +// NewKeeper creates a new interchain accounts controller Keeper instance +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + ics4Wrapper icatypes.ICS4Wrapper, + channelKeeper icatypes.ChannelKeeper, + portKeeper icatypes.PortKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +- msgRouter *baseapp.MsgServiceRouter, ++ msgRouter *icatypes.MessageRouter, +) Keeper +``` + +```diff +// NewKeeper creates a new interchain accounts host Keeper instance +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + channelKeeper icatypes.ChannelKeeper, + portKeeper icatypes.PortKeeper, + accountKeeper icatypes.AccountKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +- msgRouter *baseapp.MsgServiceRouter, ++ msgRouter *icatypes.MessageRouter, +) Keeper +``` + +The new `MessageRouter` interface is defined as: + +```go +type MessageRouter interface { + Handler(msg sdk.Msg) baseapp.MsgServiceHandler +} +``` + +The `RegisterRESTRoutes` function in `modules/apps/27-interchain-accounts` has been removed. + +An additional parameter, `ics4Wrapper` has been added to the `host` submodule `NewKeeper` function in `modules/apps/27-interchain-accounts/host/keeper`. +This allows the `host` submodule to correctly unwrap the channel version for channel reopening handshakes in the `OnChanOpenTry` callback. + +```diff +func NewKeeper( + cdc codec.BinaryCodec, + key storetypes.StoreKey, + paramSpace paramtypes.Subspace, ++ ics4Wrapper icatypes.ICS4Wrapper, + channelKeeper icatypes.ChannelKeeper, + portKeeper icatypes.PortKeeper, + accountKeeper icatypes.AccountKeeper, + scopedKeeper icatypes.ScopedKeeper, + msgRouter icatypes.MessageRouter, +) Keeper +``` + +#### Cosmos SDK message handler responses in packet acknowledgement + +The construction of the transaction response of a message execution on the host chain has changed. The `Data` field in the `sdk.TxMsgData` has been deprecated and since Cosmos SDK 0.46 the `MsgResponses` field contains the message handler responses packed into `Any`s. + +For chains on Cosmos SDK 0.45 and below, the message response was constructed like this: + +```go +txMsgData := &sdk.TxMsgData{ + Data: make([]*sdk.MsgData, len(msgs)), +} + +for i, msg := range msgs { + // message validation + + msgResponse, err := k.executeMsg(cacheCtx, msg) + // return if err != nil + + txMsgData.Data[i] = &sdk.MsgData{ + MsgType: sdk.MsgTypeURL(msg), + Data: msgResponse, + } +} + +// emit events + +txResponse, err := proto.Marshal(txMsgData) +// return if err != nil + +return txResponse, nil +``` + +And for chains on Cosmos SDK 0.46 and above, it is now done like this: + +```go +txMsgData := &sdk.TxMsgData{ + MsgResponses: make([]*codectypes.Any, len(msgs)), +} + +for i, msg := range msgs { + // message validation + + any, err := k.executeMsg(cacheCtx, msg) + // return if err != nil + + txMsgData.MsgResponses[i] = any +} + +// emit events + +txResponse, err := proto.Marshal(txMsgData) +// return if err != nil + +return txResponse, nil +``` + +When handling the acknowledgement in the `OnAcknowledgementPacket` callback of a custom ICA controller module, then depending on whether `txMsgData.Data` is empty or not, the logic to handle the message handler response will be different. **Only controller chains on Cosmos SDK 0.46 or above will be able to write the logic needed to handle the response from a host chain on Cosmos SDK 0.46 or above.** + +```go +var ack channeltypes.Acknowledgement +if err := channeltypes.SubModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { + return err +} + +var txMsgData sdk.TxMsgData +if err := proto.Unmarshal(ack.GetResult(), txMsgData); err != nil { + return err +} + +switch len(txMsgData.Data) { +case 0: // for SDK 0.46 and above + for _, msgResponse := range txMsgData.MsgResponses { + // unmarshall msgResponse and execute logic based on the response + } + return nil +default: // for SDK 0.45 and below + for _, msgData := range txMsgData.Data { + // unmarshall msgData and execute logic based on the response + } +} +``` + +See [ADR-03](/architecture/adr-003-ics27-acknowledgement#next-major-version-format) for more information or the [corresponding documentation about authentication modules](../02-apps/02-interchain-accounts/03-auth-modules.md#onacknowledgementpacket). + +### ICS29 - Fee Middleware + +The `key` parameter of the `NewKeeper` function in `modules/apps/29-fee` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + ics4Wrapper types.ICS4Wrapper, + channelKeeper types.ChannelKeeper, + portKeeper types.PortKeeper, + authKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, +) Keeper +``` + +The `RegisterRESTRoutes` function in `modules/apps/29-fee` has been removed. + +### IBC testing package + +The `MockIBCApp` type has been renamed to `IBCApp` (and the corresponding constructor function to `NewIBCApp`). This has resulted therefore in: + +- The `IBCApp` field of the `*IBCModule` in `testing/mock` to change its type as well to `*IBCApp`: + +```diff +type IBCModule struct { + appModule *AppModule +- IBCApp *MockIBCApp // base application of an IBC middleware stack ++ IBCApp *IBCApp // base application of an IBC middleware stack +} +``` + +- The `app` parameter to `*NewIBCModule` in `testing/mock` to change its type as well to `*IBCApp`: + +```diff +func NewIBCModule( + appModule *AppModule, +- app *MockIBCApp ++ app *IBCApp +) IBCModule +``` + +The `MockEmptyAcknowledgement` type has been renamed to `EmptyAcknowledgement` (and the corresponding constructor function to `NewEmptyAcknowledgement`). + +The `TestingApp` interface in `testing` has gone through some modifications: + +- The return type of the function `GetStakingKeeper` is not the concrete type `stakingkeeper.Keeper` anymore (where `stakingkeeper` is an import alias for `"github.com/cosmos/cosmos-sdk/x/staking/keeper"`), but it has been changed to the interface `ibctestingtypes.StakingKeeper` (where `ibctestingtypes` is an import alias for `""github.com/cosmos/ibc-go/v5/testing/types"`). See this [PR](https://github.com/cosmos/ibc-go/pull/2028) for more details. The `StakingKeeper` interface is defined as: + +```go +type StakingKeeper interface { + GetHistoricalInfo(ctx sdk.Context, height int64) (stakingtypes.HistoricalInfo, bool) +} +``` + +- The return type of the function `LastCommitID` has changed to `storetypes.CommitID` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`). + +See the following `git diff` for more details: + +```diff +type TestingApp interface { + abci.Application + + // ibc-go additions + GetBaseApp() *baseapp.BaseApp +- GetStakingKeeper() stakingkeeper.Keeper ++ GetStakingKeeper() ibctestingtypes.StakingKeeper + GetIBCKeeper() *keeper.Keeper + GetScopedIBCKeeper() capabilitykeeper.ScopedKeeper + GetTxConfig() client.TxConfig + + // Implemented by SimApp + AppCodec() codec.Codec + + // Implemented by BaseApp +- LastCommitID() sdk.CommitID ++ LastCommitID() storetypes.CommitID + LastBlockHeight() int64 +} +``` + +The `powerReduction` parameter of the function `SetupWithGenesisValSet` in `testing` is now of type `math.Int` (`"cosmossdk.io/math"`): + +```diff +func SetupWithGenesisValSet( + t *testing.T, + valSet *tmtypes.ValidatorSet, + genAccs []authtypes.GenesisAccount, + chainID string, +- powerReduction sdk.Int, ++ powerReduction math.Int, + balances ...banktypes.Balance +) TestingApp +``` + +The `accAmt` parameter of the functions + +- `AddTestAddrsFromPubKeys` , +- `AddTestAddrs` +- and `AddTestAddrsIncremental` + +in `testing/simapp` are now of type `math.Int` (`"cosmossdk.io/math"`): + +```diff +func AddTestAddrsFromPubKeys( + app *SimApp, + ctx sdk.Context, + pubKeys []cryptotypes.PubKey, +- accAmt sdk.Int, ++ accAmt math.Int +) +func addTestAddrs( + app *SimApp, + ctx sdk.Context, + accNum int, +- accAmt sdk.Int, ++ accAmt math.Int, + strategy GenerateAccountStrategy +) []sdk.AccAddress +func AddTestAddrsIncremental( + app *SimApp, + ctx sdk.Context, + accNum int, +- accAmt sdk.Int, ++ accAmt math.Int +) []sdk.AccAddress +``` + +The `RegisterRESTRoutes` function in `testing/mock` has been removed. + +## Relayers + +- No relevant changes were made in this release. + +## IBC Light Clients + +### ICS02 - Client + +The `key` parameter of the `NewKeeper` function in `modules/core/02-client/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + sk types.StakingKeeper, + uk types.UpgradeKeeper +) Keeper +``` diff --git a/docs/versioned_docs/version-v7.8.x/05-migrations/07-v5-to-v6.md b/docs/versioned_docs/version-v7.8.x/05-migrations/07-v5-to-v6.md new file mode 100644 index 0000000..f87a0e4 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/05-migrations/07-v5-to-v6.md @@ -0,0 +1,299 @@ +--- +title: IBC-Go v5 to v6 +sidebar_label: IBC-Go v5 to v6 +sidebar_position: 7 +slug: /migrations/v5-to-v6 +--- + +# Migrating from ibc-go v5 to v6 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +## Chains + +The `ibc-go/v6` release introduces a new set of migrations for `27-interchain-accounts`. Ownership of ICS27 channel capabilities is transferred from ICS27 authentication modules and will now reside with the ICS27 controller submodule moving forward. + +For chains which contain a custom authentication module using the ICS27 controller submodule this requires a migration function to be included in the chain upgrade handler. A subsequent migration handler is run automatically, asserting the ownership of ICS27 channel capabilities has been transferred successfully. + +This migration is not required for chains which *do not* contain a custom authentication module using the ICS27 controller submodule. + +This migration facilitates the addition of the ICS27 controller submodule `MsgServer` which provides a standardised approach to integrating existing forms of authentication such as `x/gov` and `x/group` provided by the Cosmos SDK. + +For more information please refer to [ADR 009](/architecture/adr-009-v6-ics27-msgserver). + +### Upgrade proposal + +Please refer to [PR #2383](https://github.com/cosmos/ibc-go/pull/2383) for integrating the ICS27 channel capability migration logic or follow the steps outlined below: + +1. Add the upgrade migration logic to chain distribution. This may be, for example, maintained under a package `app/upgrades/v6`. + +```go +package v6 + +import ( + "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + + v6 "github.com/cosmos/ibc-go/v6/modules/apps/27-interchain-accounts/controller/migrations/v6" +) + +const ( + UpgradeName = "v6" +) + +func CreateUpgradeHandler( + mm *module.Manager, + configurator module.Configurator, + cdc codec.BinaryCodec, + capabilityStoreKey *storetypes.KVStoreKey, + capabilityKeeper *capabilitykeeper.Keeper, + moduleName string, +) upgradetypes.UpgradeHandler { + return func(ctx sdk.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + if err := v6.MigrateICS27ChannelCapability(ctx, cdc, capabilityStoreKey, capabilityKeeper, moduleName); err != nil { + return nil, err + } + + return mm.RunMigrations(ctx, configurator, vm) + } +} +``` + +2. Set the upgrade handler in `app.go`. The `moduleName` parameter refers to the authentication module's `ScopedKeeper` name. This is the name provided upon instantiation in `app.go` via the [`x/capability` keeper `ScopeToModule(moduleName string)`](https://github.com/cosmos/cosmos-sdk/blob/v0.46.1/x/capability/keeper/keeper.go#L70) method. [See here for an example in `simapp`](https://github.com/cosmos/ibc-go/blob/v5.0.0/testing/simapp/app.go#L304). + +```go +app.UpgradeKeeper.SetUpgradeHandler( + v6.UpgradeName, + v6.CreateUpgradeHandler( + app.mm, + app.configurator, + app.appCodec, + app.keys[capabilitytypes.ModuleName], + app.CapabilityKeeper, + >>>> moduleName <<<<, + ), +) +``` + +## IBC Apps + +### ICS27 - Interchain Accounts + +#### Controller APIs + +In previous releases of ibc-go, chain developers integrating the ICS27 interchain accounts controller functionality were expected to create a custom `Base Application` referred to as an authentication module, see the section [Building an authentication module](../02-apps/02-interchain-accounts/03-auth-modules.md) from the documentation. + +The `Base Application` was intended to be composed with the ICS27 controller submodule `Keeper` and facilitate many forms of message authentication depending on a chain's particular use case. + +Prior to ibc-go v6 the controller submodule exposed only these two functions (to which we will refer as the legacy APIs): + +- [`RegisterInterchainAccount`](https://github.com/cosmos/ibc-go/blob/v5.0.0/modules/apps/27-interchain-accounts/controller/keeper/account.go#L19) +- [`SendTx`](https://github.com/cosmos/ibc-go/blob/v5.0.0/modules/apps/27-interchain-accounts/controller/keeper/relay.go#L18) + +However, these functions have now been deprecated in favour of the new controller submodule `MsgServer` and will be removed in later releases. + +Both APIs remain functional and maintain backwards compatibility in ibc-go v6, however consumers of these APIs are now recommended to follow the message passing paradigm outlined in Cosmos SDK [ADR 031](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-031-msg-service.md) and [ADR 033](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-033-protobuf-inter-module-comm.md). This is facilitated by the Cosmos SDK [`MsgServiceRouter`](https://github.com/cosmos/cosmos-sdk/blob/main/baseapp/msg_service_router.go#L17) and chain developers creating custom application logic can now omit the ICS27 controller submodule `Keeper` from their module and instead depend on message routing. + +Depending on the use case, developers of custom authentication modules face one of three scenarios: + +![auth-module-decision-tree.png](./images/auth-module-decision-tree.png) + +**My authentication module needs to access IBC packet callbacks** + +Application developers that wish to consume IBC packet callbacks and react upon packet acknowledgements **must** continue using the controller submodule's legacy APIs. The authentication modules will not need a `ScopedKeeper` anymore, though, because the channel capability will be claimed by the controller submodule. For example, given an Interchain Accounts authentication module keeper `ICAAuthKeeper`, the authentication module's `ScopedKeeper` (`scopedICAAuthKeeper`) is not needed anymore and can be removed for the argument list of the keeper constructor function, as shown here: + +```diff +app.ICAAuthKeeper = icaauthkeeper.NewKeeper( + appCodec, + keys[icaauthtypes.StoreKey], + app.ICAControllerKeeper, +- scopedICAAuthKeeper, +) +``` + +Please note that the authentication module's `ScopedKeeper` name is still needed as part of the channel capability migration described in section [Upgrade proposal](#upgrade-proposal) above. Therefore the authentication module's `ScopedKeeper` cannot be completely removed from the chain code until the migration has run. + +In the future, the use of the legacy APIs for accessing packet callbacks will be replaced by IBC Actor Callbacks (see [ADR 008](https://github.com/cosmos/ibc-go/pull/1976) for more details) and it will also be possible to access them with the `MsgServiceRouter`. + +**My authentication module does not need access to IBC packet callbacks** + +The authentication module can migrate from using the legacy APIs and it can be composed instead with the `MsgServiceRouter`, so that the authentication module is able to pass messages to the controller submodule's `MsgServer` to register interchain accounts and send packets to the interchain account. For example, given an Interchain Accounts authentication module keeper `ICAAuthKeeper`, the ICS27 controller submodule keeper (`ICAControllerKeeper`) and authentication module scoped keeper (`scopedICAAuthKeeper`) are not needed anymore and can be replaced with the `MsgServiceRouter`, as shown here: + +```diff +app.ICAAuthKeeper = icaauthkeeper.NewKeeper( + appCodec, + keys[icaauthtypes.StoreKey], +- app.ICAControllerKeeper, +- scopedICAAuthKeeper, ++ app.MsgServiceRouter(), +) +``` + +In your authentication module you can route messages to the controller submodule's `MsgServer` instead of using the legacy APIs. For example, for registering an interchain account: + +```diff +- if err := keeper.icaControllerKeeper.RegisterInterchainAccount( +- ctx, +- connectionID, +- owner.String(), +- version, +- ); err != nil { +- return err +- } ++ msg := controllertypes.NewMsgRegisterInterchainAccount( ++ connectionID, ++ owner.String(), ++ version, ++ ) ++ handler := keeper.msgRouter.Handler(msg) ++ res, err := handler(ctx, msg) ++ if err != nil { ++ return err ++ } +``` + +where `controllertypes` is an import alias for `"github.com/cosmos/ibc-go/v6/modules/apps/27-interchain-accounts/controller/types"`. + +In addition, in this use case the authentication module does not need to implement the `IBCModule` interface anymore. + +**I do not need a custom authentication module anymore** + +If your authentication module does not have any extra functionality compared to the default authentication module added in ibc-go v6 (the `MsgServer`), or if you can use a generic authentication module, such as the `x/auth`, `x/gov` or `x/group` modules from the Cosmos SDK (v0.46 and later), then you can remove your authentication module completely and use instead the gRPC endpoints of the `MsgServer` or the CLI added in ibc-go v6. + +Please remember that the authentication module's `ScopedKeeper` name is still needed as part of the channel capability migration described in section [Upgrade proposal](#upgrade-proposal) above. + +#### Host params + +The ICS27 host submodule default params have been updated to include the `AllowAllHostMsgs` wildcard `*`. +This enables execution of any `sdk.Msg` type for ICS27 registered on the host chain `InterfaceRegistry`. + +```diff +// AllowAllHostMsgs holds the string key that allows all message types on interchain accounts host module +const AllowAllHostMsgs = "*" + +... + +// DefaultParams is the default parameter configuration for the host submodule +func DefaultParams() Params { +- return NewParams(DefaultHostEnabled, nil) ++ return NewParams(DefaultHostEnabled, []string{AllowAllHostMsgs}) +} +``` + +#### API breaking changes + +`SerializeCosmosTx` takes in a `[]proto.Message` instead of `[]sdk.Message`. This allows for the serialization of proto messages without requiring the fulfillment of the `sdk.Msg` interface. + +The `27-interchain-accounts` genesis types have been moved to their own package: `modules/apps/27-interchain-acccounts/genesis/types`. +This change facilitates the addition of the ICS27 controller submodule `MsgServer` and avoids cyclic imports. This should have minimal disruption to chain developers integrating `27-interchain-accounts`. + +The ICS27 host submodule `NewKeeper` function in `modules/apps/27-interchain-acccounts/host/keeper` now includes an additional parameter of type `ICS4Wrapper`. +This provides the host submodule with the ability to correctly unwrap channel versions in the event of a channel reopening handshake. + +```diff +func NewKeeper( + cdc codec.BinaryCodec, key storetypes.StoreKey, paramSpace paramtypes.Subspace, +- channelKeeper icatypes.ChannelKeeper, portKeeper icatypes.PortKeeper, ++ ics4Wrapper icatypes.ICS4Wrapper, channelKeeper icatypes.ChannelKeeper, portKeeper icatypes.PortKeeper, + accountKeeper icatypes.AccountKeeper, scopedKeeper icatypes.ScopedKeeper, msgRouter icatypes.MessageRouter, +) Keeper +``` + +### ICS29 - `NewKeeper` API change + +The `NewKeeper` function of ICS29 has been updated to remove the `paramSpace` parameter as it was unused. + +```diff +func NewKeeper( +- cdc codec.BinaryCodec, key storetypes.StoreKey, paramSpace paramtypes.Subspace, +- ics4Wrapper types.ICS4Wrapper, channelKeeper types.ChannelKeeper, portKeeper types.PortKeeper, authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, ++ cdc codec.BinaryCodec, key storetypes.StoreKey, ++ ics4Wrapper types.ICS4Wrapper, channelKeeper types.ChannelKeeper, ++ portKeeper types.PortKeeper, authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, +) Keeper { +``` + +### ICS20 - `SendTransfer` is no longer exported + +The `SendTransfer` function of ICS20 has been removed. IBC transfers should now be initiated with `MsgTransfer` and routed to the ICS20 `MsgServer`. + +See below for example: + +```go +if handler := msgRouter.Handler(msgTransfer); handler != nil { + if err := msgTransfer.ValidateBasic(); err != nil { + return nil, err + } + + res, err := handler(ctx, msgTransfer) + if err != nil { + return nil, err + } +} +``` + +### ICS04 - `SendPacket` API change + +The `SendPacket` API has been simplified: + +```diff +// SendPacket is called by a module in order to send an IBC packet on a channel +func (k Keeper) SendPacket( + ctx sdk.Context, + channelCap *capabilitytypes.Capability, +- packet exported.PacketI, +-) error { ++ sourcePort string, ++ sourceChannel string, ++ timeoutHeight clienttypes.Height, ++ timeoutTimestamp uint64, ++ data []byte, ++) (uint64, error) { +``` + +Callers no longer need to pass in a pre-constructed packet. +The destination port/channel identifiers and the packet sequence will be determined by core IBC. +`SendPacket` will return the packet sequence. + +### IBC testing package + +The `SendPacket` API has been simplified: + +```diff +// SendPacket is called by a module in order to send an IBC packet on a channel +func (k Keeper) SendPacket( + ctx sdk.Context, + channelCap *capabilitytypes.Capability, +- packet exported.PacketI, +-) error { ++ sourcePort string, ++ sourceChannel string, ++ timeoutHeight clienttypes.Height, ++ timeoutTimestamp uint64, ++ data []byte, ++) (uint64, error) { +``` + +Callers no longer need to pass in a pre-constructed packet. `SendPacket` will return the packet sequence. + +## Relayers + +- No relevant changes were made in this release. + +## IBC Light Clients + +- No relevant changes were made in this release. diff --git a/docs/versioned_docs/version-v7.8.x/05-migrations/08-v6-to-v7.md b/docs/versioned_docs/version-v7.8.x/05-migrations/08-v6-to-v7.md new file mode 100644 index 0000000..9e4d1c9 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/05-migrations/08-v6-to-v7.md @@ -0,0 +1,357 @@ +--- +title: IBC-Go v6 to v7 +sidebar_label: IBC-Go v6 to v7 +sidebar_position: 8 +slug: /migrations/v6-to-v7 +--- +# Migrating from ibc-go v6 to v7 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +## Chains + +Chains will perform automatic migrations to remove existing localhost clients and to migrate the solomachine to v3 of the protobuf definition. + +An optional upgrade handler has been added to prune expired tendermint consensus states. It may be used during any upgrade (from v7 onwards). +Add the following to the function call to the upgrade handler in `app/app.go`, to perform the optional state pruning. + +```go +import ( + // ... + ibctmmigrations "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint/migrations" +) + +// ... + +app.UpgradeKeeper.SetUpgradeHandler( + upgradeName, + func(ctx sdk.Context, _ upgradetypes.Plan, _ module.VersionMap) (module.VersionMap, error) { + // prune expired tendermint consensus states to save storage space + _, err := ibctmmigrations.PruneExpiredConsensusStates(ctx, app.Codec, app.IBCKeeper.ClientKeeper) + if err != nil { + return nil, err + } + + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }, +) +``` + +Checkout the logs to see how many consensus states are pruned. + +### Light client registration + +Chains must explicitly register the types of any light client modules it wishes to integrate. + +#### Tendermint registration + +To register the tendermint client, modify the `app.go` file to include the tendermint `AppModuleBasic`: + +```diff +import ( + // ... ++ ibctm "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" +) + +// ... + +ModuleBasics = module.NewBasicManager( + ... + ibc.AppModuleBasic{}, ++ ibctm.AppModuleBasic{}, + ... +) +``` + +It may be useful to reference the [PR](https://github.com/cosmos/ibc-go/pull/2825) which added the `AppModuleBasic` for the tendermint client. + +#### Solo machine registration + +To register the solo machine client, modify the `app.go` file to include the solo machine `AppModuleBasic`: + +```diff +import ( + // ... ++ solomachine "github.com/cosmos/ibc-go/v7/modules/light-clients/06-solomachine" +) + +// ... + +ModuleBasics = module.NewBasicManager( + ... + ibc.AppModuleBasic{}, ++ solomachine.AppModuleBasic{}, + ... +) +``` + +It may be useful to reference the [PR](https://github.com/cosmos/ibc-go/pull/2826) which added the `AppModuleBasic` for the solo machine client. + +### Testing package API + +The `SetChannelClosed` utility method in `testing/endpoint.go` has been updated to `SetChannelState`, which will take a `channeltypes.State` argument so that the `ChannelState` can be set to any of the possible channel states. + +## IBC Apps + +- No relevant changes were made in this release. + +## Relayers + +- No relevant changes were made in this release. + +## IBC Light Clients + +### `ClientState` interface changes + +The `VerifyUpgradeAndUpdateState` function has been modified. The client state and consensus state return values have been removed. + +Light clients **must** handle all management of client and consensus states including the setting of updated client state and consensus state in the client store. + +The `Initialize` method is now expected to set the initial client state, consensus state and any client-specific metadata in the provided store upon client creation. + +The `CheckHeaderAndUpdateState` method has been split into 4 new methods: + +- `VerifyClientMessage` verifies a `ClientMessage`. A `ClientMessage` could be a `Header`, `Misbehaviour`, or batch update. Calls to `CheckForMisbehaviour`, `UpdateState`, and `UpdateStateOnMisbehaviour` will assume that the content of the `ClientMessage` has been verified and can be trusted. An error should be returned if the `ClientMessage` fails to verify. + +- `CheckForMisbehaviour` checks for evidence of a misbehaviour in `Header` or `Misbehaviour` types. + +- `UpdateStateOnMisbehaviour` performs appropriate state changes on a `ClientState` given that misbehaviour has been detected and verified. + +- `UpdateState` updates and stores as necessary any associated information for an IBC client, such as the `ClientState` and corresponding `ConsensusState`. An error is returned if `ClientMessage` is of type `Misbehaviour`. Upon successful update, a list containing the updated consensus state height is returned. + +The `CheckMisbehaviourAndUpdateState` function has been removed from `ClientState` interface. This functionality is now encapsulated by the usage of `VerifyClientMessage`, `CheckForMisbehaviour`, `UpdateStateOnMisbehaviour`. + +The function `GetTimestampAtHeight` has been added to the `ClientState` interface. It should return the timestamp for a consensus state associated with the provided height. + +Prior to ibc-go/v7 the `ClientState` interface defined a method for each data type which was being verified in the counterparty state store. +The state verification functions for all IBC data types have been consolidated into two generic methods, `VerifyMembership` and `VerifyNonMembership`. +Both are expected to be provided with a standardised key path, `exported.Path`, as defined in [ICS 24 host requirements](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements). Membership verification requires callers to provide the marshalled value `[]byte`. Delay period values should be zero for non-packet processing verification. A zero proof height is now allowed by core IBC and may be passed into `VerifyMembership` and `VerifyNonMembership`. Light clients are responsible for returning an error if a zero proof height is invalid behaviour. + +See below for an example of how ibc-go now performs channel state verification. + +```go +merklePath := commitmenttypes.NewMerklePath(host.ChannelPath(portID, channelID)) +merklePath, err := commitmenttypes.ApplyPrefix(connection.GetCounterparty().GetPrefix(), merklePath) +if err != nil { + return err +} + +channelEnd, ok := channel.(channeltypes.Channel) +if !ok { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "invalid channel type %T", channel) +} + +bz, err := k.cdc.Marshal(&channelEnd) +if err != nil { + return err +} + +if err := clientState.VerifyMembership( + ctx, clientStore, k.cdc, height, + 0, 0, // skip delay period checks for non-packet processing verification + proof, merklePath, bz, +); err != nil { + return sdkerrors.Wrapf(err, "failed channel state verification for client (%s)", clientID) +} +``` + +### `Header` and `Misbehaviour` + +`exported.Header` and `exported.Misbehaviour` interface types have been merged and renamed to `ClientMessage` interface. + +`GetHeight` function has been removed from `exported.Header` and thus is not included in the `ClientMessage` interface + +### `ConsensusState` + +The `GetRoot` function has been removed from consensus state interface since it was not used by core IBC. + +### Client keeper + +Keeper function `CheckMisbehaviourAndUpdateState` has been removed since function `UpdateClient` can now handle updating `ClientState` on `ClientMessage` type which can be any `Misbehaviour` implementations. + +### SDK message + +`MsgSubmitMisbehaviour` is deprecated since `MsgUpdateClient` can now submit a `ClientMessage` type which can be any `Misbehaviour` implementations. + +The field `header` in `MsgUpdateClient` has been renamed to `client_message`. + +## Solomachine + +The `06-solomachine` client implementation has been simplified in ibc-go/v7. In-place store migrations have been added to migrate solomachine clients from `v2` to `v3`. + +### `ClientState` + +The `ClientState` protobuf message definition has been updated to remove the deprecated `bool` field `allow_update_after_proposal`. + +```diff +message ClientState { + option (gogoproto.goproto_getters) = false; + + uint64 sequence = 1; + bool is_frozen = 2 [(gogoproto.moretags) = "yaml:\"is_frozen\""]; + ConsensusState consensus_state = 3 [(gogoproto.moretags) = "yaml:\"consensus_state\""]; +- bool allow_update_after_proposal = 4 [(gogoproto.moretags) = "yaml:\"allow_update_after_proposal\""]; +} +``` + +### `Header` and `Misbehaviour` + +The `06-solomachine` protobuf message `Header` has been updated to remove the `sequence` field. This field was seen as redundant as the implementation can safely rely on the `sequence` value maintained within the `ClientState`. + +```diff +message Header { + option (gogoproto.goproto_getters) = false; + +- uint64 sequence = 1; +- uint64 timestamp = 2; +- bytes signature = 3; +- google.protobuf.Any new_public_key = 4 [(gogoproto.moretags) = "yaml:\"new_public_key\""]; +- string new_diversifier = 5 [(gogoproto.moretags) = "yaml:\"new_diversifier\""]; ++ uint64 timestamp = 1; ++ bytes signature = 2; ++ google.protobuf.Any new_public_key = 3 [(gogoproto.moretags) = "yaml:\"new_public_key\""]; ++ string new_diversifier = 4 [(gogoproto.moretags) = "yaml:\"new_diversifier\""]; +} +``` + +Similarly, the `Misbehaviour` protobuf message has been updated to remove the `client_id` field. + +```diff +message Misbehaviour { + option (gogoproto.goproto_getters) = false; + +- string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""]; +- uint64 sequence = 2; +- SignatureAndData signature_one = 3 [(gogoproto.moretags) = "yaml:\"signature_one\""]; +- SignatureAndData signature_two = 4 [(gogoproto.moretags) = "yaml:\"signature_two\""]; ++ uint64 sequence = 1; ++ SignatureAndData signature_one = 2 [(gogoproto.moretags) = "yaml:\"signature_one\""]; ++ SignatureAndData signature_two = 3 [(gogoproto.moretags) = "yaml:\"signature_two\""]; +} +``` + +### `SignBytes` + +Most notably, the `SignBytes` protobuf definition has been modified to replace the `data_type` field with a new field, `path`. The `path` field is defined as `bytes` and represents a serialized [ICS-24](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements) standardized key path under which the `data` is stored. + +```diff +message SignBytes { + option (gogoproto.goproto_getters) = false; + + uint64 sequence = 1; + uint64 timestamp = 2; + string diversifier = 3; +- DataType data_type = 4 [(gogoproto.moretags) = "yaml:\"data_type\""]; ++ bytes path = 4; + bytes data = 5; +} +``` + +The `DataType` enum and all associated data types have been removed, greatly reducing the number of message definitions and complexity in constructing the `SignBytes` message type. Likewise, solomachine implementations must now use the serialized `path` value when constructing `SignatureAndData` for signature verification of `SignBytes` data. + +```diff +message SignatureAndData { + option (gogoproto.goproto_getters) = false; + + bytes signature = 1; +- DataType data_type = 2 [(gogoproto.moretags) = "yaml:\"data_type\""]; ++ bytes path = 2; + bytes data = 3; + uint64 timestamp = 4; +} +``` + +For more information, please refer to [ADR-007](https://github.com/cosmos/ibc-go/blob/02-client-refactor-beta1/docs/architecture/adr-007-solomachine-signbytes.md). + +### IBC module constants + +IBC module constants have been moved from the `host` package to the `exported` package. Any usages will need to be updated. + +```diff +import ( + // ... +- host "github.com/cosmos/ibc-go/v7/modules/core/24-host" ++ ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" + // ... +) + +- host.ModuleName ++ ibcexported.ModuleName + +- host.StoreKey ++ ibcexported.StoreKey + +- host.QuerierRoute ++ ibcexported.QuerierRoute + +- host.RouterKey ++ ibcexported.RouterKey +``` + +## Upgrading to Cosmos SDK 0.47 + +The following should be considered as complementary to [Cosmos SDK v0.47 UPGRADING.md](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc2/UPGRADING.md). + +### Protobuf + +Protobuf code generation, linting and formatting have been updated to leverage the `ghcr.io/cosmos/proto-builder:0.11.5` docker container. IBC protobuf definitions are now packaged and published to [buf.build/cosmos/ibc](https://buf.build/cosmos/ibc) via CI workflows. The `third_party/proto` directory has been removed in favour of dependency management using [buf.build](https://docs.buf.build/introduction). + +### App modules + +Legacy APIs of the `AppModule` interface have been removed from ibc-go modules. For example, for + +```diff +- // Route implements the AppModule interface +- func (am AppModule) Route() sdk.Route { +- return sdk.Route{} +- } +- +- // QuerierRoute implements the AppModule interface +- func (AppModule) QuerierRoute() string { +- return types.QuerierRoute +- } +- +- // LegacyQuerierHandler implements the AppModule interface +- func (am AppModule) LegacyQuerierHandler(*codec.LegacyAmino) sdk.Querier { +- return nil +- } +- +- // ProposalContents doesn't return any content functions for governance proposals. +- func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalContent { +- return nil +- } +``` + +### Imports + +Imports for ics23 have been updated as the repository have been migrated from confio to cosmos. + +```diff +import ( + // ... +- ics23 "github.com/confio/ics23/go" ++ ics23 "github.com/cosmos/ics23/go" + // ... +) +``` + +Imports for gogoproto have been updated. + +```diff +import ( + // ... +- "github.com/gogo/protobuf/proto" ++ "github.com/cosmos/gogoproto/proto" + // ... +) +``` diff --git a/docs/versioned_docs/version-v7.8.x/05-migrations/09-v7-to-v7_1.md b/docs/versioned_docs/version-v7.8.x/05-migrations/09-v7-to-v7_1.md new file mode 100644 index 0000000..805ee41 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/05-migrations/09-v7-to-v7_1.md @@ -0,0 +1,67 @@ +--- +title: IBC-Go v7 to v7.1 +sidebar_label: IBC-Go v7 to v7.1 +sidebar_position: 9 +slug: /migrations/v7-to-v7_1 +--- +# Migrating from v7 to v7.1 + +This guide provides instructions for migrating to version `v7.1.0` of ibc-go. + +There are four sections based on the four potential user groups of this document: + +- [Migrating from v7 to v7.1](#migrating-from-v7-to-v71) + - [Chains](#chains) + - [IBC Apps](#ibc-apps) + - [Relayers](#relayers) + - [IBC Light Clients](#ibc-light-clients) + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. + +## Chains + +### 09-localhost migration + +In the previous release of ibc-go, the localhost `v1` light client module was deprecated and removed. The ibc-go `v7.1.0` release introduces `v2` of the 09-localhost light client module. + +An [automatic migration handler](https://github.com/cosmos/ibc-go/blob/v7.2.0/modules/core/module.go#L127-L145) is configured in the core IBC module to set the localhost `ClientState` and sentinel `ConnectionEnd` in state. + +In order to use the 09-localhost client chains must update the `AllowedClients` parameter in the 02-client submodule of core IBC. This can be configured directly in the application upgrade handler or alternatively updated via the legacy governance parameter change proposal. +We **strongly** recommend chains to perform this action so that intra-ledger communication can be carried out using the familiar IBC interfaces. + +See the upgrade handler code sample provided below or [follow this link](https://github.com/cosmos/ibc-go/blob/v7.2.0/testing/simapp/upgrades/upgrades.go#L85) for the upgrade handler used by the ibc-go simapp. + +```go +func CreateV7LocalhostUpgradeHandler( + mm *module.Manager, + configurator module.Configurator, + clientKeeper clientkeeper.Keeper, +) upgradetypes.UpgradeHandler { + return func(ctx sdk.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + // explicitly update the IBC 02-client params, adding the localhost client type + params := clientKeeper.GetParams(ctx) + params.AllowedClients = append(params.AllowedClients, exported.Localhost) + clientKeeper.SetParams(ctx, params) + + return mm.RunMigrations(ctx, configurator, vm) + } +} +``` + +### Transfer migration + +An [automatic migration handler](https://github.com/cosmos/ibc-go/blob/v7.2.0/modules/apps/transfer/module.go#L111-L113) is configured in the transfer module to set the total amount in escrow for all denominations of coins that have been sent out. For each denomination a state entry is added with the total amount of coins in escrow regardless of the channel from which they were transferred. + +## IBC Apps + +- No relevant changes were made in this release. + +## Relayers + +The event attribute `packet_connection` (`connectiontypes.AttributeKeyConnection`) has been deprecated. +Please use the `connection_id` attribute (`connectiontypes.AttributeKeyConnectionID`) which is emitted by all channel events. +Only send packet, receive packet, write acknowledgement, and acknowledge packet events used `packet_connection` previously. + +## IBC Light Clients + +- No relevant changes were made in this release. diff --git a/docs/versioned_docs/version-v7.8.x/05-migrations/10-v7_2-to-v7_3.md b/docs/versioned_docs/version-v7.8.x/05-migrations/10-v7_2-to-v7_3.md new file mode 100644 index 0000000..bf26252 --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/05-migrations/10-v7_2-to-v7_3.md @@ -0,0 +1,49 @@ +--- +title: IBC-Go v7.2 to v7.3 +sidebar_label: IBC-Go v7.2 to v7.3 +sidebar_position: 10 +slug: /migrations/v7_2-to-v7_3 +--- +# Migrating from v7.2 to v7.3 + +This guide provides instructions for migrating to version `v7.3.0` of ibc-go. + +There are four sections based on the four potential user groups of this document: + +- [Migrating from v7.2 to v7.3](#migrating-from-v72-to-v73) + - [Chains](#chains) + - [IBC Apps](#ibc-apps) + - [Relayers](#relayers) + - [IBC Light Clients](#ibc-light-clients) + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. + +## Chains + +- No relevant changes were made in this release. + +## IBC Apps + +A set of interfaces have been added that IBC applications may optionally implement. Developers interested in integrating their applications with the [callbacks middleware](../04-middleware/02-callbacks/01-overview.md) should implement these interfaces so that the callbacks middleware can retrieve the desired callback addresses on the source and destination chains and execute actions on packet lifecycle events. The interfaces are [`PacketDataUnmarshaler`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/core/05-port/types/module.go#L142-L147), [`PacketDataProvider`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/core/exported/packet.go#L43-L52) and [`PacketData`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/core/exported/packet.go#L36-L41). + +Sample implementations are available for reference. For `transfer`: + +- [`PacketDataUnmarshaler`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/transfer/ibc_module.go#L303-L313), +- [`PacketDataProvider`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/transfer/types/packet.go#L85-L105) +- and [`PacketData`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/transfer/types/packet.go#L74-L83). + +For `27-interchain-accounts`: + +- [`PacketDataUnmarshaler`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/27-interchain-accounts/controller/ibc_middleware.go#L258-L268), +- [`PacketDataProvider`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/27-interchain-accounts/types/packet.go#L94-L114) +- and [`PacketData`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/27-interchain-accounts/types/packet.go#L78-L92). + +## Relayers + +- No relevant changes were made in this release. + +## IBC Light Clients + +### 06-solomachine + +Solo machines are now expected to sign data on a path that 1) does not include a connection prefix (e.g `ibc`) and 2) does not escape any characters. See PR [#4429](https://github.com/cosmos/ibc-go/pull/4429) for more details. We recommend **NOT** using the solo machine light client of versions lower than v7.3.0. diff --git a/docs/versioned_docs/version-v7.8.x/05-migrations/_category_.json b/docs/versioned_docs/version-v7.8.x/05-migrations/_category_.json new file mode 100644 index 0000000..354a84e --- /dev/null +++ b/docs/versioned_docs/version-v7.8.x/05-migrations/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Migrations", + "position": 5, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v7.8.x/05-migrations/images/auth-module-decision-tree.png b/docs/versioned_docs/version-v7.8.x/05-migrations/images/auth-module-decision-tree.png new file mode 100644 index 0000000..1122ddb Binary files /dev/null and b/docs/versioned_docs/version-v7.8.x/05-migrations/images/auth-module-decision-tree.png differ diff --git a/docs/versioned_docs/version-v8.5.x/00-intro.md b/docs/versioned_docs/version-v8.5.x/00-intro.md new file mode 100644 index 0000000..a0cda07 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/00-intro.md @@ -0,0 +1,36 @@ +--- +slug: / +sidebar_position: 0 +--- + +# IBC-Go Documentation + +Welcome to the documentation for IBC-Go, the Golang implementation of the Inter-Blockchain Communication Protocol! Looking for information on ibc-rs? [Click here to go to the ibc-rs github repo](https://github.com/cosmos/ibc-rs). + +The Inter-Blockchain Communication Protocol (IBC) is an end-to-end, connection-oriented, stateful protocol for reliable, ordered, and authenticated communication between heterogeneous blockchains arranged in an unknown and dynamic topology. + +IBC is a protocol that allows blockchains to talk to each other. Chains that speak IBC can share any type of data as long as it's encoded in bytes, enabling the industry’s most feature-rich cross-chain interactions. IBC is secure and permissionless. + +The protocol realizes this interoperability by specifying a set of data structures, abstractions, and semantics that can be implemented by any distributed ledger that satisfies a small set of requirements. + +IBC can be used to build a wide range of cross-chain applications that include token transfers, atomic swaps, multi-chain smart contracts (with or without mutually comprehensible VMs), cross-chain account control, and data and code sharding of various kinds. + +## High-level overview of IBC + +The following diagram shows how IBC works at a high level: + +![Light Mode IBC Overview](./images/ibcoverview-light.svg#gh-light-mode-only)![Dark Mode IBC Overview](./images/ibcoverview-dark.svg#gh-dark-mode-only) + +The transport layer (TAO) provides the necessary infrastructure to establish secure connections and authenticate data packets between chains. The application layer builds on top of the transport layer and defines exactly how data packets should be packaged and interpreted by the sending and receiving chains. + +IBC provides a reliable, permissionless, and generic base layer (allowing for the secure relaying of data packets), while allowing for composability and modularity with separation of concerns by moving application designs (interpreting and acting upon the packet data) to a higher-level layer. This separation is reflected in the categories: + +- **IBC/TAO** comprises the Transport, Authentication, and Ordering of packets, i.e. the infrastructure layer. +- **IBC/APP** consists of the application handlers for the data packets being passed over the transport layer. These include but are not limited to fungible token transfers (ICS-20), NFT transfers (ICS-721), and interchain accounts (ICS-27). +- **Application module:** groups any application, middleware or smart contract that may wrap downstream application handlers to provide enhanced functionality. + +Note three crucial elements in the diagram: + +- The chains depend on relayers to communicate. [Relayers](https://github.com/cosmos/ibc/blob/main/spec/relayer/ics-018-relayer-algorithms/README.md) are the "physical" connection layer of IBC: off-chain processes responsible for relaying data between two chains running the IBC protocol by scanning the state of each chain, constructing appropriate datagrams, and executing them on the opposite chain as is allowed by the protocol. +- Many relayers can serve one or more channels to send messages between the chains. +- Each side of the connection uses the light client of the other chain to quickly verify incoming messages. diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/01-overview.md b/docs/versioned_docs/version-v8.5.x/01-ibc/01-overview.md new file mode 100644 index 0000000..63e85ac --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/01-overview.md @@ -0,0 +1,329 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /ibc/overview +--- + + +# Overview + +:::note Synopsis +Learn about IBC, its components, and its use cases. +::: + +## What is the Inter-Blockchain Communication Protocol (IBC)? + +This document serves as a guide for developers who want to write their own Inter-Blockchain +Communication Protocol (IBC) applications for custom use cases. + +> IBC applications must be written as self-contained modules. + +Due to the modular design of the IBC Protocol, IBC +application developers do not need to be concerned with the low-level details of clients, +connections, and proof verification. + +This brief explanation of the lower levels of the +stack gives application developers a broad understanding of the IBC +Protocol. Abstraction layer details for channels and ports are most relevant for application developers and describe how to define custom packets and `IBCModule` callbacks. + +The requirements to have your module interact over IBC are: + +- Bind to a port or ports. +- Define your packet data. +- Use the default acknowledgment struct provided by core IBC or optionally define a custom acknowledgment struct. +- Standardize an encoding of the packet data. +- Implement the `IBCModule` interface. + +Read on for a detailed explanation of how to write a self-contained IBC application module. + +## Components Overview + +### [Clients](https://github.com/cosmos/ibc-go/blob/main/modules/core/02-client) + +IBC clients are on-chain light clients. Each light client is identified by a unique client-id. +IBC clients track the consensus states of other blockchains, along with the proof spec necessary to +properly verify proofs against the client's consensus state. A client can be associated with any number +of connections to the counterparty chain. The client identifier is auto generated using the client type +and the global client counter appended in the format: `{client-type}-{N}`. + +A `ClientState` should contain chain specific and light client specific information necessary for verifying updates +and upgrades to the IBC client. The `ClientState` may contain information such as chain-id, latest height, proof specs, +unbonding periods or the status of the light client. The `ClientState` should not contain information that +is specific to a given block at a certain height, this is the function of the `ConsensusState`. Each `ConsensusState` +should be associated with a unique block and should be referenced using a height. IBC clients are given a +client identifier prefixed store to store their associated client state and consensus states along with +any metadata associated with the consensus states. Consensus states are stored using their associated height. + +The supported IBC clients are: + +- [Solo Machine light client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/06-solomachine): Devices such as phones, browsers, or laptops. +- [Tendermint light client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/07-tendermint): The default for Cosmos SDK-based chains. +- [Localhost (loopback) client](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/09-localhost): Useful for +testing, simulation, and relaying packets to modules on the same application. + +### IBC Client Heights + +IBC Client Heights are represented by the struct: + +```go +type Height struct { + RevisionNumber uint64 + RevisionHeight uint64 +} +``` + +The `RevisionNumber` represents the revision of the chain that the height is representing. +A revision typically represents a continuous, monotonically increasing range of block-heights. +The `RevisionHeight` represents the height of the chain within the given revision. + +On any reset of the `RevisionHeight`—for example, when hard-forking a Tendermint chain— +the `RevisionNumber` will get incremented. This allows IBC clients to distinguish between a +block-height `n` of a previous revision of the chain (at revision `p`) and block-height `n` of the current +revision of the chain (at revision `e`). + +`Height`s that share the same revision number can be compared by simply comparing their respective `RevisionHeight`s. +`Height`s that do not share the same revision number will only be compared using their respective `RevisionNumber`s. +Thus a height `h` with revision number `e+1` will always be greater than a height `g` with revision number `e`, +**REGARDLESS** of the difference in revision heights. + +Ex: + +```go +Height{RevisionNumber: 3, RevisionHeight: 0} > Height{RevisionNumber: 2, RevisionHeight: 100000000000} +``` + +When a Tendermint chain is running a particular revision, relayers can simply submit headers and proofs with the revision number +given by the chain's `chainID`, and the revision height given by the Tendermint block height. When a chain updates using a hard-fork +and resets its block-height, it is responsible for updating its `chainID` to increment the revision number. +IBC Tendermint clients then verifies the revision number against their `chainID` and treat the `RevisionHeight` as the Tendermint block-height. + +Tendermint chains wishing to use revisions to maintain persistent IBC connections even across height-resetting upgrades must format their `chainID`s +in the following manner: `{chainID}-{revision_number}`. On any height-resetting upgrade, the `chainID` **MUST** be updated with a higher revision number +than the previous value. + +Ex: + +- Before upgrade `chainID`: `gaiamainnet-3` +- After upgrade `chainID`: `gaiamainnet-4` + +Clients that do not require revisions, such as the solo-machine client, simply hardcode `0` into the revision number whenever they +need to return an IBC height when implementing IBC interfaces and use the `RevisionHeight` exclusively. + +Other client-types can implement their own logic to verify the IBC heights that relayers provide in their `Update`, `Misbehavior`, and +`Verify` functions respectively. + +The IBC interfaces expect an `ibcexported.Height` interface, however all clients must use the concrete implementation provided in +`02-client/types` and reproduced above. + +### [Connections](https://github.com/cosmos/ibc-go/blob/main/modules/core/03-connection) + +Connections encapsulate two [`ConnectionEnd`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/connection/v1/connection.proto#L17) +objects on two separate blockchains. Each `ConnectionEnd` is associated with a client of the +other blockchain (for example, the counterparty blockchain). The connection handshake is responsible +for verifying that the light clients on each chain are correct for their respective counterparties. +Connections, once established, are responsible for facilitating all cross-chain verifications of IBC state. +A connection can be associated with any number of channels. + +The connection handshake is a 4-step handshake. Briefly, if a given chain A wants to open a connection with +chain B using already established light-clients on both chains: + +1. chain A sends a `ConnectionOpenInit` message to signal a connection initialization attempt with chain B. +2. chain B sends a `ConnectionOpenTry` message to try opening the connection on chain A. +3. chain A sends a `ConnectionOpenAck` message to mark its connection end state as open. +4. chain B sends a `ConnectionOpenConfirm` message to mark its connection end state as open. + +#### Time Delayed Connections + +Connections can be opened with a time delay by setting the `delayPeriod` field (in nanoseconds) in the [`MsgConnectionOpenInit`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/connection/v1/tx.proto#L45). +The time delay is used to require that the underlying light clients have been updated to a certain height before commitment verification can be performed. + +`delayPeriod` is used in conjunction with the [`max_expected_time_per_block`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/connection/v1/connection.proto#L113) parameter of the connection submodule to determine the `blockDelay`, which is number of blocks that the connection must be delayed by. + +When commitment verification is performed, the connection submodule will pass `delayPeriod` and `blockDelay` to the light client. It is up to the light client to determine whether the light client has been updated to the required height. Only the following light clients in `ibc-go` support time delayed connections: + +- `07-tendermint` +- `08-wasm` (passed to the contact) + +### [Proofs](https://github.com/cosmos/ibc-go/blob/main/modules/core/23-commitment) and [Paths](https://github.com/cosmos/ibc-go/blob/main/modules/core/24-host) + +In IBC, blockchains do not directly pass messages to each other over the network. Instead, to +communicate, a blockchain commits some state to a specifically defined path that is reserved for a +specific message type and a specific counterparty. For example, for storing a specific connectionEnd as part +of a handshake or a packet intended to be relayed to a module on the counterparty chain. A relayer +process monitors for updates to these paths and relays messages by submitting the data stored +under the path and a proof to the counterparty chain. + +Proofs are passed from core IBC to light-clients as bytes. It is up to light client implementation to interpret these bytes appropriately. + +- The paths that all IBC implementations must use for committing IBC messages is defined in +[ICS-24 Host State Machine Requirements](https://github.com/cosmos/ibc/tree/master/spec/core/ics-024-host-requirements). +- The proof format that all implementations must be able to produce and verify is defined in [ICS-23 Proofs](https://github.com/cosmos/ics23) implementation. + +### Capabilities + +IBC is intended to work in execution environments where modules do not necessarily trust each +other. Thus, IBC must authenticate module actions on ports and channels so that only modules with the +appropriate permissions can use them. + +This module authentication is accomplished using a [dynamic +capability store](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-003-dynamic-capability-store.md). Upon binding to a port or +creating a channel for a module, IBC returns a dynamic capability that the module must claim in +order to use that port or channel. The dynamic capability module prevents other modules from using that port or channel since +they do not own the appropriate capability. + +While this background information is useful, IBC modules do not need to interact at all with +these lower-level abstractions. The relevant abstraction layer for IBC application developers is +that of channels and ports. IBC applications must be written as self-contained **modules**. + +A module on one blockchain can communicate with other modules on other blockchains by sending, +receiving, and acknowledging packets through channels that are uniquely identified by the +`(channelID, portID)` tuple. + +A useful analogy is to consider IBC modules as internet applications on +a computer. A channel can then be conceptualized as an IP connection, with the IBC portID being +analogous to an IP port and the IBC channelID being analogous to an IP address. Thus, a single +instance of an IBC module can communicate on the same port with any number of other modules and +IBC correctly routes all packets to the relevant module using the (channelID, portID tuple). An +IBC module can also communicate with another IBC module over multiple ports, with each +`(portID<->portID)` packet stream being sent on a different unique channel. + +### [Ports](https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port) + +An IBC module can bind to any number of ports. Each port must be identified by a unique `portID`. +Since IBC is designed to be secure with mutually distrusted modules operating on the same ledger, +binding a port returns a dynamic object capability. In order to take action on a particular port +(for example, an open channel with its portID), a module must provide the dynamic object capability to the IBC +handler. This requirement prevents a malicious module from opening channels with ports it does not own. Thus, +IBC modules are responsible for claiming the capability that is returned on `BindPort`. + +### [Channels](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +An IBC channel can be established between two IBC ports. Currently, a port is exclusively owned by a +single module. IBC packets are sent over channels. Just as IP packets contain the destination IP +address and IP port, and the source IP address and source IP port, IBC packets contain +the destination portID and channelID, and the source portID and channelID. This packet structure enables IBC to +correctly route packets to the destination module while allowing modules receiving packets to +know the sender module. + +A channel can be `ORDERED`, where packets from a sending module must be processed by the +receiving module in the order they were sent. Or a channel can be `UNORDERED`, where packets +from a sending module are processed in the order they arrive (might be in a different order than they were sent). + +Modules can choose which channels they wish to communicate over with, thus IBC expects modules to +implement callbacks that are called during the channel handshake. These callbacks can do custom +channel initialization logic. If any callback returns an error, the channel handshake fails. Thus, by +returning errors on callbacks, modules can programmatically reject and accept channels. + +The channel handshake is a 4-step handshake. Briefly, if a given chain A wants to open a channel with +chain B using an already established connection: + +1. chain A sends a `ChanOpenInit` message to signal a channel initialization attempt with chain B. +2. chain B sends a `ChanOpenTry` message to try opening the channel on chain A. +3. chain A sends a `ChanOpenAck` message to mark its channel end status as open. +4. chain B sends a `ChanOpenConfirm` message to mark its channel end status as open. + +If all handshake steps are successful, the channel is opened on both sides. At each step in the handshake, the module +associated with the `ChannelEnd` executes its callback. So +on `ChanOpenInit`, the module on chain A executes its callback `OnChanOpenInit`. + +The channel identifier is auto derived in the format: `channel-{N}` where N is the next sequence to be used. + +Just as ports came with dynamic capabilities, channel initialization returns a dynamic capability +that the module **must** claim so that they can pass in a capability to authenticate channel actions +like sending packets. The channel capability is passed into the callback on the first parts of the +handshake; either `OnChanOpenInit` on the initializing chain or `OnChanOpenTry` on the other chain. + +#### Closing channels + +Closing a channel occurs in 2 handshake steps as defined in [ICS 04](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics). +Once a channel is closed, it cannot be reopened. The channel handshake steps are: + +**`ChanCloseInit`** closes a channel on the executing chain if + +- the channel exists and it is not already closed, +- the connection it exists upon is OPEN, +- the [IBC module callback `OnChanCloseInit`](./03-apps/02-ibcmodule.md#channel-closing-callbacks) returns `nil`. + +`ChanCloseInit` can be initiated by any user by submitting a `MsgChannelCloseInit` transaction. +Note that channels are automatically closed when a packet times out on an `ORDERED` channel. +A timeout on an `ORDERED` channel skips the `ChanCloseInit` step and immediately closes the channel. + +**`ChanCloseConfirm`** is a response to a counterparty channel executing `ChanCloseInit`. The channel +on the executing chain closes if + +- the channel exists and is not already closed, +- the connection the channel exists upon is OPEN, +- the executing chain successfully verifies that the counterparty channel has been closed +- the [IBC module callback `OnChanCloseConfirm`](./03-apps/02-ibcmodule.md#channel-closing-callbacks) returns `nil`. + +Currently, none of the IBC applications provided in ibc-go support `ChanCloseInit`. + +### [Packets](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +Modules communicate with each other by sending packets over IBC channels. All +IBC packets contain the destination `portID` and `channelID` along with the source `portID` and +`channelID`. This packet structure allows modules to know the sender module of a given packet. IBC packets +contain a sequence to optionally enforce ordering. + +IBC packets also contain a `TimeoutHeight` and a `TimeoutTimestamp` that determine the deadline before the receiving module must process a packet. + +Modules send custom application data to each other inside the `Data []byte` field of the IBC packet. +Thus, packet data is opaque to IBC handlers. It is incumbent on a sender module to encode +their application-specific packet information into the `Data` field of packets. The receiver +module must decode that `Data` back to the original application data. + +### [Receipts and Timeouts](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +Since IBC works over a distributed network and relies on potentially faulty relayers to relay messages between ledgers, +IBC must handle the case where a packet does not get sent to its destination in a timely manner or at all. Packets must +specify a non-zero value for timeout height (`TimeoutHeight`) or timeout timestamp (`TimeoutTimestamp` ) after which a packet can no longer be successfully received on the destination chain. + +- The `timeoutHeight` indicates a consensus height on the destination chain after which the packet is no longer be processed, and instead counts as having timed-out. +- The `timeoutTimestamp` indicates a timestamp on the destination chain after which the packet is no longer be processed, and instead counts as having timed-out. + +If the timeout passes without the packet being successfully received, the packet can no longer be +received on the destination chain. The sending module can timeout the packet and take appropriate actions. + +If the timeout is reached, then a proof of packet timeout can be submitted to the original chain. The original chain can then perform +application-specific logic to timeout the packet, perhaps by rolling back the packet send changes (refunding senders any locked funds, etc.). + +- In ORDERED channels, a timeout of a single packet in the channel causes the channel to close. + + - If packet sequence `n` times out, then a packet at sequence `k > n` cannot be received without violating the contract of ORDERED channels that packets are processed in the order that they are sent. + - Since ORDERED channels enforce this invariant, a proof that sequence `n` has not been received on the destination chain by the specified timeout of packet `n` is sufficient to timeout packet `n` and close the channel. + +- In UNORDERED channels, the application-specific timeout logic for that packet is applied and the channel is not closed. + + - Packets can be received in any order. + + - IBC writes a packet receipt for each sequence receives in the UNORDERED channel. This receipt does not contain information; it is simply a marker intended to signify that the UNORDERED channel has received a packet at the specified sequence. + + - To timeout a packet on an UNORDERED channel, a proof is required that a packet receipt **does not exist** for the packet's sequence by the specified timeout. + +For this reason, most modules should use UNORDERED channels as they require fewer liveness guarantees to function effectively for users of that channel. + +### [Acknowledgments](https://github.com/cosmos/ibc-go/blob/main/modules/core/04-channel) + +Modules can also choose to write application-specific acknowledgments upon processing a packet. Acknowledgments can be done: + +- Synchronously on `OnRecvPacket` if the module processes packets as soon as they are received from IBC module. +- Asynchronously if module processes packets at some later point after receiving the packet. + +This acknowledgment data is opaque to IBC much like the packet `Data` and is treated by IBC as a simple byte string `[]byte`. Receiver modules must encode their acknowledgment so that the sender module can decode it correctly. The encoding must be negotiated between the two parties during version negotiation in the channel handshake. + +The acknowledgment can encode whether the packet processing succeeded or failed, along with additional information that allows the sender module to take appropriate action. + +After the acknowledgment has been written by the receiving chain, a relayer relays the acknowledgment back to the original sender module. + +The original sender module then executes application-specific acknowledgment logic using the contents of the acknowledgment. + +- After an acknowledgement fails, packet-send changes can be rolled back (for example, refunding senders in ICS20). + +- After an acknowledgment is received successfully on the original sender on the chain, the corresponding packet commitment is deleted since it is no longer needed. + +## Further Readings and Specs + +If you want to learn more about IBC, check the following specifications: + +- [IBC specification overview](https://github.com/cosmos/ibc/blob/master/README.md) diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/02-integration.md b/docs/versioned_docs/version-v8.5.x/01-ibc/02-integration.md new file mode 100644 index 0000000..1dd9d13 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/02-integration.md @@ -0,0 +1,230 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /ibc/integration +--- + +# Integration + +:::note Synopsis +Learn how to integrate IBC to your application and send data packets to other chains. +::: + +This document outlines the required steps to integrate and configure the [IBC +module](https://github.com/cosmos/ibc-go/tree/main/modules/core) to your Cosmos SDK application and +send fungible token transfers to other chains. + +## Integrating the IBC module + +Integrating the IBC module to your SDK-based application is straightforward. The general changes can be summarized in the following steps: + +- Add required modules to the `module.BasicManager` +- Define additional `Keeper` fields for the new modules on the `App` type +- Add the module's `StoreKey`s and initialize their `Keeper`s +- Set up corresponding routers and routes for the `ibc` module +- Add the modules to the module `Manager` +- Add modules to `Begin/EndBlockers` and `InitGenesis` +- Update the module `SimulationManager` to enable simulations + +### Module `BasicManager` and `ModuleAccount` permissions + +The first step is to add the following modules to the `BasicManager`: `x/capability`, `x/ibc`, +and `x/ibc-transfer`. After that, we need to grant `Minter` and `Burner` permissions to +the `ibc-transfer` `ModuleAccount` to mint and burn relayed tokens. + +### Integrating light clients + +> Note that from v7 onwards, all light clients have to be explicitly registered in a chain's app.go and follow the steps listed below. +> This is in contrast to earlier versions of ibc-go when `07-tendermint` and `06-solomachine` were added out of the box. + +All light clients must be registered with `module.BasicManager` in a chain's app.go file. + +The following code example shows how to register the existing `ibctm.AppModuleBasic{}` light client implementation. + +```go +import ( + ... + // highlight-next-line ++ ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint" + ... +) + +// app.go +var ( + ModuleBasics = module.NewBasicManager( + // ... + capability.AppModuleBasic{}, + ibc.AppModuleBasic{}, + transfer.AppModuleBasic{}, // i.e ibc-transfer module + + // register light clients on IBC + // highlight-next-line ++ ibctm.AppModuleBasic{}, + ) + + // module account permissions + maccPerms = map[string][]string{ + // other module accounts permissions + // ... + ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + } +) +``` + +### Application fields + +Then, we need to register the `Keepers` as follows: + +```go title="app.go" +type App struct { + // baseapp, keys and subspaces definitions + + // other keepers + // ... + IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly + TransferKeeper ibctransferkeeper.Keeper // for cross-chain fungible token transfers + + // make scoped keepers public for test purposes + ScopedIBCKeeper capabilitykeeper.ScopedKeeper + ScopedTransferKeeper capabilitykeeper.ScopedKeeper + + /// ... + /// module and simulation manager definitions +} +``` + +### Configure the `Keepers` + +During initialization, besides initializing the IBC `Keepers` (for the `x/ibc`, and +`x/ibc-transfer` modules), we need to grant specific capabilities through the capability module +`ScopedKeepers` so that we can authenticate the object-capability permissions for each of the IBC +channels. + +```go +func NewApp(...args) *App { + // define codecs and baseapp + + // add capability keeper and ScopeToModule for ibc module + app.CapabilityKeeper = capabilitykeeper.NewKeeper(appCodec, keys[capabilitytypes.StoreKey], memKeys[capabilitytypes.MemStoreKey]) + + // grant capabilities for the ibc and ibc-transfer modules + scopedIBCKeeper := app.CapabilityKeeper.ScopeToModule(ibcexported.ModuleName) + scopedTransferKeeper := app.CapabilityKeeper.ScopeToModule(ibctransfertypes.ModuleName) + + // ... other modules keepers + + // Create IBC Keeper + app.IBCKeeper = ibckeeper.NewKeeper( + appCodec, keys[ibcexported.StoreKey], app.GetSubspace(ibcexported.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, + ) + + // Create Transfer Keepers + app.TransferKeeper = ibctransferkeeper.NewKeeper( + appCodec, keys[ibctransfertypes.StoreKey], app.GetSubspace(ibctransfertypes.ModuleName), + app.IBCKeeper.ChannelKeeper, app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.AccountKeeper, app.BankKeeper, scopedTransferKeeper, + ) + transferModule := transfer.NewAppModule(app.TransferKeeper) + + // .. continues +} +``` + +### Register `Routers` + +IBC needs to know which module is bound to which port so that it can route packets to the +appropriate module and call the appropriate callbacks. The port to module name mapping is handled by +IBC's port `Keeper`. However, the mapping from module name to the relevant callbacks is accomplished +by the port +[`Router`](https://github.com/cosmos/ibc-go/blob/main/modules/core/05-port/types/router.go) on the +IBC module. + +Adding the module routes allows the IBC handler to call the appropriate callback when processing a +channel handshake or a packet. + +Currently, a `Router` is static so it must be initialized and set correctly on app initialization. +Once the `Router` has been set, no new routes can be added. + +```go title="app.go" +func NewApp(...args) *App { + // .. continuation from above + + // Create static IBC router, add ibc-transfer module route, then set and seal it + ibcRouter := port.NewRouter() + ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferModule) + // Setting Router will finalize all routes by sealing router + // No more routes can be added + app.IBCKeeper.SetRouter(ibcRouter) + + // .. continues +``` + +### Module Managers + +In order to use IBC, we need to add the new modules to the module `Manager` and to the `SimulationManager` in case your application supports [simulations](https://docs.cosmos.network/main/learn/advanced/simulation). + +```go title="app.go" +func NewApp(...args) *App { + // .. continuation from above + + app.mm = module.NewManager( + // other modules + // ... + capability.NewAppModule(appCodec, *app.CapabilityKeeper), + ibc.NewAppModule(app.IBCKeeper), + transferModule, + ) + + // ... + + app.sm = module.NewSimulationManager( + // other modules + // ... + capability.NewAppModule(appCodec, *app.CapabilityKeeper), + ibc.NewAppModule(app.IBCKeeper), + transferModule, + ) + + // .. continues +``` + +### Application ABCI Ordering + +One addition from IBC is the concept of `HistoricalEntries` which are stored on the staking module. +Each entry contains the historical information for the `Header` and `ValidatorSet` of this chain which is stored +at each height during the `BeginBlock` call. The historical info is required to introspect the +past historical info at any given height in order to verify the light client `ConsensusState` during the +connection handshake. + +```go title="app.go" +func NewApp(...args) *App { + // .. continuation from above + + // add staking and ibc modules to BeginBlockers + app.mm.SetOrderBeginBlockers( + // other modules ... + stakingtypes.ModuleName, ibcexported.ModuleName, + ) + + // ... + + // NOTE: Capability module must occur first so that it can initialize any capabilities + // so that other modules that want to create or claim capabilities afterwards in InitChain + // can do so safely. + app.mm.SetOrderInitGenesis( + capabilitytypes.ModuleName, + // other modules ... + ibcexported.ModuleName, ibctransfertypes.ModuleName, + ) + + // .. continues +``` + +:::warning +**IMPORTANT**: The capability module **must** be declared first in `SetOrderInitGenesis` +::: + +That's it! You have now wired up the IBC module and are now able to send fungible tokens across +different chains. If you want to have a broader view of the changes take a look into the SDK's +[`SimApp`](https://github.com/cosmos/ibc-go/blob/main/testing/simapp/app.go). diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/01-apps.md b/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/01-apps.md new file mode 100644 index 0000000..bb08a83 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/01-apps.md @@ -0,0 +1,494 @@ +--- +title: IBC Applications +sidebar_label: IBC Applications +sidebar_position: 1 +slug: /ibc/apps/apps +--- + +# IBC Applications + +Learn how to configure your application to use IBC and send data packets to other chains. + +This document serves as a guide for developers who want to write their own Inter-blockchain +Communication Protocol (IBC) applications for custom use cases. + +Due to the modular design of the IBC protocol, IBC +application developers do not need to concern themselves with the low-level details of clients, +connections, and proof verification. Nevertheless a brief explanation of the lower levels of the +stack is given so that application developers may have a high-level understanding of the IBC +protocol. Then the document goes into detail on the abstraction layer most relevant for application +developers (channels and ports), and describes how to define your own custom packets, and +`IBCModule` callbacks. + +To have your module interact over IBC you must: bind to a port(s), define your own packet data and acknowledgement structs as well as how to encode/decode them, and implement the +`IBCModule` interface. Below is a more detailed explanation of how to write an IBC application +module correctly. + +:::note + +## Pre-requisites Readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: + +## Create a custom IBC application module + +### Implement `IBCModule` Interface and callbacks + +The Cosmos SDK expects all IBC modules to implement the [`IBCModule` +interface](https://github.com/cosmos/ibc-go/tree/main/modules/core/05-port/types/module.go). This +interface contains all of the callbacks IBC expects modules to implement. This section will describe +the callbacks that are called during channel handshake execution. + +Here are the channel handshake callbacks that modules are expected to implement: + +```go +// Called by IBC Handler on MsgOpenInit +func (k Keeper) OnChanOpenInit(ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) error { + // OpenInit must claim the channelCapability that IBC passes into the callback + if err := k.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return err + } + + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + // Examples: Abort if order == UNORDERED, + // Abort if version is unsupported + err := checkArguments(args) + return err +} + +// Called by IBC Handler on MsgOpenTry +OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + // OpenTry must claim the channelCapability that IBC passes into the callback + if err := k.scopedKeeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return err + } + + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + if err := checkArguments(args); err != nil { + return err + } + + // Construct application version + // IBC applications must return the appropriate application version + // This can be a simple string or it can be a complex version constructed + // from the counterpartyVersion and other arguments. + // The version returned will be the channel version used for both channel ends. + appVersion := negotiateAppVersion(counterpartyVersion, args) + + return appVersion, nil +} + +// Called by IBC Handler on MsgOpenAck +OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyVersion string, +) error { + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} + +// Called by IBC Handler on MsgOpenConfirm +OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} +``` + +The channel closing handshake will also invoke module callbacks that can return errors to abort the +closing handshake. Closing a channel is a 2-step handshake, the initiating chain calls +`ChanCloseInit` and the finalizing chain calls `ChanCloseConfirm`. + +```go +// Called by IBC Handler on MsgCloseInit +OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + // ... do custom finalization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} + +// Called by IBC Handler on MsgCloseConfirm +OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // ... do custom finalization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} +``` + +#### Channel Handshake Version Negotiation + +Application modules are expected to verify versioning used during the channel handshake procedure. + +- `ChanOpenInit` callback should verify that the `MsgChanOpenInit.Version` is valid +- `ChanOpenTry` callback should construct the application version used for both channel ends. If no application version can be constructed, it must return an error. +- `ChanOpenAck` callback should verify that the `MsgChanOpenAck.CounterpartyVersion` is valid and supported. + +IBC expects application modules to perform application version negotiation in `OnChanOpenTry`. The negotiated version +must be returned to core IBC. If the version cannot be negotiated, an error should be returned. + +Versions must be strings but can implement any versioning structure. If your application plans to +have linear releases then semantic versioning is recommended. If your application plans to release +various features in between major releases then it is advised to use the same versioning scheme +as IBC. This versioning scheme specifies a version identifier and compatible feature set with +that identifier. Valid version selection includes selecting a compatible version identifier with +a subset of features supported by your application for that version. The struct is used for this +scheme can be found in `03-connection/types`. + +Since the version type is a string, applications have the ability to do simple version verification +via string matching or they can use the already implemented versioning system and pass the proto +encoded version into each handhshake call as necessary. + +ICS20 currently implements basic string matching with a single supported version. + +### Bind Ports + +Currently, ports must be bound on app initialization. A module may bind to ports in `InitGenesis` +like so: + +```go +func InitGenesis(ctx sdk.Context, keeper keeper.Keeper, state types.GenesisState) { + // ... other initialization logic + + // Only try to bind to port if it is not already bound, since we may already own + // port capability from capability InitGenesis + if !hasCapability(ctx, state.PortID) { + // module binds to desired ports on InitChain + // and claims returned capabilities + cap1 := keeper.IBCPortKeeper.BindPort(ctx, port1) + cap2 := keeper.IBCPortKeeper.BindPort(ctx, port2) + cap3 := keeper.IBCPortKeeper.BindPort(ctx, port3) + + // NOTE: The module's scoped capability keeper must be private + keeper.scopedKeeper.ClaimCapability(cap1) + keeper.scopedKeeper.ClaimCapability(cap2) + keeper.scopedKeeper.ClaimCapability(cap3) + } + + // ... more initialization logic +} +``` + +### Custom Packets + +Modules connected by a channel must agree on what application data they are sending over the +channel, as well as how they will encode/decode it. This process is not specified by IBC as it is up +to each application module to determine how to implement this agreement. However, for most +applications this will happen as a version negotiation during the channel handshake. While more +complex version negotiation is possible to implement inside the channel opening handshake, a very +simple version negotiation is implemented in the [ibc-transfer module](https://github.com/cosmos/ibc-go/tree/main/modules/apps/transfer/module.go). + +Thus, a module must define its custom packet data structure, along with a well-defined way to +encode and decode it to and from `[]byte`. + +```go +// Custom packet data defined in application module +type CustomPacketData struct { + // Custom fields ... +} + +EncodePacketData(packetData CustomPacketData) []byte { + // encode packetData to bytes +} + +DecodePacketData(encoded []byte) (CustomPacketData) { + // decode from bytes to packet data +} +``` + +Then a module must encode its packet data before sending it through IBC. + +```go +// retrieve the dynamic capability for this channel +channelCap := scopedKeeper.GetCapability(ctx, channelCapName) +// Sending custom application packet data +data := EncodePacketData(customPacketData) +packet.Data = data +// Send packet to IBC, authenticating with channelCap +sequence, err := IBCChannelKeeper.SendPacket( + ctx, + channelCap, + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + data, +) +``` + +A module receiving a packet must decode the `PacketData` into a structure it expects so that it can +act on it. + +```go +// Receiving custom application packet data (in OnRecvPacket) +packetData := DecodePacketData(packet.Data) +// handle received custom packet data +``` + +#### Packet Flow Handling + +Just as IBC expected modules to implement callbacks for channel handshakes, IBC also expects modules +to implement callbacks for handling the packet flow through a channel. + +Once a module A and module B are connected to each other, relayers can start relaying packets and +acknowledgements back and forth on the channel. + +![IBC packet flow diagram](https://media.githubusercontent.com/media/cosmos/ibc/old/spec/ics-004-channel-and-packet-semantics/channel-state-machine.png) + +Briefly, a successful packet flow works as follows: + +1. module A sends a packet through the IBC module +2. the packet is received by module B +3. if module B writes an acknowledgement of the packet then module A will process the + acknowledgement +4. if the packet is not successfully received before the timeout, then module A processes the + packet's timeout. + +##### Sending Packets + +Modules do not send packets through callbacks, since the modules initiate the action of sending +packets to the IBC module, as opposed to other parts of the packet flow where msgs sent to the IBC +module must trigger execution on the port-bound module through the use of callbacks. Thus, to send a +packet a module simply needs to call `SendPacket` on the `IBCChannelKeeper`. + +```go +// retrieve the dynamic capability for this channel +channelCap := scopedKeeper.GetCapability(ctx, channelCapName) +// Sending custom application packet data +data := EncodePacketData(customPacketData) +// Send packet to IBC, authenticating with channelCap +sequence, err := IBCChannelKeeper.SendPacket( + ctx, + channelCap, + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + data, +) +``` + +:::warning +In order to prevent modules from sending packets on channels they do not own, IBC expects +modules to pass in the correct channel capability for the packet's source channel. +::: + +##### Receiving Packets + +To handle receiving packets, the module must implement the `OnRecvPacket` callback. This gets +invoked by the IBC module after the packet has been proved valid and correctly processed by the IBC +keepers. Thus, the `OnRecvPacket` callback only needs to worry about making the appropriate state +changes given the packet data without worrying about whether the packet is valid or not. + +Modules may return to the IBC handler an acknowledgement which implements the Acknowledgement interface. +The IBC handler will then commit this acknowledgement of the packet so that a relayer may relay the +acknowledgement back to the sender module. + +The state changes that occurred during this callback will only be written if: + +- the acknowledgement was successful as indicated by the `Success()` function of the acknowledgement +- if the acknowledgement returned is nil indicating that an asynchronous process is occurring + +NOTE: Applications which process asynchronous acknowledgements must handle reverting state changes +when appropriate. Any state changes that occurred during the `OnRecvPacket` callback will be written +for asynchronous acknowledgements. + +```go +OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, +) ibcexported.Acknowledgement { + // Decode the packet data + packetData := DecodePacketData(packet.Data) + + // do application state changes based on packet data and return the acknowledgement + // NOTE: The acknowledgement will indicate to the IBC handler if the application + // state changes should be written via the `Success()` function. Application state + // changes are only written if the acknowledgement is successful or the acknowledgement + // returned is nil indicating that an asynchronous acknowledgement will occur. + ack := processPacket(ctx, packet, packetData) + + return ack +} +``` + +The Acknowledgement interface: + +```go +// Acknowledgement defines the interface used to return +// acknowledgements in the OnRecvPacket callback. +type Acknowledgement interface { + Success() bool + Acknowledgement() []byte +} +``` + +### Acknowledgements + +Modules may commit an acknowledgement upon receiving and processing a packet in the case of synchronous packet processing. +In the case where a packet is processed at some later point after the packet has been received (asynchronous execution), the acknowledgement +will be written once the packet has been processed by the application which may be well after the packet receipt. + +NOTE: Most blockchain modules will want to use the synchronous execution model in which the module processes and writes the acknowledgement +for a packet as soon as it has been received from the IBC module. + +This acknowledgement can then be relayed back to the original sender chain, which can take action +depending on the contents of the acknowledgement. + +Just as packet data was opaque to IBC, acknowledgements are similarly opaque. Modules must pass and +receive acknowledegments with the IBC modules as byte strings. + +Thus, modules must agree on how to encode/decode acknowledgements. The process of creating an +acknowledgement struct along with encoding and decoding it, is very similar to the packet data +example above. [ICS 04](https://github.com/cosmos/ibc/blob/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope) +specifies a recommended format for acknowledgements. This acknowledgement type can be imported from +[channel types](https://github.com/cosmos/ibc-go/tree/main/modules/core/04-channel/types). + +While modules may choose arbitrary acknowledgement structs, a default acknowledgement types is provided by IBC [here](https://github.com/cosmos/ibc-go/blob/main/proto/ibc/core/channel/v1/channel.proto): + +```proto +// Acknowledgement is the recommended acknowledgement format to be used by +// app-specific protocols. +// NOTE: The field numbers 21 and 22 were explicitly chosen to avoid accidental +// conflicts with other protobuf message formats used for acknowledgements. +// The first byte of any message with this format will be the non-ASCII values +// `0xaa` (result) or `0xb2` (error). Implemented as defined by ICS: +// https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope +message Acknowledgement { + // response contains either a result or an error and must be non-empty + oneof response { + bytes result = 21; + string error = 22; + } +} +``` + +#### Acknowledging Packets + +After a module writes an acknowledgement, a relayer can relay back the acknowledgement to the sender module. The sender module can +then process the acknowledgement using the `OnAcknowledgementPacket` callback. The contents of the +acknowledgement is entirely up to the modules on the channel (just like the packet data); however, it +may often contain information on whether the packet was successfully processed along +with some additional data that could be useful for remediation if the packet processing failed. + +Since the modules are responsible for agreeing on an encoding/decoding standard for packet data and +acknowledgements, IBC will pass in the acknowledgements as `[]byte` to this callback. The callback +is responsible for decoding the acknowledgement and processing it. + +```go +OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, +) (*sdk.Result, error) { + // Decode acknowledgement + ack := DecodeAcknowledgement(acknowledgement) + + // process ack + res, err := processAck(ack) + return res, err +} +``` + +#### Timeout Packets + +If the timeout for a packet is reached before the packet is successfully received or the +counterparty channel end is closed before the packet is successfully received, then the receiving +chain can no longer process it. Thus, the sending chain must process the timeout using +`OnTimeoutPacket` to handle this situation. Again the IBC module will verify that the timeout is +indeed valid, so our module only needs to implement the state machine logic for what to do once a +timeout is reached and the packet can no longer be received. + +```go +OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, +) (*sdk.Result, error) { + // do custom timeout logic +} +``` + +### Routing + +As mentioned above, modules must implement the IBC module interface (which contains both channel +handshake callbacks and packet handling callbacks). The concrete implementation of this interface +must be registered with the module name as a route on the IBC `Router`. + +```go +// app.go +func NewApp(...args) *App { +// ... + +// Create static IBC router, add module routes, then set and seal it +ibcRouter := port.NewRouter() + +ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferModule) +// Note: moduleCallbacks must implement IBCModule interface +ibcRouter.AddRoute(moduleName, moduleCallbacks) + +// Setting Router will finalize all routes by sealing router +// No more routes can be added +app.IBCKeeper.SetRouter(ibcRouter) +``` + +## Working Example + +For a real working example of an IBC application, you can look through the `ibc-transfer` module +which implements everything discussed above. + +Here are the useful parts of the module to look at: + +[Binding to transfer +port](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/genesis.go) + +[Sending transfer +packets](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/relay.go) + +[Implementing IBC +callbacks](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/ibc_module.go) diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/02-ibcmodule.md b/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/02-ibcmodule.md new file mode 100644 index 0000000..3d8aa09 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/02-ibcmodule.md @@ -0,0 +1,383 @@ +--- +title: Implement IBCModule interface and callbacks +sidebar_label: Implement IBCModule interface and callbacks +sidebar_position: 2 +slug: /ibc/apps/ibcmodule +--- + +# Implement `IBCModule` interface and callbacks + +:::note Synopsis +Learn how to implement the `IBCModule` interface and all of the callbacks it requires. +::: + +The Cosmos SDK expects all IBC modules to implement the [`IBCModule` +interface](https://github.com/cosmos/ibc-go/tree/main/modules/core/05-port/types/module.go). This interface contains all of the callbacks IBC expects modules to implement. They include callbacks related to channel handshake, closing and packet callbacks (`OnRecvPacket`, `OnAcknowledgementPacket` and `OnTimeoutPacket`). + +```go +// IBCModule implements the ICS26 interface for given the keeper. +// The implementation of the IBCModule interface could for example be in a file called ibc_module.go, +// but ultimately file structure is up to the developer +type IBCModule struct { + keeper keeper.Keeper +} +``` + +Additionally, in the `module.go` file, add the following line: + +```go +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + // Add this line + _ porttypes.IBCModule = IBCModule{} +) +``` + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: + +## Channel handshake callbacks + +This section will describe the callbacks that are called during channel handshake execution. Among other things, it will claim channel capabilities passed on from core IBC. For a refresher on capabilities, check [the Overview section](../01-overview.md#capabilities). + +Here are the channel handshake callbacks that modules are expected to implement: + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `checkArguments` and `negotiateAppVersion` functions. + +```go +// Called by IBC Handler on MsgOpenInit +func (im IBCModule) OnChanOpenInit(ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + // Examples: + // - Abort if order == UNORDERED, + // - Abort if version is unsupported + if err := checkArguments(args); err != nil { + return "", err + } + + // OpenInit must claim the channelCapability that IBC passes into the callback + if err := im.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return "", err + } + + return version, nil +} + +// Called by IBC Handler on MsgOpenTry +func (im IBCModule) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + // ... do custom initialization logic + + // Use above arguments to determine if we want to abort handshake + if err := checkArguments(args); err != nil { + return "", err + } + + // OpenTry must claim the channelCapability that IBC passes into the callback + if err := im.keeper.scopedKeeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil { + return err + } + + // Construct application version + // IBC applications must return the appropriate application version + // This can be a simple string or it can be a complex version constructed + // from the counterpartyVersion and other arguments. + // The version returned will be the channel version used for both channel ends. + appVersion := negotiateAppVersion(counterpartyVersion, args) + + return appVersion, nil +} + +// Called by IBC Handler on MsgOpenAck +func (im IBCModule) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyVersion string, +) error { + if counterpartyVersion != types.Version { + return sdkerrors.Wrapf(types.ErrInvalidVersion, "invalid counterparty version: %s, expected %s", counterpartyVersion, types.Version) + } + + // do custom logic + + return nil +} + +// Called by IBC Handler on MsgOpenConfirm +func (im IBCModule) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // do custom logic + + return nil +} +``` + +### Channel closing callbacks + +The channel closing handshake will also invoke module callbacks that can return errors to abort the closing handshake. Closing a channel is a 2-step handshake, the initiating chain calls `ChanCloseInit` and the finalizing chain calls `ChanCloseConfirm`. + +Currently, all IBC modules in this repository return an error for `OnChanCloseInit` to prevent the channels from closing. This is because any user can call `ChanCloseInit` by submitting a `MsgChannelCloseInit` transaction. + +```go +// Called by IBC Handler on MsgCloseInit +func (im IBCModule) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + // ... do custom finalization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} + +// Called by IBC Handler on MsgCloseConfirm +func (im IBCModule) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // ... do custom finalization logic + + // Use above arguments to determine if we want to abort handshake + err := checkArguments(args) + return err +} +``` + +### Channel handshake version negotiation + +Application modules are expected to verify versioning used during the channel handshake procedure. + +- `OnChanOpenInit` will verify that the relayer-chosen parameters + are valid and perform any custom `INIT` logic. + It may return an error if the chosen parameters are invalid + in which case the handshake is aborted. + If the provided version string is non-empty, `OnChanOpenInit` should return + the version string if valid or an error if the provided version is invalid. + **If the version string is empty, `OnChanOpenInit` is expected to + return a default version string representing the version(s) + it supports.** + If there is no default version string for the application, + it should return an error if the provided version is an empty string. +- `OnChanOpenTry` will verify the relayer-chosen parameters along with the + counterparty-chosen version string and perform custom `TRY` logic. + If the relayer-chosen parameters + are invalid, the callback must return an error to abort the handshake. + If the counterparty-chosen version is not compatible with this module's + supported versions, the callback must return an error to abort the handshake. + If the versions are compatible, the try callback must select the final version + string and return it to core IBC. + `OnChanOpenTry` may also perform custom initialization logic. +- `OnChanOpenAck` will error if the counterparty selected version string + is invalid and abort the handshake. It may also perform custom ACK logic. + +Versions must be strings but can implement any versioning structure. If your application plans to +have linear releases then semantic versioning is recommended. If your application plans to release +various features in between major releases then it is advised to use the same versioning scheme +as IBC. This versioning scheme specifies a version identifier and compatible feature set with +that identifier. Valid version selection includes selecting a compatible version identifier with +a subset of features supported by your application for that version. The struct used for this +scheme can be found in [03-connection/types](https://github.com/cosmos/ibc-go/blob/main/modules/core/03-connection/types/version.go#L16). + +Since the version type is a string, applications have the ability to do simple version verification +via string matching or they can use the already implemented versioning system and pass the proto +encoded version into each handhshake call as necessary. + +ICS20 currently implements basic string matching with a single supported version. + +## Packet callbacks + +Just as IBC expects modules to implement callbacks for channel handshakes, it also expects modules to implement callbacks for handling the packet flow through a channel, as defined in the `IBCModule` interface. + +Once a module A and module B are connected to each other, relayers can start relaying packets and acknowledgements back and forth on the channel. + +![IBC packet flow diagram](./images/packet_flow.png) + +Briefly, a successful packet flow works as follows: + +1. Module A sends a packet through the IBC module +2. The packet is received by module B +3. If module B writes an acknowledgement of the packet then module A will process the acknowledgement +4. If the packet is not successfully received before the timeout, then module A processes the packet's timeout. + +### Sending packets + +Modules **do not send packets through callbacks**, since the modules initiate the action of sending packets to the IBC module, as opposed to other parts of the packet flow where messages sent to the IBC +module must trigger execution on the port-bound module through the use of callbacks. Thus, to send a packet a module simply needs to call `SendPacket` on the `IBCChannelKeeper`. + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `EncodePacketData(customPacketData)` function. + +```go +// retrieve the dynamic capability for this channel +channelCap := scopedKeeper.GetCapability(ctx, channelCapName) +// Sending custom application packet data +data := EncodePacketData(customPacketData) +// Send packet to IBC, authenticating with channelCap +sequence, err := IBCChannelKeeper.SendPacket( + ctx, + channelCap, + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + data, +) +``` + +:::warning +In order to prevent modules from sending packets on channels they do not own, IBC expects +modules to pass in the correct channel capability for the packet's source channel. +::: + +### Receiving packets + +To handle receiving packets, the module must implement the `OnRecvPacket` callback. This gets +invoked by the IBC module after the packet has been proved valid and correctly processed by the IBC +keepers. Thus, the `OnRecvPacket` callback only needs to worry about making the appropriate state +changes given the packet data without worrying about whether the packet is valid or not. + +Modules may return to the IBC handler an acknowledgement which implements the `Acknowledgement` interface. +The IBC handler will then commit this acknowledgement of the packet so that a relayer may relay the +acknowledgement back to the sender module. + +The state changes that occurred during this callback will only be written if: + +- the acknowledgement was successful as indicated by the `Success()` function of the acknowledgement +- if the acknowledgement returned is nil indicating that an asynchronous process is occurring + +NOTE: Applications which process asynchronous acknowledgements must handle reverting state changes +when appropriate. Any state changes that occurred during the `OnRecvPacket` callback will be written +for asynchronous acknowledgements. + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `DecodePacketData(packet.Data)` function. + +```go +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, +) ibcexported.Acknowledgement { + // Decode the packet data + packetData := DecodePacketData(packet.Data) + + // do application state changes based on packet data and return the acknowledgement + // NOTE: The acknowledgement will indicate to the IBC handler if the application + // state changes should be written via the `Success()` function. Application state + // changes are only written if the acknowledgement is successful or the acknowledgement + // returned is nil indicating that an asynchronous acknowledgement will occur. + ack := processPacket(ctx, packet, packetData) + + return ack +} +``` + +Reminder, the `Acknowledgement` interface: + +```go +// Acknowledgement defines the interface used to return +// acknowledgements in the OnRecvPacket callback. +type Acknowledgement interface { + Success() bool + Acknowledgement() []byte +} +``` + +### Acknowledging packets + +After a module writes an acknowledgement, a relayer can relay back the acknowledgement to the sender module. The sender module can +then process the acknowledgement using the `OnAcknowledgementPacket` callback. The contents of the +acknowledgement is entirely up to the modules on the channel (just like the packet data); however, it +may often contain information on whether the packet was successfully processed along +with some additional data that could be useful for remediation if the packet processing failed. + +Since the modules are responsible for agreeing on an encoding/decoding standard for packet data and +acknowledgements, IBC will pass in the acknowledgements as `[]byte` to this callback. The callback +is responsible for decoding the acknowledgement and processing it. + +> Note that some of the code below is *pseudo code*, indicating what actions need to happen but leaving it up to the developer to implement a custom implementation. E.g. the `DecodeAcknowledgement(acknowledgments)` and `processAck(ack)` functions. + +```go +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, +) (*sdk.Result, error) { + // Decode acknowledgement + ack := DecodeAcknowledgement(acknowledgement) + + // process ack + res, err := processAck(ack) + return res, err +} +``` + +### Timeout packets + +If the timeout for a packet is reached before the packet is successfully received or the +counterparty channel end is closed before the packet is successfully received, then the receiving +chain can no longer process it. Thus, the sending chain must process the timeout using +`OnTimeoutPacket` to handle this situation. Again the IBC module will verify that the timeout is +indeed valid, so our module only needs to implement the state machine logic for what to do once a +timeout is reached and the packet can no longer be received. + +```go +func (im IBCModule) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, +) (*sdk.Result, error) { + // do custom timeout logic +} +``` + +### Optional interfaces + +The following interface are optional and MAY be implemented by an IBCModule. + +#### PacketDataUnmarshaler + +The `PacketDataUnmarshaler` interface is defined as follows: + +```go +// PacketDataUnmarshaler defines an optional interface which allows a middleware to +// request the packet data to be unmarshaled by the base application. +type PacketDataUnmarshaler interface { + // UnmarshalPacketData unmarshals the packet data into a concrete type + UnmarshalPacketData([]byte) (interface{}, error) +} +``` + +The implementation of `UnmarshalPacketData` should unmarshal the bytes into the packet data type defined for an IBC stack. +The base application of an IBC stack should unmarshal the bytes into its packet data type, while a middleware may simply defer the call to the underlying application. + +This interface allows middlewares to unmarshal a packet data in order to make use of interfaces the packet data type implements. +For example, the callbacks middleware makes use of this function to access packet data types which implement the `PacketData` and `PacketDataProvider` interfaces. diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/03-bindports.md b/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/03-bindports.md new file mode 100644 index 0000000..f5c3c12 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/03-bindports.md @@ -0,0 +1,122 @@ +--- +title: Bind ports +sidebar_label: Bind ports +sidebar_position: 3 +slug: /ibc/apps/bindports +--- + +# Bind ports + +:::note Synopsis +Learn what changes to make to bind modules to their ports on initialization. +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: +Currently, ports must be bound on app initialization. In order to bind modules to their respective ports on initialization, the following needs to be implemented: + +> Note that `portID` does not refer to a certain numerical ID, like `localhost:8080` with a `portID` 8080. Rather it refers to the application module the port binds. For IBC Modules built with the Cosmos SDK, it defaults to the module's name and for Cosmwasm contracts it defaults to the contract address. + +1. Add port ID to the `GenesisState` proto definition: + +```protobuf +message GenesisState { + string port_id = 1; + // other fields +} +``` + +1. Add port ID as a key to the module store: + +```go +// x//types/keys.go +const ( + // ModuleName defines the IBC Module name + ModuleName = "moduleName" + + // Version defines the current version the IBC + // module supports + Version = "moduleVersion-1" + + // PortID is the default port id that module binds to + PortID = "portID" + + // ... +) +``` + +1. Add port ID to `x//types/genesis.go`: + +```go +// in x//types/genesis.go + +// DefaultGenesisState returns a GenesisState with "transfer" as the default PortID. +func DefaultGenesisState() *GenesisState { + return &GenesisState{ + PortId: PortID, + // additional k-v fields + } +} + +// Validate performs basic genesis state validation returning an error upon any +// failure. +func (gs GenesisState) Validate() error { + if err := host.PortIdentifierValidator(gs.PortId); err != nil { + return err + } + //additional validations + + return gs.Params.Validate() +} +``` + +1. Bind to port(s) in the module keeper's `InitGenesis`: + +```go +// InitGenesis initializes the ibc-module state and binds to PortID. +func (k Keeper) InitGenesis(ctx sdk.Context, state types.GenesisState) { + k.SetPort(ctx, state.PortId) + + // ... + + // Only try to bind to port if it is not already bound, since we may already own + // port capability from capability InitGenesis + if !k.hasCapability(ctx, state.PortId) { + // transfer module binds to the transfer port on InitChain + // and claims the returned capability + err := k.BindPort(ctx, state.PortId) + if err != nil { + panic(fmt.Sprintf("could not claim port capability: %v", err)) + } + } + + // ... +} +``` + +With: + +```go +// IsBound checks if the module is already bound to the desired port +func (k Keeper) IsBound(ctx sdk.Context, portID string) bool { + _, ok := k.scopedKeeper.GetCapability(ctx, host.PortPath(portID)) + return ok +} + +// BindPort defines a wrapper function for the port Keeper's function in +// order to expose it to module's InitGenesis function +func (k Keeper) BindPort(ctx sdk.Context, portID string) error { + cap := k.portKeeper.BindPort(ctx, portID) + return k.ClaimCapability(ctx, cap, host.PortPath(portID)) +} +``` + +The module binds to the desired port(s) and returns the capabilities. + +In the above we find reference to keeper methods that wrap other keeper functionality, in the next section the keeper methods that need to be implemented will be defined. diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/04-keeper.md b/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/04-keeper.md new file mode 100644 index 0000000..262b913 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/04-keeper.md @@ -0,0 +1,96 @@ +--- +title: Keeper +sidebar_label: Keeper +sidebar_position: 4 +slug: /ibc/apps/keeper +--- + +# Keeper + +:::note Synopsis +Learn how to implement the IBC Module keeper. +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: +In the previous sections, on channel handshake callbacks and port binding in `InitGenesis`, a reference was made to keeper methods that need to be implemented when creating a custom IBC module. Below is an overview of how to define an IBC module's keeper. + +> Note that some code has been left out for clarity, to get a full code overview, please refer to [the transfer module's keeper in the ibc-go repo](https://github.com/cosmos/ibc-go/blob/main/modules/apps/transfer/keeper/keeper.go). + +```go +// Keeper defines the IBC app module keeper +type Keeper struct { + storeKey sdk.StoreKey + cdc codec.BinaryCodec + paramSpace paramtypes.Subspace + + channelKeeper types.ChannelKeeper + portKeeper types.PortKeeper + scopedKeeper capabilitykeeper.ScopedKeeper + + // ... additional according to custom logic +} + +// NewKeeper creates a new IBC app module Keeper instance +func NewKeeper( + // args +) Keeper { + // ... + + return Keeper{ + cdc: cdc, + storeKey: key, + paramSpace: paramSpace, + + channelKeeper: channelKeeper, + portKeeper: portKeeper, + scopedKeeper: scopedKeeper, + + // ... additional according to custom logic + } +} + +// hasCapability checks if the IBC app module owns the port capability for the desired port +func (k Keeper) hasCapability(ctx sdk.Context, portID string) bool { + _, ok := k.scopedKeeper.GetCapability(ctx, host.PortPath(portID)) + return ok +} + +// BindPort defines a wrapper function for the port Keeper's function in +// order to expose it to module's InitGenesis function +func (k Keeper) BindPort(ctx sdk.Context, portID string) error { + cap := k.portKeeper.BindPort(ctx, portID) + return k.ClaimCapability(ctx, cap, host.PortPath(portID)) +} + +// GetPort returns the portID for the IBC app module. Used in ExportGenesis +func (k Keeper) GetPort(ctx sdk.Context) string { + store := ctx.KVStore(k.storeKey) + return string(store.Get(types.PortKey)) +} + +// SetPort sets the portID for the IBC app module. Used in InitGenesis +func (k Keeper) SetPort(ctx sdk.Context, portID string) { + store := ctx.KVStore(k.storeKey) + store.Set(types.PortKey, []byte(portID)) +} + +// AuthenticateCapability wraps the scopedKeeper's AuthenticateCapability function +func (k Keeper) AuthenticateCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) bool { + return k.scopedKeeper.AuthenticateCapability(ctx, cap, name) +} + +// ClaimCapability allows the IBC app module to claim a capability that core IBC +// passes to it +func (k Keeper) ClaimCapability(ctx sdk.Context, cap *capabilitytypes.Capability, name string) error { + return k.scopedKeeper.ClaimCapability(ctx, cap, name) +} + +// ... additional according to custom logic +``` diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/05-packets_acks.md b/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/05-packets_acks.md new file mode 100644 index 0000000..f7a9756 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/05-packets_acks.md @@ -0,0 +1,166 @@ +--- +title: Define packets and acks +sidebar_label: Define packets and acks +sidebar_position: 5 +slug: /ibc/apps/packets_acks +--- + +# Define packets and acks + +:::note Synopsis +Learn how to define custom packet and acknowledgement structs and how to encode and decode them. +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: + +## Custom packets + +Modules connected by a channel must agree on what application data they are sending over the +channel, as well as how they will encode/decode it. This process is not specified by IBC as it is up +to each application module to determine how to implement this agreement. However, for most +applications this will happen as a version negotiation during the channel handshake. While more +complex version negotiation is possible to implement inside the channel opening handshake, a very +simple version negotiation is implemented in the [ibc-transfer module](https://github.com/cosmos/ibc-go/tree/main/modules/apps/transfer/module.go). + +Thus, a module must define its custom packet data structure, along with a well-defined way to +encode and decode it to and from `[]byte`. + +```go +// Custom packet data defined in application module +type CustomPacketData struct { + // Custom fields ... +} + +EncodePacketData(packetData CustomPacketData) []byte { + // encode packetData to bytes +} + +DecodePacketData(encoded []byte) (CustomPacketData) { + // decode from bytes to packet data +} +``` + +> Note that the `CustomPacketData` struct is defined in the proto definition and then compiled by the protobuf compiler. + +Then a module must encode its packet data before sending it through IBC. + +```go +// retrieve the dynamic capability for this channel +channelCap := scopedKeeper.GetCapability(ctx, channelCapName) +// Sending custom application packet data +data := EncodePacketData(customPacketData) +// Send packet to IBC, authenticating with channelCap +sequence, err := IBCChannelKeeper.SendPacket( + ctx, + channelCap, + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + data, +) +``` + +A module receiving a packet must decode the `PacketData` into a structure it expects so that it can +act on it. + +```go +// Receiving custom application packet data (in OnRecvPacket) +packetData := DecodePacketData(packet.Data) +// handle received custom packet data +``` + +### Optional interfaces + +The following interfaces are optional and MAY be implemented by a custom packet type. +They allow middlewares such as callbacks to access information stored within the packet data. + +#### PacketData interface + +The `PacketData` interface is defined as follows: + +```go +// PacketData defines an optional interface which an application's packet data structure may implement. +type PacketData interface { + // GetPacketSender returns the sender address of the packet data. + // If the packet sender is unknown or undefined, an empty string should be returned. + GetPacketSender(sourcePortID string) string +} +``` + +The implementation of `GetPacketSender` should return the sender of the packet data. +If the packet sender is unknown or undefined, an empty string should be returned. + +This interface is intended to give IBC middlewares access to the packet sender of a packet data type. + +#### PacketDataProvider interface + +The `PacketDataProvider` interface is defined as follows: + +```go +// PacketDataProvider defines an optional interfaces for retrieving custom packet data stored on behalf of another application. +// An existing problem in the IBC middleware design is the inability for a middleware to define its own packet data type and insert packet sender provided information. +// A short term solution was introduced into several application's packet data to utilize a memo field to carry this information on behalf of another application. +// This interfaces standardizes that behaviour. Upon realization of the ability for middleware's to define their own packet data types, this interface will be deprecated and removed with time. +type PacketDataProvider interface { + // GetCustomPacketData returns the packet data held on behalf of another application. + // The name the information is stored under should be provided as the key. + // If no custom packet data exists for the key, nil should be returned. + GetCustomPacketData(key string) interface{} +} +``` + +The implementation of `GetCustomPacketData` should return packet data held on behalf of another application (if present and supported). +If this functionality is not supported, it should return nil. Otherwise it should return the packet data associated with the provided key. + +This interface gives IBC applications access to the packet data information embedded into the base packet data type. +Within transfer and interchain accounts, the embedded packet data is stored within the Memo field. + +Once all IBC applications within an IBC stack are capable of creating/maintaining their own packet data type's, this interface function will be deprecated and removed. + +## Acknowledgements + +Modules may commit an acknowledgement upon receiving and processing a packet in the case of synchronous packet processing. +In the case where a packet is processed at some later point after the packet has been received (asynchronous execution), the acknowledgement +will be written once the packet has been processed by the application which may be well after the packet receipt. + +NOTE: Most blockchain modules will want to use the synchronous execution model in which the module processes and writes the acknowledgement +for a packet as soon as it has been received from the IBC module. + +This acknowledgement can then be relayed back to the original sender chain, which can take action +depending on the contents of the acknowledgement. + +Just as packet data was opaque to IBC, acknowledgements are similarly opaque. Modules must pass and +receive acknowledegments with the IBC modules as byte strings. + +Thus, modules must agree on how to encode/decode acknowledgements. The process of creating an +acknowledgement struct along with encoding and decoding it, is very similar to the packet data +example above. [ICS 04](https://github.com/cosmos/ibc/blob/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope) +specifies a recommended format for acknowledgements. This acknowledgement type can be imported from +[channel types](https://github.com/cosmos/ibc-go/tree/main/modules/core/04-channel/types). + +While modules may choose arbitrary acknowledgement structs, a default acknowledgement types is provided by IBC [here](https://github.com/cosmos/ibc-go/blob/main/proto/ibc/core/channel/v1/channel.proto): + +```protobuf +// Acknowledgement is the recommended acknowledgement format to be used by +// app-specific protocols. +// NOTE: The field numbers 21 and 22 were explicitly chosen to avoid accidental +// conflicts with other protobuf message formats used for acknowledgements. +// The first byte of any message with this format will be the non-ASCII values +// `0xaa` (result) or `0xb2` (error). Implemented as defined by ICS: +// https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope +message Acknowledgement { + // response contains either a result or an error and must be non-empty + oneof response { + bytes result = 21; + string error = 22; + } +} +``` diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/06-routing.md b/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/06-routing.md new file mode 100644 index 0000000..6680444 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/06-routing.md @@ -0,0 +1,44 @@ +--- +title: Routing +sidebar_label: Routing +sidebar_position: 6 +slug: /ibc/apps/routing +--- + +# Routing + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC default integration](../02-integration.md) + +::: +:::note Synopsis +Learn how to hook a route to the IBC router for the custom IBC module. +::: + +As mentioned above, modules must implement the `IBCModule` interface (which contains both channel +handshake callbacks and packet handling callbacks). The concrete implementation of this interface +must be registered with the module name as a route on the IBC `Router`. + +```go +// app.go +func NewApp(...args) *App { + // ... + + // Create static IBC router, add module routes, then set and seal it + ibcRouter := port.NewRouter() + + ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferModule) + // Note: moduleCallbacks must implement IBCModule interface + ibcRouter.AddRoute(moduleName, moduleCallbacks) + + // Setting Router will finalize all routes by sealing router + // No more routes can be added + app.IBCKeeper.SetRouter(ibcRouter) + + // ... +} +``` diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/_category_.json b/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/_category_.json new file mode 100644 index 0000000..1c34da9 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Applications", + "position": 3, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/images/packet_flow.png b/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/images/packet_flow.png new file mode 100644 index 0000000..e5bae3f Binary files /dev/null and b/docs/versioned_docs/version-v8.5.x/01-ibc/03-apps/images/packet_flow.png differ diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/04-middleware/01-overview.md b/docs/versioned_docs/version-v8.5.x/01-ibc/04-middleware/01-overview.md new file mode 100644 index 0000000..d26c73b --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/04-middleware/01-overview.md @@ -0,0 +1,55 @@ +--- +title: IBC middleware +sidebar_label: IBC middleware +sidebar_position: 1 +slug: /ibc/middleware/overview +--- + +# IBC middleware + +:::note Synopsis +Learn how to write your own custom middleware to wrap an IBC application, and understand how to hook different middleware to IBC base applications to form different IBC application stacks +::: + +This documentation serves as a guide for middleware developers who want to write their own middleware and for chain developers who want to use IBC middleware on their chains. + +After going through the overview they can consult respectively: + +- [documentation on developing custom middleware](02-develop.md) +- [documentation on integrating middleware into a stack on a chain](03-integration.md) + +:::note + +## Pre-requisite readings + +- [IBC Overview](../01-overview.md) +- [IBC Integration](../02-integration.md) +- [IBC Application Developer Guide](../03-apps/01-apps.md) + +::: + +## Why middleware? + +IBC applications are designed to be self-contained modules that implement their own application-specific logic through a set of interfaces with the core IBC handlers. These core IBC handlers, in turn, are designed to enforce the correctness properties of IBC (transport, authentication, ordering) while delegating all application-specific handling to the IBC application modules. **However, there are cases where some functionality may be desired by many applications, yet not appropriate to place in core IBC.** + +Middleware allows developers to define the extensions as separate modules that can wrap over the base application. This middleware can thus perform its own custom logic, and pass data into the application so that it may run its logic without being aware of the middleware's existence. This allows both the application and the middleware to implement its own isolated logic while still being able to run as part of a single packet flow. + +## Definitions + +`Middleware`: A self-contained module that sits between core IBC and an underlying IBC application during packet execution. All messages between core IBC and underlying application must flow through middleware, which may perform its own custom logic. + +`Underlying Application`: An underlying application is the application that is directly connected to the middleware in question. This underlying application may itself be middleware that is chained to a base application. + +`Base Application`: A base application is an IBC application that does not contain any middleware. It may be nested by 0 or multiple middleware to form an application stack. + +`Application Stack (or stack)`: A stack is the complete set of application logic (middleware(s) + base application) that gets connected to core IBC. A stack may be just a base application, or it may be a series of middlewares that nest a base application. + +The diagram below gives an overview of a middleware stack consisting of two middleware (one stateless, the other stateful). + +![middleware-stack.png](./images/middleware-stack.png) + +Keep in mind that: + +- **The order of the middleware matters** (more on how to correctly define your stack in the code will follow in the [integration section](03-integration.md)). +- Depending on the type of message, it will either be passed on from the base application up the middleware stack to core IBC or down the stack in the reverse situation (handshake and packet callbacks). +- IBC middleware will wrap over an underlying IBC application and sits between core IBC and the application. It has complete control in modifying any message coming from IBC to the application, and any message coming from the application to core IBC. **Middleware must be completely trusted by chain developers who wish to integrate them**, as this gives them complete flexibility in modifying the application(s) they wrap. diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/04-middleware/02-develop.md b/docs/versioned_docs/version-v8.5.x/01-ibc/04-middleware/02-develop.md new file mode 100644 index 0000000..d8058dd --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/04-middleware/02-develop.md @@ -0,0 +1,495 @@ +--- +title: Create a custom IBC middleware +sidebar_label: Create a custom IBC middleware +sidebar_position: 2 +slug: /ibc/middleware/develop +--- + + +# Create a custom IBC middleware + +IBC middleware will wrap over an underlying IBC application (a base application or downstream middleware) and sits between core IBC and the base application. + +:::warning +middleware developers must use the same serialization and deserialization method as in ibc-go's codec: transfertypes.ModuleCdc.[Must]MarshalJSON +::: + +For middleware builders this means: + +```go +import transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +transfertypes.ModuleCdc.[Must]MarshalJSON +func MarshalAsIBCDoes(ack channeltypes.Acknowledgement) ([]byte, error) { + return transfertypes.ModuleCdc.MarshalJSON(&ack) +} +``` + +The interfaces a middleware must implement are found [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/05-port/types/module.go). + +```go +// Middleware implements the ICS26 Module interface +type Middleware interface { + IBCModule // middleware has access to an underlying application which may be wrapped by more middleware + ICS4Wrapper // middleware has access to ICS4Wrapper which may be core IBC Channel Handler or a higher-level middleware that wraps this middleware. +} +``` + +An `IBCMiddleware` struct implementing the `Middleware` interface, can be defined with its constructor as follows: + +```go +// @ x/module_name/ibc_middleware.go + +// IBCMiddleware implements the ICS26 callbacks and ICS4Wrapper for the fee middleware given the +// fee keeper and the underlying application. +type IBCMiddleware struct { + app porttypes.IBCModule + keeper keeper.Keeper +} + +// NewIBCMiddleware creates a new IBCMiddleware given the keeper and underlying application +func NewIBCMiddleware(app porttypes.IBCModule, k keeper.Keeper) IBCMiddleware { + return IBCMiddleware{ + app: app, + keeper: k, + } +} +``` + +## Implement `IBCModule` interface + +`IBCMiddleware` is a struct that implements the [ICS-26 `IBCModule` interface (`porttypes.IBCModule`)](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/05-port/types/module.go#L14-L107). It is recommended to separate these callbacks into a separate file `ibc_middleware.go`. + +> Note how this is analogous to implementing the same interfaces for IBC applications that act as base applications. + +As will be mentioned in the [integration section](03-integration.md), this struct should be different than the struct that implements `AppModule` in case the middleware maintains its own internal state and processes separate SDK messages. + +The middleware must have access to the underlying application, and be called before it during all ICS-26 callbacks. It may execute custom logic during these callbacks, and then call the underlying application's callback. + +> Middleware **may** choose not to call the underlying application's callback at all. Though these should generally be limited to error cases. + +The `IBCModule` interface consists of the channel handshake callbacks and packet callbacks. Most of the custom logic will be performed in the packet callbacks, in the case of the channel handshake callbacks, introducing the middleware requires consideration to the version negotiation and passing of capabilities. + +### Channel handshake callbacks + +#### Version negotiation + +In the case where the IBC middleware expects to speak to a compatible IBC middleware on the counterparty chain, they must use the channel handshake to negotiate the middleware version without interfering in the version negotiation of the underlying application. + +Middleware accomplishes this by formatting the version in a JSON-encoded string containing the middleware version and the application version. The application version may as well be a JSON-encoded string, possibly including further middleware and app versions, if the application stack consists of multiple milddlewares wrapping a base application. The format of the version is specified in ICS-30 as the following: + +```json +{ + "": "", + "app_version": "" +} +``` + +The `` key in the JSON struct should be replaced by the actual name of the key for the corresponding middleware (e.g. `fee_version`). + +During the handshake callbacks, the middleware can unmarshal the version string and retrieve the middleware and application versions. It can do its negotiation logic on ``, and pass the `` to the underlying application. + +> **NOTE**: Middleware that does not need to negotiate with a counterparty middleware on the remote stack will not implement the version unmarshalling and negotiation, and will simply perform its own custom logic on the callbacks without relying on the counterparty behaving similarly. + +#### `OnChanOpenInit` + +```go +func (im IBCMiddleware) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + if version != "" { + // try to unmarshal JSON-encoded version string and pass + // the app-specific version to app callback. + // otherwise, pass version directly to app callback. + metadata, err := Unmarshal(version) + if err != nil { + // Since it is valid for fee version to not be specified, + // the above middleware version may be for another middleware. + // Pass the entire version string onto the underlying application. + return im.app.OnChanOpenInit( + ctx, + order, + connectionHops, + portID, + channelID, + channelCap, + counterparty, + version, + ) + } + else { + metadata = { + // set middleware version to default value + MiddlewareVersion: defaultMiddlewareVersion, + // allow application to return its default version + AppVersion: "", + } + } + } + + doCustomLogic() + + // if the version string is empty, OnChanOpenInit is expected to return + // a default version string representing the version(s) it supports + appVersion, err := im.app.OnChanOpenInit( + ctx, + order, + connectionHops, + portID, + channelID, + channelCap, + counterparty, + metadata.AppVersion, // note we only pass app version here + ) + if err != nil { + return "", err + } + + version := constructVersion(metadata.MiddlewareVersion, appVersion) + + return version, nil +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L36-L83) an example implementation of this callback for the ICS-29 Fee Middleware module. + +#### `OnChanOpenTry` + +```go +func (im IBCMiddleware) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + channelCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + // try to unmarshal JSON-encoded version string and pass + // the app-specific version to app callback. + // otherwise, pass version directly to app callback. + cpMetadata, err := Unmarshal(counterpartyVersion) + if err != nil { + return app.OnChanOpenTry( + ctx, + order, + connectionHops, + portID, + channelID, + channelCap, + counterparty, + counterpartyVersion, + ) + } + + doCustomLogic() + + // Call the underlying application's OnChanOpenTry callback. + // The try callback must select the final app-specific version string and return it. + appVersion, err := app.OnChanOpenTry( + ctx, + order, + connectionHops, + portID, + channelID, + channelCap, + counterparty, + cpMetadata.AppVersion, // note we only pass counterparty app version here + ) + if err != nil { + return "", err + } + + // negotiate final middleware version + middlewareVersion := negotiateMiddlewareVersion(cpMetadata.MiddlewareVersion) + version := constructVersion(middlewareVersion, appVersion) + + return version, nil +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L88-L125) an example implementation of this callback for the ICS-29 Fee Middleware module. + +#### `OnChanOpenAck` + +```go +func (im IBCMiddleware) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyChannelID string, + counterpartyVersion string, +) error { + // try to unmarshal JSON-encoded version string and pass + // the app-specific version to app callback. + // otherwise, pass version directly to app callback. + cpMetadata, err = UnmarshalJSON(counterpartyVersion) + if err != nil { + return app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) + } + + if !isCompatible(cpMetadata.MiddlewareVersion) { + return error + } + doCustomLogic() + + // call the underlying application's OnChanOpenTry callback + return app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, cpMetadata.AppVersion) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L128-L153)) an example implementation of this callback for the ICS-29 Fee Middleware module. + +#### `OnChanOpenConfirm` + +```go +func OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + doCustomLogic() + + return app.OnChanOpenConfirm(ctx, portID, channelID) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L156-L163) an example implementation of this callback for the ICS-29 Fee Middleware module. + +#### `OnChanCloseInit` + +```go +func OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + doCustomLogic() + + return app.OnChanCloseInit(ctx, portID, channelID) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L166-L188) an example implementation of this callback for the ICS-29 Fee Middleware module. + +#### `OnChanCloseConfirm` + +```go +func OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + doCustomLogic() + + return app.OnChanCloseConfirm(ctx, portID, channelID) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L191-L213) an example implementation of this callback for the ICS-29 Fee Middleware module. + +#### Capabilities + +The middleware should simply pass the capability in the callback arguments along to the underlying application so that it may be claimed by the base application. The base application will then pass the capability up the stack in order to authenticate an outgoing packet/acknowledgement, which you can check in the [`ICS4Wrapper` section](#ics-04-wrappers). + +In the case where the middleware wishes to send a packet or acknowledgment without the involvement of the underlying application, it should be given access to the same `scopedKeeper` as the base application so that it can retrieve the capabilities by itself. + +### Packet callbacks + +The packet callbacks just like the handshake callbacks wrap the application's packet callbacks. The packet callbacks are where the middleware performs most of its custom logic. The middleware may read the packet flow data and perform some additional packet handling, or it may modify the incoming data before it reaches the underlying application. This enables a wide degree of usecases, as a simple base application like token-transfer can be transformed for a variety of usecases by combining it with custom middleware. + +#### `OnRecvPacket` + +```go +func (im IBCMiddleware) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) ibcexported.Acknowledgement { + doCustomLogic(packet) + + ack := app.OnRecvPacket(ctx, packet, relayer) + + doCustomLogic(ack) // middleware may modify outgoing ack + + return ack +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L217-L238) an example implementation of this callback for the ICS-29 Fee Middleware module. + +#### `OnAcknowledgementPacket` + +```go +func (im IBCMiddleware) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + doCustomLogic(packet, ack) + + return app.OnAcknowledgementPacket(ctx, packet, ack, relayer) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L242-L293) an example implementation of this callback for the ICS-29 Fee Middleware module. + +#### `OnTimeoutPacket` + +```go +func (im IBCMiddleware) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + doCustomLogic(packet) + + return app.OnTimeoutPacket(ctx, packet, relayer) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/ibc_middleware.go#L297-L335) an example implementation of this callback for the ICS-29 Fee Middleware module. + +## ICS-04 wrappers + +Middleware must also wrap ICS-04 so that any communication from the application to the `channelKeeper` goes through the middleware first. Similar to the packet callbacks, the middleware may modify outgoing acknowledgements and packets in any way it wishes. + +To ensure optimal generalisability, the `ICS4Wrapper` abstraction serves to abstract away whether a middleware is the topmost middleware (and thus directly calling into the ICS-04 `channelKeeper`) or itself being wrapped by another middleware. + +Remember that middleware can be stateful or stateless. When defining the stateful middleware's keeper, the `ics4Wrapper` field is included. Then the appropriate keeper can be passed when instantiating the middleware's keeper in `app.go` + +```go +type Keeper struct { + storeKey storetypes.StoreKey + cdc codec.BinaryCodec + + ics4Wrapper porttypes.ICS4Wrapper + channelKeeper types.ChannelKeeper + portKeeper types.PortKeeper + ... +} +``` + +For stateless middleware, the `ics4Wrapper` can be passed on directly without having to instantiate a keeper struct for the middleware. + +[The interface](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/05-port/types/module.go#L110-L133) looks as follows: + +```go +// This is implemented by ICS4 and all middleware that are wrapping base application. +// The base application will call `sendPacket` or `writeAcknowledgement` of the middleware directly above them +// which will call the next middleware until it reaches the core IBC handler. +type ICS4Wrapper interface { + SendPacket( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + data []byte, + ) (sequence uint64, err error) + + WriteAcknowledgement( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + packet exported.PacketI, + ack exported.Acknowledgement, + ) error + + GetAppVersion( + ctx sdk.Context, + portID, + channelID string, + ) (string, bool) +} +``` + +:warning: In the following paragraphs, the methods are presented in pseudo code which has been kept general, not stating whether the middleware is stateful or stateless. Remember that when the middleware is stateful, `ics4Wrapper` can be accessed through the keeper. + +Check out the references provided for an actual implementation to clarify, where the `ics4Wrapper` methods in `ibc_middleware.go` simply call the equivalent keeper methods where the actual logic resides. + +### `SendPacket` + +```go +func SendPacket( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + appData []byte, +) (uint64, error) { + // middleware may modify data + data = doCustomLogic(appData) + + return ics4Wrapper.SendPacket( + ctx, + chanCap, + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + data, + ) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/keeper/relay.go#L17-L27) an example implementation of this function for the ICS-29 Fee Middleware module. + +### `WriteAcknowledgement` + +```go +// only called for async acks +func WriteAcknowledgement( + ctx sdk.Context, + chanCap *capabilitytypes.Capability, + packet exported.PacketI, + ack exported.Acknowledgement, +) error { + // middleware may modify acknowledgement + ack_bytes = doCustomLogic(ack) + + return ics4Wrapper.WriteAcknowledgement(packet, ack_bytes) +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/keeper/relay.go#L31-L55) an example implementation of this function for the ICS-29 Fee Middleware module. + +### `GetAppVersion` + +```go +// middleware must return the underlying application version +func GetAppVersion( + ctx sdk.Context, + portID, + channelID string, +) (string, bool) { + version, found := ics4Wrapper.GetAppVersion(ctx, portID, channelID) + if !found { + return "", false + } + + if !MiddlewareEnabled { + return version, true + } + + // unwrap channel version + metadata, err := Unmarshal(version) + if err != nil { + panic(fmt.Errof("unable to unmarshal version: %w", err)) + } + + return metadata.AppVersion, true +} +``` + +See [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/apps/29-fee/keeper/relay.go#L58-L74) an example implementation of this function for the ICS-29 Fee Middleware module. diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/04-middleware/03-integration.md b/docs/versioned_docs/version-v8.5.x/01-ibc/04-middleware/03-integration.md new file mode 100644 index 0000000..14cfed2 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/04-middleware/03-integration.md @@ -0,0 +1,74 @@ +--- +title: Integrating IBC middleware into a chain +sidebar_label: Integrating IBC middleware into a chain +sidebar_position: 3 +slug: /ibc/middleware/integration +--- + + +# Integrating IBC middleware into a chain + +Learn how to integrate IBC middleware(s) with a base application to your chain. The following document only applies for Cosmos SDK chains. + +If the middleware is maintaining its own state and/or processing SDK messages, then it should create and register its SDK module with the module manager in `app.go`. + +All middleware must be connected to the IBC router and wrap over an underlying base IBC application. An IBC application may be wrapped by many layers of middleware, only the top layer middleware should be hooked to the IBC router, with all underlying middlewares and application getting wrapped by it. + +The order of middleware **matters**, function calls from IBC to the application travel from top-level middleware to the bottom middleware and then to the application. Function calls from the application to IBC goes through the bottom middleware in order to the top middleware and then to core IBC handlers. Thus the same set of middleware put in different orders may produce different effects. + +## Example integration + +```go +// app.go pseudocode + +// middleware 1 and middleware 3 are stateful middleware, +// perhaps implementing separate sdk.Msg and Handlers +mw1Keeper := mw1.NewKeeper(storeKey1, ..., ics4Wrapper: channelKeeper, ...) // in stack 1 & 3 +// middleware 2 is stateless +mw3Keeper1 := mw3.NewKeeper(storeKey3,..., ics4Wrapper: mw1Keeper, ...) // in stack 1 +mw3Keeper2 := mw3.NewKeeper(storeKey3,..., ics4Wrapper: channelKeeper, ...) // in stack 2 + +// Only create App Module **once** and register in app module +// if the module maintains independent state and/or processes sdk.Msgs +app.moduleManager = module.NewManager( + ... + mw1.NewAppModule(mw1Keeper), + mw3.NewAppModule(mw3Keeper1), + mw3.NewAppModule(mw3Keeper2), + transfer.NewAppModule(transferKeeper), + custom.NewAppModule(customKeeper) +) + +scopedKeeperTransfer := capabilityKeeper.NewScopedKeeper("transfer") +scopedKeeperCustom1 := capabilityKeeper.NewScopedKeeper("custom1") +scopedKeeperCustom2 := capabilityKeeper.NewScopedKeeper("custom2") + +// NOTE: IBC Modules may be initialized any number of times provided they use a separate +// scopedKeeper and underlying port. + +customKeeper1 := custom.NewKeeper(..., scopedKeeperCustom1, ...) +customKeeper2 := custom.NewKeeper(..., scopedKeeperCustom2, ...) + +// initialize base IBC applications +// if you want to create two different stacks with the same base application, +// they must be given different scopedKeepers and assigned different ports. +transferIBCModule := transfer.NewIBCModule(transferKeeper) +customIBCModule1 := custom.NewIBCModule(customKeeper1, "portCustom1") +customIBCModule2 := custom.NewIBCModule(customKeeper2, "portCustom2") + +// create IBC stacks by combining middleware with base application +// NOTE: since middleware2 is stateless it does not require a Keeper +// stack 1 contains mw1 -> mw3 -> transfer +stack1 := mw1.NewIBCMiddleware(mw3.NewIBCMiddleware(transferIBCModule, mw3Keeper1), mw1Keeper) +// stack 2 contains mw3 -> mw2 -> custom1 +stack2 := mw3.NewIBCMiddleware(mw2.NewIBCMiddleware(customIBCModule1), mw3Keeper2) +// stack 3 contains mw2 -> mw1 -> custom2 +stack3 := mw2.NewIBCMiddleware(mw1.NewIBCMiddleware(customIBCModule2, mw1Keeper)) + +// associate each stack with the moduleName provided by the underlying scopedKeeper +ibcRouter := porttypes.NewRouter() +ibcRouter.AddRoute("transfer", stack1) +ibcRouter.AddRoute("custom1", stack2) +ibcRouter.AddRoute("custom2", stack3) +app.IBCKeeper.SetRouter(ibcRouter) +``` diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/04-middleware/_category_.json b/docs/versioned_docs/version-v8.5.x/01-ibc/04-middleware/_category_.json new file mode 100644 index 0000000..ec27d4e --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/04-middleware/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Middleware", + "position": 4, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/04-middleware/images/middleware-stack.png b/docs/versioned_docs/version-v8.5.x/01-ibc/04-middleware/images/middleware-stack.png new file mode 100644 index 0000000..ed5852f Binary files /dev/null and b/docs/versioned_docs/version-v8.5.x/01-ibc/04-middleware/images/middleware-stack.png differ diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/05-upgrades/00-intro.md b/docs/versioned_docs/version-v8.5.x/01-ibc/05-upgrades/00-intro.md new file mode 100644 index 0000000..d7bb473 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/05-upgrades/00-intro.md @@ -0,0 +1,15 @@ +--- +title: Upgrading IBC Chains Overview +sidebar_label: Overview +sidebar_position: 0 +slug: /ibc/upgrades/intro +--- + +# Upgrading IBC Chains Overview + +This directory contains information on how to upgrade an IBC chain without breaking counterparty clients and connections. + +IBC-connected chains must be able to upgrade without breaking connections to other chains. Otherwise there would be a massive disincentive towards upgrading and disrupting high-value IBC connections, thus preventing chains in the IBC ecosystem from evolving and improving. Many chain upgrades may be irrelevant to IBC, however some upgrades could potentially break counterparty clients if not handled correctly. Thus, any IBC chain that wishes to perform an IBC-client-breaking upgrade must perform an IBC upgrade in order to allow counterparty clients to securely upgrade to the new light client. + +1. The [quick-guide](./01-quick-guide.md) describes how IBC-connected chains can perform client-breaking upgrades and how relayers can securely upgrade counterparty clients using the SDK. +2. The [developer-guide](./02-developer-guide.md) is a guide for developers intending to develop IBC client implementations with upgrade functionality. diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/05-upgrades/01-quick-guide.md b/docs/versioned_docs/version-v8.5.x/01-ibc/05-upgrades/01-quick-guide.md new file mode 100644 index 0000000..23448f7 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/05-upgrades/01-quick-guide.md @@ -0,0 +1,60 @@ +--- +title: How to Upgrade IBC Chains and their Clients +sidebar_label: How to Upgrade IBC Chains and their Clients +sidebar_position: 1 +slug: /ibc/upgrades/quick-guide +--- + + +# How to Upgrade IBC Chains and their Clients + +:::note Synopsis +Learn how to upgrade your chain and counterparty clients. +::: + +The information in this doc for upgrading chains is relevant to SDK chains. However, the guide for counterparty clients is relevant to any Tendermint client that enables upgrades. + +## IBC Client Breaking Upgrades + +IBC-connected chains must perform an IBC upgrade if their upgrade will break counterparty IBC clients. The current IBC protocol supports upgrading tendermint chains for a specific subset of IBC-client-breaking upgrades. Here is the exhaustive list of IBC client-breaking upgrades and whether the IBC protocol currently supports such upgrades. + +IBC currently does **NOT** support unplanned upgrades. All of the following upgrades must be planned and committed to in advance by the upgrading chain, in order for counterparty clients to maintain their connections securely. + +Note: Since upgrades are only implemented for Tendermint clients, this doc only discusses upgrades on Tendermint chains that would break counterparty IBC Tendermint Clients. + +1. Changing the Chain-ID: **Supported** +2. Changing the UnbondingPeriod: **Partially Supported**, chains may increase the unbonding period with no issues. However, decreasing the unbonding period may irreversibly break some counterparty clients. Thus, it is **not recommended** that chains reduce the unbonding period. +3. Changing the height (resetting to 0): **Supported**, so long as chains remember to increment the revision number in their chain-id. +4. Changing the ProofSpecs: **Supported**, this should be changed if the proof structure needed to verify IBC proofs is changed across the upgrade. Ex: Switching from an IAVL store, to a SimpleTree Store +5. Changing the UpgradePath: **Supported**, this might involve changing the key under which upgraded clients and consensus states are stored in the upgrade store, or even migrating the upgrade store itself. +6. Migrating the IBC store: **Unsupported**, as the IBC store location is negotiated by the connection. +7. Upgrading to a backwards compatible version of IBC: Supported +8. Upgrading to a non-backwards compatible version of IBC: **Unsupported**, as IBC version is negotiated on connection handshake. +9. Changing the Tendermint LightClient algorithm: **Partially Supported**. Changes to the light client algorithm that do not change the ClientState or ConsensusState struct may be supported, provided that the counterparty is also upgraded to support the new light client algorithm. Changes that require updating the ClientState and ConsensusState structs themselves are theoretically possible by providing a path to translate an older ClientState struct into the new ClientState struct; however this is not currently implemented. + +## Step-by-Step Upgrade Process for SDK chains + +If the IBC-connected chain is conducting an upgrade that will break counterparty clients, it must ensure that the upgrade is first supported by IBC using the list above and then execute the upgrade process described below in order to prevent counterparty clients from breaking. + +1. Create a governance proposal with the [`MsgIBCSoftwareUpgrade`](https://buf.build/cosmos/ibc/docs/main:ibc.core.client.v1#ibc.core.client.v1.MsgIBCSoftwareUpgrade) message which contains an `UpgradePlan` and a new IBC `ClientState` in the `UpgradedClientState` field. Note that the `UpgradePlan` must specify an upgrade height **only** (no upgrade time), and the `ClientState` should only include the fields common to all valid clients (chain-specified parameters) and zero out any client-customizable fields (such as `TrustingPeriod`). +2. Vote on and pass the governance proposal. + +Upon passing the governance proposal, the upgrade module will commit the `UpgradedClient` under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedClient`. On the block right before the upgrade height, the upgrade module will also commit an initial consensus state for the next chain under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedConsState`. + +Once the chain reaches the upgrade height and halts, a relayer can upgrade the counterparty clients to the last block of the old chain. They can then submit the proofs of the `UpgradedClient` and `UpgradedConsensusState` against this last block and upgrade the counterparty client. + +## Step-by-Step Upgrade Process for Relayers Upgrading Counterparty Clients + +Once the upgrading chain has committed to upgrading, relayers must wait till the chain halts at the upgrade height before upgrading counterparty clients. This is because chains may reschedule or cancel upgrade plans before they occur. Thus, relayers must wait till the chain reaches the upgrade height and halts before they can be sure the upgrade will take place. + +Thus, the upgrade process for relayers trying to upgrade the counterparty clients is as follows: + +1. Wait for the upgrading chain to reach the upgrade height and halt +2. Query a full node for the proofs of `UpgradedClient` and `UpgradedConsensusState` at the last height of the old chain. +3. Update the counterparty client to the last height of the old chain using the `UpdateClient` msg. +4. Submit an `UpgradeClient` msg to the counterparty chain with the `UpgradedClient`, `UpgradedConsensusState` and their respective proofs. +5. Submit an `UpdateClient` msg to the counterparty chain with a header from the new upgraded chain. + +The Tendermint client on the counterparty chain will verify that the upgrading chain did indeed commit to the upgraded client and upgraded consensus state at the upgrade height (since the upgrade height is included in the key). If the proofs are verified against the upgrade height, then the client will upgrade to the new client while retaining all of its client-customized fields. Thus, it will retain its old TrustingPeriod, TrustLevel, MaxClockDrift, etc; while adopting the new chain-specified fields such as UnbondingPeriod, ChainId, UpgradePath, etc. Note, this can lead to an invalid client since the old client-chosen fields may no longer be valid given the new chain-chosen fields. Upgrading chains should try to avoid these situations by not altering parameters that can break old clients. For an example, see the UnbondingPeriod example in the supported upgrades section. + +The upgraded consensus state will serve purely as a basis of trust for future `UpdateClientMsgs` and will not contain a consensus root to perform proof verification against. Thus, relayers must submit an `UpdateClientMsg` with a header from the new chain so that the connection can be used for proof verification again. diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/05-upgrades/02-developer-guide.md b/docs/versioned_docs/version-v8.5.x/01-ibc/05-upgrades/02-developer-guide.md new file mode 100644 index 0000000..0b6f696 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/05-upgrades/02-developer-guide.md @@ -0,0 +1,14 @@ +--- +title: IBC Client Developer Guide to Upgrades +sidebar_label: IBC Client Developer Guide to Upgrades +sidebar_position: 2 +slug: /ibc/upgrades/developer-guide +--- + +# IBC Client Developer Guide to Upgrades + +:::note Synopsis +Learn how to implement upgrade functionality for your custom IBC client. +::: + +Please see the section [Handling upgrades](../../03-light-clients/01-developer-guide/05-upgrades.md) from the light client developer guide for more information. diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/05-upgrades/03-genesis-restart.md b/docs/versioned_docs/version-v8.5.x/01-ibc/05-upgrades/03-genesis-restart.md new file mode 100644 index 0000000..8f1d612 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/05-upgrades/03-genesis-restart.md @@ -0,0 +1,52 @@ +--- +title: Genesis Restart Upgrades +sidebar_label: Genesis Restart Upgrades +sidebar_position: 3 +slug: /ibc/upgrades/genesis-restart +--- + + +# Genesis Restart Upgrades + +:::note Synopsis +Learn how to upgrade your chain and counterparty clients using genesis restarts. +::: + +**NOTE**: Regular genesis restarts are currently unsupported by relayers! + +## IBC Client Breaking Upgrades + +IBC client breaking upgrades are possible using genesis restarts. +It is highly recommended to use the in-place migrations instead of a genesis restart. +Genesis restarts should be used sparingly and as backup plans. + +Genesis restarts still require the usage of an IBC upgrade proposal in order to correctly upgrade counterparty clients. + +### Step-by-Step Upgrade Process for SDK Chains + +If the IBC-connected chain is conducting an upgrade that will break counterparty clients, it must ensure that the upgrade is first supported by IBC using the [IBC Client Breaking Upgrade List](./01-quick-guide.md#ibc-client-breaking-upgrades) and then execute the upgrade process described below in order to prevent counterparty clients from breaking. + +1. Create a governance proposal with the [`MsgIBCSoftwareUpgrade`](https://buf.build/cosmos/ibc/docs/main:ibc.core.client.v1#ibc.core.client.v1.MsgIBCSoftwareUpgrade) which contains an `UpgradePlan` and a new IBC `ClientState` in the `UpgradedClientState` field. Note that the `UpgradePlan` must specify an upgrade height **only** (no upgrade time), and the `ClientState` should only include the fields common to all valid clients and zero out any client-customizable fields (such as `TrustingPeriod`). +2. Vote on and pass the governance proposal. +3. Halt the node after successful upgrade. +4. Export the genesis file. +5. Swap to the new binary. +6. Run migrations on the genesis file. +7. Remove the upgrade plan set by the governance proposal from the genesis file. This may be done by migrations. +8. Change desired chain-specific fields (chain id, unbonding period, etc). This may be done by migrations. +8. Reset the node's data. +9. Start the chain. + +Upon passing the governance proposal, the upgrade module will commit the `UpgradedClient` under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedClient`. On the block right before the upgrade height, the upgrade module will also commit an initial consensus state for the next chain under the key: `upgrade/UpgradedIBCState/{upgradeHeight}/upgradedConsState`. + +Once the chain reaches the upgrade height and halts, a relayer can upgrade the counterparty clients to the last block of the old chain. They can then submit the proofs of the `UpgradedClient` and `UpgradedConsensusState` against this last block and upgrade the counterparty client. + +### Step-by-Step Upgrade Process for Relayers Upgrading Counterparty Clients + +These steps are identical to the regular [IBC client breaking upgrade process](./01-quick-guide.md#step-by-step-upgrade-process-for-relayers-upgrading-counterparty-clients). + +## Non-IBC Client Breaking Upgrades + +While ibc-go supports genesis restarts which do not break IBC clients, relayers do not support this upgrade path. +Here is a tracking issue on [Hermes](https://github.com/informalsystems/ibc-rs/issues/1152). +Please do not attempt a regular genesis restarts unless you have a tool to update counterparty clients correctly. diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/05-upgrades/_category_.json b/docs/versioned_docs/version-v8.5.x/01-ibc/05-upgrades/_category_.json new file mode 100644 index 0000000..c2db1ee --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/05-upgrades/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Upgrades", + "position": 5, + "link": { "type": "doc", "id": "intro" } +} diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/06-channel-upgrades.md b/docs/versioned_docs/version-v8.5.x/01-ibc/06-channel-upgrades.md new file mode 100644 index 0000000..359eda1 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/06-channel-upgrades.md @@ -0,0 +1,413 @@ +--- +title: Channel Upgrades +sidebar_label: Channel Upgrades +sidebar_position: 6 +slug: /ibc/channel-upgrades +--- + +# Channel Upgrades + +:::note Synopsis +Learn how to upgrade existing IBC channels. +::: + +Channel upgradability is an IBC-level protocol that allows chains to leverage new application and channel features without having to create new channels or perform a network-wide upgrade. + +Prior to this feature, developers who wanted to update an application module or add a middleware to their application flow would need to create a new channel in order to use the updated application feature/middleware, resulting in a loss of the accumulated state/liquidity, token fungibility (as the channel ID is encoded in the IBC denom), and any other larger network effects of losing usage of the existing channel from relayers monitoring, etc. + +With channel upgradability, applications will be able to implement features such as but not limited to: potentially adding [denom metadata to tokens](https://github.com/cosmos/ibc/discussions/719), or utilizing the [fee middleware](https://github.com/cosmos/ibc/tree/main/spec/app/ics-029-fee-payment), all while maintaining the channels on which they currently operate. + +This document outlines the channel upgrade feature, and the multiple steps used in the upgrade process. + +## Channel Upgrade Handshake + +Channel upgrades will be achieved using a handshake process that is designed to be similar to the standard connection/channel opening handshake. + +```go +type Channel struct { + // current state of the channel end + State State `protobuf:"varint,1,opt,name=state,proto3,enum=ibc.core.channel.v1.State" json:"state,omitempty"` + // whether the channel is ordered or unordered + Ordering Order `protobuf:"varint,2,opt,name=ordering,proto3,enum=ibc.core.channel.v1.Order" json:"ordering,omitempty"` + // counterparty channel end + Counterparty Counterparty `protobuf:"bytes,3,opt,name=counterparty,proto3" json:"counterparty"` + // list of connection identifiers, in order, along which packets sent on + // this channel will travel + ConnectionHops []string `protobuf:"bytes,4,rep,name=connection_hops,json=connectionHops,proto3" json:"connection_hops,omitempty"` + // opaque channel version, which is agreed upon during the handshake + Version string `protobuf:"bytes,5,opt,name=version,proto3" json:"version,omitempty"` + // upgrade sequence indicates the latest upgrade attempt performed by this channel + // the value of 0 indicates the channel has never been upgraded + UpgradeSequence uint64 `protobuf:"varint,6,opt,name=upgrade_sequence,json=upgradeSequence,proto3" json:"upgrade_sequence,omitempty"` +} +``` + +The version, connection hops, and channel ordering are fields in this channel struct which can be changed. For example, the fee middleware can be added to an application module by updating the version string [shown here](https://github.com/cosmos/ibc-go/blob/v8.1.0/modules/apps/29-fee/types/metadata.pb.go#L29). However, although connection hops can change in a channel upgrade, both sides must still be each other's counterparty. This is enforced by the upgrade protocol and upgrade attempts which try to alter an expected counterparty will fail. + +On a high level, successful handshake process for channel upgrades works as follows: + +1. The chain initiating the upgrade process will propose an upgrade. +2. If the counterparty agrees with the proposal, it will block sends and begin flushing any in-flight packets on its channel end. This flushing process will be covered in more detail below. +3. Upon successful completion of the previous step, the initiating chain will also block packet sends and begin flushing any in-flight packets on its channel end. +4. Once both channel ends have completed flushing packets within the upgrade timeout window, both channel ends can be opened and upgraded to the new channel fields. + +Each handshake step will be documented below in greater detail. + +## Initializing a Channel Upgrade + +A channel upgrade is initialised by submitting the `MsgChannelUpgradeInit` message, which can be submitted only by the chain itself upon governance authorization. It is possible to upgrade the channel ordering, the channel connection hops, and the channel version, which can be found in the `UpgradeFields`. + +```go +type MsgChannelUpgradeInit struct { + PortId string + ChannelId string + Fields UpgradeFields + Signer string +} +``` + +As part of the handling of the `MsgChannelUpgradeInit` message, the application's `OnChanUpgradeInit` callback will be triggered as well. + +After this message is handled successfully, the channel's upgrade sequence will be incremented. This upgrade sequence will serve as a nonce for the upgrade process to provide replay protection. + +:::warning +Initiating an upgrade in the same block as opening a channel may potentially prevent the counterparty channel from also opening. +::: + +### Governance gating on `ChanUpgradeInit` + +The message signer for `MsgChannelUpgradeInit` must be the address which has been designated as the `authority` of the `IBCKeeper`. If this proposal passes, the counterparty's channel will upgrade by default. + +If chains want to initiate the upgrade of many channels, they will need to submit a governance proposal with multiple `MsgChannelUpgradeInit` messages, one for each channel they would like to upgrade, again with message signer as the designated `authority` of the `IBCKeeper`. The `upgrade-channels` CLI command can be used to submit a proposal that initiates the upgrade of multiple channels; see section [Upgrading channels with the CLI](#upgrading-channels-with-the-cli) below for more information. + +## Channel State and Packet Flushing + +`FLUSHING` and `FLUSHCOMPLETE` are additional channel states which have been added to enable the upgrade feature. + +These states may consist of: + +```go +const ( + // Default State + UNINITIALIZED State = 0 + // A channel has just started the opening handshake. + INIT State = 1 + // A channel has acknowledged the handshake step on the counterparty chain. + TRYOPEN State = 2 + // A channel has completed the handshake. Open channels are + // ready to send and receive packets. + OPEN State = 3 + // A channel has been closed and can no longer be used to send or receive + // packets. + CLOSED State = 4 + // A channel has just accepted the upgrade handshake attempt and is flushing in-flight packets. + FLUSHING State = 5 + // A channel has just completed flushing any in-flight packets. + FLUSHCOMPLETE State = 6 +) +``` + +These are found in `State` on the `Channel` struct: + +```go +type Channel struct { + // current state of the channel end + State State `protobuf:"varint,1,opt,name=state,proto3,enum=ibc.core.channel.v1.State" json:"state,omitempty"` + // whether the channel is ordered or unordered + Ordering Order `protobuf:"varint,2,opt,name=ordering,proto3,enum=ibc.core.channel.v1.Order" json:"ordering,omitempty"` + // counterparty channel end + Counterparty Counterparty `protobuf:"bytes,3,opt,name=counterparty,proto3" json:"counterparty"` + // list of connection identifiers, in order, along which packets sent on + // this channel will travel + ConnectionHops []string `protobuf:"bytes,4,rep,name=connection_hops,json=connectionHops,proto3" json:"connection_hops,omitempty"` + // opaque channel version, which is agreed upon during the handshake + Version string `protobuf:"bytes,5,opt,name=version,proto3" json:"version,omitempty"` + // upgrade sequence indicates the latest upgrade attempt performed by this channel + // the value of 0 indicates the channel has never been upgraded + UpgradeSequence uint64 `protobuf:"varint,6,opt,name=upgrade_sequence,json=upgradeSequence,proto3" json:"upgrade_sequence,omitempty"` +} +``` + +`startFlushing` is the specific method which is called in `ChanUpgradeTry` and `ChanUpgradeAck` to update the state on the channel end. This will set the timeout on the upgrade and update the channel state to `FLUSHING` which will block the upgrade from continuing until all in-flight packets have been flushed. + +This will also set the upgrade timeout for the counterparty (i.e. the timeout before which the counterparty chain must move from `FLUSHING` to `FLUSHCOMPLETE`; if it doesn't then the chain will cancel the upgrade and write an error receipt). The timeout is a relative time duration in nanoseconds that can be changed with `MsgUpdateParams` and by default is 10 minutes. + +The state will change to `FLUSHCOMPLETE` once there are no in-flight packets left and the channel end is ready to move to `OPEN`. This flush state will also have an impact on how a channel upgrade can be cancelled, as detailed below. + +All other parameters will remain the same during the upgrade handshake until the upgrade handshake completes. When the channel is reset to `OPEN` on a successful upgrade handshake, the relevant fields on the channel end will be switched over to the `UpgradeFields` specified in the upgrade. + +:::note + +When transitioning a channel from UNORDERED to ORDERED, new packet sends from the channel end which upgrades first will be incapable of being timed out until the counterparty has finished upgrading. + +::: + +:::warning + +Due to the addition of new channel states, packets can still be received and processed in both `FLUSHING` and `FLUSHCOMPLETE` states. +Packets can also be acknowledged in the `FLUSHING` state. Acknowledging will **not** be possible when the channel is in the `FLUSHCOMPLETE` state, since all packets sent from that channel end have been flushed. +Application developers should consider these new states when implementing application logic that relies on the channel state. +It is still only possible to send packets when the channel is in the `OPEN` state, but sending is disallowed when the channel enters `FLUSHING` and `FLUSHCOMPLETE`. When the channel reopens, sending will be possible again. + +::: + +## Cancelling a Channel Upgrade + +Channel upgrade cancellation is performed by submitting a `MsgChannelUpgradeCancel` message. + +It is possible for the authority to cancel an in-progress channel upgrade if the following are true: + +- The signer is the authority +- The channel state has not reached FLUSHCOMPLETE +- If the channel state has reached FLUSHCOMPLETE, an existence proof of an `ErrorReceipt` on the counterparty chain is provided at our upgrade sequence or greater + +It is possible for a relayer to cancel an in-progress channel upgrade if the following are true: + +- An existence proof of an `ErrorReceipt` on the counterparty chain is provided at our upgrade sequence or greater + +> Note: if the signer is the authority, e.g. the `gov` address, no `ErrorReceipt` or proof is required if the current channel state is not in FLUSHCOMPLETE. +> These can be left empty in the `MsgChannelUpgradeCancel` message in that case. + +Upon cancelling a channel upgrade, an `ErrorReceipt` will be written with the channel's current upgrade sequence, and +the channel will move back to `OPEN` state keeping its original parameters. + +It will then be possible to re-initiate an upgrade by sending a `MsgChannelOpenInit` message. + +:::warning + +Performing sequentially an upgrade cancellation, upgrade initialization, and another upgrade cancellation in a single block while the counterparty is in `FLUSHCOMPLETE` will lead to corrupted state. +The counterparty will be unable to cancel its upgrade attempt and will require a manual migration. +When the counterparty is in `FLUSHCOMPLETE`, it requires a proof that the counterparty cancelled its current upgrade attempt. +When this cancellation is succeeded by an initialization and cancellation in the same block, it results in the proof of cancellation existing only for the next upgrade attempt, not the current. + +::: + +## Timing Out a Channel Upgrade + +Timing out an outstanding channel upgrade may be necessary during the flushing packet stage of the channel upgrade process. As stated above, with `ChanUpgradeTry` or `ChanUpgradeAck`, the channel state has been changed from `OPEN` to `FLUSHING`, so no new packets will be allowed to be sent over this channel while flushing. If upgrades cannot be performed in a timely manner (due to unforeseen flushing issues), upgrade timeouts allow the channel to avoid blocking packet sends indefinitely. If flushing exceeds the time limit set in the `UpgradeTimeout` channel `Params`, the upgrade process will need to be timed out to abort the upgrade attempt and resume normal channel processing. + +Channel upgrades require setting a valid timeout value in the channel `Params` before submitting a `MsgChannelUpgradeTry` or `MsgChannelUpgradeAck` message (by default, 10 minutes): + +```go +type Params struct { + UpgradeTimeout Timeout +} +``` + +A valid Timeout contains either one or both of a timestamp and block height (sequence). + +```go +type Timeout struct { + // block height after which the packet or upgrade times out + Height types.Height + // block timestamp (in nanoseconds) after which the packet or upgrade times out + Timestamp uint64 +} +``` + +This timeout will then be set as a field on the `Upgrade` struct itself when flushing is started. + +```go +type Upgrade struct { + Fields UpgradeFields + Timeout Timeout + NextSequenceSend uint64 +} +``` + +If the timeout has been exceeded during flushing, a chain can then submit the `MsgChannelUpgradeTimeout` to timeout the channel upgrade process: + +```go +type MsgChannelUpgradeTimeout struct { + PortId string + ChannelId string + CounterpartyChannel Channel + ProofChannel []byte + ProofHeight types.Height + Signer string +} +``` + +An `ErrorReceipt` will be written with the channel's current upgrade sequence, and the channel will move back to `OPEN` state keeping its original parameters. + +Note that timing out a channel upgrade will end the upgrade process, and a new `MsgChannelUpgradeInit` will have to be submitted via governance in order to restart the upgrade process. + +## Pruning Acknowledgements + +Acknowledgements can be pruned by broadcasting the `MsgPruneAcknowledgements` message. + +> Note: It is only possible to prune acknowledgements after a channel has been upgraded, so pruning will fail +> if the channel has not yet been upgraded. + +```protobuf +// MsgPruneAcknowledgements defines the request type for the PruneAcknowledgements rpc. +message MsgPruneAcknowledgements { + option (cosmos.msg.v1.signer) = "signer"; + option (gogoproto.goproto_getters) = false; + + string port_id = 1; + string channel_id = 2; + uint64 limit = 3; + string signer = 4; +} +``` + +The `port_id` and `channel_id` specify the port and channel to act on, and the `limit` specifies the upper bound for the number +of acknowledgements and packet receipts to prune. + +### CLI Usage + +Acknowledgements can be pruned via the cli with the `prune-acknowledgements` command. + +```bash +simd tx ibc channel prune-acknowledgements [port] [channel] [limit] +``` + +## IBC App Recommendations + +IBC application callbacks should be primarily used to validate data fields and do compatibility checks. Application developers +should be aware that callbacks will be invoked before any core ibc state changes are written. + +`OnChanUpgradeInit` should validate the proposed version, order, and connection hops, and should return the application version to upgrade to. + +`OnChanUpgradeTry` should validate the proposed version (provided by the counterparty), order, and connection hops. The desired upgrade version should be returned. + +`OnChanUpgradeAck` should validate the version proposed by the counterparty. + +`OnChanUpgradeOpen` should perform any logic associated with changing of the channel fields. + +> IBC applications should not attempt to process any packet data under the new conditions until after `OnChanUpgradeOpen` +> has been executed, as up until this point it is still possible for the upgrade handshake to fail and for the channel +> to remain in the pre-upgraded state. + +## Upgrade an existing transfer application stack to use 29-fee middleware + +### Wire up the transfer stack and middleware in app.go + +In app.go, the existing transfer stack must be wrapped with the fee middleware. + +```golang + +import ( + // ... + ibcfee "github.com/cosmos/ibc-go/v8/modules/apps/29-fee" + ibctransferkeeper "github.com/cosmos/ibc-go/v8/modules/apps/transfer/keeper" + transfer "github.com/cosmos/ibc-go/v8/modules/apps/transfer" + porttypes "github.com/cosmos/ibc-go/v8/modules/core/05-port/types" + // ... +) + +type App struct { + // ... + TransferKeeper ibctransferkeeper.Keeper + IBCFeeKeeper ibcfeekeeper.Keeper + // .. +} + +// ... + +app.IBCFeeKeeper = ibcfeekeeper.NewKeeper( + appCodec, keys[ibcfeetypes.StoreKey], + app.IBCKeeper.ChannelKeeper, // may be replaced with IBC middleware + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.PortKeeper, app.AccountKeeper, app.BankKeeper, +) + +// Create Transfer Keeper and pass IBCFeeKeeper as expected Channel and PortKeeper +// since fee middleware will wrap the IBCKeeper for underlying application. +app.TransferKeeper = ibctransferkeeper.NewKeeper( + appCodec, keys[ibctransfertypes.StoreKey], app.GetSubspace(ibctransfertypes.ModuleName), + app.IBCFeeKeeper, // ISC4 Wrapper: fee IBC middleware + app.IBCKeeper.ChannelKeeper, app.IBCKeeper.PortKeeper, + app.AccountKeeper, app.BankKeeper, scopedTransferKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), +) + + +ibcRouter := porttypes.NewRouter() + +// create IBC module from bottom to top of stack +var transferStack porttypes.IBCModule +transferStack = transfer.NewIBCModule(app.TransferKeeper) +transferStack = ibcfee.NewIBCMiddleware(transferStack, app.IBCFeeKeeper) + +// Add transfer stack to IBC Router +ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) +``` + +### Submit a governance proposal to execute a MsgChannelUpgradeInit message + +> This process can be performed with the new CLI that has been added +> outlined [here](#upgrading-channels-with-the-cli). + +Only the configured authority for the ibc module is able to initiate a channel upgrade by submitting a `MsgChannelUpgradeInit` message. + +Execute a governance proposal specifying the relevant fields to perform a channel upgrade. + +Update the following json sample, and copy the contents into `proposal.json`. + +```json +{ + "title": "Channel upgrade init", + "summary": "Channel upgrade init", + "messages": [ + { + "@type": "/ibc.core.channel.v1.MsgChannelUpgradeInit", + "signer": "", + "port_id": "transfer", + "channel_id": "channel-...", + "fields": { + "ordering": "ORDER_UNORDERED", + "connection_hops": ["connection-0"], + "version": "{\"fee_version\":\"ics29-1\",\"app_version\":\"ics20-1\"}" + } + } + ], + "metadata": "", + "deposit": "10stake" +} +``` + +> Note: ensure the correct fields.version is specified. This is the new version that the channels will be upgraded to. + +### Submit the proposal + +```shell +simd tx submit-proposal proposal.json --from +``` + +## Upgrading channels with the CLI + +A new cli has been added which enables either + - submitting a governance proposal which contains a `MsgChannelUpgradeInit` for every channel to be upgraded. + - generating a `proposal.json` file which contains the proposal contents to be edited/submitted at a later date. + +The following example, would submit a governance proposal with the specified deposit, title and summary which would +contain a `MsgChannelUpgradeInit` for all `OPEN` channels whose port matches the regular expression `transfer`. + +> Note: by adding the `--json` flag, the command would instead output the contents of the proposal which could be +> stored in a `proposal.json` file to be edited and submitted at a later date. + +```bash +simd tx ibc channel upgrade-channels "{\"fee_version\":\"ics29-1\",\"app_version\":\"ics20-1\"}" \ + --deposit "10stake" \ + --title "Channel Upgrades Governance Proposal" \ + --summary "Upgrade all transfer channels to be fee enabled" \ + --port-pattern "transfer" +``` + +It is also possible to explicitly list a comma separated string of channel IDs. It is important to note that the +regular expression matching specified by `--port-pattern` (which defaults to `transfer`) still applies. + +For example the following command would generate the contents of a `proposal.json` file which would attempt to upgrade +channels with a port ID of `transfer` and a channelID of `channel-0`, `channel-1` or `channel-2`. + +```bash +simd tx ibc channel upgrade-channels "{\"fee_version\":\"ics29-1\",\"app_version\":\"ics20-1\"}" \ + --deposit "10stake" \ + --title "Channel Upgrades Governance Proposal" \ + --summary "Upgrade all transfer channels to be fee enabled" \ + --port-pattern "transfer" \ + --channel-ids "channel-0,channel-1,channel-2" \ + --json +``` diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/07-proposals.md b/docs/versioned_docs/version-v8.5.x/01-ibc/07-proposals.md new file mode 100644 index 0000000..e77c93b --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/07-proposals.md @@ -0,0 +1,118 @@ +--- +title: Governance Proposals +sidebar_label: Governance Proposals +sidebar_position: 7 +slug: /ibc/proposals +--- + +# Governance Proposals + +In uncommon situations, a highly valued client may become frozen due to uncontrollable +circumstances. A highly valued client might have hundreds of channels being actively used. +Some of those channels might have a significant amount of locked tokens used for ICS 20. + +If the one third of the validator set of the chain the client represents decides to collude, +they can sign off on two valid but conflicting headers each signed by the other one third +of the honest validator set. The light client can now be updated with two valid, but conflicting +headers at the same height. The light client cannot know which header is trustworthy and therefore +evidence of such misbehaviour is likely to be submitted resulting in a frozen light client. + +Frozen light clients cannot be updated under any circumstance except via a governance proposal. +Since a quorum of validators can sign arbitrary state roots which may not be valid executions +of the state machine, a governance proposal has been added to ease the complexity of unfreezing +or updating clients which have become "stuck". Without this mechanism, validator sets would need +to construct a state root to unfreeze the client. Unfreezing clients, re-enables all of the channels +built upon that client. This may result in recovery of otherwise lost funds. + +Tendermint light clients may become expired if the trusting period has passed since their +last update. This may occur if relayers stop submitting headers to update the clients. + +An unplanned upgrade by the counterparty chain may also result in expired clients. If the counterparty +chain undergoes an unplanned upgrade, there may be no commitment to that upgrade signed by the validator +set before the chain ID changes. In this situation, the validator set of the last valid update for the +light client is never expected to produce another valid header since the chain ID has changed, which will +ultimately lead the on-chain light client to become expired. + +In the case that a highly valued light client is frozen, expired, or rendered non-updateable, a +governance proposal may be submitted to update this client, known as the subject client. The +proposal includes the client identifier for the subject and the client identifier for a substitute +client. Light client implementations may implement custom updating logic, but in most cases, +the subject will be updated to the latest consensus state of the substitute client, if the proposal passes. +The substitute client is used as a "stand in" while the subject is on trial. It is best practice to create +a substitute client *after* the subject has become frozen to avoid the substitute from also becoming frozen. +An active substitute client allows headers to be submitted during the voting period to prevent accidental expiry +once the proposal passes. + +*note* two of these parameters: `AllowUpdateAfterExpiry` and `AllowUpdateAfterMisbehavior` have been deprecated, and will both be set to `false` upon upgrades even if they were previously set to `true`. These parameters will no longer play a role in restricting a client upgrade. Please see ADR026 for more details. + +# How to recover an expired client with a governance proposal + +See also the relevant documentation: [ADR-026, IBC client recovery mechanisms](/architecture/adr-026-ibc-client-recovery-mechanisms) + +> **Who is this information for?** +> Although technically anyone can submit the governance proposal to recover an expired client, often it will be **relayer operators** (at least coordinating the submission). + +## Preconditions + +- There exists an active client (with a known client identifier) for the same counterparty chain as the expired client. +- The governance deposit. + +## Steps + +### Step 1 + +Check if the client is attached to the expected `chain_id`. For example, for an expired Tendermint client representing the Akash chain the client state looks like this on querying the client state: + +```text +{ + client_id: 07-tendermint-146 + client_state: + '@type': /ibc.lightclients.tendermint.v1.ClientState + allow_update_after_expiry: true + allow_update_after_misbehaviour: true + chain_id: akashnet-2 +} +``` + +The client is attached to the expected Akash `chain_id`. Note that although the parameters (`allow_update_after_expiry` and `allow_update_after_misbehaviour`) exist to signal intent, these parameters have been deprecated and will not enforce any checks on the revival of client. See ADR-026 for more context on this deprecation. + +### Step 2 + +Anyone can submit the governance proposal to recover the client by executing the following via CLI. +If the chain is on an ibc-go version older than v8, please see the [relevant documentation](https://ibc.cosmos.network/v7/ibc/proposals). + +- From ibc-go v8 onwards + + ```shell + tx gov submit-proposal [path-to-proposal-json] + ``` + + where `proposal.json` contains: + + ```json + { + "messages": [ + { + "@type": "/ibc.core.client.v1.MsgRecoverClient", + "subject_client_id": "", + "substitute_client_id": "", + "signer": "" + } + ], + "metadata": "", + "deposit": "10stake" + "title": "My proposal", + "summary": "A short summary of my proposal", + "expedited": false + } + ``` + +The `` identifier is the proposed client to be updated. This client must be either frozen or expired. + +The `` represents a substitute client. It carries all the state for the client which may be updated. It must have identical client and chain parameters to the client which may be updated (except for latest height, frozen height, and chain ID). It should be continually updated during the voting period. + +After this, all that remains is deciding who funds the governance deposit and ensuring the governance proposal passes. If it does, the client on trial will be updated to the latest state of the substitute. + +## Important considerations + +Please note that if the counterparty client is also expired, that client will also need to update. This process updates only one client. diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/08-relayer.md b/docs/versioned_docs/version-v8.5.x/01-ibc/08-relayer.md new file mode 100644 index 0000000..a971c20 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/08-relayer.md @@ -0,0 +1,53 @@ +--- +title: Relayer +sidebar_label: Relayer +sidebar_position: 8 +slug: /ibc/relayer +--- + +# Relayer + +:::note + +## Pre-requisite readings + +- [IBC Overview](01-overview.md) +- [Events](https://docs.cosmos.network/main/learn/advanced/events) + +::: + +## Events + +Events are emitted for every transaction processed by the base application to indicate the execution +of some logic clients may want to be aware of. This is extremely useful when relaying IBC packets. +Any message that uses IBC will emit events for the corresponding TAO logic executed as defined in +the [IBC events document](/events/events). + +In the SDK, it can be assumed that for every message there is an event emitted with the type `message`, +attribute key `action`, and an attribute value representing the type of message sent +(`channel_open_init` would be the attribute value for `MsgChannelOpenInit`). If a relayer queries +for transaction events, it can split message events using this event Type/Attribute Key pair. + +The Event Type `message` with the Attribute Key `module` may be emitted multiple times for a single +message due to application callbacks. It can be assumed that any TAO logic executed will result in +a module event emission with the attribute value `ibc_` (02-client emits `ibc_client`). + +### Subscribing with Tendermint + +Calling the Tendermint RPC method `Subscribe` via Tendermint's Websocket will return events using +Tendermint's internal representation of them. Instead of receiving back a list of events as they +were emitted, Tendermint will return the type `map[string][]string` which maps a string in the +form `.` to `attribute_value`. This causes extraction of the event +ordering to be non-trivial, but still possible. + +A relayer should use the `message.action` key to extract the number of messages in the transaction +and the type of IBC transactions sent. For every IBC transaction within the string array for +`message.action`, the necessary information should be extracted from the other event fields. If +`send_packet` appears at index 2 in the value for `message.action`, a relayer will need to use the +value at index 2 of the key `send_packet.packet_sequence`. This process should be repeated for each +piece of information needed to relay a packet. + +## Example Implementations + +- [Golang Relayer](https://github.com/cosmos/relayer) +- [Hermes](https://github.com/informalsystems/hermes) diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/09-proto-docs.md b/docs/versioned_docs/version-v8.5.x/01-ibc/09-proto-docs.md new file mode 100644 index 0000000..45e73ce --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/09-proto-docs.md @@ -0,0 +1,11 @@ +--- +title: Protobuf Documentation +sidebar_label: Protobuf Documentation +sidebar_position: 9 +slug: /ibc/proto-docs +--- + + +# Protobuf documentation + +See [ibc-go Buf Protobuf documentation](https://buf.build/cosmos/ibc/docs/main). diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/10-roadmap.md b/docs/versioned_docs/version-v8.5.x/01-ibc/10-roadmap.md new file mode 100644 index 0000000..f2ee884 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/10-roadmap.md @@ -0,0 +1,54 @@ +--- +title: Roadmap +sidebar_label: Roadmap +sidebar_position: 10 +slug: /ibc/roadmap +--- + +# Roadmap ibc-go + +*Latest update: December 4th, 2023* + +This document endeavours to inform the wider IBC community about plans and priorities for work on ibc-go by the team at Interchain GmbH. It is intended to broadly inform all users of ibc-go, including developers and operators of IBC, relayer, chain and wallet applications. + +This roadmap should be read as a high-level guide, rather than a commitment to schedules and deliverables. The degree of specificity is inversely proportional to the timeline. We will update this document periodically to reflect the status and plans. For the latest expected release timelines, please check [here](https://github.com/cosmos/ibc-go/wiki/Release-timeline). + +## 08-wasm/v0.1.0 + +Follow the progress with the [milestone](https://github.com/cosmos/ibc-go/milestone/40). + +The first release of this new module will add support for Wasm light clients. The first Wasm client developed on top of ibc-go/v7 02-client refactor and stored as Wasm bytecode will be the GRANDPA light client used for Cosmos x Substrate IBC connections. This feature will be used also for a NEAR light client in the future. + +This feature has been developed by Composable and Strangelove. + +## v8.1.0 + +### Channel upgradability + +Channel upgradability will allow chains to renegotiate an existing channel to take advantage of new features without having to create a new channel, thus preserving all existing packet state processed on the channel. This feature will enable, for example, the adoption of existing channels of features like [path unwinding](https://github.com/cosmos/ibc/discussions/824) or fee middleware. + +Follow the progress with the [alpha milestone](https://github.com/cosmos/ibc-go/milestone/29) or the [project board](https://github.com/orgs/cosmos/projects/7/views/17). + +## v9.0.0 + +### Conditional clients + +Conditional clients are light clients which are dependent on another client in order to verify or update state. Conditional clients are essential for integration with modular blockchains which break up consensus and state management, such as rollups. Currently, light clients receive a single provable store they maintain. There is a unidirectional communication channel with 02-client: the 02-client module will call into the light client, without allowing for the light client to call into the 02-client module. But modular blockchains break up a logical blockchain into many constituent parts, so in order to accurately represent these chains and also to account for various types of shared security primitives that are coming up, we need to introduce dependencies between clients. In the case of optimistic rollups, for example, in order to prove execution (allowing for fraud proofs), you must prove data availability and sequencing. A potential solution to this problem is that a light client may optionally specify a list of dependencies and the 02-client module would lookup read-only provable stores for each dependency and provide this to the conditional client to perform verification. Please see [this issue](https://github.com/cosmos/ibc-go/issues/5112) for more details. + +## v10.0.0 + +### Multihop channels + +Multihop channels specify a way to route messages across a path of IBC enabled blockchains utilizing multiple pre-existing IBC connections. The current IBC protocol defines messaging in a point-to-point paradigm which allows message passing between two directly connected IBC chains, but as more IBC enabled chains come into existence there becomes a need to relay IBC packets across chains because IBC connections may not exist between the two chains wishing to exchange messages. IBC connections may not exist for a variety of reasons which could include economic inviability since connections require client state to be continuously exchanged between connection ends which carries a cost. Please see the [ICS 33 spec](https://github.com/cosmos/ibc/blob/main/spec/core/ics-033-multi-hop/README.md) for more information. + +--- + +This roadmap is also available as a [project board](https://github.com/orgs/cosmos/projects/7/views/25). + +For the latest expected release timelines, please check [here](https://github.com/cosmos/ibc-go/wiki/Release-timeline). + +For the latest information on the progress of the work or the decisions made that might influence the roadmap, please follow the [Announcements](https://github.com/cosmos/ibc-go/discussions/categories/announcements) category in the Discussions tab of the repository. + +--- + +**Note**: release version numbers may be subject to change. diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/11-troubleshooting.md b/docs/versioned_docs/version-v8.5.x/01-ibc/11-troubleshooting.md new file mode 100644 index 0000000..4254701 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/11-troubleshooting.md @@ -0,0 +1,15 @@ +--- +title: Troubleshooting +sidebar_label: Troubleshooting +sidebar_position: 11 +slug: /ibc/troubleshooting +--- + +# Troubleshooting + +## Unauthorized client states + +If it is being reported that a client state is unauthorized, this is due to the client type not being present +in the [`AllowedClients`](https://github.com/cosmos/ibc-go/blob/v6.0.0/modules/core/02-client/types/client.pb.go#L345) array. + +Unless the client type is present in this array or the `AllowAllClients` wildcard (`"*"`) is used, all usage of clients of this type will be prevented. diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/12-capability-module.md b/docs/versioned_docs/version-v8.5.x/01-ibc/12-capability-module.md new file mode 100644 index 0000000..8256a9a --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/12-capability-module.md @@ -0,0 +1,139 @@ +--- +title: Capability Module +sidebar_label: Capability Module +sidebar_position: 12 +slug: /ibc/capability-module +--- + +# Capability Module + +## Overview + +`modules/capability` is an implementation of a Cosmos SDK module, per [ADR 003](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-003-dynamic-capability-store.md), that allows for provisioning, tracking, and authenticating multi-owner capabilities at runtime. + +The keeper maintains two states: persistent and ephemeral in-memory. The persistent +store maintains a globally unique auto-incrementing index and a mapping from +capability index to a set of capability owners that are defined as a module and +capability name tuple. The in-memory ephemeral state keeps track of the actual +capabilities, represented as addresses in local memory, with both forward and reverse indexes. +The forward index maps module name and capability tuples to the capability name. The +reverse index maps between the module and capability name and the capability itself. + +The keeper allows the creation of "scoped" sub-keepers which are tied to a particular +module by name. Scoped keepers must be created at application initialization and +passed to modules, which can then use them to claim capabilities they receive and +retrieve capabilities which they own by name, in addition to creating new capabilities +& authenticating capabilities passed by other modules. A scoped keeper cannot escape its scope, +so a module cannot interfere with or inspect capabilities owned by other modules. + +The keeper provides no other core functionality that can be found in other modules +like queriers, REST and CLI handlers, and genesis state. + +## Initialization + +During application initialization, the keeper must be instantiated with a persistent +store key and an in-memory store key. + +```go +type App struct { + // ... + + capabilityKeeper *capability.Keeper +} + +func NewApp(...) *App { + // ... + + app.capabilityKeeper = capabilitykeeper.NewKeeper(codec, persistentStoreKey, memStoreKey) +} +``` + +After the keeper is created, it can be used to create scoped sub-keepers which +are passed to other modules that can create, authenticate, and claim capabilities. +After all the necessary scoped keepers are created and the state is loaded, the +main capability keeper must be sealed to prevent further scoped keepers from +being created. + +```go +func NewApp(...) *App { + // ... + + // Creating a scoped keeper + scopedIBCKeeper := app.CapabilityKeeper.ScopeToModule(ibchost.ModuleName) + + // Seal the capability keeper to prevent any further modules from creating scoped + // sub-keepers. + app.capabilityKeeper.Seal() + + return app +} +``` + +## Contents + +- [`modules/capability`](#capability-module) + - [Overview](#overview) + - [Initialization](#initialization) + - [Contents](#contents) + - [Concepts](#concepts) + - [Capabilities](#capabilities) + - [Stores](#stores) + - [State](#state) + - [Persisted KV store](#persisted-kv-store) + - [In-memory KV store](#in-memory-kv-store) + +## Concepts + +### Capabilities + +Capabilities are multi-owner. A scoped keeper can create a capability via `NewCapability` +which creates a new unique, unforgeable object-capability reference. The newly +created capability is automatically persisted; the calling module need not call +`ClaimCapability`. Calling `NewCapability` will create the capability with the +calling module and name as a tuple to be treated the capabilities first owner. + +Capabilities can be claimed by other modules which add them as owners. `ClaimCapability` +allows a module to claim a capability key which it has received from another +module so that future `GetCapability` calls will succeed. `ClaimCapability` MUST +be called if a module which receives a capability wishes to access it by name in +the future. Again, capabilities are multi-owner, so if multiple modules have a +single Capability reference, they will all own it. If a module receives a capability +from another module but does not call `ClaimCapability`, it may use it in the executing +transaction but will not be able to access it afterwards. + +`AuthenticateCapability` can be called by any module to check that a capability +does in fact correspond to a particular name (the name can be un-trusted user input) +with which the calling module previously associated it. + +`GetCapability` allows a module to fetch a capability which it has previously +claimed by name. The module is not allowed to retrieve capabilities which it does +not own. + +### Stores + +- MemStore +- KeyStore + +## State + +### Persisted KV store + +1. Global unique capability index +2. Capability owners + +Indexes: + +- Unique index: `[]byte("index") -> []byte(currentGlobalIndex)` +- Capability Index: `[]byte("capability_index") | []byte(index) -> ProtocolBuffer(CapabilityOwners)` + +### In-memory KV store + +1. Initialized flag +2. Mapping between the module and capability tuple and the capability name +3. Mapping between the module and capability name and its index + +Indexes: + +- Initialized flag: `[]byte("mem_initialized")` +- RevCapabilityKey: `[]byte(moduleName + "/rev/" + capabilityName) -> []byte(index)` +- FwdCapabilityKey: `[]byte(moduleName + "/fwd/" + capabilityPointerAddress) -> []byte(capabilityName)` diff --git a/docs/versioned_docs/version-v8.5.x/01-ibc/_category_.json b/docs/versioned_docs/version-v8.5.x/01-ibc/_category_.json new file mode 100644 index 0000000..afc6d18 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/01-ibc/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Using IBC-Go", + "position": 1, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/01-overview.md b/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/01-overview.md new file mode 100644 index 0000000..3ddce94 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/01-overview.md @@ -0,0 +1,138 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /apps/transfer/overview +--- + +# Overview + +:::note Synopsis +Learn about what the token Transfer module is +::: + +## What is the Transfer module? + +Transfer is the Cosmos SDK implementation of the [ICS-20](https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer) protocol, which enables cross-chain fungible token transfers. + +## Concepts + +### Acknowledgements + +ICS20 uses the recommended acknowledgement format as specified by [ICS 04](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope). + +A successful receive of a transfer packet will result in a Result Acknowledgement being written +with the value `[]byte{byte(1)}` in the `Response` field. + +An unsuccessful receive of a transfer packet will result in an Error Acknowledgement being written +with the error message in the `Response` field. + +### Denomination trace + +The denomination trace corresponds to the information that allows a token to be traced back to its +origin chain. It contains a sequence of port and channel identifiers ordered from the most recent to +the oldest in the timeline of transfers. + +This information is included on the token's base denomination field in the form of a hash to prevent an +unbounded denomination length. For example, the token `transfer/channelToA/uatom` will be displayed +as `ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2`. The human readable denomination +is stored using `x/bank` module's [denom metadata](https://docs.cosmos.network/main/build/modules/bank#denom-metadata) +feature. You may display the human readable denominations by querying balances with the `--resolve-denom` flag, as in: + +```shell +simd query bank balances [address] --resolve-denom +``` + +Each send to any chain other than the one it was previously received from is a movement forwards in +the token's timeline. This causes trace to be added to the token's history and the destination port +and destination channel to be prefixed to the denomination. In these instances the sender chain is +acting as the "source zone". When the token is sent back to the chain it previously received from, the +prefix is removed. This is a backwards movement in the token's timeline and the sender chain is +acting as the "sink zone". + +It is strongly recommended to read the full details of [ADR 001: Coin Source Tracing](/architecture/adr-001-coin-source-tracing) to understand the implications and context of the IBC token representations. + +## UX suggestions for clients + +For clients (wallets, exchanges, applications, block explorers, etc) that want to display the source of the token, it is recommended to use the following alternatives for each of the cases below: + +### Direct connection + +If the denomination trace contains a single identifier prefix pair (as in the example above), then +the easiest way to retrieve the chain and light client identifier is to map the trace information +directly. In summary, this requires querying the channel from the denomination trace identifiers, +and then the counterparty client state using the counterparty port and channel identifiers from the +retrieved channel. + +A general pseudo algorithm would look like the following: + +1. Query the full denomination trace. +2. Query the channel with the `portID/channelID` pair, which corresponds to the first destination of the + token. +3. Query the client state using the identifiers pair. Note that this query will return a `"Not +Found"` response if the current chain is not connected to this channel. +4. Retrieve the client identifier or chain identifier from the client state (eg: on + Tendermint clients) and store it locally. + +Using the gRPC gateway client service the steps above would be, with a given IBC token `ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2` stored on `chainB`: + +1. `GET /ibc/apps/transfer/v1/denom_traces/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2` -> `{"path": "transfer/channelToA", "base_denom": "uatom"}` +2. `GET /ibc/apps/transfer/v1/channels/channelToA/ports/transfer/client_state"` -> `{"client_id": "clientA", "chain-id": "chainA", ...}` +3. `GET /ibc/apps/transfer/v1/channels/channelToA/ports/transfer"` -> `{"channel_id": "channelToA", port_id": "transfer", counterparty: {"channel_id": "channelToB", port_id": "transfer"}, ...}` +4. `GET /ibc/apps/transfer/v1/channels/channelToB/ports/transfer/client_state" -> {"client_id": "clientB", "chain-id": "chainB", ...}` + +Then, the token transfer chain path for the `uatom` denomination would be: `chainA` -> `chainB`. + +### Multiple hops + +The multiple channel hops case applies when the token has passed through multiple chains between the original source and final destination chains. + +The IBC protocol doesn't know the topology of the overall network (i.e connections between chains and identifier names between them). For this reason, in the multiple hops case, a particular chain in the timeline of the individual transfers can't query the chain and client identifiers of the other chains. + +Take for example the following sequence of transfers `A -> B -> C` for an IBC token, with a final prefix path (trace info) of `transfer/channelChainC/transfer/channelChainB`. What the paragraph above means is that even in the case that chain `C` is directly connected to chain `A`, querying the port and channel identifiers that chain `B` uses to connect to chain `A` (eg: `transfer/channelChainA`) can be completely different from the one that chain `C` uses to connect to chain `A` (eg: `transfer/channelToChainA`). + +Thus the proposed solution for clients that the IBC team recommends are the following: + +- **Connect to all chains**: Connecting to all the chains in the timeline would allow clients to + perform the queries outlined in the [direct connection](#direct-connection) section to each + relevant chain. By repeatedly following the port and channel denomination trace transfer timeline, + clients should always be able to find all the relevant identifiers. This comes at the tradeoff + that the client must connect to nodes on each of the chains in order to perform the queries. +- **Relayer as a Service (RaaS)**: A longer term solution is to use/create a relayer service that + could map the denomination trace to the chain path timeline for each token (i.e `origin chain -> +chain #1 -> ... -> chain #(n-1) -> final chain`). These services could provide merkle proofs in + order to allow clients to optionally verify the path timeline correctness for themselves by + running light clients. If the proofs are not verified, they should be considered as trusted third + parties services. Additionally, client would be advised in the future to use RaaS that support the + largest number of connections between chains in the ecosystem. Unfortunately, none of the existing + public relayers (in [Golang](https://github.com/cosmos/relayer) and + [Rust](https://github.com/informalsystems/ibc-rs)), provide this service to clients. + +:::tip +The only viable alternative for clients (at the time of writing) to tokens with multiple connection hops, is to connect to all chains directly and perform relevant queries to each of them in the sequence. +::: + +## Locked funds + +In some [exceptional cases](/architecture/adr-026-ibc-client-recovery-mechanisms#exceptional-cases), a client state associated with a given channel cannot be updated. This causes that funds from fungible tokens in that channel will be permanently locked and thus can no longer be transferred. + +To mitigate this, a client update governance proposal can be submitted to update the frozen client +with a new valid header. Once the proposal passes the client state will be unfrozen and the funds +from the associated channels will then be unlocked. This mechanism only applies to clients that +allow updates via governance, such as Tendermint clients. + +In addition to this, it's important to mention that a token must be sent back along the exact route +that it took originally in order to return it to its original form on the source chain (eg: the +Cosmos Hub for the `uatom`). Sending a token back to the same chain across a different channel will +**not** move the token back across its timeline. If a channel in the chain history closes before the +token can be sent back across that channel, then the token will not be returnable to its original +form. + +## Security considerations + +For safety, no other module must be capable of minting tokens with the `ibc/` prefix. The IBC +transfer module needs a subset of the denomination space that only it can create tokens in. + +## Channel Closure + +The IBC transfer module does not support channel closure. diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/02-state.md b/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/02-state.md new file mode 100644 index 0000000..17f48a2 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/02-state.md @@ -0,0 +1,13 @@ +--- +title: State +sidebar_label: State +sidebar_position: 2 +slug: /apps/transfer/state +--- + +# State + +The IBC transfer application module keeps state of the port to which the module is binded and the denomination trace information as outlined in [ADR 001](/architecture/adr-001-coin-source-tracing). + +- `Port`: `0x01 -> ProtocolBuffer(string)` +- `DenomTrace`: `0x02 | []bytes(traceHash) -> ProtocolBuffer(DenomTrace)` diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/03-state-transitions.md b/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/03-state-transitions.md new file mode 100644 index 0000000..23bf1bf --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/03-state-transitions.md @@ -0,0 +1,37 @@ +--- +title: State Transitions +sidebar_label: State Transitions +sidebar_position: 3 +slug: /apps/transfer/state-transitions +--- + +# State transitions + +## Send fungible tokens + +A successful fungible token send has two state transitions depending if the transfer is a movement forward or backwards in the token's timeline: + +1. Sender chain is the source chain, *i.e* a transfer to any chain other than the one it was previously received from is a movement forwards in the token's timeline. This results in the following state transitions: + + - The coins are transferred to an escrow address (i.e locked) on the sender chain. + - The coins are transferred to the receiving chain through IBC TAO logic. + +2. Sender chain is the sink chain, *i.e* the token is sent back to the chain it previously received from. This is a backwards movement in the token's timeline. This results in the following state transitions: + + - The coins (vouchers) are burned on the sender chain. + - The coins are transferred to the receiving chain through IBC TAO logic. + +## Receive fungible tokens + +A successful fungible token receive has two state transitions depending if the transfer is a movement forward or backwards in the token's timeline: + +1. Receiver chain is the source chain. This is a backwards movement in the token's timeline. This results in the following state transitions: + + - The leftmost port and channel identifier pair is removed from the token denomination prefix. + - The tokens are unescrowed and sent to the receiving address. + +2. Receiver chain is the sink chain. This is a movement forwards in the token's timeline. This results in the following state transitions: + + - Token vouchers are minted by prefixing the destination port and channel identifiers to the trace information. + - The receiving chain stores the new trace information in the store (if not set already). + - The vouchers are sent to the receiving address. diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/04-messages.md b/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/04-messages.md new file mode 100644 index 0000000..ebf6854 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/04-messages.md @@ -0,0 +1,61 @@ +--- +title: Messages +sidebar_label: Messages +sidebar_position: 4 +slug: /apps/transfer/messages +--- + +# Messages + +## `MsgTransfer` + +A fungible token cross chain transfer is achieved by using the `MsgTransfer`: + +```go +type MsgTransfer struct { + SourcePort string + SourceChannel string + Token sdk.Coin + Sender string + Receiver string + TimeoutHeight ibcexported.Height + TimeoutTimestamp uint64 + Memo string +} +``` + +This message is expected to fail if: + +- `SourcePort` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators). +- `SourceChannel` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +- `Token` is invalid (denom is invalid or amount is negative) + - `Token.Amount` is not positive. + - `Token.Denom` is not a valid IBC denomination as per [ADR 001 - Coin Source Tracing](/architecture/adr-001-coin-source-tracing). +- `Sender` is empty. +- `Receiver` is empty or contains more than 2048 bytes. +- `Memo` contains more than 32768 bytes. +- `TimeoutHeight` and `TimeoutTimestamp` are both zero. + +This message will send a fungible token to the counterparty chain represented by the counterparty Channel End connected to the Channel End with the identifiers `SourcePort` and `SourceChannel`. + +The denomination provided for transfer should correspond to the same denomination represented on this chain. The prefixes will be added as necessary upon by the receiving chain. + +If the `Amount` is set to the maximum value for a 256-bit unsigned integer (i.e. 2^256 - 1), then the whole balance of the corresponding denomination will be transferred. The helper function `UnboundedSpendLimit` in the `types` package of the `transfer` module provides the sentinel value that can be used. + +### Memo + +The memo field was added to allow applications and users to attach metadata to transfer packets. The field is optional and may be left empty. When it is used to attach metadata for a particular middleware, the memo field should be represented as a json object where different middlewares use different json keys. + +For example, the following memo field is used by the [callbacks middleware](../../04-middleware/02-callbacks/01-overview.md) to attach a source callback to a transfer packet: + +```jsonc +{ + "src_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} +``` + +You can find more information about other applications that use the memo field in the [chain registry](https://github.com/cosmos/chain-registry/blob/master/_memo_keys/ICS20_memo_keys.json). diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/05-events.md b/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/05-events.md new file mode 100644 index 0000000..25f66d0 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/05-events.md @@ -0,0 +1,57 @@ +--- +title: Events +sidebar_label: Events +sidebar_position: 5 +slug: /apps/transfer/events +--- + +# Events + +## `MsgTransfer` + +| Type | Attribute Key | Attribute Value | +|--------------|---------------|-----------------| +| ibc_transfer | sender | \{sender\} | +| ibc_transfer | receiver | \{receiver\} | +| ibc_transfer | amount | \{amount\} | +| ibc_transfer | denom | \{denom\} | +| ibc_transfer | memo | \{memo\} | +| message | module | transfer | + +## `OnRecvPacket` callback + +| Type | Attribute Key | Attribute Value | +|-----------------------|---------------|-----------------| +| fungible_token_packet | module | transfer | +| fungible_token_packet | sender | \{sender\} | +| fungible_token_packet | receiver | \{receiver\} | +| fungible_token_packet | denom | \{denom\} | +| fungible_token_packet | amount | \{amount\} | +| fungible_token_packet | memo | \{memo\} | +| fungible_token_packet | success | \{ackSuccess\} | +| fungible_token_packet | error | \{ackError\} | +| denomination_trace | trace_hash | \{hex_hash\} | +| denomination_trace | denom | \{voucherDenom\}| + +## `OnAcknowledgePacket` callback + +| Type | Attribute Key | Attribute Value | +|-----------------------|-----------------|------------------| +| fungible_token_packet | module | transfer | +| fungible_token_packet | sender | \{sender\} | +| fungible_token_packet | receiver | \{receiver\} | +| fungible_token_packet | denom | \{denom\} | +| fungible_token_packet | amount | \{amount\} | +| fungible_token_packet | memo | \{memo\} | +| fungible_token_packet | acknowledgement | \{ack.String()\} | +| fungible_token_packet | success / error | \{ack.Response\} | + +## `OnTimeoutPacket` callback + +| Type | Attribute Key | Attribute Value | +|-----------------------|-----------------|-----------------| +| fungible_token_packet | module | transfer | +| fungible_token_packet | refund_receiver | \{receiver\} | +| fungible_token_packet | denom | \{denom\} | +| fungible_token_packet | amount | \{amount\} | +| fungible_token_packet | memo | \{memo\} | diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/06-metrics.md b/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/06-metrics.md new file mode 100644 index 0000000..47a71b6 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/06-metrics.md @@ -0,0 +1,18 @@ +--- +title: Metrics +sidebar_label: Metrics +sidebar_position: 6 +slug: /apps/transfer/metrics +--- + + +# Metrics + +The IBC transfer application module exposes the following set of [metrics](hhttps://docs.cosmos.network/main/learn/advanced/telemetry). + +| Metric | Description | Unit | Type | +|:--------------------------------|:------------------------------------------------------------------------------------------|:----------------|:--------| +| `tx_msg_ibc_transfer` | The total amount of tokens transferred via IBC in a `MsgTransfer` (source or sink chain) | token | gauge | +| `ibc_transfer_packet_receive` | The total amount of tokens received in a `FungibleTokenPacketData` (source or sink chain) | token | gauge | +| `ibc_transfer_send` | Total number of IBC transfers sent from a chain (source or sink) | transfer | counter | +| `ibc_transfer_receive` | Total number of IBC transfers received to a chain (source or sink) | transfer | counter | diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/07-params.md b/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/07-params.md new file mode 100644 index 0000000..aaa0dfd --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/07-params.md @@ -0,0 +1,94 @@ +--- +title: Params +sidebar_label: Params +sidebar_position: 7 +slug: /apps/transfer/params +--- + + +# Parameters + +The IBC transfer application module contains the following parameters: + +| Name | Type | Default Value | +| ---------------- | ---- | ------------- | +| `SendEnabled` | bool | `true` | +| `ReceiveEnabled` | bool | `true` | + +The IBC transfer module stores its parameters in its keeper with the prefix of `0x03`. + +## `SendEnabled` + +The `SendEnabled` parameter controls send cross-chain transfer capabilities for all fungible tokens. + +To prevent a single token from being transferred from the chain, set the `SendEnabled` parameter to `true` and then, depending on the Cosmos SDK version, do one of the following: + +- For Cosmos SDK v0.46.x or earlier, set the bank module's [`SendEnabled` parameter](https://github.com/cosmos/cosmos-sdk/blob/release/v0.46.x/x/bank/spec/05_params.md#sendenabled) for the denomination to `false`. +- For Cosmos SDK versions above v0.46.x, set the bank module's `SendEnabled` entry for the denomination to `false` using `MsgSetSendEnabled` as a governance proposal. + +:::warning +Doing so will prevent the token from being transferred between any accounts in the blockchain. +::: + +## `ReceiveEnabled` + +The transfers enabled parameter controls receive cross-chain transfer capabilities for all fungible tokens. + +To prevent a single token from being transferred to the chain, set the `ReceiveEnabled` parameter to `true` and then, depending on the Cosmos SDK version, do one of the following: + +- For Cosmos SDK v0.46.x or earlier, set the bank module's [`SendEnabled` parameter](https://github.com/cosmos/cosmos-sdk/blob/release/v0.46.x/x/bank/spec/05_params.md#sendenabled) for the denomination to `false`. +- For Cosmos SDK versions above v0.46.x, set the bank module's `SendEnabled` entry for the denomination to `false` using `MsgSetSendEnabled` as a governance proposal. + +:::warning +Doing so will prevent the token from being transferred between any accounts in the blockchain. +::: + +## Queries + +Current parameter values can be queried via a query message. + + + +```protobuf +// proto/ibc/applications/transfer/v1/query.proto + +// QueryParamsRequest is the request type for the Query/Params RPC method. +message QueryParamsRequest {} + +// QueryParamsResponse is the response type for the Query/Params RPC method. +message QueryParamsResponse { + // params defines the parameters of the module. + Params params = 1; +} +``` + +To execute the query in `simd`, you use the following command: + +```bash +simd query ibc-transfer params +``` + +## Changing Parameters + +To change the parameter values, you must make a governance proposal that executes the `MsgUpdateParams` message. + + + +```protobuf +// proto/ibc/applications/transfer/v1/tx.proto + +// MsgUpdateParams is the Msg/UpdateParams request type. +message MsgUpdateParams { + // signer address (it may be the address that controls the module, which defaults to x/gov unless overwritten). + string signer = 1; + + // params defines the transfer parameters to update. + // + // NOTE: All parameters must be supplied. + Params params = 2 [(gogoproto.nullable) = false]; +} + +// MsgUpdateParamsResponse defines the response structure for executing a +// MsgUpdateParams message. +message MsgUpdateParamsResponse {} +``` diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/08-authorizations.md b/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/08-authorizations.md new file mode 100644 index 0000000..e5f4744 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/08-authorizations.md @@ -0,0 +1,59 @@ +--- +title: Authorizations +sidebar_label: Authorizations +sidebar_position: 8 +slug: /apps/transfer/authorizations +--- +# `TransferAuthorization` + +`TransferAuthorization` implements the `Authorization` interface for `ibc.applications.transfer.v1.MsgTransfer`. It allows a granter to grant a grantee the privilege to submit `MsgTransfer` on its behalf. Please see the [Cosmos SDK docs](https://docs.cosmos.network/v0.47/modules/authz) for more details on granting privileges via the `x/authz` module. + +More specifically, the granter allows the grantee to transfer funds that belong to the granter over a specified channel. + +For the specified channel, the granter must be able to specify a spend limit of a specific denomination they wish to allow the grantee to be able to transfer. + +The granter may be able to specify the list of addresses that they allow to receive funds. If empty, then all addresses are allowed. + +It takes: + +- a `SourcePort` and a `SourceChannel` which together comprise the unique transfer channel identifier over which authorized funds can be transferred. + +- a `SpendLimit` that specifies the maximum amount of tokens the grantee can transfer. The `SpendLimit` is updated as the tokens are transferred, unless the sentinel value of the maximum value for a 256-bit unsigned integer (i.e. 2^256 - 1) is used for the amount, in which case the `SpendLimit` will not be updated (please be aware that using this sentinel value will grant the grantee the privilege to transfer **all** the tokens of a given denomination available at the granter's account). The helper function `UnboundedSpendLimit` in the `types` package of the `transfer` module provides the sentinel value that can be used. This `SpendLimit` may also be updated to increase or decrease the limit as the granter wishes. + +- an `AllowList` list that specifies the list of addresses that are allowed to receive funds. If this list is empty, then all addresses are allowed to receive funds from the `TransferAuthorization`. + +- an `AllowedPacketData` list that specifies the list of memo strings that are allowed to be included in the memo field of the packet. If this list is empty, then only an empty memo is allowed (a `memo` field with non-empty content will be denied). If this list includes a single element equal to `"*"`, then any content in the `memo` field will be allowed. + +Setting a `TransferAuthorization` is expected to fail if: + +- the spend limit is nil +- the denomination of the spend limit is an invalid coin type +- the source port ID is invalid +- the source channel ID is invalid +- there are duplicate entries in the `AllowList` +- the `memo` field is not allowed by `AllowedPacketData` + +Below is the `TransferAuthorization` message: + +```go +func NewTransferAuthorization(allocations ...Allocation) *TransferAuthorization { + return &TransferAuthorization{ + Allocations: allocations, + } +} + +type Allocation struct { + // the port on which the packet will be sent + SourcePort string + // the channel by which the packet will be sent + SourceChannel string + // spend limitation on the channel + SpendLimit sdk.Coins + // allow list of receivers, an empty allow list permits any receiver address + AllowList []string + // allow list of packet data keys, an empty list prohibits all packet data keys; + // a list only with "*" permits any packet data key + AllowedPacketData []string +} + +``` diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/09-client.md b/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/09-client.md new file mode 100644 index 0000000..8023648 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/09-client.md @@ -0,0 +1,69 @@ +--- +title: Client +sidebar_label: Client +sidebar_position: 9 +slug: /apps/transfer/client +--- + +# Client + +## CLI + +A user can query and interact with the `transfer` module using the CLI. Use the `--help` flag to discover the available commands: + +### Query + +The `query` commands allow users to query `transfer` state. + +```shell +simd query ibc-transfer --help +``` + +#### `total-escrow` + +The `total-escrow` command allows users to query the total amount in escrow for a particular coin denomination regardless of the transfer channel from where the coins were sent out. + +```shell +simd query ibc-transfer total-escrow [denom] [flags] +``` + +Example: + +```shell +simd query ibc-transfer total-escrow samoleans +``` + +Example Output: + +```shell +amount: "100" +``` + +## gRPC + +A user can query the `transfer` module using gRPC endpoints. + +### `TotalEscrowForDenom` + +The `TotalEscrowForDenom` endpoint allows users to query the total amount in escrow for a particular coin denomination regardless of the transfer channel from where the coins were sent out. + +```shell +ibc.applications.transfer.v1.Query/TotalEscrowForDenom +``` + +Example: + +```shell +grpcurl -plaintext \ + -d '{"denom":"samoleans"}' \ + localhost:9090 \ + ibc.applications.transfer.v1.Query/TotalEscrowForDenom +``` + +Example output: + +```shell +{ + "amount": "100" +} +``` diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/_category_.json b/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/_category_.json new file mode 100644 index 0000000..50d492b --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/01-transfer/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Transfer", + "position": 1, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/01-overview.md b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/01-overview.md new file mode 100644 index 0000000..b9075f3 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/01-overview.md @@ -0,0 +1,48 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /apps/interchain-accounts/overview +--- + + +# Overview + +:::note Synopsis +Learn about what the Interchain Accounts module is +::: + +## What is the Interchain Accounts module? + +Interchain Accounts is the Cosmos SDK implementation of the ICS-27 protocol, which enables cross-chain account management built upon IBC. + +- How does an interchain account differ from a regular account? + +Regular accounts use a private key to sign transactions. Interchain Accounts are instead controlled programmatically by counterparty chains via IBC packets. + +## Concepts + +`Host Chain`: The chain where the interchain account is registered. The host chain listens for IBC packets from a controller chain which should contain instructions (e.g. Cosmos SDK messages) for which the interchain account will execute. + +`Controller Chain`: The chain registering and controlling an account on a host chain. The controller chain sends IBC packets to the host chain to control the account. + +`Interchain Account`: An account on a host chain created using the ICS-27 protocol. An interchain account has all the capabilities of a normal account. However, rather than signing transactions with a private key, a controller chain will send IBC packets to the host chain which signals what transactions the interchain account should execute. + +`Authentication Module`: A custom application module on the controller chain that uses the Interchain Accounts module to build custom logic for the creation & management of interchain accounts. It can be either an IBC application module using the [legacy API](10-legacy/03-keeper-api.md), or a regular Cosmos SDK application module sending messages to the controller submodule's `MsgServer` (this is the recommended approach from ibc-go v6 if access to packet callbacks is not needed). Please note that the legacy API will eventually be removed and IBC applications will not be able to use them in later releases. + +## SDK security model + +SDK modules on a chain are assumed to be trustworthy. For example, there are no checks to prevent an untrustworthy module from accessing the bank keeper. + +The implementation of ICS-27 in ibc-go uses this assumption in its security considerations. + +The implementation assumes other IBC application modules will not bind to ports within the ICS-27 namespace. + +## Channel Closure + +The provided interchain account host and controller implementations do not support `ChanCloseInit`. However, they do support `ChanCloseConfirm`. +This means that the host and controller modules cannot close channels, but they will confirm channel closures initiated by other implementations of ICS-27. + +In the event of a channel closing (due to a packet timeout in an ordered channel, for example), the interchain account associated with that channel can become accessible again if a new channel is created with a (JSON-formatted) version string that encodes the exact same `Metadata` information of the previous channel. The channel can be reopened using either [`MsgRegisterInterchainAccount`](./05-messages.md#msgregisterinterchainaccount) or `MsgChannelOpenInit`. If `MsgRegisterInterchainAccount` is used, then it is possible to leave the `version` field of the message empty, since it will be filled in by the controller submodule. If `MsgChannelOpenInit` is used, then the `version` field must be provided with the correct JSON-encoded `Metadata` string. See section [Understanding Active Channels](./09-active-channels.md#understanding-active-channels) for more information. + +When reopening a channel with the default controller submodule, the ordering of the channel cannot be changed. In order to change the ordering of the channel, the channel has to go through a [channel upgrade handshake](../../01-ibc/06-channel-upgrades.md) or reopen the channel with a custom controller implementation. diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/02-development.md b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/02-development.md new file mode 100644 index 0000000..9a36469 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/02-development.md @@ -0,0 +1,40 @@ +--- +title: Development Use Cases +sidebar_label: Development Use Cases +sidebar_position: 2 +slug: /apps/interchain-accounts/development +--- + + +# Development use cases + +The initial version of Interchain Accounts allowed for the controller submodule to be extended by providing it with an underlying application which would handle all packet callbacks. +That functionality is now being deprecated in favor of alternative approaches. +This document will outline potential use cases and redirect each use case to the appropriate documentation. + +## Custom authentication + +Interchain accounts may be associated with alternative types of authentication relative to the traditional public/private key signing. +If you wish to develop or use Interchain Accounts with a custom authentication module and do not need to execute custom logic on the packet callbacks, we recommend you use ibc-go v6 or greater and that your custom authentication module interacts with the controller submodule via the [`MsgServer`](05-messages.md). + +If you wish to consume and execute custom logic in the packet callbacks, then please read the section [Packet callbacks](#packet-callbacks) below. + +## Redirection to a smart contract + +It may be desirable to allow smart contracts to control an interchain account. +To facilitate such an action, the controller submodule may be provided an underlying application which redirects to smart contract callers. +An improved design has been suggested in [ADR 008](https://github.com/cosmos/ibc-go/pull/1976) which performs this action via middleware. + +Implementers of this use case are recommended to follow the ADR 008 approach. +The underlying application may continue to be used as a short term solution for ADR 008 and the [legacy API](./10-legacy/01-auth-modules.md) should continue to be utilized in such situations. + +## Packet callbacks + +If a developer requires access to packet callbacks for their use case, then they have the following options: + +1. Write a smart contract which is connected via an ADR 008 or equivalent IBC application (recommended). +2. Use the controller's underlying application to implement packet callback logic. + +In the first case, the smart contract should use the [`MsgServer`](05-messages.md). + +In the second case, the underlying application should use the [legacy API](10-legacy/03-keeper-api.md). diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/03-auth-modules.md b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/03-auth-modules.md new file mode 100644 index 0000000..bac035e --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/03-auth-modules.md @@ -0,0 +1,27 @@ +--- +title: Authentication Modules +sidebar_label: Authentication Modules +sidebar_position: 3 +slug: /apps/interchain-accounts/auth-modules +--- + + +# Building an authentication module + +:::note Synopsis +Authentication modules enable application developers to perform custom logic when interacting with the Interchain Accounts controller sumbmodule's `MsgServer`. +::: + +The controller submodule is used for account registration and packet sending. It executes only logic required of all controllers of interchain accounts. The type of authentication used to manage the interchain accounts remains unspecified. There may exist many different types of authentication which are desirable for different use cases. Thus the purpose of the authentication module is to wrap the controller submodule with custom authentication logic. + +In ibc-go, authentication modules can communicate with the controller submodule by passing messages through `baseapp`'s `MsgServiceRouter`. To implement an authentication module, the `IBCModule` interface need not be fulfilled; it is only required to fulfill Cosmos SDK's `AppModuleBasic` interface, just like any regular Cosmos SDK application module. + +The authentication module must: + +- Authenticate interchain account owners. +- Track the associated interchain account address for an owner. +- Send packets on behalf of an owner (after authentication). + +## Integration into `app.go` file + +To integrate the authentication module into your chain, please follow the steps outlined in [`app.go` integration](04-integration.md#example-integration). diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/04-integration.md b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/04-integration.md new file mode 100644 index 0000000..d46dd8c --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/04-integration.md @@ -0,0 +1,202 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 4 +slug: /apps/interchain-accounts/integration +--- + + +# Integration + +:::note Synopsis +Learn how to integrate Interchain Accounts host and controller functionality to your chain. The following document only applies for Cosmos SDK chains. +::: + +The Interchain Accounts module contains two submodules. Each submodule has its own IBC application. The Interchain Accounts module should be registered as an `AppModule` in the same way all SDK modules are registered on a chain, but each submodule should create its own `IBCModule` as necessary. A route should be added to the IBC router for each submodule which will be used. + +Chains who wish to support ICS-27 may elect to act as a host chain, a controller chain or both. Disabling host or controller functionality may be done statically by excluding the host or controller submodule entirely from the `app.go` file or it may be done dynamically by taking advantage of the on-chain parameters which enable or disable the host or controller submodules. + +Interchain Account authentication modules (both custom or generic, such as the `x/gov`, `x/group` or `x/auth` Cosmos SDK modules) can send messages to the controller submodule's [`MsgServer`](05-messages.md) to register interchain accounts and send packets to the interchain account. To accomplish this, the authentication module needs to be composed with `baseapp`'s `MsgServiceRouter`. + +![ica-v6.png](./images/ica-v6.png) + +> Please note that since ibc-go v8.3.0 it is mandatory to register the gRPC query router after the creation of the host submodule's keeper; otherwise, nodes will not start. The query router is used to execute on the host query messages encoded in the ICA packet data. Please check the sample integration code below for more details. + +## Example integration + +```go +// app.go + +// Register the AppModule for the Interchain Accounts module and the authentication module +// Note: No `icaauth` exists, this must be substituted with an actual Interchain Accounts authentication module +ModuleBasics = module.NewBasicManager( + ... + ica.AppModuleBasic{}, + icaauth.AppModuleBasic{}, + ... +) + +... + +// Add module account permissions for the Interchain Accounts module +// Only necessary for host chain functionality +// Each Interchain Account created on the host chain is derived from the module account created +maccPerms = map[string][]string{ + ... + icatypes.ModuleName: nil, +} + +... + +// Add Interchain Accounts Keepers for each submodule used and the authentication module +// If a submodule is being statically disabled, the associated Keeper does not need to be added. +type App struct { + ... + + ICAControllerKeeper icacontrollerkeeper.Keeper + ICAHostKeeper icahostkeeper.Keeper + ICAAuthKeeper icaauthkeeper.Keeper + + ... +} + +... + +// Create store keys for each submodule Keeper and the authentication module +keys := sdk.NewKVStoreKeys( + ... + icacontrollertypes.StoreKey, + icahosttypes.StoreKey, + icaauthtypes.StoreKey, + ... +) + +... + +// Create the scoped keepers for each submodule keeper and authentication keeper +scopedICAControllerKeeper := app.CapabilityKeeper.ScopeToModule(icacontrollertypes.SubModuleName) +scopedICAHostKeeper := app.CapabilityKeeper.ScopeToModule(icahosttypes.SubModuleName) +scopedICAAuthKeeper := app.CapabilityKeeper.ScopeToModule(icaauthtypes.ModuleName) + +... + +// Create the Keeper for each submodule +app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + scopedICAControllerKeeper, app.MsgServiceRouter(), +) +app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.AccountKeeper, scopedICAHostKeeper, app.MsgServiceRouter(), +) +app.ICAHostKeeper.WithQueryRouter(app.GRPCQueryRouter()) + +// Create Interchain Accounts AppModule +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper) + +// Create your Interchain Accounts authentication module +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.MsgServiceRouter()) + +// ICA auth AppModule +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) + +// Create controller IBC application stack and host IBC module as desired +icaControllerStack := icacontroller.NewIBCMiddleware(nil, app.ICAControllerKeeper) +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host and authentication routes +ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icahosttypes.SubModuleName, icaHostIBCModule) +... + +// Register Interchain Accounts and authentication module AppModule's +app.moduleManager = module.NewManager( + ... + icaModule, + icaAuthModule, +) + +... + +// Add Interchain Accounts to begin blocker logic +app.moduleManager.SetOrderBeginBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add Interchain Accounts to end blocker logic +app.moduleManager.SetOrderEndBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add Interchain Accounts module InitGenesis logic +app.moduleManager.SetOrderInitGenesis( + ... + icatypes.ModuleName, + ... +) + +// initParamsKeeper init params keeper and its subspaces +func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey sdk.StoreKey) paramskeeper.Keeper { + ... + paramsKeeper.Subspace(icahosttypes.SubModuleName) + paramsKeeper.Subspace(icacontrollertypes.SubModuleName) + ... +} +``` + +If no custom athentication module is needed and a generic Cosmos SDK authentication module can be used, then from the sample integration code above all references to `ICAAuthKeeper` and `icaAuthModule` can be removed. That's it, the following code would not be needed: + +```go +// Create your Interchain Accounts authentication module +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.MsgServiceRouter()) + +// ICA auth AppModule +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) +``` + +### Using submodules exclusively + +As described above, the Interchain Accounts application module is structured to support the ability of exclusively enabling controller or host functionality. +This can be achieved by simply omitting either controller or host `Keeper` from the Interchain Accounts `NewAppModule` constructor function, and mounting only the desired submodule via the `IBCRouter`. +Alternatively, submodules can be enabled and disabled dynamically using [on-chain parameters](06-parameters.md). + +The following snippets show basic examples of statically disabling submodules using `app.go`. + +#### Disabling controller chain functionality + +```go +// Create Interchain Accounts AppModule omitting the controller keeper +icaModule := ica.NewAppModule(nil, &app.ICAHostKeeper) + +// Create host IBC Module +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host route +ibcRouter.AddRoute(icahosttypes.SubModuleName, icaHostIBCModule) +``` + +#### Disabling host chain functionality + +```go +// Create Interchain Accounts AppModule omitting the host keeper +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, nil) + + +// Optionally instantiate your custom authentication module if needed, or not otherwise +... + +// Create controller IBC application stack +icaControllerStack := icacontroller.NewIBCMiddleware(nil, app.ICAControllerKeeper) + +// Register controller route +ibcRouter.AddRoute(icacontrollertypes.SubModuleName, icaControllerStack) +``` diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/05-messages.md b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/05-messages.md new file mode 100644 index 0000000..fb9ebab --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/05-messages.md @@ -0,0 +1,153 @@ +--- +title: Messages +sidebar_label: Messages +sidebar_position: 5 +slug: /apps/interchain-accounts/messages +--- + + +# Messages + +## `MsgRegisterInterchainAccount` + +An Interchain Accounts channel handshake can be initiated using `MsgRegisterInterchainAccount`: + +```go +type MsgRegisterInterchainAccount struct { + Owner string + ConnectionID string + Version string + Ordering channeltypes.Order +} +``` + +This message is expected to fail if: + +- `Owner` is an empty string or contains more than 2048 bytes. +- `ConnectionID` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). + +This message will construct a new `MsgChannelOpenInit` on chain and route it to the core IBC message server to initiate the opening step of the channel handshake. + +The controller submodule will generate a new port identifier and claim the associated port capability. The caller is expected to provide an appropriate application version string. For example, this may be an ICS-27 JSON encoded [`Metadata`](https://github.com/cosmos/ibc-go/blob/v6.0.0/proto/ibc/applications/interchain_accounts/v1/metadata.proto#L11) type or an ICS-29 JSON encoded [`Metadata`](https://github.com/cosmos/ibc-go/blob/v6.0.0/proto/ibc/applications/fee/v1/metadata.proto#L11) type with a nested application version. +If the `Version` string is omitted, the controller submodule will construct a default version string in the `OnChanOpenInit` handshake callback. + +```go +type MsgRegisterInterchainAccountResponse struct { + ChannelID string + PortId string +} +``` + +The `ChannelID` and `PortID` are returned in the message response. + +## `MsgSendTx` + +An Interchain Accounts transaction can be executed on a remote host chain by sending a `MsgSendTx` from the corresponding controller chain: + +```go +type MsgSendTx struct { + Owner string + ConnectionID string + PacketData InterchainAccountPacketData + RelativeTimeout uint64 +} +``` + +This message is expected to fail if: + +- `Owner` is an empty string or contains more than 2048 bytes. +- `ConnectionID` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +- `PacketData` contains an `UNSPECIFIED` type enum, the length of `Data` bytes is zero or the `Memo` field exceeds 256 characters in length. +- `RelativeTimeout` is zero. + +This message will create a new IBC packet with the provided `PacketData` and send it via the channel associated with the `Owner` and `ConnectionID`. +The `PacketData` is expected to contain a list of serialized `[]sdk.Msg` in the form of `CosmosTx`. Please note the signer field of each `sdk.Msg` must be the interchain account address. +When the packet is relayed to the host chain, the `PacketData` is unmarshalled and the messages are authenticated and executed. + +```go +type MsgSendTxResponse struct { + Sequence uint64 +} +``` + +The packet `Sequence` is returned in the message response. + +### Queries + +It is possible to use [`MsgModuleQuerySafe`](https://github.com/cosmos/ibc-go/blob/eecfa5c09a4c38a5c9f2cc2a322d2286f45911da/proto/ibc/applications/interchain_accounts/host/v1/tx.proto#L41-L51) to execute a list of queries on the host chain. This message can be included in the list of encoded `sdk.Msg`s of `InterchainPacketData`. The host chain will return on the acknowledgment the responses for all the queries. Please note that only module safe queries can be executed ([deterministic queries that are safe to be called from within the state machine](https://docs.cosmos.network/main/build/building-modules/query-services#calling-queries-from-the-state-machine)). + +The queries available from Cosmos SDK are: + +```plaintext +/cosmos.auth.v1beta1.Query/Accounts +/cosmos.auth.v1beta1.Query/Account +/cosmos.auth.v1beta1.Query/AccountAddressByID +/cosmos.auth.v1beta1.Query/Params +/cosmos.auth.v1beta1.Query/ModuleAccounts +/cosmos.auth.v1beta1.Query/ModuleAccountByName +/cosmos.auth.v1beta1.Query/AccountInfo +/cosmos.bank.v1beta1.Query/Balance +/cosmos.bank.v1beta1.Query/AllBalances +/cosmos.bank.v1beta1.Query/SpendableBalances +/cosmos.bank.v1beta1.Query/SpendableBalanceByDenom +/cosmos.bank.v1beta1.Query/TotalSupply +/cosmos.bank.v1beta1.Query/SupplyOf +/cosmos.bank.v1beta1.Query/Params +/cosmos.bank.v1beta1.Query/DenomMetadata +/cosmos.bank.v1beta1.Query/DenomMetadataByQueryString +/cosmos.bank.v1beta1.Query/DenomsMetadata +/cosmos.bank.v1beta1.Query/DenomOwners +/cosmos.bank.v1beta1.Query/SendEnabled +/cosmos.circuit.v1.Query/Account +/cosmos.circuit.v1.Query/Accounts +/cosmos.circuit.v1.Query/DisabledList +/cosmos.staking.v1beta1.Query/Validators +/cosmos.staking.v1beta1.Query/Validator +/cosmos.staking.v1beta1.Query/ValidatorDelegations +/cosmos.staking.v1beta1.Query/ValidatorUnbondingDelegations +/cosmos.staking.v1beta1.Query/Delegation +/cosmos.staking.v1beta1.Query/UnbondingDelegation +/cosmos.staking.v1beta1.Query/DelegatorDelegations +/cosmos.staking.v1beta1.Query/DelegatorUnbondingDelegations +/cosmos.staking.v1beta1.Query/Redelegations +/cosmos.staking.v1beta1.Query/DelegatorValidators +/cosmos.staking.v1beta1.Query/DelegatorValidator +/cosmos.staking.v1beta1.Query/HistoricalInfo +/cosmos.staking.v1beta1.Query/Pool +/cosmos.staking.v1beta1.Query/Params +``` + +And the query available from ibc-go is: + +```plaintext +/ibc.core.client.v1.Query/VerifyMembership +``` + +The following code block shows an example of how `MsgModuleQuerySafe` can be used to query the account balance of an account on the host chain. The resulting packet data variable is used to set the `PacketData` of `MsgSendTx`. + +```go +balanceQuery := banktypes.NewQueryBalanceRequest("cosmos1...", "uatom") +queryBz, err := balanceQuery.Marshal() + +// signer of message must be the interchain account on the host +queryMsg := icahosttypes.NewMsgModuleQuerySafe("cosmos2...", []*icahosttypes.QueryRequest{ + { + Path: "/cosmos.bank.v1beta1.Query/Balance", + Data: queryBz, + }, +}) + +bz, err := icatypes.SerializeCosmosTx(cdc, []proto.Message{queryMsg}, icatypes.EncodingProtobuf) + +packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: bz, + Memo: "", +} +``` + +## Atomicity + +As the Interchain Accounts module supports the execution of multiple transactions using the Cosmos SDK `Msg` interface, it provides the same atomicity guarantees as Cosmos SDK-based applications, leveraging the [`CacheMultiStore`](https://docs.cosmos.network/main/learn/advanced/store#cachemultistore) architecture provided by the [`Context`](https://docs.cosmos.network/main/learn/advanced/context.html) type. + +This provides atomic execution of transactions when using Interchain Accounts, where state changes are only committed if all `Msg`s succeed. diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/06-parameters.md b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/06-parameters.md new file mode 100644 index 0000000..5004118 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/06-parameters.md @@ -0,0 +1,65 @@ +--- +title: Parameters +sidebar_label: Parameters +sidebar_position: 6 +slug: /apps/interchain-accounts/parameters +--- + + +# Parameters + +The Interchain Accounts module contains the following on-chain parameters, logically separated for each distinct submodule: + +## Controller Submodule Parameters + +| Name | Type | Default Value | +|------------------------|------|---------------| +| `ControllerEnabled` | bool | `true` | + +### ControllerEnabled + +The `ControllerEnabled` parameter controls a chains ability to service ICS-27 controller specific logic. This includes the sending of Interchain Accounts packet data as well as the following ICS-26 callback handlers: + +- `OnChanOpenInit` +- `OnChanOpenAck` +- `OnChanCloseConfirm` +- `OnAcknowledgementPacket` +- `OnTimeoutPacket` + +## Host Submodule Parameters + +| Name | Type | Default Value | +|------------------------|----------|---------------| +| `HostEnabled` | bool | `true` | +| `AllowMessages` | []string | `["*"]` | + +### HostEnabled + +The `HostEnabled` parameter controls a chains ability to service ICS-27 host specific logic. This includes the following ICS-26 callback handlers: + +- `OnChanOpenTry` +- `OnChanOpenConfirm` +- `OnChanCloseConfirm` +- `OnRecvPacket` + +### AllowMessages + +The `AllowMessages` parameter provides the ability for a chain to limit the types of messages or transactions that hosted interchain accounts are authorized to execute by defining an allowlist using the Protobuf message type URL format. + +For example, a Cosmos SDK-based chain that elects to provide hosted Interchain Accounts with the ability of governance voting and staking delegations will define its parameters as follows: + +```json +"params": { + "host_enabled": true, + "allow_messages": ["/cosmos.staking.v1beta1.MsgDelegate", "/cosmos.gov.v1beta1.MsgVote"] +} +``` + +There is also a special wildcard `"*"` value which allows any type of message to be executed by the interchain account. This must be the only value in the `allow_messages` array. + +```json +"params": { + "host_enabled": true, + "allow_messages": ["*"] +} +``` diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/07-tx-encoding.md b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/07-tx-encoding.md new file mode 100644 index 0000000..c30ee07 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/07-tx-encoding.md @@ -0,0 +1,58 @@ +--- +title: Transaction Encoding +sidebar_label: Transaction Encoding +sidebar_position: 7 +slug: /apps/interchain-accounts/tx-encoding +--- + +# Transaction Encoding + +When orchestrating an interchain account transaction, which comprises multiple `sdk.Msg` objects represented as `Any` types, the transactions must be encoded as bytes within [`InterchainAccountPacketData`](https://github.com/cosmos/ibc-go/blob/v7.2.0/proto/ibc/applications/interchain_accounts/v1/packet.proto#L21-L26). + +```protobuf +// InterchainAccountPacketData is comprised of a raw transaction, type of transaction and optional memo field. +message InterchainAccountPacketData { + Type type = 1; + bytes data = 2; + string memo = 3; +} +``` + +The `data` field must be encoded as a [`CosmosTx`](https://github.com/cosmos/ibc-go/blob/v7.2.0/proto/ibc/applications/interchain_accounts/v1/packet.proto#L28-L31). + +```protobuf +// CosmosTx contains a list of sdk.Msg's. It should be used when sending transactions to an SDK host chain. +message CosmosTx { + repeated google.protobuf.Any messages = 1; +} +``` + +The encoding method for `CosmosTx` is determined during the channel handshake process. If the channel version [metadata's `encoding` field](https://github.com/cosmos/ibc-go/blob/v7.2.0/proto/ibc/applications/interchain_accounts/v1/metadata.proto#L22) is marked as `proto3`, then `CosmosTx` undergoes protobuf encoding. Conversely, if the field is set to `proto3json`, then [proto3 json](https://protobuf.dev/programming-guides/proto3/#json) encoding takes place, which generates a JSON representation of the protobuf message. + +## Protobuf Encoding + +Protobuf encoding serves as the standard encoding process for `CosmosTx`. This occurs if the channel handshake initiates with an empty channel version metadata or if the `encoding` field explicitly denotes `proto3`. In Golang, the protobuf encoding procedure utilizes the `proto.Marshal` function. Every protobuf autogenerated Golang type comes equipped with a `Marshal` method that can be employed to encode the message. + +## (Protobuf) JSON Encoding + +The proto3 JSON encoding presents an alternative encoding technique for `CosmosTx`. It is selected if the channel handshake begins with the channel version metadata `encoding` field labeled as `proto3json`. In Golang, the Proto3 canonical encoding in JSON is implemented by the `"github.com/cosmos/gogoproto/jsonpb"` package. Within Cosmos SDK, the `ProtoCodec` structure implements the `JSONCodec` interface, leveraging the `jsonpb` package. This method generates a JSON format as follows: + +```json +{ + "messages": [ + { + "@type": "/cosmos.bank.v1beta1.MsgSend", + "from_address": "cosmos1...", + "to_address": "cosmos1...", + "amount": [ + { + "denom": "uatom", + "amount": "1000000" + } + ] + } + ] +} +``` + +Here, the `"messages"` array is populated with transactions. Each transaction is represented as a JSON object with the `@type` field denoting the transaction type and the remaining fields representing the transaction's attributes. diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/08-client.md b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/08-client.md new file mode 100644 index 0000000..079a97a --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/08-client.md @@ -0,0 +1,202 @@ +--- +title: Client +sidebar_label: Client +sidebar_position: 8 +slug: /apps/interchain-accounts/client +--- + +# Client + +## CLI + +A user can query and interact with the Interchain Accounts module using the CLI. Use the `--help` flag to discover the available commands: + +```shell +simd query interchain-accounts --help +``` + +> Please not that this section does not document all the available commands, but only the ones that deserved extra documentation that was not possible to fit in the command line documentation. + +### Controller + +A user can query and interact with the controller submodule. + +#### Query + +The `query` commands allow users to query the controller submodule. + +```shell +simd query interchain-accounts controller --help +``` + +#### Transactions + +The `tx` commands allow users to interact with the controller submodule. + +```shell +simd tx interchain-accounts controller --help +``` + +#### `register` + +The `register` command allows users to register an interchain account on a host chain on the provided connection. + +```shell +simd tx interchain-accounts controller register [connection-id] [flags] +``` + +During registration a new channel is set up between controller and host. There are two flags available that influence the channel that is created: + +- `--version` to specify the (JSON-formatted) version string of the channel. For example: `{\"version\":\"ics27-1\",\"encoding\":\"proto3\",\"tx_type\":\"sdk_multi_msg\",\"controller_connection_id\":\"connection-0\",\"host_connection_id\":\"connection-0\"}`. Passing a custom version string is useful if you want to specify, for example, the encoding format of the interchain accounts packet data (either `proto3` or `proto3json`). If not specified the controller submodule will generate a default version string. +- `--ordering` to specify the ordering of the channel. Available options are `order_ordered` and `order_unordered` (default if not specified). + +Example: + +```shell +simd tx interchain-accounts controller register connection-0 --ordering order_ordered --from cosmos1.. +``` + +#### `send-tx` + +The `send-tx` command allows users to send a transaction on the provided connection to be executed using an interchain account on the host chain. + +```shell +simd tx interchain-accounts controller send-tx [connection-id] [path/to/packet_msg.json] +``` + +Example: + +```shell +simd tx interchain-accounts controller send-tx connection-0 packet-data.json --from cosmos1.. +``` + +See below for example contents of `packet-data.json`. The CLI handler will unmarshal the following into `InterchainAccountPacketData` appropriately. + +```json +{ + "type":"TYPE_EXECUTE_TX", + "data":"CqIBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEoEBCkFjb3Ntb3MxNWNjc2hobXAwZ3N4MjlxcHFxNmc0em1sdG5udmdteXU5dWV1YWRoOXkybmM1emowc3psczVndGRkehItY29zbW9zMTBoOXN0YzV2Nm50Z2V5Z2Y1eGY5NDVuanFxNWgzMnI1M3VxdXZ3Gg0KBXN0YWtlEgQxMDAw", + "memo":"" +} +``` + +Note the `data` field is a base64 encoded byte string as per the tx encoding agreed upon during the channel handshake. + +A helper CLI is provided in the host submodule which can be used to generate the packet data JSON using the counterparty chain's binary. See the [`generate-packet-data` command](#generate-packet-data) for an example. + +### Host + +A user can query and interact with the host submodule. + +#### Query + +The `query` commands allow users to query the host submodule. + +```shell +simd query interchain-accounts host --help +``` + +#### Transactions + +The `tx` commands allow users to interact with the controller submodule. + +```shell +simd tx interchain-accounts host --help +``` + +##### `generate-packet-data` + +The `generate-packet-data` command allows users to generate protobuf or proto3 JSON encoded interchain accounts packet data for input message(s). The packet data can then be used with the controller submodule's [`send-tx` command](#send-tx). The `--encoding` flag can be used to specify the encoding format (value must be either `proto3` or `proto3json`); if not specified, the default will be `proto3`. The `--memo` flag can be used to include a memo string in the interchain accounts packet data. + +```shell +simd tx interchain-accounts host generate-packet-data [message] +``` + +Example: + +```shell +simd tx interchain-accounts host generate-packet-data '[{ + "@type":"/cosmos.bank.v1beta1.MsgSend", + "from_address":"cosmos15ccshhmp0gsx29qpqq6g4zmltnnvgmyu9ueuadh9y2nc5zj0szls5gtddz", + "to_address":"cosmos10h9stc5v6ntgeygf5xf945njqq5h32r53uquvw", + "amount": [ + { + "denom": "stake", + "amount": "1000" + } + ] +}]' --memo memo +``` + +The command accepts a single `sdk.Msg` or a list of `sdk.Msg`s that will be encoded into the outputs `data` field. + +Example output: + +```json +{ + "type":"TYPE_EXECUTE_TX", + "data":"CqIBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEoEBCkFjb3Ntb3MxNWNjc2hobXAwZ3N4MjlxcHFxNmc0em1sdG5udmdteXU5dWV1YWRoOXkybmM1emowc3psczVndGRkehItY29zbW9zMTBoOXN0YzV2Nm50Z2V5Z2Y1eGY5NDVuanFxNWgzMnI1M3VxdXZ3Gg0KBXN0YWtlEgQxMDAw", + "memo":"memo" +} +``` + +## gRPC + +A user can query the interchain account module using gRPC endpoints. + +### Controller + +A user can query the controller submodule using gRPC endpoints. + +#### `InterchainAccount` + +The `InterchainAccount` endpoint allows users to query the controller submodule for the interchain account address for a given owner on a particular connection. + +```shell +ibc.applications.interchain_accounts.controller.v1.Query/InterchainAccount +``` + +Example: + +```shell +grpcurl -plaintext \ + -d '{"owner":"cosmos1..","connection_id":"connection-0"}' \ + localhost:9090 \ + ibc.applications.interchain_accounts.controller.v1.Query/InterchainAccount +``` + +#### `Params` + +The `Params` endpoint users to query the current controller submodule parameters. + +```shell +ibc.applications.interchain_accounts.controller.v1.Query/Params +``` + +Example: + +```shell +grpcurl -plaintext \ + localhost:9090 \ + ibc.applications.interchain_accounts.controller.v1.Query/Params +``` + +### Host + +A user can query the host submodule using gRPC endpoints. + +#### `Params` + +The `Params` endpoint users to query the current host submodule parameters. + +```shell +ibc.applications.interchain_accounts.host.v1.Query/Params +``` + +Example: + +```shell +grpcurl -plaintext \ + localhost:9090 \ + ibc.applications.interchain_accounts.host.v1.Query/Params +``` diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/09-active-channels.md b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/09-active-channels.md new file mode 100644 index 0000000..21ac578 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/09-active-channels.md @@ -0,0 +1,45 @@ +--- +title: Active Channels +sidebar_label: Active Channels +sidebar_position: 9 +slug: /apps/interchain-accounts/active-channels +--- + +# Understanding Active Channels + +The Interchain Accounts module uses either [ORDERED or UNORDERED](https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#ordering) channels. + +When using `ORDERED` channels, the order of transactions when sending packets from a controller to a host chain is maintained. + +When using `UNORDERED` channels, there is no guarantee that the order of transactions when sending packets from the controller to the host chain is maintained. Since ibc-go v8.3.0, the default ordering for new ICA channels is `UNORDERED`, if no ordering is specified in `MsgRegisterInterchainAccount` (previously the default ordering was `ORDERED`). + +> A limitation when using ORDERED channels is that when a packet times out the channel will be closed. + +In the case of a channel closing, a controller chain needs to be able to regain access to the interchain account registered on this channel. `Active Channels` enable this functionality. + +When an Interchain Account is registered using `MsgRegisterInterchainAccount`, a new channel is created on a particular port. During the `OnChanOpenAck` and `OnChanOpenConfirm` steps (on controller & host chain respectively) the `Active Channel` for this interchain account is stored in state. + +It is possible to create a new channel using the same controller chain portID if the previously set `Active Channel` is now in a `CLOSED` state. This channel creation can be initialized programmatically by sending a new `MsgChannelOpenInit` message like so: + +```go +msg := channeltypes.NewMsgChannelOpenInit(portID, string(versionBytes), channeltypes.ORDERED, []string{connectionID}, icatypes.HostPortID, authtypes.NewModuleAddress(icatypes.ModuleName).String()) +handler := keeper.msgRouter.Handler(msg) +res, err := handler(ctx, msg) +if err != nil { + return err +} +``` + +Alternatively, any relayer operator may initiate a new channel handshake for this interchain account once the previously set `Active Channel` is in a `CLOSED` state. This is done by initiating the channel handshake on the controller chain using the same portID associated with the interchain account in question. + +It is important to note that once a channel has been opened for a given interchain account, new channels can not be opened for this account until the currently set `Active Channel` is set to `CLOSED`. + +## Future improvements + +Future versions of the ICS-27 protocol and the Interchain Accounts module will likely use a new channel type that provides ordering of packets without the channel closing in the event of a packet timing out, thus removing the need for `Active Channels` entirely. +The following is a list of issues which will provide the infrastructure to make this possible: + +- [IBC Channel Upgrades](https://github.com/cosmos/ibc-go/issues/1599) +- [Implement ORDERED_ALLOW_TIMEOUT logic in 04-channel](https://github.com/cosmos/ibc-go/issues/1661) +- [Add ORDERED_ALLOW_TIMEOUT as supported ordering in 03-connection](https://github.com/cosmos/ibc-go/issues/1662) +- [Allow ICA channels to be opened as ORDERED_ALLOW_TIMEOUT](https://github.com/cosmos/ibc-go/issues/1663) diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/10-legacy/01-auth-modules.md b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/10-legacy/01-auth-modules.md new file mode 100644 index 0000000..2c8f5db --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/10-legacy/01-auth-modules.md @@ -0,0 +1,274 @@ +--- +title: Authentication Modules +sidebar_label: Authentication Modules +sidebar_position: 1 +slug: /apps/interchain-accounts/legacy/auth-modules +--- + + +# Building an authentication module + +## Deprecation Notice + +**This document is deprecated and will be removed in future releases**. + +:::note Synopsis +Authentication modules play the role of the `Base Application` as described in [ICS-30 IBC Middleware](https://github.com/cosmos/ibc/tree/master/spec/app/ics-030-middleware), and enable application developers to perform custom logic when working with the Interchain Accounts controller API. +::: + +The controller submodule is used for account registration and packet sending. It executes only logic required of all controllers of interchain accounts. The type of authentication used to manage the interchain accounts remains unspecified. There may exist many different types of authentication which are desirable for different use cases. Thus the purpose of the authentication module is to wrap the controller submodule with custom authentication logic. + +In ibc-go, authentication modules are connected to the controller chain via a middleware stack. The controller submodule is implemented as [middleware](https://github.com/cosmos/ibc/tree/master/spec/app/ics-030-middleware) and the authentication module is connected to the controller submodule as the base application of the middleware stack. To implement an authentication module, the `IBCModule` interface must be fulfilled. By implementing the controller submodule as middleware, any amount of authentication modules can be created and connected to the controller submodule without writing redundant code. + +The authentication module must: + +- Authenticate interchain account owners. +- Track the associated interchain account address for an owner. +- Send packets on behalf of an owner (after authentication). + +> Please note that since ibc-go v6 the channel capability is claimed by the controller submodule and therefore it is not required for authentication modules to claim the capability in the `OnChanOpenInit` callback. When the authentication module sends packets on the channel created for the associated interchain account it can pass a `nil` capability to the legacy function `SendTx` of the controller keeper (see section [`SendTx`](03-keeper-api.md#sendtx) for more information). + +## `IBCModule` implementation + +The following `IBCModule` callbacks must be implemented with appropriate custom logic: + +```go +// OnChanOpenInit implements the IBCModule interface +func (im IBCModule) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + // since ibc-go v6 the authentication module *must not* claim the channel capability on OnChanOpenInit + + // perform custom logic + + return version, nil +} + +// OnChanOpenAck implements the IBCModule interface +func (im IBCModule) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyVersion string, +) error { + // perform custom logic + + return nil +} + +// OnChanCloseConfirm implements the IBCModule interface +func (im IBCModule) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + // perform custom logic + + return nil +} + +// OnAcknowledgementPacket implements the IBCModule interface +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + // perform custom logic + + return nil +} + +// OnTimeoutPacket implements the IBCModule interface. +func (im IBCModule) OnTimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + // perform custom logic + + return nil +} +``` + +The following functions must be defined to fulfill the `IBCModule` interface, but they will never be called by the controller submodule so they may error or panic. That is because in Interchain Accounts, the channel handshake is always initiated on the controller chain and packets are always sent to the host chain and never to the controller chain. + +```go +// OnChanOpenTry implements the IBCModule interface +func (im IBCModule) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + chanCap *capabilitytypes.Capability, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + panic("UNIMPLEMENTED") +} + +// OnChanOpenConfirm implements the IBCModule interface +func (im IBCModule) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + panic("UNIMPLEMENTED") +} + +// OnChanCloseInit implements the IBCModule interface +func (im IBCModule) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + panic("UNIMPLEMENTED") +} + +// OnRecvPacket implements the IBCModule interface. A successful acknowledgement +// is returned if the packet data is successfully decoded and the receive application +// logic returns without error. +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) ibcexported.Acknowledgement { + panic("UNIMPLEMENTED") +} +``` + +## `OnAcknowledgementPacket` + +Controller chains will be able to access the acknowledgement written into the host chain state once a relayer relays the acknowledgement. +The acknowledgement bytes contain either the response of the execution of the message(s) on the host chain or an error. They will be passed to the auth module via the `OnAcknowledgementPacket` callback. Auth modules are expected to know how to decode the acknowledgement. + +If the controller chain is connected to a host chain using the host module on ibc-go, it may interpret the acknowledgement bytes as follows: + +Begin by unmarshaling the acknowledgement into `sdk.TxMsgData`: + +```go +var ack channeltypes.Acknowledgement +if err := channeltypes.SubModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { + return err +} + +txMsgData := &sdk.TxMsgData{} +if err := proto.Unmarshal(ack.GetResult(), txMsgData); err != nil { + return err +} +``` + +If the `txMsgData.Data` field is non nil, the host chain is using SDK version \<\= v0.45. +The auth module should interpret the `txMsgData.Data` as follows: + +```go +switch len(txMsgData.Data) { +case 0: + // see documentation below for SDK 0.46.x or greater +default: + for _, msgData := range txMsgData.Data { + if err := handler(msgData); err != nil { + return err + } + } +... +} +``` + +A handler will be needed to interpret what actions to perform based on the message type sent. +A router could be used, or more simply a switch statement. + +```go +func handler(msgData sdk.MsgData) error { +switch msgData.MsgType { +case sdk.MsgTypeURL(&banktypes.MsgSend{}): + msgResponse := &banktypes.MsgSendResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleBankSendMsg(msgResponse) + +case sdk.MsgTypeURL(&stakingtypes.MsgDelegate{}): + msgResponse := &stakingtypes.MsgDelegateResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleStakingDelegateMsg(msgResponse) + +case sdk.MsgTypeURL(&transfertypes.MsgTransfer{}): + msgResponse := &transfertypes.MsgTransferResponse{} + if err := proto.Unmarshal(msgData.Data, msgResponse}; err != nil { + return err + } + + handleIBCTransferMsg(msgResponse) + +default: + return +} +``` + +If the `txMsgData.Data` is empty, the host chain is using SDK version > v0.45. +The auth module should interpret the `txMsgData.Responses` as follows: + +```go +... +// switch statement from above +case 0: + for _, any := range txMsgData.MsgResponses { + if err := handleAny(any); err != nil { + return err + } + } +} +``` + +A handler will be needed to interpret what actions to perform based on the type URL of the Any. +A router could be used, or more simply a switch statement. +It may be possible to deduplicate logic between `handler` and `handleAny`. + +```go +func handleAny(any *codectypes.Any) error { +switch any.TypeURL { +case banktypes.MsgSend: + msgResponse, err := unpackBankMsgSendResponse(any) + if err != nil { + return err + } + + handleBankSendMsg(msgResponse) + +case stakingtypes.MsgDelegate: + msgResponse, err := unpackStakingDelegateResponse(any) + if err != nil { + return err + } + + handleStakingDelegateMsg(msgResponse) + + case transfertypes.MsgTransfer: + msgResponse, err := unpackIBCTransferMsgResponse(any) + if err != nil { + return err + } + + handleIBCTransferMsg(msgResponse) + +default: + return +} +``` + +## Integration into `app.go` file + +To integrate the authentication module into your chain, please follow the steps outlined in [`app.go` integration](02-integration.md#example-integration). diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/10-legacy/02-integration.md b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/10-legacy/02-integration.md new file mode 100644 index 0000000..60f3f8a --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/10-legacy/02-integration.md @@ -0,0 +1,203 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /apps/interchain-accounts/legacy/integration +--- + + +# Integration + +## Deprecation Notice + +**This document is deprecated and will be removed in future releases**. + +:::note Synopsis +Learn how to integrate Interchain Accounts host and controller functionality to your chain. The following document only applies for Cosmos SDK chains. +::: + +The Interchain Accounts module contains two submodules. Each submodule has its own IBC application. The Interchain Accounts module should be registered as an `AppModule` in the same way all SDK modules are registered on a chain, but each submodule should create its own `IBCModule` as necessary. A route should be added to the IBC router for each submodule which will be used. + +Chains who wish to support ICS-27 may elect to act as a host chain, a controller chain or both. Disabling host or controller functionality may be done statically by excluding the host or controller module entirely from the `app.go` file or it may be done dynamically by taking advantage of the on-chain parameters which enable or disable the host or controller submodules. + +Interchain Account authentication modules are the base application of a middleware stack. The controller submodule is the middleware in this stack. + +![ica-pre-v6.png](./images/ica-pre-v6.png) + +> Please note that since ibc-go v6 the channel capability is claimed by the controller submodule and therefore it is not required for authentication modules to claim the capability in the `OnChanOpenInit` callback. Therefore the custom authentication module does not need a scoped keeper anymore. +> Please note that since ibc-go v8.3.0 it is mandatory to register the gRPC query router after the creation of the host submodule's keeper; otherwise, nodes will not start. The query router is used to execute on the host query messages encoded in the ICA packet data. Please check the sample integration code below for more details. + +## Example integration + +```go +// app.go + +// Register the AppModule for the Interchain Accounts module and the authentication module +// Note: No `icaauth` exists, this must be substituted with an actual Interchain Accounts authentication module +ModuleBasics = module.NewBasicManager( + ... + ica.AppModuleBasic{}, + icaauth.AppModuleBasic{}, + ... +) + +... + +// Add module account permissions for the Interchain Accounts module +// Only necessary for host chain functionality +// Each Interchain Account created on the host chain is derived from the module account created +maccPerms = map[string][]string{ + ... + icatypes.ModuleName: nil, +} + +... + +// Add Interchain Accounts Keepers for each submodule used and the authentication module +// If a submodule is being statically disabled, the associated Keeper does not need to be added. +type App struct { + ... + + ICAControllerKeeper icacontrollerkeeper.Keeper + ICAHostKeeper icahostkeeper.Keeper + ICAAuthKeeper icaauthkeeper.Keeper + + ... +} + +... + +// Create store keys for each submodule Keeper and the authentication module +keys := sdk.NewKVStoreKeys( + ... + icacontrollertypes.StoreKey, + icahosttypes.StoreKey, + icaauthtypes.StoreKey, + ... +) + +... + +// Create the scoped keepers for each submodule keeper and authentication keeper +scopedICAControllerKeeper := app.CapabilityKeeper.ScopeToModule(icacontrollertypes.SubModuleName) +scopedICAHostKeeper := app.CapabilityKeeper.ScopeToModule(icahosttypes.SubModuleName) + +... + +// Create the Keeper for each submodule +app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + scopedICAControllerKeeper, app.MsgServiceRouter(), +) +app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, // may be replaced with middleware such as ics29 fee + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.AccountKeeper, scopedICAHostKeeper, app.MsgServiceRouter(), +) +app.ICAHostKeeper.WithQueryRouter(app.GRPCQueryRouter()) + +// Create Interchain Accounts AppModule +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper) + +// Create your Interchain Accounts authentication module +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.ICAControllerKeeper) + +// ICA auth AppModule +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) + +// ICA auth IBC Module +icaAuthIBCModule := icaauth.NewIBCModule(app.ICAAuthKeeper) + +// Create controller IBC application stack and host IBC module as desired +icaControllerStack := icacontroller.NewIBCMiddleware(icaAuthIBCModule, app.ICAControllerKeeper) +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host and authentication routes +ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icahosttypes.SubModuleName, icaHostIBCModule). + AddRoute(icaauthtypes.ModuleName, icaControllerStack) // Note, the authentication module is routed to the top level of the middleware stack + +... + +// Register Interchain Accounts and authentication module AppModule's +app.moduleManager = module.NewManager( + ... + icaModule, + icaAuthModule, +) + +... + +// Add fee middleware to begin blocker logic +app.moduleManager.SetOrderBeginBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add fee middleware to end blocker logic +app.moduleManager.SetOrderEndBlockers( + ... + icatypes.ModuleName, + ... +) + +// Add Interchain Accounts module InitGenesis logic +app.moduleManager.SetOrderInitGenesis( + ... + icatypes.ModuleName, + ... +) + +// initParamsKeeper init params keeper and its subspaces +func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey sdk.StoreKey) paramskeeper.Keeper { + ... + paramsKeeper.Subspace(icahosttypes.SubModuleName) + paramsKeeper.Subspace(icacontrollertypes.SubModuleName) + ... +``` + +## Using submodules exclusively + +As described above, the Interchain Accounts application module is structured to support the ability of exclusively enabling controller or host functionality. +This can be achieved by simply omitting either controller or host `Keeper` from the Interchain Accounts `NewAppModule` constructor function, and mounting only the desired submodule via the `IBCRouter`. +Alternatively, submodules can be enabled and disabled dynamically using [on-chain parameters](../06-parameters.md). + +The following snippets show basic examples of statically disabling submodules using `app.go`. + +### Disabling controller chain functionality + +```go +// Create Interchain Accounts AppModule omitting the controller keeper +icaModule := ica.NewAppModule(nil, &app.ICAHostKeeper) + +// Create host IBC Module +icaHostIBCModule := icahost.NewIBCModule(app.ICAHostKeeper) + +// Register host route +ibcRouter.AddRoute(icahosttypes.SubModuleName, icaHostIBCModule) +``` + +### Disabling host chain functionality + +```go +// Create Interchain Accounts AppModule omitting the host keeper +icaModule := ica.NewAppModule(&app.ICAControllerKeeper, nil) + +// Create your Interchain Accounts authentication module, setting up the Keeper, AppModule and IBCModule appropriately +app.ICAAuthKeeper = icaauthkeeper.NewKeeper(appCodec, keys[icaauthtypes.StoreKey], app.ICAControllerKeeper) +icaAuthModule := icaauth.NewAppModule(appCodec, app.ICAAuthKeeper) +icaAuthIBCModule := icaauth.NewIBCModule(app.ICAAuthKeeper) + +// Create controller IBC application stack +icaControllerStack := icacontroller.NewIBCMiddleware(icaAuthIBCModule, app.ICAControllerKeeper) + +// Register controller and authentication routes +ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icaauthtypes.ModuleName, icaControllerStack) // Note, the authentication module is routed to the top level of the middleware stack +``` diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/10-legacy/03-keeper-api.md b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/10-legacy/03-keeper-api.md new file mode 100644 index 0000000..5767a7f --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/10-legacy/03-keeper-api.md @@ -0,0 +1,127 @@ +--- +title: Keeper API +sidebar_label: Keeper API +sidebar_position: 3 +slug: /apps/interchain-accounts/legacy/keeper-api +--- + + +# Keeper API + +## Deprecation Notice + +**This document is deprecated and will be removed in future releases**. + +The controller submodule keeper exposes two legacy functions that allow respectively for custom authentication modules to register interchain accounts and send packets to the interchain account. + +## `RegisterInterchainAccount` + +The authentication module can begin registering interchain accounts by calling `RegisterInterchainAccount`: + +```go +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, connectionID, owner.String(), version); err != nil { + return err +} + +return nil +``` + +The `version` argument is used to support ICS-29 fee middleware for relayer incentivization of ICS-27 packets. Consumers of the `RegisterInterchainAccount` are expected to build the appropriate JSON encoded version string themselves and pass it accordingly. If an empty string is passed in the `version` argument, then the version will be initialized to a default value in the `OnChanOpenInit` callback of the controller's handler, so that channel handshake can proceed. + +The following code snippet illustrates how to construct an appropriate interchain accounts `Metadata` and encode it as a JSON bytestring: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, controllerConnectionID, owner.String(), string(appVersion)); err != nil { + return err +} +``` + +Similarly, if the application stack is configured to route through ICS-29 fee middleware and a fee enabled channel is desired, construct the appropriate ICS-29 `Metadata` type: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +feeMetadata := feetypes.Metadata{ + AppVersion: string(appVersion), + FeeVersion: feetypes.Version, +} + +feeEnabledVersion, err := feetypes.ModuleCdc.MarshalJSON(&feeMetadata) +if err != nil { + return err +} + +if err := keeper.icaControllerKeeper.RegisterInterchainAccount(ctx, controllerConnectionID, owner.String(), string(feeEnabledVersion)); err != nil { + return err +} +``` + +> Since ibc-go v8.3.0 the default ordering of new ICA channels created when invoking `RegisterInterchainAccount` has changed from `ORDERED` to `UNORDERED`. If this default behaviour does not meet your use case, please use the function `RegisterInterchainAccountWithOrdering` (available since ibc-go v8.3.0), which takes an extra parameter that can be used to specify the ordering of the channel. + +## `SendTx` + +The authentication module can attempt to send a packet by calling `SendTx`: + +```go +// Authenticate owner +// perform custom logic + +// Construct controller portID based on interchain account owner address +portID, err := icatypes.NewControllerPortID(owner.String()) +if err != nil { + return err +} + +// Obtain data to be sent to the host chain. +// In this example, the owner of the interchain account would like to send a bank MsgSend to the host chain. +// The appropriate serialization function should be called. The host chain must be able to deserialize the transaction. +// If the host chain is using the ibc-go host module, `SerializeCosmosTx` should be used. +msg := &banktypes.MsgSend{FromAddress: fromAddr, ToAddress: toAddr, Amount: amt} +data, err := icatypes.SerializeCosmosTx(keeper.cdc, []proto.Message{msg}) +if err != nil { + return err +} + +// Construct packet data +packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, +} + +// Obtain timeout timestamp +// An appropriate timeout timestamp must be determined based on the usage of the interchain account. +// If the packet times out, the channel will be closed requiring a new channel to be created. +timeoutTimestamp := obtainTimeoutTimestamp() + +// Send the interchain accounts packet, returning the packet sequence +// A nil channel capability can be passed, since the controller submodule (and not the authentication module) +// claims the channel capability since ibc-go v6. +seq, err = keeper.icaControllerKeeper.SendTx(ctx, nil, portID, packetData, timeoutTimestamp) +``` + +The data within an `InterchainAccountPacketData` must be serialized using a format supported by the host chain. +If the host chain is using the ibc-go host chain submodule, `SerializeCosmosTx` should be used. If the `InterchainAccountPacketData.Data` is serialized using a format not supported by the host chain, the packet will not be successfully received. diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/10-legacy/_category_.json b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/10-legacy/_category_.json new file mode 100644 index 0000000..da34288 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/10-legacy/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Legacy", + "position": 10, + "link": null +} diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/10-legacy/images/ica-pre-v6.png b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/10-legacy/images/ica-pre-v6.png new file mode 100644 index 0000000..4529b23 Binary files /dev/null and b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/10-legacy/images/ica-pre-v6.png differ diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/_category_.json b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/_category_.json new file mode 100644 index 0000000..41e3ac2 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Interchain Accounts", + "position": 2, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/images/ica-v6.png b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/images/ica-v6.png new file mode 100644 index 0000000..abe3eba Binary files /dev/null and b/docs/versioned_docs/version-v8.5.x/02-apps/02-interchain-accounts/images/ica-v6.png differ diff --git a/docs/versioned_docs/version-v8.5.x/02-apps/_category_.json b/docs/versioned_docs/version-v8.5.x/02-apps/_category_.json new file mode 100644 index 0000000..83a389b --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/02-apps/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "IBC Application Modules", + "position": 2, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/01-overview.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/01-overview.md new file mode 100644 index 0000000..9c6cc5d --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/01-overview.md @@ -0,0 +1,79 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /ibc/light-clients/overview +--- + +# Overview + +:::note Synopsis +Learn how to build IBC light client modules and fulfill the interfaces required to integrate with core IBC. +::: + +:::note + +## Pre-requisite readings + +- [IBC Overview](../../01-ibc/01-overview.md) +- [IBC Transport, Authentication, and Ordering Layer - Clients](https://tutorials.cosmos.network/academy/3-ibc/4-clients.html) +- [ICS-002 Client Semantics](https://github.com/cosmos/ibc/tree/main/spec/core/ics-002-client-semantics) + +::: + +IBC uses light clients in order to provide trust-minimized interoperability between sovereign blockchains. Light clients operate under a strict set of rules which provide security guarantees for state updates and facilitate the ability to verify the state of a remote blockchain using merkle proofs. + +The following aims to provide a high level IBC light client module developer guide. Access to IBC light clients is gated by the core IBC `MsgServer` which utilizes the abstractions set by the `02-client` submodule to call into a light client module. A light client module developer is only required to implement a set interfaces as defined in the `modules/core/exported` package of ibc-go. + +A light client module developer should be concerned with three main interfaces: + +- [`ClientState`](#clientstate) encapsulates the light client implementation and its semantics. +- [`ConsensusState`](#consensusstate) tracks consensus data used for verification of client updates, misbehaviour detection and proof verification of counterparty state. +- [`ClientMessage`](#clientmessage) used for submitting block headers for client updates and submission of misbehaviour evidence using conflicting headers. + +Throughout this guide the `07-tendermint` light client module may be referred to as a reference example. + +## Concepts and vocabulary + +### `ClientState` + +`ClientState` is a term used to define the data structure which encapsulates opaque light client state. The `ClientState` contains all the information needed to verify a `ClientMessage` and perform membership and non-membership proof verification of counterparty state. This includes properties that refer to the remote state machine, the light client type and the specific light client instance. + +For example: + +- Constraints used for client updates. +- Constraints used for misbehaviour detection. +- Constraints used for state verification. +- Constraints used for client upgrades. + +The `ClientState` type maintained within the light client module *must* implement the [`ClientState`](https://github.com/cosmos/ibc-go/tree/02-client-refactor-beta1/modules/core/exported/client.go#L36) interface defined in `core/modules/exported/client.go`. +The methods which make up this interface are detailed at a more granular level in the [ClientState section of this guide](02-client-state.md). + +Please refer to the `07-tendermint` light client module's [`ClientState` definition](https://github.com/cosmos/ibc-go/tree/02-client-refactor-beta1/proto/ibc/lightclients/tendermint/v1/tendermint.proto#L18) containing information such as chain ID, status, latest height, unbonding period and proof specifications. + +### `ConsensusState` + +`ConsensusState` is a term used to define the data structure which encapsulates consensus data at a particular point in time, i.e. a unique height or sequence number of a state machine. There must exist a single trusted `ConsensusState` for each height. `ConsensusState` generally contains a trusted root, validator set information and timestamp. + +For example, the `ConsensusState` of the `07-tendermint` light client module defines a trusted root which is used by the `ClientState` to perform verification of membership and non-membership commitment proofs, as well as the next validator set hash used for verifying headers can be trusted in client updates. + +The `ConsensusState` type maintained within the light client module *must* implement the [`ConsensusState`](https://github.com/cosmos/ibc-go/tree/02-client-refactor-beta1/modules/core/exported/client.go#L134) interface defined in `modules/core/exported/client.go`. +The methods which make up this interface are detailed at a more granular level in the [`ConsensusState` section of this guide](03-consensus-state.md). + +### `Height` + +`Height` defines a monotonically increasing sequence number which provides ordering of consensus state data persisted through client updates. +IBC light client module developers are expected to use the [concrete type](https://github.com/cosmos/ibc-go/tree/02-client-refactor-beta1/proto/ibc/core/client/v1/client.proto#L89) provided by the `02-client` submodule. This implements the expectations required by the [`Height`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L156) interface defined in `modules/core/exported/client.go`. + +### `ClientMessage` + +`ClientMessage` refers to the interface type [`ClientMessage`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L147) used for performing updates to a `ClientState` stored on chain. +This may be any concrete type which produces a change in state to the IBC client when verified. + +The following are considered as valid update scenarios: + +- A block header which when verified inserts a new `ConsensusState` at a unique height. +- A batch of block headers which when verified inserts `N` `ConsensusState` instances for `N` unique heights. +- Evidence of misbehaviour provided by two conflicting block headers. + +Learn more in the [Handling update and misbehaviour](04-updates-and-misbehaviour.md) section. diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/02-client-state.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/02-client-state.md new file mode 100644 index 0000000..f117b58 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/02-client-state.md @@ -0,0 +1,79 @@ +--- +title: Client State interface +sidebar_label: Client State interface +sidebar_position: 2 +slug: /ibc/light-clients/client-state +--- + + +# Implementing the `ClientState` interface + +Learn how to implement the [`ClientState`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L36) interface. This list of methods described here does not include all methods of the interface. Some methods are explained in detail in the relevant sections of the guide. + +## `ClientType` method + +`ClientType` should return a unique string identifier of the light client. This will be used when generating a client identifier. +The format is created as follows: `ClientType-{N}` where `{N}` is the unique global nonce associated with a specific client. + +## `GetLatestHeight` method + +`GetLatestHeight` should return the latest block height that the client state represents. + +## `Validate` method + +`Validate` should validate every client state field and should return an error if any value is invalid. The light client +implementer is in charge of determining which checks are required. See the [Tendermint light client implementation](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/client_state.go#L111) as a reference. + +## `Status` method + +`Status` must return the status of the client. + +- An `Active` status indicates that clients are allowed to process packets. +- A `Frozen` status indicates that misbehaviour was detected in the counterparty chain and the client is not allowed to be used. +- An `Expired` status indicates that a client is not allowed to be used because it was not updated for longer than the trusting period. +- An `Unknown` status indicates that there was an error in determining the status of a client. + +All possible `Status` types can be found [here](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L22-L32). + +This field is returned in the response of the gRPC [`ibc.core.client.v1.Query/ClientStatus`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/types/query.pb.go#L665) endpoint. + +## `ZeroCustomFields` method + +`ZeroCustomFields` should return a copy of the light client with all client customizable fields with their zero value. It should not mutate the fields of the light client. +This method is used when [scheduling upgrades](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/keeper/proposal.go#L82). Upgrades are used to upgrade chain specific fields. +In the tendermint case, this may be the chain ID or the unbonding period. +For more information about client upgrades see the [Handling upgrades](05-upgrades.md) section. + +## `GetTimestampAtHeight` method + +`GetTimestampAtHeight` must return the timestamp for the consensus state associated with the provided height. +This value is used to facilitate timeouts by checking the packet timeout timestamp against the returned value. + +## `Initialize` method + +Clients must validate the initial consensus state, and set the initial client state and consensus state in the provided client store. +Clients may also store any necessary client-specific metadata. + +`Initialize` is called when a [client is created](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/keeper/client.go#L30). + +## `VerifyMembership` method + +`VerifyMembership` must verify the existence of a value at a given commitment path at the specified height. For more information about membership proofs +see the [Existence and non-existence proofs section](06-proofs.md). + +## `VerifyNonMembership` method + +`VerifyNonMembership` must verify the absence of a value at a given commitment path at a specified height. For more information about non-membership proofs +see the [Existence and non-existence proofs section](06-proofs.md). + +## `VerifyClientMessage` method + +`VerifyClientMessage` must verify a `ClientMessage`. A `ClientMessage` could be a `Header`, `Misbehaviour`, or batch update. +It must handle each type of `ClientMessage` appropriately. Calls to `CheckForMisbehaviour`, `UpdateState`, and `UpdateStateOnMisbehaviour` +will assume that the content of the `ClientMessage` has been verified and can be trusted. An error should be returned +if the ClientMessage fails to verify. + +## `CheckForMisbehaviour` method + +Checks for evidence of a misbehaviour in `Header` or `Misbehaviour` type. It assumes the `ClientMessage` +has already been verified. diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/03-consensus-state.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/03-consensus-state.md new file mode 100644 index 0000000..2d1b0e8 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/03-consensus-state.md @@ -0,0 +1,29 @@ +--- +title: Consensus State interface +sidebar_label: Consensus State interface +sidebar_position: 3 +slug: /ibc/light-clients/consensus-state +--- + + +# Implementing the `ConsensusState` interface + +A `ConsensusState` is the snapshot of the counterparty chain, that an IBC client uses to verify proofs (e.g. a block). + +The further development of multiple types of IBC light clients and the difficulties presented by this generalization problem (see [ADR-006](https://github.com/cosmos/ibc-go/blob/main/docs/architecture/adr-006-02-client-refactor.md) for more information about this historical context) led to the design decision of each client keeping track of and set its own `ClientState` and `ConsensusState`, as well as the simplification of client `ConsensusState` updates through the generalized `ClientMessage` interface. + +The below [`ConsensusState`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L133) interface is a generalized interface for the types of information a `ConsensusState` could contain. For a reference `ConsensusState` implementation, please see the [Tendermint light client `ConsensusState`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/consensus_state.go). + +## `ClientType` method + +This is the type of client consensus. It should be the same as the `ClientType` return value for the [corresponding `ClientState` implementation](02-client-state.md). + +## `GetTimestamp` method + +*Deprecated*: soon to be removed from interface + +`GetTimestamp` should return the timestamp (in nanoseconds) of the consensus state snapshot. + +## `ValidateBasic` method + +`ValidateBasic` should validate every consensus state field and should return an error if any value is invalid. The light client implementer is in charge of determining which checks are required. diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/04-updates-and-misbehaviour.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/04-updates-and-misbehaviour.md new file mode 100644 index 0000000..d244d1f --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/04-updates-and-misbehaviour.md @@ -0,0 +1,98 @@ +--- +title: Handling Updates and Misbehaviour +sidebar_label: Handling Updates and Misbehaviour +sidebar_position: 4 +slug: /ibc/light-clients/updates-and-misbehaviour +--- + + +# Handling `ClientMessage`s: updates and misbehaviour + +As mentioned before in the documentation about [implementing the `ConsensusState` interface](03-consensus-state.md), [`ClientMessage`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L147) is an interface used to update an IBC client. This update may be performed by: + +- a single header +- a batch of headers +- evidence of misbehaviour, +- or any type which when verified produces a change to the consensus state of the IBC client. + +This interface has been purposefully kept generic in order to give the maximum amount of flexibility to the light client implementer. + +## Implementing the `ClientMessage` interface + +Find the `ClientMessage`interface in `modules/core/exported`: + +```go +type ClientMessage interface { + proto.Message + + ClientType() string + ValidateBasic() error +} +``` + +The `ClientMessage` will be passed to the client to be used in [`UpdateClient`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/keeper/client.go#L48), which retrieves the `ClientState` by client ID (available in `MsgUpdateClient`). This `ClientState` implements the [`ClientState` interface](02-client-state.md) for its specific consenus type (e.g. Tendermint). + +`UpdateClient` will then handle a number of cases including misbehaviour and/or updating the consensus state, utilizing the specific methods defined in the relevant `ClientState`. + +```go +VerifyClientMessage(ctx sdk.Context, cdc codec.BinaryCodec, clientStore sdk.KVStore, clientMsg ClientMessage) error +CheckForMisbehaviour(ctx sdk.Context, cdc codec.BinaryCodec, clientStore sdk.KVStore, clientMsg ClientMessage) bool +UpdateStateOnMisbehaviour(ctx sdk.Context, cdc codec.BinaryCodec, clientStore sdk.KVStore, clientMsg ClientMessage) +UpdateState(ctx sdk.Context, cdc codec.BinaryCodec, clientStore sdk.KVStore, clientMsg ClientMessage) []Height +``` + +## Handling updates and misbehaviour + +The functions for handling updates to a light client and evidence of misbehaviour are all found in the [`ClientState`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L36) interface, and will be discussed below. + +> It is important to note that `Misbehaviour` in this particular context is referring to misbehaviour on the chain level intended to fool the light client. This will be defined by each light client. + +## `VerifyClientMessage` + +`VerifyClientMessage` must verify a `ClientMessage`. A `ClientMessage` could be a `Header`, `Misbehaviour`, or batch update. To understand how to implement a `ClientMessage`, please refer to the [Implementing the `ClientMessage` interface](#implementing-the-clientmessage-interface) section. + +It must handle each type of `ClientMessage` appropriately. Calls to `CheckForMisbehaviour`, `UpdateState`, and `UpdateStateOnMisbehaviour` will assume that the content of the `ClientMessage` has been verified and can be trusted. An error should be returned if the `ClientMessage` fails to verify. + +For an example of a `VerifyClientMessage` implementation, please check the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/update.go#L20). + +## `CheckForMisbehaviour` + +Checks for evidence of a misbehaviour in `Header` or `Misbehaviour` type. It assumes the `ClientMessage` has already been verified. + +For an example of a `CheckForMisbehaviour` implementation, please check the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/misbehaviour_handle.go#L19). + +> The Tendermint light client [defines `Misbehaviour`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/misbehaviour.go) as two different types of situations: a situation where two conflicting `Header`s with the same height have been submitted to update a client's `ConsensusState` within the same trusting period, or that the two conflicting `Header`s have been submitted at different heights but the consensus states are not in the correct monotonic time ordering (BFT time violation). More explicitly, updating to a new height must have a timestamp greater than the previous consensus state, or, if inserting a consensus at a past height, then time must be less than those heights which come after and greater than heights which come before. + +## `UpdateStateOnMisbehaviour` + +`UpdateStateOnMisbehaviour` should perform appropriate state changes on a client state given that misbehaviour has been detected and verified. This method should only be called when misbehaviour is detected, as it does not perform any misbehaviour checks. Notably, it should freeze the client so that calling the `Status` function on the associated client state no longer returns `Active`. + +For an example of a `UpdateStateOnMisbehaviour` implementation, please check the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/update.go#L199). + +## `UpdateState` + +`UpdateState` updates and stores as necessary any associated information for an IBC client, such as the `ClientState` and corresponding `ConsensusState`. It should perform a no-op on duplicate updates. + +It assumes the `ClientMessage` has already been verified. + +For an example of a `UpdateState` implementation, please check the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/update.go#L131). + +## Putting it all together + +The `02-client` `Keeper` module in ibc-go offers a reference as to how these functions will be used to [update the client](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/keeper/client.go#L48). + +```go +if err := clientState.VerifyClientMessage(clientMessage); err != nil { + return err +} + +foundMisbehaviour := clientState.CheckForMisbehaviour(clientMessage) +if foundMisbehaviour { + clientState.UpdateStateOnMisbehaviour(clientMessage) + // emit misbehaviour event + return +} + +clientState.UpdateState(clientMessage) // expects no-op on duplicate header +// emit update event +return diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/05-upgrades.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/05-upgrades.md new file mode 100644 index 0000000..61cb42e --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/05-upgrades.md @@ -0,0 +1,66 @@ +--- +title: Handling Upgrades +sidebar_label: Handling Upgrades +sidebar_position: 5 +slug: /ibc/light-clients/upgrades +--- + + +# Handling upgrades + +It is vital that high-value IBC clients can upgrade along with their underlying chains to avoid disruption to the IBC ecosystem. Thus, IBC client developers will want to implement upgrade functionality to enable clients to maintain connections and channels even across chain upgrades. + +## Implementing `VerifyUpgradeAndUpdateState` + +The IBC protocol allows client implementations to provide a path to upgrading clients given the upgraded `ClientState`, upgraded `ConsensusState` and proofs for each. This path is provided in the `VerifyUpgradeAndUpdateState` method: + +```go +// NOTE: proof heights are not included as upgrade to a new revision is expected to pass only on the last height committed by the current revision. Clients are responsible for ensuring that the planned last height of the current revision is somehow encoded in the proof verification process. +// This is to ensure that no premature upgrades occur, since upgrade plans committed to by the counterparty may be cancelled or modified before the last planned height. +// If the upgrade is verified, the upgraded client and consensus states must be set in the client store. +func (cs ClientState) VerifyUpgradeAndUpdateState( + ctx sdk.Context, + cdc codec.BinaryCodec, + store sdk.KVStore, + newClient ClientState, + newConsState ConsensusState, + upgradeClientProof, + upgradeConsensusStateProof []byte, +) error +``` + +> Please refer to the [Tendermint light client implementation](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/upgrade.go#L27) as an example for implementation. + +It is important to note that light clients **must** handle all management of client and consensus states including the setting of updated `ClientState` and `ConsensusState` in the client store. This can include verifying that the submitted upgraded `ClientState` is of a valid `ClientState` type, that the height of the upgraded client is not greater than the height of the current client (in order to preserve BFT monotonic time), or that certain parameters which should not be changed have not been altered in the upgraded `ClientState`. + +Developers must ensure that the `MsgUpgradeClient` does not pass until the last height of the old chain has been committed, and after the chain upgrades, the `MsgUpgradeClient` should pass once and only once on all counterparty clients. + +### Upgrade path + +Clients should have **prior knowledge of the merkle path** that the upgraded client and upgraded consensus states will use. The height at which the upgrade has occurred should also be encoded in the proof. +> The Tendermint client implementation accomplishes this by including an `UpgradePath` in the `ClientState` itself, which is used along with the upgrade height to construct the merkle path under which the client state and consensus state are committed. + +## Chain specific vs client specific client parameters + +Developers should maintain the distinction between client parameters that are uniform across every valid light client of a chain (chain-chosen parameters), and client parameters that are customizable by each individual client (client-chosen parameters); since this distinction is necessary to implement the `ZeroCustomFields` method in the [`ClientState` interface](02-client-state.md): + +```go +// Utility function that zeroes out any client customizable fields in client state +// Ledger enforced fields are maintained while all custom fields are zero values +// Used to verify upgrades +func (cs ClientState) ZeroCustomFields() ClientState +``` + +Developers must ensure that the new client adopts all of the new client parameters that must be uniform across every valid light client of a chain (chain-chosen parameters), while maintaining the client parameters that are customizable by each individual client (client-chosen parameters) from the previous version of the client. `ZeroCustomFields` is a useful utility function to ensure only chain specific fields are updated during upgrades. + +## Security + +Upgrades must adhere to the IBC Security Model. IBC does not rely on the assumption of honest relayers for correctness. Thus users should not have to rely on relayers to maintain client correctness and security (though honest relayers must exist to maintain relayer liveness). While relayers may choose any set of client parameters while creating a new `ClientState`, this still holds under the security model since users can always choose a relayer-created client that suits their security and correctness needs or create a client with their desired parameters if no such client exists. + +However, when upgrading an existing client, one must keep in mind that there are already many users who depend on this client's particular parameters. **We cannot give the upgrading relayer free choice over these parameters once they have already been chosen. This would violate the security model** since users who rely on the client would have to rely on the upgrading relayer to maintain the same level of security. + +Thus, developers must make sure that their upgrade mechanism allows clients to upgrade the chain-specified parameters whenever a chain upgrade changes these parameters (examples in the Tendermint client include `UnbondingPeriod`, `TrustingPeriod`, `ChainID`, `UpgradePath`, etc), while ensuring that the relayer submitting the `MsgUpgradeClient` cannot alter the client-chosen parameters that the users are relying upon (examples in Tendermint client include `TrustLevel`, `MaxClockDrift`, etc). The previous paragraph discusses how `ZeroCustomFields` helps achieve this. + +### Document potential client parameter conflicts during upgrades + +Counterparty clients can upgrade securely by using all of the chain-chosen parameters from the chain-committed `UpgradedClient` and preserving all of the old client-chosen parameters. This enables chains to securely upgrade without relying on an honest relayer, however it can in some cases lead to an invalid final `ClientState` if the new chain-chosen parameters clash with the old client-chosen parameter. This can happen in the Tendermint client case if the upgrading chain lowers the `UnbondingPeriod` (chain-chosen) to a duration below that of a counterparty client's `TrustingPeriod` (client-chosen). Such cases should be clearly documented by developers, so that chains know which upgrades should be avoided to prevent this problem. The final upgraded client should also be validated in `VerifyUpgradeAndUpdateState` before returning to ensure that the client does not upgrade to an invalid `ClientState`. diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/06-proofs.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/06-proofs.md new file mode 100644 index 0000000..245e0d5 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/06-proofs.md @@ -0,0 +1,66 @@ +--- +title: Existence/Non-Existence Proofs +sidebar_label: Existence/Non-Existence Proofs +sidebar_position: 6 +slug: /ibc/light-clients/proofs +--- + + +# Existence and non-existence proofs + +IBC uses merkle proofs in order to verify the state of a remote counterparty state machine given a trusted root, and [ICS-23](https://github.com/cosmos/ics23/tree/master/go) is a general approach for verifying merkle trees which is used in ibc-go. + +Currently, all Cosmos SDK modules contain their own stores, which maintain the state of the application module in an IAVL (immutable AVL) binary merkle tree format. Specifically with regard to IBC, core IBC maintains its own IAVL store, and IBC apps (e.g. transfer) maintain their own dedicated stores. The Cosmos SDK multistore therefore creates a simple merkle tree of all of these IAVL trees, and from each of these individual IAVL tree root hashes it derives a root hash for the application state tree as a whole (the `AppHash`). + +For the purposes of ibc-go, there are two types of proofs which are important: existence and non-existence proofs, terms which have been used interchangeably with membership and non-membership proofs. For the purposes of this guide, we will stick with "existence" and "non-existence". + +## Existence proofs + +Existence proofs are used in IBC transactions which involve verification of counterparty state for transactions which will result in the writing of provable state. For example, this includes verification of IBC store state for handshakes and packets. + +Put simply, existence proofs prove that a particular key and value exists in the tree. Under the hood, an IBC existence proof is comprised of two proofs: an IAVL proof that the key exists in IBC store/IBC root hash, and a proof that the IBC root hash exists in the multistore root hash. + +## Non-existence proofs + +Non-existence proofs verify the absence of data stored within counterparty state and are used to prove that a key does NOT exist in state. As stated above, these types of proofs can be used to timeout packets by proving that the counterparty has not written a packet receipt into the store, meaning that a token transfer has NOT successfully occurred. + +Some trees (e.g. SMT) may have a sentinel empty child for non-existent keys. In this case, the ICS-23 proof spec should include this `EmptyChild` so that ICS-23 handles the non-existence proof correctly. + +In some cases, there is a necessity to "mock" non-existence proofs if the counterparty does not have ability to prove absence. Since the verification method is designed to give complete control to client implementations, clients can support chains that do not provide absence proofs by verifying the existence of a non-empty sentinel `ABSENCE` value. In these special cases, the proof provided will be an ICS-23 `Existence` proof, and the client will verify that the `ABSENCE` value is stored under the given path for the given height. + +## State verification methods: `VerifyMembership` and `VerifyNonMembership` + +The state verification functions for all IBC data types have been consolidated into two generic methods, `VerifyMembership` and `VerifyNonMembership`. + +From the [`ClientState` interface definition](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L68-L91), we find: + +```go +VerifyMembership( + ctx sdk.Context, + clientStore sdk.KVStore, + cdc codec.BinaryCodec, + height Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path Path, + value []byte, +) error + +// VerifyNonMembership is a generic proof verification method which verifies the absence of a given CommitmentPath at a specified height. +// The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). +VerifyNonMembership( + ctx sdk.Context, + clientStore sdk.KVStore, + cdc codec.BinaryCodec, + height Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path Path, +) error +``` + +Both are expected to be provided with a standardised key path, `exported.Path`, as defined in [ICS-24 host requirements](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements). Membership verification requires callers to provide the value marshalled as `[]byte`. Delay period values should be zero for non-packet processing verification. A zero proof height is now allowed by core IBC and may be passed into `VerifyMembership` and `VerifyNonMembership`. Light clients are responsible for returning an error if a zero proof height is invalid behaviour. + +Please refer to the [ICS-23 implementation](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/23-commitment/types/merkle.go#L131-L205) for a concrete example. diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/07-proposals.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/07-proposals.md new file mode 100644 index 0000000..47c9433 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/07-proposals.md @@ -0,0 +1,36 @@ +--- +title: Handling Proposals +sidebar_label: Handling Proposals +sidebar_position: 7 +slug: /ibc/light-clients/proposals +--- + + +# Handling proposals + +It is possible to update the client with the state of the substitute client through a governance proposal. [This type of governance proposal](../../01-ibc/07-proposals.md) is typically used to recover an expired or frozen client, as it can recover the entire state and therefore all existing channels built on top of the client. `CheckSubstituteAndUpdateState` should be implemented to handle the proposal. + +## Implementing `CheckSubstituteAndUpdateState` + +In the [`ClientState`interface](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go), we find: + +```go +// CheckSubstituteAndUpdateState must verify that the provided substitute may be used to update the subject client. +// The light client must set the updated client and consensus states within the clientStore for the subject client. +CheckSubstituteAndUpdateState( + ctx sdk.Context, + cdc codec.BinaryCodec, + subjectClientStore, + substituteClientStore sdk.KVStore, + substituteClient ClientState, +) error +``` + +Prior to updating, this function must verify that: + +- the substitute client is the same type as the subject client. For a reference implementation, please see the [Tendermint light client](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/proposal_handle.go#L32). +- the provided substitute may be used to update the subject client. This may mean that certain parameters must remain unaltered. For example, a [valid substitute Tendermint light client](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/proposal_handle.go#L84) must NOT change the chain ID, trust level, max clock drift, unbonding period, proof specs or upgrade path. Please note that `AllowUpdateAfterMisbehaviour` and `AllowUpdateAfterExpiry` have been deprecated (see ADR 026 for more information). + +After these checks are performed, the function must [set the updated client and consensus states](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/proposal_handle.go#L77) within the client store for the subject client. + +Please refer to the [Tendermint light client implementation](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/proposal_handle.go#L27) for reference. diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/08-genesis.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/08-genesis.md new file mode 100644 index 0000000..e3e193a --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/08-genesis.md @@ -0,0 +1,45 @@ +--- +title: Handling Genesis +sidebar_label: Handling Genesis +sidebar_position: 8 +slug: /ibc/light-clients/genesis +--- + +# Genesis metadata + +:::note Synopsis +Learn how to implement the `ExportMetadata` interface +::: + +:::note + +## Pre-requisite readings + +- [Cosmos SDK module genesis](https://docs.cosmos.network/v0.47/building-modules/genesis) + +::: + +`ClientState` instances are provided their own isolated and namespaced client store upon initialisation. `ClientState` implementations may choose to store any amount of arbitrary metadata in order to verify counterparty consensus state and perform light client updates correctly. + +The `ExportMetadata` method of the [`ClientState` interface](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L47) provides light client modules with the ability to persist metadata in genesis exports. + +```go +ExportMetadata(clientStore sdk.KVStore) []GenesisMetadata +``` + +`ExportMetadata` is provided the client store and returns an array of `GenesisMetadata`. For maximum flexibility, `GenesisMetadata` is defined as a simple interface containing two distinct `Key` and `Value` accessor methods. + +```go +type GenesisMetadata interface { + // return store key that contains metadata without clientID-prefix + GetKey() []byte + // returns metadata value + GetValue() []byte +} +``` + +This allows `ClientState` instances to retrieve and export any number of key-value pairs which are maintained within the store in their raw `[]byte` form. + +When a chain is started with a `genesis.json` file which contains `ClientState` metadata (for example, when performing manual upgrades using an exported `genesis.json`) the `02-client` submodule of core IBC will handle setting the key-value pairs within their respective client stores. [See `02-client` `InitGenesis`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/genesis.go#L18-L22). + +Please refer to the [Tendermint light client implementation](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/genesis.go#L12) for an example. diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/09-setup.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/09-setup.md new file mode 100644 index 0000000..1e4c7e3 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/09-setup.md @@ -0,0 +1,135 @@ +--- +title: Setup +sidebar_label: Setup +sidebar_position: 9 +slug: /ibc/light-clients/setup +--- + + +# Setup + +:::note Synopsis +Learn how to configure light client modules and create clients using core IBC and the `02-client` submodule. +::: + +A last step to finish the development of the light client, is to implement the `AppModuleBasic` interface to allow it to be added to the chain's `app.go` alongside other light client types the chain enables. + +Finally, a succinct rundown is given of the remaining steps to make the light client operational, getting the light client type passed through governance and creating the clients. + +## Configuring a light client module + +An IBC light client module must implement the [`AppModuleBasic`](https://github.com/cosmos/cosmos-sdk/blob/main/types/module/module.go#L50) interface in order to register its concrete types against the core IBC interfaces defined in `modules/core/exported`. This is accomplished via the `RegisterInterfaces` method which provides the light client module with the opportunity to register codec types using the chain's `InterfaceRegistry`. Please refer to the [`07-tendermint` codec registration](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/codec.go#L11). + +The `AppModuleBasic` interface may also be leveraged to install custom CLI handlers for light client module users. Light client modules can safely no-op for interface methods which it does not wish to implement. + +Please refer to the [core IBC documentation](../../01-ibc/02-integration.md#integrating-light-clients) for how to configure additional light client modules alongside `07-tendermint` in `app.go`. + +See below for an example of the `07-tendermint` implementation of `AppModuleBasic`. + +```go +var _ module.AppModuleBasic = AppModuleBasic{} + +// AppModuleBasic defines the basic application module used by the tendermint light client. +// Only the RegisterInterfaces function needs to be implemented. All other function perform +// a no-op. +type AppModuleBasic struct{} + +// Name returns the tendermint module name. +func (AppModuleBasic) Name() string { + return ModuleName +} + +// RegisterLegacyAminoCodec performs a no-op. The Tendermint client does not support amino. +func (AppModuleBasic) RegisterLegacyAminoCodec(*codec.LegacyAmino) {} + +// RegisterInterfaces registers module concrete types into protobuf Any. This allows core IBC +// to unmarshal tendermint light client types. +func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) { + RegisterInterfaces(registry) +} + +// DefaultGenesis performs a no-op. Genesis is not supported for the tendermint light client. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return nil +} + +// ValidateGenesis performs a no-op. Genesis is not supported for the tendermint light client. +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { + return nil +} + +// RegisterGRPCGatewayRoutes performs a no-op. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {} + +// GetTxCmd performs a no-op. Please see the 02-client cli commands. +func (AppModuleBasic) GetTxCmd() *cobra.Command { + return nil +} + +// GetQueryCmd performs a no-op. Please see the 02-client cli commands. +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return nil +} +``` + +## Creating clients + +A client is created by executing a new `MsgCreateClient` transaction composed with a valid `ClientState` and initial `ConsensusState` encoded as protobuf `Any`s. +Generally, this is performed by an off-chain process known as an [IBC relayer](https://github.com/cosmos/ibc/tree/main/spec/relayer/ics-018-relayer-algorithms) however, this is not a strict requirement. + +See below for a list of IBC relayer implementations: + +- [cosmos/relayer](https://github.com/cosmos/relayer) +- [informalsystems/hermes](https://github.com/informalsystems/hermes) +- [confio/ts-relayer](https://github.com/confio/ts-relayer) + +Stateless checks are performed within the [`ValidateBasic`](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/types/msgs.go#L48) method of `MsgCreateClient`. + +```protobuf +// MsgCreateClient defines a message to create an IBC client +message MsgCreateClient { + option (gogoproto.goproto_getters) = false; + + // light client state + google.protobuf.Any client_state = 1 [(gogoproto.moretags) = "yaml:\"client_state\""]; + // consensus state associated with the client that corresponds to a given + // height. + google.protobuf.Any consensus_state = 2 [(gogoproto.moretags) = "yaml:\"consensus_state\""]; + // signer address + string signer = 3; +} +``` + +Leveraging protobuf `Any` encoding allows core IBC to [unpack](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/keeper/msg_server.go#L28-L36) both the `ClientState` and `ConsensusState` into their respective interface types registered previously using the light client module's `RegisterInterfaces` method. + +Within the `02-client` submodule, the [`ClientState` is then initialized](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/keeper/client.go#L30-L32) with its own isolated key-value store, namespaced using a unique client identifier. + +In order to successfully create an IBC client using a new client type, it [must be supported](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/02-client/keeper/client.go#L19-L25). Light client support in IBC is gated by on-chain governance. The allow list may be updated by submitting a new governance proposal to update the `02-client` parameter `AllowedClients`. + +See below for example: + +```shell +%s tx gov submit-proposal --from +``` + +where `proposal.json` contains: + +```json +{ + "title": "IBC Clients Param Change", + "summary": "Update allowed clients", + "messages": [ + { + "@type": "/ibc.core.client.v1.MsgUpdateParams", + "signer": "cosmos1...", // The gov module account address + "params": { + "allowed_clients": ["06-solomachine", "07-tendermint", "0x-new-client"] + } + } + ], + "metadata": "AQ==", + "deposit": "100stake" +} +``` + +If the `AllowedClients` list contains a single element that is equal to the wildcard `"*"`, then all client types are allowed and it is thus not necessary to submit a governance proposal to update the parameter. diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/_category_.json b/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/_category_.json new file mode 100644 index 0000000..e1c4f32 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/01-developer-guide/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Developer Guide", + "position": 1, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/02-localhost/01-overview.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/02-localhost/01-overview.md new file mode 100644 index 0000000..8c62579 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/02-localhost/01-overview.md @@ -0,0 +1,46 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /ibc/light-clients/localhost/overview +--- + + +# `09-localhost` + +## Overview + +:::note Synopsis +Learn about the 09-localhost light client module. +::: + +The 09-localhost light client module implements a localhost loopback client with the ability to send and receive IBC packets to and from the same state machine. + +### Context + +In a multichain environment, application developers will be used to developing cross-chain applications through IBC. From their point of view, whether or not they are interacting with multiple modules on the same chain or on different chains should not matter. The localhost client module enables a unified interface to interact with different applications on a single chain, using the familiar IBC application layer semantics. + +### Implementation + +There exists a [single sentinel `ClientState`](03-client-state.md) instance with the client identifier `09-localhost`. + +To supplement this, a [sentinel `ConnectionEnd` is stored in core IBC](04-connection.md) state with the connection identifier `connection-localhost`. This enables IBC applications to create channels directly on top of the sentinel connection which leverage the 09-localhost loopback functionality. + +[State verification](05-state-verification.md) for channel state in handshakes or processing packets is reduced in complexity, the `09-localhost` client can simply compare bytes stored under the standardized key paths. + +### Localhost vs *regular* client + +The localhost client aims to provide a unified approach to interacting with applications on a single chain, as the IBC application layer provides for cross-chain interactions. To achieve this unified interface though, there are a number of differences under the hood compared to a 'regular' IBC client (excluding `06-solomachine` and `09-localhost` itself). + +The table below lists some important differences: + +| | Regular client | Localhost | +| -------------------------------------------- | --------------------------------------------------------------------------- | --------- | +| Number of clients | Many instances of a client *type* corresponding to different counterparties | A single sentinel client with the client identifier `09-localhost`| +| Client creation | Relayer (permissionless) | `ClientState` is instantiated in the `InitGenesis` handler of the 02-client submodule in core IBC | +| Client updates | Relayer submits headers using `MsgUpdateClient` | Latest height is updated periodically through the ABCI [`BeginBlock`](https://docs.cosmos.network/v0.47/building-modules/beginblock-endblock) interface of the 02-client submodule in core IBC | +| Number of connections | Many connections, 1 (or more) per client | A single sentinel connection with the connection identifier `connection-localhost` | +| Connection creation | Connection handshake, provided underlying client | Sentinel `ConnectionEnd` is created and set in store in the `InitGenesis` handler of the 03-connection submodule in core IBC | +| Counterparty | Underlying client, representing another chain | Client with identifier `09-localhost` in same chain | +| `VerifyMembership` and `VerifyNonMembership` | Performs proof verification using consensus state roots | Performs state verification using key-value lookups in the core IBC store | +| Integration | Expected to register codec types using the `AppModuleBasic` interface | Registers codec types within the core IBC module | diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/02-localhost/02-integration.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/02-localhost/02-integration.md new file mode 100644 index 0000000..56d284a --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/02-localhost/02-integration.md @@ -0,0 +1,19 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /ibc/light-clients/localhost/integration +--- + + +# Integration + +The 09-localhost light client module registers codec types within the core IBC module. This differs from other light client module implementations which are expected to register codec types using the `AppModuleBasic` interface. + +The localhost client is implicitly enabled by using the `AllowAllClients` wildcard (`"*"`) in the 02-client submodule default value for param [`allowed_clients`](https://github.com/cosmos/ibc-go/blob/v7.0.0/proto/ibc/core/client/v1/client.proto#L102). + +```go +// DefaultAllowedClients are the default clients for the AllowedClients parameter. +// By default it allows all client types. +var DefaultAllowedClients = []string{AllowAllClients} +``` diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/02-localhost/03-client-state.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/02-localhost/03-client-state.md new file mode 100644 index 0000000..534d570 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/02-localhost/03-client-state.md @@ -0,0 +1,64 @@ +--- +title: ClientState +sidebar_label: ClientState +sidebar_position: 3 +slug: /ibc/light-clients/localhost/client-state +--- + + +# `ClientState` + +The 09-localhost `ClientState` maintains a single field used to track the latest sequence of the state machine i.e. the height of the blockchain. + +```go +type ClientState struct { + // the latest height of the blockchain + LatestHeight clienttypes.Height +} +``` + +The 09-localhost `ClientState` is instantiated in the `InitGenesis` handler of the 02-client submodule in core IBC. +It calls `CreateLocalhostClient`, declaring a new `ClientState` and initializing it with its own client prefixed store. + +```go +func (k Keeper) CreateLocalhostClient(ctx sdk.Context) error { + var clientState localhost.ClientState + return clientState.Initialize(ctx, k.cdc, k.ClientStore(ctx, exported.LocalhostClientID), nil) +} +``` + +It is possible to disable the localhost client by removing the `09-localhost` entry from the `allowed_clients` list through governance. + +## Client updates + +The latest height is updated periodically through the ABCI [`BeginBlock`](https://docs.cosmos.network/v0.47/building-modules/beginblock-endblock) interface of the 02-client submodule in core IBC. + +[See `BeginBlocker` in abci.go.](https://github.com/cosmos/ibc-go/blob/v8.5.0/modules/core/02-client/abci.go#L12) + +```go +func BeginBlocker(ctx sdk.Context, k keeper.Keeper) { + // ... + + if clientState, found := k.GetClientState(ctx, exported.Localhost); found { + if k.GetClientStatus(ctx, clientState, exported.Localhost) == exported.Active { + k.UpdateLocalhostClient(ctx, clientState) + } + } +} +``` + +The above calls into the 09-localhost `UpdateState` method of the `ClientState` . +It retrieves the current block height from the application context and sets the `LatestHeight` of the 09-localhost client. + +```go +func (cs ClientState) UpdateState(ctx sdk.Context, cdc codec.BinaryCodec, clientStore sdk.KVStore, clientMsg exported.ClientMessage) []exported.Height { + height := clienttypes.GetSelfHeight(ctx) + cs.LatestHeight = height + + clientStore.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(cdc, &cs)) + + return []exported.Height{height} +} +``` + +Note that the 09-localhost `ClientState` is not updated through the 02-client interface leveraged by conventional IBC light clients. diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/02-localhost/04-connection.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/02-localhost/04-connection.md new file mode 100644 index 0000000..480e63d --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/02-localhost/04-connection.md @@ -0,0 +1,29 @@ +--- +title: Connection +sidebar_label: Connection +sidebar_position: 4 +slug: /ibc/light-clients/localhost/connection +--- + + +# Localhost connections + +The 09-localhost light client module integrates with core IBC through a single sentinel localhost connection. +The sentinel `ConnectionEnd` is stored by default in the core IBC store. + +This enables channel handshakes to be initiated out of the box by supplying the localhost connection identifier (`connection-localhost`) in the `connectionHops` parameter of `MsgChannelOpenInit`. + +The `ConnectionEnd` is created and set in store via the `InitGenesis` handler of the 03-connection submodule in core IBC. +The `ConnectionEnd` and its `Counterparty` both reference the `09-localhost` client identifier, and share the localhost connection identifier `connection-localhost`. + +```go +// CreateSentinelLocalhostConnection creates and sets the sentinel localhost connection end in the IBC store. +func (k Keeper) CreateSentinelLocalhostConnection(ctx sdk.Context) { + counterparty := types.NewCounterparty(exported.LocalhostClientID, exported.LocalhostConnectionID, commitmenttypes.NewMerklePrefix(k.GetCommitmentPrefix().Bytes())) + connectionEnd := types.NewConnectionEnd(types.OPEN, exported.LocalhostClientID, counterparty, types.GetCompatibleVersions(), 0) + + k.SetConnection(ctx, exported.LocalhostConnectionID, connectionEnd) +} +``` + +Note that connection handshakes are disallowed when using the `09-localhost` client type. diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/02-localhost/05-state-verification.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/02-localhost/05-state-verification.md new file mode 100644 index 0000000..6a65410 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/02-localhost/05-state-verification.md @@ -0,0 +1,22 @@ +--- +title: State Verification +sidebar_label: State Verification +sidebar_position: 5 +slug: /ibc/light-clients/localhost/state-verification +--- + + +# State verification + +The localhost client handles state verification through the `ClientState` interface methods `VerifyMembership` and `VerifyNonMembership` by performing read-only operations directly on the core IBC store. + +When verifying channel state in handshakes or processing packets the `09-localhost` client can simply compare bytes stored under the standardized key paths defined by [ICS-24](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements). + +For existence proofs via `VerifyMembership` the 09-localhost client will retrieve the value stored under the provided key path and compare it against the value provided by the caller. In contrast, non-existence proofs via `VerifyNonMembership` assert the absence of a value at the provided key path. + +Relayers are expected to provide a sentinel proof when sending IBC messages. Submission of nil or empty proofs is disallowed in core IBC messaging. +The 09-localhost light client module defines a `SentinelProof` as a single byte. Localhost client state verification will fail if the sentinel proof value is not provided. + +```go +var SentinelProof = []byte{0x01} +``` diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/02-localhost/_category_.json b/docs/versioned_docs/version-v8.5.x/03-light-clients/02-localhost/_category_.json new file mode 100644 index 0000000..f2e9bd7 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/02-localhost/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Localhost", + "position": 2, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/03-solomachine/01-solomachine.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/03-solomachine/01-solomachine.md new file mode 100644 index 0000000..91c41b1 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/03-solomachine/01-solomachine.md @@ -0,0 +1,26 @@ +--- +title: Solomachine +sidebar_label: Solomachine +sidebar_position: 1 +slug: /ibc/light-clients/solomachine/solomachine +--- + + +# `solomachine` + +## Abstract + +This paper defines the implementation of the ICS06 protocol on the Cosmos SDK. For the general +specification please refer to the [ICS06 Specification](https://github.com/cosmos/ibc/tree/master/spec/client/ics-006-solo-machine-client). + +This implementation of a solo machine light client supports single and multi-signature public +keys. The client is capable of handling public key updates by header and governance proposals. +The light client is capable of processing client misbehaviour. Proofs of the counterparty state +are generated by the solo machine client by signing over the desired state with a certain sequence, +diversifier, and timestamp. + +## Contents + +1. **[Concepts](02-concepts.md)** +2. **[State](03-state.md)** +3. **[State Transitions](04-state_transitions.md)** diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/03-solomachine/02-concepts.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/03-solomachine/02-concepts.md new file mode 100644 index 0000000..f6c7ff7 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/03-solomachine/02-concepts.md @@ -0,0 +1,168 @@ +--- +title: Concepts +sidebar_label: Concepts +sidebar_position: 2 +slug: /ibc/light-clients/solomachine/concepts +--- + + +# Concepts + +## Client State + +The `ClientState` for a solo machine light client stores the latest sequence, the frozen sequence, +the latest consensus state, and client flag indicating if the client should be allowed to be updated +after a governance proposal. + +If the client is not frozen then the frozen sequence is 0. + +## Consensus State + +The consensus states stores the public key, diversifier, and timestamp of the solo machine light client. + +The diversifier is used to prevent accidental misbehaviour if the same public key is used across +different chains with the same client identifier. It should be unique to the chain the light client +is used on. + +## Public Key + +The public key can be a single public key or a multi-signature public key. The public key type used +must fulfill the tendermint public key interface (this will become the SDK public key interface in the +near future). The public key must be registered on the application codec otherwise encoding/decoding +errors will arise. The public key stored in the consensus state is represented as a protobuf `Any`. +This allows for flexibility in what other public key types can be supported in the future. + +## Counterparty Verification + +The solo machine light client can verify counterparty client state, consensus state, connection state, +channel state, packet commitments, packet acknowledgements, packet receipt absence, +and the next sequence receive. At the end of each successful verification call the light +client sequence number will be incremented. + +Successful verification requires the current public key to sign over the proof. + +## Proofs + +A solo machine proof should verify that the solomachine public key signed +over some specified data. The format for generating marshaled proofs for +the SDK's implementation of solo machine is as follows: + +1. Construct the data using the associated protobuf definition and marshal it. + +For example: + +```go +data := &ClientStateData{ + Path: []byte(path.String()), + ClientState: protoAny, +} + +dataBz, err := cdc.Marshal(data) +``` + +The helper functions `...DataBytes()` in [proof.go](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/06-solomachine/proof.go) handle this +functionality. + +2. Construct the `SignBytes` and marshal it. + +For example: + +```go +signBytes := &SignBytes{ + Sequence: sequence, + Timestamp: timestamp, + Diversifier: diversifier, + DataType: CLIENT, + Data: dataBz, +} + +signBz, err := cdc.Marshal(signBytes) +``` + +The helper functions `...SignBytes()` in [proof.go](https://github.com/cosmos/ibc-go/blob/main/modules/light-clients/06-solomachine/proof.go) handle this functionality. +The `DataType` field is used to disambiguate what type of data was signed to prevent potential +proto encoding overlap. + +3. Sign the sign bytes. Embed the signatures into either `SingleSignatureData` or `MultiSignatureData`. +Convert the `SignatureData` to proto and marshal it. + +For example: + +```go +sig, err := key.Sign(signBz) +sigData := &signing.SingleSignatureData{ + Signature: sig, +} + +protoSigData := signing.SignatureDataToProto(sigData) +bz, err := cdc.Marshal(protoSigData) +``` + +4. Construct a `TimestampedSignatureData` and marshal it. The marshaled result can be passed in +as the proof parameter to the verification functions. + +For example: + +```go +timestampedSignatureData := &solomachine.TimestampedSignatureData{ + SignatureData: sigData, + Timestamp: solomachine.Time, +} + +proof, err := cdc.Marshal(timestampedSignatureData) +``` + +NOTE: At the end of this process, the sequence associated with the key needs to be updated. +The sequence must be incremented each time proof is generated. + +## Updates By Header + +An update by a header will only succeed if: + +- the header provided is parseable to solo machine header +- the header sequence matches the current sequence +- the header timestamp is greater than or equal to the consensus state timestamp +- the currently registered public key generated the proof + +If the update is successful: + +- the public key is updated +- the diversifier is updated +- the timestamp is updated +- the sequence is incremented by 1 +- the new consensus state is set in the client state + +## Updates By Proposal + +An update by a governance proposal will only succeed if: + +- the substitute provided is parseable to solo machine client state +- the new consensus state public key does not equal the current consensus state public key + +If the update is successful: + +- the subject client state is updated to the substitute client state +- the subject consensus state is updated to the substitute consensus state +- the client is unfrozen (if it was previously frozen) + +NOTE: Previously, `AllowUpdateAfterProposal` was used to signal the update/recovery options for the solo machine client. However, this has now been deprecated because a code migration can overwrite the client and consensus states regardless of the value of this parameter. If governance would vote to overwrite a client or consensus state, it is likely that governance would also be willing to perform a code migration to do the same. + +## Misbehaviour + +Misbehaviour handling will only succeed if: + +- the misbehaviour provided is parseable to solo machine misbehaviour +- the client is not already frozen +- the current public key signed over two unique data messages at the same sequence and diversifier. + +If the misbehaviour is successfully processed: + +- the client is frozen by setting the frozen sequence to the misbehaviour sequence + +NOTE: Misbehaviour processing is data processing order dependent. A misbehaving solo machine +could update to a new public key to prevent being frozen before misbehaviour is submitted. + +## Upgrades + +Upgrades to solo machine light clients are not supported since an entirely different type of +public key can be set using normal client updates. diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/03-solomachine/03-state.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/03-solomachine/03-state.md new file mode 100644 index 0000000..bdb3102 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/03-solomachine/03-state.md @@ -0,0 +1,12 @@ +--- +title: State +sidebar_label: State +sidebar_position: 3 +slug: /ibc/light-clients/solomachine/state +--- + + +# State + +The solo machine light client will only store consensus states for each update by a header +or a governance proposal. The latest client state is also maintained in the store. diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/03-solomachine/04-state_transitions.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/03-solomachine/04-state_transitions.md new file mode 100644 index 0000000..aabaa58 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/03-solomachine/04-state_transitions.md @@ -0,0 +1,43 @@ +--- +title: State Transitions +sidebar_label: State Transitions +sidebar_position: 4 +slug: /ibc/light-clients/solomachine/state_transitions +--- + + +# State Transitions + +## Client State Verification Functions + +Successful state verification by a solo machine light client will result in: + +- the sequence being incremented by 1. + +## Update By Header + +A successful update of a solo machine light client by a header will result in: + +- the public key being updated to the new public key provided by the header. +- the diversifier being updated to the new diviersifier provided by the header. +- the timestamp being updated to the new timestamp provided by the header. +- the sequence being incremented by 1 +- the consensus state being updated (consensus state stores the public key, diversifier, and timestamp) + +## Update By Governance Proposal + +A successful update of a solo machine light client by a governance proposal will result in: + +- the client state being updated to the substitute client state +- the consensus state being updated to the substitute consensus state (consensus state stores the public key, diversifier, and timestamp) +- the frozen sequence being set to zero (client is unfrozen if it was previously frozen). + +## Upgrade + +Client udgrades are not supported for the solo machine light client. No state transition occurs. + +## Misbehaviour + +Successful misbehaviour processing of a solo machine light client will result in: + +- the frozen sequence being set to the sequence the misbehaviour occurred at diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/03-solomachine/_category_.json b/docs/versioned_docs/version-v8.5.x/03-light-clients/03-solomachine/_category_.json new file mode 100644 index 0000000..3bfa67e --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/03-solomachine/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Solomachine", + "position": 3, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/01-overview.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/01-overview.md new file mode 100644 index 0000000..79e26a2 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/01-overview.md @@ -0,0 +1,26 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /ibc/light-clients/wasm/overview +--- + +# `08-wasm` + +## Overview + +Learn about the `08-wasm` light client proxy module. + +### Context + +Traditionally, light clients used by ibc-go have been implemented only in Go, and since ibc-go v7 (with the release of the 02-client refactor), they are [first-class Cosmos SDK modules](/architecture/adr-010-light-clients-as-sdk-modules). This means that updating existing light client implementations or adding support for new light clients is a multi-step, time-consuming process involving on-chain governance: it is necessary to modify the codebase of ibc-go (if the light client is part of its codebase), re-build chains' binaries, pass a governance proposal and have validators upgrade their nodes. + +### Motivation + +To break the limitation of being able to write light client implementations only in Go, the `08-wasm` adds support to run light clients written in a Wasm-compilable language. The light client byte code implements the entry points of a [CosmWasm](https://docs.cosmwasm.com/docs/) smart contract, and runs inside a Wasm VM. The `08-wasm` module exposes a proxy light client interface that routes incoming messages to the appropriate handler function, inside the Wasm VM, for execution. + +Adding a new light client to a chain is just as simple as submitting a governance proposal with the message that stores the byte code of the light client contract. No coordinated upgrade is needed. When the governance proposal passes and the message is executed, the contract is ready to be instantiated upon receiving a relayer-submitted `MsgCreateClient`. The process of creating a Wasm light client is the same as with a regular light client implemented in Go. + +### Use cases + +- Development of light clients for non-Cosmos ecosystem chains: state machines in other ecosystems are, in many cases, implemented in Rust, and thus there are probably libraries used in their light client implementations for which there is no equivalent in Go. This makes the development of a light client in Go very difficult, but relatively simple to do it in Rust. Therefore, writing a CosmWasm smart contract in Rust that implements the light client algorithm becomes a lower effort. diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/02-concepts.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/02-concepts.md new file mode 100644 index 0000000..e8c57d1 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/02-concepts.md @@ -0,0 +1,74 @@ +--- +title: Concepts +sidebar_label: Concepts +sidebar_position: 2 +slug: /ibc/light-clients/wasm/concepts +--- + +# Concepts + +Learn about the differences between a proxy light client and a Wasm light client. + +## Proxy light client + +The `08-wasm` module is not a regular light client in the same sense as, for example, the 07-tendermint light client. `08-wasm` is instead a *proxy* light client module, and this means that the module acts a proxy to the actual implementations of light clients. The module will act as a wrapper for the actual light clients uploaded as Wasm byte code and will delegate all operations to them (i.e. `08-wasm` just passes through the requests to the Wasm light clients). Still, the `08-wasm` module implements all the required interfaces necessary to integrate with core IBC, so that 02-client can call into it as it would for any other light client module. These interfaces are `ClientState`, `ConsensusState` and `ClientMessage`, and we will describe them in the context of `08-wasm` in the following sections. For more information about this set of interfaces, please read section [Overview of the light client module developer guide](../01-developer-guide/01-overview.md#overview). + +### `ClientState` + +The `08-wasm`'s `ClientState` data structure contains three fields: + +- `Data` contains the bytes of the Protobuf-encoded client state of the underlying light client implemented as a Wasm contract. For example, if the Wasm light client contract implements the GRANDPA light client algorithm, then `Data` will contain the bytes for a [GRANDPA client state](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L35-L60). +- `Checksum` is the sha256 hash of the Wasm contract's byte code. This hash is used as an identifier to call the right contract. +- `LatestHeight` is the latest height of the counterparty state machine (i.e. the height of the blockchain), whose consensus state the light client tracks. + +```go +type ClientState struct { + // bytes encoding the client state of the underlying + // light client implemented as a Wasm contract + Data []byte + // sha256 hash of Wasm contract byte code + Checksum []byte + // latest height of the counterparty ledger + LatestHeight types.Height +} +``` + +See section [`ClientState` of the light client module developer guide](../01-developer-guide/01-overview.md#clientstate) for more information about the `ClientState` interface. + +### `ConsensusState` + +The `08-wasm`'s `ConsensusState` data structure maintains one field: + +- `Data` contains the bytes of the Protobuf-encoded consensus state of the underlying light client implemented as a Wasm contract. For example, if the Wasm light client contract implements the GRANDPA light client algorithm, then `Data` will contain the bytes for a [GRANDPA consensus state](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L87-L94). + +```go +type ConsensusState struct { + // bytes encoding the consensus state of the underlying light client + // implemented as a Wasm contract. + Data []byte +} +``` + +See section [`ConsensusState` of the light client module developer guide](../01-developer-guide/01-overview.md#consensusstate) for more information about the `ConsensusState` interface. + +### `ClientMessage` + +`ClientMessage` is used for performing updates to a `ClientState` stored on chain. The `08-wasm`'s `ClientMessage` data structure maintains one field: + +- `Data` contains the bytes of the Protobuf-encoded header(s) or misbehaviour for the underlying light client implemented as a Wasm contract. For example, if the Wasm light client contract implements the GRANDPA light client algorithm, then `Data` will contain the bytes of either [header](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L96-L104) or [misbehaviour](https://github.com/ComposableFi/composable-ibc/blob/02ce69e2843e7986febdcf795f69a757ce569272/light-clients/ics10-grandpa/src/proto/grandpa.proto#L106-L112) for a GRANDPA light client. + +```go +type ClientMessage struct { + // bytes encoding the header(s) or misbehaviour for the underlying light client + // implemented as a Wasm contract. + Data []byte +} +``` + +See section [`ClientMessage` of the light client module developer guide](../01-developer-guide/01-overview.md#clientmessage) for more information about the `ClientMessage` interface. + +## Wasm light client + +The actual light client can be implemented in any language that compiles to Wasm and implements the interfaces of a [CosmWasm](https://docs.cosmwasm.com/docs/) contract. Even though in theory other languages could be used, in practice (at least for the time being) the most suitable language to use would be Rust, since there is already good support for it for developing CosmWasm smart contracts. + +At the moment of writing there are two contracts available: one for [Tendermint](https://github.com/ComposableFi/composable-ibc/tree/master/light-clients/ics07-tendermint-cw) and one [GRANDPA](https://github.com/ComposableFi/composable-ibc/tree/master/light-clients/ics10-grandpa-cw) (which is being used in production in [Composable Finance's Centauri bridge](https://github.com/ComposableFi/composable-ibc)). And there are others in development (e.g. for Near). diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/03-integration.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/03-integration.md new file mode 100644 index 0000000..ebd7f49 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/03-integration.md @@ -0,0 +1,396 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 3 +slug: /ibc/light-clients/wasm/integration +--- + +# Integration + +Learn how to integrate the `08-wasm` module in a chain binary and about the recommended approaches depending on whether the [`x/wasm` module](https://github.com/CosmWasm/wasmd/tree/main/x/wasm) is already used in the chain. The following document only applies for Cosmos SDK chains. + +## Importing the `08-wasm` module + +`08-wasm` has no stable releases yet. To use it, you need to import the git commit that contains the module with the compatible versions of `ibc-go` and `wasmvm`. To do so, run the following command with the desired git commit in your project: + +```sh +go get github.com/cosmos/ibc-go/modules/light-clients/08-wasm@57fcdb9a9a9db9b206f7df2f955866dc4e10fef4 +``` + +You can find the version matrix in [here](../../../../docs/03-light-clients/04-wasm/03-integration.md#importing-the-08-wasm-module). + +## `app.go` setup + +The sample code below shows the relevant integration points in `app.go` required to setup the `08-wasm` module in a chain binary. Since `08-wasm` is a light client module itself, please check out as well the section [Integrating light clients](../../01-ibc/02-integration.md#integrating-light-clients) for more information: + +```go +// app.go +import ( + ... + "github.com/cosmos/cosmos-sdk/runtime" + + cmtos "github.com/cometbft/cometbft/libs/os" + + ibcwasm "github.com/cosmos/ibc-go/modules/light-clients/08-wasm" + ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" + ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + ... +) + +... + +// Register the AppModule for the 08-wasm module +ModuleBasics = module.NewBasicManager( + ... + ibcwasm.AppModuleBasic{}, + ... +) + +// Add 08-wasm Keeper +type SimApp struct { + ... + WasmClientKeeper ibcwasmkeeper.Keeper + ... +} + +func NewSimApp( + logger log.Logger, + db dbm.DB, + traceStore io.Writer, + loadLatest bool, + appOpts servertypes.AppOptions, + baseAppOptions ...func(*baseapp.BaseApp), +) *SimApp { + ... + keys := sdk.NewKVStoreKeys( + ... + ibcwasmtypes.StoreKey, + ) + + // Instantiate 08-wasm's keeper + // This sample code uses a constructor function that + // accepts a pointer to an existing instance of Wasm VM. + // This is the recommended approach when the chain + // also uses `x/wasm`, and then the Wasm VM instance + // can be shared. + app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM( + appCodec, + runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmVM, + app.GRPCQueryRouter(), + ) + app.ModuleManager = module.NewManager( + // SDK app modules + ... + ibcwasm.NewAppModule(app.WasmClientKeeper), + ) + app.ModuleManager.SetOrderBeginBlockers( + ... + ibcwasmtypes.ModuleName, + ... + ) + app.ModuleManager.SetOrderEndBlockers( + ... + ibcwasmtypes.ModuleName, + ... + ) + genesisModuleOrder := []string{ + ... + ibcwasmtypes.ModuleName, + ... + } + app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...) + app.ModuleManager.SetOrderExportGenesis(genesisModuleOrder...) + ... + + // initialize BaseApp + app.SetInitChainer(app.InitChainer) + ... + + // must be before Loading version + if manager := app.SnapshotManager(); manager != nil { + err := manager.RegisterExtensions( + ibcwasmkeeper.NewWasmSnapshotter(app.CommitMultiStore(), &app.WasmClientKeeper), + ) + if err != nil { + panic(fmt.Errorf("failed to register snapshot extension: %s", err)) + } + } + ... + + if loadLatest { + ... + + ctx := app.BaseApp.NewUncachedContext(true, cmtproto.Header{}) + + // Initialize pinned codes in wasmvm as they are not persisted there + if err := ibcwasmkeeper.InitializePinnedCodes(ctx); err != nil { + cmtos.Exit(fmt.Sprintf("failed initialize pinned codes %s", err)) + } + } +} +``` + +## Keeper instantiation + +When it comes to instantiating `08-wasm`'s keeper there are two recommended ways of doing it. Choosing one or the other will depend on whether the chain already integrates [`x/wasm`](https://github.com/CosmWasm/wasmd/tree/main/x/wasm) or not. + +### If `x/wasm` is present + +If the chain where the module is integrated uses `x/wasm` then we recommend that both `08-wasm` and `x/wasm` share the same Wasm VM instance. Having two separate Wasm VM instances is still possible, but care should be taken to make sure that both instances do not share the directory when the VM stores blobs and various caches, otherwise unexpected behaviour is likely to happen. + +In order to share the Wasm VM instance please follow the guideline below. Please note that this requires `x/wasm`v0.41 or above. + +- Instantiate the Wasm VM in `app.go` with the parameters of your choice. +- [Create an `Option` with this Wasm VM instance](https://github.com/CosmWasm/wasmd/blob/db93d7b6c7bb6f4a340d74b96a02cec885729b59/x/wasm/keeper/options.go#L21-L25). +- Add the option created in the previous step to a slice and [pass it to the `x/wasm NewKeeper` constructor function](https://github.com/CosmWasm/wasmd/blob/db93d7b6c7bb6f4a340d74b96a02cec885729b59/x/wasm/keeper/keeper_cgo.go#L36). +- Pass the pointer to the Wasm VM instance to `08-wasm` [`NewKeeperWithVM` constructor function](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/keeper/keeper.go#L39-L47). + +The code to set this up would look something like this: + +```go +// app.go +import ( + ... + "github.com/cosmos/cosmos-sdk/runtime" + + wasmvm "github.com/CosmWasm/wasmvm" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + + ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" + ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + ... +) + +... + +// instantiate the Wasm VM with the chosen parameters +wasmer, err := wasmvm.NewVM( + dataDir, + availableCapabilities, + contractMemoryLimit, // default of 32 + contractDebugMode, + memoryCacheSize, +) +if err != nil { + panic(err) +} + +// create an Option slice (or append to an existing one) +// with the option to use a custom Wasm VM instance +wasmOpts = []wasmkeeper.Option{ + wasmkeeper.WithWasmEngine(wasmer), +} + +// the keeper will use the provided Wasm VM instance, +// instead of instantiating a new one +app.WasmKeeper = wasmkeeper.NewKeeper( + appCodec, + keys[wasmtypes.StoreKey], + app.AccountKeeper, + app.BankKeeper, + app.StakingKeeper, + distrkeeper.NewQuerier(app.DistrKeeper), + app.IBCFeeKeeper, // ISC4 Wrapper: fee IBC middleware + app.IBCKeeper.ChannelKeeper, + &app.IBCKeeper.PortKeeper, + scopedWasmKeeper, + app.TransferKeeper, + app.MsgServiceRouter(), + app.GRPCQueryRouter(), + wasmDir, + wasmConfig, + availableCapabilities, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmOpts..., +) + +app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM( + appCodec, + runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmer, // pass the Wasm VM instance to `08-wasm` keeper constructor + app.GRPCQueryRouter(), +) +... +``` + +### If `x/wasm` is not present + +If the chain does not use [`x/wasm`](https://github.com/CosmWasm/wasmd/tree/main/x/wasm), even though it is still possible to use the method above from the previous section +(e.g. instantiating a Wasm VM in app.go an pass it to 08-wasm's [`NewKeeperWithVM` constructor function](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/keeper/keeper.go#L39-L47), since there would be no need in this case to share the Wasm VM instance with another module, you can use the [`NewKeeperWithConfig` constructor function](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/keeper/keeper.go#L88-L96) and provide the Wasm VM configuration parameters of your choice instead. A Wasm VM instance will be created in `NewKeeperWithConfig`. The parameters that can set are: + +- `DataDir` is the [directory for Wasm blobs and various caches](https://github.com/CosmWasm/wasmvm/blob/1638725b25d799f078d053391945399cb35664b1/lib.go#L25). In `wasmd` this is set to the [`wasm` folder under the home directory](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/app/app.go#L578). +- `SupportedCapabilities` is a comma separated [list of capabilities supported by the chain](https://github.com/CosmWasm/wasmvm/blob/1638725b25d799f078d053391945399cb35664b1/lib.go#L26). [`wasmd` sets this to all the available capabilities](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/app/app.go#L586), but 08-wasm only requires `iterator`. +- `MemoryCacheSize` sets [the size in MiB of an in-memory cache for e.g. module caching](https://github.com/CosmWasm/wasmvm/blob/1638725b25d799f078d053391945399cb35664b1/lib.go#L29C16-L29C104). It is not consensus-critical and should be defined on a per-node basis, often in the range 100 to 1000 MB. [`wasmd` reads this value of](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/app/app.go#L579). Default value is 256. +- `ContractDebugMode` is a [flag to enable/disable printing debug logs from the contract to STDOUT](https://github.com/CosmWasm/wasmvm/blob/1638725b25d799f078d053391945399cb35664b1/lib.go#L28). This should be false in production environments. Default value is false. + +Another configuration parameter of the Wasm VM is the contract memory limit (in MiB), which is [set to 32](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/types/config.go#L8), [following the example of `wasmd`](https://github.com/CosmWasm/wasmd/blob/36416def20effe47fb77f29f5ba35a003970fdba/x/wasm/keeper/keeper.go#L32-L34). This parameter is not configurable by users of `08-wasm`. + +The following sample code shows how the keeper would be constructed using this method: + +```go +// app.go +import ( + ... + "github.com/cosmos/cosmos-sdk/runtime" + + ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" + ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + ... +) + +... + +// homePath is the path to the directory where the data +// directory for Wasm blobs and caches will be created +wasmConfig := ibcwasmtypes.WasmConfig{ + DataDir: filepath.Join(homePath, "ibc_08-wasm_client_data"), + SupportedCapabilities: "iterator", + ContractDebugMode: false, +} +app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithConfig( + appCodec, + runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmConfig, + app.GRPCQueryRouter(), +) +``` + +Check out also the [`WasmConfig` type definition](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/types/config.go#L21-L31) for more information on each of the configurable parameters. Some parameters allow node-level configurations. There is additionally the function [`DefaultWasmConfig`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/types/config.go#L36-L42) available that returns a configuration with the default values. + +### Options + +The `08-wasm` module comes with an options API inspired by the one in `x/wasm`. +Currently the only option available is the `WithQueryPlugins` option, which allows registration of custom query plugins for the `08-wasm` module. The use of this API is optional and it is only required if the chain wants to register custom query plugins for the `08-wasm` module. + +#### `WithQueryPlugins` + +By default, the `08-wasm` module does not configure any querier options for light client contracts. However, it is possible to register custom query plugins for [`QueryRequest::Custom`](https://github.com/CosmWasm/cosmwasm/blob/v2.0.1/packages/std/src/query/mod.rs#L48) and [`QueryRequest::Stargate`](https://github.com/CosmWasm/cosmwasm/blob/v2.0.1/packages/std/src/query/mod.rs#L57-L65). + +Assuming that the keeper is not yet instantiated, the following sample code shows how to register query plugins for the `08-wasm` module. + +We first construct a [`QueryPlugins`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/types/querier.go#L78-L87) object with the desired query plugins: + +```go +queryPlugins := ibcwasmtypes.QueryPlugins { + Custom: MyCustomQueryPlugin(), + // `myAcceptList` is a `[]string` containing the list of gRPC query paths that the chain wants to allow for the `08-wasm` module to query. + // These queries must be registered in the chain's gRPC query router, be deterministic, and track their gas usage. + // The `AcceptListStargateQuerier` function will return a query plugin that will only allow queries for the paths in the `myAcceptList`. + // The query responses are encoded in protobuf unlike the implementation in `x/wasm`. + Stargate: ibcwasmtypes.AcceptListStargateQuerier(myAcceptList), +} +``` + +Note that the `Stargate` querier appends the user defined accept list of query routes to a default list defined by the `08-wasm` module. +The `defaultAcceptList` defines a single query route: `"/ibc.core.client.v1.Query/VerifyMembership"`. This allows for light client smart contracts to delegate parts of their workflow to other light clients for auxiliary proof verification. For example, proof of inclusion of block and tx data by a data availability provider. + +```go +// defaultAcceptList defines a set of default allowed queries made available to the Querier. +var defaultAcceptList = []string{ + "/ibc.core.client.v1.Query/VerifyMembership", +} +``` + +You may leave any of the fields in the `QueryPlugins` object as `nil` if you do not want to register a query plugin for that query type. + +Then, we pass the `QueryPlugins` object to the `WithQueryPlugins` option: + +```go +querierOption := ibcwasmkeeper.WithQueryPlugins(&queryPlugins) +``` + +Finally, we pass the option to the `NewKeeperWithConfig` or `NewKeeperWithVM` constructor function during [Keeper instantiation](#keeper-instantiation): + +```diff +app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithConfig( + appCodec, + runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmConfig, + app.GRPCQueryRouter(), ++ querierOption, +) +``` + +```diff +app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM( + appCodec, + runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + wasmer, // pass the Wasm VM instance to `08-wasm` keeper constructor + app.GRPCQueryRouter(), ++ querierOption, +) +``` + +## Updating `AllowedClients` + +If the chain's 02-client submodule parameter `AllowedClients` contains the single wildcard `"*"` element, then it is not necessary to do anything in order to allow the creation of `08-wasm` clients. However, if the parameter contains a list of client types (e.g. `["06-solomachine", "07-tendermint"]`), then in order to use the `08-wasm` module chains must update the [`AllowedClients` parameter](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/client/v1/client.proto#L64) of core IBC. This can be configured directly in the application upgrade handler with the sample code below: + +```go +import ( + ... + ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + ... +) + +... + +func CreateWasmUpgradeHandler( + mm *module.Manager, + configurator module.Configurator, + clientKeeper clientkeeper.Keeper, +) upgradetypes.UpgradeHandler { + return func(goCtx context.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + // explicitly update the IBC 02-client params, adding the wasm client type + params := clientKeeper.GetParams(ctx) + params.AllowedClients = append(params.AllowedClients, ibcwasmtypes.Wasm) + clientKeeper.SetParams(ctx, params) + + return mm.RunMigrations(goCtx, configurator, vm) + } +} +``` + +Or alternatively the parameter can be updated via a governance proposal (see at the bottom of section [`Creating clients`](../01-developer-guide/09-setup.md#creating-clients) for an example of how to do this). + +## Adding the module to the store + +As part of the upgrade migration you must also add the module to the upgrades store. + +```go +func (app SimApp) RegisterUpgradeHandlers() { + + ... + + if upgradeInfo.Name == UpgradeName && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { + storeUpgrades := storetypes.StoreUpgrades{ + Added: []string{ + ibcwasmtypes.ModuleName, + }, + } + + // configure store loader that checks if version == upgradeHeight and applies store upgrades + app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) + } +} +``` + +## Adding snapshot support + +In order to use the `08-wasm` module chains are required to register the `WasmSnapshotter` extension in the snapshot manager. This snapshotter takes care of persisting the external state, in the form of contract code, of the Wasm VM instance to disk when the chain is snapshotted. [This code](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/testing/simapp/app.go#L775-L782) should be placed in `NewSimApp` function in `app.go`. + +## Pin byte codes at start + +Wasm byte codes should be pinned to the WasmVM cache on every application start, therefore [this code](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/testing/simapp/app.go#L825-L830) should be placed in `NewSimApp` function in `app.go`. diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/04-messages.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/04-messages.md new file mode 100644 index 0000000..675cfd5 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/04-messages.md @@ -0,0 +1,75 @@ +--- +title: Messages +sidebar_label: Messages +sidebar_position: 4 +slug: /ibc/light-clients/wasm/messages +--- + +# Messages + +## `MsgStoreCode` + +Uploading the Wasm light client contract to the Wasm VM storage is achieved by means of `MsgStoreCode`: + +```go +type MsgStoreCode struct { + // signer address + Signer string + // wasm byte code of light client contract. It can be raw or gzip compressed + WasmByteCode []byte +} +``` + +This message is expected to fail if: + +- `Signer` is an invalid Bech32 address, or it does not match the designated authority address. +- `WasmByteCode` is empty or it exceeds the maximum size, currently set to 3MB. + +Only light client contracts stored using `MsgStoreCode` are allowed to be instantiated. An attempt to create a light client from contracts uploaded via other means (e.g. through `x/wasm` if the module shares the same Wasm VM instance with 08-wasm) will fail. Due to the idempotent nature of the Wasm VM's `StoreCode` function, it is possible to store the same byte code multiple times. + +When execution of `MsgStoreCode` succeeds, the checksum of the contract (i.e. the sha256 hash of the contract's byte code) is stored in an allow list. When a relayer submits [`MsgCreateClient`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/client/v1/tx.proto#L25-L37) with 08-wasm's `ClientState`, the client state includes the checksum of the Wasm byte code that should be called. Then 02-client calls [08-wasm's implementation of `Initialize` function](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/core/02-client/keeper/client.go#L36) (which is an interface function part of `ClientState`), and it will check that the checksum in the client state matches one of the checksums in the allow list. If a match is found, the light client is initialized; otherwise, the transaction is aborted. + +## `MsgMigrateContract` + +Migrating a contract to a new Wasm byte code is achieved by means of `MsgMigrateContract`: + +```go +type MsgMigrateContract struct { + // signer address + Signer string + // the client id of the contract + ClientId string + // the SHA-256 hash of the new wasm byte code for the contract + Checksum []byte + // the json-encoded migrate msg to be passed to the contract on migration + Msg []byte +} +``` + +This message is expected to fail if: + +- `Signer` is an invalid Bech32 address, or it does not match the designated authority address. +- `ClientId` is not a valid identifier prefixed by `08-wasm`. +- `Checksum` is not exactly 32 bytes long or it is not found in the list of allowed checksums (a new checksum is added to the list when executing `MsgStoreCode`), or it matches the current checksum of the contract. + +When a Wasm light client contract is migrated to a new Wasm byte code the checksum for the contract will be updated with the new checksum. + +## `MsgRemoveChecksum` + +Removing a checksum from the list of allowed checksums is achieved by means of `MsgRemoveChecksum`: + +```go +type MsgRemoveChecksum struct { + // signer address + Signer string + // Wasm byte code checksum to be removed from the store + Checksum []byte +} +``` + +This message is expected to fail if: + +- `Signer` is an invalid Bech32 address, or it does not match the designated authority address. +- `Checksum` is not exactly 32 bytes long or it is not found in the list of allowed checksums (a new checksum is added to the list when executing `MsgStoreCode`). + +When a checksum is removed from the list of allowed checksums, then the corresponding Wasm byte code will not be available for instantiation in [08-wasm's implementation of `Initialize` function](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/core/02-client/keeper/client.go#L36). diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/05-governance.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/05-governance.md new file mode 100644 index 0000000..7c4bc65 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/05-governance.md @@ -0,0 +1,126 @@ +--- +title: Governance +sidebar_label: Governance +sidebar_position: 5 +slug: /ibc/light-clients/wasm/governance +--- + +# Governance + +Learn how to upload Wasm light client byte code on a chain, and how to migrate an existing Wasm light client contract. + +## Setting an authority + +Both the storage of Wasm light client byte code as well as the migration of an existing Wasm light client contract are permissioned (i.e. only allowed to an authority such as governance). The designated authority is specified when instantiating `08-wasm`'s keeper: both [`NewKeeperWithVM`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/keeper/keeper.go#L39-L47) and [`NewKeeperWithConfig`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/keeper/keeper.go#L88-L96) constructor functions accept an `authority` argument that must be the address of the authorized actor. For example, in `app.go`, when instantiating the keeper, you can pass the address of the governance module: + +```go +// app.go +import ( + ... + "github.com/cosmos/cosmos-sdk/runtime" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/keeper" + ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + ... +) + +// app.go +app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM( + appCodec, + runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), + app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), // authority + wasmVM, + app.GRPCQueryRouter(), +) +``` + +## Storing new Wasm light client byte code + + If governance is the allowed authority, the governance v1 proposal that needs to be submitted to upload a new light client contract should contain the message [`MsgStoreCode`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/proto/ibc/lightclients/wasm/v1/tx.proto#L23-L30) with the base64-encoded byte code of the Wasm contract. Use the following CLI command and JSON as an example: + +```shell +simd tx gov submit-proposal --from +``` + +where `proposal.json` contains: + +```json +{ + "title": "Upload IBC Wasm light client", + "summary": "Upload wasm client", + "messages": [ + { + "@type": "/ibc.lightclients.wasm.v1.MsgStoreCode", + "signer": "cosmos1...", // the authority address (e.g. the gov module account address) + "wasm_byte_code": "YWJ...PUB+" // standard base64 encoding of the Wasm contract byte code + } + ], + "metadata": "AQ==", + "deposit": "100stake" +} +``` + +To learn more about the `submit-proposal` CLI command, please check out [the relevant section in Cosmos SDK documentation](https://docs.cosmos.network/main/modules/gov#submit-proposal). + +Alternatively, the process of submitting the proposal may be simpler if you use the CLI command `store-code`. This CLI command accepts as argument the file of the Wasm light client contract and takes care of constructing the proposal message with `MsgStoreCode` and broadcasting it. See section [`store-code`](./08-client.md#store-code) for more information. + +## Migrating an existing Wasm light client contract + +If governance is the allowed authority, the governance v1 proposal that needs to be submitted to migrate an existing new Wasm light client contract should contain the message [`MsgMigrateContract`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/proto/ibc/lightclients/wasm/v1/tx.proto#L52-L63) with the checksum of the Wasm byte code to migrate to. Use the following CLI command and JSON as an example: + +```shell +simd tx gov submit-proposal --from +``` + +where `proposal.json` contains: + +```json +{ + "title": "Migrate IBC Wasm light client", + "summary": "Migrate wasm client", + "messages": [ + { + "@type": "/ibc.lightclients.wasm.v1.MsgMigrateContract", + "signer": "cosmos1...", // the authority address (e.g. the gov module account address) + "client_id": "08-wasm-1", // client identifier of the Wasm light client contract that will be migrated + "checksum": "a8ad...4dc0", // SHA-256 hash of the Wasm byte code to migrate to, previously stored with MsgStoreCode + "msg": "{}" // JSON-encoded message to be passed to the contract on migration + } + ], + "metadata": "AQ==", + "deposit": "100stake" +} +``` + +To learn more about the `submit-proposal` CLI command, please check out [the relevant section in Cosmos SDK documentation](https://docs.cosmos.network/main/modules/gov#submit-proposal). + +## Removing an existing checksum + +If governance is the allowed authority, the governance v1 proposal that needs to be submitted to remove a specific checksum from the list of allowed checksums should contain the message [`MsgRemoveChecksum`](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/proto/ibc/lightclients/wasm/v1/tx.proto#L39-L46) with the checksum (of a corresponding Wasm byte code). Use the following CLI command and JSON as an example: + +```shell +simd tx gov submit-proposal --from +``` + +where `proposal.json` contains: + +```json +{ + "title": "Remove checksum of Wasm light client byte code", + "summary": "Remove checksum", + "messages": [ + { + "@type": "/ibc.lightclients.wasm.v1.MsgRemoveChecksum", + "signer": "cosmos1...", // the authority address (e.g. the gov module account address) + "checksum": "a8ad...4dc0", // SHA-256 hash of the Wasm byte code that should be removed from the list of allowed checksums + } + ], + "metadata": "AQ==", + "deposit": "100stake" +} +``` + +To learn more about the `submit-proposal` CLI command, please check out [the relevant section in Cosmos SDK documentation](https://docs.cosmos.network/main/modules/gov#submit-proposal). diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/06-events.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/06-events.md new file mode 100644 index 0000000..5d68ce9 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/06-events.md @@ -0,0 +1,26 @@ +--- +title: Events +sidebar_label: Events +sidebar_position: 6 +slug: /ibc/light-clients/wasm/events +--- + +# Events + +The `08-wasm` module emits the following events: + +## `MsgStoreCode` + +| Type | Attribute Key | Attribute Value | +|------------------|----------------|--------------------------| +| store_wasm_code | wasm_checksum | \{hex.Encode(checksum)\} | +| message | module | 08-wasm | + +## `MsgMigrateContract` + +| Type | Attribute Key | Attribute Value | +|------------------|----------------|-----------------------------| +| migrate_contract | client_id | \{clientId\} | +| migrate_contract | wasm_checksum | \{hex.Encode(checksum)\} | +| migrate_contract | new_checksum | \{hex.Encode(newChecksum)\} | +| message | module | 08-wasm | diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/07-contracts.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/07-contracts.md new file mode 100644 index 0000000..90f96c0 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/07-contracts.md @@ -0,0 +1,113 @@ +--- +title: Contracts +sidebar_label: Contracts +sidebar_position: 7 +slug: /ibc/light-clients/wasm/contracts +--- + +# Contracts + +Learn about the expected behaviour of Wasm light client contracts and the between with `08-wasm`. + +## API + +The `08-wasm` light client proxy performs calls to the Wasm light client via the Wasm VM. The calls require as input JSON-encoded payload messages that fall in the three categories described in the next sections. + +## `InstantiateMessage` + +This is the message sent to the contract's `instantiate` entry point. It contains the bytes of the protobuf-encoded client and consensus states of the underlying light client, both provided in [`MsgCreateClient`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/client/v1/tx.proto#L40-L52). Please note that the bytes contained within the JSON message are represented as base64-encoded strings. + +```go +type InstantiateMessage struct { + ClientState []byte `json:"client_state"` + ConsensusState []byte `json:"consensus_state"` + Checksum []byte `json:"checksum" +} +``` + +The Wasm light client contract is expected to store the client and consensus state in the corresponding keys of the client-prefixed store. + +## `QueryMsg` + +`QueryMsg` acts as a discriminated union type that is used to encode the messages that are sent to the contract's `query` entry point. Only one of the fields of the type should be set at a time, so that the other fields are omitted in the encoded JSON and the payload can be correctly translated to the corresponding element of the enumeration in Rust. + +```go +type QueryMsg struct { + Status *StatusMsg `json:"status,omitempty"` + ExportMetadata *ExportMetadataMsg `json:"export_metadata,omitempty"` + TimestampAtHeight *TimestampAtHeightMsg `json:"timestamp_at_height,omitempty"` + VerifyClientMessage *VerifyClientMessageMsg `json:"verify_client_message,omitempty"` + CheckForMisbehaviour *CheckForMisbehaviourMsg `json:"check_for_misbehaviour,omitempty"` +} +``` + +```rust +#[cw_serde] +pub enum QueryMsg { + Status(StatusMsg), + ExportMetadata(ExportMetadataMsg), + TimestampAtHeight(TimestampAtHeightMsg), + VerifyClientMessage(VerifyClientMessageRaw), + CheckForMisbehaviour(CheckForMisbehaviourMsgRaw), +} +``` + +To learn what it is expected from the Wasm light client contract when processing each message, please read the corresponding section of the [Light client developer guide](../01-developer-guide/01-overview.md): + +- For `StatusMsg`, see the section [`Status` method](../01-developer-guide/02-client-state.md#status-method). +- For `ExportMetadataMsg`, see the section [Genesis metadata](../01-developer-guide/08-genesis.md#genesis-metadata). +- For `TimestampAtHeightMsg`, see the section [`GetTimestampAtHeight` method](../01-developer-guide/02-client-state.md#gettimestampatheight-method). +- For `VerifyClientMessageMsg`, see the section [`VerifyClientMessage`](../01-developer-guide/04-updates-and-misbehaviour.md#verifyclientmessage). +- For `CheckForMisbehaviourMsg`, see the section [`CheckForMisbehaviour` method](../01-developer-guide/02-client-state.md#checkformisbehaviour-method). + +## `SudoMsg` + +`SudoMsg` acts as a discriminated union type that is used to encode the messages that are sent to the contract's `sudo` entry point. Only one of the fields of the type should be set at a time, so that the other fields are omitted in the encoded JSON and the payload can be correctly translated to the corresponding element of the enumeration in Rust. + +The `sudo` entry point is able to perform state-changing writes in the client-prefixed store. + +```go +type SudoMsg struct { + UpdateState *UpdateStateMsg `json:"update_state,omitempty"` + UpdateStateOnMisbehaviour *UpdateStateOnMisbehaviourMsg `json:"update_state_on_misbehaviour,omitempty"` + VerifyUpgradeAndUpdateState *VerifyUpgradeAndUpdateStateMsg `json:"verify_upgrade_and_update_state,omitempty"` + VerifyMembership *VerifyMembershipMsg `json:"verify_membership,omitempty"` + VerifyNonMembership *VerifyNonMembershipMsg `json:"verify_non_membership,omitempty"` + MigrateClientStore *MigrateClientStoreMsg `json:"migrate_client_store,omitempty"` +} +``` + +```rust +#[cw_serde] +pub enum SudoMsg { + UpdateState(UpdateStateMsgRaw), + UpdateStateOnMisbehaviour(UpdateStateOnMisbehaviourMsgRaw), + VerifyUpgradeAndUpdateState(VerifyUpgradeAndUpdateStateMsgRaw), + VerifyMembership(VerifyMembershipMsgRaw), + VerifyNonMembership(VerifyNonMembershipMsgRaw), + MigrateClientStore(MigrateClientStoreMsgRaw), +} +``` + +To learn what it is expected from the Wasm light client contract when processing each message, please read the corresponding section of the [Light client developer guide](../01-developer-guide/01-overview.md): + +- For `UpdateStateMsg`, see the section [`UpdateState`](../01-developer-guide/04-updates-and-misbehaviour.md#updatestate). +- For `UpdateStateOnMisbehaviourMsg`, see the section [`UpdateStateOnMisbehaviour`](../01-developer-guide/04-updates-and-misbehaviour.md#updatestateonmisbehaviour). +- For `VerifyUpgradeAndUpdateStateMsg`, see the section [`GetTimestampAtHeight` method](../01-developer-guide/05-upgrades.md#implementing-verifyupgradeandupdatestate). +- For `VerifyMembershipMsg`, see the section [`VerifyMembership` method](../01-developer-guide/02-client-state.md#verifymembership-method). +- For `VerifyNonMembershipMsg`, see the section [`VerifyNonMembership` method](../01-developer-guide/02-client-state.md#verifynonmembership-method). +- For `MigrateClientStoreMsg`, see the section [Implementing `CheckSubstituteAndUpdateState`](../01-developer-guide/07-proposals.md#implementing-checksubstituteandupdatestate). + +### Migration + +The `08-wasm` proxy light client exposes the `MigrateContract` RPC endpoint that can be used to migrate a given Wasm light client contract (specified by the client identifier) to a new Wasm byte code (specified by the hash of the byte code). The expected use case for this RPC endpoint is to enable contracts to migrate to new byte code in case the current byte code is found to have a bug or vulnerability. The Wasm byte code that contracts are migrated have to be uploaded beforehand using `MsgStoreCode` and must implement the `migrate` entry point. See section[`MsgMigrateContract`](./04-messages.md#msgmigratecontract) for information about the request message for this RPC endpoint. + +## Expected behaviour + +The `08-wasm` proxy light client modules expects the following behaviour from the Wasm light client contracts when executing messages that perform state-changing writes: + +- The contract must not delete the client state from the store. +- The contract must not change the client state to a client state of another type. +- The contract must not change the checksum in the client state. + +Any violation of these rules will result in an error returned from `08-wasm` that will abort the transaction. diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/08-client.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/08-client.md new file mode 100644 index 0000000..03b5e18 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/08-client.md @@ -0,0 +1,151 @@ +--- +title: Client +sidebar_label: Client +sidebar_position: 7 +slug: /ibc/light-clients/wasm/client +--- + +# Client + +## CLI + +A user can query and interact with the `08-wasm` module using the CLI. Use the `--help` flag to discover the available commands: + +### Transactions + +The `tx` commands allow users to interact with the `08-wasm` submodule. + +```shell +simd tx ibc-wasm --help +``` + +#### `store-code` + +The `store-code` command allows users to submit a governance proposal with a `MsgStoreCode` to store the byte code of a Wasm light client contract. + +```shell +simd tx ibc-wasm store-code [path/to/wasm-file] [flags] +``` + +`path/to/wasm-file` is the path to the `.wasm` or `.wasm.gz` file. + +#### `migrate-contract` + +The `migrate-contract` command allows users to broadcast a transaction with a `MsgMigrateContract` to migrate the contract for a given light client to a new byte code denoted by the given checksum. + +```shell +simd tx ibc-wasm migrate-contract [client-id] [checksum] [migrate-msg] +``` + +The migrate message must not be empty and is expected to be a JSON-encoded string. + +### Query + +The `query` commands allow users to query `08-wasm` state. + +```shell +simd query ibc-wasm --help +``` + +#### `checksums` + +The `checksums` command allows users to query the list of checksums of Wasm light client contracts stored in the Wasm VM via the `MsgStoreCode`. The checksums are hex-encoded. + +```shell +simd query ibc-wasm checksums [flags] +``` + +Example: + +```shell +simd query ibc-wasm checksums +``` + +Example Output: + +```shell +checksums: +- c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64 +pagination: + next_key: null + total: "1" +``` + +#### `code` + +The `code` command allows users to query the Wasm byte code of a light client contract given the provided input checksum. + +```shell +./simd q ibc-wasm code +``` + +Example: + +```shell +simd query ibc-wasm code c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64 +``` + +Example Output: + +```shell +code: AGFzb...AqBBE= +``` + +## gRPC + +A user can query the `08-wasm` module using gRPC endpoints. + +### `Checksums` + +The `Checksums` endpoint allows users to query the list of checksums of Wasm light client contracts stored in the Wasm VM via the `MsgStoreCode`. + +```shell +ibc.lightclients.wasm.v1.Query/Checksums +``` + +Example: + +```shell +grpcurl -plaintext \ + -d '{}' \ + localhost:9090 \ + ibc.lightclients.wasm.v1.Query/Checksums +``` + +Example output: + +```shell +{ + "checksums": [ + "c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64" + ], + "pagination": { + "total": "1" + } +} +``` + +### `Code` + +The `Code` endpoint allows users to query the Wasm byte code of a light client contract given the provided input checksum. + +```shell +ibc.lightclients.wasm.v1.Query/Code +``` + +Example: + +```shell +grpcurl -plaintext \ + -d '{"checksum":"c64f75091a6195b036f472cd8c9f19a56780b9eac3c3de7ced0ec2e29e985b64"}' \ + localhost:9090 \ + ibc.lightclients.wasm.v1.Query/Code +``` + +Example output: + +```shell +{ + "code": AGFzb...AqBBE= +} +``` diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/09-migrations.md b/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/09-migrations.md new file mode 100644 index 0000000..6faac5f --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/09-migrations.md @@ -0,0 +1,103 @@ +--- +title: Migrations +sidebar_label: Migrations +sidebar_position: 9 +slug: /ibc/light-clients/wasm/migrations +--- + +# Migrations + +This guide provides instructions for migrating 08-wasm versions. + +## From ibc-go v7.3.x to ibc-go v8.0.x + +## Chains + +In the 08-wasm versions compatible with ibc-go v7.3.x and above from the v7 release line, the checksums of the uploaded Wasm bytecodes are all stored under a single key. From ibc-go v8.0.x the checksums are stored using [`collections.KeySet`](https://docs.cosmos.network/v0.50/build/packages/collections#keyset), whose full functionality became available in Cosmos SDK v0.50. There is therefore an [automatic migration handler](https://github.com/cosmos/ibc-go/blob/57fcdb9a9a9db9b206f7df2f955866dc4e10fef4/modules/light-clients/08-wasm/module.go#L115-L118) configured in the 08-wasm module to migrate the stored checksums to `collections.KeySet`. + +## From v0.1.0+ibc-go-v8.0-wasmvm-v1.5 to v0.2.0-ibc-go-v8.3-wasmvm-v2.0 + +The `WasmEngine` interface has been updated to reflect changes in the function signatures of Wasm VM: + +```diff +type WasmEngine interface { +- StoreCode(code wasmvm.WasmCode) (wasmvm.Checksum, error) ++ StoreCode(code wasmvm.WasmCode, gasLimit uint64) (wasmvmtypes.Checksum, uint64, error) + + StoreCodeUnchecked(code wasmvm.WasmCode) (wasmvm.Checksum, error) + + Instantiate( + checksum wasmvm.Checksum, + env wasmvmtypes.Env, + info wasmvmtypes.MessageInfo, + initMsg []byte, + store wasmvm.KVStore, + goapi wasmvm.GoAPI, + querier wasmvm.Querier, + gasMeter wasmvm.GasMeter, + gasLimit uint64, + deserCost wasmvmtypes.UFraction, +- ) (*wasmvmtypes.Response, uint64, error) ++ ) (*wasmvmtypes.ContractResult, uint64, error) + + Query( + checksum wasmvm.Checksum, + env wasmvmtypes.Env, + queryMsg []byte, + store wasmvm.KVStore, + goapi wasmvm.GoAPI, + querier wasmvm.Querier, + gasMeter wasmvm.GasMeter, + gasLimit uint64, + deserCost wasmvmtypes.UFraction, +- ) ([]byte, uint64, error) ++ ) (*wasmvmtypes.QueryResult, uint64, error) + + Migrate( + checksum wasmvm.Checksum, + env wasmvmtypes.Env, + migrateMsg []byte, + store wasmvm.KVStore, + goapi wasmvm.GoAPI, + querier wasmvm.Querier, + gasMeter wasmvm.GasMeter, + gasLimit uint64, + deserCost wasmvmtypes.UFraction, +- ) (*wasmvmtypes.Response, uint64, error) ++ ) (*wasmvmtypes.ContractResult, uint64, error) + + Sudo( + checksum wasmvm.Checksum, + env wasmvmtypes.Env, + sudoMsg []byte, + store wasmvm.KVStore, + goapi wasmvm.GoAPI, + querier wasmvm.Querier, + gasMeter wasmvm.GasMeter, + gasLimit uint64, + deserCost wasmvmtypes.UFraction, +- ) (*wasmvmtypes.Response, uint64, error) ++ ) (*wasmvmtypes.ContractResult, uint64, error) + + GetCode(checksum wasmvm.Checksum) (wasmvm.WasmCode, error) + + Pin(checksum wasmvm.Checksum) error + + Unpin(checksum wasmvm.Checksum) error +} +``` + +Similar changes were required in the functions of the `MockWasmEngine` interface. + +### Chains + +The `SupportedCapabilities` field of `WasmConfig` is now of type `[]string`: + +```diff +type WasmConfig struct { + DataDir string +- SupportedCapabilities string ++ SupportedCapabilities []string + ContractDebugMode bool +} +``` diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/_category_.json b/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/_category_.json new file mode 100644 index 0000000..51c4eb7 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/04-wasm/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Wasm", + "position": 4, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v8.5.x/03-light-clients/_category_.json b/docs/versioned_docs/version-v8.5.x/03-light-clients/_category_.json new file mode 100644 index 0000000..e42f1b9 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/03-light-clients/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "IBC Light Clients", + "position": 3, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/01-overview.md b/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/01-overview.md new file mode 100644 index 0000000..fbac6cd --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/01-overview.md @@ -0,0 +1,54 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /middleware/ics29-fee/overview +--- + +# Overview + +:::note Synopsis +Learn about what the Fee Middleware module is, and how to build custom modules that utilize the Fee Middleware functionality +::: + +## What is the Fee Middleware module? + +IBC does not depend on relayer operators for transaction verification. However, the relayer infrastructure ensures liveness of the Interchain network — operators listen for packets sent through channels opened between chains, and perform the vital service of ferrying these packets (and proof of the transaction on the sending chain/receipt on the receiving chain) to the clients on each side of the channel. + +Though relaying is permissionless and completely decentralized and accessible, it does come with operational costs. Running full nodes to query transaction proofs and paying for transaction fees associated with IBC packets are two of the primary cost burdens which have driven the overall discussion on **a general, in-protocol incentivization mechanism for relayers**. + +Initially, a [simple proposal](https://github.com/cosmos/ibc/pull/577/files) was created to incentivize relaying on ICS20 token transfers on the destination chain. However, the proposal was specific to ICS20 token transfers and would have to be reimplemented in this format on every other IBC application module. + +After much discussion, the proposal was expanded to a [general incentivisation design](https://github.com/cosmos/ibc/tree/master/spec/app/ics-029-fee-payment) that can be adopted by any ICS application protocol as [middleware](../../01-ibc/04-middleware/02-develop.md). + +## Concepts + +ICS29 fee payments in this middleware design are built on the assumption that sender chains are the source of incentives — the chain on which packets are incentivized is the chain that distributes fees to relayer operators. However, as part of the IBC packet flow, messages have to be submitted on both sender and destination chains. This introduces the requirement of a mapping of relayer operator's addresses on both chains. + +To achieve the stated requirements, the **fee middleware module has two main groups of functionality**: + +- Registering of relayer addresses associated with each party involved in relaying the packet on the source chain. This registration process can be automated on start up of relayer infrastructure and happens only once, not every packet flow. + + This is described in the [Fee distribution section](04-fee-distribution.md). + +- Escrowing fees by any party which will be paid out to each rightful party on completion of the packet lifecycle. + + This is described in the [Fee messages section](03-msgs.md). + +We complete the introduction by giving a list of definitions of relevant terminology. + +`Forward relayer`: The relayer that submits the `MsgRecvPacket` message for a given packet (on the destination chain). + +`Reverse relayer`: The relayer that submits the `MsgAcknowledgement` message for a given packet (on the source chain). + +`Timeout relayer`: The relayer that submits the `MsgTimeout` or `MsgTimeoutOnClose` messages for a given packet (on the source chain). + +`Payee`: The account address on the source chain to be paid on completion of the packet lifecycle. The packet lifecycle on the source chain completes with the receipt of a `MsgTimeout`/`MsgTimeoutOnClose` or a `MsgAcknowledgement`. + +`Counterparty payee`: The account address to be paid on completion of the packet lifecycle on the destination chain. The package lifecycle on the destination chain completes with a successful `MsgRecvPacket`. + +`Refund address`: The address of the account paying for the incentivization of packet relaying. The account is refunded timeout fees upon successful acknowledgement. In the event of a packet timeout, both acknowledgement and receive fees are refunded. + +## Known Limitations + +The first version of fee payments middleware will only support incentivisation of new channels, however, channel upgradeability will enable incentivisation of all existing channels. diff --git a/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/02-integration.md b/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/02-integration.md new file mode 100644 index 0000000..c808ac7 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/02-integration.md @@ -0,0 +1,177 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /middleware/ics29-fee/integration +--- + +# Integration + +:::note Synopsis +Learn how to configure the Fee Middleware module with IBC applications. The following document is intended for developers building on top of the Cosmos SDK and only applies for Cosmos SDK chains. +::: + +:::note + +## Pre-requisite Readings + +- [IBC middleware development](../../01-ibc/04-middleware/02-develop.md) +- [IBC middleware integration](../../01-ibc/04-middleware/03-integration.md) + +::: + +The Fee Middleware module, as the name suggests, plays the role of an IBC middleware and as such must be configured by chain developers to route and handle IBC messages correctly. +For Cosmos SDK chains this setup is done via the `app/app.go` file, where modules are constructed and configured in order to bootstrap the blockchain application. + +## Example integration of the Fee Middleware module + +```go +// app.go + +// Register the AppModule for the fee middleware module +ModuleBasics = module.NewBasicManager( + ... + ibcfee.AppModuleBasic{}, + ... +) + +... + +// Add module account permissions for the fee middleware module +maccPerms = map[string][]string{ + ... + ibcfeetypes.ModuleName: nil, +} + +... + +// Add fee middleware Keeper +type App struct { + ... + + IBCFeeKeeper ibcfeekeeper.Keeper + + ... +} + +... + +// Create store keys +keys := sdk.NewKVStoreKeys( + ... + ibcfeetypes.StoreKey, + ... +) + +... + +app.IBCFeeKeeper = ibcfeekeeper.NewKeeper( + appCodec, keys[ibcfeetypes.StoreKey], + app.IBCKeeper.ChannelKeeper, // may be replaced with IBC middleware + app.IBCKeeper.ChannelKeeper, + &app.IBCKeeper.PortKeeper, app.AccountKeeper, app.BankKeeper, +) + + +// See the section below for configuring an application stack with the fee middleware module + +... + +// Register fee middleware AppModule +app.moduleManager = module.NewManager( + ... + ibcfee.NewAppModule(app.IBCFeeKeeper), +) + +... + +// Add fee middleware to begin blocker logic +app.moduleManager.SetOrderBeginBlockers( + ... + ibcfeetypes.ModuleName, + ... +) + +// Add fee middleware to end blocker logic +app.moduleManager.SetOrderEndBlockers( + ... + ibcfeetypes.ModuleName, + ... +) + +// Add fee middleware to init genesis logic +app.moduleManager.SetOrderInitGenesis( + ... + ibcfeetypes.ModuleName, + ... +) +``` + +## Configuring an application stack with Fee Middleware + +As mentioned in [IBC middleware development](../../01-ibc/04-middleware/02-develop.md) an application stack may be composed of many or no middlewares that nest a base application. +These layers form the complete set of application logic that enable developers to build composable and flexible IBC application stacks. +For example, an application stack may be just a single base application like `transfer`, however, the same application stack composed with `29-fee` will nest the `transfer` base application +by wrapping it with the Fee Middleware module. + +### Transfer + +See below for an example of how to create an application stack using `transfer` and `29-fee`. +The following `transferStack` is configured in `app/app.go` and added to the IBC `Router`. +The in-line comments describe the execution flow of packets between the application stack and IBC core. + +```go +// Create Transfer Stack +// SendPacket, since it is originating from the application to core IBC: +// transferKeeper.SendPacket -> fee.SendPacket -> channel.SendPacket + +// RecvPacket, message that originates from core IBC and goes down to app, the flow is the other way +// channel.RecvPacket -> fee.OnRecvPacket -> transfer.OnRecvPacket + +// transfer stack contains (from top to bottom): +// - IBC Fee Middleware +// - Transfer + +// create IBC module from bottom to top of stack +var transferStack porttypes.IBCModule +transferStack = transfer.NewIBCModule(app.TransferKeeper) +transferStack = ibcfee.NewIBCMiddleware(transferStack, app.IBCFeeKeeper) + +// Add transfer stack to IBC Router +ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) +``` + +### Interchain Accounts + +See below for an example of how to create an application stack using `27-interchain-accounts` and `29-fee`. +The following `icaControllerStack` and `icaHostStack` are configured in `app/app.go` and added to the IBC `Router` with the associated authentication module. +The in-line comments describe the execution flow of packets between the application stack and IBC core. + +```go +// Create Interchain Accounts Stack +// SendPacket, since it is originating from the application to core IBC: +// icaAuthModuleKeeper.SendTx -> icaController.SendPacket -> fee.SendPacket -> channel.SendPacket + +// initialize ICA module with mock module as the authentication module on the controller side +var icaControllerStack porttypes.IBCModule +icaControllerStack = ibcmock.NewIBCModule(&mockModule, ibcmock.NewMockIBCApp("", scopedICAMockKeeper)) +app.ICAAuthModule = icaControllerStack.(ibcmock.IBCModule) +icaControllerStack = icacontroller.NewIBCMiddleware(icaControllerStack, app.ICAControllerKeeper) +icaControllerStack = ibcfee.NewIBCMiddleware(icaControllerStack, app.IBCFeeKeeper) + +// RecvPacket, message that originates from core IBC and goes down to app, the flow is: +// channel.RecvPacket -> fee.OnRecvPacket -> icaHost.OnRecvPacket + +var icaHostStack porttypes.IBCModule +icaHostStack = icahost.NewIBCModule(app.ICAHostKeeper) +icaHostStack = ibcfee.NewIBCMiddleware(icaHostStack, app.IBCFeeKeeper) + +// Add authentication module, controller and host to IBC router +ibcRouter. + // the ICA Controller middleware needs to be explicitly added to the IBC Router because the + // ICA controller module owns the port capability for ICA. The ICA authentication module + // owns the channel capability. + AddRoute(ibcmock.ModuleName+icacontrollertypes.SubModuleName, icaControllerStack) // ica with mock auth module stack route to ica (top level of middleware stack) + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icahosttypes.SubModuleName, icaHostStack). +``` diff --git a/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/03-msgs.md b/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/03-msgs.md new file mode 100644 index 0000000..eadf087 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/03-msgs.md @@ -0,0 +1,97 @@ +--- +title: Fee Messages +sidebar_label: Fee Messages +sidebar_position: 3 +slug: /middleware/ics29-fee/msgs +--- + +# Fee messages + +:::note Synopsis +Learn about the different ways to pay for fees, how the fees are paid out and what happens when not enough escrowed fees are available for payout +::: + +## Escrowing fees + +The fee middleware module exposes two different ways to pay fees for relaying IBC packets: + +### `MsgPayPacketFee` + +`MsgPayPacketFee` enables the escrowing of fees for a packet at the next sequence send and should be combined into one `MultiMsgTx` with the message that will be paid for. Note that the `Relayers` field has been set up to allow for an optional whitelist of relayers permitted to receive this fee, however, this feature has not yet been enabled at this time. + +```go +type MsgPayPacketFee struct{ + // fee encapsulates the recv, ack and timeout fees associated with an IBC packet + Fee Fee + // the source port unique identifier + SourcePortId string + // the source channel unique identifier + SourceChannelId string + // account address to refund fee if necessary + Signer string + // optional list of relayers permitted to the receive packet fee + Relayers []string +} +``` + +The `Fee` message contained in this synchronous fee payment method configures different fees which will be paid out for `MsgRecvPacket`, `MsgAcknowledgement`, and `MsgTimeout`/`MsgTimeoutOnClose`. +The amount of fees escrowed in total is the denomwise maximum of `RecvFee + AckFee` and `TimeoutFee`. This is because we do not know whether the packet will be successfully received and acknowledged or whether it will timeout. + +```go +type Fee struct { + RecvFee types.Coins + AckFee types.Coins + TimeoutFee types.Coins +} +``` + +The diagram below shows the `MultiMsgTx` with the `MsgTransfer` coming from a token transfer message, along with `MsgPayPacketFee`. + +![msgpaypacket.png](./images/msgpaypacket.png) + +### `MsgPayPacketFeeAsync` + +`MsgPayPacketFeeAsync` enables the asynchronous escrowing of fees for a specified packet. Note that a packet can be 'topped up' multiple times with additional fees of any coin denomination by broadcasting multiple `MsgPayPacketFeeAsync` messages. + +```go +type MsgPayPacketFeeAsync struct { + // unique packet identifier comprised of the channel ID, port ID and sequence + PacketId channeltypes.PacketId + // the packet fee associated with a particular IBC packet + PacketFee PacketFee +} +``` + +where the `PacketFee` also specifies the `Fee` to be paid as well as the refund address for fees which are not paid out + +```go +type PacketFee struct { + Fee Fee + RefundAddress string + Relayers []string +} +``` + +The diagram below shows how multiple `MsgPayPacketFeeAsync` can be broadcasted asynchronously. Escrowing of the fee associated with a packet can be carried out by any party because ICS-29 does not dictate a particular fee payer. In fact, chains can choose to simply not expose this fee payment to end users at all and rely on a different module account or even the community pool as the source of relayer incentives. + +![paypacketfeeasync.png](./images/paypacketfeeasync.png) + +Please see our [wiki](https://github.com/cosmos/ibc-go/wiki/Fee-enabled-fungible-token-transfers) for example flows on how to use these messages to incentivise a token transfer channel using a CLI. + +## Paying out the escrowed fees + +Following diagram takes a look at the packet flow for an incentivized token transfer and investigates the several scenario's for paying out the escrowed fees. We assume that the relayers have registered their counterparty address, detailed in the [Fee distribution section](04-fee-distribution.md). + +![feeflow.png](./images/feeflow.png) + +- In the case of a successful transaction, `RecvFee` will be paid out to the designated counterparty payee address which has been registered on the receiver chain and sent back with the `MsgAcknowledgement`, `AckFee` will be paid out to the relayer address which has submitted the `MsgAcknowledgement` on the sending chain (or the registered payee in case one has been registered for the relayer address), and the remaining fees (if any) will be reimbursed to the account which escrowed the fee. (The reimbursed amount equals `EscrowedAmount - (RecvFee + AckFee)`). + +- In case of a timeout transaction, the `TimeoutFee` will be paid to the `Timeout Relayer` (who submits the timeout message to the source chain), and the remaining fees (if any) will be reimbursed to the account which escrowed the fee. (The reimbursed amount equals `EscrowedAmount - (TimeoutFee)`). + +> Please note that fee payments are built on the assumption that sender chains are the source of incentives — the chain that sends the packets is the same chain where fee payments will occur -- please see the [Fee distribution section](04-fee-distribution.md) to understand the flow for registering payee and counterparty payee (fee receiving) addresses. + +## A locked fee middleware module + +The fee middleware module can become locked if the situation arises that the escrow account for the fees does not have sufficient funds to pay out the fees which have been escrowed for each packet. *This situation indicates a severe bug.* In this case, the fee module will be locked until manual intervention fixes the issue. + +> A locked fee module will simply skip fee logic and continue on to the underlying packet flow. A channel with a locked fee module will temporarily function as a fee disabled channel, and the locking of a fee module will not affect the continued flow of packets over the channel. diff --git a/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/04-fee-distribution.md b/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/04-fee-distribution.md new file mode 100644 index 0000000..af5f95e --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/04-fee-distribution.md @@ -0,0 +1,117 @@ +--- +title: Fee Distribution +sidebar_label: Fee Distribution +sidebar_position: 4 +slug: /middleware/ics29-fee/fee-distribution +--- + +# Fee distribution + +:::note Synopsis +Learn about payee registration for the distribution of packet fees. The following document is intended for relayer operators. +::: + +:::note + +## Pre-requisite readings + +- [Fee Middleware](01-overview.md) + +::: + +Packet fees are divided into 3 distinct amounts in order to compensate relayer operators for packet relaying on fee enabled IBC channels. + +- `RecvFee`: The sum of all packet receive fees distributed to a payee for successful execution of `MsgRecvPacket`. +- `AckFee`: The sum of all packet acknowledgement fees distributed to a payee for successful execution of `MsgAcknowledgement`. +- `TimeoutFee`: The sum of all packet timeout fees distributed to a payee for successful execution of `MsgTimeout`. + +## Register a counterparty payee address for forward relaying + +As mentioned in [ICS29 Concepts](01-overview.md#concepts), the forward relayer describes the actor who performs the submission of `MsgRecvPacket` on the destination chain. +Fee distribution for incentivized packet relays takes place on the packet source chain. + +> Relayer operators are expected to register a counterparty payee address, in order to be compensated accordingly with `RecvFee`s upon completion of a packet lifecycle. + +The counterparty payee address registered on the destination chain is encoded into the packet acknowledgement and communicated as such to the source chain for fee distribution. +**If a counterparty payee is not registered for the forward relayer on the destination chain, the escrowed fees will be refunded upon fee distribution.** + +### Relayer operator actions + +A transaction must be submitted **to the destination chain** including a `CounterpartyPayee` address of an account on the source chain. +The transaction must be signed by the `Relayer`. + +Note: If a module account address is used as the `CounterpartyPayee` but the module has been set as a blocked address in the `BankKeeper`, the refunding to the module account will fail. This is because many modules use invariants to compare internal tracking of module account balances against the actual balance of the account stored in the `BankKeeper`. If a token transfer to the module account occurs without going through this module and updating the account balance of the module on the `BankKeeper`, then invariants may break and unknown behaviour could occur depending on the module implementation. Therefore, if it is desirable to use a module account that is currently blocked, the module developers should be consulted to gauge to possibility of removing the module account from the blocked list. + +```go +type MsgRegisterCounterpartyPayee struct { + // unique port identifier + PortId string + // unique channel identifier + ChannelId string + // the relayer address + Relayer string + // the counterparty payee address + CounterpartyPayee string +} +``` + +> This message is expected to fail if: +> +> - `PortId` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators). +> - `ChannelId` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +> - `Relayer` is an invalid address. +> - `CounterpartyPayee` is empty or contains more than 2048 bytes. + +See below for an example CLI command: + +```bash +simd tx ibc-fee register-counterparty-payee transfer channel-0 \ + cosmos1rsp837a4kvtgp2m4uqzdge0zzu6efqgucm0qdh \ + osmo1v5y0tz01llxzf4c2afml8s3awue0ymju22wxx2 \ + --from cosmos1rsp837a4kvtgp2m4uqzdge0zzu6efqgucm0qdh +``` + +## Register an alternative payee address for reverse and timeout relaying + +As mentioned in [ICS29 Concepts](01-overview.md#concepts), the reverse relayer describes the actor who performs the submission of `MsgAcknowledgement` on the source chain. +Similarly the timeout relayer describes the actor who performs the submission of `MsgTimeout` (or `MsgTimeoutOnClose`) on the source chain. + +> Relayer operators **may choose** to register an optional payee address, in order to be compensated accordingly with `AckFee`s and `TimeoutFee`s upon completion of a packet life cycle. + +If a payee is not registered for the reverse or timeout relayer on the source chain, then fee distribution assumes the default behaviour, where fees are paid out to the relayer account which delivers `MsgAcknowledgement` or `MsgTimeout`/`MsgTimeoutOnClose`. + +### Relayer operator actions + +A transaction must be submitted **to the source chain** including a `Payee` address of an account on the source chain. +The transaction must be signed by the `Relayer`. + +Note: If a module account address is used as the `Payee` it is recommended to [turn off invariant checks](https://github.com/cosmos/ibc-go/blob/v7.0.0/testing/simapp/app.go#L727) for that module. + +```go +type MsgRegisterPayee struct { + // unique port identifier + PortId string + // unique channel identifier + ChannelId string + // the relayer address + Relayer string + // the payee address + Payee string +} +``` + +> This message is expected to fail if: +> +> - `PortId` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators). +> - `ChannelId` is invalid (see [24-host naming requirements](https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements/README.md#paths-identifiers-separators)). +> - `Relayer` is an invalid address. +> - `Payee` is an invalid address. + +See below for an example CLI command: + +```bash +simd tx ibc-fee register-payee transfer channel-0 \ + cosmos1rsp837a4kvtgp2m4uqzdge0zzu6efqgucm0qdh \ + cosmos153lf4zntqt33a4v0sm5cytrxyqn78q7kz8j8x5 \ + --from cosmos1rsp837a4kvtgp2m4uqzdge0zzu6efqgucm0qdh +``` diff --git a/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/05-events.md b/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/05-events.md new file mode 100644 index 0000000..a7d76f2 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/05-events.md @@ -0,0 +1,43 @@ +--- +title: Events +sidebar_label: Events +sidebar_position: 5 +slug: /middleware/ics29-fee/events +--- + + +# Events + +:::note Synopsis +An overview of all events related to ICS-29 +::: + +## `MsgPayPacketFee`, `MsgPayPacketFeeAsync` + +| Type | Attribute Key | Attribute Value | +| ----------------------- | --------------- | --------------- | +| incentivized_ibc_packet | port_id | \{portID\} | +| incentivized_ibc_packet | channel_id | \{channelID\} | +| incentivized_ibc_packet | packet_sequence | \{sequence\} | +| incentivized_ibc_packet | recv_fee | \{recvFee\} | +| incentivized_ibc_packet | ack_fee | \{ackFee\} | +| incentivized_ibc_packet | timeout_fee | \{timeoutFee\} | +| message | module | fee-ibc | + +## `RegisterPayee` + +| Type | Attribute Key | Attribute Value | +| -------------- | ------------- | --------------- | +| register_payee | relayer | \{relayer\} | +| register_payee | payee | \{payee\} | +| register_payee | channel_id | \{channelID\} | +| message | module | fee-ibc | + +## `RegisterCounterpartyPayee` + +| Type | Attribute Key | Attribute Value | +| --------------------------- | ------------------ | --------------------- | +| register_counterparty_payee | relayer | \{relayer\} | +| register_counterparty_payee | counterparty_payee | \{counterpartyPayee\} | +| register_counterparty_payee | channel_id | \{channelID\} | +| message | module | fee-ibc | diff --git a/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/06-end-users.md b/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/06-end-users.md new file mode 100644 index 0000000..3d61d8c --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/06-end-users.md @@ -0,0 +1,39 @@ +--- +title: End Users +sidebar_label: End Users +sidebar_position: 6 +slug: /middleware/ics29-fee/end-users +--- + +# For end users + +:::note Synopsis +Learn how to incentivize IBC packets using the ICS29 Fee Middleware module. +::: + +:::note + +## Pre-requisite readings + +- [Fee Middleware](01-overview.md) + +::: + +## Summary + +Different types of end users: + +- CLI users who want to manually incentivize IBC packets +- Client developers + +The Fee Middleware module allows end users to add a 'tip' to each IBC packet which will incentivize relayer operators to relay packets between chains. gRPC endpoints are exposed for client developers as well as a simple CLI for manually incentivizing IBC packets. + +## CLI Users + +For an in depth guide on how to use the ICS29 Fee Middleware module using the CLI please take a look at the [wiki](https://github.com/cosmos/ibc-go/wiki/Fee-enabled-fungible-token-transfers#asynchronous-incentivization-of-a-fungible-token-transfer) on the `ibc-go` repo. + +## Client developers + +Client developers can read more about the relevant ICS29 message types in the [Fee messages section](03-msgs.md). + +[CosmJS](https://github.com/cosmos/cosmjs) is a useful client library for signing and broadcasting Cosmos SDK messages. diff --git a/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/_category_.json b/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/_category_.json new file mode 100644 index 0000000..639bd0e --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Fee Middleware", + "position": 1, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/images/feeflow.png b/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/images/feeflow.png new file mode 100644 index 0000000..4e77529 Binary files /dev/null and b/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/images/feeflow.png differ diff --git a/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/images/msgpaypacket.png b/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/images/msgpaypacket.png new file mode 100644 index 0000000..a454f52 Binary files /dev/null and b/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/images/msgpaypacket.png differ diff --git a/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/images/paypacketfeeasync.png b/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/images/paypacketfeeasync.png new file mode 100644 index 0000000..73d1e04 Binary files /dev/null and b/docs/versioned_docs/version-v8.5.x/04-middleware/01-ics29-fee/images/paypacketfeeasync.png differ diff --git a/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/01-overview.md b/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/01-overview.md new file mode 100644 index 0000000..a5abfd3 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/01-overview.md @@ -0,0 +1,51 @@ +--- +title: Overview +sidebar_label: Overview +sidebar_position: 1 +slug: /middleware/callbacks/overview +--- + +# Overview + +Learn about what the Callbacks Middleware is, and how to build custom modules that utilize the Callbacks Middleware functionality + +## What is the Callbacks Middleware? + +IBC was designed with callbacks between core IBC and IBC applications. IBC apps would send a packet to core IBC, and receive a callback on every step of that packet's lifecycle. This allows IBC applications to be built on top of core IBC, and to be able to execute custom logic on packet lifecycle events (e.g. unescrow tokens for ICS-20). + +This setup worked well for off-chain users interacting with IBC applications. However, we are now seeing the desire for secondary applications (e.g. smart contracts, modules) to call into IBC apps as part of their state machine logic and then do some actions on packet lifecycle events. + +The Callbacks Middleware allows for this functionality by allowing the packets of the underlying IBC applications to register callbacks to secondary applications for lifecycle events. These callbacks are then executed by the Callbacks Middleware when the corresponding packet lifecycle event occurs. + +After much discussion, the design was expanded to [an ADR](/architecture/adr-008-app-caller-cbs), and the Callbacks Middleware is an implementation of that ADR. + +## Concepts + +Callbacks Middleware was built with smart contracts in mind, but can be used by any secondary application that wants to allow IBC packets to call into it. Think of the Callbacks Middleware as a bridge between core IBC and a secondary application. + +We have the following definitions: + +- `Underlying IBC application`: The IBC application that is wrapped by the Callbacks Middleware. This is the IBC application that is actually sending and receiving packet lifecycle events from core IBC. For example, the transfer module, or the ICA controller submodule. +- `IBC Actor`: IBC Actor is an on-chain or off-chain entity that can initiate a packet on the underlying IBC application. For example, a smart contract, an off-chain user, or a module that sends a transfer packet are all IBC Actors. +- `Secondary application`: The application that is being called into by the Callbacks Middleware for packet lifecycle events. This is the application that is receiving the callback directly from the Callbacks Middleware module. For example, the `x/wasm` module. +- `Callback Actor`: The on-chain smart contract or module that is registered to receive callbacks from the secondary application. For example, a Wasm smart contract (gatekeeped by the `x/wasm` module). Note that the Callback Actor is not necessarily the same as the IBC Actor. For example, an off-chain user can initiate a packet on the underlying IBC application, but the Callback Actor could be a smart contract. The secondary application may want to check that the IBC Actor is allowed to call into the Callback Actor, for example, by checking that the IBC Actor is the same as the Callback Actor. +- `Callback Address`: Address of the Callback Actor. This is the address that the secondary application will call into when a packet lifecycle event occurs. For example, the address of the Wasm smart contract. +- `Maximum gas limit`: The maximum amount of gas that the Callbacks Middleware will allow the secondary application to use when it executes its custom logic. +- `User defined gas limit`: The amount of gas that the IBC Actor wants to allow the secondary application to use when it executes its custom logic. This is the gas limit that the IBC Actor specifies when it sends a packet to the underlying IBC application. This cannot be greater than the maximum gas limit. + +Think of the secondary application as a bridge between the Callbacks Middleware and the Callback Actor. The secondary application is responsible for executing the custom logic of the Callback Actor when a packet lifecycle event occurs. The secondary application is also responsible for checking that the IBC Actor is allowed to call into the Callback Actor. + +Note that it is possible that the IBC Actor, Secondary Application, and Callback Actor are all the same entity. In which case, the Callback Address should be the secondary application's module address. + +The following diagram shows how a typical `RecvPacket`, `AcknowledgementPacket`, and `TimeoutPacket` execution flow would look like: +![callbacks-middleware](./images/callbackflow.svg) + +And the following diagram shows how a typical `SendPacket` and `WriteAcknowledgement` execution flow would look like: +![callbacks-middleware](./images/ics4-callbackflow.svg) + +## Known Limitations + +- Callbacks are always executed after the underlying IBC application has executed its logic. +- Maximum gas limit is hardcoded manually during wiring. It requires a coordinated upgrade to change the maximum gas limit. +- The receive packet callback does not pass the relayer address to the secondary application. This is so that we can use the same callback for both synchronous and asynchronous acknowledgements. +- The receive packet callback does not pass IBC Actor's address, this is because the IBC Actor lives in the counterparty chain and cannot be trusted. diff --git a/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/02-integration.md b/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/02-integration.md new file mode 100644 index 0000000..bf1bd52 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/02-integration.md @@ -0,0 +1,108 @@ +--- +title: Integration +sidebar_label: Integration +sidebar_position: 2 +slug: /middleware/callbacks/integration +--- + +# Integration + +Learn how to integrate the callbacks middleware with IBC applications. The following document is intended for developers building on top of the Cosmos SDK and only applies for Cosmos SDK chains. + +The callbacks middleware is a minimal and stateless implementation of the IBC middleware interface. It does not have a keeper, nor does it store any state. It simply routes IBC middleware messages to the appropriate callback function, which is implemented by the secondary application. Therefore, it doesn't need to be registered as a module, nor does it need to be added to the module manager. It only needs to be added to the IBC application stack. + +## Pre-requisite Readings + +- [IBC middleware development](../../01-ibc/04-middleware/02-develop.md) +- [IBC middleware integration](../../01-ibc/04-middleware/03-integration.md) + +The callbacks middleware, as the name suggests, plays the role of an IBC middleware and as such must be configured by chain developers to route and handle IBC messages correctly. +For Cosmos SDK chains this setup is done via the `app/app.go` file, where modules are constructed and configured in order to bootstrap the blockchain application. + +## Importing the callbacks middleware + +The callbacks middleware has no stable releases yet. To use it, you need to import the git commit that contains the module with the compatible version of `ibc-go`. To do so, run the following command with the desired git commit in your project: + +```sh +go get github.com/cosmos/ibc-go/modules/apps/callbacks@342c00b0f8bd7feeebf0780f208a820b0faf90d1 +``` + +You can find the version matrix in [here](../../../../docs/04-middleware/01-callbacks/02-integration.md#importing-the-callbacks-middleware). + +## Configuring an application stack with the callbacks middleware + +As mentioned in [IBC middleware development](../../01-ibc/04-middleware/02-develop.md) an application stack may be composed of many or no middlewares that nest a base application. +These layers form the complete set of application logic that enable developers to build composable and flexible IBC application stacks. +For example, an application stack may just be a single base application like `transfer`, however, the same application stack composed with `29-fee` and `callbacks` will nest the `transfer` base application twice by wrapping it with the Fee Middleware module and then callbacks middleware. + +The callbacks middleware also **requires** a secondary application that will receive the callbacks to implement the [`ContractKeeper`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/callbacks/types/expected_keepers.go#L11-L83). Since the wasm module does not yet support the callbacks middleware, we will use the `mockContractKeeper` module in the examples below. You should replace this with a module that implements `ContractKeeper`. + +### Transfer + +See below for an example of how to create an application stack using `transfer`, `29-fee`, and `callbacks`. Feel free to omit the `29-fee` middleware if you do not want to use it. +The following `transferStack` is configured in `app/app.go` and added to the IBC `Router`. +The in-line comments describe the execution flow of packets between the application stack and IBC core. + +```go +// Create Transfer Stack +// SendPacket, since it is originating from the application to core IBC: +// transferKeeper.SendPacket -> callbacks.SendPacket -> feeKeeper.SendPacket -> channel.SendPacket + +// RecvPacket, message that originates from core IBC and goes down to app, the flow is the other way +// channel.RecvPacket -> fee.OnRecvPacket -> callbacks.OnRecvPacket -> transfer.OnRecvPacket + +// transfer stack contains (from top to bottom): +// - IBC Fee Middleware +// - IBC Callbacks Middleware +// - Transfer + +// create IBC module from bottom to top of stack +var transferStack porttypes.IBCModule +transferStack = transfer.NewIBCModule(app.TransferKeeper) +transferStack = ibccallbacks.NewIBCMiddleware(transferStack, app.IBCFeeKeeper, app.MockContractKeeper, maxCallbackGas) +transferICS4Wrapper := transferStack.(porttypes.ICS4Wrapper) +transferStack = ibcfee.NewIBCMiddleware(transferStack, app.IBCFeeKeeper) +// Since the callbacks middleware itself is an ics4wrapper, it needs to be passed to the transfer keeper +app.TransferKeeper.WithICS4Wrapper(transferICS4Wrapper) + +// Add transfer stack to IBC Router +ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) +``` + +:::warning +The usage of `WithICS4Wrapper` after `transferStack`'s configuration is critical! It allows the callbacks middleware to do `SendPacket` callbacks and asynchronous `ReceivePacket` callbacks. You must do this regardless of whether you are using the `29-fee` middleware or not. +::: + +### Interchain Accounts Controller + +```go +// Create Interchain Accounts Stack +// SendPacket, since it is originating from the application to core IBC: +// icaControllerKeeper.SendTx -> callbacks.SendPacket -> fee.SendPacket -> channel.SendPacket + +// initialize ICA module with mock module as the authentication module on the controller side +var icaControllerStack porttypes.IBCModule +icaControllerStack = ibcmock.NewIBCModule(&mockModule, ibcmock.NewIBCApp("", scopedICAMockKeeper)) +app.ICAAuthModule = icaControllerStack.(ibcmock.IBCModule) +icaControllerStack = icacontroller.NewIBCMiddleware(icaControllerStack, app.ICAControllerKeeper) +icaControllerStack = ibccallbacks.NewIBCMiddleware(icaControllerStack, app.IBCFeeKeeper, app.MockContractKeeper, maxCallbackGas) +icaICS4Wrapper := icaControllerStack.(porttypes.ICS4Wrapper) +icaControllerStack = ibcfee.NewIBCMiddleware(icaControllerStack, app.IBCFeeKeeper) +// Since the callbacks middleware itself is an ics4wrapper, it needs to be passed to the ica controller keeper +app.ICAControllerKeeper.WithICS4Wrapper(icaICS4Wrapper) + +// RecvPacket, message that originates from core IBC and goes down to app, the flow is: +// channel.RecvPacket -> fee.OnRecvPacket -> icaHost.OnRecvPacket + +var icaHostStack porttypes.IBCModule +icaHostStack = icahost.NewIBCModule(app.ICAHostKeeper) +icaHostStack = ibcfee.NewIBCMiddleware(icaHostStack, app.IBCFeeKeeper) + +// Add ICA host and controller to IBC router ibcRouter. +AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). +AddRoute(icahosttypes.SubModuleName, icaHostStack). +``` + +:::warning +The usage of `WithICS4Wrapper` here is also critical! +::: diff --git a/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/03-interfaces.md b/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/03-interfaces.md new file mode 100644 index 0000000..b2c4d84 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/03-interfaces.md @@ -0,0 +1,151 @@ +--- +title: Interfaces +sidebar_label: Interfaces +sidebar_position: 3 +slug: /middleware/callbacks/interfaces +--- + +# Interfaces + +The callbacks middleware requires certain interfaces to be implemented by the underlying IBC applications and the secondary application. If you're simply wiring up the callbacks middleware to an existing IBC application stack and a secondary application such as `icacontroller` and `x/wasm`, you can skip this section. + +## Interfaces for developing the Underlying IBC Application + +### `PacketDataUnmarshaler` + +```go +// PacketDataUnmarshaler defines an optional interface which allows a middleware to +// request the packet data to be unmarshaled by the base application. +type PacketDataUnmarshaler interface { + // UnmarshalPacketData unmarshals the packet data into a concrete type + UnmarshalPacketData([]byte) (interface{}, error) +} +``` + +The callbacks middleware **requires** the underlying ibc application to implement the [`PacketDataUnmarshaler`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/core/05-port/types/module.go#L142-L147) interface so that it can unmarshal the packet data bytes into the appropriate packet data type. This allows usage of interface functions implemented by the packet data type. The packet data type is expected to implement the `PacketDataProvider` interface (see section below), which is used to parse the callback data that is currently stored in the packet memo field for `transfer` and `ica` packets as a JSON string. See its implementation in the [`transfer`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/transfer/ibc_module.go#L303-L313) and [`icacontroller`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/27-interchain-accounts/controller/ibc_middleware.go#L258-L268) modules for reference. + +If the underlying application is a middleware itself, then it can implement this interface by simply passing the function call to its underlying application. See its implementation in the [`fee middleware`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/29-fee/ibc_middleware.go#L368-L378) for reference. + +### `PacketDataProvider` + +```go +// PacketDataProvider defines an optional interfaces for retrieving custom packet data stored on behalf of another application. +// An existing problem in the IBC middleware design is the inability for a middleware to define its own packet data type and insert packet sender provided information. +// A short term solution was introduced into several application's packet data to utilize a memo field to carry this information on behalf of another application. +// This interfaces standardizes that behaviour. Upon realization of the ability for middleware's to define their own packet data types, this interface will be deprecated and removed with time. +type PacketDataProvider interface { + // GetCustomPacketData returns the packet data held on behalf of another application. + // The name the information is stored under should be provided as the key. + // If no custom packet data exists for the key, nil should be returned. + GetCustomPacketData(key string) interface{} +} +``` + +The callbacks middleware also **requires** the underlying ibc application's packet data type to implement the [`PacketDataProvider`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/core/exported/packet.go#L43-L52) interface. This interface is used to retrieve the callback data from the packet data (using the memo field in the case of `transfer` and `ica`). For example, see its implementation in the [`transfer`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/transfer/types/packet.go#L85-L105) module. + +Since middlewares do not have packet types, they do not need to implement this interface. + +### `PacketData` + +```go +// PacketData defines an optional interface which an application's packet data structure may implement. +type PacketData interface { + // GetPacketSender returns the sender address of the packet data. + // If the packet sender is unknown or undefined, an empty string should be returned. + GetPacketSender(sourcePortID string) string +} +``` + +[`PacketData`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/core/exported/packet.go#L36-L41) is an optional interface that can be implemented by the underlying ibc application's packet data type. It is used to retrieve the packet sender address from the packet data. The callbacks middleware uses this interface to retrieve the packet sender address and pass it to the callback function during a source callback. If this interface is not implemented, then the callbacks middleware passes and empty string as the sender address. For example, see its implementation in the [`transfer`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/transfer/types/packet.go#L74-L83) and [`ica`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/27-interchain-accounts/types/packet.go#L78-L92) module. + +This interface was added so that secondary applications can retrieve the packet sender address to perform custom authorization logic if needed. + +Since middlewares do not have packet types, they do not need to implement this interface. + +## Interfaces for developing the Secondary Application + +### `ContractKeeper` + +The callbacks middleware requires the secondary application to implement the [`ContractKeeper`](https://github.com/cosmos/ibc-go/blob/v7.3.0/modules/apps/callbacks/types/expected_keepers.go#L11-L83) interface. The contract keeper will be invoked at each step of the packet lifecycle. When a packet is sent, if callback information is provided, the contract keeper will be invoked via the `IBCSendPacketCallback`. This allows the contract keeper to prevent packet sends when callback information is provided, for example if the sender is unauthorized to perform callbacks on the given information. If the packet send is successful, the contract keeper on the destination (if present) will be invoked when a packet has been received and the acknowledgement is written, this will occur via `IBCReceivePacketCallback`. At the end of the packet lifecycle, when processing acknowledgements or timeouts, the source contract keeper will be invoked either via `IBCOnAcknowledgementPacket` or `IBCOnTimeoutPacket`. Once a packet has been sent, each step of the packet lifecycle can be processed given that a relayer sets the gas limit to be more than or equal to the required `CommitGasLimit`. State changes performed in the callback will only be committed upon successful execution. + +```go +// ContractKeeper defines the entry points exposed to the VM module which invokes a smart contract +type ContractKeeper interface { + // IBCSendPacketCallback is called in the source chain when a PacketSend is executed. The + // packetSenderAddress is determined by the underlying module, and may be empty if the sender is + // unknown or undefined. The contract is expected to handle the callback within the user defined + // gas limit, and handle any errors, or panics gracefully. + // This entry point is called with a cached context. If an error is returned, then the changes in + // this context will not be persisted, and the error will be propagated to the underlying IBC + // application, resulting in a packet send failure. + // + // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform + // validation on the origin of a given packet. It is recommended to perform the same validation + // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This + // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. + IBCSendPacketCallback( + cachedCtx sdk.Context, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + packetData []byte, + contractAddress, + packetSenderAddress string, + ) error + // IBCOnAcknowledgementPacketCallback is called in the source chain when a packet acknowledgement + // is received. The packetSenderAddress is determined by the underlying module, and may be empty if + // the sender is unknown or undefined. The contract is expected to handle the callback within the + // user defined gas limit, and handle any errors, or panics gracefully. + // This entry point is called with a cached context. If an error is returned, then the changes in + // this context will not be persisted, but the packet lifecycle will not be blocked. + // + // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform + // validation on the origin of a given packet. It is recommended to perform the same validation + // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This + // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. + IBCOnAcknowledgementPacketCallback( + cachedCtx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, + contractAddress, + packetSenderAddress string, + ) error + // IBCOnTimeoutPacketCallback is called in the source chain when a packet is not received before + // the timeout height. The packetSenderAddress is determined by the underlying module, and may be + // empty if the sender is unknown or undefined. The contract is expected to handle the callback + // within the user defined gas limit, and handle any error, out of gas, or panics gracefully. + // This entry point is called with a cached context. If an error is returned, then the changes in + // this context will not be persisted, but the packet lifecycle will not be blocked. + // + // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform + // validation on the origin of a given packet. It is recommended to perform the same validation + // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This + // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. + IBCOnTimeoutPacketCallback( + cachedCtx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, + contractAddress, + packetSenderAddress string, + ) error + // IBCReceivePacketCallback is called in the destination chain when a packet acknowledgement is written. + // The contract is expected to handle the callback within the user defined gas limit, and handle any errors, + // out of gas, or panics gracefully. + // This entry point is called with a cached context. If an error is returned, then the changes in + // this context will not be persisted, but the packet lifecycle will not be blocked. + IBCReceivePacketCallback( + cachedCtx sdk.Context, + packet ibcexported.PacketI, + ack ibcexported.Acknowledgement, + contractAddress string, + ) error +} +``` + +These are the callback entry points exposed to the secondary application. The secondary application is expected to execute its custom logic within these entry points. The callbacks middleware will handle the execution of these callbacks and revert the state if needed. + +:::tip +Note that the source callback entry points are provided with the `packetSenderAddress` and MAY choose to use this to perform validation on the origin of a given packet. It is recommended to perform the same validation on all source chain callbacks (SendPacket, AcknowledgePacket, TimeoutPacket). This defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. +::: diff --git a/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/04-events.md b/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/04-events.md new file mode 100644 index 0000000..448a56f --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/04-events.md @@ -0,0 +1,39 @@ +--- +title: Events +sidebar_label: Events +sidebar_position: 4 +slug: /middleware/callbacks/events +--- + +# Events + +An overview of all events related to the callbacks middleware. There are two types of events, `"ibc_src_callback"` and `"ibc_dest_callback"`. + +## Shared Attributes + +Both of these event types share the following attributes: + +| **Attribute Key** | **Attribute Values** | **Optional** | +|:-------------------------:|:---------------------------------------------------------------------------------------:|:------------------:| +| module | "ibccallbacks" | | +| callback_type | **One of**: "send_packet", "acknowledgement_packet", "timeout_packet", "receive_packet" | | +| callback_address | string | | +| callback_exec_gas_limit | string (parsed from uint64) | | +| callback_commit_gas_limit | string (parsed from uint64) | | +| packet_sequence | string (parsed from uint64) | | +| callback_result | **One of**: "success", "failure" | | +| callback_error | string (parsed from callback err) | Yes, if err != nil | + +## `ibc_src_callback` Attributes + +| **Attribute Key** | **Attribute Values** | +|:------------------:|:------------------------:| +| packet_src_port | string (sourcePortID) | +| packet_src_channel | string (sourceChannelID) | + +## `ibc_dest_callback` Attributes + +| **Attribute Key** | **Attribute Values** | +|:-------------------:|:------------------------:| +| packet_dest_port | string (destPortID) | +| packet_dest_channel | string (destChannelID) | diff --git a/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/05-end-users.md b/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/05-end-users.md new file mode 100644 index 0000000..2142168 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/05-end-users.md @@ -0,0 +1,96 @@ +--- +title: End Users +sidebar_label: End Users +sidebar_position: 5 +slug: /middleware/callbacks/end-users +--- + +# Usage + +This section explains how to use the callbacks middleware from the perspective of an IBC Actor. Callbacks middleware provides two types of callbacks: + +- Source callbacks: + - `SendPacket` callback + - `OnAcknowledgementPacket` callback + - `OnTimeoutPacket` callback +- Destination callbacks: + - `ReceivePacket` callback + +For a given channel, the source callbacks are supported if the source chain has the callbacks middleware wired up in the channel's IBC stack. Similarly, the destination callbacks are supported if the destination chain has the callbacks middleware wired up in the channel's IBC stack. + +:::tip +Callbacks are always executed after the packet has been processed by the underlying IBC module. +::: + +:::warning +If the underlying application module is doing an asynchronous acknowledgement on packet receive (for example, if the [packet forward middleware](https://github.com/cosmos/ibc-apps/tree/main/middleware/packet-forward-middleware) is in the stack, and is being used by this packet), then the callbacks middleware will execute the `ReceivePacket` callback after the acknowledgement has been received. +::: + +## Source Callbacks + +Source callbacks are natively supported in the following ibc modules (if they are wrapped by the callbacks middleware): + +- `transfer` +- `icacontroller` + +To have your source callbacks be processed by the callbacks middleware, you must set the memo in the application's packet data to the following format: + +```jsonc +{ + "src_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} +``` + +## Destination Callbacks + +Destination callbacks are natively only supported in the transfer module. Note that wrapping icahost is not supported. This is because icahost should be able to execute an arbitrary transaction anyway, and can call contracts or modules directly. + +To have your destination callbacks processed by the callbacks middleware, you must set the memo in the application's packet data to the following format: + +```jsonc +{ + "dest_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} +``` + +Note that a packet can have both a source and destination callback. + +```jsonc +{ + "src_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + }, + "dest_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} +``` + +# User Defined Gas Limit + +User defined gas limit was added for the following reasons: + +- To prevent callbacks from blocking packet lifecycle. +- To prevent relayers from being able to DOS the callback execution by sending a packet with a low amount of gas. + +:::tip +There is a chain wide parameter that sets the maximum gas limit that a user can set for a callback. This is to prevent a user from setting a gas limit that is too high for relayers. If the `"gas_limit"` is not set in the packet memo, then the maximum gas limit is used. +::: + +These goals are achieved by creating a minimum gas amount required for callback execution. If the relayer provides at least the minimum gas limit for the callback execution, then the packet lifecycle will not be blocked if the callback runs out of gas during execution, and the callback cannot be retried. If the relayer does not provided the minimum amount of gas and the callback executions runs out of gas, the entire tx is reverted and it may be executed again. + +:::tip +`SendPacket` callback is always reverted if the callback execution fails or returns an error for any reason. This is so that the packet is not sent if the callback execution fails. +::: diff --git a/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/06-gas.md b/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/06-gas.md new file mode 100644 index 0000000..27cbf0f --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/06-gas.md @@ -0,0 +1,77 @@ +--- +title: Gas Management +sidebar_label: Gas Management +sidebar_position: 6 +slug: /middleware/callbacks/gas +--- + +# Gas Management + +## Overview + +Executing arbitrary code on a chain can be arbitrarily expensive. In general, a callback may consume infinite gas (think of a callback that loops forever). This is problematic for a few reasons: + +- It can block the packet lifecycle. +- It can be used to consume all of the relayer's funds and gas. +- A relayer can DOS the callback execution by sending a packet with a low amount of gas. + +To prevent these, the callbacks middleware introduces two gas limits: a chain wide gas limit (`maxCallbackGas`) and a user defined gas limit. + +### Chain Wide Gas Limit + +Since the callbacks middleware does not have a keeper, it does not use a governance parameter to set the chain wide gas limit. Instead, the chain wide gas limit is passed in as a parameter to the callbacks middleware during initialization. + +```go +// app.go + +maxCallbackGas := uint64(1_000_000) + +var transferStack porttypes.IBCModule +transferStack = transfer.NewIBCModule(app.TransferKeeper) +transferStack = ibcfee.NewIBCMiddleware(transferStack, app.IBCFeeKeeper) +transferStack = ibccallbacks.NewIBCMiddleware(transferStack, app.IBCFeeKeeper, app.MockContractKeeper, maxCallbackGas) +// Since the callbacks middleware itself is an ics4wrapper, it needs to be passed to the transfer keeper +app.TransferKeeper.WithICS4Wrapper(transferStack.(porttypes.ICS4Wrapper)) + +// Add transfer stack to IBC Router +ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) +``` + +### User Defined Gas Limit + +The user defined gas limit is set by the IBC Actor during packet creation. The user defined gas limit is set in the packet memo. If the user defined gas limit is not set or if the user defined gas limit is greater than the chain wide gas limit, then the chain wide gas limit is used as the user defined gas limit. + +```jsonc +{ + "src_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + }, + "dest_callback": { + "address": "callbackAddressString", + // optional + "gas_limit": "userDefinedGasLimitString", + } +} +``` + +## Gas Limit Enforcement + +During a callback execution, there are three types of gas limits that are enforced: + +- User defined gas limit +- Chain wide gas limit +- Context gas limit (amount of gas that the relayer has left for this execution) + +Chain wide gas limit is used as a maximum to the user defined gas limit as explained in the [previous section](#user-defined-gas-limit). It may also be used as a default value if no user gas limit is provided. Therefore, we can ignore the chain wide gas limit for the rest of this section and work with the minimum of the chain wide gas limit and user defined gas limit. This minimum is called the commit gas limit. + +The gas limit enforcement is done by executing the callback inside a cached context with a new gas meter. The gas meter is initialized with the minimum of the commit gas limit and the context gas limit. This minimum is called the execution gas limit. We say that retries are allowed if `context gas limit < commit gas limit`. Otherwise, we say that retries are not allowed. + +If the callback execution fails due to an out of gas error, then the middleware checks if retries are allowed. If retries are not allowed, then it recovers from the out of gas error, consumes execution gas limit from the original context, and continues with the packet life cycle. If retries are allowed, then it panics with an out of gas error to revert the entire tx. The packet can then be submitted again with a higher gas limit. The out of gas panic descriptor is shown below. + +```go +fmt.Sprintf("ibc %s callback out of gas; commitGasLimit: %d", callbackType, callbackData.CommitGasLimit)} +``` + +If the callback execution does not fail due to an out of gas error then the callbacks middleware does not block the packet life cycle regardless of whether retries are allowed or not. diff --git a/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/_category_.json b/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/_category_.json new file mode 100644 index 0000000..4c2654b --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Callbacks Middleware", + "position": 2, + "link": null +} diff --git a/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/images/callbackflow.svg b/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/images/callbackflow.svg new file mode 100644 index 0000000..df58e93 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/images/callbackflow.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/images/ics4-callbackflow.svg b/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/images/ics4-callbackflow.svg new file mode 100644 index 0000000..4b42440 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/04-middleware/02-callbacks/images/ics4-callbackflow.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/versioned_docs/version-v8.5.x/04-middleware/_category_.json b/docs/versioned_docs/version-v8.5.x/04-middleware/_category_.json new file mode 100644 index 0000000..cc8fa26 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/04-middleware/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "IBC Middleware Modules", + "position": 4, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v8.5.x/05-migrations/01-support-denoms-with-slashes.md b/docs/versioned_docs/version-v8.5.x/05-migrations/01-support-denoms-with-slashes.md new file mode 100644 index 0000000..ac1481d --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/05-migrations/01-support-denoms-with-slashes.md @@ -0,0 +1,87 @@ +--- +title: Support transfer of coins whose base denom contains slashes +sidebar_label: Support transfer of coins whose base denom contains slashes +sidebar_position: 1 +slug: /migrations/support-denoms-with-slashes +--- +# Migrating from not supporting base denoms with slashes to supporting base denoms with slashes + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +This document is necessary when chains are upgrading from a version that does not support base denoms with slashes (e.g. v3.0.0) to a version that does (e.g. v3.2.0). All versions of ibc-go smaller than v1.5.0 for the v1.x release line, v2.3.0 for the v2.x release line, and v3.1.0 for the v3.x release line do **NOT** support IBC token transfers of coins whose base denoms contain slashes. Therefore the in-place of genesis migration described in this document are required when upgrading. + +If a chain receives coins of a base denom with slashes before it upgrades to supporting it, the receive may pass however the trace information will be incorrect. + +E.g. If a base denom of `testcoin/testcoin/testcoin` is sent to a chain that does not support slashes in the base denom, the receive will be successful. However, the trace information stored on the receiving chain will be: `Trace: "transfer/{channel-id}/testcoin/testcoin", BaseDenom: "testcoin"`. + +This incorrect trace information must be corrected when the chain does upgrade to fully supporting denominations with slashes. + +To do so, chain binaries should include a migration script that will run when the chain upgrades from not supporting base denominations with slashes to supporting base denominations with slashes. + +## Chains + +### ICS20 - Transfer + +The transfer module will now support slashes in base denoms, so we must iterate over current traces to check if any of them are incorrectly formed and correct the trace information. + +### Upgrade Proposal + +```go +app.UpgradeKeeper.SetUpgradeHandler("MigrateTraces", + func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + // transfer module consensus version has been bumped to 2 + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) +``` + +This is only necessary if there are denom traces in the store with incorrect trace information from previously received coins that had a slash in the base denom. However, it is recommended that any chain upgrading to support base denominations with slashes runs this code for safety. + +For a more detailed sample, please check out the code changes in [this pull request](https://github.com/cosmos/ibc-go/pull/1680). + +### Genesis Migration + +If the chain chooses to add support for slashes in base denoms via genesis export, then the trace information must be corrected during genesis migration. + +The migration code required may look like: + +```go +func migrateGenesisSlashedDenomsUpgrade(appState genutiltypes.AppMap, clientCtx client.Context, genDoc *tmtypes.GenesisDoc) (genutiltypes.AppMap, error) { + if appState[ibctransfertypes.ModuleName] != nil { + transferGenState := &ibctransfertypes.GenesisState{} + clientCtx.Codec.MustUnmarshalJSON(appState[ibctransfertypes.ModuleName], transferGenState) + + substituteTraces := make([]ibctransfertypes.DenomTrace, len(transferGenState.DenomTraces)) + for i, dt := range transferGenState.DenomTraces { + // replace all previous traces with the latest trace if validation passes + // note most traces will have same value + newTrace := ibctransfertypes.ParseDenomTrace(dt.GetFullDenomPath()) + + if err := newTrace.Validate(); err != nil { + substituteTraces[i] = dt + } else { + substituteTraces[i] = newTrace + } + } + + transferGenState.DenomTraces = substituteTraces + + // delete old genesis state + delete(appState, ibctransfertypes.ModuleName) + + // set new ibc transfer genesis state + appState[ibctransfertypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(transferGenState) + } + + return appState, nil +} +``` + +For a more detailed sample, please check out the code changes in [this pull request](https://github.com/cosmos/ibc-go/pull/1528). diff --git a/docs/versioned_docs/version-v8.5.x/05-migrations/02-sdk-to-v1.md b/docs/versioned_docs/version-v8.5.x/05-migrations/02-sdk-to-v1.md new file mode 100644 index 0000000..8b333e4 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/05-migrations/02-sdk-to-v1.md @@ -0,0 +1,195 @@ +--- +title: SDK v0.43 to IBC-Go v1 +sidebar_label: SDK v0.43 to IBC-Go v1 +sidebar_position: 2 +slug: /migrations/sdk-to-v1 +--- + +# Migrating to ibc-go + +This file contains information on how to migrate from the IBC module contained in the SDK 0.41.x and 0.42.x lines to the IBC module in the ibc-go repository based on the 0.44 SDK version. + +## Import Changes + +The most obvious changes is import name changes. We need to change: + +- applications -> apps +- cosmos-sdk/x/ibc -> ibc-go + +On my GNU/Linux based machine I used the following commands, executed in order: + +```shell +grep -RiIl 'cosmos-sdk\/x\/ibc\/applications' | xargs sed -i 's/cosmos-sdk\/x\/ibc\/applications/ibc-go\/modules\/apps/g' +``` + +```shell +grep -RiIl 'cosmos-sdk\/x\/ibc' | xargs sed -i 's/cosmos-sdk\/x\/ibc/ibc-go\/modules/g' +``` + +ref: [explanation of the above commands](https://www.internalpointers.com/post/linux-find-and-replace-text-multiple-files) + +Executing these commands out of order will cause issues. + +Feel free to use your own method for modifying import names. + +NOTE: Updating to the `v0.44.0` SDK release and then running `go mod tidy` will cause a downgrade to `v0.42.0` in order to support the old IBC import paths. +Update the import paths before running `go mod tidy`. + +## Chain Upgrades + +Chains may choose to upgrade via an upgrade proposal or genesis upgrades. Both in-place store migrations and genesis migrations are supported. + +**WARNING**: Please read at least the quick guide for [IBC client upgrades](../01-ibc/05-upgrades/01-quick-guide.md) before upgrading your chain. It is highly recommended you do not change the chain-ID during an upgrade, otherwise you must follow the IBC client upgrade instructions. + +Both in-place store migrations and genesis migrations will: + +- migrate the solo machine client state from v1 to v2 protobuf definitions +- prune all solo machine consensus states +- prune all expired tendermint consensus states + +Chains must set a new connection parameter during either in place store migrations or genesis migration. The new parameter, max expected block time, is used to enforce packet processing delays on the receiving end of an IBC packet flow. Checkout the [docs](https://github.com/cosmos/ibc-go/blob/release/v1.0.x/docs/ibc/proto-docs.md#params-2) for more information. + +### In-Place Store Migrations + +The new chain binary will need to run migrations in the upgrade handler. The fromVM (previous module version) for the IBC module should be 1. This will allow migrations to be run for IBC updating the version from 1 to 2. + +Ex: + +```go +app.UpgradeKeeper.SetUpgradeHandler("my-upgrade-proposal", + func(ctx sdk.Context, _ upgradetypes.Plan, _ module.VersionMap) (module.VersionMap, error) { + // set max expected block time parameter. Replace the default with your expected value + // https://github.com/cosmos/ibc-go/blob/release/v1.0.x/docs/ibc/proto-docs.md#params-2 + app.IBCKeeper.ConnectionKeeper.SetParams(ctx, ibcconnectiontypes.DefaultParams()) + + fromVM := map[string]uint64{ + ... // other modules + "ibc": 1, + ... + } + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) + +``` + +### Genesis Migrations + +To perform genesis migrations, the following code must be added to your existing migration code. + +```go +// add imports as necessary +import ( + ibcv100 "github.com/cosmos/ibc-go/modules/core/legacy/v100" + ibchost "github.com/cosmos/ibc-go/modules/core/24-host" +) + +... + +// add in migrate cmd function +// expectedTimePerBlock is a new connection parameter +// https://github.com/cosmos/ibc-go/blob/release/v1.0.x/docs/ibc/proto-docs.md#params-2 +newGenState, err = ibcv100.MigrateGenesis(newGenState, clientCtx, *genDoc, expectedTimePerBlock) +if err != nil { + return err +} +``` + +**NOTE:** The genesis chain-id, time and height MUST be updated before migrating IBC, otherwise the tendermint consensus state will not be pruned. + +## IBC Keeper Changes + +The IBC Keeper now takes in the Upgrade Keeper. Please add the chains' Upgrade Keeper after the Staking Keeper: + +```diff +// Create IBC Keeper +app.IBCKeeper = ibckeeper.NewKeeper( +- appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, scopedIBCKeeper, ++ appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper, +) +``` + +## Proposals + +### UpdateClientProposal + +The `UpdateClient` has been modified to take in two client-identifiers and one initial height. Please see the [documentation](../01-ibc/07-proposals.md) for more information. + +### UpgradeProposal + +A new IBC proposal type has been added, `UpgradeProposal`. This handles an IBC (breaking) Upgrade. +The previous `UpgradedClientState` field in an Upgrade `Plan` has been deprecated in favor of this new proposal type. + +### Proposal Handler Registration + +The `ClientUpdateProposalHandler` has been renamed to `ClientProposalHandler`. +It handles both `UpdateClientProposal`s and `UpgradeProposal`s. + +Add this import: + +```diff ++ ibcclienttypes "github.com/cosmos/ibc-go/modules/core/02-client/types" +``` + +Please ensure the governance module adds the correct route: + +```diff +- AddRoute(ibchost.RouterKey, ibcclient.NewClientUpdateProposalHandler(app.IBCKeeper.ClientKeeper)) ++ AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)) +``` + +NOTE: Simapp registration was incorrect in the 0.41.x releases. The `UpdateClient` proposal handler should be registered with the router key belonging to `ibc-go/core/02-client/types` +as shown in the diffs above. + +### Proposal CLI Registration + +Please ensure both proposal type CLI commands are registered on the governance module by adding the following arguments to `gov.NewAppModuleBasic()`: + +Add the following import: + +```diff ++ ibcclientclient "github.com/cosmos/ibc-go/modules/core/02-client/client" +``` + +Register the cli commands: + +```diff +gov.NewAppModuleBasic( + paramsclient.ProposalHandler, distrclient.ProposalHandler, upgradeclient.ProposalHandler, upgradeclient.CancelProposalHandler, ++ ibcclientclient.UpdateClientProposalHandler, ibcclientclient.UpgradeProposalHandler, +), +``` + +REST routes are not supported for these proposals. + +## Proto file changes + +The gRPC querier service endpoints have changed slightly. The previous files used `v1beta1` gRPC route, this has been updated to `v1`. + +The solo machine has replaced the FrozenSequence uint64 field with a IsFrozen boolean field. The package has been bumped from `v1` to `v2` + +## IBC callback changes + +### OnRecvPacket + +Application developers need to update their `OnRecvPacket` callback logic. + +The `OnRecvPacket` callback has been modified to only return the acknowledgement. The acknowledgement returned must implement the `Acknowledgement` interface. The acknowledgement should indicate if it represents a successful processing of a packet by returning true on `Success()` and false in all other cases. A return value of false on `Success()` will result in all state changes which occurred in the callback being discarded. More information can be found in the [documentation](../01-ibc/03-apps/01-apps.md#receiving-packets). + +The `OnRecvPacket`, `OnAcknowledgementPacket`, and `OnTimeoutPacket` callbacks are now passed the `sdk.AccAddress` of the relayer who relayed the IBC packet. Applications may use or ignore this information. + +## IBC Event changes + +The `packet_data` attribute has been deprecated in favor of `packet_data_hex`, in order to provide standardized encoding/decoding of packet data in events. While the `packet_data` event still exists, all relayers and IBC Event consumers are strongly encouraged to switch over to using `packet_data_hex` as soon as possible. + +The `packet_ack` attribute has also been deprecated in favor of `packet_ack_hex` for the same reason stated above. All relayers and IBC Event consumers are strongly encouraged to switch over to using `packet_ack_hex` as soon as possible. + +The `consensus_height` attribute has been removed in the Misbehaviour event emitted. IBC clients no longer have a frozen height and misbehaviour does not necessarily have an associated height. + +## Relevant SDK changes + +- (codec) [\#9226](https://github.com/cosmos/cosmos-sdk/pull/9226) Rename codec interfaces and methods, to follow a general Go interfaces: + - `codec.Marshaler` → `codec.Codec` (this defines objects which serialize other objects) + - `codec.BinaryMarshaler` → `codec.BinaryCodec` + - `codec.JSONMarshaler` → `codec.JSONCodec` + - Removed `BinaryBare` suffix from `BinaryCodec` methods (`MarshalBinaryBare`, `UnmarshalBinaryBare`, ...) + - Removed `Binary` infix from `BinaryCodec` methods (`MarshalBinaryLengthPrefixed`, `UnmarshalBinaryLengthPrefixed`, ...) diff --git a/docs/versioned_docs/version-v8.5.x/05-migrations/03-v1-to-v2.md b/docs/versioned_docs/version-v8.5.x/05-migrations/03-v1-to-v2.md new file mode 100644 index 0000000..13afccd --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/05-migrations/03-v1-to-v2.md @@ -0,0 +1,59 @@ +--- +title: IBC-Go v1 to v2 +sidebar_label: IBC-Go v1 to v2 +sidebar_position: 3 +slug: /migrations/v1-to-v2 +--- +# Migrating from ibc-go v1 to v2 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go -> github.com/cosmos/ibc-go/v2 +``` + +## Chains + +- No relevant changes were made in this release. + +## IBC Apps + +A new function has been added to the app module interface: + +```go +// NegotiateAppVersion performs application version negotiation given the provided channel ordering, connectionID, portID, counterparty and proposed version. +// An error is returned if version negotiation cannot be performed. For example, an application module implementing this interface +// may decide to return an error in the event of the proposed version being incompatible with it's own +NegotiateAppVersion( + ctx sdk.Context, + order channeltypes.Order, + connectionID string, + portID string, + counterparty channeltypes.Counterparty, + proposedVersion string, +) (version string, err error) +``` + +This function should perform application version negotiation and return the negotiated version. If the version cannot be negotiated, an error should be returned. This function is only used on the client side. + +### `sdk.Result` removed + +`sdk.Result` has been removed as a return value in the application callbacks. Previously it was being discarded by core IBC and was thus unused. + +## Relayers + +A new gRPC has been added to 05-port, `AppVersion`. It returns the negotiated app version. This function should be used for the `ChanOpenTry` channel handshake step to decide upon the application version which should be set in the channel. + +## IBC Light Clients + +- No relevant changes were made in this release. diff --git a/docs/versioned_docs/version-v8.5.x/05-migrations/04-v2-to-v3.md b/docs/versioned_docs/version-v8.5.x/05-migrations/04-v2-to-v3.md new file mode 100644 index 0000000..2db12c5 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/05-migrations/04-v2-to-v3.md @@ -0,0 +1,186 @@ +--- +title: IBC-Go v2 to v3 +sidebar_label: IBC-Go v2 to v3 +sidebar_position: 4 +slug: /migrations/v2-to-v3 +--- + +# Migrating from ibc-go v2 to v3 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go/v2 -> github.com/cosmos/ibc-go/v3 +``` + +No genesis or in-place migrations are required when upgrading from v1 or v2 of ibc-go. + +## Chains + +### ICS20 + +The `transferkeeper.NewKeeper(...)` now takes in an ICS4Wrapper. +The ICS4Wrapper should be the IBC Channel Keeper unless ICS 20 is being connected to a middleware application. + +### ICS27 + +ICS27 Interchain Accounts has been added as a supported IBC application of ibc-go. +Please see the [ICS27 documentation](../02-apps/02-interchain-accounts/01-overview.md) for more information. + +### Upgrade Proposal + +If the chain will adopt ICS27, it must set the appropriate params during the execution of the upgrade handler in `app.go`: + +```go +app.UpgradeKeeper.SetUpgradeHandler("v3", + func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + // set the ICS27 consensus version so InitGenesis is not run + fromVM[icatypes.ModuleName] = icamodule.ConsensusVersion() + + // create ICS27 Controller submodule params + controllerParams := icacontrollertypes.Params{ + ControllerEnabled: true, + } + + // create ICS27 Host submodule params + hostParams := icahosttypes.Params{ + HostEnabled: true, + AllowMessages: []string{"/cosmos.bank.v1beta1.MsgSend", ...}, + } + + // initialize ICS27 module + icamodule.InitModule(ctx, controllerParams, hostParams) + + ... + + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) +``` + +The host and controller submodule params only need to be set if the chain integrates those submodules. +For example, if a chain chooses not to integrate a controller submodule, it may pass empty params into `InitModule`. + +#### Add `StoreUpgrades` for ICS27 module + +For ICS27 it is also necessary to [manually add store upgrades](https://docs.cosmos.network/main/learn/advanced/upgrade#add-storeupgrades-for-new-modules) for the new ICS27 module and then configure the store loader to apply those upgrades in `app.go`: + +```go +if upgradeInfo.Name == "v3" && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { + storeUpgrades := store.StoreUpgrades{ + Added: []string{icacontrollertypes.StoreKey, icahosttypes.StoreKey}, + } + + app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) +} +``` + +This ensures that the new module's stores are added to the multistore before the migrations begin. +The host and controller submodule keys only need to be added if the chain integrates those submodules. +For example, if a chain chooses not to integrate a controller submodule, it does not need to add the controller key to the `Added` field. + +### Genesis migrations + +If the chain will adopt ICS27 and chooses to upgrade via a genesis export, then the ICS27 parameters must be set during genesis migration. + +The migration code required may look like: + +```go + controllerGenesisState := icatypes.DefaultControllerGenesis() + // overwrite parameters as desired + controllerGenesisState.Params = icacontrollertypes.Params{ + ControllerEnabled: true, + } + + hostGenesisState := icatypes.DefaultHostGenesis() + // overwrite parameters as desired + hostGenesisState.Params = icahosttypes.Params{ + HostEnabled: true, + AllowMessages: []string{"/cosmos.bank.v1beta1.MsgSend", ...}, + } + + icaGenesisState := icatypes.NewGenesisState(controllerGenesisState, hostGenesisState) + + // set new ics27 genesis state + appState[icatypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(icaGenesisState) +``` + +### Ante decorator + +The field of type `channelkeeper.Keeper` in the `AnteDecorator` structure has been replaced with a field of type `*keeper.Keeper`: + +```diff +type AnteDecorator struct { +- k channelkeeper.Keeper ++ k *keeper.Keeper +} + +- func NewAnteDecorator(k channelkeeper.Keeper) AnteDecorator { ++ func NewAnteDecorator(k *keeper.Keeper) AnteDecorator { + return AnteDecorator{k: k} +} +``` + +## IBC Apps + +### `OnChanOpenTry` must return negotiated application version + +The `OnChanOpenTry` application callback has been modified. +The return signature now includes the application version. +IBC applications must perform application version negotiation in `OnChanOpenTry` using the counterparty version. +The negotiated application version then must be returned in `OnChanOpenTry` to core IBC. +Core IBC will set this version in the TRYOPEN channel. + +### `OnChanOpenAck` will take additional `counterpartyChannelID` argument + +The `OnChanOpenAck` application callback has been modified. +The arguments now include the counterparty channel id. + +### `NegotiateAppVersion` removed from `IBCModule` interface + +Previously this logic was handled by the `NegotiateAppVersion` function. +Relayers would query this function before calling `ChanOpenTry`. +Applications would then need to verify that the passed in version was correct. +Now applications will perform this version negotiation during the channel handshake, thus removing the need for `NegotiateAppVersion`. + +### Channel state will not be set before application callback + +The channel handshake logic has been reorganized within core IBC. +Channel state will not be set in state after the application callback is performed. +Applications must rely only on the passed in channel parameters instead of querying the channel keeper for channel state. + +### IBC application callbacks moved from `AppModule` to `IBCModule` + +Previously, IBC module callbacks were apart of the `AppModule` type. +The recommended approach is to create an `IBCModule` type and move the IBC module callbacks from `AppModule` to `IBCModule` in a separate file `ibc_module.go`. + +The mock module go API has been broken in this release by applying the above format. +The IBC module callbacks have been moved from the mock modules `AppModule` into a new type `IBCModule`. + +As apart of this release, the mock module now supports middleware testing. Please see the [README](https://github.com/cosmos/ibc-go/blob/v3.0.0/testing/README.md#middleware-testing) for more information. + +Please review the [mock](https://github.com/cosmos/ibc-go/blob/v3.0.0/testing/mock/ibc_module.go) and [transfer](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/transfer/ibc_module.go) modules as examples. Additionally, [simapp](https://github.com/cosmos/ibc-go/blob/v3.0.0/testing/simapp/app.go) provides an example of how `IBCModule` types should now be added to the IBC router in favour of `AppModule`. + +### IBC testing package + +`TestChain`s are now created with chainID's beginning from an index of 1. Any calls to `GetChainID(0)` will now fail. Please increment all calls to `GetChainID` by 1. + +## Relayers + +`AppVersion` gRPC has been removed. +The `version` string in `MsgChanOpenTry` has been deprecated and will be ignored by core IBC. +Relayers no longer need to determine the version to use on the `ChanOpenTry` step. +IBC applications will determine the correct version using the counterparty version. + +## IBC Light Clients + +The `GetProofSpecs` function has been removed from the `ClientState` interface. This function was previously unused by core IBC. Light clients which don't use this function may remove it. diff --git a/docs/versioned_docs/version-v8.5.x/05-migrations/05-v3-to-v4.md b/docs/versioned_docs/version-v8.5.x/05-migrations/05-v3-to-v4.md new file mode 100644 index 0000000..b88f308 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/05-migrations/05-v3-to-v4.md @@ -0,0 +1,159 @@ +--- +title: IBC-Go v3 to v4 +sidebar_label: IBC-Go v3 to v4 +sidebar_position: 5 +slug: /migrations/v3-to-v4 +--- +# Migrating from ibc-go v3 to v4 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go/v3 -> github.com/cosmos/ibc-go/v4 +``` + +No genesis or in-place migrations required when upgrading from v1 or v2 of ibc-go. + +## Chains + +### ICS27 - Interchain Accounts + +The controller submodule implements now the 05-port `Middleware` interface instead of the 05-port `IBCModule` interface. Chains that integrate the controller submodule, need to create it with the `NewIBCMiddleware` constructor function. For example: + +```diff +- icacontroller.NewIBCModule(app.ICAControllerKeeper, icaAuthIBCModule) ++ icacontroller.NewIBCMiddleware(icaAuthIBCModule, app.ICAControllerKeeper) +``` + +where `icaAuthIBCModule` is the Interchain Accounts authentication IBC Module. + +### ICS29 - Fee Middleware + +The Fee Middleware module, as the name suggests, plays the role of an IBC middleware and as such must be configured by chain developers to route and handle IBC messages correctly. + +Please read the Fee Middleware [integration documentation](../04-middleware/01-ics29-fee/02-integration.md) for an in depth guide on how to configure the module correctly in order to incentivize IBC packets. + +Take a look at the following diff for an [example setup](https://github.com/cosmos/ibc-go/pull/1432/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08aL366) of how to incentivize ics27 channels. + +### Migration to fix support for base denoms with slashes + +As part of [v1.5.0](https://github.com/cosmos/ibc-go/releases/tag/v1.5.0), [v2.3.0](https://github.com/cosmos/ibc-go/releases/tag/v2.3.0) and [v3.1.0](https://github.com/cosmos/ibc-go/releases/tag/v3.1.0) some [migration handler code sample was documented](./01-support-denoms-with-slashes.md#upgrade-proposal) that needs to run in order to correct the trace information of coins transferred using ICS20 whose base denom contains slashes. + +Based on feedback from the community we add now an improved solution to run the same migration that does not require copying a large piece of code over from the migration document, but instead requires only adding a one-line upgrade handler. + +If the chain will migrate to supporting base denoms with slashes, it must set the appropriate params during the execution of the upgrade handler in `app.go`: + +```go +app.UpgradeKeeper.SetUpgradeHandler("MigrateTraces", + func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + // transfer module consensus version has been bumped to 2 + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }) +``` + +If a chain receives coins of a base denom with slashes before it upgrades to supporting it, the receive may pass however the trace information will be incorrect. + +E.g. If a base denom of `testcoin/testcoin/testcoin` is sent to a chain that does not support slashes in the base denom, the receive will be successful. However, the trace information stored on the receiving chain will be: `Trace: "transfer/{channel-id}/testcoin/testcoin", BaseDenom: "testcoin"`. + +This incorrect trace information must be corrected when the chain does upgrade to fully supporting denominations with slashes. + +## IBC Apps + +### ICS03 - Connection + +Crossing hellos have been removed from 03-connection handshake negotiation. +`PreviousConnectionId` in `MsgConnectionOpenTry` has been deprecated and is no longer used by core IBC. + +`NewMsgConnectionOpenTry` no longer takes in the `PreviousConnectionId` as crossing hellos are no longer supported. A non-empty `PreviousConnectionId` will fail basic validation for this message. + +### ICS04 - Channel + +The `WriteAcknowledgement` API now takes the `exported.Acknowledgement` type instead of passing in the acknowledgement byte array directly. +This is an API breaking change and as such IBC application developers will have to update any calls to `WriteAcknowledgement`. + +The `OnChanOpenInit` application callback has been modified. +The return signature now includes the application version as detailed in the latest IBC [spec changes](https://github.com/cosmos/ibc/pull/629). + +The `NewErrorAcknowledgement` method signature has changed. +It now accepts an `error` rather than a `string`. This was done in order to prevent accidental state changes. +All error acknowledgements now contain a deterministic ABCI code and error message. It is the responsibility of the application developer to emit error details in events. + +Crossing hellos have been removed from 04-channel handshake negotiation. +IBC Applications no longer need to account from already claimed capabilities in the `OnChanOpenTry` callback. The capability provided by core IBC must be able to be claimed with error. +`PreviousChannelId` in `MsgChannelOpenTry` has been deprecated and is no longer used by core IBC. + +`NewMsgChannelOpenTry` no longer takes in the `PreviousChannelId` as crossing hellos are no longer supported. A non-empty `PreviousChannelId` will fail basic validation for this message. + +### ICS27 - Interchain Accounts + +The `RegisterInterchainAccount` API has been modified to include an additional `version` argument. This change has been made in order to support ICS29 fee middleware, for relayer incentivization of ICS27 packets. +Consumers of the `RegisterInterchainAccount` are now expected to build the appropriate JSON encoded version string themselves and pass it accordingly. +This should be constructed within the interchain accounts authentication module which leverages the APIs exposed via the interchain accounts `controllerKeeper`. If an empty string is passed in the `version` argument, then the version will be initialized to a default value in the `OnChanOpenInit` callback of the controller's handler, so that channel handshake can proceed. + +The following code snippet illustrates how to construct an appropriate interchain accounts `Metadata` and encode it as a JSON bytestring: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +if err := k.icaControllerKeeper.RegisterInterchainAccount(ctx, msg.ConnectionId, msg.Owner, string(appVersion)); err != nil { + return err +} +``` + +Similarly, if the application stack is configured to route through ICS29 fee middleware and a fee enabled channel is desired, construct the appropriate ICS29 `Metadata` type: + +```go +icaMetadata := icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, +} + +appVersion, err := icatypes.ModuleCdc.MarshalJSON(&icaMetadata) +if err != nil { + return err +} + +feeMetadata := feetypes.Metadata{ + AppVersion: string(appVersion), + FeeVersion: feetypes.Version, +} + +feeEnabledVersion, err := feetypes.ModuleCdc.MarshalJSON(&feeMetadata) +if err != nil { + return err +} + +if err := k.icaControllerKeeper.RegisterInterchainAccount(ctx, msg.ConnectionId, msg.Owner, string(feeEnabledVersion)); err != nil { + return err +} +``` + +## Relayers + +When using the `DenomTrace` gRPC, the full IBC denomination with the `ibc/` prefix may now be passed in. + +Crossing hellos are no longer supported by core IBC for 03-connection and 04-channel. The handshake should be completed in the logical 4 step process (INIT, TRY, ACK, CONFIRM). diff --git a/docs/versioned_docs/version-v8.5.x/05-migrations/06-v4-to-v5.md b/docs/versioned_docs/version-v8.5.x/05-migrations/06-v4-to-v5.md new file mode 100644 index 0000000..d3fdafb --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/05-migrations/06-v4-to-v5.md @@ -0,0 +1,441 @@ +--- +title: IBC-Go v4 to v5 +sidebar_label: IBC-Go v4 to v5 +sidebar_position: 6 +slug: /migrations/v4-to-v5 +--- + +# Migrating from v4 to v5 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- [Chains](#chains) +- [IBC Apps](#ibc-apps) +- [Relayers](#relayers) +- [IBC Light Clients](#ibc-light-clients) + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +```go +github.com/cosmos/ibc-go/v4 -> github.com/cosmos/ibc-go/v5 +``` + +## Chains + +### Ante decorator + +The `AnteDecorator` type in `core/ante` has been renamed to `RedundantRelayDecorator` (and the corresponding constructor function to `NewRedundantRelayDecorator`). Therefore in the function that creates the instance of the `sdk.AnteHandler` type (e.g. `NewAnteHandler`) the change would be like this: + +```diff +func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { + // parameter validation + + anteDecorators := []sdk.AnteDecorator{ + // other ante decorators +- ibcante.NewAnteDecorator(opts.IBCkeeper), ++ ibcante.NewRedundantRelayDecorator(options.IBCKeeper), + } + + return sdk.ChainAnteDecorators(anteDecorators...), nil +} +``` + +The `AnteDecorator` was actually renamed twice, but in [this PR](https://github.com/cosmos/ibc-go/pull/1820) you can see the changes made for the final rename. + +## IBC Apps + +### Core + +The `key` parameter of the `NewKeeper` function in `modules/core/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + stakingKeeper clienttypes.StakingKeeper, + upgradeKeeper clienttypes.UpgradeKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +) *Keeper +``` + +The `RegisterRESTRoutes` function in `modules/core` has been removed. + +### ICS03 - Connection + +The `key` parameter of the `NewKeeper` function in `modules/core/03-connection/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + ck types.ClientKeeper +) Keeper +``` + +### ICS04 - Channel + +The function `NewPacketId` in `modules/core/04-channel/types` has been renamed to `NewPacketID`: + +```diff +- func NewPacketId( ++ func NewPacketID( + portID, + channelID string, + seq uint64 +) PacketId +``` + +The `key` parameter of the `NewKeeper` function in `modules/core/04-channel/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + clientKeeper types.ClientKeeper, + connectionKeeper types.ConnectionKeeper, + portKeeper types.PortKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +) Keeper +``` + +### ICS20 - Transfer + +The `key` parameter of the `NewKeeper` function in `modules/apps/transfer/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + ics4Wrapper types.ICS4Wrapper, + channelKeeper types.ChannelKeeper, + portKeeper types.PortKeeper, + authKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +) Keeper +``` + +The `amount` parameter of function `GetTransferCoin` in `modules/apps/transfer/types` is now of type `math.Int` (`"cosmossdk.io/math"`): + +```diff +func GetTransferCoin( + portID, channelID, baseDenom string, +- amount sdk.Int ++ amount math.Int +) sdk.Coin +``` + +The `RegisterRESTRoutes` function in `modules/apps/transfer` has been removed. + +### ICS27 - Interchain Accounts + +The `key` and `msgRouter` parameters of the `NewKeeper` functions in + +- `modules/apps/27-interchain-accounts/controller/keeper` +- and `modules/apps/27-interchain-accounts/host/keeper` + +have changed type. The `key` parameter is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`), and the `msgRouter` parameter is now of type `*icatypes.MessageRouter` (where `icatypes` is an import alias for `"github.com/cosmos/ibc-go/v5/modules/apps/27-interchain-accounts/types"`): + +```diff +// NewKeeper creates a new interchain accounts controller Keeper instance +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + ics4Wrapper icatypes.ICS4Wrapper, + channelKeeper icatypes.ChannelKeeper, + portKeeper icatypes.PortKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +- msgRouter *baseapp.MsgServiceRouter, ++ msgRouter *icatypes.MessageRouter, +) Keeper +``` + +```diff +// NewKeeper creates a new interchain accounts host Keeper instance +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + channelKeeper icatypes.ChannelKeeper, + portKeeper icatypes.PortKeeper, + accountKeeper icatypes.AccountKeeper, + scopedKeeper capabilitykeeper.ScopedKeeper, +- msgRouter *baseapp.MsgServiceRouter, ++ msgRouter *icatypes.MessageRouter, +) Keeper +``` + +The new `MessageRouter` interface is defined as: + +```go +type MessageRouter interface { + Handler(msg sdk.Msg) baseapp.MsgServiceHandler +} +``` + +The `RegisterRESTRoutes` function in `modules/apps/27-interchain-accounts` has been removed. + +An additional parameter, `ics4Wrapper` has been added to the `host` submodule `NewKeeper` function in `modules/apps/27-interchain-accounts/host/keeper`. +This allows the `host` submodule to correctly unwrap the channel version for channel reopening handshakes in the `OnChanOpenTry` callback. + +```diff +func NewKeeper( + cdc codec.BinaryCodec, + key storetypes.StoreKey, + paramSpace paramtypes.Subspace, ++ ics4Wrapper icatypes.ICS4Wrapper, + channelKeeper icatypes.ChannelKeeper, + portKeeper icatypes.PortKeeper, + accountKeeper icatypes.AccountKeeper, + scopedKeeper icatypes.ScopedKeeper, + msgRouter icatypes.MessageRouter, +) Keeper +``` + +#### Cosmos SDK message handler responses in packet acknowledgement + +The construction of the transaction response of a message execution on the host chain has changed. The `Data` field in the `sdk.TxMsgData` has been deprecated and since Cosmos SDK 0.46 the `MsgResponses` field contains the message handler responses packed into `Any`s. + +For chains on Cosmos SDK 0.45 and below, the message response was constructed like this: + +```go +txMsgData := &sdk.TxMsgData{ + Data: make([]*sdk.MsgData, len(msgs)), +} + +for i, msg := range msgs { + // message validation + + msgResponse, err := k.executeMsg(cacheCtx, msg) + // return if err != nil + + txMsgData.Data[i] = &sdk.MsgData{ + MsgType: sdk.MsgTypeURL(msg), + Data: msgResponse, + } +} + +// emit events + +txResponse, err := proto.Marshal(txMsgData) +// return if err != nil + +return txResponse, nil +``` + +And for chains on Cosmos SDK 0.46 and above, it is now done like this: + +```go +txMsgData := &sdk.TxMsgData{ + MsgResponses: make([]*codectypes.Any, len(msgs)), +} + +for i, msg := range msgs { + // message validation + + protoAny, err := k.executeMsg(cacheCtx, msg) + // return if err != nil + + txMsgData.MsgResponses[i] = protoAny +} + +// emit events + +txResponse, err := proto.Marshal(txMsgData) +// return if err != nil + +return txResponse, nil +``` + +When handling the acknowledgement in the `OnAcknowledgementPacket` callback of a custom ICA controller module, then depending on whether `txMsgData.Data` is empty or not, the logic to handle the message handler response will be different. **Only controller chains on Cosmos SDK 0.46 or above will be able to write the logic needed to handle the response from a host chain on Cosmos SDK 0.46 or above.** + +```go +var ack channeltypes.Acknowledgement +if err := channeltypes.SubModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { + return err +} + +var txMsgData sdk.TxMsgData +if err := proto.Unmarshal(ack.GetResult(), txMsgData); err != nil { + return err +} + +switch len(txMsgData.Data) { +case 0: // for SDK 0.46 and above + for _, msgResponse := range txMsgData.MsgResponses { + // unmarshall msgResponse and execute logic based on the response + } + return nil +default: // for SDK 0.45 and below + for _, msgData := range txMsgData.Data { + // unmarshall msgData and execute logic based on the response + } +} +``` + +See [ADR-03](/architecture/adr-003-ics27-acknowledgement#next-major-version-format) for more information or the [corresponding documentation about authentication modules](../02-apps/02-interchain-accounts/03-auth-modules.md#onacknowledgementpacket). + +### ICS29 - Fee Middleware + +The `key` parameter of the `NewKeeper` function in `modules/apps/29-fee` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + ics4Wrapper types.ICS4Wrapper, + channelKeeper types.ChannelKeeper, + portKeeper types.PortKeeper, + authKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, +) Keeper +``` + +The `RegisterRESTRoutes` function in `modules/apps/29-fee` has been removed. + +### IBC testing package + +The `MockIBCApp` type has been renamed to `IBCApp` (and the corresponding constructor function to `NewIBCApp`). This has resulted therefore in: + +- The `IBCApp` field of the `*IBCModule` in `testing/mock` to change its type as well to `*IBCApp`: + +```diff +type IBCModule struct { + appModule *AppModule +- IBCApp *MockIBCApp // base application of an IBC middleware stack ++ IBCApp *IBCApp // base application of an IBC middleware stack +} +``` + +- The `app` parameter to `*NewIBCModule` in `testing/mock` to change its type as well to `*IBCApp`: + +```diff +func NewIBCModule( + appModule *AppModule, +- app *MockIBCApp ++ app *IBCApp +) IBCModule +``` + +The `MockEmptyAcknowledgement` type has been renamed to `EmptyAcknowledgement` (and the corresponding constructor function to `NewEmptyAcknowledgement`). + +The `TestingApp` interface in `testing` has gone through some modifications: + +- The return type of the function `GetStakingKeeper` is not the concrete type `stakingkeeper.Keeper` anymore (where `stakingkeeper` is an import alias for `"github.com/cosmos/cosmos-sdk/x/staking/keeper"`), but it has been changed to the interface `ibctestingtypes.StakingKeeper` (where `ibctestingtypes` is an import alias for `""github.com/cosmos/ibc-go/v5/testing/types"`). See this [PR](https://github.com/cosmos/ibc-go/pull/2028) for more details. The `StakingKeeper` interface is defined as: + +```go +type StakingKeeper interface { + GetHistoricalInfo(ctx sdk.Context, height int64) (stakingtypes.HistoricalInfo, bool) +} +``` + +- The return type of the function `LastCommitID` has changed to `storetypes.CommitID` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`). + +See the following `git diff` for more details: + +```diff +type TestingApp interface { + abci.Application + + // ibc-go additions + GetBaseApp() *baseapp.BaseApp +- GetStakingKeeper() stakingkeeper.Keeper ++ GetStakingKeeper() ibctestingtypes.StakingKeeper + GetIBCKeeper() *keeper.Keeper + GetScopedIBCKeeper() capabilitykeeper.ScopedKeeper + GetTxConfig() client.TxConfig + + // Implemented by SimApp + AppCodec() codec.Codec + + // Implemented by BaseApp +- LastCommitID() sdk.CommitID ++ LastCommitID() storetypes.CommitID + LastBlockHeight() int64 +} +``` + +The `powerReduction` parameter of the function `SetupWithGenesisValSet` in `testing` is now of type `math.Int` (`"cosmossdk.io/math"`): + +```diff +func SetupWithGenesisValSet( + t *testing.T, + valSet *tmtypes.ValidatorSet, + genAccs []authtypes.GenesisAccount, + chainID string, +- powerReduction sdk.Int, ++ powerReduction math.Int, + balances ...banktypes.Balance +) TestingApp +``` + +The `accAmt` parameter of the functions + +- `AddTestAddrsFromPubKeys` , +- `AddTestAddrs` +- and `AddTestAddrsIncremental` + +in `testing/simapp` are now of type `math.Int` (`"cosmossdk.io/math"`): + +```diff +func AddTestAddrsFromPubKeys( + app *SimApp, + ctx sdk.Context, + pubKeys []cryptotypes.PubKey, +- accAmt sdk.Int, ++ accAmt math.Int +) +func addTestAddrs( + app *SimApp, + ctx sdk.Context, + accNum int, +- accAmt sdk.Int, ++ accAmt math.Int, + strategy GenerateAccountStrategy +) []sdk.AccAddress +func AddTestAddrsIncremental( + app *SimApp, + ctx sdk.Context, + accNum int, +- accAmt sdk.Int, ++ accAmt math.Int +) []sdk.AccAddress +``` + +The `RegisterRESTRoutes` function in `testing/mock` has been removed. + +## Relayers + +- No relevant changes were made in this release. + +## IBC Light Clients + +### ICS02 - Client + +The `key` parameter of the `NewKeeper` function in `modules/core/02-client/keeper` is now of type `storetypes.StoreKey` (where `storetypes` is an import alias for `"github.com/cosmos/cosmos-sdk/store/types"`): + +```diff +func NewKeeper( + cdc codec.BinaryCodec, +- key sdk.StoreKey, ++ key storetypes.StoreKey, + paramSpace paramtypes.Subspace, + sk types.StakingKeeper, + uk types.UpgradeKeeper +) Keeper +``` diff --git a/docs/versioned_docs/version-v8.5.x/05-migrations/07-v5-to-v6.md b/docs/versioned_docs/version-v8.5.x/05-migrations/07-v5-to-v6.md new file mode 100644 index 0000000..539b27e --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/05-migrations/07-v5-to-v6.md @@ -0,0 +1,299 @@ +--- +title: IBC-Go v5 to v6 +sidebar_label: IBC-Go v5 to v6 +sidebar_position: 7 +slug: /migrations/v5-to-v6 +--- + +# Migrating from ibc-go v5 to v6 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +## Chains + +The `ibc-go/v6` release introduces a new set of migrations for `27-interchain-accounts`. Ownership of ICS27 channel capabilities is transferred from ICS27 authentication modules and will now reside with the ICS27 controller submodule moving forward. + +For chains which contain a custom authentication module using the ICS27 controller submodule this requires a migration function to be included in the chain upgrade handler. A subsequent migration handler is run automatically, asserting the ownership of ICS27 channel capabilities has been transferred successfully. + +This migration is not required for chains which *do not* contain a custom authentication module using the ICS27 controller submodule. + +This migration facilitates the addition of the ICS27 controller submodule `MsgServer` which provides a standardised approach to integrating existing forms of authentication such as `x/gov` and `x/group` provided by the Cosmos SDK. + +For more information please refer to [ADR 009](/architecture/adr-009-v6-ics27-msgserver). + +### Upgrade proposal + +Please refer to [PR #2383](https://github.com/cosmos/ibc-go/pull/2383) for integrating the ICS27 channel capability migration logic or follow the steps outlined below: + +1. Add the upgrade migration logic to chain distribution. This may be, for example, maintained under a package `app/upgrades/v6`. + +```go +package v6 + +import ( + "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper" + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + + v6 "github.com/cosmos/ibc-go/v6/modules/apps/27-interchain-accounts/controller/migrations/v6" +) + +const ( + UpgradeName = "v6" +) + +func CreateUpgradeHandler( + mm *module.Manager, + configurator module.Configurator, + cdc codec.BinaryCodec, + capabilityStoreKey *storetypes.KVStoreKey, + capabilityKeeper *capabilitykeeper.Keeper, + moduleName string, +) upgradetypes.UpgradeHandler { + return func(ctx sdk.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + if err := v6.MigrateICS27ChannelCapability(ctx, cdc, capabilityStoreKey, capabilityKeeper, moduleName); err != nil { + return nil, err + } + + return mm.RunMigrations(ctx, configurator, vm) + } +} +``` + +2. Set the upgrade handler in `app.go`. The `moduleName` parameter refers to the authentication module's `ScopedKeeper` name. This is the name provided upon instantiation in `app.go` via the [`x/capability` keeper `ScopeToModule(moduleName string)`](https://github.com/cosmos/cosmos-sdk/blob/v0.46.1/x/capability/keeper/keeper.go#L70) method. [See here for an example in `simapp`](https://github.com/cosmos/ibc-go/blob/v5.0.0/testing/simapp/app.go#L304). + +```go +app.UpgradeKeeper.SetUpgradeHandler( + v6.UpgradeName, + v6.CreateUpgradeHandler( + app.mm, + app.configurator, + app.appCodec, + app.keys[capabilitytypes.ModuleName], + app.CapabilityKeeper, + >>>> moduleName <<<<, + ), +) +``` + +## IBC Apps + +### ICS27 - Interchain Accounts + +#### Controller APIs + +In previous releases of ibc-go, chain developers integrating the ICS27 interchain accounts controller functionality were expected to create a custom `Base Application` referred to as an authentication module, see the section [Building an authentication module](../02-apps/02-interchain-accounts/03-auth-modules.md) from the documentation. + +The `Base Application` was intended to be composed with the ICS27 controller submodule `Keeper` and facilitate many forms of message authentication depending on a chain's particular use case. + +Prior to ibc-go v6 the controller submodule exposed only these two functions (to which we will refer as the legacy APIs): + +- [`RegisterInterchainAccount`](https://github.com/cosmos/ibc-go/blob/v5.0.0/modules/apps/27-interchain-accounts/controller/keeper/account.go#L19) +- [`SendTx`](https://github.com/cosmos/ibc-go/blob/v5.0.0/modules/apps/27-interchain-accounts/controller/keeper/relay.go#L18) + +However, these functions have now been deprecated in favour of the new controller submodule `MsgServer` and will be removed in later releases. + +Both APIs remain functional and maintain backwards compatibility in ibc-go v6, however consumers of these APIs are now recommended to follow the message passing paradigm outlined in Cosmos SDK [ADR 031](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-031-msg-service.md) and [ADR 033](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-033-protobuf-inter-module-comm.md). This is facilitated by the Cosmos SDK [`MsgServiceRouter`](https://github.com/cosmos/cosmos-sdk/blob/main/baseapp/msg_service_router.go#L17) and chain developers creating custom application logic can now omit the ICS27 controller submodule `Keeper` from their module and instead depend on message routing. + +Depending on the use case, developers of custom authentication modules face one of three scenarios: + +![auth-module-decision-tree.png](./images/auth-module-decision-tree.png) + +**My authentication module needs to access IBC packet callbacks** + +Application developers that wish to consume IBC packet callbacks and react upon packet acknowledgements **must** continue using the controller submodule's legacy APIs. The authentication modules will not need a `ScopedKeeper` anymore, though, because the channel capability will be claimed by the controller submodule. For example, given an Interchain Accounts authentication module keeper `ICAAuthKeeper`, the authentication module's `ScopedKeeper` (`scopedICAAuthKeeper`) is not needed anymore and can be removed for the argument list of the keeper constructor function, as shown here: + +```diff +app.ICAAuthKeeper = icaauthkeeper.NewKeeper( + appCodec, + keys[icaauthtypes.StoreKey], + app.ICAControllerKeeper, +- scopedICAAuthKeeper, +) +``` + +Please note that the authentication module's `ScopedKeeper` name is still needed as part of the channel capability migration described in section [Upgrade proposal](#upgrade-proposal) above. Therefore the authentication module's `ScopedKeeper` cannot be completely removed from the chain code until the migration has run. + +In the future, the use of the legacy APIs for accessing packet callbacks will be replaced by IBC Actor Callbacks (see [ADR 008](https://github.com/cosmos/ibc-go/pull/1976) for more details) and it will also be possible to access them with the `MsgServiceRouter`. + +**My authentication module does not need access to IBC packet callbacks** + +The authentication module can migrate from using the legacy APIs and it can be composed instead with the `MsgServiceRouter`, so that the authentication module is able to pass messages to the controller submodule's `MsgServer` to register interchain accounts and send packets to the interchain account. For example, given an Interchain Accounts authentication module keeper `ICAAuthKeeper`, the ICS27 controller submodule keeper (`ICAControllerKeeper`) and authentication module scoped keeper (`scopedICAAuthKeeper`) are not needed anymore and can be replaced with the `MsgServiceRouter`, as shown here: + +```diff +app.ICAAuthKeeper = icaauthkeeper.NewKeeper( + appCodec, + keys[icaauthtypes.StoreKey], +- app.ICAControllerKeeper, +- scopedICAAuthKeeper, ++ app.MsgServiceRouter(), +) +``` + +In your authentication module you can route messages to the controller submodule's `MsgServer` instead of using the legacy APIs. For example, for registering an interchain account: + +```diff +- if err := keeper.icaControllerKeeper.RegisterInterchainAccount( +- ctx, +- connectionID, +- owner.String(), +- version, +- ); err != nil { +- return err +- } ++ msg := controllertypes.NewMsgRegisterInterchainAccount( ++ connectionID, ++ owner.String(), ++ version, ++ ) ++ handler := keeper.msgRouter.Handler(msg) ++ res, err := handler(ctx, msg) ++ if err != nil { ++ return err ++ } +``` + +where `controllertypes` is an import alias for `"github.com/cosmos/ibc-go/v6/modules/apps/27-interchain-accounts/controller/types"`. + +In addition, in this use case the authentication module does not need to implement the `IBCModule` interface anymore. + +**I do not need a custom authentication module anymore** + +If your authentication module does not have any extra functionality compared to the default authentication module added in ibc-go v6 (the `MsgServer`), or if you can use a generic authentication module, such as the `x/auth`, `x/gov` or `x/group` modules from the Cosmos SDK (v0.46 and later), then you can remove your authentication module completely and use instead the gRPC endpoints of the `MsgServer` or the CLI added in ibc-go v6. + +Please remember that the authentication module's `ScopedKeeper` name is still needed as part of the channel capability migration described in section [Upgrade proposal](#upgrade-proposal) above. + +#### Host params + +The ICS27 host submodule default params have been updated to include the `AllowAllHostMsgs` wildcard `*`. +This enables execution of any `sdk.Msg` type for ICS27 registered on the host chain `InterfaceRegistry`. + +```diff +// AllowAllHostMsgs holds the string key that allows all message types on interchain accounts host module +const AllowAllHostMsgs = "*" + +... + +// DefaultParams is the default parameter configuration for the host submodule +func DefaultParams() Params { +- return NewParams(DefaultHostEnabled, nil) ++ return NewParams(DefaultHostEnabled, []string{AllowAllHostMsgs}) +} +``` + +#### API breaking changes + +`SerializeCosmosTx` takes in a `[]proto.Message` instead of `[]sdk.Message`. This allows for the serialization of proto messages without requiring the fulfillment of the `sdk.Msg` interface. + +The `27-interchain-accounts` genesis types have been moved to their own package: `modules/apps/27-interchain-acccounts/genesis/types`. +This change facilitates the addition of the ICS27 controller submodule `MsgServer` and avoids cyclic imports. This should have minimal disruption to chain developers integrating `27-interchain-accounts`. + +The ICS27 host submodule `NewKeeper` function in `modules/apps/27-interchain-acccounts/host/keeper` now includes an additional parameter of type `ICS4Wrapper`. +This provides the host submodule with the ability to correctly unwrap channel versions in the event of a channel reopening handshake. + +```diff +func NewKeeper( + cdc codec.BinaryCodec, key storetypes.StoreKey, paramSpace paramtypes.Subspace, +- channelKeeper icatypes.ChannelKeeper, portKeeper icatypes.PortKeeper, ++ ics4Wrapper icatypes.ICS4Wrapper, channelKeeper icatypes.ChannelKeeper, portKeeper icatypes.PortKeeper, + accountKeeper icatypes.AccountKeeper, scopedKeeper icatypes.ScopedKeeper, msgRouter icatypes.MessageRouter, +) Keeper +``` + +### ICS29 - `NewKeeper` API change + +The `NewKeeper` function of ICS29 has been updated to remove the `paramSpace` parameter as it was unused. + +```diff +func NewKeeper( +- cdc codec.BinaryCodec, key storetypes.StoreKey, paramSpace paramtypes.Subspace, +- ics4Wrapper types.ICS4Wrapper, channelKeeper types.ChannelKeeper, portKeeper types.PortKeeper, authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, ++ cdc codec.BinaryCodec, key storetypes.StoreKey, ++ ics4Wrapper types.ICS4Wrapper, channelKeeper types.ChannelKeeper, ++ portKeeper types.PortKeeper, authKeeper types.AccountKeeper, bankKeeper types.BankKeeper, +) Keeper { +``` + +### ICS20 - `SendTransfer` is no longer exported + +The `SendTransfer` function of ICS20 has been removed. IBC transfers should now be initiated with `MsgTransfer` and routed to the ICS20 `MsgServer`. + +See below for example: + +```go +if handler := msgRouter.Handler(msgTransfer); handler != nil { + if err := msgTransfer.ValidateBasic(); err != nil { + return nil, err + } + + res, err := handler(ctx, msgTransfer) + if err != nil { + return nil, err + } +} +``` + +### ICS04 - `SendPacket` API change + +The `SendPacket` API has been simplified: + +```diff +// SendPacket is called by a module in order to send an IBC packet on a channel +func (k Keeper) SendPacket( + ctx sdk.Context, + channelCap *capabilitytypes.Capability, +- packet exported.PacketI, +-) error { ++ sourcePort string, ++ sourceChannel string, ++ timeoutHeight clienttypes.Height, ++ timeoutTimestamp uint64, ++ data []byte, ++) (uint64, error) { +``` + +Callers no longer need to pass in a pre-constructed packet. +The destination port/channel identifiers and the packet sequence will be determined by core IBC. +`SendPacket` will return the packet sequence. + +### IBC testing package + +The `SendPacket` API has been simplified: + +```diff +// SendPacket is called by a module in order to send an IBC packet on a channel +func (k Keeper) SendPacket( + ctx sdk.Context, + channelCap *capabilitytypes.Capability, +- packet exported.PacketI, +-) error { ++ sourcePort string, ++ sourceChannel string, ++ timeoutHeight clienttypes.Height, ++ timeoutTimestamp uint64, ++ data []byte, ++) (uint64, error) { +``` + +Callers no longer need to pass in a pre-constructed packet. `SendPacket` will return the packet sequence. + +## Relayers + +- No relevant changes were made in this release. + +## IBC Light Clients + +- No relevant changes were made in this release. diff --git a/docs/versioned_docs/version-v8.5.x/05-migrations/08-v6-to-v7.md b/docs/versioned_docs/version-v8.5.x/05-migrations/08-v6-to-v7.md new file mode 100644 index 0000000..e2214cf --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/05-migrations/08-v6-to-v7.md @@ -0,0 +1,357 @@ +--- +title: IBC-Go v6 to v7 +sidebar_label: IBC-Go v6 to v7 +sidebar_position: 8 +slug: /migrations/v6-to-v7 +--- +# Migrating from ibc-go v6 to v7 + +This document is intended to highlight significant changes which may require more information than presented in the CHANGELOG. +Any changes that must be done by a user of ibc-go should be documented here. + +There are four sections based on the four potential user groups of this document: + +- Chains +- IBC Apps +- Relayers +- IBC Light Clients + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated to bump the version number on major releases. + +## Chains + +Chains will perform automatic migrations to remove existing localhost clients and to migrate the solomachine to v3 of the protobuf definition. + +An optional upgrade handler has been added to prune expired tendermint consensus states. It may be used during any upgrade (from v7 onwards). +Add the following to the function call to the upgrade handler in `app/app.go`, to perform the optional state pruning. + +```go +import ( + // ... + ibctmmigrations "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint/migrations" +) + +// ... + +app.UpgradeKeeper.SetUpgradeHandler( + upgradeName, + func(ctx sdk.Context, _ upgradetypes.Plan, _ module.VersionMap) (module.VersionMap, error) { + // prune expired tendermint consensus states to save storage space + _, err := ibctmmigrations.PruneExpiredConsensusStates(ctx, app.Codec, app.IBCKeeper.ClientKeeper) + if err != nil { + return nil, err + } + + return app.mm.RunMigrations(ctx, app.configurator, fromVM) + }, +) +``` + +Checkout the logs to see how many consensus states are pruned. + +### Light client registration + +Chains must explicitly register the types of any light client modules it wishes to integrate. + +#### Tendermint registration + +To register the tendermint client, modify the `app.go` file to include the tendermint `AppModuleBasic`: + +```diff +import ( + // ... ++ ibctm "github.com/cosmos/ibc-go/v7/modules/light-clients/07-tendermint" +) + +// ... + +ModuleBasics = module.NewBasicManager( + ... + ibc.AppModuleBasic{}, ++ ibctm.AppModuleBasic{}, + ... +) +``` + +It may be useful to reference the [PR](https://github.com/cosmos/ibc-go/pull/2825) which added the `AppModuleBasic` for the tendermint client. + +#### Solo machine registration + +To register the solo machine client, modify the `app.go` file to include the solo machine `AppModuleBasic`: + +```diff +import ( + // ... ++ solomachine "github.com/cosmos/ibc-go/v7/modules/light-clients/06-solomachine" +) + +// ... + +ModuleBasics = module.NewBasicManager( + ... + ibc.AppModuleBasic{}, ++ solomachine.AppModuleBasic{}, + ... +) +``` + +It may be useful to reference the [PR](https://github.com/cosmos/ibc-go/pull/2826) which added the `AppModuleBasic` for the solo machine client. + +### Testing package API + +The `SetChannelClosed` utility method in `testing/endpoint.go` has been updated to `SetChannelState`, which will take a `channeltypes.State` argument so that the `ChannelState` can be set to any of the possible channel states. + +## IBC Apps + +- No relevant changes were made in this release. + +## Relayers + +- No relevant changes were made in this release. + +## IBC Light Clients + +### `ClientState` interface changes + +The `VerifyUpgradeAndUpdateState` function has been modified. The client state and consensus state return values have been removed. + +Light clients **must** handle all management of client and consensus states including the setting of updated client state and consensus state in the client store. + +The `Initialize` method is now expected to set the initial client state, consensus state and any client-specific metadata in the provided store upon client creation. + +The `CheckHeaderAndUpdateState` method has been split into 4 new methods: + +- `VerifyClientMessage` verifies a `ClientMessage`. A `ClientMessage` could be a `Header`, `Misbehaviour`, or batch update. Calls to `CheckForMisbehaviour`, `UpdateState`, and `UpdateStateOnMisbehaviour` will assume that the content of the `ClientMessage` has been verified and can be trusted. An error should be returned if the `ClientMessage` fails to verify. + +- `CheckForMisbehaviour` checks for evidence of a misbehaviour in `Header` or `Misbehaviour` types. + +- `UpdateStateOnMisbehaviour` performs appropriate state changes on a `ClientState` given that misbehaviour has been detected and verified. + +- `UpdateState` updates and stores as necessary any associated information for an IBC client, such as the `ClientState` and corresponding `ConsensusState`. An error is returned if `ClientMessage` is of type `Misbehaviour`. Upon successful update, a list containing the updated consensus state height is returned. + +The `CheckMisbehaviourAndUpdateState` function has been removed from `ClientState` interface. This functionality is now encapsulated by the usage of `VerifyClientMessage`, `CheckForMisbehaviour`, `UpdateStateOnMisbehaviour`. + +The function `GetTimestampAtHeight` has been added to the `ClientState` interface. It should return the timestamp for a consensus state associated with the provided height. + +Prior to ibc-go/v7 the `ClientState` interface defined a method for each data type which was being verified in the counterparty state store. +The state verification functions for all IBC data types have been consolidated into two generic methods, `VerifyMembership` and `VerifyNonMembership`. +Both are expected to be provided with a standardised key path, `exported.Path`, as defined in [ICS 24 host requirements](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements). Membership verification requires callers to provide the marshalled value `[]byte`. Delay period values should be zero for non-packet processing verification. A zero proof height is now allowed by core IBC and may be passed into `VerifyMembership` and `VerifyNonMembership`. Light clients are responsible for returning an error if a zero proof height is invalid behaviour. + +See below for an example of how ibc-go now performs channel state verification. + +```go +merklePath := commitmenttypes.NewMerklePath(host.ChannelPath(portID, channelID)) +merklePath, err := commitmenttypes.ApplyPrefix(connection.GetCounterparty().GetPrefix(), merklePath) +if err != nil { + return err +} + +channelEnd, ok := channel.(channeltypes.Channel) +if !ok { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "invalid channel type %T", channel) +} + +bz, err := k.cdc.Marshal(&channelEnd) +if err != nil { + return err +} + +if err := clientState.VerifyMembership( + ctx, clientStore, k.cdc, height, + 0, 0, // skip delay period checks for non-packet processing verification + proof, merklePath, bz, +); err != nil { + return sdkerrors.Wrapf(err, "failed channel state verification for client (%s)", clientID) +} +``` + +### `Header` and `Misbehaviour` + +`exported.Header` and `exported.Misbehaviour` interface types have been merged and renamed to `ClientMessage` interface. + +`GetHeight` function has been removed from `exported.Header` and thus is not included in the `ClientMessage` interface + +### `ConsensusState` + +The `GetRoot` function has been removed from consensus state interface since it was not used by core IBC. + +### Client keeper + +Keeper function `CheckMisbehaviourAndUpdateState` has been removed since function `UpdateClient` can now handle updating `ClientState` on `ClientMessage` type which can be any `Misbehaviour` implementations. + +### SDK message + +`MsgSubmitMisbehaviour` is deprecated since `MsgUpdateClient` can now submit a `ClientMessage` type which can be any `Misbehaviour` implementations. + +The field `header` in `MsgUpdateClient` has been renamed to `client_message`. + +## Solomachine + +The `06-solomachine` client implementation has been simplified in ibc-go/v7. In-place store migrations have been added to migrate solomachine clients from `v2` to `v3`. + +### `ClientState` + +The `ClientState` protobuf message definition has been updated to remove the deprecated `bool` field `allow_update_after_proposal`. + +```diff +message ClientState { + option (gogoproto.goproto_getters) = false; + + uint64 sequence = 1; + bool is_frozen = 2 [(gogoproto.moretags) = "yaml:\"is_frozen\""]; + ConsensusState consensus_state = 3 [(gogoproto.moretags) = "yaml:\"consensus_state\""]; +- bool allow_update_after_proposal = 4 [(gogoproto.moretags) = "yaml:\"allow_update_after_proposal\""]; +} +``` + +### `Header` and `Misbehaviour` + +The `06-solomachine` protobuf message `Header` has been updated to remove the `sequence` field. This field was seen as redundant as the implementation can safely rely on the `sequence` value maintained within the `ClientState`. + +```diff +message Header { + option (gogoproto.goproto_getters) = false; + +- uint64 sequence = 1; +- uint64 timestamp = 2; +- bytes signature = 3; +- google.protobuf.Any new_public_key = 4 [(gogoproto.moretags) = "yaml:\"new_public_key\""]; +- string new_diversifier = 5 [(gogoproto.moretags) = "yaml:\"new_diversifier\""]; ++ uint64 timestamp = 1; ++ bytes signature = 2; ++ google.protobuf.Any new_public_key = 3 [(gogoproto.moretags) = "yaml:\"new_public_key\""]; ++ string new_diversifier = 4 [(gogoproto.moretags) = "yaml:\"new_diversifier\""]; +} +``` + +Similarly, the `Misbehaviour` protobuf message has been updated to remove the `client_id` field. + +```diff +message Misbehaviour { + option (gogoproto.goproto_getters) = false; + +- string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""]; +- uint64 sequence = 2; +- SignatureAndData signature_one = 3 [(gogoproto.moretags) = "yaml:\"signature_one\""]; +- SignatureAndData signature_two = 4 [(gogoproto.moretags) = "yaml:\"signature_two\""]; ++ uint64 sequence = 1; ++ SignatureAndData signature_one = 2 [(gogoproto.moretags) = "yaml:\"signature_one\""]; ++ SignatureAndData signature_two = 3 [(gogoproto.moretags) = "yaml:\"signature_two\""]; +} +``` + +### `SignBytes` + +Most notably, the `SignBytes` protobuf definition has been modified to replace the `data_type` field with a new field, `path`. The `path` field is defined as `bytes` and represents a serialized [ICS-24](https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements) standardized key path under which the `data` is stored. + +```diff +message SignBytes { + option (gogoproto.goproto_getters) = false; + + uint64 sequence = 1; + uint64 timestamp = 2; + string diversifier = 3; +- DataType data_type = 4 [(gogoproto.moretags) = "yaml:\"data_type\""]; ++ bytes path = 4; + bytes data = 5; +} +``` + +The `DataType` enum and all associated data types have been removed, greatly reducing the number of message definitions and complexity in constructing the `SignBytes` message type. Likewise, solomachine implementations must now use the serialized `path` value when constructing `SignatureAndData` for signature verification of `SignBytes` data. + +```diff +message SignatureAndData { + option (gogoproto.goproto_getters) = false; + + bytes signature = 1; +- DataType data_type = 2 [(gogoproto.moretags) = "yaml:\"data_type\""]; ++ bytes path = 2; + bytes data = 3; + uint64 timestamp = 4; +} +``` + +For more information, please refer to [ADR-007](https://github.com/cosmos/ibc-go/blob/02-client-refactor-beta1/docs/architecture/adr-007-solomachine-signbytes.md). + +### IBC module constants + +IBC module constants have been moved from the `host` package to the `exported` package. Any usages will need to be updated. + +```diff +import ( + // ... +- host "github.com/cosmos/ibc-go/v7/modules/core/24-host" ++ ibcexported "github.com/cosmos/ibc-go/v7/modules/core/exported" + // ... +) + +- host.ModuleName ++ ibcexported.ModuleName + +- host.StoreKey ++ ibcexported.StoreKey + +- host.QuerierRoute ++ ibcexported.QuerierRoute + +- host.RouterKey ++ ibcexported.RouterKey +``` + +## Upgrading to Cosmos SDK 0.47 + +The following should be considered as complementary to [Cosmos SDK v0.47 UPGRADING.md](https://github.com/cosmos/cosmos-sdk/blob/v0.47.0-rc2/UPGRADING.md). + +### Protobuf + +Protobuf code generation, linting and formatting have been updated to leverage the `ghcr.io/cosmos/proto-builder:0.11.5` docker container. IBC protobuf definitions are now packaged and published to [buf.build/cosmos/ibc](https://buf.build/cosmos/ibc) via CI workflows. The `third_party/proto` directory has been removed in favour of dependency management using [buf.build](https://docs.buf.build/introduction). + +### App modules + +Legacy APIs of the `AppModule` interface have been removed from ibc-go modules. For example, for + +```diff +- // Route implements the AppModule interface +- func (am AppModule) Route() sdk.Route { +- return sdk.Route{} +- } +- +- // QuerierRoute implements the AppModule interface +- func (AppModule) QuerierRoute() string { +- return types.QuerierRoute +- } +- +- // LegacyQuerierHandler implements the AppModule interface +- func (am AppModule) LegacyQuerierHandler(*codec.LegacyAmino) sdk.Querier { +- return nil +- } +- +- // ProposalContents doesn't return any content functions for governance proposals. +- func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalContent { +- return nil +- } +``` + +### Imports + +Imports for ics23 have been updated as the repository have been migrated from confio to cosmos. + +```diff +import ( + // ... +- ics23 "github.com/confio/ics23/go" ++ ics23 "github.com/cosmos/ics23/go" + // ... +) +``` + +Imports for gogoproto have been updated. + +```diff +import ( + // ... +- "github.com/gogo/protobuf/proto" ++ "github.com/cosmos/gogoproto/proto" + // ... +) +``` diff --git a/docs/versioned_docs/version-v8.5.x/05-migrations/09-v7-to-v7_1.md b/docs/versioned_docs/version-v8.5.x/05-migrations/09-v7-to-v7_1.md new file mode 100644 index 0000000..04b7b30 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/05-migrations/09-v7-to-v7_1.md @@ -0,0 +1,66 @@ +--- +title: IBC-Go v7 to v7.1 +sidebar_label: IBC-Go v7 to v7.1 +sidebar_position: 9 +slug: /migrations/v7-to-v7_1 +--- + +# Migrating from v7 to v7.1 + +This guide provides instructions for migrating to version `v7.1.0` of ibc-go. + +There are four sections based on the four potential user groups of this document: + +- [Migrating from v7 to v7.1](#migrating-from-v7-to-v71) + - [Chains](#chains) + - [IBC Apps](#ibc-apps) + - [Relayers](#relayers) + - [IBC Light Clients](#ibc-light-clients) + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. + +## Chains + +In the previous release of ibc-go, the localhost `v1` light client module was deprecated and removed. The ibc-go `v7.1.0` release introduces `v2` of the 09-localhost light client module. + +An [automatic migration handler](https://github.com/cosmos/ibc-go/blob/v7.2.0/modules/core/module.go#L127-L145) is configured in the core IBC module to set the localhost `ClientState` and sentinel `ConnectionEnd` in state. + +In order to use the 09-localhost client chains must update the `AllowedClients` parameter in the 02-client submodule of core IBC. This can be configured directly in the application upgrade handler or alternatively updated via the legacy governance parameter change proposal. +We **strongly** recommend chains to perform this action so that intra-ledger communication can be carried out using the familiar IBC interfaces. + +See the upgrade handler code sample provided below or [follow this link](https://github.com/cosmos/ibc-go/blob/v7.2.0/testing/simapp/upgrades/upgrades.go#L85) for the upgrade handler used by the ibc-go simapp. + +```go +func CreateV7LocalhostUpgradeHandler( + mm *module.Manager, + configurator module.Configurator, + clientKeeper clientkeeper.Keeper, +) upgradetypes.UpgradeHandler { + return func(ctx sdk.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + // explicitly update the IBC 02-client params, adding the localhost client type + params := clientKeeper.GetParams(ctx) + params.AllowedClients = append(params.AllowedClients, exported.Localhost) + clientKeeper.SetParams(ctx, params) + + return mm.RunMigrations(ctx, configurator, vm) + } +} +``` + +### Transfer migration + +An [automatic migration handler](https://github.com/cosmos/ibc-go/blob/v7.2.0/modules/apps/transfer/module.go#L111-L113) is configured in the transfer module to set the total amount in escrow for all denominations of coins that have been sent out. For each denomination a state entry is added with the total amount of coins in escrow regardless of the channel from which they were transferred. + +## IBC Apps + +- No relevant changes were made in this release. + +## Relayers + +The event attribute `packet_connection` (`connectiontypes.AttributeKeyConnection`) has been deprecated. +Please use the `connection_id` attribute (`connectiontypes.AttributeKeyConnectionID`) which is emitted by all channel events. +Only send packet, receive packet, write acknowledgement, and acknowledge packet events used `packet_connection` previously. + +## IBC Light Clients + +- No relevant changes were made in this release. diff --git a/docs/versioned_docs/version-v8.5.x/05-migrations/10-v7_2-to-v7_3.md b/docs/versioned_docs/version-v8.5.x/05-migrations/10-v7_2-to-v7_3.md new file mode 100644 index 0000000..83cbd44 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/05-migrations/10-v7_2-to-v7_3.md @@ -0,0 +1,50 @@ +--- +title: IBC-Go v7.2 to v7.3 +sidebar_label: IBC-Go v7.2 to v7.3 +sidebar_position: 10 +slug: /migrations/v7_2-to-v7_3 +--- + +# Migrating from v7.2 to v7.3 + +This guide provides instructions for migrating to version `v7.3.0` of ibc-go. + +There are four sections based on the four potential user groups of this document: + +- [Migrating from v7.2 to v7.3](#migrating-from-v72-to-v73) + - [Chains](#chains) + - [IBC Apps](#ibc-apps) + - [Relayers](#relayers) + - [IBC Light Clients](#ibc-light-clients) + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. + +## Chains + +- No relevant changes were made in this release. + +## IBC Apps + +A set of interfaces have been added that IBC applications may optionally implement. Developers interested in integrating their applications with the [callbacks middleware](../04-middleware/02-callbacks/01-overview.md) should implement these interfaces so that the callbacks middleware can retrieve the desired callback addresses on the source and destination chains and execute actions on packet lifecycle events. The interfaces are [`PacketDataUnmarshaler`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/core/05-port/types/module.go#L142-L147), [`PacketDataProvider`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/core/exported/packet.go#L43-L52) and [`PacketData`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/core/exported/packet.go#L36-L41). + +Sample implementations are available for reference. For `transfer`: + +- [`PacketDataUnmarshaler`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/transfer/ibc_module.go#L303-L313), +- [`PacketDataProvider`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/transfer/types/packet.go#L85-L105) +- and [`PacketData`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/transfer/types/packet.go#L74-L83). + +For `27-interchain-accounts`: + +- [`PacketDataUnmarshaler`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/27-interchain-accounts/controller/ibc_middleware.go#L258-L268), +- [`PacketDataProvider`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/27-interchain-accounts/types/packet.go#L94-L114) +- and [`PacketData`](https://github.com/cosmos/ibc-go/blob/v7.3.0-rc1/modules/apps/27-interchain-accounts/types/packet.go#L78-L92). + +## Relayers + +- No relevant changes were made in this release. + +## IBC Light Clients + +### 06-solomachine + +Solo machines are now expected to sign data on a path that 1) does not include a connection prefix (e.g `ibc`) and 2) does not escape any characters. See PR [#4429](https://github.com/cosmos/ibc-go/pull/4429) for more details. We recommend **NOT** using the solo machine light client of versions lower than v7.3.0. diff --git a/docs/versioned_docs/version-v8.5.x/05-migrations/11-v7-to-v8.md b/docs/versioned_docs/version-v8.5.x/05-migrations/11-v7-to-v8.md new file mode 100644 index 0000000..b9814ff --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/05-migrations/11-v7-to-v8.md @@ -0,0 +1,221 @@ +--- +title: IBC-Go v7 to v8 +sidebar_label: IBC-Go v7 to v8 +sidebar_position: 11 +slug: /migrations/v7-to-v8 +--- + +# Migrating from v7 to v8 + +This guide provides instructions for migrating to version `v8.0.0` of ibc-go. + +There are four sections based on the four potential user groups of this document: + +- [Migrating from v7 to v8](#migrating-from-v7-to-v8) + - [Chains](#chains) + - [Cosmos SDK v0.50 upgrade](#cosmos-sdk-v050-upgrade) + - [Authority](#authority) + - [Testing package](#testing-package) + - [Params migration](#params-migration) + - [Governance V1 migration](#governance-v1-migration) + - [Transfer migration](#transfer-migration) + - [IBC Apps](#ibc-apps) + - [ICS20 - Transfer](#ics20---transfer) + - [ICS27 - Interchain Accounts](#ics27---interchain-accounts) + - [Relayers](#relayers) + - [IBC Light Clients](#ibc-light-clients) + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. + +## Chains + +The type of the `PortKeeper` field of the IBC keeper have been changed to `*portkeeper.Keeper`: + +```diff +// Keeper defines each ICS keeper for IBC +type Keeper struct { + // implements gRPC QueryServer interface + types.QueryServer + + cdc codec.BinaryCodec + + ClientKeeper clientkeeper.Keeper + ConnectionKeeper connectionkeeper.Keeper + ChannelKeeper channelkeeper.Keeper +- PortKeeper portkeeper.Keeper ++ PortKeeper *portkeeper.Keeper + Router *porttypes.Router + + authority string +} +``` + +See [this PR](https://github.com/cosmos/ibc-go/pull/4703/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08a) for the changes required in `app.go`. + +An extra parameter `totalEscrowed` of type `sdk.Coins` has been added to transfer module's [`NewGenesisState` function](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/transfer/types/genesis.go#L10). This parameter specifies the total amount of tokens that are in the module's escrow accounts. + +### Cosmos SDK v0.50 upgrade + +Version `v8.0.0` of ibc-go upgrades to Cosmos SDK v0.50. Please follow the [Cosmos SDK v0.50 upgrading guide](https://github.com/cosmos/cosmos-sdk/blob/v0.50.1/UPGRADING.md) to account for its API breaking changes. + +### Authority + +An authority identifier (e.g. an address) needs to be passed in the `NewKeeper` functions of the following keepers: + +- You must pass the `authority` to the ica/host keeper (implemented in [#3520](https://github.com/cosmos/ibc-go/pull/3520)). See [diff](https://github.com/cosmos/ibc-go/pull/3520/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08a): + +```diff +// app.go + +// ICA Host keeper +app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, keys[icahosttypes.StoreKey], app.GetSubspace(icahosttypes.SubModuleName), + app.IBCFeeKeeper, // use ics29 fee as ics4Wrapper in middleware stack + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.AccountKeeper, scopedICAHostKeeper, app.MsgServiceRouter(), ++ authtypes.NewModuleAddress(govtypes.ModuleName).String(), +) +``` + +- You must pass the `authority` to the ica/controller keeper (implemented in [#3590](https://github.com/cosmos/ibc-go/pull/3590)). See [diff](https://github.com/cosmos/ibc-go/pull/3590/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08a): + +```diff +// app.go + +// ICA Controller keeper +app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + appCodec, keys[icacontrollertypes.StoreKey], app.GetSubspace(icacontrollertypes.SubModuleName), + app.IBCFeeKeeper, // use ics29 fee as ics4Wrapper in middleware stack + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + scopedICAControllerKeeper, app.MsgServiceRouter(), ++ authtypes.NewModuleAddress(govtypes.ModuleName).String(), +) +``` + +- You must pass the `authority` to the ibctransfer keeper (implemented in [#3553](https://github.com/cosmos/ibc-go/pull/3553)). See [diff](https://github.com/cosmos/ibc-go/pull/3553/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08a): + +```diff +// app.go + +// Create Transfer Keeper and pass IBCFeeKeeper as expected Channel and PortKeeper +// since fee middleware will wrap the IBCKeeper for underlying application. +app.TransferKeeper = ibctransferkeeper.NewKeeper( + appCodec, keys[ibctransfertypes.StoreKey], app.GetSubspace(ibctransfertypes.ModuleName), + app.IBCFeeKeeper, // ISC4 Wrapper: fee IBC middleware + app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, + app.AccountKeeper, app.BankKeeper, scopedTransferKeeper, ++ authtypes.NewModuleAddress(govtypes.ModuleName).String(), +) +``` + +- You should pass the `authority` to the IBC keeper (implemented in [#3640](https://github.com/cosmos/ibc-go/pull/3640) and [#3650](https://github.com/cosmos/ibc-go/pull/3650)). See [diff](https://github.com/cosmos/ibc-go/pull/3640/files#diff-d18972debee5e64f16e40807b2ae112ddbe609504a93ea5e1c80a5d489c3a08a): + +```diff +// app.go + +// IBC Keepers +app.IBCKeeper = ibckeeper.NewKeeper( + appCodec, + keys[ibcexported.StoreKey], + app.GetSubspace(ibcexported.ModuleName), + app.StakingKeeper, + app.UpgradeKeeper, + scopedIBCKeeper, ++ authtypes.NewModuleAddress(govtypes.ModuleName).String(), +) +``` + +The authority determines the transaction signer allowed to execute certain messages (e.g. `MsgUpdateParams`). + +### Testing package + +- The function `SetupWithGenesisAccounts` has been removed. +- The function [`RelayPacketWithResults`](https://github.com/cosmos/ibc-go/blob/v8.0.0/testing/path.go#L66) has been added. This function returns the result of the packet receive transaction, the acknowledgement written on the receiving chain, an error if a relay step fails or the packet commitment does not exist on either chain. + +### Params migration + +Params are now self managed in the following submodules: + +- ica/controller [#3590](https://github.com/cosmos/ibc-go/pull/3590) +- ica/host [#3520](https://github.com/cosmos/ibc-go/pull/3520) +- ibc/connection [#3650](https://github.com/cosmos/ibc-go/pull/3650) +- ibc/client [#3640](https://github.com/cosmos/ibc-go/pull/3640) +- ibc/transfer [#3553](https://github.com/cosmos/ibc-go/pull/3553) + +Each module has a corresponding `MsgUpdateParams` message with a `Params` which can be specified in full to update the modules' `Params`. + +Legacy params subspaces must still be initialised in app.go in order to successfully migrate from `x/params`` to the new self-contained approach. See reference [this](https://github.com/cosmos/ibc-go/blob/v8.0.0/testing/simapp/app.go#L1007-L1012) for reference. + +For new chains which do not rely on migration of parameters from `x/params`, an expected interface has been added for each module. This allows chain developers to provide `nil` as the `legacySubspace` argument to `NewKeeper` functions. + +### Governance V1 migration + +Proposals have been migrated to [gov v1 messages](https://docs.cosmos.network/v0.50/modules/gov#messages) (see [#4620](https://github.com/cosmos/ibc-go/pull/4620)). The proposal `ClientUpdateProposal` has been deprecated and [`MsgRecoverClient`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/client/v1/tx.proto#L121-L134) should be used instead. Likewise, the proposal `UpgradeProposal` has been deprecated and [`MsgIBCSoftwareUpgrade`](https://github.com/cosmos/ibc-go/blob/v8.0.0/proto/ibc/core/client/v1/tx.proto#L139-L154) should be used instead. Both proposals will be removed in the next major release. + +`MsgRecoverClient` and `MsgIBCSoftwareUpgrade` will only be allowed to be executed if the signer is the authority designated at the time of instantiating the IBC keeper. So please make sure that the correct authority is provided to the IBC keeper. + +Remove the `UpgradeProposalHandler` and `UpdateClientProposalHandler` from the `BasicModuleManager`: + +```diff +app.BasicModuleManager = module.NewBasicManagerFromManager( + app.ModuleManager, + map[string]module.AppModuleBasic{ + genutiltypes.ModuleName: genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator), + govtypes.ModuleName: gov.NewAppModuleBasic( + []govclient.ProposalHandler{ + paramsclient.ProposalHandler, +- ibcclientclient.UpdateClientProposalHandler, +- ibcclientclient.UpgradeProposalHandler, + }, + ), +}) +``` + +Support for in-flight legacy recover client proposals (i.e. `ClientUpdateProposal`) will be made for v8, but chains should use `MsgRecoverClient` only afterwards to avoid in-flight client recovery failing when upgrading to v9. See [this issue](https://github.com/cosmos/ibc-go/issues/4721) for more information. + +Please note that ibc-go offers facilities to test an ibc-go upgrade: + +- All e2e tests of the repository can be [run with custom Docker chain images](https://github.com/cosmos/ibc-go/blob/c5bac5e03a0eae449b9efe0d312258115c1a1e85/e2e/README.md#running-tests-with-custom-images). +- An [importable workflow](https://github.com/cosmos/ibc-go/blob/c5bac5e03a0eae449b9efe0d312258115c1a1e85/e2e/README.md#importable-workflow) that can be used from any other repository to test chain upgrades. + +### Transfer migration + +An [automatic migration handler](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/transfer/module.go#L136) is configured in the transfer module to set the [denomination metadata](https://github.com/cosmos/cosmos-sdk/blob/v0.50.1/proto/cosmos/bank/v1beta1/bank.proto#L96-L125) for the IBC denominations of all vouchers minted by the transfer module. + +## IBC Apps + +### ICS20 - Transfer + +- The function `IsBound` has been renamed to [`hasCapability`](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/transfer/keeper/keeper.go#L98) and made unexported. + +### ICS27 - Interchain Accounts + +- Functions [`SerializeCosmosTx`](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/27-interchain-accounts/types/codec.go#L32) and [`DeserializeCosmosTx`](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/27-interchain-accounts/types/codec.go#L76) now accept an extra parameter `encoding` of type `string` that specifies the format in which the transaction messages are marshaled. Both [protobuf and proto3 JSON formats](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/27-interchain-accounts/types/metadata.go#L14-L17) are supported. +- The function `IsBound` of controller submodule has been renamed to [`hasCapability`](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/27-interchain-accounts/controller/keeper/keeper.go#L111) and made unexported. +- The function `IsBound` of host submodule has been renamed to [`hasCapability`](https://github.com/cosmos/ibc-go/blob/v8.0.0/modules/apps/27-interchain-accounts/host/keeper/keeper.go#L94) and made unexported. + +## Relayers + +- Getter functions in `MsgChannelOpenInitResponse`, `MsgChannelOpenTryResponse`, `MsgTransferResponse`, `MsgRegisterInterchainAccountResponse` and `MsgSendTxResponse` have been removed. The fields can be accessed directly. +- `channeltypes.EventTypeTimeoutPacketOnClose` (where `channeltypes` is an import alias for `"github.com/cosmos/ibc-go/v8/modules/core/04-channel"`) has been removed, since core IBC does not emit any event with this key. +- Attribute with key `counterparty_connection_id` has been removed from event with key `connectiontypes.EventTypeConnectionOpenInit` (where `connectiontypes` is an import alias for `"github.com/cosmos/ibc-go/v8/modules/core/03-connection/types"`) and attribute with key `counterparty_channel_id` has been removed from event with key `channeltypes.EventTypeChannelOpenInit` (where `channeltypes` is an import alias for `"github.com/cosmos/ibc-go/v8/modules/core/04-channel"`) since both (counterparty connection ID and counterparty channel ID) are empty on `ConnectionOpenInit` and `ChannelOpenInit` respectively. +- As part of the migration to [governance V1 messages](#governance-v1-migration) the following changes in events have been made: + +```diff +// IBC client events vars +var ( + EventTypeCreateClient = "create_client" + EventTypeUpdateClient = "update_client" + EventTypeUpgradeClient = "upgrade_client" + EventTypeSubmitMisbehaviour = "client_misbehaviour" +- EventTypeUpdateClientProposal = "update_client_proposal" +- EventTypeUpgradeClientProposal = "upgrade_client_proposal" ++ EventTypeRecoverClient = "recover_client" ++ EventTypeScheduleIBCSoftwareUpgrade = "schedule_ibc_software_upgrade" + EventTypeUpgradeChain = "upgrade_chain" +) +``` + +## IBC Light Clients + +- Functions `Pretty` and `String` of type `MerklePath` have been [removed](https://github.com/cosmos/ibc-go/pull/4459/files#diff-dd94ec1dde9b047c0cdfba204e30dad74a81de202e3b09ac5b42f493153811af). diff --git a/docs/versioned_docs/version-v8.5.x/05-migrations/12-v8-to-v8_1.md b/docs/versioned_docs/version-v8.5.x/05-migrations/12-v8-to-v8_1.md new file mode 100644 index 0000000..0208599 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/05-migrations/12-v8-to-v8_1.md @@ -0,0 +1,42 @@ +--- +title: IBC-Go v8 to v8.1 +sidebar_label: IBC-Go v8 to v8.1 +sidebar_position: 12 +slug: /migrations/v8-to-v8_1 +--- + +# Migrating from v8 to v8.1 + +This guide provides instructions for migrating to version `v8.1.0` of ibc-go. + +There are four sections based on the four potential user groups of this document: + +- [Migrating from v8 to v8.1](#migrating-from-v8-to-v81) + - [Chains](#chains) + - [IBC apps](#ibc-apps) + - [Relayers](#relayers) + - [IBC light clients](#ibc-light-clients) + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. + +## Chains + +### `04-channel` params migration + +Self-managed [params](https://github.com/cosmos/ibc-go/blob/v8.1.0/proto/ibc/core/channel/v1/channel.proto#L183-L187) have been added for `04-channel` module. The params include the `upgrade_timeout` that is used in channel upgradability to specify the interval of time during which the counterparty chain must flush all in-flight packets on its end and move to `FLUSH_COMPLETE` state (see [Channel Upgrades](../01-ibc/06-channel-upgrades.md#) for more information). An [automatic migration handler](https://github.com/cosmos/ibc-go/blob/v8.1.0/modules/core/module.go#L162-L166) is configured in the `04-channel` module that sets the default params (with a default upgrade timeout of 10 minutes). The module has a corresponding [`MsgUpdateParams` message](https://github.com/cosmos/ibc-go/blob/v8.1.0/proto/ibc/core/channel/v1/tx.proto#L435-L447) with a `Params` field which can be specified in full to update the module's `Params`. + +### Fee migration + +In ibc-go v8.1.0 an improved, more efficient escrow calculation of fees for packet incentivisation has been introduced (see [this issue](https://github.com/cosmos/ibc-go/issues/5509) for more information). Before v8.1.0 the amount escrowed was `(ReckFee + AckFee + TimeoutFee)`; from ibc-go v8.1.0, the calculation is changed to `Max(RecvFee + AckFee, TimeoutFee)`. In order to guarantee that the correct amount of fees are refunded for packets that are in-flight during the upgrade to ibc-go v8.1.0, an [automatic migration handler](https://github.com/cosmos/ibc-go/blob/v8.1.0/modules/apps/29-fee/module.go#L113-L115) is configured in the `29-fee` module to refund the leftover fees (i.e `(ReckFee + AckFee + TimeoutFee) - Max(RecvFee + AckFee, TimeoutFee)`) that otherwise would not be refunded when the packet lifecycle completes and the new calculation is used. + +## IBC apps + +- No relevant changes were made in this release. + +## Relayers + +- No relevant changes were made in this release. + +## IBC light clients + +- No relevant changes were made in this release. diff --git a/docs/versioned_docs/version-v8.5.x/05-migrations/_category_.json b/docs/versioned_docs/version-v8.5.x/05-migrations/_category_.json new file mode 100644 index 0000000..354a84e --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/05-migrations/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Migrations", + "position": 5, + "link": null +} \ No newline at end of file diff --git a/docs/versioned_docs/version-v8.5.x/05-migrations/images/auth-module-decision-tree.png b/docs/versioned_docs/version-v8.5.x/05-migrations/images/auth-module-decision-tree.png new file mode 100644 index 0000000..1122ddb Binary files /dev/null and b/docs/versioned_docs/version-v8.5.x/05-migrations/images/auth-module-decision-tree.png differ diff --git a/docs/versioned_docs/version-v8.5.x/05-migrations/migration.template.md b/docs/versioned_docs/version-v8.5.x/05-migrations/migration.template.md new file mode 100644 index 0000000..182686e --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/05-migrations/migration.template.md @@ -0,0 +1,28 @@ +# Migrating from \ to \ + +This guide provides instructions for migrating to a new version of ibc-go. + +There are four sections based on the four potential user groups of this document: + +- [Chains](#chains) +- [IBC Apps](#ibc-apps) +- [Relayers](#relayers) +- [IBC Light Clients](#ibc-light-clients) + +**Note:** ibc-go supports golang semantic versioning and therefore all imports must be updated on major version releases. + +## Chains + +- No relevant changes were made in this release. + +## IBC Apps + +- No relevant changes were made in this release. + +## Relayers + +- No relevant changes were made in this release. + +## IBC Light Clients + +- No relevant changes were made in this release. diff --git a/docs/versioned_docs/version-v8.5.x/images/ibcoverview-dark.svg b/docs/versioned_docs/version-v8.5.x/images/ibcoverview-dark.svg new file mode 100644 index 0000000..d8c12ed --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/images/ibcoverview-dark.svg @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/versioned_docs/version-v8.5.x/images/ibcoverview-light.svg b/docs/versioned_docs/version-v8.5.x/images/ibcoverview-light.svg new file mode 100644 index 0000000..12e38b8 --- /dev/null +++ b/docs/versioned_docs/version-v8.5.x/images/ibcoverview-light.svg @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/versioned_sidebars/version-v10.1.x-sidebars.json b/docs/versioned_sidebars/version-v10.1.x-sidebars.json new file mode 100644 index 0000000..9146012 --- /dev/null +++ b/docs/versioned_sidebars/version-v10.1.x-sidebars.json @@ -0,0 +1,40 @@ +{ + "defaultSidebar": [ + { + "type": "autogenerated", + "dirName": "." + }, + { + "type": "category", + "label": "Resources", + "collapsed": false, + "items": [ + { + "type": "link", + "label": "IBC Specification", + "href": "https://github.com/cosmos/ibc" + }, + { + "type": "link", + "label": "Protobuf Documentation", + "href": "https://buf.build/cosmos/ibc/docs/main" + }, + { + "type": "link", + "label": "Developer Portal", + "href": "https://tutorials.cosmos.network" + }, + { + "type": "link", + "label": "Awesome Cosmos", + "href": "https://github.com/cosmos/awesome-cosmos" + }, + { + "type": "link", + "label": "Roadmap", + "href": "https://github.com/orgs/cosmos/projects/38/views/14" + } + ] + } + ] +} diff --git a/docs/versioned_sidebars/version-v4.6.x-sidebars.json b/docs/versioned_sidebars/version-v4.6.x-sidebars.json new file mode 100644 index 0000000..5d2708b --- /dev/null +++ b/docs/versioned_sidebars/version-v4.6.x-sidebars.json @@ -0,0 +1,35 @@ +{ + "defaultSidebar": [ + { + "type": "autogenerated", + "dirName": "." + }, + { + "type": "category", + "label": "Resources", + "collapsed": false, + "items": [ + { + "type": "link", + "label": "IBC Specification", + "href": "https://github.com/cosmos/ibc" + }, + { + "type": "link", + "label": "Protobuf Documentation", + "href": "https://buf.build/cosmos/ibc/docs/main" + }, + { + "type": "link", + "label": "Developer Portal", + "href": "https://tutorials.cosmos.network" + }, + { + "type": "link", + "label": "Awesome Cosmos", + "href": "https://github.com/cosmos/awesome-cosmos" + } + ] + } + ] +} diff --git a/docs/versioned_sidebars/version-v5.4.x-sidebars.json b/docs/versioned_sidebars/version-v5.4.x-sidebars.json new file mode 100644 index 0000000..5d2708b --- /dev/null +++ b/docs/versioned_sidebars/version-v5.4.x-sidebars.json @@ -0,0 +1,35 @@ +{ + "defaultSidebar": [ + { + "type": "autogenerated", + "dirName": "." + }, + { + "type": "category", + "label": "Resources", + "collapsed": false, + "items": [ + { + "type": "link", + "label": "IBC Specification", + "href": "https://github.com/cosmos/ibc" + }, + { + "type": "link", + "label": "Protobuf Documentation", + "href": "https://buf.build/cosmos/ibc/docs/main" + }, + { + "type": "link", + "label": "Developer Portal", + "href": "https://tutorials.cosmos.network" + }, + { + "type": "link", + "label": "Awesome Cosmos", + "href": "https://github.com/cosmos/awesome-cosmos" + } + ] + } + ] +} diff --git a/docs/versioned_sidebars/version-v6.3.x-sidebars.json b/docs/versioned_sidebars/version-v6.3.x-sidebars.json new file mode 100644 index 0000000..5d2708b --- /dev/null +++ b/docs/versioned_sidebars/version-v6.3.x-sidebars.json @@ -0,0 +1,35 @@ +{ + "defaultSidebar": [ + { + "type": "autogenerated", + "dirName": "." + }, + { + "type": "category", + "label": "Resources", + "collapsed": false, + "items": [ + { + "type": "link", + "label": "IBC Specification", + "href": "https://github.com/cosmos/ibc" + }, + { + "type": "link", + "label": "Protobuf Documentation", + "href": "https://buf.build/cosmos/ibc/docs/main" + }, + { + "type": "link", + "label": "Developer Portal", + "href": "https://tutorials.cosmos.network" + }, + { + "type": "link", + "label": "Awesome Cosmos", + "href": "https://github.com/cosmos/awesome-cosmos" + } + ] + } + ] +} diff --git a/docs/versioned_sidebars/version-v7.8.x-sidebars.json b/docs/versioned_sidebars/version-v7.8.x-sidebars.json new file mode 100644 index 0000000..5d2708b --- /dev/null +++ b/docs/versioned_sidebars/version-v7.8.x-sidebars.json @@ -0,0 +1,35 @@ +{ + "defaultSidebar": [ + { + "type": "autogenerated", + "dirName": "." + }, + { + "type": "category", + "label": "Resources", + "collapsed": false, + "items": [ + { + "type": "link", + "label": "IBC Specification", + "href": "https://github.com/cosmos/ibc" + }, + { + "type": "link", + "label": "Protobuf Documentation", + "href": "https://buf.build/cosmos/ibc/docs/main" + }, + { + "type": "link", + "label": "Developer Portal", + "href": "https://tutorials.cosmos.network" + }, + { + "type": "link", + "label": "Awesome Cosmos", + "href": "https://github.com/cosmos/awesome-cosmos" + } + ] + } + ] +} diff --git a/docs/versioned_sidebars/version-v8.5.x-sidebars.json b/docs/versioned_sidebars/version-v8.5.x-sidebars.json new file mode 100644 index 0000000..4f91558 --- /dev/null +++ b/docs/versioned_sidebars/version-v8.5.x-sidebars.json @@ -0,0 +1,40 @@ +{ + "defaultSidebar": [ + { + "type": "autogenerated", + "dirName": "." + }, + { + "type": "category", + "label": "Resources", + "collapsed": false, + "items": [ + { + "type": "link", + "label": "IBC Specification", + "href": "https://github.com/cosmos/ibc" + }, + { + "type": "link", + "label": "Protobuf Documentation", + "href": "https://buf.build/cosmos/ibc/docs/main" + }, + { + "type": "link", + "label": "Developer Portal", + "href": "https://tutorials.cosmos.network" + }, + { + "type": "link", + "label": "Awesome Cosmos", + "href": "https://github.com/cosmos/awesome-cosmos" + }, + { + "type": "link", + "label": "ibc-rs", + "href": "https://github.com/cosmos/ibc-rs" + } + ] + } + ] +} diff --git a/docs/versions.json b/docs/versions.json new file mode 100644 index 0000000..ab330d8 --- /dev/null +++ b/docs/versions.json @@ -0,0 +1,8 @@ +[ + "v10.1.x", + "v8.5.x", + "v7.8.x", + "v6.3.x", + "v5.4.x", + "v4.6.x" +] diff --git a/e2e/Makefile b/e2e/Makefile new file mode 100644 index 0000000..08659ea --- /dev/null +++ b/e2e/Makefile @@ -0,0 +1,19 @@ +DOCKER := $(shell which docker) +TEST_CONTAINERS=$(shell docker ps --filter "label=ibc-test" -a -q) + +cleanup-ibc-test-containers: + for id in $(TEST_CONTAINERS) ; do \ + $(DOCKER) stop $$id ; \ + $(DOCKER) rm $$id ; \ + done + +init: + ./scripts/init.sh + +e2e-test: init cleanup-ibc-test-containers + ./scripts/run-e2e.sh $(test) $(entrypoint) + +e2e-suite: init cleanup-ibc-test-containers + RUN_SUITE="true" ./scripts/run-e2e.sh "" $(entrypoint) + +.PHONY: cleanup-ibc-test-containers e2e-test init diff --git a/e2e/README.md b/e2e/README.md new file mode 100644 index 0000000..862271e --- /dev/null +++ b/e2e/README.md @@ -0,0 +1,242 @@ +# Table of Contents + +1. [Running Tests](#running-tests) +2. [Adding a new test](#adding-a-new-test) +3. [Test design](#test-design) + - a. [interchaintest](#interchaintest) + - b. [CI configuration](#ci-configuration) +4. [Building images](#building-and-pushing-images) +5. [Compatibility Tests](#compatibility-tests) + - a. [Running Compatibility Tests](#running-compatibility-tests) + - b. [How Compatibility Tests Work](#how-compatibility-tests-work) +6. [Troubleshooting](#troubleshooting) +7. [Importable Workflow](#importable-workflow) +8. [Future Improvements](#future-improvements) + +# Running tests + +Tests can be run using a Makefile target under the e2e directory. `e2e/Makefile` + +The tests can be configured using a configuration file or environment variables. + +See the [minimal example](./sample.config.yaml) or [extended example](./sample.config.extended.yaml) to get started. The default location the tests look is `~/.ibc-go-e2e-config.yaml` +But this can be specified directly using the `E2E_CONFIG_PATH` environment variable. + +It is possible to maintain multiple configuration files for tests. This can be useful when wanting to run the tests +using different images, relayers etc. + +By creating an `./e2e/dev-configs` directory, and placing any number of configurations there. You will be prompted to choose +which configuration to use when running tests. + +> Note: this requires fzf to be installed to support the interactive selection of configuration files. + +There are several environment variables that alter the behaviour of the make target which will override any +options specified in your config file. These are primarily used for CI and are not required for local development. + +See the extended sample config to understand all the available fields and their purposes. + +> Note: when running tests locally, **no images are pushed** to the `ghcr.io/cosmos/ibc-go-simd` registry. +> The images which are used only exist locally only. + +These environment variables allow us to run tests with arbitrary versions (from branches or releases) of simd and the go / hermes relayer. + +Every time changes are pushed to a branch or to `main`, a new `simd` image is built and +pushed [here](https://github.com/orgs/cosmos/packages?repo_name=ibc-go). + +On PRs, E2E tests will only run once the PR is marked as ready for review. This is to prevent unnecessary test runs on PRs that are still in progress. + +> If you need the E2E tests to run, you can either run them locally, or you can mark the PR as R4R and then convert it back to a draft PR. + +## Adding a new test + +All tests should go under the [e2e](https://github.com/cosmos/ibc-go/tree/main/e2e) directory. When adding a new test, either add a new test function +to an existing test suite ***in the same file***, or create a new test suite in a new file and add test functions there. +New test files should follow the convention of `module_name_test.go`. + +After creating a new test file, be sure to add a build constraint that ensures this file will **not** be included in the package to be built when +running tests locally via `make test`. For an example of this, see any of the existing test files. + +New test suites should be composed of `testsuite.E2ETestSuite`. This type has lots of useful helper functionality that will +be quite common in most tests. + +Override the default `SetupSuite` function with the number of chains required for the suite. Example: + +```go +// SetupSuite sets up chains for the current test suite +func (s *ConnectionTestSuite) SetupSuite() { + s.SetupChains(context.TODO(), 2, nil) // This suite requires at most two chains. +} +``` + +> Note: see [here](#how-tests-are-run) for details about these requirements. + +### Example of running a single test + +> NOTE: environment variables can be set to override one or more config file variables, but the config file can still +> be used to set defaults. + +```sh + +make e2e-test entrypoint=TestInterchainAccountsTestSuite test=TestMsgSubmitTx_SuccessfulTransfer +``` + +If `jq` is installed, you only need to specify the `test`. + +If `fzf` is also installed, you only need to run `make e2e-test` and you will be prompted with interactive test +selection. + +```sh +make e2e-test test=TestMsgSubmitTx_SuccessfulTransfer +``` + +> Note: sometimes it can be useful to make changes to [interchaintest](https://github.com/cosmos/interchaintest) +> when running tests locally. In order to do this, add the following line to +> e2e/go.mod + +`replace github.com/cosmos/interchaintest/v10 => ../../interchaintest` + +Or point it to any local checkout you have. + +### Example of running a full testsuite + +> NOTE: not all tests may support full parallel runs due to possible chain wide modifications such as params / gov +> proposals / chain restarts. See [When to Use t.Parallel()](#when-to-use-tparallel) for more information. + +```sh +make e2e-suite entrypoint=TestTransferTestSuite +``` + +Similar to running a single test, if `jq` and `fzf` are installed you can run `make e2e-suite` and be prompted +to interactively select a test suite to run. + +### Running tests outside the context of the Makefile + +In order to run tests outside the context of the Makefile (e.g. from an IDE) + +The default location for a config file will be `~/.ibc-go-e2e-config.yaml` but this can be overridden by setting the +`E2E_CONFIG_PATH` environment variable. + +This should be set to the path of a valid config file you want to use, setting this env will depend on the IDE being used. + +## Test design + +### interchaintest + +These E2E tests use the [interchaintest framework](https://github.com/cosmos/interchaintest). This framework creates chains and relayers in containers and allows for arbitrary commands to be executed in the chain containers, +as well as allowing us to broadcast arbitrary messages which are signed on behalf of a user created in the test. + +### Test Suites + +In order for tests to be run in parallel, we create the chains in `SetupSuite`, and each test is in charge of +creating clients/connections/channels for itself. + +This is explicitly not being done in `SetupTest` to enable maximum control and flexibility over the channel creation +params. e.g. some tests may not want a channel created initially, and may want more flexibility over the channel creation itself. + +### When to use t.Parallel() + +tests should **not** be run in parallel when: + +- the test is modifying chain wide state such as modifying params via a gov proposal. +- the test needs to perform a chain restart. +- the test must make assertions which may not be deterministic due to other tests. (e.g. the TotalEscrowForDenom may be modified between tests) + +### CI Configuration + +There are two main github actions for standard e2e tests. + +[e2e.yaml](https://github.com/cosmos/ibc-go/blob/main/.github/workflows/e2e.yaml) which runs when collaborators create branches. + +In `e2e.yaml`, the `simd` image is built and pushed to [a registry](https://github.com/orgs/cosmos/packages?repo_name=ibc-go) and every test +that is run uses the image that was built. + +In `e2e-fork.yaml`, images are not pushed to this registry, but instead remain local to the host runner. + +## How Tests Are Run + +The tests use the `matrix` feature of Github Actions. The matrix is +dynamically generated using [this tool](https://github.com/cosmos/ibc-go/blob/main/cmd/build_test_matrix/main.go). + +> Note: there is currently a limitation that all tests belonging to a test suite must be in the same file. +> In order to support test functions spread in different files, we would either need to manually maintain a matrix +> or update the script to account for this. The script assumes there is a single test suite per test file to avoid an +> overly complex generation process. + +Which looks under the `e2e` directory, and creates a task for each test suite function. + +This tool can be run locally to see which tests will be run in CI. + +```sh +go run cmd/build_test_matrix/main.go | jq +``` + +This string is used to generate a test matrix in the Github Action that runs the E2E tests. + +All tests will be run on different hosts when running `make e2e-test` but `make e2e-suite` will run multiple tests +in parallel on a shared host. + +In a CI environment variables are passed to the test runner to configure test parameters, while locally using +environment variables is supported, but it is often more convenient to use configuration files. + +## Building and pushing images + +If we ever need to manually build and push an image, we can do so with the [Build Simd Image](../.github/workflows/build-simd-image-from-tag.yml) GitHub workflow. + +This can be triggered manually from the UI by navigating to + +`Actions` -> `Build Simd Image` -> `Run Workflow` + +And providing the git tag. + +> There are similar workflows for other simapps in the repo. + +## Compatibility Tests + +### Running Compatibility Tests + +To trigger the compatibility tests for a release branch, you can trigger these manually from the Github UI. + +This will build an image from the tip of the release branch and run all tests specified in the corresponding +E2E test annotations. + +Navigate to `Actions` -> `Compatibility E2E` -> `Run Workflow` -> `release/v8.0.x` + +> Note: this will spawn a large number of runners, and should only be used when there is a release candidate and +> and so should not be run regularly. We can rely on the regular E2Es on PRs for the most part. + +### How Compatibility Tests Work + +The compatibility tests are executed in [this workflow](../.github/workflows/e2e-compatibility.yaml). This workflow +will build an image for a specified release candidate based on the release branch as an input. And run the corresponding +jobs which are maintained under the `.github/compatibility-test-matrices` directory. + +> At the moment these are manually maintained, but in the future we may be able to generate these matrices dynamically. See the [future improvements](#future-improvements) section for more details. + +See [this example](https://github.com/cosmos/ibc-go/actions/runs/11645461969) to what the output of a compatibility test run looks like. + +## Troubleshooting + +- On Mac, after running a lot of tests, it can happen that containers start failing. To fix this, you can try clearing existing containers and restarting the docker daemon. + + This generally manifests itself as relayer or simd containers timing out during setup stages of the test. This doesn't happen in CI. + + ```bash + # delete all images + docker system prune -af + ``` + + This issue doesn't seem to occur on other operating systems. + +### Accessing Logs + +- When a test fails in GitHub. The logs of the test will be uploaded (viewable in the summary page of the workflow). Note: There + may be some discrepancy in the logs collected and the output of interchaintest. The containers may run for a some + time after the logs are collected, resulting in the displayed logs to differ slightly. + +### Prerequisites + +- In order to run this workflow, a docker container is required with tags for the versions you want to test. + +- If you are running an upgrade, Have an upgrade handler in the chain binary which is being upgraded to. + +> It's worth noting that all github repositories come with a built-in docker registry that makes it convenient to build and push images to. diff --git a/e2e/ci-e2e-config.yaml b/e2e/ci-e2e-config.yaml new file mode 100644 index 0000000..c4b865e --- /dev/null +++ b/e2e/ci-e2e-config.yaml @@ -0,0 +1,81 @@ +# This file contains configuration for running e2e tests. +# Many of these fields can be overridden with environment variables. +# All fields that support this have the corresponding environment variable name in a comment beside the field. + +# | Environment Variable | Description | Default Value | +# |----------------------|-------------------------------------------|-------------------------------| +# | CHAIN_IMAGE | The image that will be used for the chain | ghcr.io/cosmos/ibc-go-simd | +# | CHAIN_A_TAG | The tag used for chain A | N/A (must be set) | +# | CHAIN_B_TAG | The tag used for chain B | N/A (must be set) | +# | CHAIN_C_TAG | The tag used for chain C | N/A (optional; fallback to A) | +# | CHAIN_D_TAG | The tag used for chain D | N/A (optional; fallback to A) | +# | CHAIN_BINARY | The binary used in the container | simd | +# | RELAYER_TAG | The tag used for the relayer | 1.13.1 | +# | RELAYER_ID | The type of relayer to use (rly/hermes) | hermes | + +# see sample.config.yaml for a bare minimum configuration example. +# set env E2E_CONFIG_PATH to point to this file to use it. +--- +chains: + # the entry at index 0 corresponds to CHAIN_A + - chainId: chainA-1 + numValidators: 4 + numFullNodes: 1 + image: ghcr.io/cosmos/ibc-go-simd # override with CHAIN_IMAGE + binary: simd # override with CHAIN_BINARY + + # the entry at index 1 corresponds to CHAIN_B + - chainId: chainB-1 + numValidators: 4 + numFullNodes: 1 + image: ghcr.io/cosmos/ibc-go-simd # override with CHAIN_IMAGE + binary: simd # override with CHAIN_BINARY + + # the entry at index 2 corresponds to CHAIN_C + - chainId: chainC-1 + numValidators: 4 + numFullNodes: 1 + image: ghcr.io/cosmos/ibc-go-simd # override with CHAIN_IMAGE + binary: simd # override with CHAIN_BINARY + + # the entry at index 3 corresponds to CHAIN_D + - chainId: chainD-1 + numValidators: 4 + numFullNodes: 1 + image: ghcr.io/cosmos/ibc-go-simd # override with CHAIN_IMAGE + binary: simd # override with CHAIN_BINARY + +# activeRelayer must match the id of a relayer specified in the relayers list below. +activeRelayer: hermes # override with RELAYER_ID + +relayers: + - id: hermes + image: ghcr.io/informalsystems/hermes + tag: "1.13.1" + - id: rly + image: ghcr.io/cosmos/relayer + tag: "latest" + +cometbft: + logLevel: info + +debug: + # setting this value to true will force log collection even if the test passes. + dumpLogs: false + # settings this value to true will keep the containers running after the test finishes. + keepContainers: true + +upgradePlanName: "" +upgrades: + - planName: "v7" + tag: "v7.0.0" + - planName: "v7.1" + tag: "v7.1.0" + - planName: "v8" + tag: "v8.0.0" + - planName: "v8.1" + tag: "v8.1.0" + - planName: "v10" + tag: "branch-release-v10.4.x" + - planName: "ibcwasm-v8" + tag: "v8.0.0-e2e-upgrade" diff --git a/e2e/dockerutil/dockerutil.go b/e2e/dockerutil/dockerutil.go new file mode 100644 index 0000000..833fb6d --- /dev/null +++ b/e2e/dockerutil/dockerutil.go @@ -0,0 +1,71 @@ +package dockerutil + +import ( + "archive/tar" + "context" + "fmt" + "io" + "path" + + dockertypes "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" + mobycli "github.com/moby/moby/client" +) + +const testLabel = "ibc-test" + +// GetTestContainers returns all docker containers that have been created by interchain test. +// note: the test suite name must be passed as the chains are created in SetupSuite which will label +// them with the name of the test suite rather than the test. +func GetTestContainers(ctx context.Context, suiteName string, dc *mobycli.Client) ([]dockertypes.Container, error) { + testContainers, err := dc.ContainerList(ctx, container.ListOptions{ + All: true, + Filters: filters.NewArgs( + // see interchaintest Docker setup for how suiteName label is used to identify test containers. + // https://github.com/cosmos/interchaintest/blob/main/internal/dockerutil/setup.go + filters.Arg("label", testLabel+"="+suiteName), + ), + }) + if err != nil { + return nil, fmt.Errorf("failed listing containers: %s", err) + } + + return testContainers, nil +} + +// GetContainerLogs returns the logs of a container as a byte array. +func GetContainerLogs(ctx context.Context, dc *mobycli.Client, containerName string) ([]byte, error) { + readCloser, err := dc.ContainerLogs(ctx, containerName, container.LogsOptions{ + ShowStdout: true, + ShowStderr: true, + }) + if err != nil { + return nil, fmt.Errorf("failed reading logs in test cleanup: %s", err) + } + return io.ReadAll(readCloser) +} + +// GetFileContentsFromContainer reads the contents of a specific file from a container. +func GetFileContentsFromContainer(ctx context.Context, dc *mobycli.Client, containerID, absolutePath string) ([]byte, error) { + readCloser, _, err := dc.CopyFromContainer(ctx, containerID, absolutePath) + if err != nil { + return nil, fmt.Errorf("copying from container: %w", err) + } + + defer readCloser.Close() + + fileName := path.Base(absolutePath) + tr := tar.NewReader(readCloser) + + hdr, err := tr.Next() + if err != nil { + return nil, err + } + + if hdr.Name != fileName { + return nil, fmt.Errorf("expected to find %s but found %s", fileName, hdr.Name) + } + + return io.ReadAll(tr) +} diff --git a/e2e/go.mod b/e2e/go.mod new file mode 100644 index 0000000..c4e845e --- /dev/null +++ b/e2e/go.mod @@ -0,0 +1,282 @@ +module github.com/cosmos/ibc-go/e2e + +go 1.23.8 + +replace ( + github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10 => ../modules/light-clients/08-wasm + // uncomment to use the local version of ibc-go, you will need to run `go mod tidy` in e2e directory. + github.com/cosmos/ibc-go/v10 => ../ + + github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 + github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 +) + +require ( + cosmossdk.io/api v0.9.2 + cosmossdk.io/errors v1.0.2 + cosmossdk.io/math v1.5.3 + cosmossdk.io/x/upgrade v0.2.0 + github.com/cometbft/cometbft v0.38.17 + github.com/cosmos/cosmos-sdk v0.53.4 + github.com/cosmos/gogoproto v1.7.0 + github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10 v10.2.0 + github.com/cosmos/ibc-go/v10 v10.3.0 + github.com/cosmos/interchaintest/v10 v10.0.0 + github.com/docker/docker v27.5.1+incompatible + github.com/moby/moby v27.5.1+incompatible + github.com/pelletier/go-toml v1.9.5 + github.com/stretchr/testify v1.11.1 + go.uber.org/zap v1.27.0 + golang.org/x/mod v0.25.0 + google.golang.org/grpc v1.75.0 + google.golang.org/protobuf v1.36.8 + gopkg.in/yaml.v2 v2.4.0 +) + +require ( + cel.dev/expr v0.24.0 // indirect + cloud.google.com/go v0.116.0 // indirect + cloud.google.com/go/auth v0.14.1 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect + cloud.google.com/go/compute/metadata v0.7.0 // indirect + cloud.google.com/go/iam v1.2.2 // indirect + cloud.google.com/go/monitoring v1.21.2 // indirect + cloud.google.com/go/storage v1.49.0 // indirect + cosmossdk.io/collections v1.2.1 // indirect + cosmossdk.io/core v0.11.3 // indirect + cosmossdk.io/depinject v1.2.1 // indirect + cosmossdk.io/log v1.6.1 // indirect + cosmossdk.io/schema v1.1.0 // indirect + cosmossdk.io/store v1.1.2 // indirect + cosmossdk.io/x/evidence v0.2.0 // indirect + cosmossdk.io/x/feegrant v0.2.0 // indirect + cosmossdk.io/x/tx v0.14.0 // indirect + filippo.io/edwards25519 v1.1.0 // indirect + github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect + github.com/99designs/keyring v1.2.2 // indirect + github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect + github.com/CosmWasm/wasmvm/v2 v2.2.4 // indirect + github.com/DataDog/datadog-go v4.8.3+incompatible // indirect + github.com/DataDog/zstd v1.5.7 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/StackExchange/wmi v1.2.1 // indirect + github.com/avast/retry-go/v4 v4.5.1 // indirect + github.com/aws/aws-sdk-go v1.49.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect + github.com/bgentry/speakeasy v0.2.0 // indirect + github.com/bits-and-blooms/bitset v1.22.0 // indirect + github.com/btcsuite/btcd v0.22.1 // indirect + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/chzyer/readline v1.5.1 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect + github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect + github.com/cockroachdb/apd/v2 v2.0.2 // indirect + github.com/cockroachdb/errors v1.12.0 // indirect + github.com/cockroachdb/fifo v0.0.0-20240616162244-4768e80dfb9a // indirect + github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 // indirect + github.com/cockroachdb/pebble v1.1.5 // indirect + github.com/cockroachdb/redact v1.1.6 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect + github.com/cometbft/cometbft-db v0.14.1 // indirect + github.com/consensys/bavard v0.1.27 // indirect + github.com/consensys/gnark-crypto v0.16.0 // indirect + github.com/cosmos/btcutil v1.0.5 // indirect + github.com/cosmos/cosmos-db v1.1.3 // indirect + github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect + github.com/cosmos/go-bip39 v1.0.0 // indirect + github.com/cosmos/gogogateway v1.2.0 // indirect + github.com/cosmos/iavl v1.2.4 // indirect + github.com/cosmos/ics23/go v0.11.0 // indirect + github.com/cosmos/interchain-security/v7 v7.0.0-20250408210344-06e0dc6bf6d6 // indirect + github.com/cosmos/ledger-cosmos-go v0.14.0 // indirect + github.com/crate-crypto/go-eth-kzg v1.3.0 // indirect + github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect + github.com/danieljoos/wincred v1.2.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/deckarep/golang-set/v2 v2.6.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect + github.com/desertbit/timer v1.0.1 // indirect + github.com/dgraph-io/badger/v4 v4.2.0 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/dvsekhvalnov/jose2go v1.7.0 // indirect + github.com/emicklei/dot v1.6.2 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect + github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect + github.com/ethereum/c-kzg-4844/v2 v2.1.0 // indirect + github.com/ethereum/go-ethereum v1.15.11 // indirect + github.com/ethereum/go-verkle v0.2.2 // indirect + github.com/fatih/color v1.17.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/getsentry/sentry-go v0.32.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.1 // indirect + github.com/go-kit/kit v0.13.0 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect + github.com/gogo/googleapis v1.4.1 // indirect + github.com/gogo/protobuf v1.3.3 // indirect + github.com/golang/glog v1.2.5 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.5-0.20231225225746-43d5d4cd4e0e // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/flatbuffers v24.3.25+incompatible // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/orderedcode v0.0.1 // indirect + github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/gax-go/v2 v2.14.1 // indirect + github.com/gorilla/handlers v1.5.2 // indirect + github.com/gorilla/mux v1.8.1 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect + github.com/gtank/merlin v0.1.1 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-getter v1.7.8 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-metrics v0.5.4 // indirect + github.com/hashicorp/go-plugin v1.6.3 // indirect + github.com/hashicorp/go-safetemp v1.0.0 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/yamux v0.1.2 // indirect + github.com/hdevalence/ed25519consensus v0.2.0 // indirect + github.com/holiman/uint256 v1.3.2 // indirect + github.com/huandu/skiplist v1.2.1 // indirect + github.com/iancoleman/strcase v0.3.0 // indirect + github.com/icza/dyno v0.0.0-20230330125955-09f820a8d9c0 // indirect + github.com/improbable-eng/grpc-web v0.15.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/jmhodges/levigo v1.0.0 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/libp2p/go-buffer-pool v0.1.0 // indirect + github.com/linxGnu/grocksdb v1.9.2 // indirect + github.com/manifoldco/promptui v0.9.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b // indirect + github.com/minio/highwayhash v1.0.3 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/mtibben/percent v0.2.1 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect + github.com/oklog/run v1.1.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0-rc5 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.63.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/rs/cors v1.11.1 // indirect + github.com/rs/zerolog v1.34.0 // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/sasha-s/go-deadlock v0.3.5 // indirect + github.com/shamaton/msgpack/v2 v2.2.0 // indirect + github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/cast v1.9.2 // indirect + github.com/spf13/cobra v1.10.1 // indirect + github.com/spf13/pflag v1.0.9 // indirect + github.com/spf13/viper v1.20.1 // indirect + github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/supranational/blst v0.3.14 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect + github.com/tendermint/go-amino v0.16.0 // indirect + github.com/tendermint/tendermint v0.38.0-dev // indirect + github.com/tidwall/btree v1.7.0 // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ulikunitz/xz v0.5.11 // indirect + github.com/zeebo/errs v1.4.0 // indirect + github.com/zondax/hid v0.9.2 // indirect + github.com/zondax/ledger-go v0.14.3 // indirect + go.etcd.io/bbolt v1.4.0-alpha.1 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect + go.uber.org/mock v0.5.2 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/arch v0.17.0 // indirect + golang.org/x/crypto v0.39.0 // indirect + golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.15.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.26.0 // indirect + golang.org/x/time v0.10.0 // indirect + golang.org/x/tools v0.33.0 // indirect + google.golang.org/api v0.222.0 // indirect + google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gotest.tools/v3 v3.5.2 // indirect + modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect + modernc.org/libc v1.55.3 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.8.0 // indirect + modernc.org/sqlite v1.31.1 // indirect + modernc.org/strutil v1.2.0 // indirect + modernc.org/token v1.1.0 // indirect + nhooyr.io/websocket v1.8.17 // indirect + pgregory.net/rapid v1.2.0 // indirect + rsc.io/tmplfunc v0.0.3 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect +) diff --git a/e2e/go.sum b/e2e/go.sum new file mode 100644 index 0000000..6d84b62 --- /dev/null +++ b/e2e/go.sum @@ -0,0 +1,2652 @@ +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= +cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= +cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= +cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= +cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= +cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= +cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= +cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= +cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= +cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= +cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= +cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= +cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= +cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= +cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= +cloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k= +cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= +cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= +cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= +cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= +cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE= +cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= +cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= +cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= +cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= +cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= +cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= +cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= +cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= +cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= +cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= +cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= +cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= +cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= +cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= +cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= +cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= +cloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A= +cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= +cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= +cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= +cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY= +cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= +cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= +cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= +cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= +cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= +cloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI= +cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ= +cloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI= +cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= +cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= +cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= +cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= +cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= +cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= +cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= +cloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg= +cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= +cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= +cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= +cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= +cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= +cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= +cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= +cloud.google.com/go/auth v0.14.1 h1:AwoJbzUdxA/whv1qj3TLKwh3XX5sikny2fc40wUl+h0= +cloud.google.com/go/auth v0.14.1/go.mod h1:4JHUxlGXisL0AW8kXPtUF6ztuOksyfUQNFjfsOCXkPM= +cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= +cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= +cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= +cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= +cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= +cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= +cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= +cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= +cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= +cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= +cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= +cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= +cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= +cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= +cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= +cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= +cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= +cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= +cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= +cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E= +cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= +cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q= +cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= +cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= +cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= +cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= +cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= +cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= +cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= +cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= +cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= +cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= +cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= +cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= +cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= +cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= +cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= +cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= +cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= +cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= +cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= +cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= +cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= +cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= +cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg= +cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= +cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= +cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= +cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= +cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= +cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= +cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= +cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= +cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= +cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= +cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= +cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= +cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= +cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= +cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= +cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= +cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= +cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= +cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= +cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= +cloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM= +cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= +cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= +cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= +cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= +cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= +cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= +cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= +cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= +cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= +cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= +cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= +cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= +cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= +cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= +cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= +cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= +cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= +cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= +cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= +cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= +cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= +cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= +cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= +cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= +cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= +cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= +cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= +cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= +cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= +cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= +cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= +cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= +cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= +cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= +cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= +cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= +cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= +cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= +cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= +cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= +cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= +cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= +cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= +cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= +cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= +cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= +cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= +cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= +cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= +cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= +cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= +cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= +cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= +cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM= +cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= +cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= +cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= +cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= +cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= +cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= +cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= +cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= +cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= +cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= +cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= +cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= +cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= +cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= +cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= +cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= +cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= +cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= +cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= +cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= +cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= +cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= +cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= +cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= +cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= +cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= +cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= +cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= +cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= +cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= +cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= +cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= +cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= +cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw= +cloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA= +cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= +cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= +cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= +cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= +cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= +cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= +cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= +cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= +cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= +cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= +cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= +cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= +cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= +cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= +cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= +cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= +cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= +cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= +cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= +cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= +cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= +cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= +cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= +cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= +cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= +cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= +cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA= +cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= +cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= +cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= +cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= +cloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo= +cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= +cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= +cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= +cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= +cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= +cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= +cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= +cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= +cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= +cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= +cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= +cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg= +cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= +cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= +cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= +cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= +cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= +cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= +cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= +cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= +cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= +cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= +cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= +cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= +cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= +cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk= +cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM= +cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= +cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= +cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= +cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc= +cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= +cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= +cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= +cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= +cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= +cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= +cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= +cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= +cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= +cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= +cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= +cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= +cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= +cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= +cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= +cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= +cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= +cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= +cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= +cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= +cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= +cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= +cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= +cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= +cloud.google.com/go/monitoring v1.21.2 h1:FChwVtClH19E7pJ+e0xUhJPGksctZNVOk2UhMmblmdU= +cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= +cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= +cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= +cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= +cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= +cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= +cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= +cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= +cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= +cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= +cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= +cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= +cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= +cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= +cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= +cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= +cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= +cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= +cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= +cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= +cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= +cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= +cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= +cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= +cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= +cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= +cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= +cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= +cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= +cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= +cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= +cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= +cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= +cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= +cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= +cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= +cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= +cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= +cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= +cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= +cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= +cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= +cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= +cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= +cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= +cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= +cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= +cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= +cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= +cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= +cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= +cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= +cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= +cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= +cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= +cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= +cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= +cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= +cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= +cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= +cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= +cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= +cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= +cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= +cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= +cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= +cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= +cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= +cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= +cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= +cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= +cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= +cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= +cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= +cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= +cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= +cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= +cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= +cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= +cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= +cloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo= +cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= +cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= +cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= +cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= +cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= +cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= +cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= +cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= +cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= +cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= +cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= +cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= +cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= +cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= +cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= +cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= +cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= +cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= +cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= +cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= +cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= +cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= +cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= +cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= +cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= +cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= +cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= +cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= +cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= +cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= +cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= +cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= +cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= +cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= +cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= +cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= +cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= +cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= +cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= +cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc= +cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= +cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= +cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= +cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= +cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= +cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= +cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= +cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= +cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= +cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= +cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= +cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= +cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= +cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec= +cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= +cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= +cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= +cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= +cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= +cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= +cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= +cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= +cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= +cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= +cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= +cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= +cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= +cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= +cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= +cloud.google.com/go/storage v1.49.0 h1:zenOPBOWHCnojRd9aJZAyQXBYqkJkdQS42dxL55CIMw= +cloud.google.com/go/storage v1.49.0/go.mod h1:k1eHhhpLvrPjVGfo0mOUPEJ4Y2+a/Hv5PiwehZI9qGU= +cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= +cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= +cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= +cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= +cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= +cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= +cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= +cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= +cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= +cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= +cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= +cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= +cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= +cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= +cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= +cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= +cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= +cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= +cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= +cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI= +cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io= +cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= +cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= +cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= +cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= +cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= +cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= +cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk= +cloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= +cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= +cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= +cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= +cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= +cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= +cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= +cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= +cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= +cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= +cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= +cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= +cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= +cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= +cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= +cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= +cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= +cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= +cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= +cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= +cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= +cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= +cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= +cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= +cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= +cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= +cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= +cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= +cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= +cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= +cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= +cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= +cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= +cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= +cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= +cosmossdk.io/api v0.9.2 h1:9i9ptOBdmoIEVEVWLtYYHjxZonlF/aOVODLFaxpmNtg= +cosmossdk.io/api v0.9.2/go.mod h1:CWt31nVohvoPMTlPv+mMNCtC0a7BqRdESjCsstHcTkU= +cosmossdk.io/client/v2 v2.0.0-beta.9 h1:xc06zg4G858/pK5plhf8RCfo+KR2mdDKJNrEkfrVAqc= +cosmossdk.io/client/v2 v2.0.0-beta.9/go.mod h1:pHf3CCHX5gmbL9rDCVbXhGI2+/DdAVTEZSLpdd5V9Zs= +cosmossdk.io/collections v1.2.1 h1:mAlNMs5vJwkda4TA+k5q/43p24RVAQ/qyDrjANu3BXE= +cosmossdk.io/collections v1.2.1/go.mod h1:PSsEJ/fqny0VPsHLFT6gXDj/2C1tBOTS9eByK0+PBFU= +cosmossdk.io/core v0.11.3 h1:mei+MVDJOwIjIniaKelE3jPDqShCc/F4LkNNHh+4yfo= +cosmossdk.io/core v0.11.3/go.mod h1:9rL4RE1uDt5AJ4Tg55sYyHWXA16VmpHgbe0PbJc6N2Y= +cosmossdk.io/depinject v1.2.1 h1:eD6FxkIjlVaNZT+dXTQuwQTKZrFZ4UrfCq1RKgzyhMw= +cosmossdk.io/depinject v1.2.1/go.mod h1:lqQEycz0H2JXqvOgVwTsjEdMI0plswI7p6KX+MVqFOM= +cosmossdk.io/errors v1.0.2 h1:wcYiJz08HThbWxd/L4jObeLaLySopyyuUFB5w4AGpCo= +cosmossdk.io/errors v1.0.2/go.mod h1:0rjgiHkftRYPj//3DrD6y8hcm40HcPv/dR4R/4efr0k= +cosmossdk.io/log v1.6.1 h1:YXNwAgbDwMEKwDlCdH8vPcoggma48MgZrTQXCfmMBeI= +cosmossdk.io/log v1.6.1/go.mod h1:gMwsWyyDBjpdG9u2avCFdysXqxq28WJapJvu+vF1y+E= +cosmossdk.io/math v1.5.3 h1:WH6tu6Z3AUCeHbeOSHg2mt9rnoiUWVWaQ2t6Gkll96U= +cosmossdk.io/math v1.5.3/go.mod h1:uqcZv7vexnhMFJF+6zh9EWdm/+Ylyln34IvPnBauPCQ= +cosmossdk.io/schema v1.1.0 h1:mmpuz3dzouCoyjjcMcA/xHBEmMChN+EHh8EHxHRHhzE= +cosmossdk.io/schema v1.1.0/go.mod h1:Gb7pqO+tpR+jLW5qDcNOSv0KtppYs7881kfzakguhhI= +cosmossdk.io/store v1.1.2 h1:3HOZG8+CuThREKv6cn3WSohAc6yccxO3hLzwK6rBC7o= +cosmossdk.io/store v1.1.2/go.mod h1:60rAGzTHevGm592kFhiUVkNC9w7gooSEn5iUBPzHQ6A= +cosmossdk.io/x/circuit v0.2.0 h1:RJPMBQWCQU77EcM9HDTBnqRhq21fcUxgWZl7BZylJZo= +cosmossdk.io/x/circuit v0.2.0/go.mod h1:CjiGXDeZs64nMv0fG+QmvGVTcn7n3Sv4cDszMRR2JqU= +cosmossdk.io/x/evidence v0.2.0 h1:o72zbmgCM7U0v7z7b0XnMB+NqX0tFamqb1HHkQbhrZ0= +cosmossdk.io/x/evidence v0.2.0/go.mod h1:zx/Xqy+hnGVzkqVuVuvmP9KsO6YCl4SfbAetYi+k+sE= +cosmossdk.io/x/feegrant v0.2.0 h1:oq3WVpoJdxko/XgWmpib63V1mYy9ZQN/1qxDajwGzJ8= +cosmossdk.io/x/feegrant v0.2.0/go.mod h1:9CutZbmhulk/Yo6tQSVD5LG8Lk40ZAQ1OX4d1CODWAE= +cosmossdk.io/x/tx v0.14.0 h1:hB3O25kIcyDW/7kMTLMaO8Ripj3yqs5imceVd6c/heA= +cosmossdk.io/x/tx v0.14.0/go.mod h1:Tn30rSRA1PRfdGB3Yz55W4Sn6EIutr9xtMKSHij+9PM= +cosmossdk.io/x/upgrade v0.2.0 h1:ZHy0xny3wBCSLomyhE06+UmQHWO8cYlVYjfFAJxjz5g= +cosmossdk.io/x/upgrade v0.2.0/go.mod h1:DXDtkvi//TrFyHWSOaeCZGBoiGAE6Rs8/0ABt2pcDD0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= +git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= +github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0= +github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/ChainSafe/go-schnorrkel v1.0.0 h1:3aDA67lAykLaG1y3AOjs88dMxC88PgUuHRrLeDnvGIM= +github.com/ChainSafe/go-schnorrkel v1.0.0/go.mod h1:dpzHYVxLZcp8pjlV+O+UR8K0Hp/z7vcchBSbMBEhCw4= +github.com/CosmWasm/wasmvm/v2 v2.2.4 h1:V3UwXJMA8TNOuQETppDQkaXAevF7gOWLYpKvrThPv7o= +github.com/CosmWasm/wasmvm/v2 v2.2.4/go.mod h1:Aj/rB2KMRM8nAdbWxkO23rnQYb5KsoPuH9ZizSi0sVg= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/datadog-go v4.8.3+incompatible h1:fNGaYSuObuQb5nzeTQqowRAd9bpDIRRV4/gUtIBjh8Q= +github.com/DataDog/datadog-go v4.8.3+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= +github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 h1:UQ0AhxogsIRZDkElkblfnwjc3IaltCm2HUMvezQaL7s= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1 h1:oTX4vsorBZo/Zdum6OKPA4o7544hm6smoRv1QjpTwGo= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1/go.mod h1:0wEl7vrAD8mehJyohS9HZy+WyEOaQO2mJx86Cvh93kM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 h1:8nn+rsCvTq9axyEh382S0PFLBeaFwNsT43IrPWzctRU= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE= +github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= +github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= +github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= +github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/adlio/schema v1.3.6 h1:k1/zc2jNfeiZBA5aFTRy37jlBIuCkXCm0XmvpzCKI9I= +github.com/adlio/schema v1.3.6/go.mod h1:qkxwLgPBd1FgLRHYVCmQT/rrBr3JH38J9LjmVzWNudg= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= +github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= +github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/avast/retry-go/v4 v4.5.1 h1:AxIx0HGi4VZ3I02jr78j5lZ3M6x1E0Ivxa6b0pUUh7o= +github.com/avast/retry-go/v4 v4.5.1/go.mod h1:/sipNsvNB3RRuT5iNcb6h73nw3IBmXJ/H3XrCQYSOpc= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.49.0 h1:g9BkW1fo9GqKfwg2+zCD+TW/D36Ux+vtfJ8guF4AYmY= +github.com/aws/aws-sdk-go v1.49.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bgentry/speakeasy v0.2.0 h1:tgObeVOf8WAvtuAX6DhJ4xks4CFNwPDZiqzGqIHE51E= +github.com/bgentry/speakeasy v0.2.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4= +github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= +github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= +github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= +github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c= +github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= +github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= +github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= +github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= +github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/cockroachdb/datadriven v1.0.3-0.20230801171734-e384cf455877 h1:1MLK4YpFtIEo3ZtMA5C795Wtv5VuUnrXX7mQG+aHg6o= +github.com/cockroachdb/datadriven v1.0.3-0.20230801171734-e384cf455877/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.12.0 h1:d7oCs6vuIMUQRVbi6jWWWEJZahLCfJpnJSVobd1/sUo= +github.com/cockroachdb/errors v1.12.0/go.mod h1:SvzfYNNBshAVbZ8wzNc/UPK3w1vf0dKDUP41ucAIf7g= +github.com/cockroachdb/fifo v0.0.0-20240616162244-4768e80dfb9a h1:f52TdbU4D5nozMAhO9TvTJ2ZMCXtN4VIAmfrrZ0JXQ4= +github.com/cockroachdb/fifo v0.0.0-20240616162244-4768e80dfb9a/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M= +github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 h1:ASDL+UJcILMqgNeV5jiqR4j+sTuvQNHdf2chuKj1M5k= +github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506/go.mod h1:Mw7HqKr2kdtu6aYGn3tPmAftiP3QPX63LdK/zcariIo= +github.com/cockroachdb/pebble v1.1.5 h1:5AAWCBWbat0uE0blr8qzufZP5tBjkRyy/jWe1QWLnvw= +github.com/cockroachdb/pebble v1.1.5/go.mod h1:17wO9el1YEigxkP/YtV8NtCivQDgoCyBg5c4VR/eOWo= +github.com/cockroachdb/redact v1.1.6 h1:zXJBwDZ84xJNlHl1rMyCojqyIxv+7YUpQiJLQ7n4314= +github.com/cockroachdb/redact v1.1.6/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/cometbft/cometbft v0.38.17 h1:FkrQNbAjiFqXydeAO81FUzriL4Bz0abYxN/eOHrQGOk= +github.com/cometbft/cometbft v0.38.17/go.mod h1:5l0SkgeLRXi6bBfQuevXjKqML1jjfJJlvI1Ulp02/o4= +github.com/cometbft/cometbft-db v0.14.1 h1:SxoamPghqICBAIcGpleHbmoPqy+crij/++eZz3DlerQ= +github.com/cometbft/cometbft-db v0.14.1/go.mod h1:KHP1YghilyGV/xjD5DP3+2hyigWx0WTp9X+0Gnx0RxQ= +github.com/consensys/bavard v0.1.27 h1:j6hKUrGAy/H+gpNrpLU3I26n1yc+VMGmd6ID5+gAhOs= +github.com/consensys/bavard v0.1.27/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= +github.com/consensys/gnark-crypto v0.16.0 h1:8Dl4eYmUWK9WmlP1Bj6je688gBRJCJbT8Mw4KoTAawo= +github.com/consensys/gnark-crypto v0.16.0/go.mod h1:Ke3j06ndtPTVvo++PhGNgvm+lgpLvzbcE2MqljY7diU= +github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= +github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk= +github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis= +github.com/cosmos/cosmos-db v1.1.3 h1:7QNT77+vkefostcKkhrzDK9uoIEryzFrU9eoMeaQOPY= +github.com/cosmos/cosmos-db v1.1.3/go.mod h1:kN+wGsnwUJZYn8Sy5Q2O0vCYA99MJllkKASbs6Unb9U= +github.com/cosmos/cosmos-proto v1.0.0-beta.5 h1:eNcayDLpip+zVLRLYafhzLvQlSmyab+RC5W7ZfmxJLA= +github.com/cosmos/cosmos-proto v1.0.0-beta.5/go.mod h1:hQGLpiIUloJBMdQMMWb/4wRApmI9hjHH05nefC0Ojec= +github.com/cosmos/cosmos-sdk v0.53.4 h1:kPF6vY68+/xi1/VebSZGpoxQqA52qkhUzqkrgeBn3Mg= +github.com/cosmos/cosmos-sdk v0.53.4/go.mod h1:7U3+WHZtI44dEOnU46+lDzBb2tFh1QlMvi8Z5JugopI= +github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= +github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= +github.com/cosmos/gogogateway v1.2.0 h1:Ae/OivNhp8DqBi/sh2A8a1D0y638GpL3tkmLQAiKxTE= +github.com/cosmos/gogogateway v1.2.0/go.mod h1:iQpLkGWxYcnCdz5iAdLcRBSw3h7NXeOkZ4GUkT+tbFI= +github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU= +github.com/cosmos/gogoproto v1.7.0 h1:79USr0oyXAbxg3rspGh/m4SWNyoz/GLaAh0QlCe2fro= +github.com/cosmos/gogoproto v1.7.0/go.mod h1:yWChEv5IUEYURQasfyBW5ffkMHR/90hiHgbNgrtp4j0= +github.com/cosmos/iavl v1.2.4 h1:IHUrG8dkyueKEY72y92jajrizbkZKPZbMmG14QzsEkw= +github.com/cosmos/iavl v1.2.4/go.mod h1:GiM43q0pB+uG53mLxLDzimxM9l/5N9UuSY3/D0huuVw= +github.com/cosmos/ics23/go v0.11.0 h1:jk5skjT0TqX5e5QJbEnwXIS2yI2vnmLOgpQPeM5RtnU= +github.com/cosmos/ics23/go v0.11.0/go.mod h1:A8OjxPE67hHST4Icw94hOxxFEJMBG031xIGF/JHNIY0= +github.com/cosmos/interchain-security/v7 v7.0.0-20250408210344-06e0dc6bf6d6 h1:SzJ/+uqrTsJmI+f/GqPdC4lGxgDQKYvtRCMXFdJljNM= +github.com/cosmos/interchain-security/v7 v7.0.0-20250408210344-06e0dc6bf6d6/go.mod h1:W7JHsNaZ5XoH88cKT+wuCRsXkx/Fcn2kEwzpeGdJBxI= +github.com/cosmos/interchaintest/v10 v10.0.0 h1:DEsXOS10x191Q3EU4RkOnyqahGCTnLaBGEN//C2MvUQ= +github.com/cosmos/interchaintest/v10 v10.0.0/go.mod h1:caS4BRkAg8NkiZ8BsHEzjNBibt2OVdTctW5Ezz+Jqxs= +github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE= +github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/crate-crypto/go-eth-kzg v1.3.0 h1:05GrhASN9kDAidaFJOda6A4BEvgvuXbazXg/0E3OOdI= +github.com/crate-crypto/go-eth-kzg v1.3.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= +github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= +github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= +github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4= +github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks= +github.com/creachadair/taskgroup v0.3.2 h1:zlfutDS+5XG40AOxcHDSThxKzns8Tnr9jnr6VqkYlkM= +github.com/creachadair/taskgroup v0.3.2/go.mod h1:wieWwecHVzsidg2CsUnFinW1faVN4+kq+TDlRJQ0Wbk= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs= +github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= +github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= +github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= +github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= +github.com/desertbit/timer v1.0.1 h1:yRpYNn5Vaaj6QXecdLMPMJsW81JLiI1eokUft5nBmeo= +github.com/desertbit/timer v1.0.1/go.mod h1:htRrYeY5V/t4iu1xCJ5XsQvp4xve8QulXXctAzxqcwE= +github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= +github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= +github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs= +github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v27.5.1+incompatible h1:4PYU5dnBYqRQi0294d1FBECqT9ECWeQAIfE8q4YnPY8= +github.com/docker/docker v27.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/dvsekhvalnov/jose2go v1.7.0 h1:bnQc8+GMnidJZA8zc6lLEAb4xNrIqHwO+9TzqvtQZPo= +github.com/dvsekhvalnov/jose2go v1.7.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= +github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= +github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= +github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= +github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= +github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= +github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= +github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= +github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/ethereum/c-kzg-4844/v2 v2.1.0 h1:gQropX9YFBhl3g4HYhwE70zq3IHFRgbbNPw0Shwzf5w= +github.com/ethereum/c-kzg-4844/v2 v2.1.0/go.mod h1:TC48kOKjJKPbN7C++qIgt0TJzZ70QznYR7Ob+WXl57E= +github.com/ethereum/go-ethereum v1.15.11 h1:JK73WKeu0WC0O1eyX+mdQAVHUV+UR1a9VB/domDngBU= +github.com/ethereum/go-ethereum v1.15.11/go.mod h1:mf8YiHIb0GR4x4TipcvBUPxJLw1mFdmxzoDi11sDRoI= +github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= +github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/getsentry/sentry-go v0.32.0 h1:YKs+//QmwE3DcYtfKRH8/KyOOF/I6Qnx7qYGNHCGmCY= +github.com/getsentry/sentry-go v0.32.0/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= +github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= +github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= +github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= +github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= +github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= +github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.4.1-0.20201022092350-68b0159b7869/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= +github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= +github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= +github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I= +github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.5-0.20231225225746-43d5d4cd4e0e h1:4bw4WeyTYPp0smaXiJZCNnLrvVBqirQVreixayXezGc= +github.com/golang/snappy v0.0.5-0.20231225225746-43d5d4cd4e0e/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= +github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= +github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us= +github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= +github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= +github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= +github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is= +github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= +github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= +github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= +github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-getter v1.7.8 h1:mshVHx1Fto0/MydBekWan5zUipGq7jO0novchgMmSiY= +github.com/hashicorp/go-getter v1.7.8/go.mod h1:2c6CboOEb9jG6YvmC9xdD+tyAFsrUaJPedwXDGr0TM4= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY= +github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= +github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= +github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= +github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= +github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= +github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= +github.com/herumi/bls-eth-go-binary v1.31.0 h1:9eeW3EA4epCb7FIHt2luENpAW69MvKGL5jieHlBiP+w= +github.com/herumi/bls-eth-go-binary v1.31.0/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U= +github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6wn4Ej8vjuVGxeHdan+bRb2ebyv4= +github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= +github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= +github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= +github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= +github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= +github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= +github.com/huandu/skiplist v1.2.1 h1:dTi93MgjwErA/8idWTzIw4Y1kZsMWx35fmI2c8Rij7w= +github.com/huandu/skiplist v1.2.1/go.mod h1:7v3iFjLcSAzO4fN5B8dvebvo/qsfumiLiDXMrPiHF9w= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/icza/dyno v0.0.0-20230330125955-09f820a8d9c0 h1:nHoRIX8iXob3Y2kdt9KsjyIb7iApSvb3vgsd93xb5Ow= +github.com/icza/dyno v0.0.0-20230330125955-09f820a8d9c0/go.mod h1:c1tRKs5Tx7E2+uHGSyyncziFjvGpgv4H2HrqXeUQ/Uk= +github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= +github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= +github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= +github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= +github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= +github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4= +github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= +github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/linxGnu/grocksdb v1.9.2 h1:O3mzvO0wuzQ9mtlHbDrShixyVjVbmuqTjFrzlf43wZ8= +github.com/linxGnu/grocksdb v1.9.2/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA= +github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= +github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b h1:QrHweqAtyJ9EwCaGHBu1fghwxIPiopAHV06JlXrMHjk= +github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b/go.mod h1:xxLb2ip6sSUts3g1irPVHyk/DGslwQsNOo9I7smJfNU= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= +github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= +github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/moby v27.5.1+incompatible h1:/pN59F/t3U7Q4FPzV88nzqf7Fp0qqCSL2KzhZaiKcKw= +github.com/moby/moby v27.5.1+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= +github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= +github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= +github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/grpc-proxy v0.0.0-20181017164139-0f1106ef9c76/go.mod h1:x5OoJHDHqxHS801UIuhqGl6QdSAEJvtausosHSdazIo= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a h1:dlRvE5fWabOchtH7znfiFCcOvmIYgOeAS5ifBXBlh9Q= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= +github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= +github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= +github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw= +github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= +github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= +github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0= +github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ= +github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c= +github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= +github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM= +github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= +github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prysmaticlabs/fastssz v0.0.0-20241008181541-518c4ce73516 h1:xuVAdtz5ShYblG2sPyb4gw01DF8InbOI/kBCQjk7NiM= +github.com/prysmaticlabs/fastssz v0.0.0-20241008181541-518c4ce73516/go.mod h1:h2OlIZD/M6wFvV3YMZbW16lFgh3Rsye00G44J2cwLyU= +github.com/prysmaticlabs/go-bitfield v0.0.0-20240328144219-a1caa50c3a1e h1:ATgOe+abbzfx9kCPeXIW4fiWyDdxlwHw07j8UGhdTd4= +github.com/prysmaticlabs/go-bitfield v0.0.0-20240328144219-a1caa50c3a1e/go.mod h1:wmuf/mdK4VMD+jA9ThwcUKjg3a2XWM9cVfFYjDyY4j4= +github.com/prysmaticlabs/gohashtree v0.0.4-beta.0.20240624100937-73632381301b h1:VK7thFOnhxAZ/5aolr5Os4beiubuD08WiuiHyRqgwks= +github.com/prysmaticlabs/gohashtree v0.0.4-beta.0.20240624100937-73632381301b/go.mod h1:HRuvtXLZ4WkaB1MItToVH2e8ZwKwZPY5/Rcby+CvvLY= +github.com/prysmaticlabs/prysm/v5 v5.3.0 h1:7Lr8ndapBTZg00YE+MgujN6+yvJR6Bdfn28ZDSJ00II= +github.com/prysmaticlabs/prysm/v5 v5.3.0/go.mod h1:r1KhlduqDMIGZ1GhR5pjZ2Ko8Q89noTDYTRoPKwf1+c= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4= +github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC5sZ4OhQ3+NtdbZ6oBDKQwq5Ou+FI= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= +github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= +github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU= +github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shamaton/msgpack/v2 v2.2.0 h1:IP1m01pHwCrMa6ZccP9B3bqxEMKMSmMVAVKk54g3L/Y= +github.com/shamaton/msgpack/v2 v2.2.0/go.mod h1:6khjYnkx73f7VQU7wjcFS9DFjs+59naVWJv1TB7qdOI= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= +github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= +github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= +github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo= +github.com/supranational/blst v0.3.14/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok= +github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= +github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= +github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= +github.com/tendermint/tendermint v0.38.0-dev h1:yX4zsEgTF9PxlLmhx9XAPTGH2E9FSlqSpHcY7sW7Vb8= +github.com/tendermint/tendermint v0.38.0-dev/go.mod h1:EHKmaqObmcGysoRr7krxXoxxhUDyYWbKvvRYJ9tCGWY= +github.com/tendermint/tm-db v0.6.6 h1:EzhaOfR0bdKyATqcd5PNeyeq8r+V4bRPHBfyFdD9kGM= +github.com/tendermint/tm-db v0.6.6/go.mod h1:wP8d49A85B7/erz/r4YbKssKw6ylsO/hKtFk7E1aWZI= +github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e h1:cR8/SYRgyQCt5cNCMniB/ZScMkhI9nk8U5C7SbISXjo= +github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e/go.mod h1:Tu4lItkATkonrYuvtVjG0/rhy15qrNGNTjPdaphtZ/8= +github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI= +github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= +github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= +github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= +github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= +github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= +github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.4.0-alpha.1 h1:3yrqQzbRRPFPdOMWS/QQIVxVnzSkAZQYeWlZFv1kbj4= +go.etcd.io/bbolt v1.4.0-alpha.1/go.mod h1:S/Z/Nm3iuOnyO1W4XuFfPci51Gj6F1Hv0z8hisyYYOw= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 h1:BEj3SPM81McUZHYjRS5pEgNgnmzGJ5tRpU5krWnV8Bs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0/go.mod h1:9cKLGBDzI/F3NoHLQGm4ZrYdIHsvGt6ej6hUowxY0J4= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= +go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= +golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU= +golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= +golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= +gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= +gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= +google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= +google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= +google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= +google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= +google.golang.org/api v0.222.0 h1:Aiewy7BKLCuq6cUCeOUrsAlzjXPqBkEeQ/iwGHVQa/4= +google.golang.org/api v0.222.0/go.mod h1:efZia3nXpWELrwMlN5vyQrD4GmJN1Vw0x68Et3r+a9c= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200324203455-a04cca1dde73/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= +google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= +google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= +google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= +google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= +google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= +google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= +google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= +modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= +modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= +modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= +modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= +modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= +modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y= +modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= +modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= +modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= +modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= +modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= +modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= +modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= +modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= +modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= +modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= +modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= +modernc.org/sqlite v1.31.1 h1:XVU0VyzxrYHlBhIs1DiEgSl0ZtdnPtbLVy8hSkzxGrs= +modernc.org/sqlite v1.31.1/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA= +modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= +modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= +nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= +nhooyr.io/websocket v1.8.17 h1:KEVeLJkUywCKVsnLIDlD/5gtayKp8VoCkksHCGGfT9Y= +nhooyr.io/websocket v1.8.17/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/e2e/internal/directories/directories.go b/e2e/internal/directories/directories.go new file mode 100644 index 0000000..9cb8d86 --- /dev/null +++ b/e2e/internal/directories/directories.go @@ -0,0 +1,37 @@ +package directories + +import ( + "fmt" + "os" + "path" + "strings" +) + +const ( + e2eDir = "e2e" + + // DefaultGenesisExportPath is the default path to which Genesis debug files will be exported to. + DefaultGenesisExportPath = "diagnostics/genesis.json" +) + +// E2E finds the e2e directory above the test. +func E2E() (string, error) { + wd, err := os.Getwd() + if err != nil { + return "", err + } + + const maxAttempts = 100 + count := 0 + for ; !strings.HasSuffix(wd, e2eDir) && count < maxAttempts; wd = path.Dir(wd) { + count++ + } + + // arbitrary value to avoid getting stuck in an infinite loop if this is called + // in a context where the e2e directory does not exist. + if count == maxAttempts { + return "", fmt.Errorf("unable to find e2e directory after %d tries", maxAttempts) + } + + return wd, nil +} diff --git a/e2e/relayer/relayer.go b/e2e/relayer/relayer.go new file mode 100644 index 0000000..ad54570 --- /dev/null +++ b/e2e/relayer/relayer.go @@ -0,0 +1,179 @@ +package relayer + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/cosmos/interchaintest/v10" + "github.com/cosmos/interchaintest/v10/ibc" + "github.com/cosmos/interchaintest/v10/relayer" + "github.com/cosmos/interchaintest/v10/relayer/hermes" + mobycli "github.com/moby/moby/client" + "github.com/pelletier/go-toml" + "go.uber.org/zap" +) + +const ( + Rly = "rly" + Hermes = "hermes" + + HermesRelayerRepository = "ghcr.io/informalsystems/hermes" + hermesRelayerUser = "2000:2000" + RlyRelayerRepository = "ghcr.io/cosmos/relayer" + rlyRelayerUser = "100:1000" + + // relativeHermesConfigFilePath is the path to the hermes config file relative to the home directory within the container. + relativeHermesConfigFilePath = ".hermes/config.toml" +) + +// Config holds configuration values for the relayer used in the tests. +type Config struct { + // Tag is the tag used for the relayer image. + Tag string `yaml:"tag"` + // ID specifies the type of relayer that this is. + ID string `yaml:"id"` + // Image is the image that should be used for the relayer. + Image string `yaml:"image"` +} + +// New returns an implementation of ibc.Relayer depending on the provided RelayerType. +func New(t *testing.T, cfg Config, logger *zap.Logger, dockerClient *mobycli.Client, network string) ibc.Relayer { + t.Helper() + switch cfg.ID { + case Rly: + return newCosmosRelayer(t, cfg.Tag, logger, dockerClient, network, cfg.Image) + case Hermes: + return newHermesRelayer(t, cfg.Tag, logger, dockerClient, network, cfg.Image) + default: + panic(fmt.Errorf("unknown relayer specified: %s", cfg.ID)) + } +} + +// ApplyPacketFilter applies a packet filter to the hermes config file, which specifies a complete set of channels +// to watch for packets. +func ApplyPacketFilter(ctx context.Context, t *testing.T, r ibc.Relayer, chainID string, channels []ibc.ChannelOutput) error { + t.Helper() + + h, ok := r.(*hermes.Relayer) + if !ok { + t.Logf("relayer %T does not support packet filtering, or it has not been implemented yet.", r) + return nil + } + + return modifyHermesConfigFile(ctx, h, func(config map[string]any) error { + chains, ok := config["chains"].([]map[string]any) + if !ok { + return errors.New("failed to get chains from hermes config") + } + var chain map[string]any + for _, c := range chains { + if c["id"] == chainID { + chain = c + break + } + } + + if chain == nil { + return fmt.Errorf("failed to find chain with id %s", chainID) + } + + var channelEndpoints [][]string + for _, c := range channels { + channelEndpoints = append(channelEndpoints, []string{c.PortID, c.ChannelID}) + } + + // [chains.packet_filter] + // # policy = 'allow' + // # list = [ + // # ['ica*', '*'], + // # ['transfer', 'channel-0'], + // # ] + + // TODO(chatton): explicitly enable watching of ICA channels + // this will ensure the ICA tests pass, but this will need to be modified to make sure + // ICA tests will succeed in parallel. + channelEndpoints = append(channelEndpoints, []string{"ica*", "*"}) + + // we explicitly override the full list, this allows this function to provide a complete set of channels to watch. + chain["packet_filter"] = map[string]any{ + "policy": "allow", + "list": channelEndpoints, + } + + return nil + }) +} + +// modifyHermesConfigFile reads the hermes config file, applies a modification function and returns an error if any. +func modifyHermesConfigFile(ctx context.Context, h *hermes.Relayer, modificationFn func(map[string]any) error) error { + bz, err := h.ReadFileFromHomeDir(ctx, relativeHermesConfigFilePath) + if err != nil { + return fmt.Errorf("failed to read hermes config file: %w", err) + } + + var config map[string]any + if err := toml.Unmarshal(bz, &config); err != nil { + return errors.New("failed to unmarshal hermes config bytes") + } + + if modificationFn != nil { + if err := modificationFn(config); err != nil { + return fmt.Errorf("failed to modify hermes config: %w", err) + } + } + + bz, err = toml.Marshal(config) + if err != nil { + return errors.New("failed to marshal hermes config bytes") + } + + return h.WriteFileToHomeDir(ctx, relativeHermesConfigFilePath, bz) +} + +// newCosmosRelayer returns an instance of the go relayer. +// Options are used to allow for relayer version selection and specifying the default processing option. +func newCosmosRelayer(t *testing.T, tag string, logger *zap.Logger, dockerClient *mobycli.Client, network, relayerImage string) ibc.Relayer { + t.Helper() + + customImageOption := relayer.CustomDockerImage(relayerImage, tag, rlyRelayerUser) + relayerProcessingOption := relayer.StartupFlags("-p", "events") // relayer processes via events + + relayerFactory := interchaintest.NewBuiltinRelayerFactory(ibc.CosmosRly, logger, customImageOption, relayerProcessingOption) + + return relayerFactory.Build( + t, dockerClient, network, + ) +} + +// newHermesRelayer returns an instance of the hermes relayer. +func newHermesRelayer(t *testing.T, tag string, logger *zap.Logger, dockerClient *mobycli.Client, network, relayerImage string) ibc.Relayer { + t.Helper() + + customImageOption := relayer.CustomDockerImage(relayerImage, tag, hermesRelayerUser) + relayerFactory := interchaintest.NewBuiltinRelayerFactory(ibc.Hermes, logger, customImageOption) + + return relayerFactory.Build( + t, dockerClient, network, + ) +} + +// Map is a mapping from test names to a relayer set for that test. +type Map map[string]map[ibc.Wallet]bool + +// AddRelayer adds the given relayer to the relayer set for the given test name. +func (r Map) AddRelayer(testName string, ibcrelayer ibc.Wallet) { + if _, ok := r[testName]; !ok { + r[testName] = make(map[ibc.Wallet]bool) + } + r[testName][ibcrelayer] = true +} + +// ContainsRelayer returns true if the given relayer is in the relayer set for the given test name. +func (r Map) ContainsRelayer(testName string, wallet ibc.Wallet) bool { + if relayerSet, ok := r[testName]; ok { + return relayerSet[wallet] + } + return false +} diff --git a/e2e/sample.config.extended.yaml b/e2e/sample.config.extended.yaml new file mode 100644 index 0000000..f03899d --- /dev/null +++ b/e2e/sample.config.extended.yaml @@ -0,0 +1,70 @@ +# This file contains configuration for running e2e tests. +# Many of these fields can be overridden with environment variables. +# All fields that support this have the corresponding environment variable name in a comment beside the field. + +# | Environment Variable | Description | Default Value | +# |----------------------|-------------------------------------------|------------------------------| +# | CHAIN_IMAGE | The image that will be used for the chain | ghcr.io/cosmos/ibc-go-simd | +# | CHAIN_A_TAG | The tag used for chain A | N/A (must be set) | +# | CHAIN_B_TAG | The tag used for chain B | N/A (must be set) | +# | CHAIN_BINARY | The binary used in the container | simd | +# | RELAYER_TAG | The tag used for the relayer | 1.10.4 | +# | RELAYER_ID | The type of relayer to use (rly/hermes) | hermes | + +# see sample.config.yaml for a bare minimum configuration example. +# set env E2E_CONFIG_PATH to point to this file to use it. +--- +chains: + # the entry at index 0 corresponds to CHAIN_A + - chainId: chainA-1 + numValidators: 4 + numFullNodes: 1 + image: ghcr.io/cosmos/ibc-go-simd # override with CHAIN_IMAGE + tag: main # override with CHAIN_A_TAG + binary: simd # override with CHAIN_BINARY + + # the entry at index 1 corresponds to CHAIN_B + - chainId: chainB-1 + numValidators: 4 + numFullNodes: 1 + image: ghcr.io/cosmos/ibc-go-simd # override with CHAIN_IMAGE + tag: main # override with CHAIN_B_TAG + binary: simd # override with CHAIN_BINARY + +# activeRelayer must match the id of a relayer specified in the relayers list below. +activeRelayer: hermes # override with RELAYER_ID + +# relayers provides a list all possible relayers that will be usable within a test +# NOTE: the activeRelayer field, must match the id specified in one of the relayers in this list. +# if that is not the case, validation will fail. +relayers: + - id: hermes + image: ghcr.io/informalsystems/hermes + tag: "1.10.4" # override with RELAYER_TAG + - id: rly + image: ghcr.io/cosmos/relayer + tag: "latest" # override with RELAYER_TAG + +cometbft: + logLevel: info + +debug: + # setting this value to true will force log collection even if the test passes. + dumpLogs: false + # settings this value to true will keep the containers running after the test finishes. + keepContainers: true + +upgradePlanName: "" # specify a value that matches a planName in the upgrades list below. +upgrades: + - planName: "v7" + tag: "v7.0.0" + - planName: "v7.1" + tag: "v7.1.0" + - planName: "v8" + tag: "v8.0.0" + - planName: "v8.1" + tag: "v8.1.0" + - planName: "v10" + tag: "v10.1.0" + - planName: "ibcwasm-v8" + tag: "v8.0.0-e2e-upgrade" diff --git a/e2e/sample.config.yaml b/e2e/sample.config.yaml new file mode 100644 index 0000000..376e03a --- /dev/null +++ b/e2e/sample.config.yaml @@ -0,0 +1,8 @@ +# This file contains a bare minimum configuration for running e2e tests. +# for a more detailed configuration, see sample.config.extended.yaml. +--- +chains: + - tag: main # override with CHAIN_A_TAG + chainId: chainA-1 + - tag: main # override with CHAIN_B_TAG + chainId: chainB-1 diff --git a/e2e/scripts/init.sh b/e2e/scripts/init.sh new file mode 100755 index 0000000..216153a --- /dev/null +++ b/e2e/scripts/init.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +set -euo pipefail + +# TODO: when we are using config files for CI we can remove this. +# ref: https://github.com/cosmos/ibc-go/issues/4697 +# if running in CI we just use env vars. +if [[ "${CI:-}" = "true" ]]; then + exit 0 +fi + +# ensure_config_file makes sure there is a config file for the e2e tests either by creating a new one using the sample, +# it is copied to either the default location or the specified env location. +function ensure_config_file(){ + local config_file_path="${HOME}/.ibc-go-e2e-config.yaml" + if [[ ! -z "${E2E_CONFIG_PATH:-}" ]]; then + config_file_path="${E2E_CONFIG_PATH}" + fi + if [[ ! -f "${config_file_path}" ]]; then + echo "creating e2e config file from sample." + echo "copying sample.config.yaml to ${config_file_path}" + cp sample.config.yaml "${config_file_path}" + fi + echo "using config file at ${config_file_path} for e2e test" +} + +ensure_config_file diff --git a/e2e/scripts/run-e2e.sh b/e2e/scripts/run-e2e.sh new file mode 100755 index 0000000..4591bd6 --- /dev/null +++ b/e2e/scripts/run-e2e.sh @@ -0,0 +1,116 @@ +#!/bin/bash + +set -eo pipefail + +TEST="${1:-}" +export ENTRY_POINT="${2:-}" + +function _verify_jq() { + if ! command -v jq > /dev/null ; then + echo "jq is required to extract test entrypoint." + exit 1 + fi +} + +function _verify_fzf() { + if ! command -v fzf > /dev/null ; then + echo "fzf is required to interactively select a test." + exit 1 + fi +} + +function _verify_test_dependencies() { + if [ -z "${TEST}" ]; then + # fzf is only required if we are not explicitly specifying a test. + _verify_fzf + fi + # jq is always required to determine the entrypoint of the test. + _verify_jq +} + +function _verify_suite_dependencies() { + if [ -z "${ENTRY_POINT}" ]; then + # fzf is only required if we are not explicitly specifying an entrypoint. + _verify_fzf + fi + # jq is always required to determine the entrypoint of the test. + _verify_jq +} + +# _select_test_config lets you dynamically select a test config for the specific test. +function _select_test_config() { + ls -1 dev-configs | fzf +} + +# _get_test returns the test that should be used in the e2e test. If an argument is provided, that argument +# is returned. Otherwise, fzf is used to interactively choose from all available tests. +function _get_test(){ + # if an argument is provided, it is used directly. This enables the drop down selection with fzf. + if [ -n "${1:-}" ]; then + echo "$1" + return + # if fzf and jq are installed, we can use them to provide an interactive mechanism to select from all available tests. + else + cd .. + go run -mod=readonly cmd/build_test_matrix/main.go | jq -r '.include[] | .test' | fzf + cd - > /dev/null + fi +} + +# run_test runs a single E2E test. +function run_test() { + _verify_test_dependencies + + # if test is set, that is used directly, otherwise the test can be interactively provided if fzf is installed. + TEST="$(_get_test ${TEST})" + + # if jq is installed, we can automatically determine the test entrypoint. + if command -v jq > /dev/null; then + cd .. + ENTRY_POINT="$(go run -mod=readonly cmd/build_test_matrix/main.go | jq -r --arg TEST "${TEST}" '.include[] | select( .test == $TEST) | .entrypoint')" + cd - > /dev/null + fi + + + # find the name of the file that has this test in it. + test_file="$(grep --recursive --files-with-matches './' -e "${TEST}()")" + + # we run the test on the directory as specific files may reference types in other files but within the package. + test_dir="$(dirname $test_file)" + + # run the test file directly, this allows log output to be streamed directly in the terminal sessions + # without needed to wait for the test to finish. + # it shouldn't take 30m, but the wasm test can be quite slow, so we can be generous. + go test -v "${test_dir}" --run ${ENTRY_POINT} -testify.m ^${TEST}$ -timeout 30m +} + +# run_suite runs a full E2E test suite. +function run_suite() { + _verify_suite_dependencies + # if jq is installed, we can automatically determine the test entrypoint. + if [ -z "${ENTRY_POINT}" ]; then + cd .. + ENTRY_POINT="$(go run -mod=readonly cmd/build_test_matrix/main.go | jq -r '.include[] | .entrypoint' | uniq | fzf)" + cd - > /dev/null + fi + + # find the name of the file that has this test in it. + test_file="$(grep --recursive --files-with-matches './tests' -e "${ENTRY_POINT}(")" + test_dir="$(dirname $test_file)" + + go test -v "${test_dir}" --run ^${ENTRY_POINT}$ -timeout 30m -p 10 +} + + +# if the dev configs directory is present, enable fzf completion to select a test config file to use. +if [[ -d "dev-configs" ]]; then + export E2E_CONFIG_PATH="$(pwd)/dev-configs/$(_select_test_config)" + echo "Using configuration file at ${E2E_CONFIG_PATH}" +fi + +if [ "${RUN_SUITE:-}" = "true" ]; then + run_suite +else + run_test +fi + diff --git a/e2e/semverutil/semver.go b/e2e/semverutil/semver.go new file mode 100644 index 0000000..2c56277 --- /dev/null +++ b/e2e/semverutil/semver.go @@ -0,0 +1,61 @@ +package semverutil + +import ( + "strings" + + "golang.org/x/mod/semver" +) + +// FeatureReleases contains the combination of versions the feature was released in. +type FeatureReleases struct { + // MajorVersion is the major version in the format including the v. E.g. "v6" + MajorVersion string + // MinorVersions contains a slice of versions including the v and excluding the patch version. E.g. v2.5 + MinorVersions []string +} + +// IsSupported returns whether the version contains the feature. +// This is true if the version is greater than or equal to the major version it was released in +// or is greater than or equal to the list of minor releases it was included in. +func (fr FeatureReleases) IsSupported(versionStr string) bool { + // in our compatibility tests, our images are in the format of "release-v1.0.x". We want to actually look at + // the "1.0.x" part but we also need this to be a valid version. We can change it to "1.0.0" + // TODO: change the way we provide the ibc-go version. This should be done in a more flexible way such + // as docker labels/metadata instead of the tag, as this will only work for our versioning scheme. + const releasePrefix = "release-" + if strings.HasPrefix(versionStr, releasePrefix) { + versionStr = versionStr[len(releasePrefix):] + // replace x with 999 so the release version is always larger than the others in the release line. + versionStr = strings.ReplaceAll(versionStr, "x", "999") + } + + // assume any non-semantic version formatted version supports the feature + // this will be useful during development of the e2e test with the new feature + if !semver.IsValid(versionStr) { + return true + } + + if fr.MajorVersion != "" && GTE(versionStr, fr.MajorVersion) { + return true + } + + for _, mv := range fr.MinorVersions { + mvMajor, versionStrMajor := semver.Major(mv), semver.Major(versionStr) + + if semverEqual(mvMajor, versionStrMajor) { + return GTE(versionStr, mv) + } + } + + return false +} + +// GTE returns true if versionA is greater than or equal to versionB. +func GTE(versionA, versionB string) bool { + return semver.Compare(versionA, versionB) >= 0 +} + +// semverEqual returns true if versionA is equal to versionB. +func semverEqual(versionA, versionB string) bool { + return semver.Compare(versionA, versionB) == 0 +} diff --git a/e2e/semverutil/semver_test.go b/e2e/semverutil/semver_test.go new file mode 100644 index 0000000..1e0d7b9 --- /dev/null +++ b/e2e/semverutil/semver_test.go @@ -0,0 +1,54 @@ +package semverutil_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc-go/e2e/semverutil" +) + +func TestIsSupported(t *testing.T) { + releases := semverutil.FeatureReleases{ + MajorVersion: "v6", + MinorVersions: []string{ + "v2.5", + "v3.4", + "v4.2", + "v5.1", + }, + } + + testCases := []struct { + name string + version string + expSupported bool + }{ + {"non semantic version", "main", true}, + {"non semantic version starts with v", "v", true}, + {"non semantic version", "pr-155", true}, + {"non semantic version", "major.5.1", true}, + {"non semantic version", "1.minor.1", true}, + {"supported semantic version", "v2.5.0", true}, + {"supported semantic version", "v3.4.0", true}, + {"supported semantic version", "v4.2.0", true}, + {"supported semantic version", "v5.1.0", true}, + {"supported semantic version", "v6.0.0", true}, + {"supported semantic version", "v6.1.0", true}, + {"supported semantic version", "v7.1.0", true}, + {"supported semantic version", "v22.5.1", true}, + {"supported semantic version pre-release", "v6.0.0-rc.0", false}, + {"supported semantic version without v", "2.5.0", true}, + {"unsupported semantic version", "v1.5.0", false}, + {"unsupported semantic version", "v2.4.5", false}, + {"unsupported semantic version", "v3.1.0", false}, + {"unsupported semantic version", "v4.1.0", false}, + {"unsupported semantic version", "v5.0.0", false}, + {"unsupported semantic version on partially supported major line", "v2.4.0", false}, + } + + for _, tc := range testCases { + supported := releases.IsSupported(tc.version) + require.Equal(t, tc.expSupported, supported, tc.name) + } +} diff --git a/e2e/tests/core/02-client/client_test.go b/e2e/tests/core/02-client/client_test.go new file mode 100644 index 0000000..80feab8 --- /dev/null +++ b/e2e/tests/core/02-client/client_test.go @@ -0,0 +1,526 @@ +//go:build !test_e2e + +package client + +import ( + "context" + "fmt" + "slices" + "sort" + "strings" + "testing" + "time" + + "github.com/cosmos/interchaintest/v10/ibc" + test "github.com/cosmos/interchaintest/v10/testutil" + testifysuite "github.com/stretchr/testify/suite" + + upgradetypes "cosmossdk.io/x/upgrade/types" + + "github.com/cosmos/cosmos-sdk/client/grpc/cmtservice" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + paramsproposaltypes "github.com/cosmos/cosmos-sdk/x/params/types/proposal" + + "github.com/cometbft/cometbft/crypto/tmhash" + cmtjson "github.com/cometbft/cometbft/libs/json" + "github.com/cometbft/cometbft/privval" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + cmtprotoversion "github.com/cometbft/cometbft/proto/tendermint/version" + cmttypes "github.com/cometbft/cometbft/types" + cmtversion "github.com/cometbft/cometbft/version" + + "github.com/cosmos/ibc-go/e2e/dockerutil" + "github.com/cosmos/ibc-go/e2e/testsuite" + "github.com/cosmos/ibc-go/e2e/testsuite/query" + "github.com/cosmos/ibc-go/e2e/testvalues" + wasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +const ( + invalidHashValue = "invalid_hash" +) + +// compatibility:from_version: v7.10.0 +func TestClientTestSuite(t *testing.T) { + testifysuite.Run(t, new(ClientTestSuite)) +} + +type ClientTestSuite struct { + testsuite.E2ETestSuite +} + +// QueryAllowedClients queries the on-chain AllowedClients parameter for 02-client +func (s *ClientTestSuite) QueryAllowedClients(ctx context.Context, chain ibc.Chain) []string { + res, err := query.GRPCQuery[clienttypes.QueryClientParamsResponse](ctx, chain, &clienttypes.QueryClientParamsRequest{}) + s.Require().NoError(err) + + return res.Params.AllowedClients +} + +// SetupSuite sets up chains for the current test suite +func (s *ClientTestSuite) SetupSuite() { + s.SetupChains(context.TODO(), 2, nil) +} + +// TestScheduleIBCUpgrade_Succeeds tests that a governance proposal to schedule an IBC software upgrade is successful. +// compatibility:TestScheduleIBCUpgrade_Succeeds:from_versions: v8.7.0,v10.0.0 +func (s *ClientTestSuite) TestScheduleIBCUpgrade_Succeeds() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + s.CreateDefaultPaths(testName) + + chainA, chainB := s.GetChains() + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + + const planHeight = int64(300) + const legacyPlanHeight = planHeight * 2 + var newChainID string + + t.Run("execute proposal for MsgIBCSoftwareUpgrade", func(t *testing.T) { + authority, err := query.ModuleAccountAddress(ctx, govtypes.ModuleName, chainA) + s.Require().NoError(err) + s.Require().NotNil(authority) + + clientState, err := query.ClientState(ctx, chainB, ibctesting.FirstClientID) + s.Require().NoError(err) + + originalChainID := clientState.(*ibctm.ClientState).ChainId + revisionNumber := clienttypes.ParseChainID(originalChainID) + // increment revision number even with new chain ID to prevent loss of misbehaviour detection support + newChainID, err = clienttypes.SetRevisionNumber(originalChainID, revisionNumber+1) + s.Require().NoError(err) + s.Require().NotEqual(originalChainID, newChainID) + + upgradedClientState, ok := clientState.(*ibctm.ClientState) + s.Require().True(ok) + upgradedClientState.ChainId = newChainID + + scheduleUpgradeMsg, err := clienttypes.NewMsgIBCSoftwareUpgrade( + authority.String(), + upgradetypes.Plan{ + Name: "upgrade-client", + Height: planHeight, + }, + upgradedClientState, + ) + s.Require().NoError(err) + s.ExecuteAndPassGovV1Proposal(ctx, scheduleUpgradeMsg, chainA, chainAWallet) + }) + + t.Run("check that IBC software upgrade has been scheduled successfully on chainA", func(t *testing.T) { + // checks there is an upgraded client state stored + upgradedCsResp, err := query.GRPCQuery[clienttypes.QueryUpgradedClientStateResponse](ctx, chainA, &clienttypes.QueryUpgradedClientStateRequest{}) + s.Require().NoError(err) + + clientStateAny := upgradedCsResp.UpgradedClientState + + cfg := chainA.Config().EncodingConfig + var cs ibcexported.ClientState + err = cfg.InterfaceRegistry.UnpackAny(clientStateAny, &cs) + s.Require().NoError(err) + + upgradedClientState, ok := cs.(*ibctm.ClientState) + s.Require().True(ok) + s.Require().Equal(upgradedClientState.ChainId, newChainID) + + planResponse, err := query.GRPCQuery[upgradetypes.QueryCurrentPlanResponse](ctx, chainA, &upgradetypes.QueryCurrentPlanRequest{}) + s.Require().NoError(err) + + plan := planResponse.Plan + + s.Require().Equal("upgrade-client", plan.Name) + s.Require().Equal(planHeight, plan.Height) + }) + + t.Run("ensure legacy proposal does not succeed", func(t *testing.T) { + authority, err := query.ModuleAccountAddress(ctx, govtypes.ModuleName, chainA) + s.Require().NoError(err) + s.Require().NotNil(authority) + + clientState, err := query.ClientState(ctx, chainB, ibctesting.FirstClientID) + s.Require().NoError(err) + + originalChainID := clientState.(*ibctm.ClientState).ChainId + revisionNumber := clienttypes.ParseChainID(originalChainID) + // increment revision number even with new chain ID to prevent loss of misbehaviour detection support + newChainID, err = clienttypes.SetRevisionNumber(originalChainID, revisionNumber+1) + s.Require().NoError(err) + s.Require().NotEqual(originalChainID, newChainID) + + upgradedClientState := clientState.(*ibctm.ClientState).ZeroCustomFields() + upgradedClientState.ChainId = newChainID + + legacyUpgradeProposal, err := clienttypes.NewUpgradeProposal(ibctesting.Title, ibctesting.Description, upgradetypes.Plan{ + Name: "upgrade-client-legacy", + Height: legacyPlanHeight, + }, upgradedClientState) + + s.Require().NoError(err) + txResp := s.ExecuteGovV1Beta1Proposal(ctx, chainA, chainAWallet, legacyUpgradeProposal) + s.AssertTxFailure(txResp, govtypes.ErrInvalidProposalType, govtypes.ErrInvalidProposalContent) + }) +} + +// TestRecoverClient_Succeeds tests that a governance proposal to recover a client using a MsgRecoverClient is successful. +// compatibility:TestRecoverClient_Succeeds:from_versions: v8.7.0,v10.0.0 +func (s *ClientTestSuite) TestRecoverClient_Succeeds() { + t := s.T() + ctx := context.TODO() + + var ( + pathName string + subjectClientID string + substituteClientID string + // set the trusting period to a value which will still be valid upon client creation, but invalid before the first update + badTrustingPeriod = time.Second * 10 + ) + + testName := t.Name() + relayer := s.CreateDefaultPaths(testName) + + t.Run("create substitute client with correct trusting period", func(t *testing.T) { + // TODO: update when client identifier created is accessible + // currently assumes first client is 07-tendermint-0 + substituteClientID = clienttypes.FormatClientIdentifier(ibcexported.Tendermint, 0) + + pathName = s.GetPaths(testName)[0] + }) + + chainA, chainB := s.GetChains() + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + + t.Run("create subject client with bad trusting period", func(t *testing.T) { + createClientOptions := ibc.CreateClientOptions{ + TrustingPeriod: badTrustingPeriod.String(), + } + + s.SetupClients(ctx, relayer, createClientOptions) + + // TODO: update when client identifier created is accessible + // currently assumes second client is 07-tendermint-1 + subjectClientID = clienttypes.FormatClientIdentifier(ibcexported.Tendermint, 1) + }) + + time.Sleep(badTrustingPeriod) + + t.Run("update substitute client", func(t *testing.T) { + s.UpdateClients(ctx, relayer, pathName) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("check status of each client", func(t *testing.T) { + t.Run("substitute should be active", func(t *testing.T) { + status, err := query.ClientStatus(ctx, chainA, substituteClientID) + s.Require().NoError(err) + s.Require().Equal(ibcexported.Active.String(), status) + }) + + t.Run("subject should be expired", func(t *testing.T) { + status, err := query.ClientStatus(ctx, chainA, subjectClientID) + s.Require().NoError(err) + s.Require().Equal(ibcexported.Expired.String(), status) + }) + }) + + t.Run("execute proposal for MsgRecoverClient", func(t *testing.T) { + authority, err := query.ModuleAccountAddress(ctx, govtypes.ModuleName, chainA) + s.Require().NoError(err) + recoverClientMsg := clienttypes.NewMsgRecoverClient(authority.String(), subjectClientID, substituteClientID) + s.Require().NotNil(recoverClientMsg) + s.ExecuteAndPassGovV1Proposal(ctx, recoverClientMsg, chainA, chainAWallet) + }) + + t.Run("check status of each client", func(t *testing.T) { + t.Run("substitute should be active", func(t *testing.T) { + status, err := query.ClientStatus(ctx, chainA, substituteClientID) + s.Require().NoError(err) + s.Require().Equal(ibcexported.Active.String(), status) + }) + + t.Run("subject should be active", func(t *testing.T) { + status, err := query.ClientStatus(ctx, chainA, subjectClientID) + s.Require().NoError(err) + s.Require().Equal(ibcexported.Active.String(), status) + }) + }) +} + +func (s *ClientTestSuite) TestClient_Update_Misbehaviour() { + t := s.T() + ctx := context.TODO() + + var ( + trustedHeight clienttypes.Height + latestHeight clienttypes.Height + clientState ibcexported.ClientState + header *cmtservice.Header + signers []cmttypes.PrivValidator + validatorSet []*cmttypes.Validator + maliciousHeader *ibctm.Header + err error + ) + + testName := t.Name() + relayer := s.CreateDefaultPaths(testName) + + chainA, chainB := s.GetChains() + + s.Require().NoError(test.WaitForBlocks(ctx, 10, chainA, chainB)) + + t.Run("update clients", func(t *testing.T) { + err := relayer.UpdateClients(ctx, s.GetRelayerExecReporter(), s.GetPaths(testName)[0]) + s.Require().NoError(err) + + clientState, err = query.ClientState(ctx, chainA, ibctesting.FirstClientID) + s.Require().NoError(err) + }) + + t.Run("fetch trusted height", func(t *testing.T) { + tmClientState, ok := clientState.(*ibctm.ClientState) + s.Require().True(ok) + + trustedHeight = tmClientState.LatestHeight + s.Require().True(trustedHeight.GT(clienttypes.ZeroHeight())) + }) + + t.Run("update clients", func(t *testing.T) { + err := relayer.UpdateClients(ctx, s.GetRelayerExecReporter(), s.GetPaths(testName)[0]) + s.Require().NoError(err) + + clientState, err = query.ClientState(ctx, chainA, ibctesting.FirstClientID) + s.Require().NoError(err) + }) + + t.Run("fetch client state latest height", func(t *testing.T) { + tmClientState, ok := clientState.(*ibctm.ClientState) + s.Require().True(ok) + + latestHeight = tmClientState.LatestHeight + s.Require().True(latestHeight.GT(clienttypes.ZeroHeight())) + }) + + t.Run("create validator set", func(t *testing.T) { + var validators []*cmtservice.Validator + + t.Run("fetch block header at latest client state height", func(t *testing.T) { + headerResp, err := query.GRPCQuery[cmtservice.GetBlockByHeightResponse](ctx, chainB, &cmtservice.GetBlockByHeightRequest{ + Height: int64(latestHeight.GetRevisionHeight()), + }) + s.Require().NoError(err) + + header = &headerResp.SdkBlock.Header + }) + + t.Run("get validators at latest height", func(t *testing.T) { + validators, err = query.GetValidatorSetByHeight(ctx, chainB, latestHeight.GetRevisionHeight()) + s.Require().NoError(err) + }) + + t.Run("extract validator private keys", func(t *testing.T) { + privateKeys := s.extractChainPrivateKeys(ctx, chainB) + s.Require().NotEmpty(privateKeys, "private keys are empty") + + for i, pv := range privateKeys { + pubKey, err := pv.GetPubKey() + s.Require().NoError(err) + + validator := cmttypes.NewValidator(pubKey, validators[i].VotingPower) + err = validator.ValidateBasic() + s.Require().NoError(err, "invalid validator: %s", err) + + validatorSet = append(validatorSet, validator) + signers = append(signers, pv) + } + }) + }) + + s.Require().NotEmpty(validatorSet, "validator set is empty") + + t.Run("create malicious header", func(t *testing.T) { + valSet := cmttypes.NewValidatorSet(validatorSet) + err := valSet.ValidateBasic() + s.Require().NoError(err, "invalid validator set: %s", err) + maliciousHeader, err = createMaliciousTMHeader(chainB.Config().ChainID, int64(latestHeight.GetRevisionHeight()), trustedHeight, + header.GetTime(), valSet, valSet, signers, header) + s.Require().NoError(err) + }) + + t.Run("update client with duplicate misbehaviour header", func(t *testing.T) { + rlyWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + msgUpdateClient, err := clienttypes.NewMsgUpdateClient(ibctesting.FirstClientID, maliciousHeader, rlyWallet.FormattedAddress()) + s.Require().NoError(err) + + txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgUpdateClient) + s.AssertTxSuccess(txResp) + }) + + t.Run("ensure client status is frozen", func(t *testing.T) { + status, err := query.ClientStatus(ctx, chainA, ibctesting.FirstClientID) + s.Require().NoError(err) + s.Require().Equal(ibcexported.Frozen.String(), status) + }) +} + +// TestAllowedClientsParam tests changing the AllowedClients parameter using a governance proposal +func (s *ClientTestSuite) TestAllowedClientsParam() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + s.CreateDefaultPaths(testName) + + chainA, chainB := s.GetChains() + chainAVersion := chainA.Config().Images[0].Version + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("ensure allowed clients are set to the default", func(t *testing.T) { + allowedClients := s.QueryAllowedClients(ctx, chainA) + + defaultAllowedClients := clienttypes.DefaultAllowedClients + if !testvalues.AllowAllClientsWildcardFeatureReleases.IsSupported(chainAVersion) { + defaultAllowedClients = []string{ibcexported.Solomachine, ibcexported.Tendermint, ibcexported.Localhost, wasmtypes.Wasm} + } + if !testvalues.LocalhostClientFeatureReleases.IsSupported(chainAVersion) { + defaultAllowedClients = slices.DeleteFunc(defaultAllowedClients, func(s string) bool { return s == ibcexported.Localhost }) + } + s.Require().Equal(defaultAllowedClients, allowedClients) + }) + + allowedClient := ibcexported.Solomachine + t.Run("change the allowed client to only allow solomachine clients", func(t *testing.T) { + if testvalues.SelfParamsFeatureReleases.IsSupported(chainAVersion) { + authority, err := query.ModuleAccountAddress(ctx, govtypes.ModuleName, chainA) + s.Require().NoError(err) + s.Require().NotNil(authority) + + msg := clienttypes.NewMsgUpdateParams(authority.String(), clienttypes.NewParams(allowedClient)) + s.ExecuteAndPassGovV1Proposal(ctx, msg, chainA, chainAWallet) + } else { + value, err := cmtjson.Marshal([]string{allowedClient}) + s.Require().NoError(err) + changes := []paramsproposaltypes.ParamChange{ + paramsproposaltypes.NewParamChange(ibcexported.ModuleName, string(clienttypes.KeyAllowedClients), string(value)), + } + + proposal := paramsproposaltypes.NewParameterChangeProposal(ibctesting.Title, ibctesting.Description, changes) + s.ExecuteAndPassGovV1Beta1Proposal(ctx, chainA, chainAWallet, proposal) + } + }) + + t.Run("validate the param was successfully changed", func(t *testing.T) { + allowedClients := s.QueryAllowedClients(ctx, chainA) + s.Require().Equal([]string{allowedClient}, allowedClients) + }) + + t.Run("ensure querying non-allowed client's status returns Unauthorized Status", func(t *testing.T) { + status, err := query.ClientStatus(ctx, chainA, ibctesting.FirstClientID) + s.Require().NoError(err) + s.Require().Equal(ibcexported.Unauthorized.String(), status) + + status, err = query.ClientStatus(ctx, chainA, ibcexported.Localhost) + s.Require().NoError(err) + s.Require().Equal(ibcexported.Unauthorized.String(), status) + }) +} + +// extractChainPrivateKeys returns a slice of cmttypes.PrivValidator which hold the private keys for all validator +// nodes for a given chain. +func (s *ClientTestSuite) extractChainPrivateKeys(ctx context.Context, chain ibc.Chain) []cmttypes.PrivValidator { + testContainers, err := dockerutil.GetTestContainers(ctx, s.SuiteName(), s.DockerClient) + s.Require().NoError(err) + s.Require().NotEmpty(testContainers, "no test containers found") + + var filePvs []privval.FilePVKey + var pvs []cmttypes.PrivValidator + for _, container := range testContainers { + isNodeForDifferentChain := !strings.Contains(container.Names[0], chain.Config().ChainID) + isFullNode := strings.Contains(container.Names[0], fmt.Sprintf("%s-fn", chain.Config().ChainID)) + if isNodeForDifferentChain || isFullNode { + s.T().Logf("skipping container %s for chain %s", container.Names[0], chain.Config().ChainID) + continue + } + + validatorPrivKey := fmt.Sprintf("/var/cosmos-chain/%s/config/priv_validator_key.json", chain.Config().Name) + privKeyFileContents, err := dockerutil.GetFileContentsFromContainer(ctx, s.DockerClient, container.ID, validatorPrivKey) + s.Require().NoError(err) + + var filePV privval.FilePVKey + err = cmtjson.Unmarshal(privKeyFileContents, &filePV) + s.Require().NoError(err) + filePvs = append(filePvs, filePV) + } + + // We sort by address as GetValidatorSetByHeight also sorts by address. When iterating over them, the index + // will correspond to the correct ibcmock.PV. + sort.SliceStable(filePvs, func(i, j int) bool { + return filePvs[i].Address.String() < filePvs[j].Address.String() + }) + + for _, filePV := range filePvs { + pvs = append(pvs, cmttypes.NewMockPVWithParams( + filePV.PrivKey, false, false, + )) + } + + return pvs +} + +// createMaliciousTMHeader creates a header with the provided trusted height with an invalid app hash. +func createMaliciousTMHeader(chainID string, blockHeight int64, trustedHeight clienttypes.Height, timestamp time.Time, tmValSet, tmTrustedVals *cmttypes.ValidatorSet, signers []cmttypes.PrivValidator, oldHeader *cmtservice.Header) (*ibctm.Header, error) { + tmHeader := cmttypes.Header{ + Version: cmtprotoversion.Consensus{Block: cmtversion.BlockProtocol, App: 2}, + ChainID: chainID, + Height: blockHeight, + Time: timestamp, + LastBlockID: ibctesting.MakeBlockID(make([]byte, tmhash.Size), 10_000, make([]byte, tmhash.Size)), + LastCommitHash: oldHeader.GetLastCommitHash(), + ValidatorsHash: tmValSet.Hash(), + NextValidatorsHash: tmValSet.Hash(), + DataHash: tmhash.Sum([]byte(invalidHashValue)), + ConsensusHash: tmhash.Sum([]byte(invalidHashValue)), + AppHash: tmhash.Sum([]byte(invalidHashValue)), + LastResultsHash: tmhash.Sum([]byte(invalidHashValue)), + EvidenceHash: tmhash.Sum([]byte(invalidHashValue)), + ProposerAddress: tmValSet.Proposer.Address, //nolint:staticcheck + } + + hhash := tmHeader.Hash() + blockID := ibctesting.MakeBlockID(hhash, 3, tmhash.Sum([]byte(invalidHashValue))) + voteSet := cmttypes.NewVoteSet(chainID, blockHeight, 1, cmtproto.PrecommitType, tmValSet) + + extCommit, err := cmttypes.MakeExtCommit(blockID, blockHeight, 1, voteSet, signers, timestamp, false) + if err != nil { + return nil, err + } + + signedHeader := &cmtproto.SignedHeader{ + Header: tmHeader.ToProto(), + Commit: extCommit.ToCommit().ToProto(), + } + + valSet, err := tmValSet.ToProto() + if err != nil { + return nil, err + } + + trustedVals, err := tmTrustedVals.ToProto() + if err != nil { + return nil, err + } + + return &ibctm.Header{ + SignedHeader: signedHeader, + ValidatorSet: valSet, + TrustedHeight: trustedHeight, + TrustedValidators: trustedVals, + }, nil +} diff --git a/e2e/tests/core/03-connection/connection_test.go b/e2e/tests/core/03-connection/connection_test.go new file mode 100644 index 0000000..bed7203 --- /dev/null +++ b/e2e/tests/core/03-connection/connection_test.go @@ -0,0 +1,153 @@ +//go:build !test_e2e + +package connection + +import ( + "context" + "fmt" + "strconv" + "strings" + "testing" + "time" + + "github.com/cosmos/interchaintest/v10/ibc" + test "github.com/cosmos/interchaintest/v10/testutil" + testifysuite "github.com/stretchr/testify/suite" + + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + paramsproposaltypes "github.com/cosmos/cosmos-sdk/x/params/types/proposal" + + "github.com/cosmos/ibc-go/e2e/testsuite" + "github.com/cosmos/ibc-go/e2e/testsuite/query" + "github.com/cosmos/ibc-go/e2e/testvalues" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +// compatibility:from_version: v7.10.0 +func TestConnectionTestSuite(t *testing.T) { + testifysuite.Run(t, new(ConnectionTestSuite)) +} + +type ConnectionTestSuite struct { + testsuite.E2ETestSuite +} + +// SetupSuite sets up chains for the current test suite +func (s *ConnectionTestSuite) SetupSuite() { + s.SetupChains(context.TODO(), 2, nil) +} + +func (s *ConnectionTestSuite) CreateConnectionTestPath(testName string) (ibc.Relayer, ibc.ChannelOutput) { + return s.CreatePaths(ibc.DefaultClientOpts(), s.TransferChannelOptions(), testName), s.GetChainAChannelForTest(testName) +} + +// QueryMaxExpectedTimePerBlockParam queries the on-chain max expected time per block param for 03-connection +func (s *ConnectionTestSuite) QueryMaxExpectedTimePerBlockParam(ctx context.Context, chain ibc.Chain) uint64 { + if testvalues.SelfParamsFeatureReleases.IsSupported(chain.Config().Images[0].Version) { + res, err := query.GRPCQuery[connectiontypes.QueryConnectionParamsResponse](ctx, chain, &connectiontypes.QueryConnectionParamsRequest{}) + s.Require().NoError(err) + + return res.Params.MaxExpectedTimePerBlock + } + res, err := query.GRPCQuery[paramsproposaltypes.QueryParamsResponse](ctx, chain, ¶msproposaltypes.QueryParamsRequest{ + Subspace: ibcexported.ModuleName, + Key: string(connectiontypes.KeyMaxExpectedTimePerBlock), + }) + s.Require().NoError(err) + + // removing additional strings that are used for amino + delay := strings.ReplaceAll(res.Param.Value, "\"", "") + // convert to uint64 + uinttime, err := strconv.ParseUint(delay, 10, 64) + s.Require().NoError(err) + + return uinttime +} + +// TestMaxExpectedTimePerBlockParam tests changing the MaxExpectedTimePerBlock param using a governance proposal +func (s *ConnectionTestSuite) TestMaxExpectedTimePerBlockParam() { + t := s.T() + ctx := context.TODO() + testName := t.Name() + relayer, channelA := s.CreateConnectionTestPath(testName) + + chainA, chainB := s.GetChains() + chainAVersion := chainA.Config().Images[0].Version + + chainBDenom := chainB.Config().Denom + chainAIBCToken := testsuite.GetIBCToken(chainBDenom, channelA.PortID, channelA.ChannelID) + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainAAddress := chainAWallet.FormattedAddress() + + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + chainBAddress := chainBWallet.FormattedAddress() + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("ensure delay is set to the default of 30 seconds", func(t *testing.T) { + delay := s.QueryMaxExpectedTimePerBlockParam(ctx, chainA) + s.Require().Equal(uint64(connectiontypes.DefaultTimePerBlock), delay) + }) + + t.Run("change the delay to 60 seconds", func(t *testing.T) { + delay := uint64(1 * time.Minute) + if testvalues.SelfParamsFeatureReleases.IsSupported(chainAVersion) { + authority, err := query.ModuleAccountAddress(ctx, govtypes.ModuleName, chainA) + s.Require().NoError(err) + s.Require().NotNil(authority) + + msg := connectiontypes.NewMsgUpdateParams(authority.String(), connectiontypes.NewParams(delay)) + s.ExecuteAndPassGovV1Proposal(ctx, msg, chainA, chainAWallet) + } else { + changes := []paramsproposaltypes.ParamChange{ + paramsproposaltypes.NewParamChange(ibcexported.ModuleName, string(connectiontypes.KeyMaxExpectedTimePerBlock), fmt.Sprintf(`"%d"`, delay)), + } + + proposal := paramsproposaltypes.NewParameterChangeProposal(ibctesting.Title, ibctesting.Description, changes) + s.ExecuteAndPassGovV1Beta1Proposal(ctx, chainA, chainAWallet, proposal) + } + }) + + t.Run("validate the param was successfully changed", func(t *testing.T) { + expectedDelay := uint64(1 * time.Minute) + delay := s.QueryMaxExpectedTimePerBlockParam(ctx, chainA) + s.Require().Equal(expectedDelay, delay) + }) + + t.Run("ensure packets can be received, send from chainB to chainA", func(t *testing.T) { + t.Run("send tokens from chainB to chainA", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainB, chainBWallet, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, testvalues.DefaultTransferAmount(chainBDenom), chainBAddress, chainAAddress, s.GetTimeoutHeight(ctx, chainA), 0, "") + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainBNativeBalance(ctx, chainBWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, 1) + + actualBalance, err := query.Balance(ctx, chainA, chainAAddress, chainAIBCToken.IBCDenom()) + + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance.Int64()) + }) + + t.Run("stop relayer", func(t *testing.T) { + s.StopRelayer(ctx, relayer) + }) + }) +} diff --git a/e2e/tests/interchain_accounts/base_test.go b/e2e/tests/interchain_accounts/base_test.go new file mode 100644 index 0000000..7861f95 --- /dev/null +++ b/e2e/tests/interchain_accounts/base_test.go @@ -0,0 +1,542 @@ +//go:build !test_e2e + +package interchainaccounts + +import ( + "context" + "testing" + "time" + + "github.com/cosmos/gogoproto/proto" + "github.com/cosmos/interchaintest/v10" + "github.com/cosmos/interchaintest/v10/ibc" + test "github.com/cosmos/interchaintest/v10/testutil" + testifysuite "github.com/stretchr/testify/suite" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + + "github.com/cosmos/ibc-go/e2e/testsuite" + "github.com/cosmos/ibc-go/e2e/testsuite/query" + "github.com/cosmos/ibc-go/e2e/testvalues" + controllertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +// orderMapping is a mapping from channel ordering to the string representation of the ordering. +// the representation can be different depending on the relayer implementation. +var orderMapping = map[channeltypes.Order][]string{ + channeltypes.ORDERED: {channeltypes.ORDERED.String(), "Ordered"}, + channeltypes.UNORDERED: {channeltypes.UNORDERED.String(), "Unordered"}, +} + +// compatibility:from_version: v7.10.0 +func TestInterchainAccountsTestSuite(t *testing.T) { + testifysuite.Run(t, new(InterchainAccountsTestSuite)) +} + +type InterchainAccountsTestSuite struct { + testsuite.E2ETestSuite +} + +// SetupSuite sets up chains for the current test suite +func (s *InterchainAccountsTestSuite) SetupSuite() { + s.SetupChains(context.TODO(), 2, nil) +} + +// RegisterInterchainAccount will attempt to register an interchain account on the counterparty chain. +func (s *InterchainAccountsTestSuite) RegisterInterchainAccount(ctx context.Context, chain ibc.Chain, user ibc.Wallet, msgRegisterAccount *controllertypes.MsgRegisterInterchainAccount) { + txResp := s.BroadcastMessages(ctx, chain, user, msgRegisterAccount) + s.AssertTxSuccess(txResp) +} + +func (s *InterchainAccountsTestSuite) TestMsgSendTx_SuccessfulTransfer() { + s.testMsgSendTxSuccessfulTransfer(channeltypes.ORDERED) +} + +// compatibility:TestMsgSendTx_SuccessfulTransfer_UnorderedChannel:from_versions: v7.10.0,v8.7.0,v10.0.0 +func (s *InterchainAccountsTestSuite) TestMsgSendTx_SuccessfulTransfer_UnorderedChannel() { + s.testMsgSendTxSuccessfulTransfer(channeltypes.UNORDERED) +} + +func (s *InterchainAccountsTestSuite) testMsgSendTxSuccessfulTransfer(order channeltypes.Order) { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + relayer := s.CreateDefaultPaths(testName) + + chainA, chainB := s.GetChains() + + // setup 2 accounts: controller account on chain A, a second chain B account. + // host account will be created when the ICA is registered + controllerAccount := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + controllerAddress := controllerAccount.FormattedAddress() + chainBAccount := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + var hostAccount string + + t.Run("broadcast MsgRegisterInterchainAccount", func(t *testing.T) { + // explicitly set the version string because we don't want to use incentivized channels. + version := icatypes.NewDefaultMetadataString(ibctesting.FirstConnectionID, ibctesting.FirstConnectionID) + msgRegisterAccount := controllertypes.NewMsgRegisterInterchainAccount(ibctesting.FirstConnectionID, controllerAddress, version, order) + + txResp := s.BroadcastMessages(ctx, chainA, controllerAccount, msgRegisterAccount) + s.AssertTxSuccess(txResp) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("verify interchain account", func(t *testing.T) { + var err error + hostAccount, err = query.InterchainAccount(ctx, chainA, controllerAddress, ibctesting.FirstConnectionID) + s.Require().NoError(err) + s.Require().NotEmpty(hostAccount) + + channels, err := relayer.GetChannels(ctx, s.GetRelayerExecReporter(), chainA.Config().ChainID) + s.Require().NoError(err) + s.Require().Equal(len(channels), 2) + icaChannel := channels[0] + + s.Require().Contains(orderMapping[order], icaChannel.Ordering) + }) + + t.Run("interchain account executes a bank transfer on behalf of the corresponding owner account", func(t *testing.T) { + t.Run("fund interchain account wallet", func(t *testing.T) { + // fund the host account so it has some $$ to send + err := chainB.SendFunds(ctx, interchaintest.FaucetAccountKeyName, ibc.WalletAmount{ + Address: hostAccount, + Amount: sdkmath.NewInt(testvalues.StartingTokenAmount), + Denom: chainB.Config().Denom, + }) + s.Require().NoError(err) + }) + + t.Run("broadcast MsgSendTx", func(t *testing.T) { + // assemble bank transfer message from host account to user account on host chain + msgSend := &banktypes.MsgSend{ + FromAddress: hostAccount, + ToAddress: chainBAccount.FormattedAddress(), + Amount: sdk.NewCoins(testvalues.DefaultTransferAmount(chainB.Config().Denom)), + } + + cdc := testsuite.Codec() + bz, err := icatypes.SerializeCosmosTx(cdc, []proto.Message{msgSend}, icatypes.EncodingProtobuf) + s.Require().NoError(err) + + packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: bz, + Memo: "e2e", + } + + msgSendTx := controllertypes.NewMsgSendTx(controllerAddress, ibctesting.FirstConnectionID, uint64(time.Hour.Nanoseconds()), packetData) + + resp := s.BroadcastMessages( + ctx, + chainA, + controllerAccount, + msgSendTx, + ) + + s.AssertTxSuccess(resp) + + s.Require().NoError(test.WaitForBlocks(ctx, 10, chainA, chainB)) + }) + + t.Run("verify tokens transferred", func(t *testing.T) { + balance, err := query.Balance(ctx, chainB, chainBAccount.FormattedAddress(), chainB.Config().Denom) + s.Require().NoError(err) + + _, err = query.Balance(ctx, chainB, hostAccount, chainB.Config().Denom) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + testvalues.StartingTokenAmount + s.Require().Equal(expected, balance.Int64()) + }) + }) +} + +func (s *InterchainAccountsTestSuite) TestMsgSendTx_FailedTransfer_InsufficientFunds() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + relayer := s.CreateDefaultPaths(testName) + + chainA, chainB := s.GetChains() + + // setup 2 accounts: controller account on chain A, a second chain B account. + // host account will be created when the ICA is registered + controllerAccount := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + controllerAddress := controllerAccount.FormattedAddress() + chainBAccount := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + var hostAccount string + + t.Run("broadcast MsgRegisterInterchainAccount", func(t *testing.T) { + // explicitly set the version string because we don't want to use incentivized channels. + version := icatypes.NewDefaultMetadataString(ibctesting.FirstConnectionID, ibctesting.FirstConnectionID) + msgRegisterAccount := controllertypes.NewMsgRegisterInterchainAccount(ibctesting.FirstConnectionID, controllerAddress, version, channeltypes.ORDERED) + + txResp := s.BroadcastMessages(ctx, chainA, controllerAccount, msgRegisterAccount) + s.AssertTxSuccess(txResp) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("verify interchain account", func(t *testing.T) { + var err error + hostAccount, err = query.InterchainAccount(ctx, chainA, controllerAddress, ibctesting.FirstConnectionID) + s.Require().NoError(err) + s.Require().NotEmpty(hostAccount) + + channels, err := relayer.GetChannels(ctx, s.GetRelayerExecReporter(), chainA.Config().ChainID) + s.Require().NoError(err) + s.Require().Equal(len(channels), 2) + }) + + t.Run("fail to execute bank transfer over ICA", func(t *testing.T) { + t.Run("verify empty host wallet", func(t *testing.T) { + hostAccountBalance, err := query.Balance(ctx, chainB, hostAccount, chainB.Config().Denom) + + s.Require().NoError(err) + s.Require().Zero(hostAccountBalance.Int64()) + }) + + t.Run("broadcast MsgSendTx", func(t *testing.T) { + // assemble bank transfer message from host account to user account on host chain + msgSend := &banktypes.MsgSend{ + FromAddress: hostAccount, + ToAddress: chainBAccount.FormattedAddress(), + Amount: sdk.NewCoins(testvalues.DefaultTransferAmount(chainB.Config().Denom)), + } + + cdc := testsuite.Codec() + bz, err := icatypes.SerializeCosmosTx(cdc, []proto.Message{msgSend}, icatypes.EncodingProtobuf) + s.Require().NoError(err) + + packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: bz, + Memo: "e2e", + } + + msgSendTx := controllertypes.NewMsgSendTx(controllerAddress, ibctesting.FirstConnectionID, uint64(time.Hour.Nanoseconds()), packetData) + + txResp := s.BroadcastMessages( + ctx, + chainA, + controllerAccount, + msgSendTx, + ) + + s.AssertTxSuccess(txResp) + + s.Require().NoError(test.WaitForBlocks(ctx, 10, chainA, chainB)) + }) + + t.Run("verify balance is the same", func(t *testing.T) { + balance, err := query.Balance(ctx, chainB, chainBAccount.FormattedAddress(), chainB.Config().Denom) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount + s.Require().Equal(expected, balance.Int64()) + }) + }) +} + +func (s *InterchainAccountsTestSuite) TestMsgSendTx_SuccessfulTransfer_AfterReopeningICA() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + relayer := s.CreateDefaultPaths(testName) + + chainA, chainB := s.GetChains() + + // setup 2 accounts: controller account on chain A, a second chain B account. + // host account will be created when the ICA is registered + controllerAccount := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + controllerAddress := controllerAccount.FormattedAddress() + chainBAccount := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + + var ( + portID string + hostAccount string + + initialChannelID = "channel-1" + channelIDAfterReopening = "channel-2" + ) + + t.Run("register interchain account", func(t *testing.T) { + var err error + // explicitly set the version string because we don't want to use incentivized channels. + version := icatypes.NewDefaultMetadataString(ibctesting.FirstConnectionID, ibctesting.FirstConnectionID) + msgRegisterInterchainAccount := controllertypes.NewMsgRegisterInterchainAccount(ibctesting.FirstConnectionID, controllerAddress, version, channeltypes.ORDERED) + s.RegisterInterchainAccount(ctx, chainA, controllerAccount, msgRegisterInterchainAccount) + portID, err = icatypes.NewControllerPortID(controllerAddress) + s.Require().NoError(err) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("verify interchain account", func(t *testing.T) { + var err error + hostAccount, err = query.InterchainAccount(ctx, chainA, controllerAddress, ibctesting.FirstConnectionID) + s.Require().NoError(err) + s.Require().NotEmpty(hostAccount) + + _, err = query.Channel(ctx, chainA, portID, initialChannelID) + s.Require().NoError(err) + }) + + // stop the relayer to let the submit tx message time out + t.Run("stop relayer", func(t *testing.T) { + s.StopRelayer(ctx, relayer) + }) + + t.Run("submit tx message with bank transfer message times out", func(t *testing.T) { + t.Run("fund interchain account wallet", func(t *testing.T) { + // fund the host account so it has some $$ to send + err := chainB.SendFunds(ctx, interchaintest.FaucetAccountKeyName, ibc.WalletAmount{ + Address: hostAccount, + Amount: sdkmath.NewInt(testvalues.StartingTokenAmount), + Denom: chainB.Config().Denom, + }) + s.Require().NoError(err) + }) + + t.Run("broadcast MsgSendTx", func(t *testing.T) { + // assemble bank transfer message from host account to user account on host chain + msgSend := &banktypes.MsgSend{ + FromAddress: hostAccount, + ToAddress: chainBAccount.FormattedAddress(), + Amount: sdk.NewCoins(testvalues.DefaultTransferAmount(chainB.Config().Denom)), + } + + cdc := testsuite.Codec() + + bz, err := icatypes.SerializeCosmosTx(cdc, []proto.Message{msgSend}, icatypes.EncodingProtobuf) + s.Require().NoError(err) + + packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: bz, + Memo: "e2e", + } + + msgSendTx := controllertypes.NewMsgSendTx(controllerAddress, ibctesting.FirstConnectionID, uint64(1), packetData) + + resp := s.BroadcastMessages( + ctx, + chainA, + controllerAccount, + msgSendTx, + ) + + s.AssertTxSuccess(resp) + + // this sleep is to allow the packet to timeout + time.Sleep(1 * time.Second) + }) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("verify channel is closed due to timeout on ordered channel", func(t *testing.T) { + channel, err := query.Channel(ctx, chainA, portID, initialChannelID) + s.Require().NoError(err) + + s.Require().Equal(channeltypes.CLOSED, channel.State, "the channel was not in an expected state") + }) + + t.Run("verify tokens not transferred", func(t *testing.T) { + balance, err := query.Balance(ctx, chainB, chainBAccount.FormattedAddress(), chainB.Config().Denom) + s.Require().NoError(err) + + _, err = query.Balance(ctx, chainB, hostAccount, chainB.Config().Denom) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount + s.Require().Equal(expected, balance.Int64()) + }) + + // re-register interchain account to reopen the channel now that it has been closed due to timeout + // on an ordered channel + t.Run("register interchain account", func(t *testing.T) { + // explicitly set the version string because we don't want to use incentivized channels. + version := icatypes.NewDefaultMetadataString(ibctesting.FirstConnectionID, ibctesting.FirstConnectionID) + msgRegisterInterchainAccount := controllertypes.NewMsgRegisterInterchainAccount(ibctesting.FirstConnectionID, controllerAddress, version, channeltypes.ORDERED) + s.RegisterInterchainAccount(ctx, chainA, controllerAccount, msgRegisterInterchainAccount) + + s.Require().NoError(test.WaitForBlocks(ctx, 10, chainA, chainB)) + }) + + t.Run("verify new channel is now open and interchain account has been reregistered with the same portID", func(t *testing.T) { + channel, err := query.Channel(ctx, chainA, portID, channelIDAfterReopening) + s.Require().NoError(err) + + s.Require().Equal(channeltypes.OPEN, channel.State, "the channel was not in an expected state") + }) + + t.Run("broadcast MsgSendTx", func(t *testing.T) { + // assemble bank transfer message from host account to user account on host chain + msgSend := &banktypes.MsgSend{ + FromAddress: hostAccount, + ToAddress: chainBAccount.FormattedAddress(), + Amount: sdk.NewCoins(testvalues.DefaultTransferAmount(chainB.Config().Denom)), + } + + cdc := testsuite.Codec() + + bz, err := icatypes.SerializeCosmosTx(cdc, []proto.Message{msgSend}, icatypes.EncodingProtobuf) + s.Require().NoError(err) + + packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: bz, + Memo: "e2e", + } + + msgSendTx := controllertypes.NewMsgSendTx(controllerAddress, ibctesting.FirstConnectionID, uint64(5*time.Minute), packetData) + + resp := s.BroadcastMessages( + ctx, + chainA, + controllerAccount, + msgSendTx, + ) + + s.AssertTxSuccess(resp) + + // time for the packet to be relayed + s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA, chainB)) + }) + + t.Run("verify tokens transferred", func(t *testing.T) { + balance, err := query.Balance(ctx, chainB, chainBAccount.FormattedAddress(), chainB.Config().Denom) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + testvalues.StartingTokenAmount + s.Require().Equal(expected, balance.Int64()) + }) +} + +func (s *InterchainAccountsTestSuite) TestMsgSendTx_SuccessfulSubmitGovProposal_OrderedChannel() { + s.testMsgSendTxSuccessfulGovProposal(channeltypes.ORDERED) +} + +// compatibility:TestMsgSendTx_SuccessfulSubmitGovProposal_UnorderedChannel:from_versions: v7.10.0,v8.7.0,v10.0.0 +func (s *InterchainAccountsTestSuite) TestMsgSendTx_SuccessfulSubmitGovProposal_UnorderedChannel() { + s.testMsgSendTxSuccessfulGovProposal(channeltypes.UNORDERED) +} + +func (s *InterchainAccountsTestSuite) testMsgSendTxSuccessfulGovProposal(order channeltypes.Order) { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + relayer := s.CreateDefaultPaths(testName) + + chainA, chainB := s.GetChains() + + // setup 2 accounts: controller account on chain A, a second chain B account. + // host account will be created when the ICA is registered + controllerAccount := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + controllerAddress := controllerAccount.FormattedAddress() + var hostAccount string + + t.Run("broadcast MsgRegisterInterchainAccount", func(t *testing.T) { + // explicitly set the version string because we don't want to use incentivized channels. + version := icatypes.NewDefaultMetadataString(ibctesting.FirstConnectionID, ibctesting.FirstConnectionID) + msgRegisterAccount := controllertypes.NewMsgRegisterInterchainAccount(ibctesting.FirstConnectionID, controllerAddress, version, order) + + txResp := s.BroadcastMessages(ctx, chainA, controllerAccount, msgRegisterAccount) + s.AssertTxSuccess(txResp) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("verify interchain account", func(t *testing.T) { + var err error + hostAccount, err = query.InterchainAccount(ctx, chainA, controllerAddress, ibctesting.FirstConnectionID) + s.Require().NoError(err) + s.Require().NotEmpty(hostAccount) + + channels, err := relayer.GetChannels(ctx, s.GetRelayerExecReporter(), chainA.Config().ChainID) + s.Require().NoError(err) + s.Require().Equal(len(channels), 2) + icaChannel := channels[0] + + s.Require().Contains(orderMapping[order], icaChannel.Ordering) + }) + + t.Run("interchain account submits a governance proposal on behalf of the corresponding owner account", func(t *testing.T) { + t.Run("fund interchain account wallet", func(t *testing.T) { + // fund the host account so it has some $$ to send + err := chainB.SendFunds(ctx, interchaintest.FaucetAccountKeyName, ibc.WalletAmount{ + Address: hostAccount, + Amount: sdkmath.NewInt(testvalues.StartingTokenAmount), + Denom: chainB.Config().Denom, + }) + s.Require().NoError(err) + }) + + t.Run("broadcast MsgSendTx for MsgSubmitProposal", func(t *testing.T) { + govModuleAddress, err := query.ModuleAccountAddress(ctx, govtypes.ModuleName, chainB) + s.Require().NoError(err) + s.Require().NotNil(govModuleAddress) + + msg, err := govv1.NewMsgSubmitProposal( + []sdk.Msg{}, + sdk.NewCoins(sdk.NewCoin(chainB.Config().Denom, sdkmath.NewInt(10_000_000))), + hostAccount, "e2e", "e2e", "e2e", false, + ) + s.Require().NoError(err) + + cdc := testsuite.Codec() + bz, err := icatypes.SerializeCosmosTx(cdc, []proto.Message{msg}, icatypes.EncodingProtobuf) + s.Require().NoError(err) + + packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: bz, + Memo: "e2e", + } + + msgSendTx := controllertypes.NewMsgSendTx(controllerAddress, ibctesting.FirstConnectionID, uint64(time.Hour.Nanoseconds()), packetData) + resp := s.BroadcastMessages( + ctx, + chainA, + controllerAccount, + msgSendTx, + ) + + s.AssertTxSuccess(resp) + + s.Require().NoError(test.WaitForBlocks(ctx, 10, chainA, chainB)) + }) + + t.Run("verify proposal included", func(t *testing.T) { + proposalResp, err := query.GRPCQuery[govv1.QueryProposalResponse](ctx, chainB, &govv1.QueryProposalRequest{ProposalId: 1}) + s.Require().NoError(err) + + s.Require().Equal("e2e", proposalResp.Proposal.Title) + }) + }) +} diff --git a/e2e/tests/interchain_accounts/gov_test.go b/e2e/tests/interchain_accounts/gov_test.go new file mode 100644 index 0000000..cb70722 --- /dev/null +++ b/e2e/tests/interchain_accounts/gov_test.go @@ -0,0 +1,131 @@ +//go:build !test_e2e + +package interchainaccounts + +import ( + "context" + "testing" + "time" + + "github.com/cosmos/gogoproto/proto" + "github.com/cosmos/interchaintest/v10" + "github.com/cosmos/interchaintest/v10/ibc" + test "github.com/cosmos/interchaintest/v10/testutil" + testifysuite "github.com/stretchr/testify/suite" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + "github.com/cosmos/ibc-go/e2e/testsuite" + "github.com/cosmos/ibc-go/e2e/testsuite/query" + "github.com/cosmos/ibc-go/e2e/testvalues" + controllertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +// compatibility:from_version: v7.10.0 +func TestInterchainAccountsGovTestSuite(t *testing.T) { + testifysuite.Run(t, new(InterchainAccountsGovTestSuite)) +} + +type InterchainAccountsGovTestSuite struct { + testsuite.E2ETestSuite +} + +// SetupSuite sets up chains for the current test suite +func (s *InterchainAccountsGovTestSuite) SetupSuite() { + s.SetupChains(context.TODO(), 2, nil) +} + +func (s *InterchainAccountsGovTestSuite) TestInterchainAccountsGovIntegration() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + relayer := s.CreateDefaultPaths(testName) + + chainA, chainB := s.GetChains() + controllerAccount := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + + chainBAccount := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + chainBAddress := chainBAccount.FormattedAddress() + + govModuleAddress, err := query.ModuleAccountAddress(ctx, govtypes.ModuleName, chainA) + s.Require().NoError(err) + s.Require().NotNil(govModuleAddress) + + t.Run("execute proposal for MsgRegisterInterchainAccount", func(t *testing.T) { + version := icatypes.NewDefaultMetadataString(ibctesting.FirstConnectionID, ibctesting.FirstConnectionID) + msgRegisterAccount := controllertypes.NewMsgRegisterInterchainAccount(ibctesting.FirstConnectionID, govModuleAddress.String(), version, channeltypes.ORDERED) + s.ExecuteAndPassGovV1Proposal(ctx, msgRegisterAccount, chainA, controllerAccount) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 10, chainA, chainB)) + + var interchainAccAddr string + t.Run("verify interchain account registration success", func(t *testing.T) { + var err error + interchainAccAddr, err = query.InterchainAccount(ctx, chainA, govModuleAddress.String(), ibctesting.FirstConnectionID) + s.Require().NoError(err) + s.Require().NotZero(len(interchainAccAddr)) + + channels, err := relayer.GetChannels(ctx, s.GetRelayerExecReporter(), chainA.Config().ChainID) + s.Require().NoError(err) + s.Require().Equal(len(channels), 2) + }) + + t.Run("interchain account executes a bank transfer on behalf of the corresponding owner account", func(t *testing.T) { + t.Run("fund interchain account wallet", func(t *testing.T) { + // fund the host account, so it has some $$ to send + err := chainB.SendFunds(ctx, interchaintest.FaucetAccountKeyName, ibc.WalletAmount{ + Address: interchainAccAddr, + Amount: sdkmath.NewInt(testvalues.StartingTokenAmount), + Denom: chainB.Config().Denom, + }) + s.Require().NoError(err) + }) + + t.Run("execute proposal for MsgSendTx", func(t *testing.T) { + msgBankSend := &banktypes.MsgSend{ + FromAddress: interchainAccAddr, + ToAddress: chainBAddress, + Amount: sdk.NewCoins(testvalues.DefaultTransferAmount(chainB.Config().Denom)), + } + + cdc := testsuite.Codec() + bz, err := icatypes.SerializeCosmosTx(cdc, []proto.Message{msgBankSend}, icatypes.EncodingProtobuf) + s.Require().NoError(err) + + packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: bz, + Memo: "e2e", + } + + msgSendTx := controllertypes.NewMsgSendTx(govModuleAddress.String(), ibctesting.FirstConnectionID, uint64(time.Hour.Nanoseconds()), packetData) + s.ExecuteAndPassGovV1Proposal(ctx, msgSendTx, chainA, controllerAccount) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 10, chainA, chainB)) // wait for the ica tx to be relayed + + t.Run("verify tokens transferred", func(t *testing.T) { + balance, err := query.Balance(ctx, chainB, chainBAccount.FormattedAddress(), chainB.Config().Denom) + s.Require().NoError(err) + + _, err = query.Balance(ctx, chainB, interchainAccAddr, chainB.Config().Denom) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + testvalues.StartingTokenAmount + s.Require().Equal(expected, balance.Int64()) + }) + }) +} diff --git a/e2e/tests/interchain_accounts/groups_test.go b/e2e/tests/interchain_accounts/groups_test.go new file mode 100644 index 0000000..f882316 --- /dev/null +++ b/e2e/tests/interchain_accounts/groups_test.go @@ -0,0 +1,221 @@ +//go:build !test_e2e + +package interchainaccounts + +import ( + "context" + "testing" + "time" + + "github.com/cosmos/gogoproto/proto" + interchaintest "github.com/cosmos/interchaintest/v10" + "github.com/cosmos/interchaintest/v10/ibc" + test "github.com/cosmos/interchaintest/v10/testutil" + testifysuite "github.com/stretchr/testify/suite" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + grouptypes "github.com/cosmos/cosmos-sdk/x/group" + + "github.com/cosmos/ibc-go/e2e/testsuite" + "github.com/cosmos/ibc-go/e2e/testsuite/query" + "github.com/cosmos/ibc-go/e2e/testvalues" + controllertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +const ( + // DefaultGroupMemberWeight is the members voting weight. + // A group members weight is used in the sum of `YES` votes required to meet a decision policy threshold. + DefaultGroupMemberWeight = "1" + + // DefaultGroupThreshold is the minimum weighted sum of `YES` votes that must be met or + // exceeded for a proposal to succeed. + DefaultGroupThreshold = "1" + + // DefaultMetadata defines a reusable metadata string for testing purposes + DefaultMetadata = "custom metadata" + + // DefaultMinExecutionPeriod is the minimum duration after the proposal submission + // where members can start sending MsgExec. This means that the window for + // sending a MsgExec transaction is: + // `[ submission + min_execution_period ; submission + voting_period + max_execution_period]` + // where max_execution_period is an app-specific config, defined in the keeper. + // If not set, min_execution_period will default to 0. + DefaultMinExecutionPeriod = time.Duration(0) + + // DefaultVotingPeriod is the duration from submission of a proposal to the end of voting period + // Within this times votes can be submitted with MsgVote. + DefaultVotingPeriod = time.Minute + + // InitialGroupID is the first group ID generated by x/group + InitialGroupID = 1 + + // InitialProposalID is the first group proposal ID generated by x/group + InitialProposalID = 1 +) + +// compatibility:from_version: v7.10.0 +func TestInterchainAccountsGroupsTestSuite(t *testing.T) { + testifysuite.Run(t, new(InterchainAccountsGroupsTestSuite)) +} + +type InterchainAccountsGroupsTestSuite struct { + testsuite.E2ETestSuite +} + +// SetupSuite sets up chains for the current test suite +func (s *InterchainAccountsGroupsTestSuite) SetupSuite() { + s.SetupChains(context.TODO(), 2, nil) +} + +func (s *InterchainAccountsGroupsTestSuite) QueryGroupPolicyAddress(ctx context.Context, chain ibc.Chain) string { + res, err := query.GRPCQuery[grouptypes.QueryGroupPoliciesByGroupResponse](ctx, chain, &grouptypes.QueryGroupPoliciesByGroupRequest{ + GroupId: InitialGroupID, // always use the initial group id + }) + s.Require().NoError(err) + + return res.GroupPolicies[0].Address +} + +func (s *InterchainAccountsGroupsTestSuite) TestInterchainAccountsGroupsIntegration() { + t := s.T() + ctx := context.TODO() + + var ( + groupPolicyAddr string + interchainAccAddr string + err error + ) + + testName := t.Name() + relayer := s.CreateDefaultPaths(testName) + + chainA, chainB := s.GetChains() + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainAAddress := chainAWallet.FormattedAddress() + + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + chainBAddress := chainBWallet.FormattedAddress() + + t.Run("create group with new threshold decision policy", func(t *testing.T) { + members := []grouptypes.MemberRequest{ + { + Address: chainAAddress, + Weight: DefaultGroupMemberWeight, + }, + } + + decisionPolicy := grouptypes.NewThresholdDecisionPolicy(DefaultGroupThreshold, DefaultVotingPeriod, DefaultMinExecutionPeriod) + msgCreateGroupWithPolicy, err := grouptypes.NewMsgCreateGroupWithPolicy(chainAAddress, members, DefaultMetadata, DefaultMetadata, true, decisionPolicy) + s.Require().NoError(err) + + txResp := s.BroadcastMessages(ctx, chainA, chainAWallet, msgCreateGroupWithPolicy) + s.AssertTxSuccess(txResp) + }) + + t.Run("submit proposal for MsgRegisterInterchainAccount", func(t *testing.T) { + groupPolicyAddr = s.QueryGroupPolicyAddress(ctx, chainA) + msgRegisterAccount := controllertypes.NewMsgRegisterInterchainAccount(ibctesting.FirstConnectionID, groupPolicyAddr, icatypes.NewDefaultMetadataString(ibctesting.FirstConnectionID, ibctesting.FirstConnectionID), channeltypes.ORDERED) + + msgSubmitProposal, err := grouptypes.NewMsgSubmitProposal(groupPolicyAddr, []string{chainAAddress}, []sdk.Msg{msgRegisterAccount}, DefaultMetadata, grouptypes.Exec_EXEC_UNSPECIFIED, "e2e groups proposal: for MsgRegisterInterchainAccount", "e2e groups proposal: for MsgRegisterInterchainAccount") + s.Require().NoError(err) + + txResp := s.BroadcastMessages(ctx, chainA, chainAWallet, msgSubmitProposal) + s.AssertTxSuccess(txResp) + }) + + t.Run("vote and exec proposal", func(t *testing.T) { + msgVote := &grouptypes.MsgVote{ + ProposalId: InitialProposalID, + Voter: chainAAddress, + Option: grouptypes.VOTE_OPTION_YES, + Exec: grouptypes.Exec_EXEC_TRY, + } + + txResp := s.BroadcastMessages(ctx, chainA, chainAWallet, msgVote) + s.AssertTxSuccess(txResp) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("verify interchain account registration success", func(t *testing.T) { + interchainAccAddr, err = query.InterchainAccount(ctx, chainA, groupPolicyAddr, ibctesting.FirstConnectionID) + s.Require().NotEmpty(interchainAccAddr) + s.Require().NoError(err) + + channels, err := relayer.GetChannels(ctx, s.GetRelayerExecReporter(), chainA.Config().ChainID) + s.Require().NoError(err) + s.Require().Equal(len(channels), 2) // 1 transfer (created by default), 1 interchain-accounts + }) + + t.Run("fund interchain account wallet", func(t *testing.T) { + err := chainB.SendFunds(ctx, interchaintest.FaucetAccountKeyName, ibc.WalletAmount{ + Address: interchainAccAddr, + Amount: sdkmath.NewInt(testvalues.StartingTokenAmount), + Denom: chainB.Config().Denom, + }) + s.Require().NoError(err) + }) + + t.Run("submit proposal for MsgSendTx", func(t *testing.T) { + msgBankSend := &banktypes.MsgSend{ + FromAddress: interchainAccAddr, + ToAddress: chainBAddress, + Amount: sdk.NewCoins(testvalues.DefaultTransferAmount(chainB.Config().Denom)), + } + + cdc := testsuite.Codec() + + bz, err := icatypes.SerializeCosmosTx(cdc, []proto.Message{msgBankSend}, icatypes.EncodingProtobuf) + s.Require().NoError(err) + + packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: bz, + Memo: "e2e", + } + + msgSubmitTx := controllertypes.NewMsgSendTx(groupPolicyAddr, ibctesting.FirstConnectionID, uint64(time.Hour.Nanoseconds()), packetData) + msgSubmitProposal, err := grouptypes.NewMsgSubmitProposal(groupPolicyAddr, []string{chainAAddress}, []sdk.Msg{msgSubmitTx}, DefaultMetadata, grouptypes.Exec_EXEC_UNSPECIFIED, "e2e groups proposal: for MsgRegisterInterchainAccount", "e2e groups proposal: for MsgRegisterInterchainAccount") + s.Require().NoError(err) + + txResp := s.BroadcastMessages(ctx, chainA, chainAWallet, msgSubmitProposal) + s.AssertTxSuccess(txResp) + }) + + t.Run("vote and exec proposal", func(t *testing.T) { + msgVote := &grouptypes.MsgVote{ + ProposalId: InitialProposalID + 1, + Voter: chainAAddress, + Option: grouptypes.VOTE_OPTION_YES, + Exec: grouptypes.Exec_EXEC_TRY, + } + + txResp := s.BroadcastMessages(ctx, chainA, chainAWallet, msgVote) + s.AssertTxSuccess(txResp) + }) + + t.Run("verify tokens transferred", func(t *testing.T) { + s.Require().NoError(test.WaitForBlocks(ctx, 10, chainA, chainB), "failed to wait for blocks") + balance, err := query.Balance(ctx, chainB, chainBAddress, chainB.Config().Denom) + + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + testvalues.StartingTokenAmount + s.Require().Equal(expected, balance.Int64()) + + balance, err = query.Balance(ctx, chainB, interchainAccAddr, chainB.Config().Denom) + s.Require().NoError(err) + + expected = testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, balance.Int64()) + }) +} diff --git a/e2e/tests/interchain_accounts/localhost_test.go b/e2e/tests/interchain_accounts/localhost_test.go new file mode 100644 index 0000000..8c7451e --- /dev/null +++ b/e2e/tests/interchain_accounts/localhost_test.go @@ -0,0 +1,490 @@ +//go:build !test_e2e + +package interchainaccounts + +import ( + "context" + "testing" + "time" + + "github.com/cosmos/gogoproto/proto" + "github.com/cosmos/interchaintest/v10" + "github.com/cosmos/interchaintest/v10/ibc" + test "github.com/cosmos/interchaintest/v10/testutil" + testifysuite "github.com/stretchr/testify/suite" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + "github.com/cosmos/ibc-go/e2e/testsuite" + "github.com/cosmos/ibc-go/e2e/testsuite/query" + "github.com/cosmos/ibc-go/e2e/testvalues" + controllertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + localhost "github.com/cosmos/ibc-go/v10/modules/light-clients/09-localhost" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func TestInterchainAccountsLocalhostTestSuite(t *testing.T) { + testifysuite.Run(t, new(LocalhostInterchainAccountsTestSuite)) +} + +// compatibility:from_version: v7.10.0 +type LocalhostInterchainAccountsTestSuite struct { + testsuite.E2ETestSuite +} + +// SetupSuite sets up chains for the current test suite +func (s *LocalhostInterchainAccountsTestSuite) SetupSuite() { + s.SetupChains(context.TODO(), 1, nil) +} + +// compatibility:TestInterchainAccounts_Localhost:from_versions: v7.10.0,v8.7.0,v10.0.0 +func (s *LocalhostInterchainAccountsTestSuite) TestInterchainAccounts_Localhost() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + s.CreateDefaultPaths(testName) + + chains := s.GetAllChains() + chainA := chains[0] + + chainADenom := chainA.Config().Denom + + rlyWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + userAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + userBWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + + var ( + msgChanOpenInitRes channeltypes.MsgChannelOpenInitResponse + msgChanOpenTryRes channeltypes.MsgChannelOpenTryResponse + ack []byte + packet channeltypes.Packet + ) + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA), "failed to wait for blocks") + + version := icatypes.NewDefaultMetadataString(exported.LocalhostConnectionID, exported.LocalhostConnectionID) + controllerPortID, err := icatypes.NewControllerPortID(userAWallet.FormattedAddress()) + s.Require().NoError(err) + + t.Run("channel open init localhost - broadcast MsgRegisterInterchainAccount", func(t *testing.T) { + msgRegisterAccount := controllertypes.NewMsgRegisterInterchainAccount(exported.LocalhostConnectionID, userAWallet.FormattedAddress(), version, channeltypes.ORDERED) + + txResp := s.BroadcastMessages(ctx, chainA, userAWallet, msgRegisterAccount) + s.AssertTxSuccess(txResp) + + s.Require().NoError(testsuite.UnmarshalMsgResponses(txResp, &msgChanOpenInitRes)) + }) + + t.Run("channel open try localhost", func(t *testing.T) { + msgChanOpenTry := channeltypes.NewMsgChannelOpenTry( + icatypes.HostPortID, icatypes.Version, + channeltypes.ORDERED, []string{exported.LocalhostConnectionID}, + controllerPortID, msgChanOpenInitRes.ChannelId, + version, localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(), + ) + + txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenTry) + s.AssertTxSuccess(txResp) + + s.Require().NoError(testsuite.UnmarshalMsgResponses(txResp, &msgChanOpenTryRes)) + }) + + t.Run("channel open ack localhost", func(t *testing.T) { + msgChanOpenAck := channeltypes.NewMsgChannelOpenAck( + controllerPortID, msgChanOpenInitRes.ChannelId, + msgChanOpenTryRes.ChannelId, msgChanOpenTryRes.Version, + localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(), + ) + + txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenAck) + s.AssertTxSuccess(txResp) + }) + + t.Run("channel open confirm localhost", func(t *testing.T) { + msgChanOpenConfirm := channeltypes.NewMsgChannelOpenConfirm( + icatypes.HostPortID, msgChanOpenTryRes.ChannelId, + localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(), + ) + + txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenConfirm) + s.AssertTxSuccess(txResp) + }) + + t.Run("query localhost interchain accounts channel ends", func(t *testing.T) { + channelEndA, err := query.Channel(ctx, chainA, controllerPortID, msgChanOpenInitRes.ChannelId) + s.Require().NoError(err) + s.Require().NotNil(channelEndA) + + channelEndB, err := query.Channel(ctx, chainA, icatypes.HostPortID, msgChanOpenTryRes.ChannelId) + s.Require().NoError(err) + s.Require().NotNil(channelEndB) + + s.Require().Equal(channelEndA.ConnectionHops, channelEndB.ConnectionHops) + }) + + t.Run("verify interchain account registration and deposit funds", func(t *testing.T) { + interchainAccAddress, err := query.InterchainAccount(ctx, chainA, userAWallet.FormattedAddress(), exported.LocalhostConnectionID) + s.Require().NoError(err) + s.Require().NotEmpty(interchainAccAddress) + + walletAmount := ibc.WalletAmount{ + Address: interchainAccAddress, + Amount: sdkmath.NewInt(testvalues.StartingTokenAmount), + Denom: chainADenom, + } + + s.Require().NoError(chainA.SendFunds(ctx, interchaintest.FaucetAccountKeyName, walletAmount)) + }) + + t.Run("send packet localhost interchain accounts", func(t *testing.T) { + interchainAccAddress, err := query.InterchainAccount(ctx, chainA, userAWallet.FormattedAddress(), exported.LocalhostConnectionID) + s.Require().NoError(err) + s.Require().NotEmpty(interchainAccAddress) + + msgSend := &banktypes.MsgSend{ + FromAddress: interchainAccAddress, + ToAddress: userBWallet.FormattedAddress(), + Amount: sdk.NewCoins(testvalues.DefaultTransferAmount(chainADenom)), + } + + cdc := testsuite.Codec() + bz, err := icatypes.SerializeCosmosTx(cdc, []proto.Message{msgSend}, icatypes.EncodingProtobuf) + s.Require().NoError(err) + + packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: bz, + Memo: "e2e", + } + + msgSendTx := controllertypes.NewMsgSendTx(userAWallet.FormattedAddress(), exported.LocalhostConnectionID, uint64(time.Hour.Nanoseconds()), packetData) + + txResp := s.BroadcastMessages(ctx, chainA, userAWallet, msgSendTx) + s.AssertTxSuccess(txResp) + + packet, err = ibctesting.ParseV1PacketFromEvents(txResp.Events) + s.Require().NoError(err) + s.Require().NotNil(packet) + }) + + t.Run("recv packet localhost interchain accounts", func(t *testing.T) { + msgRecvPacket := channeltypes.NewMsgRecvPacket(packet, localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress()) + + txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgRecvPacket) + s.AssertTxSuccess(txResp) + + ack, err = ibctesting.ParseAckFromEvents(txResp.Events) + s.Require().NoError(err) + s.Require().NotNil(ack) + }) + + t.Run("acknowledge packet localhost interchain accounts", func(t *testing.T) { + msgAcknowledgement := channeltypes.NewMsgAcknowledgement(packet, ack, localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress()) + + txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgAcknowledgement) + s.AssertTxSuccess(txResp) + }) + + t.Run("verify tokens transferred", func(t *testing.T) { + balance, err := query.Balance(ctx, chainA, userBWallet.FormattedAddress(), chainADenom) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + testvalues.StartingTokenAmount + s.Require().Equal(expected, balance.Int64()) + }) +} + +func (s *LocalhostInterchainAccountsTestSuite) TestInterchainAccounts_ReopenChannel_Localhost() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + s.CreateDefaultPaths(testName) + + chains := s.GetAllChains() + chainA := chains[0] + + chainADenom := chainA.Config().Denom + + rlyWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + userAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + userBWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + + var ( + msgChanOpenInitRes channeltypes.MsgChannelOpenInitResponse + msgChanOpenTryRes channeltypes.MsgChannelOpenTryResponse + ack []byte + packet channeltypes.Packet + ) + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA), "failed to wait for blocks") + + version := icatypes.NewDefaultMetadataString(exported.LocalhostConnectionID, exported.LocalhostConnectionID) + controllerPortID, err := icatypes.NewControllerPortID(userAWallet.FormattedAddress()) + s.Require().NoError(err) + + t.Run("channel open init localhost - broadcast MsgRegisterInterchainAccount", func(t *testing.T) { + msgRegisterAccount := controllertypes.NewMsgRegisterInterchainAccount(exported.LocalhostConnectionID, userAWallet.FormattedAddress(), version, channeltypes.ORDERED) + + txResp := s.BroadcastMessages(ctx, chainA, userAWallet, msgRegisterAccount) + s.AssertTxSuccess(txResp) + + s.Require().NoError(testsuite.UnmarshalMsgResponses(txResp, &msgChanOpenInitRes)) + }) + + t.Run("channel open try localhost", func(t *testing.T) { + msgChanOpenTry := channeltypes.NewMsgChannelOpenTry( + icatypes.HostPortID, icatypes.Version, + channeltypes.ORDERED, []string{exported.LocalhostConnectionID}, + controllerPortID, msgChanOpenInitRes.ChannelId, + version, localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(), + ) + + txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenTry) + s.AssertTxSuccess(txResp) + + s.Require().NoError(testsuite.UnmarshalMsgResponses(txResp, &msgChanOpenTryRes)) + }) + + t.Run("channel open ack localhost", func(t *testing.T) { + msgChanOpenAck := channeltypes.NewMsgChannelOpenAck( + controllerPortID, msgChanOpenInitRes.ChannelId, + msgChanOpenTryRes.ChannelId, msgChanOpenTryRes.Version, + localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(), + ) + + txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenAck) + s.AssertTxSuccess(txResp) + }) + + t.Run("channel open confirm localhost", func(t *testing.T) { + msgChanOpenConfirm := channeltypes.NewMsgChannelOpenConfirm( + icatypes.HostPortID, msgChanOpenTryRes.ChannelId, + localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(), + ) + + txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenConfirm) + s.AssertTxSuccess(txResp) + }) + + t.Run("query localhost interchain accounts channel ends", func(t *testing.T) { + channelEndA, err := query.Channel(ctx, chainA, controllerPortID, msgChanOpenInitRes.ChannelId) + s.Require().NoError(err) + s.Require().NotNil(channelEndA) + + channelEndB, err := query.Channel(ctx, chainA, icatypes.HostPortID, msgChanOpenTryRes.ChannelId) + s.Require().NoError(err) + s.Require().NotNil(channelEndB) + + s.Require().Equal(channelEndA.ConnectionHops, channelEndB.ConnectionHops) + }) + + t.Run("verify interchain account registration and deposit funds", func(t *testing.T) { + interchainAccAddress, err := query.InterchainAccount(ctx, chainA, userAWallet.FormattedAddress(), exported.LocalhostConnectionID) + s.Require().NoError(err) + s.Require().NotEmpty(interchainAccAddress) + + walletAmount := ibc.WalletAmount{ + Address: interchainAccAddress, + Amount: sdkmath.NewInt(testvalues.StartingTokenAmount), + Denom: chainADenom, + } + + s.Require().NoError(chainA.SendFunds(ctx, interchaintest.FaucetAccountKeyName, walletAmount)) + }) + + t.Run("send localhost interchain accounts packet with timeout", func(t *testing.T) { + interchainAccAddress, err := query.InterchainAccount(ctx, chainA, userAWallet.FormattedAddress(), exported.LocalhostConnectionID) + s.Require().NoError(err) + s.Require().NotEmpty(interchainAccAddress) + + msgSend := &banktypes.MsgSend{ + FromAddress: interchainAccAddress, + ToAddress: userBWallet.FormattedAddress(), + Amount: sdk.NewCoins(testvalues.DefaultTransferAmount(chainADenom)), + } + + cdc := testsuite.Codec() + bz, err := icatypes.SerializeCosmosTx(cdc, []proto.Message{msgSend}, icatypes.EncodingProtobuf) + s.Require().NoError(err) + + packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: bz, + Memo: "e2e", + } + + msgSendTx := controllertypes.NewMsgSendTx(userAWallet.FormattedAddress(), exported.LocalhostConnectionID, uint64(1), packetData) + + txResp := s.BroadcastMessages(ctx, chainA, userAWallet, msgSendTx) + s.AssertTxSuccess(txResp) + + packet, err = ibctesting.ParseV1PacketFromEvents(txResp.Events) + s.Require().NoError(err) + s.Require().NotNil(packet) + }) + + t.Run("timeout localhost interchain accounts packet", func(t *testing.T) { + msgTimeout := channeltypes.NewMsgTimeout(packet, 1, localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress()) + + txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgTimeout) + s.AssertTxSuccess(txResp) + }) + + t.Run("close interchain accounts host channel end", func(t *testing.T) { + // Pass in zero for counterpartyUpgradeSequence given that channel has not undergone any upgrades. + msgCloseConfirm := channeltypes.NewMsgChannelCloseConfirm(icatypes.HostPortID, msgChanOpenTryRes.ChannelId, localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress()) + + txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgCloseConfirm) + s.AssertTxSuccess(txResp) + }) + + t.Run("verify localhost interchain accounts channel is closed", func(t *testing.T) { + channelEndA, err := query.Channel(ctx, chainA, controllerPortID, msgChanOpenInitRes.ChannelId) + s.Require().NoError(err) + + s.Require().Equal(channeltypes.CLOSED, channelEndA.State, "the channel was not in an expected state") + + channelEndB, err := query.Channel(ctx, chainA, icatypes.HostPortID, msgChanOpenTryRes.ChannelId) + s.Require().NoError(err) + + s.Require().Equal(channeltypes.CLOSED, channelEndB.State, "the channel was not in an expected state") + }) + + t.Run("channel open init localhost: create new channel for existing account", func(t *testing.T) { + msgRegisterAccount := controllertypes.NewMsgRegisterInterchainAccount(exported.LocalhostConnectionID, userAWallet.FormattedAddress(), version, channeltypes.ORDERED) + + txResp := s.BroadcastMessages(ctx, chainA, userAWallet, msgRegisterAccount) + s.AssertTxSuccess(txResp) + + // note: response values are updated here in msgChanOpenInitRes + s.Require().NoError(testsuite.UnmarshalMsgResponses(txResp, &msgChanOpenInitRes)) + }) + + t.Run("channel open try localhost", func(t *testing.T) { + msgChanOpenTry := channeltypes.NewMsgChannelOpenTry( + icatypes.HostPortID, icatypes.Version, + channeltypes.ORDERED, []string{exported.LocalhostConnectionID}, + controllerPortID, msgChanOpenInitRes.ChannelId, + version, localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(), + ) + + txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenTry) + s.AssertTxSuccess(txResp) + + // note: response values are updated here in msgChanOpenTryRes + s.Require().NoError(testsuite.UnmarshalMsgResponses(txResp, &msgChanOpenTryRes)) + }) + + t.Run("channel open ack localhost", func(t *testing.T) { + msgChanOpenAck := channeltypes.NewMsgChannelOpenAck( + controllerPortID, msgChanOpenInitRes.ChannelId, + msgChanOpenTryRes.ChannelId, msgChanOpenTryRes.Version, + localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(), + ) + + txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenAck) + s.AssertTxSuccess(txResp) + }) + + t.Run("channel open confirm localhost", func(t *testing.T) { + msgChanOpenConfirm := channeltypes.NewMsgChannelOpenConfirm( + icatypes.HostPortID, msgChanOpenTryRes.ChannelId, + localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(), + ) + + txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenConfirm) + s.AssertTxSuccess(txResp) + }) + + t.Run("query localhost interchain accounts channel ends", func(t *testing.T) { + channelEndA, err := query.Channel(ctx, chainA, controllerPortID, msgChanOpenInitRes.ChannelId) + s.Require().NoError(err) + s.Require().NotNil(channelEndA) + + channelEndB, err := query.Channel(ctx, chainA, icatypes.HostPortID, msgChanOpenTryRes.ChannelId) + s.Require().NoError(err) + s.Require().NotNil(channelEndB) + + s.Require().Equal(channelEndA.ConnectionHops, channelEndB.ConnectionHops) + }) + + t.Run("verify interchain account and existing balance", func(t *testing.T) { + interchainAccAddress, err := query.InterchainAccount(ctx, chainA, userAWallet.FormattedAddress(), exported.LocalhostConnectionID) + s.Require().NoError(err) + s.Require().NotEmpty(interchainAccAddress) + + balance, err := query.Balance(ctx, chainA, interchainAccAddress, chainADenom) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount + s.Require().Equal(expected, balance.Int64()) + }) + + t.Run("send packet localhost interchain accounts", func(t *testing.T) { + interchainAccAddress, err := query.InterchainAccount(ctx, chainA, userAWallet.FormattedAddress(), exported.LocalhostConnectionID) + s.Require().NoError(err) + s.Require().NotEmpty(interchainAccAddress) + + msgSend := &banktypes.MsgSend{ + FromAddress: interchainAccAddress, + ToAddress: userBWallet.FormattedAddress(), + Amount: sdk.NewCoins(testvalues.DefaultTransferAmount(chainADenom)), + } + + cdc := testsuite.Codec() + bz, err := icatypes.SerializeCosmosTx(cdc, []proto.Message{msgSend}, icatypes.EncodingProtobuf) + s.Require().NoError(err) + + packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: bz, + Memo: "e2e", + } + + msgSendTx := controllertypes.NewMsgSendTx(userAWallet.FormattedAddress(), exported.LocalhostConnectionID, uint64(time.Hour.Nanoseconds()), packetData) + + txResp := s.BroadcastMessages(ctx, chainA, userAWallet, msgSendTx) + s.AssertTxSuccess(txResp) + + packet, err = ibctesting.ParseV1PacketFromEvents(txResp.Events) + s.Require().NoError(err) + s.Require().NotNil(packet) + }) + + t.Run("recv packet localhost interchain accounts", func(t *testing.T) { + msgRecvPacket := channeltypes.NewMsgRecvPacket(packet, localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress()) + + txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgRecvPacket) + s.AssertTxSuccess(txResp) + + ack, err = ibctesting.ParseAckFromEvents(txResp.Events) + s.Require().NoError(err) + s.Require().NotNil(ack) + }) + + t.Run("acknowledge packet localhost interchain accounts", func(t *testing.T) { + msgAcknowledgement := channeltypes.NewMsgAcknowledgement(packet, ack, localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress()) + + txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgAcknowledgement) + s.AssertTxSuccess(txResp) + }) + + t.Run("verify tokens transferred", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, controllerPortID, msgChanOpenInitRes.ChannelId, 1) + + balance, err := query.Balance(ctx, chainA, userBWallet.FormattedAddress(), chainADenom) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + testvalues.StartingTokenAmount + s.Require().Equal(expected, balance.Int64()) + }) +} diff --git a/e2e/tests/interchain_accounts/params_test.go b/e2e/tests/interchain_accounts/params_test.go new file mode 100644 index 0000000..486f4ef --- /dev/null +++ b/e2e/tests/interchain_accounts/params_test.go @@ -0,0 +1,275 @@ +//go:build !test_e2e + +package interchainaccounts + +import ( + "context" + "testing" + "time" + + "github.com/cosmos/gogoproto/proto" + "github.com/cosmos/interchaintest/v10" + "github.com/cosmos/interchaintest/v10/ibc" + test "github.com/cosmos/interchaintest/v10/testutil" + testifysuite "github.com/stretchr/testify/suite" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + paramsproposaltypes "github.com/cosmos/cosmos-sdk/x/params/types/proposal" + + "github.com/cosmos/ibc-go/e2e/testsuite" + "github.com/cosmos/ibc-go/e2e/testsuite/query" + "github.com/cosmos/ibc-go/e2e/testvalues" + controllertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + hosttypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + coretypes "github.com/cosmos/ibc-go/v10/modules/core/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +// compatibility:from_version: v7.10.0 +func TestInterchainAccountsParamsTestSuite(t *testing.T) { + testifysuite.Run(t, new(InterchainAccountsParamsTestSuite)) +} + +type InterchainAccountsParamsTestSuite struct { + testsuite.E2ETestSuite +} + +// SetupSuite sets up chains for the current test suite +func (s *InterchainAccountsParamsTestSuite) SetupSuite() { + s.SetupChains(context.TODO(), 2, nil) +} + +// QueryControllerParams queries the params for the controller +func (s *InterchainAccountsParamsTestSuite) QueryControllerParams(ctx context.Context, chain ibc.Chain) controllertypes.Params { + res, err := query.GRPCQuery[controllertypes.QueryParamsResponse](ctx, chain, &controllertypes.QueryParamsRequest{}) + s.Require().NoError(err) + + return *res.Params +} + +// QueryHostParams queries the host chain for the params +func (s *InterchainAccountsParamsTestSuite) QueryHostParams(ctx context.Context, chain ibc.Chain) hosttypes.Params { + res, err := query.GRPCQuery[hosttypes.QueryParamsResponse](ctx, chain, &hosttypes.QueryParamsRequest{}) + s.Require().NoError(err) + + return *res.Params +} + +// TestControllerEnabledParam tests that changing the ControllerEnabled param works as expected +func (s *InterchainAccountsParamsTestSuite) TestControllerEnabledParam() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + s.CreateDefaultPaths(testName) + + chainA, _ := s.GetChains() + chainAVersion := chainA.Config().Images[0].Version + + // setup controller account on chainA + controllerAccount := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + controllerAddress := controllerAccount.FormattedAddress() + + t.Run("ensure the controller is enabled", func(t *testing.T) { + params := s.QueryControllerParams(ctx, chainA) + s.Require().True(params.ControllerEnabled) + }) + + t.Run("disable the controller", func(t *testing.T) { + if testvalues.SelfParamsFeatureReleases.IsSupported(chainAVersion) { + authority, err := query.ModuleAccountAddress(ctx, govtypes.ModuleName, chainA) + s.Require().NoError(err) + s.Require().NotNil(authority) + + msg := controllertypes.MsgUpdateParams{ + Signer: authority.String(), + Params: controllertypes.NewParams(false), + } + s.ExecuteAndPassGovV1Proposal(ctx, &msg, chainA, controllerAccount) + } else { + changes := []paramsproposaltypes.ParamChange{ + paramsproposaltypes.NewParamChange(controllertypes.StoreKey, string(controllertypes.KeyControllerEnabled), "false"), + } + + proposal := paramsproposaltypes.NewParameterChangeProposal(ibctesting.Title, ibctesting.Description, changes) + s.ExecuteAndPassGovV1Beta1Proposal(ctx, chainA, controllerAccount, proposal) + } + }) + + t.Run("ensure controller is disabled", func(t *testing.T) { + params := s.QueryControllerParams(ctx, chainA) + s.Require().False(params.ControllerEnabled) + }) + + t.Run("ensure that broadcasting a MsgRegisterInterchainAccount fails", func(t *testing.T) { + // explicitly set the version string because we don't want to use incentivized channels. + version := icatypes.NewDefaultMetadataString(ibctesting.FirstConnectionID, ibctesting.FirstConnectionID) + msgRegisterAccount := controllertypes.NewMsgRegisterInterchainAccount(ibctesting.FirstConnectionID, controllerAddress, version, channeltypes.ORDERED) + + txResp := s.BroadcastMessages(ctx, chainA, controllerAccount, msgRegisterAccount) + s.AssertTxFailure(txResp, controllertypes.ErrControllerSubModuleDisabled) + }) +} + +// compatibility:TestHostEnabledParam:from_versions: v7.10.0,v8.7.0,v10.0.0 +func (s *InterchainAccountsParamsTestSuite) TestHostEnabledParam() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + relayer := s.CreateDefaultPaths(testName) + + chainA, chainB := s.GetChains() + chainBVersion := chainB.Config().Images[0].Version + + // setup 2 accounts: controller account on chain A, a second chain B account (to do the disable host gov proposal) + // host account will be created when the ICA is registered + controllerAccount := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + controllerAddress := controllerAccount.FormattedAddress() + chainBAccount := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + chainBAddress := chainBAccount.FormattedAddress() + var hostAccount string + + // Assert that default value for enabled is true. + t.Run("ensure the host is enabled", func(t *testing.T) { + params := s.QueryHostParams(ctx, chainB) + s.Require().True(params.HostEnabled) + s.Require().Equal([]string{hosttypes.AllowAllHostMsgs}, params.AllowMessages) + }) + + t.Run("ensure ica packets are flowing before disabling the host", func(t *testing.T) { + t.Run("broadcast MsgRegisterInterchainAccount", func(t *testing.T) { + // explicitly set the version string because we don't want to use incentivized channels. + version := icatypes.NewDefaultMetadataString(ibctesting.FirstConnectionID, ibctesting.FirstConnectionID) + msgRegisterAccount := controllertypes.NewMsgRegisterInterchainAccount(ibctesting.FirstConnectionID, controllerAddress, version, channeltypes.ORDERED) + + txResp := s.BroadcastMessages(ctx, chainA, controllerAccount, msgRegisterAccount) + s.AssertTxSuccess(txResp) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("verify interchain account", func(t *testing.T) { + var err error + hostAccount, err = query.InterchainAccount(ctx, chainA, controllerAddress, ibctesting.FirstConnectionID) + s.Require().NoError(err) + s.Require().NotEmpty(hostAccount) + + channels, err := relayer.GetChannels(ctx, s.GetRelayerExecReporter(), chainA.Config().ChainID) + s.Require().NoError(err) + s.Require().Equal(len(channels), 2) + }) + + t.Run("stop relayer", func(t *testing.T) { + s.StopRelayer(ctx, relayer) + }) + }) + + t.Run("disable the host", func(t *testing.T) { + if testvalues.SelfParamsFeatureReleases.IsSupported(chainBVersion) { + authority, err := query.ModuleAccountAddress(ctx, govtypes.ModuleName, chainB) + s.Require().NoError(err) + s.Require().NotNil(authority) + + msg := hosttypes.MsgUpdateParams{ + Signer: authority.String(), + Params: hosttypes.NewParams(false, []string{hosttypes.AllowAllHostMsgs}), + } + s.ExecuteAndPassGovV1Proposal(ctx, &msg, chainB, chainBAccount) + } else { + changes := []paramsproposaltypes.ParamChange{ + paramsproposaltypes.NewParamChange(hosttypes.StoreKey, string(hosttypes.KeyHostEnabled), "false"), + } + + proposal := paramsproposaltypes.NewParameterChangeProposal(ibctesting.Title, ibctesting.Description, changes) + s.ExecuteAndPassGovV1Beta1Proposal(ctx, chainB, chainBAccount, proposal) + } + }) + + t.Run("ensure the host is disabled", func(t *testing.T) { + params := s.QueryHostParams(ctx, chainB) + s.Require().False(params.HostEnabled) + }) + + t.Run("ensure that ica packets are not flowing", func(t *testing.T) { + t.Run("fund interchain account wallet", func(t *testing.T) { + // fund the host account so it has some $$ to send + err := chainB.SendFunds(ctx, interchaintest.FaucetAccountKeyName, ibc.WalletAmount{ + Address: hostAccount, + Amount: sdkmath.NewInt(testvalues.StartingTokenAmount), + Denom: chainB.Config().Denom, + }) + s.Require().NoError(err) + }) + + t.Run("broadcast MsgSendTx", func(t *testing.T) { + // assemble bank transfer message from host account to user account on host chain + msgSend := &banktypes.MsgSend{ + FromAddress: hostAccount, + ToAddress: chainBAddress, + Amount: sdk.NewCoins(testvalues.DefaultTransferAmount(chainB.Config().Denom)), + } + + cdc := testsuite.Codec() + bz, err := icatypes.SerializeCosmosTx(cdc, []proto.Message{msgSend}, icatypes.EncodingProtobuf) + s.Require().NoError(err) + + packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: bz, + Memo: "e2e", + } + + msgSendTx := controllertypes.NewMsgSendTx(controllerAddress, ibctesting.FirstConnectionID, uint64(time.Hour.Nanoseconds()), packetData) + + resp := s.BroadcastMessages( + ctx, + chainA, + controllerAccount, + msgSendTx, + ) + + s.AssertTxSuccess(resp) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 10, chainA, chainB)) + + t.Run("verify no tokens were transferred", func(t *testing.T) { + chainBAccountBalance, err := query.Balance(ctx, chainB, chainBAddress, chainB.Config().Denom) + s.Require().NoError(err) + s.Require().Equal(testvalues.StartingTokenAmount, chainBAccountBalance.Int64()) + + hostAccountBalance, err := query.Balance(ctx, chainB, hostAccount, chainB.Config().Denom) + s.Require().NoError(err) + s.Require().Equal(testvalues.StartingTokenAmount, hostAccountBalance.Int64()) + }) + + t.Run("verify acknowledgement error in ack transaction", func(t *testing.T) { + cmd := "message.action='/ibc.core.channel.v1.MsgRecvPacket'" + txSearchRes, err := s.QueryTxsByEvents(ctx, chainB, 1, 1, cmd, "") + s.Require().NoError(err) + s.Require().Len(txSearchRes.Txs, 1) + + errorMessage, isFound := s.ExtractValueFromEvents( + txSearchRes.TxResponses[0].Events, + coretypes.ErrorAttributeKeyPrefix+icatypes.EventTypePacket, + coretypes.ErrorAttributeKeyPrefix+icatypes.AttributeKeyAckError, + ) + + s.Require().True(isFound) + s.Require().Equal(errorMessage, hosttypes.ErrHostSubModuleDisabled.Error()) + }) + }) +} diff --git a/e2e/tests/interchain_accounts/query_test.go b/e2e/tests/interchain_accounts/query_test.go new file mode 100644 index 0000000..2c884c8 --- /dev/null +++ b/e2e/tests/interchain_accounts/query_test.go @@ -0,0 +1,167 @@ +//go:build !test_e2e + +package interchainaccounts + +import ( + "context" + "encoding/hex" + "encoding/json" + "testing" + "time" + + "github.com/cosmos/gogoproto/proto" + "github.com/cosmos/interchaintest/v10/testutil" + testifysuite "github.com/stretchr/testify/suite" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + "github.com/cosmos/ibc-go/e2e/testsuite" + "github.com/cosmos/ibc-go/e2e/testsuite/query" + "github.com/cosmos/ibc-go/e2e/testvalues" + controllertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + icahosttypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +// compatibility:from_version: v7.10.0 +func TestInterchainAccountsQueryTestSuite(t *testing.T) { + testifysuite.Run(t, new(InterchainAccountsQueryTestSuite)) +} + +type InterchainAccountsQueryTestSuite struct { + testsuite.E2ETestSuite +} + +// SetupSuite sets up chains for the current test suite +func (s *InterchainAccountsQueryTestSuite) SetupSuite() { + s.SetupChains(context.TODO(), 2, nil) +} + +// compatibility:TestInterchainAccountsQuery:from_versions: v7.10.0,v8.7.0,v10.0.0 +func (s *InterchainAccountsQueryTestSuite) TestInterchainAccountsQuery() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + relayer := s.CreateDefaultPaths(testName) + + chainA, chainB := s.GetChains() + + // setup 2 accounts: controller account on chain A, a second chain B account. + // host account will be created when the ICA is registered + controllerAccount := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + controllerAddress := controllerAccount.FormattedAddress() + chainBAccount := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + var hostAccount string + + t.Run("broadcast MsgRegisterInterchainAccount", func(t *testing.T) { + // explicitly set the version string because we don't want to use incentivized channels. + version := icatypes.NewDefaultMetadataString(ibctesting.FirstConnectionID, ibctesting.FirstConnectionID) + msgRegisterAccount := controllertypes.NewMsgRegisterInterchainAccount(ibctesting.FirstConnectionID, controllerAddress, version, channeltypes.UNORDERED) + + txResp := s.BroadcastMessages(ctx, chainA, controllerAccount, msgRegisterAccount) + s.AssertTxSuccess(txResp) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("verify interchain account", func(t *testing.T) { + var err error + hostAccount, err = query.InterchainAccount(ctx, chainA, controllerAddress, ibctesting.FirstConnectionID) + s.Require().NoError(err) + s.Require().NotEmpty(hostAccount) + + channels, err := relayer.GetChannels(ctx, s.GetRelayerExecReporter(), chainA.Config().ChainID) + s.Require().NoError(err) + s.Require().Len(channels, 2) + }) + + t.Run("query via interchain account", func(t *testing.T) { + // the host account need not be funded + t.Run("broadcast query packet", func(t *testing.T) { + balanceQuery := banktypes.NewQueryBalanceRequest(chainBAccount.Address(), chainB.Config().Denom) + queryBz, err := balanceQuery.Marshal() + s.Require().NoError(err) + + queryMsg := icahosttypes.NewMsgModuleQuerySafe(hostAccount, []icahosttypes.QueryRequest{ + { + Path: "/cosmos.bank.v1beta1.Query/Balance", + Data: queryBz, + }, + }) + + cdc := testsuite.Codec() + bz, err := icatypes.SerializeCosmosTx(cdc, []proto.Message{queryMsg}, icatypes.EncodingProtobuf) + s.Require().NoError(err) + + packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: bz, + Memo: "e2e", + } + + icaQueryMsg := controllertypes.NewMsgSendTx(controllerAddress, ibctesting.FirstConnectionID, uint64(time.Hour.Nanoseconds()), packetData) + + txResp := s.BroadcastMessages(ctx, chainA, controllerAccount, icaQueryMsg) + s.AssertTxSuccess(txResp) + + s.Require().NoError(testutil.WaitForBlocks(ctx, 20, chainA, chainB)) + }) + + t.Run("verify query response", func(t *testing.T) { + var expQueryHeight uint64 + + ack := &channeltypes.Acknowledgement_Result{} + t.Run("retrieve acknowledgement", func(t *testing.T) { + cmd := "message.action='/ibc.core.channel.v1.MsgRecvPacket'" + txSearchRes, err := s.QueryTxsByEvents(ctx, chainB, 1, 1, cmd, "") + s.Require().NoError(err) + s.Require().Len(txSearchRes.TxResponses, 1) + + expQueryHeight = uint64(txSearchRes.TxResponses[0].Height) + + ackHexValue, isFound := s.ExtractValueFromEvents( + txSearchRes.TxResponses[0].Events, + channeltypes.EventTypeWriteAck, + channeltypes.AttributeKeyAckHex, + ) + s.Require().True(isFound) + s.Require().NotEmpty(ackHexValue) + + ackBz, err := hex.DecodeString(ackHexValue) + s.Require().NoError(err) + + err = json.Unmarshal(ackBz, ack) + s.Require().NoError(err) + }) + + icaAck := &sdk.TxMsgData{} + t.Run("unmarshal ica response", func(t *testing.T) { + err := proto.Unmarshal(ack.Result, icaAck) + s.Require().NoError(err) + s.Require().Len(icaAck.GetMsgResponses(), 1) + }) + + queryTxResp := &icahosttypes.MsgModuleQuerySafeResponse{} + t.Run("unmarshal MsgModuleQuerySafeResponse", func(t *testing.T) { + err := proto.Unmarshal(icaAck.MsgResponses[0].Value, queryTxResp) + s.Require().NoError(err) + s.Require().Len(queryTxResp.Responses, 1) + s.Require().Equal(expQueryHeight, queryTxResp.Height) + }) + + balanceResp := &banktypes.QueryBalanceResponse{} + t.Run("unmarshal and verify bank query response", func(t *testing.T) { + err := proto.Unmarshal(queryTxResp.Responses[0], balanceResp) + s.Require().NoError(err) + s.Require().Equal(chainB.Config().Denom, balanceResp.Balance.Denom) + s.Require().Equal(testvalues.StartingTokenAmount, balanceResp.Balance.Amount.Int64()) + }) + }) + }) +} diff --git a/e2e/tests/transfer/authz_test.go b/e2e/tests/transfer/authz_test.go new file mode 100644 index 0000000..e96c31a --- /dev/null +++ b/e2e/tests/transfer/authz_test.go @@ -0,0 +1,379 @@ +//go:build !test_e2e + +package transfer + +import ( + "context" + "testing" + + "github.com/cosmos/interchaintest/v10/ibc" + test "github.com/cosmos/interchaintest/v10/testutil" + testifysuite "github.com/stretchr/testify/suite" + + sdkmath "cosmossdk.io/math" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/authz" + + "github.com/cosmos/ibc-go/e2e/testsuite" + "github.com/cosmos/ibc-go/e2e/testsuite/query" + "github.com/cosmos/ibc-go/e2e/testvalues" + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +// compatibility:from_version: v7.10.0 +func TestAuthzTransferTestSuite(t *testing.T) { + testifysuite.Run(t, new(AuthzTransferTestSuite)) +} + +type AuthzTransferTestSuite struct { + testsuite.E2ETestSuite +} + +// SetupSuite sets up chains for the current test suite +func (s *AuthzTransferTestSuite) SetupSuite() { + s.SetupChains(context.TODO(), 2, nil) +} + +func (s *AuthzTransferTestSuite) CreateAuthzTestPath(testName string) (ibc.Relayer, ibc.ChannelOutput) { + return s.CreatePaths(ibc.DefaultClientOpts(), s.TransferChannelOptions(), testName), s.GetChainAChannelForTest(testName) +} + +// QueryGranterGrants returns all GrantAuthorizations for the given granterAddress. +func (*AuthzTransferTestSuite) QueryGranterGrants(ctx context.Context, chain ibc.Chain, granterAddress string) ([]*authz.GrantAuthorization, error) { + res, err := query.GRPCQuery[authz.QueryGranterGrantsResponse](ctx, chain, &authz.QueryGranterGrantsRequest{ + Granter: granterAddress, + }) + if err != nil { + return nil, err + } + + return res.Grants, nil +} + +func (s *AuthzTransferTestSuite) TestAuthz_MsgTransfer_Succeeds() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + t.Parallel() + relayer, channelA := s.CreateAuthzTestPath(testName) + + chainA, chainB := s.GetChains() + chainADenom := chainA.Config().Denom + + granterWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + granterAddress := granterWallet.FormattedAddress() + + granteeWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + granteeAddress := granteeWallet.FormattedAddress() + + receiverWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + receiverWalletAddress := receiverWallet.FormattedAddress() + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + // createMsgGrantFn initializes a TransferAuthorization and broadcasts a MsgGrant message. + createMsgGrantFn := func(t *testing.T) { + t.Helper() + transferAuth := transfertypes.TransferAuthorization{ + Allocations: []transfertypes.Allocation{ + { + SourcePort: channelA.PortID, + SourceChannel: channelA.ChannelID, + SpendLimit: sdk.NewCoins(sdk.NewCoin(chainADenom, sdkmath.NewInt(testvalues.StartingTokenAmount))), + AllowList: []string{receiverWalletAddress}, + }, + }, + } + + protoAny, err := codectypes.NewAnyWithValue(&transferAuth) + s.Require().NoError(err) + + msgGrant := &authz.MsgGrant{ + Granter: granterAddress, + Grantee: granteeAddress, + Grant: authz.Grant{ + Authorization: protoAny, + // no expiration + Expiration: nil, + }, + } + + resp := s.BroadcastMessages(context.TODO(), chainA, granterWallet, msgGrant) + s.AssertTxSuccess(resp) + } + + // verifyGrantFn returns a test function which asserts chainA has a grant authorization + // with the given spend limit. + verifyGrantFn := func(expectedLimit int64) func(t *testing.T) { + t.Helper() + return func(t *testing.T) { + t.Helper() + grantAuths, err := s.QueryGranterGrants(ctx, chainA, granterAddress) + + s.Require().NoError(err) + s.Require().Len(grantAuths, 1) + grantAuthorization := grantAuths[0] + + transferAuth := s.extractTransferAuthorizationFromGrantAuthorization(grantAuthorization) + expectedSpendLimit := sdk.NewCoins(sdk.NewCoin(chainADenom, sdkmath.NewInt(expectedLimit))) + s.Require().Equal(expectedSpendLimit, transferAuth.Allocations[0].SpendLimit) + } + } + + t.Run("broadcast MsgGrant", createMsgGrantFn) + + t.Run("broadcast MsgExec for ibc MsgTransfer", func(t *testing.T) { + transferMsg := testsuite.GetMsgTransfer( + channelA.PortID, + channelA.ChannelID, + channelA.Version, + testvalues.DefaultTransferAmount(chainADenom), + granterAddress, + receiverWalletAddress, + s.GetTimeoutHeight(ctx, chainB), + 0, + "", + ) + + protoAny, err := codectypes.NewAnyWithValue(transferMsg) + s.Require().NoError(err) + + msgExec := &authz.MsgExec{ + Grantee: granteeAddress, + Msgs: []*codectypes.Any{protoAny}, + } + + resp := s.BroadcastMessages(context.TODO(), chainA, granteeWallet, msgExec) + s.AssertTxSuccess(resp) + }) + + t.Run("verify granter wallet amount", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, granterWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + }) + + s.Require().NoError(test.WaitForBlocks(context.TODO(), 10, chainB)) + + t.Run("verify receiver wallet amount", func(t *testing.T) { + chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) + actualBalance, err := query.Balance(ctx, chainB, receiverWalletAddress, chainBIBCToken.IBCDenom()) + + s.Require().NoError(err) + s.Require().Equal(testvalues.IBCTransferAmount, actualBalance.Int64()) + }) + + t.Run("granter grant spend limit reduced", verifyGrantFn(testvalues.StartingTokenAmount-testvalues.IBCTransferAmount)) + + t.Run("re-initialize MsgGrant", createMsgGrantFn) + + t.Run("granter grant was reinitialized", verifyGrantFn(testvalues.StartingTokenAmount)) + + t.Run("revoke access", func(t *testing.T) { + msgRevoke := authz.MsgRevoke{ + Granter: granterAddress, + Grantee: granteeAddress, + MsgTypeUrl: transfertypes.TransferAuthorization{}.MsgTypeURL(), + } + + resp := s.BroadcastMessages(context.TODO(), chainA, granterWallet, &msgRevoke) + s.AssertTxSuccess(resp) + }) + + t.Run("exec unauthorized MsgTransfer", func(t *testing.T) { + transferMsg := testsuite.GetMsgTransfer( + channelA.PortID, + channelA.ChannelID, + channelA.Version, + testvalues.DefaultTransferAmount(chainADenom), + granterAddress, + receiverWalletAddress, + s.GetTimeoutHeight(ctx, chainB), + 0, + "", + ) + + protoAny, err := codectypes.NewAnyWithValue(transferMsg) + s.Require().NoError(err) + + msgExec := &authz.MsgExec{ + Grantee: granteeAddress, + Msgs: []*codectypes.Any{protoAny}, + } + + resp := s.BroadcastMessages(context.TODO(), chainA, granteeWallet, msgExec) + s.AssertTxFailure(resp, authz.ErrNoAuthorizationFound) + }) +} + +func (s *AuthzTransferTestSuite) TestAuthz_InvalidTransferAuthorizations() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + t.Parallel() + relayer, channelA := s.CreateAuthzTestPath(testName) + + chainA, chainB := s.GetChains() + chainADenom := chainA.Config().Denom + chainAVersion := chainA.Config().Images[0].Version + + granterWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + granterAddress := granterWallet.FormattedAddress() + + granteeWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + granteeAddress := granteeWallet.FormattedAddress() + + receiverWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + receiverWalletAddress := receiverWallet.FormattedAddress() + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + const spendLimit = 1000 + + t.Run("broadcast MsgGrant", func(t *testing.T) { + transferAuth := transfertypes.TransferAuthorization{ + Allocations: []transfertypes.Allocation{ + { + SourcePort: channelA.PortID, + SourceChannel: channelA.ChannelID, + SpendLimit: sdk.NewCoins(sdk.NewCoin(chainADenom, sdkmath.NewInt(spendLimit))), + AllowList: []string{receiverWalletAddress}, + }, + }, + } + + protoAny, err := codectypes.NewAnyWithValue(&transferAuth) + s.Require().NoError(err) + + msgGrant := &authz.MsgGrant{ + Granter: granterAddress, + Grantee: granteeAddress, + Grant: authz.Grant{ + Authorization: protoAny, + // no expiration + Expiration: nil, + }, + } + + resp := s.BroadcastMessages(context.TODO(), chainA, granterWallet, msgGrant) + s.AssertTxSuccess(resp) + }) + + t.Run("exceed spend limit", func(t *testing.T) { + const invalidSpendAmount = spendLimit + 1 + + t.Run("broadcast MsgExec for ibc MsgTransfer", func(t *testing.T) { + transferMsg := testsuite.GetMsgTransfer( + channelA.PortID, + channelA.ChannelID, + channelA.Version, + sdk.Coin{Denom: chainADenom, Amount: sdkmath.NewInt(invalidSpendAmount)}, + granterAddress, + receiverWalletAddress, + s.GetTimeoutHeight(ctx, chainB), + 0, + "", + ) + + protoAny, err := codectypes.NewAnyWithValue(transferMsg) + s.Require().NoError(err) + + msgExec := &authz.MsgExec{ + Grantee: granteeAddress, + Msgs: []*codectypes.Any{protoAny}, + } + + resp := s.BroadcastMessages(context.TODO(), chainA, granteeWallet, msgExec) + if testvalues.IbcErrorsFeatureReleases.IsSupported(chainAVersion) { + s.AssertTxFailure(resp, ibcerrors.ErrInsufficientFunds) + } else { + s.AssertTxFailure(resp, sdkerrors.ErrInsufficientFunds) + } + }) + + t.Run("verify granter wallet amount", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, granterWallet) + s.Require().NoError(err) + s.Require().Equal(testvalues.StartingTokenAmount, actualBalance) + }) + + t.Run("verify receiver wallet amount", func(t *testing.T) { + chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) + actualBalance, err := query.Balance(ctx, chainB, receiverWalletAddress, chainBIBCToken.IBCDenom()) + + s.Require().NoError(err) + s.Require().Equal(int64(0), actualBalance.Int64()) + }) + + t.Run("granter grant spend limit unchanged", func(t *testing.T) { + grantAuths, err := s.QueryGranterGrants(ctx, chainA, granterAddress) + + s.Require().NoError(err) + s.Require().Len(grantAuths, 1) + grantAuthorization := grantAuths[0] + + transferAuth := s.extractTransferAuthorizationFromGrantAuthorization(grantAuthorization) + expectedSpendLimit := sdk.NewCoins(sdk.NewCoin(chainADenom, sdkmath.NewInt(spendLimit))) + s.Require().Equal(expectedSpendLimit, transferAuth.Allocations[0].SpendLimit) + }) + }) + + t.Run("send funds to invalid address", func(t *testing.T) { + invalidWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + invalidWalletAddress := invalidWallet.FormattedAddress() + + t.Run("broadcast MsgExec for ibc MsgTransfer", func(t *testing.T) { + transferMsg := testsuite.GetMsgTransfer( + channelA.PortID, + channelA.ChannelID, + channelA.Version, + sdk.Coin{Denom: chainADenom, Amount: sdkmath.NewInt(spendLimit)}, + granterAddress, + invalidWalletAddress, + s.GetTimeoutHeight(ctx, chainB), + 0, + "", + ) + + protoAny, err := codectypes.NewAnyWithValue(transferMsg) + s.Require().NoError(err) + + msgExec := &authz.MsgExec{ + Grantee: granteeAddress, + Msgs: []*codectypes.Any{protoAny}, + } + + resp := s.BroadcastMessages(context.TODO(), chainA, granteeWallet, msgExec) + if testvalues.IbcErrorsFeatureReleases.IsSupported(chainAVersion) { + s.AssertTxFailure(resp, ibcerrors.ErrInvalidAddress) + } else { + s.AssertTxFailure(resp, sdkerrors.ErrInvalidAddress) + } + }) + }) +} + +// extractTransferAuthorizationFromGrantAuthorization extracts a TransferAuthorization from the given +// GrantAuthorization. +func (s *AuthzTransferTestSuite) extractTransferAuthorizationFromGrantAuthorization(grantAuth *authz.GrantAuthorization) *transfertypes.TransferAuthorization { + cfg := testsuite.SDKEncodingConfig() + var authorization authz.Authorization + err := cfg.InterfaceRegistry.UnpackAny(grantAuth.Authorization, &authorization) + s.Require().NoError(err) + + transferAuth, ok := authorization.(*transfertypes.TransferAuthorization) + s.Require().True(ok) + return transferAuth +} diff --git a/e2e/tests/transfer/base_test.go b/e2e/tests/transfer/base_test.go new file mode 100644 index 0000000..d5a38f2 --- /dev/null +++ b/e2e/tests/transfer/base_test.go @@ -0,0 +1,406 @@ +//go:build !test_e2e + +package transfer + +import ( + "context" + "testing" + "time" + + "github.com/cosmos/interchaintest/v10/ibc" + test "github.com/cosmos/interchaintest/v10/testutil" + testifysuite "github.com/stretchr/testify/suite" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/e2e/testsuite" + "github.com/cosmos/ibc-go/e2e/testsuite/query" + "github.com/cosmos/ibc-go/e2e/testvalues" + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +) + +// compatibility:from_version: v7.10.0 +func TestTransferTestSuite(t *testing.T) { + testifysuite.Run(t, new(TransferTestSuite)) +} + +type TransferTestSuite struct { + transferTester +} + +// SetupSuite sets up chains for the current test suite +func (s *TransferTestSuite) SetupSuite() { + s.SetupChains(context.TODO(), 2, nil) +} + +// transferTester defines some helper functions that can be used in various test suites +// that test transfer functionality. +type transferTester struct { + testsuite.E2ETestSuite +} + +// QueryTransferParams queries the on-chain send enabled param for the transfer module +func (s *transferTester) QueryTransferParams(ctx context.Context, chain ibc.Chain) transfertypes.Params { + res, err := query.GRPCQuery[transfertypes.QueryParamsResponse](ctx, chain, &transfertypes.QueryParamsRequest{}) + s.Require().NoError(err) + return *res.Params +} + +// CreateTransferPath sets up a path between chainA and chainB with a transfer channel and returns the relayer wired +// up to watch the channel and port IDs created. +func (s *transferTester) CreateTransferPath(testName string) (ibc.Relayer, ibc.ChannelOutput) { + relayer, channel := s.CreatePaths(ibc.DefaultClientOpts(), s.TransferChannelOptions(), testName), s.GetChainAChannelForTest(testName) + s.T().Logf("test %s running on portID %s channelID %s", testName, channel.PortID, channel.ChannelID) + return relayer, channel +} + +// TestMsgTransfer_Succeeds_Nonincentivized will test sending successful IBC transfers from chainA to chainB. +// The transfer will occur over a basic transfer channel (non incentivized) and both native and non-native tokens +// will be sent forwards and backwards in the IBC transfer timeline (both chains will act as source and receiver chains). +func (s *TransferTestSuite) TestMsgTransfer_Succeeds_Nonincentivized() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + + // NOTE: t.Parallel() should be called before SetupPath in all tests. + // t.Name() must be stored in a variable before t.Parallel() otherwise t.Name() is not + // deterministic. + t.Parallel() + + relayer, channelA := s.CreateTransferPath(testName) + + chainA, chainB := s.GetChains() + + chainBVersion := chainB.Config().Images[0].Version + chainADenom := chainA.Config().Denom + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainAAddress := chainAWallet.FormattedAddress() + + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + chainBAddress := chainBWallet.FormattedAddress() + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("native IBC token transfer from chainA to chainB, sender is source of tokens", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferAmount(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "") + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + + // TODO: cannot query total escrow if tests in parallel are using the same denom. + // if testvalues.TotalEscrowFeatureReleases.IsSupported(chainAVersion) { + // actualTotalEscrow, err := query.TotalEscrowForDenom(ctx, chainA, chainADenom) + // s.Require().NoError(err) + // + // expectedTotalEscrow := sdk.NewCoin(chainADenom, sdkmath.NewInt(testvalues.IBCTransferAmount)) + // s.Require().Equal(expectedTotalEscrow, actualTotalEscrow) + // } + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) + + t.Run("packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + + actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance.Int64()) + }) + + if testvalues.TokenMetadataFeatureReleases.IsSupported(chainBVersion) { + t.Run("metadata for IBC denomination exists on chainB", func(t *testing.T) { + s.AssertHumanReadableDenom(ctx, chainB, chainADenom, channelA) + }) + } + + t.Run("non-native IBC token transfer from chainB to chainA, receiver is source of tokens", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainB, chainBWallet, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, testvalues.DefaultTransferAmount(chainBIBCToken.IBCDenom()), chainBAddress, chainAAddress, s.GetTimeoutHeight(ctx, chainA), 0, "") + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + s.Require().NoError(err) + + s.Require().Equal(sdkmath.ZeroInt(), actualBalance) + + // https://github.com/cosmos/ibc-go/issues/6742 + // if testvalues.TotalEscrowFeatureReleases.IsSupported(chainBVersion) { + // actualTotalEscrow, err := query.TotalEscrowForDenom(ctx, chainB, chainBIBCToken.IBCDenom()) + // s.Require().NoError(err) + // s.Require().Equal(sdk.NewCoin(chainBIBCToken.IBCDenom(), sdkmath.NewInt(0)), actualTotalEscrow) // total escrow is zero because sending chain is not source for tokens + // } + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA, chainB), "failed to wait for blocks") + + t.Run("packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainB, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, 1) + + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount + s.Require().Equal(expected, actualBalance) + }) + + // https://github.com/cosmos/ibc-go/issues/6742 + // if testvalues.TotalEscrowFeatureReleases.IsSupported(chainAVersion) { + // t.Run("tokens are un-escrowed", func(t *testing.T) { + // actualTotalEscrow, err := query.TotalEscrowForDenom(ctx, chainA, chainADenom) + // s.Require().NoError(err) + // s.Require().Equal(sdk.NewCoin(chainADenom, sdkmath.NewInt(0)), actualTotalEscrow) // total escrow is zero because tokens have come back + // }) + // } +} + +// TestMsgTransfer_Fails_InvalidAddress attempts to send an IBC transfer to an invalid address and ensures +// that the tokens on the sending chain are unescrowed. +func (s *TransferTestSuite) TestMsgTransfer_Fails_InvalidAddress() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + t.Parallel() + relayer, channelA := s.CreateTransferPath(testName) + + chainA, chainB := s.GetChains() + + chainADenom := chainA.Config().Denom + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainAAddress := chainAWallet.FormattedAddress() + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("native IBC token transfer from chainA to invalid address", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferAmount(chainADenom), chainAAddress, testvalues.InvalidAddress, s.GetTimeoutHeight(ctx, chainB), 0, "") + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + }) + + t.Run("token transfer amount unescrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount + s.Require().Equal(expected, actualBalance) + }) +} + +func (s *TransferTestSuite) TestMsgTransfer_Timeout_Nonincentivized() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + t.Parallel() + relayer, channelA := s.CreateTransferPath(testName) + + chainA, _ := s.GetChains() + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + + chainBWalletAmount := ibc.WalletAmount{ + Address: chainBWallet.FormattedAddress(), // destination address + Denom: chainA.Config().Denom, + Amount: sdkmath.NewInt(testvalues.IBCTransferAmount), + } + + t.Run("IBC transfer packet timesout", func(t *testing.T) { + tx, err := chainA.SendIBCTransfer(ctx, channelA.ChannelID, chainAWallet.KeyName(), chainBWalletAmount, ibc.TransferOptions{Timeout: testvalues.ImmediatelyTimeout()}) + s.Require().NoError(err) + s.Require().NoError(tx.Validate(), "source ibc transfer tx is invalid") + time.Sleep(time.Nanosecond * 1) // want it to timeout immediately + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("ensure escrowed tokens have been refunded to sender due to timeout", func(t *testing.T) { + // ensure destination address did not receive any tokens + bal, err := s.GetChainBNativeBalance(ctx, chainBWallet) + s.Require().NoError(err) + s.Require().Equal(testvalues.StartingTokenAmount, bal) + + // ensure that the sender address has been successfully refunded the full amount + bal, err = s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + s.Require().Equal(testvalues.StartingTokenAmount, bal) + }) +} + +// This can be used to test sending with a transfer packet with a memo given different combinations of +// ibc-go versions. +// +// TestMsgTransfer_WithMemo will test sending IBC transfers from chainA to chainB +// If the chains contain a version of FungibleTokenPacketData with memo, both send and receive should succeed. +// If one of the chains contains a version of FungibleTokenPacketData without memo, then receiving a packet with +// memo should fail in that chain +func (s *TransferTestSuite) TestMsgTransfer_WithMemo() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + t.Parallel() + relayer, channelA := s.CreateTransferPath(testName) + + chainA, chainB := s.GetChains() + + chainADenom := chainA.Config().Denom + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainAAddress := chainAWallet.FormattedAddress() + + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + chainBAddress := chainBWallet.FormattedAddress() + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("IBC token transfer with memo from chainA to chainB", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferAmount(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "memo") + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) + + t.Run("packets relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + + s.Require().NoError(err) + s.Require().Equal(testvalues.IBCTransferAmount, actualBalance.Int64()) + }) +} + +// TestMsgTransfer_EntireBalance tests that it is possible to transfer the entire balance +// of a given denom by using types.UnboundedSpendLimit as the amount. +// compatibility:TestMsgTransfer_EntireBalance:from_versions: v7.10.0,v8.7.0,v10.0.0 +func (s *TransferTestSuite) TestMsgTransfer_EntireBalance() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + t.Parallel() + relayer, channelA := s.CreateTransferPath(testName) + + chainA, chainB := s.GetChains() + + chainADenom := chainA.Config().Denom + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainAAddress := chainAWallet.FormattedAddress() + + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + chainBAddress := chainBWallet.FormattedAddress() + + coinFromA := testvalues.DefaultTransferAmount(chainADenom) + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("IBC token transfer from chainA to chainB", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, coinFromA, chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainA), 0, "") + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + s.Require().Equal(testvalues.StartingTokenAmount-coinFromA.Amount.Int64(), actualBalance) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) + t.Run("packets relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + + s.Require().NoError(err) + s.Require().Equal(coinFromA.Amount.Int64(), actualBalance.Int64()) + + actualBalance, err = query.Balance(ctx, chainA, chainAAddress, chainADenom) + + s.Require().NoError(err) + s.Require().Equal(testvalues.StartingTokenAmount-coinFromA.Amount.Int64(), actualBalance.Int64()) + }) + + t.Run("send entire balance from B to A", func(t *testing.T) { + transferCoin := sdk.NewCoin(chainBIBCToken.IBCDenom(), transfertypes.UnboundedSpendLimit()) + + transferTxResp := s.Transfer(ctx, chainB, chainBWallet, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, transferCoin, chainBAddress, chainAAddress, s.GetTimeoutHeight(ctx, chainB), 0, "") + s.AssertTxSuccess(transferTxResp) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA, chainB), "failed to wait for blocks") + + t.Run("packets relayed", func(t *testing.T) { + // test that chainA has the entire balance back of its native token. + s.AssertPacketRelayed(ctx, chainB, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, 1) + actualBalance, err := query.Balance(ctx, chainA, chainAAddress, chainADenom) + + s.Require().NoError(err) + s.Require().Equal(testvalues.StartingTokenAmount, actualBalance.Int64()) + + // test that chainB has a zero balance of chainA's IBC token denom. + actualBalance, err = query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + + s.Require().NoError(err) + s.Require().Zero(actualBalance.Int64()) + }) +} diff --git a/e2e/tests/transfer/localhost_test.go b/e2e/tests/transfer/localhost_test.go new file mode 100644 index 0000000..2b116fe --- /dev/null +++ b/e2e/tests/transfer/localhost_test.go @@ -0,0 +1,174 @@ +//go:build !test_e2e + +package transfer + +import ( + "context" + "testing" + + test "github.com/cosmos/interchaintest/v10/testutil" + testifysuite "github.com/stretchr/testify/suite" + + "github.com/cosmos/ibc-go/e2e/testsuite" + "github.com/cosmos/ibc-go/e2e/testsuite/query" + "github.com/cosmos/ibc-go/e2e/testvalues" + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + localhost "github.com/cosmos/ibc-go/v10/modules/light-clients/09-localhost" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +// compatibility:from_version: v7.10.0 +func TestTransferLocalhostTestSuite(t *testing.T) { + testifysuite.Run(t, new(LocalhostTransferTestSuite)) +} + +type LocalhostTransferTestSuite struct { + testsuite.E2ETestSuite +} + +// SetupSuite sets up chains for the current test suite +func (s *LocalhostTransferTestSuite) SetupSuite() { + s.SetupChains(context.TODO(), 1, nil) +} + +// TestMsgTransfer_Localhost creates two wallets on a single chain and performs MsgTransfers back and forth +// to ensure ibc functions as expected on localhost. This test is largely the same as TestMsgTransfer_Succeeds_Nonincentivized +// except that chain B is replaced with an additional wallet on chainA. +func (s *LocalhostTransferTestSuite) TestMsgTransfer_Localhost() { + t := s.T() + ctx := context.TODO() + + chains := s.GetAllChains() + chainA := chains[0] + + channelVersion := transfertypes.V1 + + chainADenom := chainA.Config().Denom + + rlyWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + userAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + userBWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + + var ( + msgChanOpenInitRes channeltypes.MsgChannelOpenInitResponse + msgChanOpenTryRes channeltypes.MsgChannelOpenTryResponse + ack []byte + packet channeltypes.Packet + ) + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA), "failed to wait for blocks") + + t.Run("channel open init localhost", func(t *testing.T) { + msgChanOpenInit := channeltypes.NewMsgChannelOpenInit( + transfertypes.PortID, channelVersion, + channeltypes.UNORDERED, []string{exported.LocalhostConnectionID}, + transfertypes.PortID, rlyWallet.FormattedAddress(), + ) + + s.Require().NoError(msgChanOpenInit.ValidateBasic()) + + txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenInit) + s.AssertTxSuccess(txResp) + + s.Require().NoError(testsuite.UnmarshalMsgResponses(txResp, &msgChanOpenInitRes)) + }) + + t.Run("channel open try localhost", func(t *testing.T) { + msgChanOpenTry := channeltypes.NewMsgChannelOpenTry( + transfertypes.PortID, channelVersion, + channeltypes.UNORDERED, []string{exported.LocalhostConnectionID}, + transfertypes.PortID, msgChanOpenInitRes.ChannelId, + channelVersion, localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(), + ) + + txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenTry) + s.AssertTxSuccess(txResp) + + s.Require().NoError(testsuite.UnmarshalMsgResponses(txResp, &msgChanOpenTryRes)) + }) + + t.Run("channel open ack localhost", func(t *testing.T) { + msgChanOpenAck := channeltypes.NewMsgChannelOpenAck( + transfertypes.PortID, msgChanOpenInitRes.ChannelId, + msgChanOpenTryRes.ChannelId, channelVersion, + localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(), + ) + + txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenAck) + s.AssertTxSuccess(txResp) + }) + + t.Run("channel open confirm localhost", func(t *testing.T) { + msgChanOpenConfirm := channeltypes.NewMsgChannelOpenConfirm( + transfertypes.PortID, msgChanOpenTryRes.ChannelId, + localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(), + ) + + txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenConfirm) + s.AssertTxSuccess(txResp) + }) + + t.Run("query localhost transfer channel ends", func(t *testing.T) { + channelEndA, err := query.Channel(ctx, chainA, transfertypes.PortID, msgChanOpenInitRes.ChannelId) + s.Require().NoError(err) + s.Require().NotNil(channelEndA) + + channelEndB, err := query.Channel(ctx, chainA, transfertypes.PortID, msgChanOpenTryRes.ChannelId) + s.Require().NoError(err) + s.Require().NotNil(channelEndB) + + s.Require().Equal(channelEndA.ConnectionHops, channelEndB.ConnectionHops) + }) + + t.Run("send packet localhost ibc transfer", func(t *testing.T) { + var err error + txResp := s.Transfer(ctx, chainA, userAWallet, transfertypes.PortID, msgChanOpenInitRes.ChannelId, testvalues.DefaultTransferAmount(chainADenom), userAWallet.FormattedAddress(), userBWallet.FormattedAddress(), clienttypes.NewHeight(1, 500), 0, "") + s.AssertTxSuccess(txResp) + + packet, err = ibctesting.ParseV1PacketFromEvents(txResp.Events) + s.Require().NoError(err) + s.Require().NotNil(packet) + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, userAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + }) + + t.Run("recv packet localhost ibc transfer", func(t *testing.T) { + var err error + msgRecvPacket := channeltypes.NewMsgRecvPacket(packet, localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress()) + + txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgRecvPacket) + s.AssertTxSuccess(txResp) + + ack, err = ibctesting.ParseAckFromEvents(txResp.Events) + s.Require().NoError(err) + s.Require().NotNil(ack) + }) + + t.Run("acknowledge packet localhost ibc transfer", func(t *testing.T) { + msgAcknowledgement := channeltypes.NewMsgAcknowledgement(packet, ack, localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress()) + + txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgAcknowledgement) + s.AssertTxSuccess(txResp) + }) + + t.Run("verify tokens transferred", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, transfertypes.PortID, msgChanOpenInitRes.ChannelId, 1) + + ibcToken := testsuite.GetIBCToken(chainADenom, transfertypes.PortID, msgChanOpenTryRes.ChannelId) + actualBalance, err := query.Balance(ctx, chainA, userBWallet.FormattedAddress(), ibcToken.IBCDenom()) + + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance.Int64()) + }) +} diff --git a/e2e/tests/transfer/send_enabled_test.go b/e2e/tests/transfer/send_enabled_test.go new file mode 100644 index 0000000..f73531b --- /dev/null +++ b/e2e/tests/transfer/send_enabled_test.go @@ -0,0 +1,99 @@ +//go:build !test_e2e + +package transfer + +import ( + "context" + "testing" + + test "github.com/cosmos/interchaintest/v10/testutil" + testifysuite "github.com/stretchr/testify/suite" + + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + paramsproposaltypes "github.com/cosmos/cosmos-sdk/x/params/types/proposal" + + "github.com/cosmos/ibc-go/e2e/testsuite" + "github.com/cosmos/ibc-go/e2e/testsuite/query" + "github.com/cosmos/ibc-go/e2e/testvalues" + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +// compatibility:from_version: v7.10.0 +func TestTransferTestSuiteSendEnabled(t *testing.T) { + testifysuite.Run(t, new(TransferTestSuiteSendEnabled)) +} + +type TransferTestSuiteSendEnabled struct { + transferTester +} + +func (s *TransferTestSuiteSendEnabled) SetupSuite() { + s.SetupChains(context.TODO(), 2, nil, func(options *testsuite.ChainOptions) { + options.RelayerCount = 1 + }) +} + +// TestSendEnabledParam tests changing ics20 SendEnabled parameter +func (s *TransferTestSuiteSendEnabled) TestSendEnabledParam() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + // Note: explicitly not using t.Parallel() in this test as it makes chain wide changes + s.CreateTransferPath(testName) + + chainA, chainB := s.GetChains() + + channelA := s.GetChainAChannelForTest(testName) + chainAVersion := chainA.Config().Images[0].Version + chainADenom := chainA.Config().Denom + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainAAddress := chainAWallet.FormattedAddress() + + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + chainBAddress := chainBWallet.FormattedAddress() + + isSelfManagingParams := testvalues.SelfParamsFeatureReleases.IsSupported(chainAVersion) + + govModuleAddress, err := query.ModuleAccountAddress(ctx, govtypes.ModuleName, chainA) + s.Require().NoError(err) + s.Require().NotNil(govModuleAddress) + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("ensure transfer sending is enabled", func(t *testing.T) { + enabled := s.QueryTransferParams(ctx, chainA).SendEnabled + s.Require().True(enabled) + }) + + t.Run("ensure packets can be sent", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferAmount(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "") + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("change send enabled parameter to disabled", func(t *testing.T) { + if isSelfManagingParams { + msg := transfertypes.NewMsgUpdateParams(govModuleAddress.String(), transfertypes.NewParams(false, true)) + s.ExecuteAndPassGovV1Proposal(ctx, msg, chainA, chainAWallet) + } else { + changes := []paramsproposaltypes.ParamChange{ + paramsproposaltypes.NewParamChange(transfertypes.StoreKey, string(transfertypes.KeySendEnabled), "false"), + } + + proposal := paramsproposaltypes.NewParameterChangeProposal(ibctesting.Title, ibctesting.Description, changes) + s.ExecuteAndPassGovV1Beta1Proposal(ctx, chainA, chainAWallet, proposal) + } + }) + + t.Run("ensure transfer params are disabled", func(t *testing.T) { + enabled := s.QueryTransferParams(ctx, chainA).SendEnabled + s.Require().False(enabled) + }) + + t.Run("ensure ics20 transfer fails", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferAmount(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "") + s.AssertTxFailure(transferTxResp, transfertypes.ErrSendDisabled) + }) +} diff --git a/e2e/tests/transfer/send_receive_test.go b/e2e/tests/transfer/send_receive_test.go new file mode 100644 index 0000000..a370192 --- /dev/null +++ b/e2e/tests/transfer/send_receive_test.go @@ -0,0 +1,155 @@ +//go:build !test_e2e + +package transfer + +import ( + "context" + "testing" + + test "github.com/cosmos/interchaintest/v10/testutil" + testifysuite "github.com/stretchr/testify/suite" + + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + paramsproposaltypes "github.com/cosmos/cosmos-sdk/x/params/types/proposal" + + "github.com/cosmos/ibc-go/e2e/testsuite" + "github.com/cosmos/ibc-go/e2e/testsuite/query" + "github.com/cosmos/ibc-go/e2e/testvalues" + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +// compatibility:from_version: v7.10.0 +func TestTransferTestSuiteSendReceive(t *testing.T) { + testifysuite.Run(t, new(TransferTestSuiteSendReceive)) +} + +type TransferTestSuiteSendReceive struct { + transferTester +} + +func (s *TransferTestSuiteSendReceive) SetupSuite() { + s.SetupChains(context.TODO(), 2, nil, func(options *testsuite.ChainOptions) { + options.RelayerCount = 1 + }) +} + +// TestReceiveEnabledParam tests changing ics20 ReceiveEnabled parameter +func (s *TransferTestSuiteSendReceive) TestReceiveEnabledParam() { + t := s.T() + ctx := context.TODO() + + testName := t.Name() + // Note: explicitly not using t.Parallel() in this test as it makes chain wide changes + s.CreateTransferPath(testName) + + chainA, chainB := s.GetChains() + + relayer := s.GetRelayerForTest(testName) + channelA := s.GetChainAChannelForTest(testName) + + chainAVersion := chainA.Config().Images[0].Version + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + + var ( + chainBDenom = chainB.Config().Denom + chainAIBCToken = testsuite.GetIBCToken(chainBDenom, channelA.PortID, channelA.ChannelID) // IBC token sent to chainA + + chainAAddress = chainAWallet.FormattedAddress() + chainBAddress = chainBWallet.FormattedAddress() + ) + + isSelfManagingParams := testvalues.SelfParamsFeatureReleases.IsSupported(chainAVersion) + + govModuleAddress, err := query.ModuleAccountAddress(ctx, govtypes.ModuleName, chainA) + s.Require().NoError(err) + s.Require().NotNil(govModuleAddress) + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("ensure transfer receive is enabled", func(t *testing.T) { + enabled := s.QueryTransferParams(ctx, chainA).ReceiveEnabled + s.Require().True(enabled) + }) + + t.Run("ensure packets can be received, send from chainB to chainA", func(t *testing.T) { + t.Run("send from chainB to chainA", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainB, chainBWallet, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, testvalues.DefaultTransferAmount(chainBDenom), chainBAddress, chainAAddress, s.GetTimeoutHeight(ctx, chainA), 0, "") + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainBNativeBalance(ctx, chainBWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, 1) + actualBalance, err := query.Balance(ctx, chainA, chainAAddress, chainAIBCToken.IBCDenom()) + + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance.Int64()) + }) + + t.Run("stop relayer", func(t *testing.T) { + s.StopRelayer(ctx, relayer) + }) + }) + + t.Run("change receive enabled parameter to disabled ", func(t *testing.T) { + if isSelfManagingParams { + msg := transfertypes.NewMsgUpdateParams(govModuleAddress.String(), transfertypes.NewParams(false, false)) + s.ExecuteAndPassGovV1Proposal(ctx, msg, chainA, chainAWallet) + } else { + changes := []paramsproposaltypes.ParamChange{ + paramsproposaltypes.NewParamChange(transfertypes.StoreKey, string(transfertypes.KeyReceiveEnabled), "false"), + } + + proposal := paramsproposaltypes.NewParameterChangeProposal(ibctesting.Title, ibctesting.Description, changes) + s.ExecuteAndPassGovV1Beta1Proposal(ctx, chainA, chainAWallet, proposal) + } + }) + + t.Run("ensure transfer params are disabled", func(t *testing.T) { + enabled := s.QueryTransferParams(ctx, chainA).ReceiveEnabled + s.Require().False(enabled) + }) + + t.Run("ensure ics20 transfer fails", func(t *testing.T) { + t.Run("send from chainB to chainA", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainB, chainBWallet, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, testvalues.DefaultTransferAmount(chainBDenom), chainBAddress, chainAAddress, s.GetTimeoutHeight(ctx, chainA), 0, "") + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainBNativeBalance(ctx, chainBWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - (testvalues.IBCTransferAmount * 2) // second send + s.Require().Equal(expected, actualBalance) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("tokens are unescrowed in failed acknowledgement", func(t *testing.T) { + actualBalance, err := s.GetChainBNativeBalance(ctx, chainBWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount // only first send marked + s.Require().Equal(expected, actualBalance) + }) + }) +} diff --git a/e2e/tests/upgrades/genesis_test.go b/e2e/tests/upgrades/genesis_test.go new file mode 100644 index 0000000..ad3771a --- /dev/null +++ b/e2e/tests/upgrades/genesis_test.go @@ -0,0 +1,267 @@ +//go:build !test_e2e + +package upgrades + +import ( + "context" + "testing" + "time" + + "github.com/cosmos/gogoproto/proto" + "github.com/cosmos/interchaintest/v10" + "github.com/cosmos/interchaintest/v10/chain/cosmos" + "github.com/cosmos/interchaintest/v10/ibc" + test "github.com/cosmos/interchaintest/v10/testutil" + "github.com/stretchr/testify/suite" + "go.uber.org/zap" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + "github.com/cosmos/ibc-go/e2e/testsuite" + "github.com/cosmos/ibc-go/e2e/testsuite/query" + "github.com/cosmos/ibc-go/e2e/testvalues" + controllertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +// compatibility:from_version: v8.7.0 +func TestGenesisTestSuite(t *testing.T) { + suite.Run(t, new(GenesisTestSuite)) +} + +type GenesisTestSuite struct { + testsuite.E2ETestSuite +} + +// SetupSuite sets up chains for the current test suite +func (s *GenesisTestSuite) SetupSuite() { + s.SetupChains(context.TODO(), 2, nil) +} + +// TODO: this configuration was originally being applied to `GetChains` in the test body, but it is not +// actually being propagated correctly. If we want to apply the configuration, we can uncomment this code +// however the test actually fails when this is done. +// func (s *GenesisTestSuite) SetupSuite() { +// configFileOverrides := make(map[string]any) +// appTomlOverrides := make(test.Toml) +// +// appTomlOverrides["halt-height"] = haltHeight +// configFileOverrides["config/app.toml"] = appTomlOverrides +// +// s.SetupChains(context.TODO(), nil, func(options *testsuite.ChainOptions) { +// // create chains with specified chain configuration options +// options.ChainSpecs[0].ConfigFileOverrides = configFileOverrides +// }) +// } + +func (s *GenesisTestSuite) TestIBCGenesis() { + t := s.T() + + haltHeight := int64(100) + + chainA, chainB := s.GetChains() + + ctx := context.Background() + testName := t.Name() + + relayer := s.CreateDefaultPaths(testName) + channelA := s.GetChainAChannelForTest(testName) + + var ( + chainADenom = chainA.Config().Denom + chainBIBCToken = testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) // IBC token sent to chainB + ) + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainAAddress := chainAWallet.FormattedAddress() + + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + chainBAddress := chainBWallet.FormattedAddress() + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("ics20: native IBC token transfer from chainA to chainB, sender is source of tokens", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferAmount(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "") + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("ics20: tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + }) + + // setup 2 accounts: controller account on chain A, a second chain B account. + // host account will be created when the ICA is registered + controllerAccount := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + controllerAddress := controllerAccount.FormattedAddress() + chainBAccount := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + var hostAccount string + + t.Run("ics27: broadcast MsgRegisterInterchainAccount", func(t *testing.T) { + // explicitly set the version string because we don't want to use incentivized channels. + version := icatypes.NewDefaultMetadataString(ibctesting.FirstConnectionID, ibctesting.FirstConnectionID) + msgRegisterAccount := controllertypes.NewMsgRegisterInterchainAccount(ibctesting.FirstConnectionID, controllerAddress, version, channeltypes.ORDERED) + + txResp := s.BroadcastMessages(ctx, chainA, controllerAccount, msgRegisterAccount) + s.AssertTxSuccess(txResp) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("ics20: packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + + actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance.Int64()) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 10, chainA, chainB), "failed to wait for blocks") + + t.Run("ics27: verify interchain account", func(t *testing.T) { + res, err := query.GRPCQuery[controllertypes.QueryInterchainAccountResponse](ctx, chainA, &controllertypes.QueryInterchainAccountRequest{ + Owner: controllerAddress, + ConnectionId: ibctesting.FirstConnectionID, + }) + s.Require().NoError(err) + s.Require().NotZero(len(res.Address)) + + hostAccount = res.Address + s.Require().NotEmpty(hostAccount) + + channels, err := relayer.GetChannels(ctx, s.GetRelayerExecReporter(), chainA.Config().ChainID) + s.Require().NoError(err) + s.Require().Equal(len(channels), 2) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 10, chainA, chainB), "failed to wait for blocks") + + t.Run("Halt chain and export genesis", func(t *testing.T) { + s.HaltChainAndExportGenesis(ctx, chainA.(*cosmos.CosmosChain), haltHeight) + }) + + t.Run("ics20: native IBC token transfer from chainA to chainB, sender is source of tokens", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferAmount(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "") + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("ics20: tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - 2*testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + }) + + t.Run("ics27: interchain account executes a bank transfer on behalf of the corresponding owner account", func(t *testing.T) { + t.Run("fund interchain account wallet", func(t *testing.T) { + // fund the host account so it has some $$ to send + err := chainB.SendFunds(ctx, interchaintest.FaucetAccountKeyName, ibc.WalletAmount{ + Address: hostAccount, + Amount: sdkmath.NewInt(testvalues.StartingTokenAmount), + Denom: chainB.Config().Denom, + }) + s.Require().NoError(err) + }) + + t.Run("broadcast MsgSendTx", func(t *testing.T) { + // assemble bank transfer message from host account to user account on host chain + msgSend := &banktypes.MsgSend{ + FromAddress: hostAccount, + ToAddress: chainBAccount.FormattedAddress(), + Amount: sdk.NewCoins(testvalues.DefaultTransferAmount(chainB.Config().Denom)), + } + + cdc := testsuite.Codec() + bz, err := icatypes.SerializeCosmosTx(cdc, []proto.Message{msgSend}, icatypes.EncodingProtobuf) + s.Require().NoError(err) + + packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: bz, + Memo: "e2e", + } + + msgSendTx := controllertypes.NewMsgSendTx(controllerAccount.FormattedAddress(), ibctesting.FirstConnectionID, uint64(time.Hour.Nanoseconds()), packetData) + + resp := s.BroadcastMessages( + ctx, + chainA, + controllerAccount, + msgSendTx, + ) + + s.AssertTxSuccess(resp) + + s.Require().NoError(test.WaitForBlocks(ctx, 10, chainA, chainB)) + }) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA, chainB), "failed to wait for blocks") +} + +func (s *GenesisTestSuite) HaltChainAndExportGenesis(ctx context.Context, chain *cosmos.CosmosChain, haltHeight int64) { + timeoutCtx, timeoutCtxCancel := context.WithTimeout(ctx, time.Minute*2) + defer timeoutCtxCancel() + + err := test.WaitForBlocks(timeoutCtx, int(haltHeight), chain) + s.Require().Error(err, "chain did not halt at halt height") + + err = chain.StopAllNodes(ctx) + s.Require().NoError(err, "error stopping node(s)") + + state, err := chain.ExportState(ctx, haltHeight) + s.Require().NoError(err) + + appTomlOverrides := make(test.Toml) + + appTomlOverrides["halt-height"] = 0 + + for _, node := range chain.Nodes() { + err := node.OverwriteGenesisFile(ctx, []byte(state)) + s.Require().NoError(err) + } + + for _, node := range chain.Nodes() { + err := test.ModifyTomlConfigFile( + ctx, + zap.NewExample(), + node.DockerClient, + node.TestName, + node.VolumeName, + "config/app.toml", + appTomlOverrides, + ) + s.Require().NoError(err) + + _, _, err = node.ExecBin(ctx, "comet", "unsafe-reset-all") + s.Require().NoError(err) + } + + err = chain.StartAllNodes(ctx) + s.Require().NoError(err) + + timeoutCtx, timeoutCtxCancel = context.WithTimeout(ctx, time.Minute*2) + defer timeoutCtxCancel() + + err = test.WaitForBlocks(timeoutCtx, int(blocksAfterUpgrade), chain) + s.Require().NoError(err, "chain did not produce blocks after halt") + + height, err := chain.Height(ctx) + s.Require().NoError(err, "error fetching height after halt") + + s.Require().Greater(height, haltHeight, "height did not increment after halt") +} diff --git a/e2e/tests/upgrades/upgrade_test.go b/e2e/tests/upgrades/upgrade_test.go new file mode 100644 index 0000000..9734be7 --- /dev/null +++ b/e2e/tests/upgrades/upgrade_test.go @@ -0,0 +1,995 @@ +//go:build !test_e2e + +package upgrades + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/cosmos/gogoproto/proto" + interchaintest "github.com/cosmos/interchaintest/v10" + "github.com/cosmos/interchaintest/v10/chain/cosmos" + "github.com/cosmos/interchaintest/v10/ibc" + test "github.com/cosmos/interchaintest/v10/testutil" + testifysuite "github.com/stretchr/testify/suite" + + sdkmath "cosmossdk.io/math" + upgradetypes "cosmossdk.io/x/upgrade/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + e2erelayer "github.com/cosmos/ibc-go/e2e/relayer" + "github.com/cosmos/ibc-go/e2e/testsuite" + "github.com/cosmos/ibc-go/e2e/testsuite/query" + "github.com/cosmos/ibc-go/e2e/testvalues" + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + v7migrations "github.com/cosmos/ibc-go/v10/modules/core/02-client/migrations/v7" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine" + localhost "github.com/cosmos/ibc-go/v10/modules/light-clients/09-localhost" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +const ( + haltHeightOffset = int64(30) + blocksAfterUpgrade = uint64(10) +) + +func TestUpgradeTestSuite(t *testing.T) { + testCfg := testsuite.LoadConfig() + if testCfg.UpgradePlanName == "" { + t.Fatalf("%s must be set when running an upgrade test", testsuite.ChainUpgradePlanEnv) + } + + testifysuite.Run(t, new(UpgradeTestSuite)) +} + +type UpgradeTestSuite struct { + testsuite.E2ETestSuite +} + +// SetupSuite sets up chains for the current test suite +func (s *UpgradeTestSuite) SetupSuite() { + s.SetupChains(context.TODO(), 2, nil) +} + +func (s *UpgradeTestSuite) CreateUpgradeTestPath(testName string) (ibc.Relayer, ibc.ChannelOutput) { + return s.CreatePaths(ibc.DefaultClientOpts(), s.TransferChannelOptions(), testName), s.GetChainAChannelForTest(testName) +} + +// UpgradeChain upgrades a chain to a specific version using the planName provided. +// The software upgrade proposal is broadcast by the provided wallet. +func (s *UpgradeTestSuite) UpgradeChain(ctx context.Context, chain *cosmos.CosmosChain, wallet ibc.Wallet, planName, currentVersion, upgradeVersion string) { + height, err := chain.GetNode().Height(ctx) + s.Require().NoError(err, "error fetching height before upgrade") + + haltHeight := height + haltHeightOffset + plan := upgradetypes.Plan{ + Name: planName, + Height: haltHeight, + Info: fmt.Sprintf("upgrade version test from %s to %s", currentVersion, upgradeVersion), + } + + if testvalues.GovV1MessagesFeatureReleases.IsSupported(chain.Config().Images[0].Version) { + msgSoftwareUpgrade := &upgradetypes.MsgSoftwareUpgrade{ + Plan: plan, + Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), + } + + s.ExecuteAndPassGovV1Proposal(ctx, msgSoftwareUpgrade, chain, wallet) + } else { + upgradeProposal := upgradetypes.NewSoftwareUpgradeProposal(fmt.Sprintf("upgrade from %s to %s", currentVersion, upgradeVersion), "upgrade chain E2E test", plan) + s.ExecuteAndPassGovV1Beta1Proposal(ctx, chain, wallet, upgradeProposal) + } + + err = test.WaitForCondition(time.Minute*2, time.Second*2, func() (bool, error) { + status, err := chain.GetNode().Client.Status(ctx) + if err != nil { + return false, err + } + return status.SyncInfo.LatestBlockHeight >= haltHeight, nil + }) + s.Require().NoError(err, "failed to wait for chain to halt") + + var allNodes []test.ChainHeighter + for _, node := range chain.Nodes() { + allNodes = append(allNodes, node) + } + + err = test.WaitForInSync(ctx, chain, allNodes...) + s.Require().NoError(err, "error waiting for node(s) to sync") + + err = chain.StopAllNodes(ctx) + s.Require().NoError(err, "error stopping node(s)") + + repository := chain.Nodes()[0].Image.Repository + chain.UpgradeVersion(ctx, s.DockerClient, repository, upgradeVersion) + + err = chain.StartAllNodes(ctx) + s.Require().NoError(err, "error starting upgraded node(s)") + + timeoutCtx, timeoutCtxCancel := context.WithTimeout(ctx, time.Minute*2) + defer timeoutCtxCancel() + + err = test.WaitForBlocks(timeoutCtx, int(blocksAfterUpgrade), chain) + s.Require().NoError(err, "chain did not produce blocks after upgrade") + + height, err = chain.Height(ctx) + s.Require().NoError(err, "error fetching height after upgrade") + + s.Require().Greater(height, haltHeight, "height did not increment after upgrade") + + // In case the query paths have changed after the upgrade, we need to repopulate them + err = query.PopulateQueryReqToPath(ctx, chain) + s.Require().NoError(err, "error populating query paths after upgrade") +} + +func (s *UpgradeTestSuite) TestIBCChainUpgrade() { + t := s.T() + testCfg := testsuite.LoadConfig() + + ctx := context.Background() + testName := t.Name() + relayer, channelA := s.CreateUpgradeTestPath(testName) + + chainA, chainB := s.GetChains() + + var ( + chainADenom = chainA.Config().Denom + chainBIBCToken = testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) // IBC token sent to chainB + + chainBDenom = chainB.Config().Denom + chainAIBCToken = testsuite.GetIBCToken(chainBDenom, channelA.PortID, channelA.ChannelID) // IBC token sent to chainA + ) + + // create separate user specifically for the upgrade proposal to more easily verify starting + // and end balances of the chainA users. + chainAUpgradeProposalWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainAAddress := chainAWallet.FormattedAddress() + + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + chainBAddress := chainBWallet.FormattedAddress() + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("native IBC token transfer from chainA to chainB, sender is source of tokens", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferAmount(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "") + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + + actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance.Int64()) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA, chainB), "failed to wait for blocks") + + t.Run("upgrade chainA", func(t *testing.T) { + s.UpgradeChain(ctx, chainA.(*cosmos.CosmosChain), chainAUpgradeProposalWallet, testCfg.GetUpgradeConfig().PlanName, testCfg.ChainConfigs[0].Tag, testCfg.GetUpgradeConfig().Tag) + }) + + t.Run("restart relayer", func(t *testing.T) { + s.StopRelayer(ctx, relayer) + s.StartRelayer(relayer, testName) + }) + + t.Run("native IBC token transfer from chainA to chainB, sender is source of tokens", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferAmount(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "") + s.AssertTxSuccess(transferTxResp) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA, chainB), "failed to wait for blocks") + + t.Run("packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 2) + actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount * 2 + s.Require().Equal(expected, actualBalance.Int64()) + }) + + t.Run("ensure packets can be received, send from chainB to chainA", func(t *testing.T) { + t.Run("send from chainB to chainA", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainB, chainBWallet, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, testvalues.DefaultTransferAmount(chainBDenom), chainBAddress, chainAAddress, s.GetTimeoutHeight(ctx, chainA), 0, "") + s.AssertTxSuccess(transferTxResp) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA, chainB), "failed to wait for blocks") + + t.Run("packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, 1) + + actualBalance, err := query.Balance(ctx, chainA, chainAAddress, chainAIBCToken.IBCDenom()) + + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance.Int64()) + }) + }) +} + +func (s *UpgradeTestSuite) TestChainUpgrade() { + t := s.T() + + ctx := context.Background() + + testName := t.Name() + s.CreateUpgradeTestPath(testName) + + // TODO(chatton): this test is still creating a relayer and a channel, but it is not using them. + chain := s.GetAllChains()[0] + + userWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + userWalletAddr := userWallet.FormattedAddress() + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chain), "failed to wait for blocks") + + t.Run("send funds to test wallet", func(t *testing.T) { + err := chain.SendFunds(ctx, interchaintest.FaucetAccountKeyName, ibc.WalletAmount{ + Address: userWalletAddr, + Amount: sdkmath.NewInt(testvalues.StartingTokenAmount), + Denom: chain.Config().Denom, + }) + s.Require().NoError(err) + }) + + t.Run("verify tokens sent", func(t *testing.T) { + balance, err := query.Balance(ctx, chain, userWalletAddr, chain.Config().Denom) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount * 2 + s.Require().Equal(expected, balance) + }) + + t.Run("upgrade chain", func(t *testing.T) { + testCfg := testsuite.LoadConfig() + proposerWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + + s.UpgradeChain(ctx, chain.(*cosmos.CosmosChain), proposerWallet, testCfg.GetUpgradeConfig().PlanName, testCfg.ChainConfigs[0].Tag, testCfg.GetUpgradeConfig().Tag) + }) + + t.Run("send funds to test wallet", func(t *testing.T) { + err := chain.SendFunds(ctx, interchaintest.FaucetAccountKeyName, ibc.WalletAmount{ + Address: userWalletAddr, + Amount: sdkmath.NewInt(testvalues.StartingTokenAmount), + Denom: chain.Config().Denom, + }) + s.Require().NoError(err) + }) + + t.Run("verify tokens sent", func(t *testing.T) { + balance, err := query.Balance(ctx, chain, userWalletAddr, chain.Config().Denom) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount * 3 + s.Require().Equal(expected, balance) + }) +} + +// TestV6ToV7ChainUpgrade will test that an upgrade from a v6 ibc-go binary to a v7 ibc-go binary is successful +// and that the automatic migrations associated with the 02-client module are performed. Namely that the solo machine +// proto definition is migrated in state from the v2 to v3 definition. This is checked by creating a solo machine client +// before the upgrade and asserting that its TypeURL has been changed after the upgrade. The test also ensure packets +// can be sent before and after the upgrade without issue +func (s *UpgradeTestSuite) TestV6ToV7ChainUpgrade() { + t := s.T() + testCfg := testsuite.LoadConfig() + + ctx := context.Background() + testName := t.Name() + + relayer, channelA := s.CreateUpgradeTestPath(testName) + + chainA, chainB := s.GetChains() + + var ( + chainADenom = chainA.Config().Denom + chainBIBCToken = testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) // IBC token sent to chainB + ) + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainAAddress := chainAWallet.FormattedAddress() + + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + chainBAddress := chainBWallet.FormattedAddress() + + // create second tendermint client + createClientOptions := ibc.CreateClientOptions{ + TrustingPeriod: ibctesting.TrustingPeriod.String(), + } + + s.SetupClients(ctx, relayer, createClientOptions) + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("check that both tendermint clients are active", func(t *testing.T) { + status, err := query.ClientStatus(ctx, chainA, testvalues.TendermintClientID(0)) + s.Require().NoError(err) + s.Require().Equal(exported.Active.String(), status) + + status, err = query.ClientStatus(ctx, chainA, testvalues.TendermintClientID(1)) + s.Require().NoError(err) + s.Require().Equal(exported.Active.String(), status) + }) + + // create solo machine client using the solomachine implementation from ibctesting + // TODO: the solomachine clientID should be updated when after fix of this issue: https://github.com/cosmos/ibc-go/issues/2907 + solo := ibctesting.NewSolomachine(t, testsuite.Codec(), "solomachine", "testing", 1) + + legacyConsensusState := &v7migrations.ConsensusState{ + PublicKey: solo.ConsensusState().PublicKey, + Diversifier: solo.ConsensusState().Diversifier, + Timestamp: solo.ConsensusState().Timestamp, + } + + legacyClientState := &v7migrations.ClientState{ + Sequence: solo.ClientState().Sequence, + IsFrozen: solo.ClientState().IsFrozen, + ConsensusState: legacyConsensusState, + AllowUpdateAfterProposal: true, + } + + msgCreateSoloMachineClient, err := clienttypes.NewMsgCreateClient(legacyClientState, legacyConsensusState, chainAAddress) + s.Require().NoError(err) + + resp := s.BroadcastMessages( + ctx, + chainA.(*cosmos.CosmosChain), + chainAWallet, + msgCreateSoloMachineClient, + ) + + s.AssertTxSuccess(resp) + + t.Run("check that the solomachine is now active and that the clientstate is a pre-upgrade v2 solomachine clientstate", func(t *testing.T) { + status, err := query.ClientStatus(ctx, chainA, testvalues.SolomachineClientID(2)) + s.Require().NoError(err) + s.Require().Equal(exported.Active.String(), status) + + res, err := s.ClientState(ctx, chainA, testvalues.SolomachineClientID(2)) + s.Require().NoError(err) + s.Require().Equal(fmt.Sprint("/", proto.MessageName(&v7migrations.ClientState{})), res.ClientState.TypeUrl) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("IBC token transfer from chainA to chainB, sender is source of tokens", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferAmount(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "") + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + + actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance.Int64()) + }) + + // create separate user specifically for the upgrade proposal to more easily verify starting + // and end balances of the chainA users. + chainAUpgradeProposalWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + + t.Run("upgrade chainA", func(t *testing.T) { + s.UpgradeChain(ctx, chainA.(*cosmos.CosmosChain), chainAUpgradeProposalWallet, testCfg.GetUpgradeConfig().PlanName, testCfg.ChainConfigs[0].Tag, testCfg.GetUpgradeConfig().Tag) + }) + + // see this issue https://github.com/informalsystems/hermes/issues/3579 + // this restart is a temporary workaround to a limitation in hermes requiring a restart + // in some cases after an upgrade. + tc := testsuite.LoadConfig() + if tc.GetActiveRelayerConfig().ID == e2erelayer.Hermes { + s.RestartRelayer(ctx, relayer, testName) + } + + t.Run("check that the tendermint clients are active again after upgrade", func(t *testing.T) { + status, err := query.ClientStatus(ctx, chainA, testvalues.TendermintClientID(0)) + s.Require().NoError(err) + s.Require().Equal(exported.Active.String(), status) + + status, err = query.ClientStatus(ctx, chainA, testvalues.TendermintClientID(1)) + s.Require().NoError(err) + s.Require().Equal(exported.Active.String(), status) + }) + + t.Run("IBC token transfer from chainA to chainB, to make sure the upgrade did not break the packet flow", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferAmount(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB.(*cosmos.CosmosChain)), 0, "") + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + + actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount * 2 + s.Require().Equal(expected, actualBalance.Int64()) + }) + + t.Run("check that the v2 solo machine clientstate has been updated to the v3 solo machine clientstate", func(t *testing.T) { + res, err := s.ClientState(ctx, chainA, testvalues.SolomachineClientID(2)) + s.Require().NoError(err) + s.Require().Equal(fmt.Sprint("/", proto.MessageName(&solomachine.ClientState{})), res.ClientState.TypeUrl) + }) +} + +func (s *UpgradeTestSuite) TestV7ToV7_1ChainUpgrade() { + t := s.T() + testCfg := testsuite.LoadConfig() + + ctx := context.Background() + testName := t.Name() + + relayer, channelA := s.CreateUpgradeTestPath(testName) + + chainA, chainB := s.GetChains() + + chainADenom := chainA.Config().Denom + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainAAddress := chainAWallet.FormattedAddress() + + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + chainBAddress := chainBWallet.FormattedAddress() + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("transfer native tokens from chainA to chainB", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferAmount(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB.(*cosmos.CosmosChain)), 0, "") + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) + + t.Run("packet is relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + + actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance.Int64()) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA), "failed to wait for blocks") + + t.Run("upgrade chain", func(t *testing.T) { + govProposalWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + s.UpgradeChain(ctx, chainA.(*cosmos.CosmosChain), govProposalWallet, testCfg.GetUpgradeConfig().PlanName, testCfg.ChainConfigs[0].Tag, testCfg.GetUpgradeConfig().Tag) + }) + + t.Run("ensure the localhost client is active and sentinel connection is stored in state", func(t *testing.T) { + localhostClientID := exported.LocalhostClientID + if !testvalues.LocalhostWithDashFeatureReleases.IsSupported(chainA.Config().Images[0].Version) { + localhostClientID = exported.Localhost + } + status, err := query.ClientStatus(ctx, chainA, localhostClientID) + s.Require().NoError(err) + s.Require().Equal(exported.Active.String(), status) + + connectionResp, err := query.GRPCQuery[connectiontypes.QueryConnectionResponse](ctx, chainA, &connectiontypes.QueryConnectionRequest{ConnectionId: exported.LocalhostConnectionID}) + s.Require().NoError(err) + s.Require().Equal(connectiontypes.OPEN, connectionResp.Connection.State) + s.Require().Equal(localhostClientID, connectionResp.Connection.ClientId) + s.Require().Equal(localhostClientID, connectionResp.Connection.Counterparty.ClientId) + s.Require().Equal(exported.LocalhostConnectionID, connectionResp.Connection.Counterparty.ConnectionId) + }) + + t.Run("ensure escrow amount for native denom is stored in state", func(t *testing.T) { + actualTotalEscrow, err := query.TotalEscrowForDenom(ctx, chainA, chainADenom) + s.Require().NoError(err) + + expectedTotalEscrow := sdk.NewCoin(chainADenom, sdkmath.NewInt(testvalues.IBCTransferAmount)) + s.Require().Equal(expectedTotalEscrow, actualTotalEscrow) // migration has run and total escrow amount has been set + }) + + t.Run("IBC token transfer from chainA to chainB, to make sure the upgrade did not break the packet flow", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferAmount(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "") + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + + actualBalance, err := chainB.GetBalance(ctx, chainBAddress, chainBIBCToken.IBCDenom()) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount * 2 + s.Require().Equal(expected, actualBalance.Int64()) + }) +} + +func (s *UpgradeTestSuite) TestV7ToV8ChainUpgrade() { + t := s.T() + testCfg := testsuite.LoadConfig() + + ctx := context.Background() + testName := t.Name() + + relayer, channelA := s.CreateUpgradeTestPath(testName) + + chainA, chainB := s.GetChains() + + chainADenom := chainA.Config().Denom + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainAAddress := chainAWallet.FormattedAddress() + + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + chainBAddress := chainBWallet.FormattedAddress() + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("transfer native tokens from chainA to chainB", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferAmount(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "") + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) + + t.Run("packet is relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + + actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance.Int64()) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA), "failed to wait for blocks") + + t.Run("upgrade chain", func(t *testing.T) { + govProposalWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + s.UpgradeChain(ctx, chainB.(*cosmos.CosmosChain), govProposalWallet, testCfg.GetUpgradeConfig().PlanName, testCfg.ChainConfigs[0].Tag, testCfg.GetUpgradeConfig().Tag) + }) + + t.Run("update params", func(t *testing.T) { + authority, err := query.ModuleAccountAddress(ctx, govtypes.ModuleName, chainB) + s.Require().NoError(err) + s.Require().NotNil(authority) + + msg := clienttypes.NewMsgUpdateParams(authority.String(), clienttypes.NewParams(exported.Tendermint, "some-client")) + s.ExecuteAndPassGovV1Proposal(ctx, msg, chainB, chainBWallet) + }) + + t.Run("query params", func(t *testing.T) { + clientParamsResp, err := query.GRPCQuery[clienttypes.QueryClientParamsResponse](ctx, chainB, &clienttypes.QueryClientParamsRequest{}) + s.Require().NoError(err) + + allowedClients := clientParamsResp.Params.AllowedClients + + s.Require().Len(allowedClients, 2) + s.Require().Contains(allowedClients, exported.Tendermint) + s.Require().Contains(allowedClients, "some-client") + }) + + t.Run("query human readable ibc denom", func(t *testing.T) { + s.AssertHumanReadableDenom(ctx, chainB, chainADenom, channelA) + }) + + t.Run("IBC token transfer from chainA to chainB, to make sure the upgrade did not break the packet flow", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferAmount(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "") + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + + actualBalance, err := chainB.GetBalance(ctx, chainBAddress, chainBIBCToken.IBCDenom()) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount * 2 + s.Require().Equal(expected, actualBalance.Int64()) + }) +} + +func (s *UpgradeTestSuite) TestV8ToV8_1ChainUpgrade() { + t := s.T() + ctx := context.Background() + + testName := t.Name() + relayer := s.CreatePaths(ibc.DefaultClientOpts(), s.TransferChannelOptions(), testName) + + channelA := s.GetChainAChannelForTest(testName) + + chainA, chainB := s.GetChains() + chainADenom := chainA.Config().Denom + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainAAddress := chainAWallet.FormattedAddress() + + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + chainBAddress := chainBWallet.FormattedAddress() + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("transfer native tokens from chainA to chainB", func(t *testing.T) { + txResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferAmount(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "") + s.AssertTxSuccess(txResp) + }) + + t.Run("tokens are escrowed", func(t *testing.T) { + actualBalance, err := s.GetChainANativeBalance(ctx, chainAWallet) + s.Require().NoError(err) + + expected := testvalues.StartingTokenAmount - testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance) + }) + + t.Run("upgrade chain", func(t *testing.T) { + testCfg := testsuite.LoadConfig() + proposalWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + s.UpgradeChain(ctx, chainA.(*cosmos.CosmosChain), proposalWallet, testCfg.GetUpgradeConfig().PlanName, testCfg.ChainConfigs[0].Tag, testCfg.GetUpgradeConfig().Tag) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) + + t.Run("packet is relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + + actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance.Int64()) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA), "failed to wait for blocks") + + t.Run("IBC token transfer from chainA to chainB, to make sure the upgrade did not break the packet flow", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferAmount(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "") + s.AssertTxSuccess(transferTxResp) + }) + + t.Run("packets are relayed", func(t *testing.T) { + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + + actualBalance, err := chainB.GetBalance(ctx, chainBAddress, chainBIBCToken.IBCDenom()) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount * 2 + s.Require().Equal(expected, actualBalance.Int64()) + }) +} + +func (s *UpgradeTestSuite) TestV8ToV10ChainUpgrade() { + t := s.T() + testCfg := testsuite.LoadConfig() + ctx := context.Background() + + testName := t.Name() + + relayer, channelA := s.CreateUpgradeTestPath(testName) + + chainA, chainB := s.GetChains() + chainADenom := chainA.Config().Denom + chainBDenom := chainB.Config().Denom + + chainAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + chainAAddress := chainAWallet.FormattedAddress() + + chainBWallet := s.CreateUserOnChainB(ctx, testvalues.StartingTokenAmount) + chainBAddress := chainBWallet.FormattedAddress() + + chainAIBCToken := testsuite.GetIBCToken(chainBDenom, channelA.PortID, channelA.ChannelID) + chainBIBCToken := testsuite.GetIBCToken(chainADenom, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID) + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("transfer native tokens from chainA to chainB", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferAmount(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "") + s.AssertTxSuccess(transferTxResp) + + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 1) + + actualBalance, err := query.Balance(ctx, chainB, chainBAddress, chainBIBCToken.IBCDenom()) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance.Int64()) + }) + + t.Run("transfer native tokens from chainB to chainA", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainB, chainBWallet, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, testvalues.DefaultTransferAmount(chainBDenom), chainBAddress, chainAAddress, s.GetTimeoutHeight(ctx, chainA.(*cosmos.CosmosChain)), 0, "") + s.AssertTxSuccess(transferTxResp) + + s.AssertPacketRelayed(ctx, chainA, channelA.Counterparty.PortID, channelA.Counterparty.ChannelID, 1) + + actualBalance, err := query.Balance(ctx, chainA, chainAAddress, chainAIBCToken.IBCDenom()) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount + s.Require().Equal(expected, actualBalance.Int64()) + }) + + s.Require().NoError(test.WaitForBlocks(ctx, 5, chainA), "failed to wait for blocks") + + t.Run("stop relayer", func(t *testing.T) { + s.StopRelayer(ctx, relayer) + }) + + t.Run("upgrade chain", func(t *testing.T) { + govProposalWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + s.UpgradeChain(ctx, chainA.(*cosmos.CosmosChain), govProposalWallet, testCfg.GetUpgradeConfig().PlanName, testCfg.ChainConfigs[0].Tag, testCfg.GetUpgradeConfig().Tag) + }) + + t.Run("start relayer", func(t *testing.T) { + s.StartRelayer(relayer, testName) + }) + + t.Run("query denoms after upgrade", func(t *testing.T) { + resp, err := query.TransferDenoms(ctx, chainA) + s.Require().NoError(err) + s.Require().Len(resp.Denoms, 1) + s.Require().Equal(chainAIBCToken, resp.Denoms[0]) + }) + + t.Run("IBC token transfer from chainA to chainB, to make sure the upgrade did not break the packet flow", func(t *testing.T) { + transferTxResp := s.Transfer(ctx, chainA, chainAWallet, channelA.PortID, channelA.ChannelID, testvalues.DefaultTransferAmount(chainADenom), chainAAddress, chainBAddress, s.GetTimeoutHeight(ctx, chainB), 0, "") + s.AssertTxSuccess(transferTxResp) + + s.AssertPacketRelayed(ctx, chainA, channelA.PortID, channelA.ChannelID, 2) + + actualBalance, err := chainB.GetBalance(ctx, chainBAddress, chainBIBCToken.IBCDenom()) + s.Require().NoError(err) + + expected := testvalues.IBCTransferAmount * 2 + s.Require().Equal(expected, actualBalance.Int64()) + }) +} + +func (s *UpgradeTestSuite) TestV8ToV10ChainUpgrade_Localhost() { + t := s.T() + testCfg := testsuite.LoadConfig() + ctx := context.Background() + + chainA, chainB := s.GetChains() + chainADenom := chainA.Config().Denom + + rlyWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + userAWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + userBWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + + var ( + srcChannelID string + dstChannelID string + ) + + s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA, chainB), "failed to wait for blocks") + + t.Run("open localhost channel", func(t *testing.T) { + var ( + msgChanOpenInitRes channeltypes.MsgChannelOpenInitResponse + msgChanOpenTryRes channeltypes.MsgChannelOpenTryResponse + ) + + msgChanOpenInit := channeltypes.NewMsgChannelOpenInit( + transfertypes.PortID, transfertypes.V1, + channeltypes.UNORDERED, []string{exported.LocalhostConnectionID}, + transfertypes.PortID, rlyWallet.FormattedAddress(), + ) + txResp := s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenInit) + s.AssertTxSuccess(txResp) + s.Require().NoError(testsuite.UnmarshalMsgResponses(txResp, &msgChanOpenInitRes)) + srcChannelID = msgChanOpenInitRes.ChannelId + + msgChanOpenTry := channeltypes.NewMsgChannelOpenTry( + transfertypes.PortID, transfertypes.V1, + channeltypes.UNORDERED, []string{exported.LocalhostConnectionID}, + transfertypes.PortID, srcChannelID, + transfertypes.V1, localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(), + ) + txResp = s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenTry) + s.AssertTxSuccess(txResp) + s.Require().NoError(testsuite.UnmarshalMsgResponses(txResp, &msgChanOpenTryRes)) + dstChannelID = msgChanOpenTryRes.ChannelId + + msgChanOpenAck := channeltypes.NewMsgChannelOpenAck( + transfertypes.PortID, srcChannelID, + dstChannelID, transfertypes.V1, + localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(), + ) + txResp = s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenAck) + s.AssertTxSuccess(txResp) + + msgChanOpenConfirm := channeltypes.NewMsgChannelOpenConfirm( + transfertypes.PortID, dstChannelID, + localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress(), + ) + txResp = s.BroadcastMessages(ctx, chainA, rlyWallet, msgChanOpenConfirm) + s.AssertTxSuccess(txResp) + }) + + t.Run("ibc transfer over localhost", func(t *testing.T) { + txResp := s.Transfer(ctx, chainA, userAWallet, transfertypes.PortID, srcChannelID, testvalues.DefaultTransferAmount(chainADenom), userAWallet.FormattedAddress(), userBWallet.FormattedAddress(), s.GetTimeoutHeight(ctx, chainA), 0, "") + s.AssertTxSuccess(txResp) + + packet, err := ibctesting.ParseV1PacketFromEvents(txResp.Events) + s.Require().NoError(err) + s.Require().NotNil(packet) + + msgRecvPacket := channeltypes.NewMsgRecvPacket(packet, localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress()) + + txResp = s.BroadcastMessages(ctx, chainA, rlyWallet, msgRecvPacket) + s.AssertTxSuccess(txResp) + + ack, err := ibctesting.ParseAckFromEvents(txResp.Events) + s.Require().NoError(err) + s.Require().NotNil(ack) + + msgAcknowledgement := channeltypes.NewMsgAcknowledgement(packet, ack, localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress()) + txResp = s.BroadcastMessages(ctx, chainA, rlyWallet, msgAcknowledgement) + s.AssertTxSuccess(txResp) + + s.AssertPacketRelayed(ctx, chainA, transfertypes.PortID, srcChannelID, 1) + ibcToken := testsuite.GetIBCToken(chainADenom, transfertypes.PortID, dstChannelID) + actualBalance, err := query.Balance(ctx, chainA, userBWallet.FormattedAddress(), ibcToken.IBCDenom()) + s.Require().NoError(err) + s.Require().Equal(testvalues.IBCTransferAmount, actualBalance.Int64()) + }) + + t.Run("localhost exists in state before upgrade", func(t *testing.T) { + localhostClientID := exported.LocalhostClientID + if !testvalues.LocalhostWithDashFeatureReleases.IsSupported(chainA.Config().Images[0].Version) { + localhostClientID = exported.Localhost + } + + status, err := query.ClientStatus(ctx, chainA, localhostClientID) + s.Require().NoError(err) + s.Require().Equal(exported.Active.String(), status) + + state, err := s.ClientState(ctx, chainA, localhostClientID) + s.Require().NoError(err) + s.Require().NotNil(state) + }) + + t.Run("upgrade chain", func(t *testing.T) { + govProposalWallet := s.CreateUserOnChainA(ctx, testvalues.StartingTokenAmount) + s.UpgradeChain(ctx, chainA.(*cosmos.CosmosChain), govProposalWallet, testCfg.GetUpgradeConfig().PlanName, testCfg.ChainConfigs[0].Tag, testCfg.GetUpgradeConfig().Tag) + }) + + t.Run("localhost does not exist in state after upgrade", func(t *testing.T) { + localhostClientID := exported.LocalhostClientID + if !testvalues.LocalhostWithDashFeatureReleases.IsSupported(chainA.Config().Images[0].Version) { + localhostClientID = exported.Localhost + } + + status, err := query.ClientStatus(ctx, chainA, localhostClientID) + s.Require().NoError(err) + s.Require().Equal(exported.Active.String(), status) + + state, err := s.ClientState(ctx, chainA, localhostClientID) + s.Require().Error(err) + s.Require().Nil(state) + }) + + t.Run("query localhost transfer channel ends after upgrade", func(t *testing.T) { + channelEndA, err := query.Channel(ctx, chainA, transfertypes.PortID, srcChannelID) + s.Require().NoError(err) + s.Require().NotNil(channelEndA) + + channelEndB, err := query.Channel(ctx, chainA, transfertypes.PortID, dstChannelID) + s.Require().NoError(err) + s.Require().NotNil(channelEndB) + + s.Require().Equal(channelEndA.ConnectionHops, channelEndB.ConnectionHops) + }) + + t.Run("ibc transfer back over localhost after upgrade", func(t *testing.T) { + ibcToken := testsuite.GetIBCToken(chainADenom, transfertypes.PortID, dstChannelID) + transferCoins := testvalues.DefaultTransferAmount(ibcToken.IBCDenom()) + txResp := s.Transfer(ctx, chainA, userBWallet, transfertypes.PortID, dstChannelID, transferCoins, userBWallet.FormattedAddress(), userAWallet.FormattedAddress(), s.GetTimeoutHeight(ctx, chainA), 0, "") + s.AssertTxSuccess(txResp) + + packet, err := ibctesting.ParseV1PacketFromEvents(txResp.Events) + s.Require().NoError(err) + s.Require().NotNil(packet) + + msgRecvPacket := channeltypes.NewMsgRecvPacket(packet, localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress()) + + txResp = s.BroadcastMessages(ctx, chainA, rlyWallet, msgRecvPacket) + s.AssertTxSuccess(txResp) + + ack, err := ibctesting.ParseAckFromEvents(txResp.Events) + s.Require().NoError(err) + s.Require().NotNil(ack) + + msgAcknowledgement := channeltypes.NewMsgAcknowledgement(packet, ack, localhost.SentinelProof, clienttypes.ZeroHeight(), rlyWallet.FormattedAddress()) + txResp = s.BroadcastMessages(ctx, chainA, rlyWallet, msgAcknowledgement) + s.AssertTxSuccess(txResp) + + s.AssertPacketRelayed(ctx, chainA, transfertypes.PortID, dstChannelID, 1) + + actualBalance, err := query.Balance(ctx, chainA, userAWallet.FormattedAddress(), chainADenom) + s.Require().NoError(err) + s.Require().Equal(testvalues.StartingTokenAmount, actualBalance.Int64()) + }) +} + +// ClientState queries the current ClientState by clientID +func (*UpgradeTestSuite) ClientState(ctx context.Context, chain ibc.Chain, clientID string) (*clienttypes.QueryClientStateResponse, error) { + res, err := query.GRPCQuery[clienttypes.QueryClientStateResponse](ctx, chain, &clienttypes.QueryClientStateRequest{ClientId: clientID}) + if err != nil { + return res, err + } + + return res, nil +} diff --git a/e2e/testsuite/codec.go b/e2e/testsuite/codec.go new file mode 100644 index 0000000..8d32a10 --- /dev/null +++ b/e2e/testsuite/codec.go @@ -0,0 +1,129 @@ +package testsuite + +import ( + "bytes" + "encoding/hex" + + "github.com/cosmos/gogoproto/jsonpb" + "github.com/cosmos/gogoproto/proto" + + upgradetypes "cosmossdk.io/x/upgrade/types" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module/testutil" + txtypes "github.com/cosmos/cosmos-sdk/types/tx" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/authz" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + grouptypes "github.com/cosmos/cosmos-sdk/x/group" + proposaltypes "github.com/cosmos/cosmos-sdk/x/params/types/proposal" + + wasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + icacontrollertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + icahosttypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + v7migrations "github.com/cosmos/ibc-go/v10/modules/core/02-client/migrations/v7" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine" + ibctmtypes "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +// Codec returns the global E2E protobuf codec. +func Codec() *codec.ProtoCodec { + cdc, _ := codecAndEncodingConfig() + return cdc +} + +// SDKEncodingConfig returns the global E2E encoding config. +func SDKEncodingConfig() *testutil.TestEncodingConfig { + _, cfg := codecAndEncodingConfig() + return &testutil.TestEncodingConfig{ + InterfaceRegistry: cfg.InterfaceRegistry, + Codec: cfg.Codec, + TxConfig: cfg.TxConfig, + Amino: cfg.Amino, + } +} + +// codecAndEncodingConfig returns the codec and encoding config used in the E2E tests. +// Note: any new types added to the codec must be added here. +func codecAndEncodingConfig() (*codec.ProtoCodec, testutil.TestEncodingConfig) { + cfg := testutil.MakeTestEncodingConfig() + + // ibc types + icacontrollertypes.RegisterInterfaces(cfg.InterfaceRegistry) + icahosttypes.RegisterInterfaces(cfg.InterfaceRegistry) + solomachine.RegisterInterfaces(cfg.InterfaceRegistry) + v7migrations.RegisterInterfaces(cfg.InterfaceRegistry) + transfertypes.RegisterInterfaces(cfg.InterfaceRegistry) + clienttypes.RegisterInterfaces(cfg.InterfaceRegistry) + channeltypes.RegisterInterfaces(cfg.InterfaceRegistry) + connectiontypes.RegisterInterfaces(cfg.InterfaceRegistry) + ibctmtypes.RegisterInterfaces(cfg.InterfaceRegistry) + wasmtypes.RegisterInterfaces(cfg.InterfaceRegistry) + channeltypesv2.RegisterInterfaces(cfg.InterfaceRegistry) + + // all other types + upgradetypes.RegisterInterfaces(cfg.InterfaceRegistry) + banktypes.RegisterInterfaces(cfg.InterfaceRegistry) + govv1beta1.RegisterInterfaces(cfg.InterfaceRegistry) + govv1.RegisterInterfaces(cfg.InterfaceRegistry) + authtypes.RegisterInterfaces(cfg.InterfaceRegistry) + cryptocodec.RegisterInterfaces(cfg.InterfaceRegistry) + grouptypes.RegisterInterfaces(cfg.InterfaceRegistry) + proposaltypes.RegisterInterfaces(cfg.InterfaceRegistry) + authz.RegisterInterfaces(cfg.InterfaceRegistry) + txtypes.RegisterInterfaces(cfg.InterfaceRegistry) + + cdc := codec.NewProtoCodec(cfg.InterfaceRegistry) + return cdc, cfg +} + +// UnmarshalMsgResponses attempts to unmarshal the tx msg responses into the provided message types. +func UnmarshalMsgResponses(txResp sdk.TxResponse, msgs ...codec.ProtoMarshaler) error { + cdc := Codec() + bz, err := hex.DecodeString(txResp.Data) + if err != nil { + return err + } + + return ibctesting.UnmarshalMsgResponses(cdc, bz, msgs...) +} + +// MustProtoMarshalJSON provides an auxiliary function to return Proto3 JSON encoded +// bytes of a message. This function should be used when marshalling a proto.Message +// from the e2e tests. This function strips out unknown fields. This is useful for +// backwards compatibility tests where the types imported by the e2e package have +// new fields that older versions do not recognize. +func MustProtoMarshalJSON(msg proto.Message) []byte { + anyResolver := codectypes.NewInterfaceRegistry() + + // EmitDefaults is set to false to prevent marshalling of unpopulated fields (memo) + // OrigName and the anyResovler match the fields the original SDK function would expect + // in order to minimize changes. + + // OrigName is true since there is no particular reason to use camel case + // The any resolver is empty, but provided anyways. + jm := &jsonpb.Marshaler{OrigName: true, EmitDefaults: false, AnyResolver: anyResolver} + + err := codectypes.UnpackInterfaces(msg, codectypes.ProtoJSONPacker{JSONPBMarshaler: jm}) + if err != nil { + panic(err) + } + + buf := new(bytes.Buffer) + if err := jm.Marshal(buf, msg); err != nil { + panic(err) + } + + return buf.Bytes() +} diff --git a/e2e/testsuite/diagnostics/diagnostics.go b/e2e/testsuite/diagnostics/diagnostics.go new file mode 100644 index 0000000..5ff5755 --- /dev/null +++ b/e2e/testsuite/diagnostics/diagnostics.go @@ -0,0 +1,163 @@ +package diagnostics + +import ( + "context" + "encoding/json" + "fmt" + "os" + ospath "path" + "strings" + "testing" + + dockertypes "github.com/docker/docker/api/types" + mobycli "github.com/moby/moby/client" + + "github.com/cosmos/ibc-go/e2e/dockerutil" + "github.com/cosmos/ibc-go/e2e/internal/directories" +) + +const ( + dockerInspectFileName = "docker-inspect.json" + defaultFilePerm = 0o750 +) + +// Collect can be used in `t.Cleanup` and will copy all the of the container logs and relevant files +// into e2e//.log. These log files will be uploaded to GH upon test failure. +func Collect(t *testing.T, dc *mobycli.Client, debugModeEnabled bool, suiteName string, chainNames ...string) { + t.Helper() + + if !debugModeEnabled { + // when we are not forcing log collection, we only upload upon test failing. + if !t.Failed() { + t.Logf("test passed, not uploading logs") + return + } + } + + t.Logf("writing logs for test: %s", t.Name()) + + ctx := context.TODO() + e2eDir, err := directories.E2E() + if err != nil { + t.Logf("failed finding log directory: %s", err) + return + } + + logsDir := fmt.Sprintf("%s/diagnostics", e2eDir) + + if err := os.MkdirAll(fmt.Sprintf("%s/%s", logsDir, t.Name()), defaultFilePerm); err != nil { + t.Logf("failed creating logs directory in test cleanup: %s", err) + return + } + + testContainers, err := dockerutil.GetTestContainers(ctx, suiteName, dc) + if err != nil { + t.Logf("failed listing containers during test cleanup: %s", err) + return + } + + for _, container := range testContainers { + containerName := getContainerName(t, container) + containerDir := fmt.Sprintf("%s/%s/%s", logsDir, t.Name(), containerName) + if err := os.MkdirAll(containerDir, defaultFilePerm); err != nil { + t.Logf("failed creating logs directory for container %s: %s", containerDir, err) + continue + } + + logsBz, err := dockerutil.GetContainerLogs(ctx, dc, container.ID) + if err != nil { + t.Logf("failed reading logs in test cleanup: %s", err) + continue + } + + logFile := fmt.Sprintf("%s/%s.log", containerDir, containerName) + if err := os.WriteFile(logFile, logsBz, defaultFilePerm); err != nil { + continue + } + + t.Logf("successfully wrote log file %s", logFile) + + var diagnosticFiles []string + for _, chainName := range chainNames { + diagnosticFiles = append(diagnosticFiles, chainDiagnosticAbsoluteFilePaths(chainName)...) + } + diagnosticFiles = append(diagnosticFiles, relayerDiagnosticAbsoluteFilePaths()...) + + for _, absoluteFilePathInContainer := range diagnosticFiles { + localFilePath := ospath.Join(containerDir, ospath.Base(absoluteFilePathInContainer)) + if err := fetchAndWriteDiagnosticsFile(ctx, dc, container.ID, localFilePath, absoluteFilePathInContainer); err != nil { + continue + } + t.Logf("successfully wrote diagnostics file %s", absoluteFilePathInContainer) + } + + localFilePath := ospath.Join(containerDir, dockerInspectFileName) + if err := fetchAndWriteDockerInspectOutput(ctx, dc, container.ID, localFilePath); err != nil { + continue + } + t.Logf("successfully wrote docker inspect output") + } +} + +// getContainerName returns an either the ID of the container or stripped down human-readable +// version of the name if the name is non-empty. +// +// Note: You should still always use the ID when interacting with the docker client. +func getContainerName(t *testing.T, container dockertypes.Container) string { + t.Helper() + // container will always have an id, by may not have a name. + containerName := container.ID + if len(container.Names) > 0 { + containerName = container.Names[0] + // remove the test name from the container as the folder structure will provide this + // information already. + containerName = strings.TrimRight(containerName, "-"+t.Name()) + containerName = strings.TrimLeft(containerName, "/") + } + return containerName +} + +// fetchAndWriteDiagnosticsFile fetches the contents of a single file from the given container id and writes +// the contents of the file to a local path provided. +func fetchAndWriteDiagnosticsFile(ctx context.Context, dc *mobycli.Client, containerID, localPath, absoluteFilePathInContainer string) error { + fileBz, err := dockerutil.GetFileContentsFromContainer(ctx, dc, containerID, absoluteFilePathInContainer) + if err != nil { + return err + } + + return os.WriteFile(localPath, fileBz, defaultFilePerm) +} + +// fetchAndWriteDockerInspectOutput writes the contents of docker inspect to the specified file. +func fetchAndWriteDockerInspectOutput(ctx context.Context, dc *mobycli.Client, containerID, localPath string) error { + containerJSON, err := dc.ContainerInspect(ctx, containerID) + if err != nil { + return err + } + + fileBz, err := json.MarshalIndent(containerJSON, "", "\t") + if err != nil { + return err + } + + return os.WriteFile(localPath, fileBz, defaultFilePerm) +} + +// chainDiagnosticAbsoluteFilePaths returns a slice of absolute file paths (in the containers) which are the files that should be +// copied locally when fetching diagnostics. +func chainDiagnosticAbsoluteFilePaths(chainName string) []string { + return []string{ + fmt.Sprintf("/var/cosmos-chain/%s/config/genesis.json", chainName), + fmt.Sprintf("/var/cosmos-chain/%s/config/app.toml", chainName), + fmt.Sprintf("/var/cosmos-chain/%s/config/config.toml", chainName), + fmt.Sprintf("/var/cosmos-chain/%s/config/client.toml", chainName), + } +} + +// relayerDiagnosticAbsoluteFilePaths returns a slice of absolute file paths (in the containers) which are the files that should be +// copied locally when fetching diagnostics. +func relayerDiagnosticAbsoluteFilePaths() []string { + return []string{ + "/home/hermes/.hermes/config.toml", + } +} diff --git a/e2e/testsuite/events.go b/e2e/testsuite/events.go new file mode 100644 index 0000000..8a7052d --- /dev/null +++ b/e2e/testsuite/events.go @@ -0,0 +1,22 @@ +package testsuite + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + abci "github.com/cometbft/cometbft/abci/types" +) + +// ABCIToSDKEvents converts a list of ABCI events to Cosmos SDK events. +func ABCIToSDKEvents(abciEvents []abci.Event) sdk.Events { + var events sdk.Events + for _, evt := range abciEvents { + var attributes []sdk.Attribute + for _, attr := range evt.GetAttributes() { + attributes = append(attributes, sdk.NewAttribute(attr.Key, attr.Value)) + } + + events = events.AppendEvent(sdk.NewEvent(evt.GetType(), attributes...)) + } + + return events +} diff --git a/e2e/testsuite/query/grpc_query.go b/e2e/testsuite/query/grpc_query.go new file mode 100644 index 0000000..4cd5eb3 --- /dev/null +++ b/e2e/testsuite/query/grpc_query.go @@ -0,0 +1,161 @@ +package query + +import ( + "context" + "fmt" + "strings" + + "github.com/cosmos/gogoproto/proto" + "github.com/cosmos/interchaintest/v10/ibc" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + pb "google.golang.org/protobuf/proto" + + msgv1 "cosmossdk.io/api/cosmos/msg/v1" + reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1" + + "github.com/cosmos/ibc-go/e2e/testvalues" +) + +var queryReqToPath = make(map[string]string) + +func PopulateQueryReqToPath(ctx context.Context, chain ibc.Chain) error { + if !testvalues.ReflectionServiceFeatureReleases.IsSupported(chain.Config().Images[0].Version) { + return nil + } + resp, err := queryFileDescriptors(ctx, chain) + if err != nil { + return err + } + + for _, fileDescriptor := range resp.Files { + for _, service := range fileDescriptor.GetService() { + // Skip services that are annotated with the "cosmos.msg.v1.service" option. + if ext := pb.GetExtension(service.GetOptions(), msgv1.E_Service); ext != nil { + if ok, extBool := ext.(bool); ok && extBool { + continue + } + } + + for _, method := range service.GetMethod() { + // trim the first character from input which is a dot + queryReqToPath[method.GetInputType()[1:]] = fileDescriptor.GetPackage() + "." + service.GetName() + "/" + method.GetName() + } + } + } + + return nil +} + +// GRPCQuery queries the chain with a query request and deserializes the response to T +func GRPCQuery[T any](ctx context.Context, chain ibc.Chain, req proto.Message, opts ...grpc.CallOption) (*T, error) { + var path string + if testvalues.ReflectionServiceFeatureReleases.IsSupported(chain.Config().Images[0].Version) { + var ok bool + path, ok = queryReqToPath[proto.MessageName(req)] + if !ok { + return nil, fmt.Errorf("no path found for %s", proto.MessageName(req)) + } + } else { + var err error + path, err = getProtoPath(req) + if err != nil { + return nil, err + } + } + + return grpcQueryWithMethod[T](ctx, chain, req, path, opts...) +} + +// grpcQueryWithMethod queries the chain with a query request with a specific method (grpc path) and deserializes the response to T +func grpcQueryWithMethod[T any](ctx context.Context, chain ibc.Chain, req proto.Message, method string, opts ...grpc.CallOption) (*T, error) { + // Create a connection to the gRPC server. + grpcConn, err := grpc.Dial( + chain.GetHostGRPCAddress(), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + if err != nil { + return nil, err + } + + defer grpcConn.Close() + + resp := new(T) + err = grpcConn.Invoke(ctx, method, req, resp, opts...) + if err != nil { + return nil, err + } + + return resp, nil +} + +func queryFileDescriptors(ctx context.Context, chain ibc.Chain) (*reflectionv1.FileDescriptorsResponse, error) { + // Create a connection to the gRPC server. + grpcConn, err := grpc.Dial( + chain.GetHostGRPCAddress(), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + if err != nil { + return nil, err + } + + defer grpcConn.Close() + + resp := new(reflectionv1.FileDescriptorsResponse) + err = grpcConn.Invoke( + ctx, reflectionv1.ReflectionService_FileDescriptors_FullMethodName, + &reflectionv1.FileDescriptorsRequest{}, resp, + ) + if err != nil { + return nil, err + } + + return resp, nil +} + +// TODO: Remove all of the below when v6 -> v7 upgrade is not supported anymore: +func getProtoPath(req proto.Message) (string, error) { + typeURL := "/" + proto.MessageName(req) + + switch { + case strings.Contains(typeURL, "Query"): + return getQueryProtoPath(typeURL) + case strings.Contains(typeURL, "cosmos.base.tendermint"): + return getCmtProtoPath(typeURL) + default: + return "", fmt.Errorf("unsupported typeURL: %s", typeURL) + } +} + +func getQueryProtoPath(queryTypeURL string) (string, error) { + queryIndex := strings.Index(queryTypeURL, "Query") + if queryIndex == -1 { + return "", fmt.Errorf("invalid typeURL: %s", queryTypeURL) + } + + // Add to the index to account for the length of "Query" + queryIndex += len("Query") + + // Add a slash before the query + urlWithSlash := queryTypeURL[:queryIndex] + "/" + queryTypeURL[queryIndex:] + if !strings.HasSuffix(urlWithSlash, "Request") { + return "", fmt.Errorf("invalid typeURL: %s", queryTypeURL) + } + + return strings.TrimSuffix(urlWithSlash, "Request"), nil +} + +func getCmtProtoPath(cmtTypeURL string) (string, error) { + cmtIndex := strings.Index(cmtTypeURL, "Get") + if cmtIndex == -1 { + return "", fmt.Errorf("invalid typeURL: %s", cmtTypeURL) + } + + // Add a slash before the commitment + urlWithSlash := cmtTypeURL[:cmtIndex] + "Service/" + cmtTypeURL[cmtIndex:] + if !strings.HasSuffix(urlWithSlash, "Request") { + return "", fmt.Errorf("invalid typeURL: %s", cmtTypeURL) + } + + return strings.TrimSuffix(urlWithSlash, "Request"), nil +} diff --git a/e2e/testsuite/query/queries.go b/e2e/testsuite/query/queries.go new file mode 100644 index 0000000..d5c805d --- /dev/null +++ b/e2e/testsuite/query/queries.go @@ -0,0 +1,165 @@ +package query + +import ( + "context" + "errors" + "sort" + + "github.com/cosmos/interchaintest/v10/ibc" + + "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/client/grpc/cmtservice" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + controllertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +const queryPathTransferDenoms = "/ibc.applications.transfer.v1.Query/Denoms" + +// ModuleAccountAddress returns the address of the given module on the given chain. +// Added because interchaintest's method doesn't work. +func ModuleAccountAddress(ctx context.Context, moduleName string, chain ibc.Chain) (sdk.AccAddress, error) { + modAccResp, err := GRPCQuery[authtypes.QueryModuleAccountByNameResponse]( + ctx, chain, &authtypes.QueryModuleAccountByNameRequest{Name: moduleName}, + ) + if err != nil { + return nil, err + } + + cfg := chain.Config().EncodingConfig + var account sdk.AccountI + err = cfg.InterfaceRegistry.UnpackAny(modAccResp.Account, &account) + if err != nil { + return nil, err + } + + govAccount, ok := account.(sdk.ModuleAccountI) + if !ok { + return nil, errors.New("account is not a module account") + } + if govAccount.GetAddress().String() == "" { + return nil, errors.New("module account address is empty") + } + + return govAccount.GetAddress(), nil +} + +// ClientState queries the client state on the given chain for the provided clientID. +func ClientState(ctx context.Context, chain ibc.Chain, clientID string) (ibcexported.ClientState, error) { + clientStateResp, err := GRPCQuery[clienttypes.QueryClientStateResponse](ctx, chain, &clienttypes.QueryClientStateRequest{ + ClientId: clientID, + }) + if err != nil { + return nil, err + } + + clientStateAny := clientStateResp.ClientState + + cfg := chain.Config().EncodingConfig + var clientState ibcexported.ClientState + err = cfg.InterfaceRegistry.UnpackAny(clientStateAny, &clientState) + if err != nil { + return nil, err + } + + return clientState, nil +} + +// ClientStatus queries the status of the client by clientID +func ClientStatus(ctx context.Context, chain ibc.Chain, clientID string) (string, error) { + clientStatusResp, err := GRPCQuery[clienttypes.QueryClientStatusResponse](ctx, chain, &clienttypes.QueryClientStatusRequest{ + ClientId: clientID, + }) + if err != nil { + return "", err + } + return clientStatusResp.Status, nil +} + +// GetValidatorSetByHeight returns the validators of the given chain at the specified height. The returned validators +// are sorted by address. +func GetValidatorSetByHeight(ctx context.Context, chain ibc.Chain, height uint64) ([]*cmtservice.Validator, error) { + res, err := GRPCQuery[cmtservice.GetValidatorSetByHeightResponse](ctx, chain, &cmtservice.GetValidatorSetByHeightRequest{ + Height: int64(height), + }) + if err != nil { + return nil, err + } + + sort.SliceStable(res.Validators, func(i, j int) bool { + return res.Validators[i].Address < res.Validators[j].Address + }) + + return res.Validators, nil +} + +// Balance returns the balance of a specific denomination for a given account by address. +func Balance(ctx context.Context, chain ibc.Chain, address string, denom string) (math.Int, error) { + res, err := GRPCQuery[banktypes.QueryBalanceResponse](ctx, chain, &banktypes.QueryBalanceRequest{ + Address: address, + Denom: denom, + }) + if err != nil { + return math.Int{}, err + } + return res.Balance.Amount, nil +} + +// Channel queries the channel on a given chain for the provided portID and channelID +func Channel(ctx context.Context, chain ibc.Chain, portID, channelID string) (channeltypes.Channel, error) { + res, err := GRPCQuery[channeltypes.QueryChannelResponse](ctx, chain, &channeltypes.QueryChannelRequest{ + PortId: portID, + ChannelId: channelID, + }) + if err != nil { + return channeltypes.Channel{}, err + } + return *res.Channel, nil +} + +// TotalEscrowForDenom queries the total amount of tokens in escrow for a denom +func TotalEscrowForDenom(ctx context.Context, chain ibc.Chain, denom string) (sdk.Coin, error) { + res, err := GRPCQuery[transfertypes.QueryTotalEscrowForDenomResponse](ctx, chain, &transfertypes.QueryTotalEscrowForDenomRequest{ + Denom: denom, + }) + if err != nil { + return sdk.Coin{}, err + } + return res.Amount, nil +} + +// PacketAcknowledgements queries the packet acknowledgements on the given chain for the provided channel (optional) list of packet commitment sequences. +func PacketAcknowledgements(ctx context.Context, chain ibc.Chain, portID, channelID string, packetCommitmentSequences []uint64) ([]*channeltypes.PacketState, error) { + res, err := GRPCQuery[channeltypes.QueryPacketAcknowledgementsResponse](ctx, chain, &channeltypes.QueryPacketAcknowledgementsRequest{ + PortId: portID, + ChannelId: channelID, + PacketCommitmentSequences: packetCommitmentSequences, + }) + if err != nil { + return nil, err + } + return res.Acknowledgements, nil +} + +// InterchainAccount queries the interchain account for the given owner and connectionID. +func InterchainAccount(ctx context.Context, chain ibc.Chain, address, connectionID string) (string, error) { + res, err := GRPCQuery[controllertypes.QueryInterchainAccountResponse](ctx, chain, &controllertypes.QueryInterchainAccountRequest{ + Owner: address, + ConnectionId: connectionID, + }) + if err != nil { + return "", err + } + return res.Address, nil +} + +func TransferDenoms(ctx context.Context, chain ibc.Chain) (*transfertypes.QueryDenomsResponse, error) { + return grpcQueryWithMethod[transfertypes.QueryDenomsResponse](ctx, chain, &transfertypes.QueryDenomsRequest{}, queryPathTransferDenoms) +} diff --git a/e2e/testsuite/sanitize/messages.go b/e2e/testsuite/sanitize/messages.go new file mode 100644 index 0000000..686d807 --- /dev/null +++ b/e2e/testsuite/sanitize/messages.go @@ -0,0 +1,82 @@ +package sanitize + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + govtypesv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + grouptypes "github.com/cosmos/cosmos-sdk/x/group" + + "github.com/cosmos/ibc-go/e2e/semverutil" + icacontrollertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" +) + +var ( + // groupsv1ProposalTitleAndSummary represents the releases that support the new title and summary fields. + groupsv1ProposalTitleAndSummary = semverutil.FeatureReleases{ + MajorVersion: "v7", + } + // govv1ProposalTitleAndSummary represents the releases that support the new title and summary fields. + govv1ProposalTitleAndSummary = semverutil.FeatureReleases{ + MajorVersion: "v7", + } + // icaUnorderedChannelFeatureReleases represents the releasees that support the new ordering field. + icaUnorderedChannelFeatureReleases = semverutil.FeatureReleases{ + MajorVersion: "v9", + MinorVersions: []string{ + "v7.5", + "v8.1", + }, + } +) + +// Messages removes any fields that are not supported by the chain version. +// For example, any fields that have been added in later sdk releases. +func Messages(tag string, msgs ...sdk.Msg) []sdk.Msg { + sanitizedMsgs := make([]sdk.Msg, len(msgs)) + for i := range msgs { + sanitizedMsgs[i] = removeUnknownFields(tag, msgs[i]) + } + return sanitizedMsgs +} + +// removeUnknownFields removes any fields that are not supported by the chain version. +// The input message is returned if no changes are made. +func removeUnknownFields(tag string, msg sdk.Msg) sdk.Msg { + switch msg := msg.(type) { + case *govtypesv1.MsgSubmitProposal: + if !govv1ProposalTitleAndSummary.IsSupported(tag) { + msg.Title = "" + msg.Summary = "" + } + // sanitize messages contained in the x/gov proposal + msgs, err := msg.GetMsgs() + if err != nil { + panic(err) + } + sanitizedMsgs := Messages(tag, msgs...) + if err := msg.SetMsgs(sanitizedMsgs); err != nil { + panic(err) + } + return msg + case *grouptypes.MsgSubmitProposal: + if !groupsv1ProposalTitleAndSummary.IsSupported(tag) { + msg.Title = "" + msg.Summary = "" + } + // sanitize messages contained in the x/group proposal + msgs, err := msg.GetMsgs() + if err != nil { + panic(err) + } + sanitizedMsgs := Messages(tag, msgs...) + if err := msg.SetMsgs(sanitizedMsgs); err != nil { + panic(err) + } + return msg + case *icacontrollertypes.MsgRegisterInterchainAccount: + if !icaUnorderedChannelFeatureReleases.IsSupported(tag) { + msg.Ordering = channeltypes.NONE + } + } + return msg +} diff --git a/e2e/testsuite/testconfig.go b/e2e/testsuite/testconfig.go new file mode 100644 index 0000000..a812d32 --- /dev/null +++ b/e2e/testsuite/testconfig.go @@ -0,0 +1,1098 @@ +package testsuite + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "os" + "path" + "strings" + "time" + + "github.com/cosmos/interchaintest/v10" + "github.com/cosmos/interchaintest/v10/ibc" + interchaintestutil "github.com/cosmos/interchaintest/v10/testutil" + "gopkg.in/yaml.v2" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module/testutil" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + + cmtjson "github.com/cometbft/cometbft/libs/json" + + "github.com/cosmos/ibc-go/e2e/internal/directories" + "github.com/cosmos/ibc-go/e2e/relayer" + "github.com/cosmos/ibc-go/e2e/semverutil" + "github.com/cosmos/ibc-go/e2e/testvalues" + wasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctypes "github.com/cosmos/ibc-go/v10/modules/core/types" +) + +const ( + // ChainImageEnv specifies the image that the chains will use. If left unspecified, it will + // default to being determined based on the specified binary. E.g. ghcr.io/cosmos/ibc-go-simd + ChainImageEnv = "CHAIN_IMAGE" + // ChainATagEnv specifies the tag that Chain A will use. + ChainATagEnv = "CHAIN_A_TAG" + // ChainBTagEnv specifies the tag that Chain B will use. If unspecified + // the value will default to the same value as Chain A. + ChainBTagEnv = "CHAIN_B_TAG" + // ChainCTagEnv specifies the tag that Chain C will use. + // the value will default to the same value as Chain A. + ChainCTagEnv = "CHAIN_C_TAG" + // ChainDTagEnv specifies the tag that Chain D will use. If unspecified + // the value will default to the same value as Chain A. + ChainDTagEnv = "CHAIN_D_TAG" + // RelayerIDEnv specifies the ID of the relayer to use. + RelayerIDEnv = "RELAYER_ID" + // ChainBinaryEnv binary is the binary that will be used for both chains. + ChainBinaryEnv = "CHAIN_BINARY" + // ChainUpgradePlanEnv specifies the upgrade plan name + ChainUpgradePlanEnv = "CHAIN_UPGRADE_PLAN" + // E2EConfigFilePathEnv allows you to specify a custom path for the config file to be used. It can be relative + // or absolute. + E2EConfigFilePathEnv = "E2E_CONFIG_PATH" + // KeepContainersEnv instructs interchaintest to not delete the containers after a test has run. + // this ensures that chain containers are not deleted after a test suite is run if other tests + // depend on those chains. + KeepContainersEnv = "KEEP_CONTAINERS" + + // defaultBinary is the default binary that will be used by the chains. + defaultBinary = "simd" + // defaultRlyTag is the tag that will be used if no relayer tag is specified. + // all images are here https://github.com/cosmos/relayer/pkgs/container/relayer/versions + defaultRlyTag = "latest" + + // defaultHermesTag is the tag that will be used if no relayer tag is specified for hermes. + defaultHermesTag = "1.10.4" + // defaultChainTag is the tag that will be used for the chains if none is specified. + defaultChainTag = "main" + // defaultConfigFileName is the default filename for the config file that can be used to configure + // e2e tests. See sample.config.yaml or sample.config.extended.yaml as an example for what this should look like. + defaultConfigFileName = ".ibc-go-e2e-config.yaml" + // defaultCIConfigFileName is the default filename for the config file that should be used for CI. + defaultCIConfigFileName = "ci-e2e-config.yaml" +) + +// defaultChainNames contains the default name for chainA, chainB, ChainC and ChainD. +var defaultChainNames = []string{"simapp-a", "simapp-b", "simapp-c", "simapp-d"} + +func getChainImage(binary string) string { + if binary == "" { + binary = defaultBinary + } + return fmt.Sprintf("ghcr.io/cosmos/ibc-go-%s", binary) +} + +// TestConfig holds configuration used throughout the different e2e tests. +type TestConfig struct { + // ChainConfigs holds configuration values related to the chains used in the tests. + ChainConfigs []ChainConfig `yaml:"chains"` + // RelayerConfigs holds all known relayer configurations that can be used in the tests. + RelayerConfigs []relayer.Config `yaml:"relayers"` + // ActiveRelayer specifies the relayer that will be used. It must match the ID of one of the entries in RelayerConfigs. + ActiveRelayer string `yaml:"activeRelayer"` + // CometBFTConfig holds values for configuring CometBFT. + CometBFTConfig CometBFTConfig `yaml:"cometbft"` + // DebugConfig holds configuration for miscellaneous options. + DebugConfig DebugConfig `yaml:"debug"` + // UpgradePlanName specifies which upgrade plan to use. It must match a plan name for an entry in the + // list of UpgradeConfigs. + UpgradePlanName string `yaml:"upgradePlanName"` + // UpgradeConfigs provides a list of all possible upgrades. + UpgradeConfigs []UpgradeConfig `yaml:"upgrades"` +} + +// Validate validates the test configuration is valid for use within the tests. +// this should be called before using the configuration. +func (tc TestConfig) Validate() error { + if err := tc.validateChains(); err != nil { + return fmt.Errorf("invalid chain configuration: %w", err) + } + + if err := tc.validateRelayers(); err != nil { + return fmt.Errorf("invalid relayer configuration: %w", err) + } + + if err := tc.validateGenesisDebugConfig(); err != nil { + return fmt.Errorf("invalid Genesis debug configuration: %w", err) + } + + if err := tc.validateUpgradeConfig(); err != nil { + return fmt.Errorf("invalid upgrade configuration: %w", err) + } + + return nil +} + +// validateChains validates the chain configurations. +func (tc TestConfig) validateChains() error { + for _, cfg := range tc.ChainConfigs { + if cfg.Binary == "" { + return fmt.Errorf("chain config missing binary: %+v", cfg) + } + if cfg.Image == "" { + return fmt.Errorf("chain config missing image: %+v", cfg) + } + if cfg.Tag == "" { + return fmt.Errorf("chain config missing tag: %+v", cfg) + } + + if cfg.NumValidators == 0 && cfg.NumFullNodes == 0 { + return fmt.Errorf("chain config missing number of validators or full nodes: %+v", cfg) + } + } + + // clienttypes.ParseChainID is used to determine revision heights. If the chainIDs are not in the expected format, + // tests can fail with timeout errors. + if clienttypes.ParseChainID(tc.GetChainAID()) != clienttypes.ParseChainID(tc.GetChainBID()) { + return fmt.Errorf("ensure both chainIDs are in the format {chainID}-{revision} and have the same revision. Got: chainA: %s, chainB: %s", tc.GetChainAID(), tc.GetChainBID()) + } + + return nil +} + +// validateRelayers validates relayer configuration. +func (tc TestConfig) validateRelayers() error { + if len(tc.RelayerConfigs) < 1 { + return errors.New("no relayer configurations specified") + } + + for _, r := range tc.RelayerConfigs { + if r.ID == "" { + return fmt.Errorf("relayer config missing ID: %+v", r) + } + if r.Image == "" { + return fmt.Errorf("relayer config missing image: %+v", r) + } + if r.Tag == "" { + return fmt.Errorf("relayer config missing tag: %+v", r) + } + } + + if tc.GetActiveRelayerConfig() == nil { + return fmt.Errorf("active relayer %s not found in relayer configs: %+v", tc.ActiveRelayer, tc.RelayerConfigs) + } + + return nil +} + +// GetUpgradeConfig returns the upgrade configuration for the current test configuration. +func (tc TestConfig) GetUpgradeConfig() UpgradeConfig { + for _, upgrade := range tc.UpgradeConfigs { + if upgrade.PlanName == tc.UpgradePlanName { + return upgrade + } + } + panic("upgrade plan not found in upgrade configs, this test config should not have passed validation") +} + +// GetChainIndex returns the index of the chain with the given name, if it +// exists. +func (tc TestConfig) GetChainIndex(name string) (int, error) { + for i := range tc.ChainConfigs { + chainName := tc.GetChainName(i) + if chainName == name { + return i, nil + } + } + return -1, fmt.Errorf("chain %s not found in chain configs", name) +} + +// validateGenesisDebugConfig validates configuration of Genesis debug options/ +func (tc TestConfig) validateGenesisDebugConfig() error { + cfg := tc.DebugConfig.GenesisDebug + if !cfg.DumpGenesisDebugInfo { + return nil + } + + // Verify that the provided chain exists in our config + _, err := tc.GetChainIndex(tc.GetGenesisChainName()) + + return err +} + +// validateUpgradeConfig ensures the upgrade configuration is valid. +func (tc TestConfig) validateUpgradeConfig() error { + if strings.TrimSpace(tc.UpgradePlanName) == "" { + return nil + } + + // the upgrade plan name specified must match one of the upgrade plans in the upgrade configs. + foundPlan := false + for _, upgrade := range tc.UpgradeConfigs { + if strings.TrimSpace(upgrade.Tag) == "" { + return fmt.Errorf("upgrade config missing tag: %+v", upgrade) + } + + if strings.TrimSpace(upgrade.PlanName) == "" { + return fmt.Errorf("upgrade config missing plan name: %+v", upgrade) + } + + if upgrade.PlanName == tc.UpgradePlanName { + foundPlan = true + } + } + + if foundPlan { + return nil + } + + return fmt.Errorf("upgrade plan %s not found in upgrade configs: %+v", tc.UpgradePlanName, tc.UpgradeConfigs) +} + +// GetActiveRelayerConfig returns the currently specified relayer config. +func (tc TestConfig) GetActiveRelayerConfig() *relayer.Config { + for _, r := range tc.RelayerConfigs { + if r.ID == tc.ActiveRelayer { + return &r + } + } + return nil +} + +// GetChainNumValidators returns the number of validators for the specific chain index. +// default 1 +func (tc TestConfig) GetChainNumValidators(idx int) int { + if tc.ChainConfigs[idx].NumValidators > 0 { + return tc.ChainConfigs[idx].NumValidators + } + return 1 +} + +// GetChainNumFullNodes returns the number of full nodes for the specific chain index. +// default 0 +func (tc TestConfig) GetChainNumFullNodes(idx int) int { + if tc.ChainConfigs[idx].NumFullNodes > 0 { + return tc.ChainConfigs[idx].NumFullNodes + } + return 0 +} + +// GetChainID returns the chain-id for i. Assumes indicies are correct. +func (tc TestConfig) GetChainID(i int) string { + if tc.ChainConfigs[i].ChainID != "" { + return tc.ChainConfigs[i].ChainID + } + return fmt.Sprintf("chain%c-1", 'A'+i) +} + +// GetChainAID returns the chain-id for chain A. +// NOTE: the default return value will ensure that ParseChainID will return 1 as the revision number. +func (tc TestConfig) GetChainAID() string { + if tc.ChainConfigs[0].ChainID != "" { + return tc.ChainConfigs[0].ChainID + } + return "chainA-1" +} + +// GetChainBID returns the chain-id for chain B. +// NOTE: the default return value will ensure that ParseChainID will return 1 as the revision number. +func (tc TestConfig) GetChainBID() string { + if tc.ChainConfigs[1].ChainID != "" { + return tc.ChainConfigs[1].ChainID + } + return "chainB-1" +} + +// GetChainCID returns the chain-id for chain C. +// NOTE: the default return value will ensure that ParseChainID will return 1 as the revision number. +func (tc TestConfig) GetChainCID() string { + if tc.ChainConfigs[2].ChainID != "" { + return tc.ChainConfigs[2].ChainID + } + return "chainC-1" +} + +// GetChainDID returns the chain-id for chain D. +// NOTE: the default return value will ensure that ParseChainID will return 1 as the revision number. +func (tc TestConfig) GetChainDID() string { + if tc.ChainConfigs[3].ChainID != "" { + return tc.ChainConfigs[3].ChainID + } + return "chainD-1" +} + +// GetChainName returns the name of the chain given an index. +func (tc TestConfig) GetChainName(idx int) string { + // Assumes that only valid indices are provided. We do the same in several other places. + chainName := tc.ChainConfigs[idx].Name + if chainName == "" { + chainName = defaultChainNames[idx] + } + return chainName +} + +// GetGenesisChainName returns the name of the chain for which to dump Genesis files. +// If no chain is provided, it uses the default one (chainA). +func (tc TestConfig) GetGenesisChainName() string { + name := tc.DebugConfig.GenesisDebug.ChainName + if name == "" { + return tc.GetChainName(0) + } + return name +} + +// UpgradeConfig holds values relevant to upgrade tests. +type UpgradeConfig struct { + PlanName string `yaml:"planName"` + Tag string `yaml:"tag"` +} + +// ChainConfig holds information about an individual chain used in the tests. +type ChainConfig struct { + ChainID string `yaml:"chainId"` + Name string `yaml:"name"` + Image string `yaml:"image"` + Tag string `yaml:"tag"` + Binary string `yaml:"binary"` + NumValidators int `yaml:"numValidators"` + NumFullNodes int `yaml:"numFullNodes"` +} + +type CometBFTConfig struct { + LogLevel string `yaml:"logLevel"` +} + +type GenesisDebugConfig struct { + // DumpGenesisDebugInfo enables the output of Genesis debug files. + DumpGenesisDebugInfo bool `yaml:"dumpGenesisDebugInfo"` + + // ExportFilePath specifies which path to export Genesis debug files to. + ExportFilePath string `yaml:"filePath"` + + // ChainName represent which chain to get Genesis debug info for. + ChainName string `yaml:"chainName"` +} + +type DebugConfig struct { + // DumpLogs forces the logs to be collected before removing test containers. + DumpLogs bool `yaml:"dumpLogs"` + + // GenesisDebug contains debug information specific to Genesis. + GenesisDebug GenesisDebugConfig `yaml:"genesis"` + + // KeepContainers specifies if the containers should be kept after the test suite is done. + // NOTE: when running a full test suite, this value should be set to true in order to preserve + // shared resources. + KeepContainers bool `yaml:"keepContainers"` +} + +// LoadConfig attempts to load a test configuration from the default file path. +// if any environment variables are specified, they will take precedence over the individual configuration +// options. +func LoadConfig() TestConfig { + tc := getConfig() + if err := tc.Validate(); err != nil { + panic(err) + } + return tc +} + +// getConfig returns the TestConfig with any environment variable overrides. +func getConfig() TestConfig { + fileTc, foundFile := fromFile() + if !foundFile { + return fromEnv() + } + + testCfg := applyEnvironmentVariableOverrides(fileTc) + + // If tags for chain C and D are not present in the file, also not set in the CI, fallback to A + if testCfg.ChainConfigs[2].Tag == "" { + testCfg.ChainConfigs[2].Tag = testCfg.ChainConfigs[0].Tag + } + if testCfg.ChainConfigs[3].Tag == "" { + testCfg.ChainConfigs[3].Tag = testCfg.ChainConfigs[0].Tag + } + return testCfg +} + +// fromFile returns a TestConfig from a json file and a boolean indicating if the file was found. +func fromFile() (TestConfig, bool) { + var tc TestConfig + bz, err := os.ReadFile(getConfigFilePath()) + if err != nil { + return TestConfig{}, false + } + + if err := yaml.Unmarshal(bz, &tc); err != nil { + panic(err) + } + + return populateDefaults(tc), true +} + +// populateDefaults populates default values for the test config if +// certain required fields are not specified. +func populateDefaults(tc TestConfig) TestConfig { + chainIDs := []string{ + "chainA-1", + "chainB-1", + "chainC-1", + "chainD-1", + } + + for i := range tc.ChainConfigs { + if tc.ChainConfigs[i].ChainID == "" { + tc.ChainConfigs[i].ChainID = chainIDs[i] + } + if tc.ChainConfigs[i].Binary == "" { + tc.ChainConfigs[i].Binary = defaultBinary + } + if tc.ChainConfigs[i].Image == "" { + tc.ChainConfigs[i].Image = getChainImage(tc.ChainConfigs[i].Binary) + } + if tc.ChainConfigs[i].NumValidators == 0 { + tc.ChainConfigs[i].NumValidators = 1 + } + + // If tag not given for chain C and D, set to chain A' tag + if tc.ChainConfigs[i].Tag == "" && i != 0 { + tc.ChainConfigs[i].Tag = tc.ChainConfigs[0].Tag + } + } + + if tc.ActiveRelayer == "" { + tc.ActiveRelayer = relayer.Hermes + } + + if tc.RelayerConfigs == nil { + tc.RelayerConfigs = []relayer.Config{ + getDefaultRlyRelayerConfig(), + getDefaultHermesRelayerConfig(), + } + } + + if tc.CometBFTConfig.LogLevel == "" { + tc.CometBFTConfig.LogLevel = "info" + } + + return tc +} + +// applyEnvironmentVariableOverrides applies all environment variable changes to the config +// loaded from a file. +func applyEnvironmentVariableOverrides(fromFile TestConfig) TestConfig { + envTc := fromEnv() + + if os.Getenv(ChainATagEnv) != "" { + fromFile.ChainConfigs[0].Tag = envTc.ChainConfigs[0].Tag + } + + if os.Getenv(ChainBTagEnv) != "" { + fromFile.ChainConfigs[1].Tag = envTc.ChainConfigs[1].Tag + } + + if os.Getenv(ChainCTagEnv) != "" { + fromFile.ChainConfigs[2].Tag = envTc.ChainConfigs[2].Tag + } + + if os.Getenv(ChainDTagEnv) != "" { + fromFile.ChainConfigs[3].Tag = envTc.ChainConfigs[3].Tag + } + + if os.Getenv(ChainBinaryEnv) != "" { + for i := range fromFile.ChainConfigs { + fromFile.ChainConfigs[i].Binary = envTc.ChainConfigs[i].Binary + } + } + + if os.Getenv(ChainImageEnv) != "" { + for i := range fromFile.ChainConfigs { + fromFile.ChainConfigs[i].Image = envTc.ChainConfigs[i].Image + } + } + + if os.Getenv(RelayerIDEnv) != "" { + fromFile.ActiveRelayer = envTc.ActiveRelayer + } + + if os.Getenv(ChainUpgradePlanEnv) != "" { + fromFile.UpgradePlanName = envTc.UpgradePlanName + } + + if isEnvTrue(KeepContainersEnv) { + fromFile.DebugConfig.KeepContainers = true + } + + return fromFile +} + +// fromEnv returns a TestConfig constructed from environment variables. +func fromEnv() TestConfig { + return TestConfig{ + ChainConfigs: getChainConfigsFromEnv(), + UpgradePlanName: os.Getenv(ChainUpgradePlanEnv), + ActiveRelayer: os.Getenv(RelayerIDEnv), + CometBFTConfig: CometBFTConfig{LogLevel: "info"}, + } +} + +// getChainConfigsFromEnv returns the chain configs from environment variables. +func getChainConfigsFromEnv() []ChainConfig { + chainBinary, ok := os.LookupEnv(ChainBinaryEnv) + if !ok { + chainBinary = defaultBinary + } + + chainATag, ok := os.LookupEnv(ChainATagEnv) + if !ok { + chainATag = defaultChainTag + } + + chainBTag, ok := os.LookupEnv(ChainBTagEnv) + if !ok { + chainBTag = chainATag + } + + chainCTag, ok := os.LookupEnv(ChainCTagEnv) + if !ok { + chainCTag = chainATag + } + + chainDTag, ok := os.LookupEnv(ChainDTagEnv) + if !ok { + chainDTag = chainATag + } + + chainAImage := getChainImage(chainBinary) + specifiedChainImage, ok := os.LookupEnv(ChainImageEnv) + if ok { + chainAImage = specifiedChainImage + } + + numValidators := 4 + numFullNodes := 1 + + chainBImage := chainAImage + chainCImage := chainAImage + chainDImage := chainAImage + + return []ChainConfig{ + { + Image: chainAImage, + Tag: chainATag, + Binary: chainBinary, + NumValidators: numValidators, + NumFullNodes: numFullNodes, + }, + { + Image: chainBImage, + Tag: chainBTag, + Binary: chainBinary, + NumValidators: numValidators, + NumFullNodes: numFullNodes, + }, + { + Image: chainCImage, + Tag: chainCTag, + Binary: chainBinary, + NumValidators: numValidators, + NumFullNodes: numFullNodes, + }, + { + Image: chainDImage, + Tag: chainDTag, + Binary: chainBinary, + NumValidators: numValidators, + NumFullNodes: numFullNodes, + }, + } +} + +// getConfigFilePath returns the absolute path where the e2e config file should be. +func getConfigFilePath() string { + if specifiedConfigPath := os.Getenv(E2EConfigFilePathEnv); specifiedConfigPath != "" { + if path.IsAbs(specifiedConfigPath) { + return specifiedConfigPath + } + + e2eDir, err := directories.E2E() + if err != nil { + panic(err) + } + + return path.Join(e2eDir, specifiedConfigPath) + } + + if IsCI() { + if err := os.Setenv(E2EConfigFilePathEnv, defaultCIConfigFileName); err != nil { + panic(err) + } + return getConfigFilePath() + } + + // running locally. + homeDir, err := os.UserHomeDir() + if err != nil { + panic(err) + } + return path.Join(homeDir, defaultConfigFileName) +} + +// TODO: remove in https://github.com/cosmos/ibc-go/issues/4697 +// getDefaultHermesRelayerConfig returns the default config for the hermes relayer. +func getDefaultHermesRelayerConfig() relayer.Config { + return relayer.Config{ + Tag: defaultHermesTag, + ID: relayer.Hermes, + Image: relayer.HermesRelayerRepository, + } +} + +// TODO: remove in https://github.com/cosmos/ibc-go/issues/4697 +// getDefaultRlyRelayerConfig returns the default config for the golang relayer. +func getDefaultRlyRelayerConfig() relayer.Config { + return relayer.Config{ + Tag: defaultRlyTag, + ID: relayer.Rly, + Image: relayer.RlyRelayerRepository, + } +} + +func GetChainATag() string { + return LoadConfig().ChainConfigs[0].Tag +} + +func GetChainBTag() string { + if chainBTag := LoadConfig().ChainConfigs[1].Tag; chainBTag != "" { + return chainBTag + } + return GetChainATag() +} + +// IsCI returns true if the tests are running in CI, false is returned +// if the tests are running locally. +// Note: github actions passes a CI env value of true by default to all runners. +func IsCI() bool { + return isEnvTrue("CI") +} + +// IsFork returns true if the tests are running in fork mode, false is returned otherwise. +func IsFork() bool { + return isEnvTrue("FORK") +} + +// IsRunSuite returns true if the tests are running in suite mode, false is returned otherwise. +func IsRunSuite() bool { + return isEnvTrue("RUN_SUITE") +} + +func isEnvTrue(env string) bool { + return strings.ToLower(os.Getenv(env)) == "true" +} + +// ChainOptions stores chain configurations for the chains that will be +// created for the tests. They can be modified by passing ChainOptionConfiguration +// to E2ETestSuite.GetChains. +type ChainOptions struct { + ChainSpecs []*interchaintest.ChainSpec + SkipPathCreation bool + RelayerCount int +} + +// ChainOptionConfiguration enables arbitrary configuration of ChainOptions. +type ChainOptionConfiguration func(options *ChainOptions) + +// DefaultChainOptions returns the default configuration for the chains. +// These options can be configured by passing configuration functions to E2ETestSuite.GetChains. +func DefaultChainOptions(chainCount int) (ChainOptions, error) { + tc := LoadConfig() + + if len(tc.ChainConfigs) < chainCount { + return ChainOptions{}, fmt.Errorf("file has %d configs. want %d configs", len(tc.ChainConfigs), chainCount) + } + + specs := make([]*interchaintest.ChainSpec, 0, chainCount) + for i := range chainCount { + denom := fmt.Sprintf("atom%c", 'a'+i) + chainName := tc.GetChainName(i) + chainID := tc.GetChainID(i) + cfg := newDefaultSimappConfig(tc.ChainConfigs[0], chainName, chainID, denom, tc.CometBFTConfig) + validators, fullNodes := getValidatorsAndFullNodes(i) + + spec := &interchaintest.ChainSpec{ + ChainConfig: cfg, + NumFullNodes: &fullNodes, + NumValidators: &validators, + } + specs = append(specs, spec) + } + + // if running a single test, only one relayer is needed. + numRelayers := 1 + if IsRunSuite() { + // Arbitrary number; if interchaintest supports dynamic relayer creation during tests, + // this can be reduced or simplified. + numRelayers = 10 + } + + return ChainOptions{ + ChainSpecs: specs, + RelayerCount: numRelayers, + }, nil +} + +// newDefaultSimappConfig creates an ibc configuration for simd. +func newDefaultSimappConfig(cc ChainConfig, name, chainID, denom string, cometCfg CometBFTConfig) ibc.ChainConfig { + configFileOverrides := make(map[string]any) + tmTomlOverrides := make(interchaintestutil.Toml) + + tmTomlOverrides["log_level"] = cometCfg.LogLevel // change to debug in the e2e test config to increase cometbft logging. + configFileOverrides["config/config.toml"] = tmTomlOverrides + + return ibc.ChainConfig{ + Type: "cosmos", + Name: name, + ChainID: chainID, + Images: []ibc.DockerImage{ + { + Repository: cc.Image, + Version: cc.Tag, + UIDGID: "1000:1000", + }, + }, + Bin: cc.Binary, + Bech32Prefix: "cosmos", + CoinType: fmt.Sprint(sdk.CoinType), + Denom: denom, + EncodingConfig: SDKEncodingConfig(), + GasPrices: fmt.Sprintf("0.00%s", denom), + GasAdjustment: 1.3, + TrustingPeriod: "508h", + NoHostMount: false, + ModifyGenesis: getGenesisModificationFunction(cc), + ConfigFileOverrides: configFileOverrides, + } +} + +// getGenesisModificationFunction returns a genesis modification function that handles the GenesisState type +// correctly depending on if the govv1beta1 gov module is used or if govv1 is being used. +func getGenesisModificationFunction(cc ChainConfig) func(ibc.ChainConfig, []byte) ([]byte, error) { + binary := cc.Binary + version := cc.Tag + + simdSupportsGovV1Genesis := binary == defaultBinary && testvalues.GovGenesisFeatureReleases.IsSupported(version) + + // TODO: Remove after we drop v7 support (this is only needed right now because of v6 -> v7 upgrade tests) + if simdSupportsGovV1Genesis { + return defaultGovv1ModifyGenesis(version) + } + + return defaultGovv1Beta1ModifyGenesis(version) +} + +// defaultGovv1ModifyGenesis will only modify governance params to ensure the voting period and minimum deposit +// are functional for e2e testing purposes. +func defaultGovv1ModifyGenesis(version string) func(ibc.ChainConfig, []byte) ([]byte, error) { + stdlibJSONMarshalling := semverutil.FeatureReleases{MajorVersion: "v8"} + return func(chainConfig ibc.ChainConfig, genbz []byte) ([]byte, error) { + appGenesis, err := genutiltypes.AppGenesisFromReader(bytes.NewReader(genbz)) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal genesis bytes into genesis doc: %w", err) + } + + var appState genutiltypes.AppMap + if err := json.Unmarshal(appGenesis.AppState, &appState); err != nil { + return nil, fmt.Errorf("failed to unmarshal genesis bytes into app state: %w", err) + } + + govGenBz, err := modifyGovV1AppState(chainConfig, appState[govtypes.ModuleName]) + if err != nil { + return nil, err + } + appState[govtypes.ModuleName] = govGenBz + + if !testvalues.AllowAllClientsWildcardFeatureReleases.IsSupported(version) { + ibcGenBz, err := modifyClientGenesisAppState(appState[ibcexported.ModuleName]) + if err != nil { + return nil, err + } + appState[ibcexported.ModuleName] = ibcGenBz + } + + if !testvalues.ChannelParamsFeatureReleases.IsSupported(version) { + ibcGenBz, err := modifyChannelGenesisAppState(appState[ibcexported.ModuleName]) + if err != nil { + return nil, err + } + appState[ibcexported.ModuleName] = ibcGenBz + } + + if !testvalues.ChannelsV2FeatureReleases.IsSupported(version) { + ibcGenBz, err := modifyChannelV2GenesisAppState(appState[ibcexported.ModuleName]) + if err != nil { + return nil, err + } + appState[ibcexported.ModuleName] = ibcGenBz + } + + if !testvalues.ClientV2FeatureReleases.IsSupported(version) { + ibcGenBz, err := modifyClientV2GenesisAppState(appState[ibcexported.ModuleName]) + if err != nil { + return nil, err + } + appState[ibcexported.ModuleName] = ibcGenBz + } + + appGenesis.AppState, err = json.Marshal(appState) + if err != nil { + return nil, err + } + + // in older version < v8, tmjson marshal must be used. + // regular json marshalling must be used for v8 and above as the + // sdk is de-coupled from comet. + marshalIndentFn := cmtjson.MarshalIndent + if stdlibJSONMarshalling.IsSupported(version) { + marshalIndentFn = json.MarshalIndent + } + + bz, err := marshalIndentFn(appGenesis, "", " ") + if err != nil { + return nil, err + } + + return bz, nil + } +} + +// defaultGovv1Beta1ModifyGenesis will only modify governance params to ensure the voting period and minimum deposit +// // are functional for e2e testing purposes. +func defaultGovv1Beta1ModifyGenesis(version string) func(ibc.ChainConfig, []byte) ([]byte, error) { + const appStateKey = "app_state" + return func(chainConfig ibc.ChainConfig, genbz []byte) ([]byte, error) { + genesisDocMap := map[string]any{} + err := json.Unmarshal(genbz, &genesisDocMap) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal genesis bytes into genesis doc: %w", err) + } + + appStateMap, ok := genesisDocMap[appStateKey].(map[string]any) + if !ok { + return nil, errors.New("failed to extract to app_state") + } + + govModuleBytes, err := json.Marshal(appStateMap[govtypes.ModuleName]) + if err != nil { + return nil, fmt.Errorf("failed to extract gov genesis bytes: %s", err) + } + + govModuleGenesisBytes, err := modifyGovv1Beta1AppState(chainConfig, govModuleBytes) + if err != nil { + return nil, err + } + + govModuleGenesisMap := map[string]any{} + err = json.Unmarshal(govModuleGenesisBytes, &govModuleGenesisMap) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal gov genesis bytes into map: %w", err) + } + + if !testvalues.AllowAllClientsWildcardFeatureReleases.IsSupported(version) { + ibcModuleBytes, err := json.Marshal(appStateMap[ibcexported.ModuleName]) + if err != nil { + return nil, fmt.Errorf("failed to extract ibc genesis bytes: %s", err) + } + + ibcGenesisBytes, err := modifyClientGenesisAppState(ibcModuleBytes) + if err != nil { + return nil, err + } + + ibcModuleGenesisMap := map[string]any{} + err = json.Unmarshal(ibcGenesisBytes, &ibcModuleGenesisMap) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal gov genesis bytes into map: %w", err) + } + appStateMap[ibcexported.ModuleName] = ibcModuleGenesisMap + } + + if !testvalues.ChannelParamsFeatureReleases.IsSupported(version) { + ibcModuleBytes, err := json.Marshal(appStateMap[ibcexported.ModuleName]) + if err != nil { + return nil, fmt.Errorf("failed to extract ibc genesis bytes: %s", err) + } + + ibcGenesisBytes, err := modifyChannelGenesisAppState(ibcModuleBytes) + if err != nil { + return nil, err + } + + ibcModuleGenesisMap := map[string]any{} + err = json.Unmarshal(ibcGenesisBytes, &ibcModuleGenesisMap) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal gov genesis bytes into map: %w", err) + } + appStateMap[ibcexported.ModuleName] = ibcModuleGenesisMap + + } + + if !testvalues.ChannelsV2FeatureReleases.IsSupported(version) { + ibcModuleBytes, err := json.Marshal(appStateMap[ibcexported.ModuleName]) + if err != nil { + return nil, fmt.Errorf("failed to extract ibc genesis bytes: %s", err) + } + + ibcGenesisBytes, err := modifyChannelV2GenesisAppState(ibcModuleBytes) + if err != nil { + return nil, err + } + + ibcModuleGenesisMap := map[string]any{} + err = json.Unmarshal(ibcGenesisBytes, &ibcModuleGenesisMap) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal gov genesis bytes into map: %w", err) + } + appStateMap[ibcexported.ModuleName] = ibcModuleGenesisMap + } + + if !testvalues.ClientV2FeatureReleases.IsSupported(version) { + ibcModuleBytes, err := json.Marshal(appStateMap[ibcexported.ModuleName]) + if err != nil { + return nil, fmt.Errorf("failed to extract ibc genesis bytes: %s", err) + } + + ibcGenesisBytes, err := modifyClientV2GenesisAppState(ibcModuleBytes) + if err != nil { + return nil, err + } + + ibcModuleGenesisMap := map[string]any{} + err = json.Unmarshal(ibcGenesisBytes, &ibcModuleGenesisMap) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal gov genesis bytes into map: %w", err) + } + appStateMap[ibcexported.ModuleName] = ibcModuleGenesisMap + } + + appStateMap[govtypes.ModuleName] = govModuleGenesisMap + genesisDocMap[appStateKey] = appStateMap + + finalGenesisDocBytes, err := json.MarshalIndent(genesisDocMap, "", " ") + if err != nil { + return nil, err + } + + return finalGenesisDocBytes, nil + } +} + +// modifyGovV1AppState takes the existing gov app state and marshals it to a govv1 GenesisState. +func modifyGovV1AppState(chainConfig ibc.ChainConfig, govAppState []byte) ([]byte, error) { + cfg := testutil.MakeTestEncodingConfig() + + cdc := codec.NewProtoCodec(cfg.InterfaceRegistry) + govv1.RegisterInterfaces(cfg.InterfaceRegistry) + + govGenesisState := &govv1.GenesisState{} + + if err := cdc.UnmarshalJSON(govAppState, govGenesisState); err != nil { + return nil, fmt.Errorf("failed to unmarshal genesis bytes into gov genesis state: %w", err) + } + + if govGenesisState.Params == nil { + govGenesisState.Params = &govv1.Params{} + } + + govGenesisState.Params.MinDeposit = sdk.NewCoins(sdk.NewCoin(chainConfig.Denom, govv1beta1.DefaultMinDepositTokens)) + maxDep := time.Second * 10 + govGenesisState.Params.MaxDepositPeriod = &maxDep + vp := testvalues.VotingPeriod + govGenesisState.Params.VotingPeriod = &vp + + govGenBz := MustProtoMarshalJSON(govGenesisState) + + return govGenBz, nil +} + +// modifyGovv1Beta1AppState takes the existing gov app state and marshals it to a govv1beta1 GenesisState. +func modifyGovv1Beta1AppState(chainConfig ibc.ChainConfig, govAppState []byte) ([]byte, error) { + cfg := testutil.MakeTestEncodingConfig() + + cdc := codec.NewProtoCodec(cfg.InterfaceRegistry) + govv1beta1.RegisterInterfaces(cfg.InterfaceRegistry) + + govGenesisState := &govv1beta1.GenesisState{} + if err := cdc.UnmarshalJSON(govAppState, govGenesisState); err != nil { + return nil, fmt.Errorf("failed to unmarshal genesis bytes into govv1beta1 genesis state: %w", err) + } + + govGenesisState.DepositParams.MinDeposit = sdk.NewCoins(sdk.NewCoin(chainConfig.Denom, govv1beta1.DefaultMinDepositTokens)) + govGenesisState.VotingParams.VotingPeriod = testvalues.VotingPeriod + + govGenBz, err := cdc.MarshalJSON(govGenesisState) + if err != nil { + return nil, fmt.Errorf("failed to marshal gov genesis state: %w", err) + } + + return govGenBz, nil +} + +// modifyClientGenesisAppState takes the existing ibc app state and marshals it to an ibc GenesisState. +func modifyClientGenesisAppState(ibcAppState []byte) ([]byte, error) { + cfg := testutil.MakeTestEncodingConfig() + + cdc := codec.NewProtoCodec(cfg.InterfaceRegistry) + clienttypes.RegisterInterfaces(cfg.InterfaceRegistry) + + ibcGenesisState := &ibctypes.GenesisState{} + if err := cdc.UnmarshalJSON(ibcAppState, ibcGenesisState); err != nil { + return nil, fmt.Errorf("failed to unmarshal genesis bytes into client genesis state: %w", err) + } + + ibcGenesisState.ClientGenesis.Params.AllowedClients = append(ibcGenesisState.ClientGenesis.Params.AllowedClients, wasmtypes.Wasm) + ibcGenBz, err := cdc.MarshalJSON(ibcGenesisState) + if err != nil { + return nil, fmt.Errorf("failed to marshal gov genesis state: %w", err) + } + + return ibcGenBz, nil +} + +// modifyChannelGenesisAppState takes the existing ibc app state, unmarshals it to a map and removes the `params` entry from ibc channel genesis. +// It marshals and returns the ibc GenesisState JSON map as bytes. +func modifyChannelGenesisAppState(ibcAppState []byte) ([]byte, error) { + var ibcGenesisMap map[string]any + if err := json.Unmarshal(ibcAppState, &ibcGenesisMap); err != nil { + return nil, err + } + + var channelGenesis map[string]any + // be ashamed, be very ashamed + channelGenesis, ok := ibcGenesisMap["channel_genesis"].(map[string]any) + if !ok { + return nil, fmt.Errorf("can't convert IBC genesis map entry into type %T", &channelGenesis) + } + delete(channelGenesis, "params") + + return json.Marshal(ibcGenesisMap) +} + +func modifyChannelV2GenesisAppState(ibcAppState []byte) ([]byte, error) { + var ibcGenesisMap map[string]any + if err := json.Unmarshal(ibcAppState, &ibcGenesisMap); err != nil { + return nil, err + } + delete(ibcGenesisMap, "channel_v2_genesis") + + return json.Marshal(ibcGenesisMap) +} + +func modifyClientV2GenesisAppState(ibcAppState []byte) ([]byte, error) { + var ibcGenesisMap map[string]any + if err := json.Unmarshal(ibcAppState, &ibcGenesisMap); err != nil { + return nil, err + } + delete(ibcGenesisMap, "client_v2_genesis") + + return json.Marshal(ibcGenesisMap) +} diff --git a/e2e/testsuite/testsuite.go b/e2e/testsuite/testsuite.go new file mode 100644 index 0000000..513231e --- /dev/null +++ b/e2e/testsuite/testsuite.go @@ -0,0 +1,740 @@ +package testsuite + +import ( + "context" + "errors" + "fmt" + "os" + "path" + "slices" + "strings" + "sync" + + interchaintest "github.com/cosmos/interchaintest/v10" + "github.com/cosmos/interchaintest/v10/chain/cosmos" + "github.com/cosmos/interchaintest/v10/ibc" + "github.com/cosmos/interchaintest/v10/testreporter" + test "github.com/cosmos/interchaintest/v10/testutil" + mobycli "github.com/moby/moby/client" + testifysuite "github.com/stretchr/testify/suite" + "go.uber.org/zap" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + "github.com/cosmos/ibc-go/e2e/internal/directories" + "github.com/cosmos/ibc-go/e2e/relayer" + "github.com/cosmos/ibc-go/e2e/testsuite/diagnostics" + "github.com/cosmos/ibc-go/e2e/testsuite/query" + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" +) + +const ( + // ChainARelayerName is the name given to the relayer wallet on ChainA + ChainARelayerName = "rlyA" + // ChainBRelayerName is the name given to the relayer wallet on ChainB + ChainBRelayerName = "rlyB" + // DefaultGasValue is the default gas value used to configure tx.Factory + DefaultGasValue = 500_000_0000 +) + +// E2ETestSuite has methods and functionality which can be shared among all test suites. +type E2ETestSuite struct { + testifysuite.Suite + + // proposalIDs keeps track of the active proposal ID for each chain. + proposalIDs map[string]uint64 + + // chains is a list of chains that are created for the test suite. + // each test suite has a single slice of chains that are used for all individual test + // cases. + chains []ibc.Chain + relayerWallets relayer.Map + logger *zap.Logger + DockerClient *mobycli.Client + network string + + // pathNameIndex is the latest index to be used for generating chains + pathNameIndex int64 + + // testSuiteName is the name of the test suite, used to store chains under the test suite name. + testSuiteName string + testPaths map[string][]string + channels map[string]map[ibc.Chain][]ibc.ChannelOutput + + // channelLock ensures concurrent tests are not creating and accessing channels as the same time. + channelLock sync.Mutex + // relayerLock ensures concurrent tests are not accessing the pool of relayers as the same time. + relayerLock sync.Mutex + // relayerPool is a pool of relayers that can be used in tests. + relayerPool []ibc.Relayer + // testRelayerMap is a map of test suite names to relayers that are used in the test suite. + // this is used as a cache after a relayer has been assigned to a test suite. + testRelayerMap map[string]ibc.Relayer +} + +// initState populates variables that are used across the test suite. +// note: this should be called only from SetupSuite. +func (s *E2ETestSuite) initState() { + s.initDockerClient() + s.proposalIDs = map[string]uint64{} + s.testPaths = make(map[string][]string) + s.channels = make(map[string]map[ibc.Chain][]ibc.ChannelOutput) + s.relayerPool = []ibc.Relayer{} + s.testRelayerMap = make(map[string]ibc.Relayer) + s.relayerWallets = make(relayer.Map) + + // testSuiteName gets populated in the context of SetupSuite and stored as s.T().Name() + // will return the name of the suite and test when called from SetupTest or within the body of tests. + // the chains will be stored under the test suite name, so we need to store this for future use. + s.testSuiteName = s.T().Name() +} + +// initDockerClient creates a docker client and populates the network to be used for the test. +func (s *E2ETestSuite) initDockerClient() { + client, network := interchaintest.DockerSetup(s.T()) + s.logger = zap.NewExample() + s.DockerClient = client + s.network = network +} + +// configureGenesisDebugExport sets, if needed, env variables to enable exporting of Genesis debug files. +func (s *E2ETestSuite) configureGenesisDebugExport() { + tc := LoadConfig() + t := s.T() + cfg := tc.DebugConfig.GenesisDebug + if !cfg.DumpGenesisDebugInfo { + return + } + + // Set the export path. + exportPath := cfg.ExportFilePath + + // If no path is provided, use the default (e2e/diagnostics/genesis.json). + if exportPath == "" { + e2eDir, err := directories.E2E() + s.Require().NoError(err, "can't get e2edir") + exportPath = path.Join(e2eDir, directories.DefaultGenesisExportPath) + } + + if !path.IsAbs(exportPath) { + wd, err := os.Getwd() + s.Require().NoError(err, "can't get working directory") + exportPath = path.Join(wd, exportPath) + } + + // These environment variables are set by interchaintest at runtime. + // Reference: https://github.com/cosmos/interchaintest/blob/main/chain/cosmos/cosmos_chain.go + t.Setenv("EXPORT_GENESIS_FILE_PATH", exportPath) + + chainName := tc.GetGenesisChainName() + chainIdx, err := tc.GetChainIndex(chainName) + s.Require().NoError(err) + + // interchaintest adds a numeric suffix to the chain name, so we do the same. + genesisChainName := fmt.Sprintf("%s-%d", chainName, chainIdx+1) + t.Setenv("EXPORT_GENESIS_CHAIN", genesisChainName) +} + +// initializeRelayerPool pre-loads the relayer pool with n relayers. +// this is a workaround due to the restriction on relayer creation during the test +// If/when interchaintest supports relayer creation during tests, this can be made lazy per-test. +func (s *E2ETestSuite) initializeRelayerPool(n int) []ibc.Relayer { + var relayers []ibc.Relayer + for range n { + relayers = append(relayers, relayer.New(s.T(), *LoadConfig().GetActiveRelayerConfig(), s.logger, s.DockerClient, s.network)) + } + return relayers +} + +// SetupChains creates the chains for the test suite, and also a relayer that is wired up to establish +// connections and channels between the chains. +func (s *E2ETestSuite) SetupChains(ctx context.Context, chainCount int, channelOptionsModifier ChainOptionModifier, chainSpecOpts ...ChainOptionConfiguration) { + s.T().Logf("Setting up %d chains: %s", chainCount, s.T().Name()) + + if LoadConfig().DebugConfig.KeepContainers { + s.Require().NoError(os.Setenv(KeepContainersEnv, "true")) + } + + s.initState() + s.configureGenesisDebugExport() + + chainOptions, err := DefaultChainOptions(chainCount) + s.Require().NoError(err) + + for _, opt := range chainSpecOpts { + opt(&chainOptions) + } + + specCount := len(chainOptions.ChainSpecs) + s.Require().GreaterOrEqualf(specCount, chainCount, "wants to create %d chains, but DefaultChainOptions has %d configurations", chainCount, specCount) + + s.chains = s.createChains(chainCount, chainOptions) + + s.relayerPool = s.initializeRelayerPool(chainOptions.RelayerCount) + + ic := s.newInterchain(s.relayerPool, s.chains, channelOptionsModifier) + + buildOpts := interchaintest.InterchainBuildOptions{ + TestName: s.T().Name(), + Client: s.DockerClient, + NetworkID: s.network, + // we skip path creation because we are just creating the chains and not connections/channels + SkipPathCreation: true, + } + + s.Require().NoError(ic.Build(ctx, s.GetRelayerExecReporter(), buildOpts)) + + // setup query paths for GRPC queries: + for _, chain := range s.chains { + s.Require().NoError(query.PopulateQueryReqToPath(ctx, chain)) + } +} + +// CreateDefaultPaths creates a path between the chains using the default client and channel options. +// this should be called as the setup function in most tests if no additional options are required. +func (s *E2ETestSuite) CreateDefaultPaths(testName string) ibc.Relayer { + return s.CreatePaths(ibc.DefaultClientOpts(), DefaultChannelOpts(s.GetAllChains()), testName) +} + +// CreatePaths creates paths between the chains using the provided client and channel options. +// The paths are created such that ChainA is connected to ChainB, ChainB is connected to ChainC etc. +func (s *E2ETestSuite) CreatePaths(clientOpts ibc.CreateClientOptions, channelOpts ibc.CreateChannelOptions, testName string) ibc.Relayer { + s.T().Logf("Setting up path for: %s", testName) + + if s.channels[testName] == nil { + s.channels[testName] = make(map[ibc.Chain][]ibc.ChannelOutput) + } + + r := s.GetRelayerForTest(testName) + + ctx := context.TODO() + allChains := s.GetAllChains() + for i := range len(allChains) - 1 { + chainA, chainB := allChains[i], allChains[i+1] + s.CreatePath(ctx, r, chainA, chainB, clientOpts, channelOpts, testName) + } + + return r +} + +// CreatePath creates a path between chainA and chainB using the provided client and channel options. +func (s *E2ETestSuite) CreatePath( + ctx context.Context, + r ibc.Relayer, + chainA ibc.Chain, + chainB ibc.Chain, + clientOpts ibc.CreateClientOptions, + channelOpts ibc.CreateChannelOptions, + testName string, +) (chainAChannel ibc.ChannelOutput, chainBChannel ibc.ChannelOutput) { + pathName := s.generatePathName() + s.testPaths[testName] = append(s.testPaths[testName], pathName) + + s.T().Logf("establishing path between %s and %s on path %s", chainA.Config().ChainID, chainB.Config().ChainID, pathName) + + err := r.GeneratePath(ctx, s.GetRelayerExecReporter(), chainA.Config().ChainID, chainB.Config().ChainID, pathName) + s.Require().NoError(err) + + // Create new clients + err = r.CreateClients(ctx, s.GetRelayerExecReporter(), pathName, clientOpts) + s.Require().NoError(err) + err = test.WaitForBlocks(ctx, 1, chainA, chainB) + s.Require().NoError(err) + + err = r.CreateConnections(ctx, s.GetRelayerExecReporter(), pathName) + s.Require().NoError(err) + err = test.WaitForBlocks(ctx, 1, chainA, chainB) + s.Require().NoError(err) + + s.createChannelWithLock(ctx, r, pathName, testName, channelOpts, chainA, chainB) + + aChannels := s.channels[testName][chainA] + bChannels := s.channels[testName][chainB] + + return aChannels[len(aChannels)-1], bChannels[len(bChannels)-1] +} + +// createChannelWithLock creates a channel between the two provided chains for the given test name. This applies a lock +// to ensure that the channels that are created are correctly mapped to the test that created them. +func (s *E2ETestSuite) createChannelWithLock(ctx context.Context, r ibc.Relayer, pathName, testName string, channelOpts ibc.CreateChannelOptions, chainA, chainB ibc.Chain) { + // NOTE: we need to lock the creation of channels and applying of packet filters, as if we don't, the result + // of `r.GetChannels` may return channels created by other relayers in different tests. + s.channelLock.Lock() + defer s.channelLock.Unlock() + + err := r.CreateChannel(ctx, s.GetRelayerExecReporter(), pathName, channelOpts) + s.Require().NoError(err) + err = test.WaitForBlocks(ctx, 1, chainA, chainB) + s.Require().NoError(err) + + for _, c := range []ibc.Chain{chainA, chainB} { + channels, err := r.GetChannels(ctx, s.GetRelayerExecReporter(), c.Config().ChainID) + s.Require().NoError(err) + + if _, ok := s.channels[testName][c]; !ok { + s.channels[testName][c] = []ibc.ChannelOutput{} + } + + // keep track of channels associated with a given chain for access within the tests. + // only the most recent channel is relevant. + s.channels[testName][c] = append(s.channels[testName][c], getLatestChannel(channels)) + + err = relayer.ApplyPacketFilter(ctx, s.T(), r, c.Config().ChainID, s.channels[testName][c]) + s.Require().NoError(err, "failed to watch port and channel on chain: %s", c.Config().ChainID) + } +} + +// getLatestChannel returns the latest channel from the list of channels. +func getLatestChannel(channels []ibc.ChannelOutput) ibc.ChannelOutput { + return slices.MaxFunc(channels, func(a, b ibc.ChannelOutput) int { + seqA, _ := channeltypes.ParseChannelSequence(a.ChannelID) + seqB, _ := channeltypes.ParseChannelSequence(b.ChannelID) + return int(seqA - seqB) + }) +} + +// GetChainAChannelForTest returns the ibc.ChannelOutput for the current test. +// this defaults to the first entry in the list, and will be what is needed in the case of +// a single channel test. +func (s *E2ETestSuite) GetChainAChannelForTest(testName string) ibc.ChannelOutput { + return s.GetChannelsForTest(s.GetAllChains()[0], testName)[0] +} + +// GetChannelsForTest returns all channels for the specified test. +func (s *E2ETestSuite) GetChannelsForTest(chain ibc.Chain, testName string) []ibc.ChannelOutput { + channels, ok := s.channels[testName][chain] + s.Require().True(ok, "channel not found for test %s", testName) + return channels +} + +// GetRelayerForTest returns the relayer for the current test from the available pool of relayers. +// once a relayer has been returned to a test, it is cached and will be reused for the duration of the test. +func (s *E2ETestSuite) GetRelayerForTest(testName string) ibc.Relayer { + s.relayerLock.Lock() + defer s.relayerLock.Unlock() + + if r, ok := s.testRelayerMap[testName]; ok { + s.T().Logf("relayer already created for test: %s", testName) + return r + } + + if len(s.relayerPool) == 0 { + panic(errors.New("relayer pool is empty")) + } + + r := s.relayerPool[0] + + // remove the relayer from the pool + s.relayerPool = s.relayerPool[1:] + + s.testRelayerMap[testName] = r + + return r +} + +// GetRelayerUsers returns two ibc.Wallet instances which can be used for the relayer users +// on the two chains. +func (s *E2ETestSuite) GetRelayerUsers(ctx context.Context, testName string) (ibc.Wallet, ibc.Wallet) { + chains := s.GetAllChains() + chainA, chainB := chains[0], chains[1] + + rlyAName := fmt.Sprintf("%s-%s", ChainARelayerName, testName) + rlyBName := fmt.Sprintf("%s-%s", ChainBRelayerName, testName) + + chainAAccountBytes, err := chainA.GetAddress(ctx, rlyAName) + s.Require().NoError(err) + + chainBAccountBytes, err := chainB.GetAddress(ctx, rlyBName) + s.Require().NoError(err) + + chainARelayerUser := cosmos.NewWallet(rlyAName, chainAAccountBytes, "", chainA.Config()) + chainBRelayerUser := cosmos.NewWallet(rlyBName, chainBAccountBytes, "", chainB.Config()) + + s.relayerWallets.AddRelayer(testName, chainARelayerUser) + s.relayerWallets.AddRelayer(testName, chainBRelayerUser) + + return chainARelayerUser, chainBRelayerUser +} + +// ChainOptionModifier is a function which accepts 2 chains as inputs, and returns a channel creation modifier function +// in order to conditionally modify the channel options based on the chains being used. +type ChainOptionModifier func(chainA, chainB ibc.Chain) func(options *ibc.CreateChannelOptions) + +// newInterchain constructs a new interchain instance that creates channels between the chains. +func (s *E2ETestSuite) newInterchain(relayers []ibc.Relayer, chains []ibc.Chain, modificationProvider ChainOptionModifier) *interchaintest.Interchain { + ic := interchaintest.NewInterchain() + for _, chain := range chains { + ic.AddChain(chain) + } + + for i, r := range relayers { + ic.AddRelayer(r, fmt.Sprintf("r-%d", i)) + } + + // iterate through all chains, and create links such that there is a channel between + // - chainA and chainB + // - chainB and chainC + // - chainC and chainD etc + for i := range len(chains) - 1 { + pathName := s.generatePathName() + channelOpts := DefaultChannelOpts(chains) + chain1, chain2 := chains[i], chains[i+1] + + if modificationProvider != nil { + // make a modification to the channel options based on the chains which are being used. + modificationFn := modificationProvider(chain1, chain2) + modificationFn(&channelOpts) + } + + for _, r := range relayers { + ic.AddLink(interchaintest.InterchainLink{ + Chain1: chains[i], + Chain2: chains[i+1], + Relayer: r, + Path: pathName, + CreateChannelOpts: channelOpts, + }) + } + } + + return ic +} + +// generatePathName generates the path name using the test suites name +func (s *E2ETestSuite) generatePathName() string { + pathName := GetPathName(s.pathNameIndex) + s.pathNameIndex++ + return pathName +} + +func (s *E2ETestSuite) GetPaths(testName string) []string { + paths, ok := s.testPaths[testName] + s.Require().True(ok, "paths not found for test %s", testName) + return paths +} + +// GetPathName returns the name of a path at a specific index. This can be used in tests +// when the path name is required. +func GetPathName(idx int64) string { + pathName := fmt.Sprintf("path-%d", idx) + return strings.ReplaceAll(pathName, "/", "-") +} + +// generatePath generates the path name using the test suites name. The indices provided specify which chains should be +// used. E.g. to generate a path between chain A and B, you would use 0 and 1, to specify between A and C, you would +// use 0 and 2 etc. +func (s *E2ETestSuite) generatePath(ctx context.Context, ibcrelayer ibc.Relayer, chainAIdx, chainBIdx int) string { + chains := s.GetAllChains() + chainA, chainB := chains[chainAIdx], chains[chainBIdx] + chainAID := chainA.Config().ChainID + chainBID := chainB.Config().ChainID + + pathName := s.generatePathName() + + err := ibcrelayer.GeneratePath(ctx, s.GetRelayerExecReporter(), chainAID, chainBID, pathName) + s.Require().NoError(err) + + return pathName +} + +// SetupClients creates clients on chainA and chainB using the provided create client options +func (s *E2ETestSuite) SetupClients(ctx context.Context, ibcrelayer ibc.Relayer, opts ibc.CreateClientOptions) { + pathName := s.generatePath(ctx, ibcrelayer, 0, 1) + err := ibcrelayer.CreateClients(ctx, s.GetRelayerExecReporter(), pathName, opts) + s.Require().NoError(err) +} + +// UpdateClients updates clients on chainA and chainB +func (s *E2ETestSuite) UpdateClients(ctx context.Context, ibcrelayer ibc.Relayer, pathName string) { + err := ibcrelayer.UpdateClients(ctx, s.GetRelayerExecReporter(), pathName) + s.Require().NoError(err) +} + +// GetChains returns two chains that can be used in a test. The pair returned +// is unique to the current test being run. Note: this function does not create containers. +func (s *E2ETestSuite) GetChains() (ibc.Chain, ibc.Chain) { + chains := s.GetAllChains() + return chains[0], chains[1] +} + +// GetAllChains returns all chains that can be used in a test. The chains returned +// are unique to the current test being run. Note: this function does not create containers. +func (s *E2ETestSuite) GetAllChains() []ibc.Chain { + // chains are stored on a per test suite level + chains := s.chains + s.Require().NotEmpty(chains, "chains not found for test %s", s.testSuiteName) + return chains +} + +// GetRelayerWallets returns the ibcrelayer wallets associated with the chains. +func (s *E2ETestSuite) GetRelayerWallets(ibcrelayer ibc.Relayer) (ibc.Wallet, ibc.Wallet, error) { + chains := s.GetAllChains() + chainA, chainB := chains[0], chains[1] + chainARelayerWallet, ok := ibcrelayer.GetWallet(chainA.Config().ChainID) + if !ok { + return nil, nil, errors.New("unable to find chain A relayer wallet") + } + + chainBRelayerWallet, ok := ibcrelayer.GetWallet(chainB.Config().ChainID) + if !ok { + return nil, nil, errors.New("unable to find chain B relayer wallet") + } + return chainARelayerWallet, chainBRelayerWallet, nil +} + +// RecoverRelayerWallets adds the corresponding ibcrelayer address to the keychain of the chain. +// This is useful if commands executed on the chains expect the relayer information to present in the keychain. +func (s *E2ETestSuite) RecoverRelayerWallets(ctx context.Context, ibcrelayer ibc.Relayer, testName string) (ibc.Wallet, ibc.Wallet, error) { + chainARelayerWallet, chainBRelayerWallet, err := s.GetRelayerWallets(ibcrelayer) + if err != nil { + return nil, nil, err + } + + chains := s.GetAllChains() + chainA, chainB := chains[0], chains[1] + + rlyAName := fmt.Sprintf("%s-%s", ChainARelayerName, testName) + rlyBName := fmt.Sprintf("%s-%s", ChainBRelayerName, testName) + + if err := chainA.RecoverKey(ctx, rlyAName, chainARelayerWallet.Mnemonic()); err != nil { + return nil, nil, fmt.Errorf("could not recover relayer wallet on chain A: %s", err) + } + if err := chainB.RecoverKey(ctx, rlyBName, chainBRelayerWallet.Mnemonic()); err != nil { + return nil, nil, fmt.Errorf("could not recover relayer wallet on chain B: %s", err) + } + return chainARelayerWallet, chainBRelayerWallet, nil +} + +// StartRelayer starts the given ibcrelayer. +func (s *E2ETestSuite) StartRelayer(r ibc.Relayer, testName string) { + s.Require().NoError(r.StartRelayer(context.TODO(), s.GetRelayerExecReporter(), s.GetPaths(testName)...), "failed to start relayer") + + chains := s.GetAllChains() + var chainHeighters []test.ChainHeighter + for _, c := range chains { + chainHeighters = append(chainHeighters, c) + } + + // wait for every chain to produce some blocks before using the relayer. + s.Require().NoError(test.WaitForBlocks(context.TODO(), 10, chainHeighters...), "failed to wait for blocks") +} + +// StopRelayer stops the given ibcrelayer. +func (s *E2ETestSuite) StopRelayer(ctx context.Context, ibcrelayer ibc.Relayer) { + err := ibcrelayer.StopRelayer(ctx, s.GetRelayerExecReporter()) + s.Require().NoError(err) +} + +// RestartRelayer restarts the given relayer. +func (s *E2ETestSuite) RestartRelayer(ctx context.Context, ibcrelayer ibc.Relayer, testName string) { + s.StopRelayer(ctx, ibcrelayer) + s.StartRelayer(ibcrelayer, testName) +} + +// CreateUserOnChainA creates a user with the given amount of funds on chain A. +func (s *E2ETestSuite) CreateUserOnChainA(ctx context.Context, amount int64) ibc.Wallet { + return s.createWalletOnChainIndex(ctx, amount, 0) +} + +// CreateUserOnChainB creates a user with the given amount of funds on chain B. +func (s *E2ETestSuite) CreateUserOnChainB(ctx context.Context, amount int64) ibc.Wallet { + return s.createWalletOnChainIndex(ctx, amount, 1) +} + +// CreateUserOnChainC creates a user with the given amount of funds on chain C. +func (s *E2ETestSuite) CreateUserOnChainC(ctx context.Context, amount int64) ibc.Wallet { + return s.createWalletOnChainIndex(ctx, amount, 2) +} + +// createWalletOnChainIndex creates a wallet with the given amount of funds on the chain of the given index. +func (s *E2ETestSuite) createWalletOnChainIndex(ctx context.Context, amount, chainIndex int64) ibc.Wallet { + chain := s.GetAllChains()[chainIndex] + wallet := interchaintest.GetAndFundTestUsers(s.T(), ctx, strings.ReplaceAll(s.T().Name(), " ", "-"), sdkmath.NewInt(amount), chain)[0] + // note the GetAndFundTestUsers requires the caller to wait for some blocks before the funds are accessible. + s.Require().NoError(test.WaitForBlocks(ctx, 2, chain)) + return wallet +} + +// GetChainANativeBalance gets the balance of a given user on chain A. +func (s *E2ETestSuite) GetChainANativeBalance(ctx context.Context, user ibc.Wallet) (int64, error) { + chainA := s.GetAllChains()[0] + return GetChainBalanceForDenom(ctx, chainA, chainA.Config().Denom, user) +} + +// GetChainBNativeBalance gets the balance of a given user on chain B. +func (s *E2ETestSuite) GetChainBNativeBalance(ctx context.Context, user ibc.Wallet) (int64, error) { + chainB := s.GetAllChains()[1] + return GetChainBalanceForDenom(ctx, chainB, chainB.Config().Denom, user) +} + +// GetChainBalanceForDenom returns the balance for a given denom given a chain. +func GetChainBalanceForDenom(ctx context.Context, chain ibc.Chain, denom string, user ibc.Wallet) (int64, error) { + balanceResp, err := query.GRPCQuery[banktypes.QueryBalanceResponse](ctx, chain, &banktypes.QueryBalanceRequest{ + Address: user.FormattedAddress(), + Denom: denom, + }) + if err != nil { + return 0, err + } + + return balanceResp.Balance.Amount.Int64(), nil +} + +// AssertPacketRelayed asserts that the packet commitment does not exist on the sending chain. +// The packet commitment will be deleted upon a packet acknowledgement or timeout. +func (s *E2ETestSuite) AssertPacketRelayed(ctx context.Context, chain ibc.Chain, portID, channelID string, sequence uint64) { + _, err := query.GRPCQuery[channeltypes.QueryPacketCommitmentResponse](ctx, chain, &channeltypes.QueryPacketCommitmentRequest{ + PortId: portID, + ChannelId: channelID, + Sequence: sequence, + }) + s.Require().ErrorContains(err, "packet commitment hash not found") +} + +// AssertPacketAcknowledged asserts that the packet has been acknowledged on the specified chain. +func (s *E2ETestSuite) AssertPacketAcknowledged(ctx context.Context, chain ibc.Chain, portID, channelID string, sequence uint64) { + _, err := query.GRPCQuery[channeltypes.QueryPacketAcknowledgementResponse](ctx, chain, &channeltypes.QueryPacketAcknowledgementRequest{ + PortId: portID, + ChannelId: channelID, + Sequence: sequence, + }) + s.Require().NoError(err) +} + +// AssertHumanReadableDenom asserts that a human readable denom is present for a given chain. +func (s *E2ETestSuite) AssertHumanReadableDenom(ctx context.Context, chain ibc.Chain, counterpartyNativeDenom string, counterpartyChannel ibc.ChannelOutput) { + chainIBCDenom := GetIBCToken(counterpartyNativeDenom, counterpartyChannel.Counterparty.PortID, counterpartyChannel.Counterparty.ChannelID) + + denomMetadataResp, err := query.GRPCQuery[banktypes.QueryDenomMetadataResponse](ctx, chain, &banktypes.QueryDenomMetadataRequest{ + Denom: chainIBCDenom.IBCDenom(), + }) + s.Require().NoError(err) + + denomMetadata := denomMetadataResp.Metadata + + s.Require().Equal(chainIBCDenom.IBCDenom(), denomMetadata.Base, "denom metadata base does not match expected %s: got %s", chainIBCDenom.IBCDenom(), denomMetadata.Base) + expectedName := fmt.Sprintf("%s/%s/%s IBC token", counterpartyChannel.Counterparty.PortID, counterpartyChannel.Counterparty.ChannelID, counterpartyNativeDenom) + s.Require().Equal(expectedName, denomMetadata.Name, "denom metadata name does not match expected %s: got %s", expectedName, denomMetadata.Name) + expectedDisplay := fmt.Sprintf("%s/%s/%s", counterpartyChannel.Counterparty.PortID, counterpartyChannel.Counterparty.ChannelID, counterpartyNativeDenom) + s.Require().Equal(expectedDisplay, denomMetadata.Display, "denom metadata display does not match expected %s: got %s", expectedDisplay, denomMetadata.Display) + s.Require().Equal(strings.ToUpper(counterpartyNativeDenom), denomMetadata.Symbol, "denom metadata symbol does not match expected %s: got %s", strings.ToUpper(counterpartyNativeDenom), denomMetadata.Symbol) +} + +// createChains creates two separate chains in docker containers. +// test and can be retrieved with GetChains. +func (s *E2ETestSuite) createChains(chainCount int, chainOptions ChainOptions) []ibc.Chain { + t := s.T() + s.Require().GreaterOrEqualf(len(chainOptions.ChainSpecs), chainCount, "len(chainOptions.ChainSpecs): %d < chainCount: %d", len(chainOptions.ChainSpecs), chainCount) + + cf := interchaintest.NewBuiltinChainFactory(s.logger, chainOptions.ChainSpecs[:chainCount]) // Take the first N specs + + // this is intentionally called after the interchaintest.DockerSetup function. The above function registers a + // cleanup task which deletes all containers. By registering a cleanup function afterwards, it is executed first + // this allows us to process the logs before the containers are removed. + t.Cleanup(func() { + dumpLogs := LoadConfig().DebugConfig.DumpLogs + var chainNames []string + for _, chain := range chainOptions.ChainSpecs { + chainNames = append(chainNames, chain.Name) + } + diagnostics.Collect(t, s.DockerClient, dumpLogs, s.testSuiteName, chainNames...) + }) + + chains, err := cf.Chains(t.Name()) + s.Require().NoError(err) + + // initialise proposal ids for all chains. + for _, chain := range chains { + s.proposalIDs[chain.Config().ChainID] = 1 + } + + return chains +} + +// GetRelayerExecReporter returns a testreporter.RelayerExecReporter instances +// using the current test's testing.T. +func (s *E2ETestSuite) GetRelayerExecReporter() *testreporter.RelayerExecReporter { + rep := testreporter.NewNopReporter() + return rep.RelayerExecReporter(s.T()) +} + +// TransferChannelOptions configures both of the chains to have non-incentivized transfer channels. +func (*E2ETestSuite) TransferChannelOptions() ibc.CreateChannelOptions { + opts := ibc.DefaultChannelOpts() + opts.Version = transfertypes.V1 + return opts +} + +// GetTimeoutHeight returns a timeout height of 1000 blocks above the current block height. +// This function should be used when the timeout is never expected to be reached +func (s *E2ETestSuite) GetTimeoutHeight(ctx context.Context, chain ibc.Chain) clienttypes.Height { + height, err := chain.Height(ctx) + s.Require().NoError(err) + return clienttypes.NewHeight(clienttypes.ParseChainID(chain.Config().ChainID), uint64(height)+1000) +} + +// GetIBCToken returns the denomination of the full token denom sent to the receiving channel +func GetIBCToken(fullTokenDenom string, portID, channelID string) transfertypes.Denom { + return transfertypes.ExtractDenomFromPath(fmt.Sprintf("%s/%s/%s", portID, channelID, fullTokenDenom)) +} + +// getValidatorsAndFullNodes returns the number of validators and full nodes respectively that should be used for +// the test. If the test is running in CI, more nodes are used, when running locally a single node is used by default to +// use less resources and allow the tests to run faster. +// both the number of validators and full nodes can be overwritten in a config file. +func getValidatorsAndFullNodes(chainIdx int) (int, int) { + tc := LoadConfig() + return tc.GetChainNumValidators(chainIdx), tc.GetChainNumFullNodes(chainIdx) +} + +// GetMsgTransfer returns a MsgTransfer that is constructed based on the channel version +func GetMsgTransfer(portID, channelID, version string, token sdk.Coin, sender, receiver string, timeoutHeight clienttypes.Height, timeoutTimestamp uint64, memo string) *transfertypes.MsgTransfer { + var msg *transfertypes.MsgTransfer + switch version { + case transfertypes.V1: + msg = &transfertypes.MsgTransfer{ + SourcePort: portID, + SourceChannel: channelID, + Token: token, + Sender: sender, + Receiver: receiver, + TimeoutHeight: timeoutHeight, + TimeoutTimestamp: timeoutTimestamp, + Memo: memo, + } + default: + panic(fmt.Errorf("unsupported transfer version: %s", version)) + } + + return msg +} + +// SuiteName returns the name of the test suite. +func (s *E2ETestSuite) SuiteName() string { + return s.testSuiteName +} + +// ThreeChainSetup provides the default behaviour to wire up 3 chains in the tests. +func ThreeChainSetup() ChainOptionConfiguration { + // copy all values of existing chains and tweak to make unique to new chain. + return func(options *ChainOptions) { + chainCSpec := *options.ChainSpecs[0] // nolint + + chainCSpec.ChainID = "chainC-1" + chainCSpec.Name = "simapp-c" + + options.ChainSpecs = append(options.ChainSpecs, &chainCSpec) + } +} + +// DefaultChannelOpts returns the default chain options for the test suite based on the provided chains. +func DefaultChannelOpts(chains []ibc.Chain) ibc.CreateChannelOptions { + channelOptions := ibc.DefaultChannelOpts() + channelOptions.Version = transfertypes.V1 + return channelOptions +} diff --git a/e2e/testsuite/tx.go b/e2e/testsuite/tx.go new file mode 100644 index 0000000..9dc25ec --- /dev/null +++ b/e2e/testsuite/tx.go @@ -0,0 +1,352 @@ +package testsuite + +import ( + "context" + "errors" + "fmt" + "slices" + "strings" + "time" + + "github.com/cosmos/interchaintest/v10/chain/cosmos" + "github.com/cosmos/interchaintest/v10/ibc" + test "github.com/cosmos/interchaintest/v10/testutil" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/tx" + sdk "github.com/cosmos/cosmos-sdk/types" + txtypes "github.com/cosmos/cosmos-sdk/types/tx" + signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + govtypesv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + govtypesv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + + abci "github.com/cometbft/cometbft/abci/types" + + "github.com/cosmos/ibc-go/e2e/testsuite/query" + "github.com/cosmos/ibc-go/e2e/testsuite/sanitize" + "github.com/cosmos/ibc-go/e2e/testvalues" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" +) + +// BroadcastMessages broadcasts the provided messages to the given chain and signs them on behalf of the provided user. +// Once the broadcast response is returned, we wait for a few blocks to be created on the chain the message was broadcast to. +func (s *E2ETestSuite) BroadcastMessages(ctx context.Context, chain ibc.Chain, user ibc.Wallet, msgs ...sdk.Msg) sdk.TxResponse { + cosmosChain, ok := chain.(*cosmos.CosmosChain) + if !ok { + panic("BroadcastMessages expects a cosmos.CosmosChain") + } + + broadcaster := cosmos.NewBroadcaster(s.T(), cosmosChain) + + // strip out any fields that may not be supported for the given chain version. + msgs = sanitize.Messages(cosmosChain.Nodes()[0].Image.Version, msgs...) + + broadcaster.ConfigureClientContextOptions(func(clientContext client.Context) client.Context { + // use a codec with all the types our tests care about registered. + // BroadcastTx will deserialize the response and will not be able to otherwise. + cdc := Codec() + return clientContext.WithCodec(cdc).WithTxConfig(authtx.NewTxConfig(cdc, []signingtypes.SignMode{signingtypes.SignMode_SIGN_MODE_DIRECT})) + }) + + broadcaster.ConfigureFactoryOptions(func(factory tx.Factory) tx.Factory { + return factory.WithGas(DefaultGasValue) + }) + + // Retry the operation a few times if the user signing the transaction is a relayer. (See issue #3264) + var resp sdk.TxResponse + var err error + broadcastFunc := func() (sdk.TxResponse, error) { + return cosmos.BroadcastTx(ctx, broadcaster, user, msgs...) + } + if s.relayerWallets.ContainsRelayer(s.T().Name(), user) { + // Retry five times, the value of 5 chosen is arbitrary. + resp, err = s.retryNtimes(broadcastFunc, 5) + } else { + resp, err = broadcastFunc() + } + s.Require().NoError(err) + + s.Require().NoError(test.WaitForBlocks(ctx, 2, chain)) + s.T().Logf("blocks created on chain %s", chain.Config().ChainID) + return resp +} + +// retryNtimes retries the provided function up to the provided number of attempts. +func (s *E2ETestSuite) retryNtimes(f func() (sdk.TxResponse, error), attempts int) (sdk.TxResponse, error) { + // Ignore account sequence mismatch errors. + retryMessages := []string{"account sequence mismatch"} + var resp sdk.TxResponse + var err error + // If the response's raw log doesn't contain any of the allowed prefixes we return, else, we retry. + for range attempts { + resp, err = f() + if err != nil { + return sdk.TxResponse{}, err + } + // If the response's raw log doesn't contain any of the allowed prefixes we return, else, we retry. + if !slices.ContainsFunc(retryMessages, func(s string) bool { return strings.Contains(resp.RawLog, s) }) { + return resp, err + } + s.T().Logf("retrying tx due to non deterministic failure: %+v", resp) + } + return resp, err +} + +// AssertTxFailure verifies that an sdk.TxResponse has failed. +func (s *E2ETestSuite) AssertTxFailure(resp sdk.TxResponse, expectedError *errorsmod.Error, alternativeError ...*errorsmod.Error) { + errorMsg := fmt.Sprintf("%+v", resp) + // In older versions, the codespace and abci codes were different. So in compatibility tests + // we can not make assertions on them. + if GetChainATag() == GetChainBTag() { + s.Require().Equal(expectedError.ABCICode(), resp.Code, errorMsg) + s.Require().Equal(expectedError.Codespace(), resp.Codespace, errorMsg) + } + // Verify that the error message contains the expected error message or one of the alternative error messages. + if strings.Contains(resp.RawLog, expectedError.Error()) { + return + } + + for _, altErr := range alternativeError { + if strings.Contains(resp.RawLog, altErr.Error()) { + return + } + } + s.Require().FailNow(fmt.Sprintf("expected error: %s, got: %s", expectedError.Error(), resp.RawLog)) +} + +// AssertTxSuccess verifies that an sdk.TxResponse has succeeded. +func (s *E2ETestSuite) AssertTxSuccess(resp sdk.TxResponse) { + errorMsg := addDebuggingInformation(fmt.Sprintf("%+v", resp)) + s.Require().Equal(resp.Code, uint32(0), errorMsg) + s.Require().NotEmpty(resp.TxHash, errorMsg) + s.Require().NotEqual(int64(0), resp.GasUsed, errorMsg) + s.Require().NotEqual(int64(0), resp.GasWanted, errorMsg) + s.Require().NotEmpty(resp.Events, errorMsg) + s.Require().NotEmpty(resp.Data, errorMsg) +} + +// addDebuggingInformation adds additional debugging information to the error message +// based on common types of errors that can occur. +func addDebuggingInformation(errorMsg string) string { + if strings.Contains(errorMsg, "errUnknownField") { + errorMsg += ` + +This error is likely due to a new an unrecognized proto field being provided to a chain using an older version of the sdk. +If this is a compatibility test, ensure that the fields are being sanitized in the sanitize.Messages function. + +` + } + return errorMsg +} + +// ExecuteAndPassGovV1Proposal submits a v1 governance proposal using the provided user and message and uses all validators +// to vote yes on the proposal. It ensures the proposal successfully passes. +func (s *E2ETestSuite) ExecuteAndPassGovV1Proposal(ctx context.Context, msg sdk.Msg, chain ibc.Chain, user ibc.Wallet) { + err := s.ExecuteGovV1Proposal(ctx, msg, chain, user) + s.Require().NoError(err) +} + +// ExecuteGovV1Proposal submits a v1 governance proposal using the provided user and message and uses all validators +// to vote yes on the proposal. +func (s *E2ETestSuite) ExecuteGovV1Proposal(ctx context.Context, msg sdk.Msg, chain ibc.Chain, user ibc.Wallet) error { + cosmosChain, ok := chain.(*cosmos.CosmosChain) + if !ok { + panic("ExecuteAndPassGovV1Proposal must be passed a cosmos.CosmosChain") + } + + sender, err := sdk.AccAddressFromBech32(user.FormattedAddress()) + s.Require().NoError(err) + + proposalID := s.proposalIDs[cosmosChain.Config().ChainID] + defer func() { + s.proposalIDs[cosmosChain.Config().ChainID] = proposalID + 1 + }() + + msgs := []sdk.Msg{msg} + + msgSubmitProposal, err := govtypesv1.NewMsgSubmitProposal( + msgs, + sdk.NewCoins(sdk.NewCoin(cosmosChain.Config().Denom, sdkmath.NewInt(testvalues.DefaultGovV1ProposalTokenAmount))), + sender.String(), + "", + fmt.Sprintf("e2e gov proposal: %d", proposalID), + fmt.Sprintf("executing gov proposal %d", proposalID), + false, + ) + s.Require().NoError(err) + + s.T().Logf("submitting proposal with ID: %d", proposalID) + resp := s.BroadcastMessages(ctx, cosmosChain, user, msgSubmitProposal) + s.AssertTxSuccess(resp) + + s.Require().NoError(cosmosChain.VoteOnProposalAllValidators(ctx, proposalID, cosmos.ProposalVoteYes)) + + s.T().Logf("validators voted %s on proposal with ID: %d", cosmos.ProposalVoteYes, proposalID) + return s.waitForGovV1ProposalToPass(ctx, cosmosChain, proposalID) +} + +// waitForGovV1ProposalToPass polls for the entire voting period to see if the proposal has passed. +// if the proposal has not passed within the duration of the voting period, an error is returned. +func (s *E2ETestSuite) waitForGovV1ProposalToPass(ctx context.Context, chain ibc.Chain, proposalID uint64) error { + var govProposal *govtypesv1.Proposal + // poll for the query for the entire voting period to see if the proposal has passed. + err := test.WaitForCondition(testvalues.VotingPeriod, 10*time.Second, func() (bool, error) { + s.T().Logf("waiting for proposal with ID: %d to pass", proposalID) + proposalResp, err := query.GRPCQuery[govtypesv1.QueryProposalResponse](ctx, chain, &govtypesv1.QueryProposalRequest{ + ProposalId: proposalID, + }) + if err != nil { + return false, err + } + + govProposal = proposalResp.Proposal + return govProposal.Status == govtypesv1.StatusPassed, nil + }) + + // in the case of a failed proposal, we wrap the polling error with additional information about why the proposal failed. + if err != nil && govProposal.FailedReason != "" { + err = errorsmod.Wrap(err, govProposal.FailedReason) + } + return err +} + +// ExecuteAndPassGovV1Beta1Proposal submits the given v1beta1 governance proposal using the provided user and uses all validators to vote yes on the proposal. +// It ensures the proposal successfully passes. +func (s *E2ETestSuite) ExecuteAndPassGovV1Beta1Proposal(ctx context.Context, chain ibc.Chain, user ibc.Wallet, content govtypesv1beta1.Content) { + cosmosChain, ok := chain.(*cosmos.CosmosChain) + if !ok { + panic("ExecuteAndPassGovV1Beta1Proposal must be passed a cosmos.CosmosChain") + } + + txResp := s.ExecuteGovV1Beta1Proposal(ctx, cosmosChain, user, content) + s.AssertTxSuccess(txResp) + + var submitProposalResponse govtypesv1beta1.MsgSubmitProposalResponse + s.Require().NoError(UnmarshalMsgResponses(txResp, &submitProposalResponse)) + + proposalID := submitProposalResponse.ProposalId + defer func() { + s.proposalIDs[chain.Config().ChainID] = proposalID + 1 + }() + + proposalResp, err := query.GRPCQuery[govtypesv1beta1.QueryProposalResponse](ctx, cosmosChain, &govtypesv1beta1.QueryProposalRequest{ + ProposalId: proposalID, + }) + s.Require().NoError(err) + + proposal := proposalResp.Proposal + s.Require().Equal(govtypesv1beta1.StatusVotingPeriod, proposal.Status) + + err = cosmosChain.VoteOnProposalAllValidators(ctx, proposalID, cosmos.ProposalVoteYes) + s.Require().NoError(err) + + // ensure voting period has not passed before validators finished voting + proposalResp, err = query.GRPCQuery[govtypesv1beta1.QueryProposalResponse](ctx, cosmosChain, &govtypesv1beta1.QueryProposalRequest{ + ProposalId: proposalID, + }) + s.Require().NoError(err) + + proposal = proposalResp.Proposal + s.Require().Equal(govtypesv1beta1.StatusVotingPeriod, proposal.Status) + + err = s.waitForGovV1Beta1ProposalToPass(ctx, cosmosChain, proposalID) + s.Require().NoError(err) +} + +// waitForGovV1Beta1ProposalToPass polls for the entire voting period to see if the proposal has passed. +// if the proposal has not passed within the duration of the voting period, an error is returned. +func (*E2ETestSuite) waitForGovV1Beta1ProposalToPass(ctx context.Context, chain ibc.Chain, proposalID uint64) error { + // poll for the query for the entire voting period to see if the proposal has passed. + return test.WaitForCondition(testvalues.VotingPeriod, 10*time.Second, func() (bool, error) { + proposalResp, err := query.GRPCQuery[govtypesv1beta1.QueryProposalResponse](ctx, chain, &govtypesv1beta1.QueryProposalRequest{ + ProposalId: proposalID, + }) + if err != nil { + return false, err + } + + proposal := proposalResp.Proposal + return proposal.Status == govtypesv1beta1.StatusPassed, nil + }) +} + +// ExecuteGovV1Beta1Proposal submits a v1beta1 governance proposal using the provided content. +func (s *E2ETestSuite) ExecuteGovV1Beta1Proposal(ctx context.Context, chain ibc.Chain, user ibc.Wallet, content govtypesv1beta1.Content) sdk.TxResponse { + sender, err := sdk.AccAddressFromBech32(user.FormattedAddress()) + s.Require().NoError(err) + + msgSubmitProposal, err := govtypesv1beta1.NewMsgSubmitProposal(content, sdk.NewCoins(sdk.NewCoin(chain.Config().Denom, govtypesv1beta1.DefaultMinDepositTokens)), sender) + s.Require().NoError(err) + + return s.BroadcastMessages(ctx, chain, user, msgSubmitProposal) +} + +// Transfer broadcasts a MsgTransfer message. +func (s *E2ETestSuite) Transfer(ctx context.Context, chain ibc.Chain, user ibc.Wallet, + portID, channelID string, token sdk.Coin, sender, receiver string, + timeoutHeight clienttypes.Height, timeoutTimestamp uint64, + memo string, +) sdk.TxResponse { + channel, err := query.Channel(ctx, chain, portID, channelID) + s.Require().NoError(err) + s.Require().NotNil(channel) + + msg := GetMsgTransfer(portID, channelID, channel.Version, token, sender, receiver, timeoutHeight, timeoutTimestamp, memo) + + return s.BroadcastMessages(ctx, chain, user, msg) +} + +// QueryTxsByEvents runs the QueryTxsByEvents command on the given chain. +// https://github.com/cosmos/cosmos-sdk/blob/65ab2530cc654fd9e252b124ed24cbaa18023b2b/x/auth/client/cli/query.go#L33 +func (*E2ETestSuite) QueryTxsByEvents( + ctx context.Context, chain ibc.Chain, + page, limit int, queryReq, orderBy string, +) (*txtypes.GetTxsEventResponse, error) { + cosmosChain, ok := chain.(*cosmos.CosmosChain) + if !ok { + return nil, errors.New("QueryTxsByEvents must be passed a cosmos.CosmosChain") + } + + req := &txtypes.GetTxsEventRequest{ + Page: uint64(page), + Limit: uint64(limit), + Query: queryReq, + } + + if !testvalues.TransactionEventQueryFeatureReleases.IsSupported(chain.Config().Images[0].Version) { + req.Events = []string{queryReq} + req.Query = "" + } + + res, err := query.GRPCQuery[txtypes.GetTxsEventResponse](ctx, cosmosChain, req) + if err != nil { + return nil, err + } + + return res, nil +} + +// ExtractValueFromEvents extracts the value of an attribute from a list of events. +// If the attribute is not found, the function returns an empty string and false. +// If the attribute is found, the function returns the value and true. +func (*E2ETestSuite) ExtractValueFromEvents(events []abci.Event, eventType, attrKey string) (string, bool) { + for _, event := range events { + if event.Type != eventType { + continue + } + + for _, attr := range event.Attributes { + if attr.Key != attrKey { + continue + } + + return attr.Value, true + } + } + + return "", false +} diff --git a/e2e/testvalues/values.go b/e2e/testvalues/values.go new file mode 100644 index 0000000..fc9b2bf --- /dev/null +++ b/e2e/testvalues/values.go @@ -0,0 +1,127 @@ +package testvalues + +import ( + "fmt" + "time" + + "github.com/cosmos/interchaintest/v10/ibc" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/e2e/semverutil" +) + +const ( + StartingTokenAmount int64 = 500_000_000_000 + IBCTransferAmount int64 = 10_000 + InvalidAddress string = "" + DefaultGovV1ProposalTokenAmount = 500_000_000 +) + +// VotingPeriod may differ per test. +var VotingPeriod = time.Second * 30 + +// ImmediatelyTimeout returns an ibc.IBCTimeout which will cause an IBC transfer to timeout immediately. +func ImmediatelyTimeout() *ibc.IBCTimeout { + return &ibc.IBCTimeout{ + NanoSeconds: 1, + } +} + +func DefaultTransferAmount(denom string) sdk.Coin { + return sdk.Coin{Denom: denom, Amount: sdkmath.NewInt(IBCTransferAmount)} +} + +func TransferAmount(amount int64, denom string) sdk.Coin { + return sdk.Coin{Denom: denom, Amount: sdkmath.NewInt(amount)} +} + +func TendermintClientID(id int) string { + return fmt.Sprintf("07-tendermint-%d", id) +} + +func SolomachineClientID(id int) string { + return fmt.Sprintf("06-solomachine-%d", id) +} + +var ReflectionServiceFeatureReleases = semverutil.FeatureReleases{ + MajorVersion: "v7", +} + +// TokenMetadataFeatureReleases represents the releases the token metadata was released in. +var TokenMetadataFeatureReleases = semverutil.FeatureReleases{ + MajorVersion: "v8", +} + +// GovGenesisFeatureReleases represents the releases the governance module genesis +// was upgraded from v1beta1 to v1. +var GovGenesisFeatureReleases = semverutil.FeatureReleases{ + MajorVersion: "v7", +} + +// SelfParamsFeatureReleases represents the releases the transfer module started managing its own params. +var SelfParamsFeatureReleases = semverutil.FeatureReleases{ + MajorVersion: "v8", +} + +// TotalEscrowFeatureReleases represents the releases the total escrow state entry was released in. +var TotalEscrowFeatureReleases = semverutil.FeatureReleases{ + MajorVersion: "v8", + MinorVersions: []string{ + "v7.1", + }, +} + +// IbcErrorsFeatureReleases represents the releases the IBC module level errors was released in. +var IbcErrorsFeatureReleases = semverutil.FeatureReleases{ + MajorVersion: "v8", +} + +// LocalhostClientFeatureReleases represents the releases the localhost client was released in. +var LocalhostClientFeatureReleases = semverutil.FeatureReleases{ + MajorVersion: "v8", + MinorVersions: []string{ + "v7.1", + }, +} + +// AllowAllClientsWildcardFeatureReleases represents the releases the allow all clients wildcard was released in. +var AllowAllClientsWildcardFeatureReleases = semverutil.FeatureReleases{ + MajorVersion: "v9", + MinorVersions: []string{ + "v8.1", + }, +} + +// ChannelParamsFeatureReleases represents the releases the params for 04-channel was released in. +var ChannelParamsFeatureReleases = semverutil.FeatureReleases{ + MajorVersion: "v9", + MinorVersions: []string{ + "v8.1", + }, +} + +// GovV1MessagesFeatureReleases represents the releases the support for x/gov v1 messages was released in. +var GovV1MessagesFeatureReleases = semverutil.FeatureReleases{ + MajorVersion: "v8", +} + +// TransactionEventQueryFeatureReleases represents the releases the support for --query flag +// in "query txs" for searching transactions that match exact events (since Cosmos SDK v0.50) was released in. +var TransactionEventQueryFeatureReleases = semverutil.FeatureReleases{ + MajorVersion: "v8", +} + +var ChannelsV2FeatureReleases = semverutil.FeatureReleases{ + MajorVersion: "v10", +} + +var ClientV2FeatureReleases = semverutil.FeatureReleases{ + MajorVersion: "v10", +} + +var LocalhostWithDashFeatureReleases = semverutil.FeatureReleases{ + MajorVersion: "v10", +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4959ebb --- /dev/null +++ b/go.mod @@ -0,0 +1,223 @@ +module git.cw.tr/mukan-network/mukan-ibc + +go 1.23.8 + +replace github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 + +require ( + cosmossdk.io/api v0.9.2 + cosmossdk.io/core v0.11.3 + cosmossdk.io/errors v1.0.2 + cosmossdk.io/log v1.6.1 + cosmossdk.io/math v1.5.3 + cosmossdk.io/store v1.1.2 + cosmossdk.io/x/tx v0.14.0 + cosmossdk.io/x/upgrade v0.2.0 + github.com/cometbft/cometbft v0.38.17 + github.com/cosmos/cosmos-db v1.1.3 + github.com/cosmos/cosmos-proto v1.0.0-beta.5 + github.com/cosmos/cosmos-sdk v0.53.4 + github.com/cosmos/gogoproto v1.7.0 + github.com/cosmos/ics23/go v0.11.0 + github.com/ethereum/go-ethereum v1.15.11 + github.com/golang/protobuf v1.5.4 + github.com/grpc-ecosystem/grpc-gateway v1.16.0 + github.com/hashicorp/go-metrics v0.5.4 + github.com/spf13/cast v1.9.2 + github.com/spf13/cobra v1.10.1 + github.com/stretchr/testify v1.11.1 + google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 + google.golang.org/grpc v1.75.0 + google.golang.org/protobuf v1.36.8 + gopkg.in/yaml.v2 v2.4.0 +) + +require ( + cel.dev/expr v0.24.0 // indirect + cloud.google.com/go v0.116.0 // indirect + cloud.google.com/go/auth v0.14.1 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect + cloud.google.com/go/compute/metadata v0.7.0 // indirect + cloud.google.com/go/iam v1.2.2 // indirect + cloud.google.com/go/monitoring v1.21.2 // indirect + cloud.google.com/go/storage v1.49.0 // indirect + cosmossdk.io/collections v1.2.1 // indirect + cosmossdk.io/depinject v1.2.1 // indirect + cosmossdk.io/schema v1.1.0 // indirect + filippo.io/edwards25519 v1.1.0 // indirect + github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect + github.com/99designs/keyring v1.2.2 // indirect + github.com/DataDog/datadog-go v4.8.3+incompatible // indirect + github.com/DataDog/zstd v1.5.7 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/aws/aws-sdk-go v1.49.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect + github.com/bgentry/speakeasy v0.2.0 // indirect + github.com/bits-and-blooms/bitset v1.22.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/chzyer/readline v1.5.1 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect + github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect + github.com/cockroachdb/apd/v2 v2.0.2 // indirect + github.com/cockroachdb/errors v1.12.0 // indirect + github.com/cockroachdb/fifo v0.0.0-20240616162244-4768e80dfb9a // indirect + github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 // indirect + github.com/cockroachdb/pebble v1.1.5 // indirect + github.com/cockroachdb/redact v1.1.6 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect + github.com/cometbft/cometbft-db v0.14.1 // indirect + github.com/cosmos/btcutil v1.0.5 // indirect + github.com/cosmos/go-bip39 v1.0.0 // indirect + github.com/cosmos/gogogateway v1.2.0 // indirect + github.com/cosmos/iavl v1.2.2 // indirect + github.com/cosmos/ledger-cosmos-go v0.14.0 // indirect + github.com/danieljoos/wincred v1.2.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect + github.com/desertbit/timer v1.0.1 // indirect + github.com/dgraph-io/badger/v4 v4.2.0 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/dvsekhvalnov/jose2go v1.7.0 // indirect + github.com/emicklei/dot v1.6.2 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect + github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect + github.com/fatih/color v1.17.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/getsentry/sentry-go v0.32.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.1 // indirect + github.com/go-kit/kit v0.13.0 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect + github.com/gogo/googleapis v1.4.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v1.2.5 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/snappy v0.0.5-0.20231225225746-43d5d4cd4e0e // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/flatbuffers v24.3.25+incompatible // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/orderedcode v0.0.1 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/gax-go/v2 v2.14.1 // indirect + github.com/gorilla/handlers v1.5.2 // indirect + github.com/gorilla/mux v1.8.1 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect + github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-getter v1.7.8 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-plugin v1.6.3 // indirect + github.com/hashicorp/go-safetemp v1.0.0 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/yamux v0.1.2 // indirect + github.com/hdevalence/ed25519consensus v0.2.0 // indirect + github.com/holiman/uint256 v1.3.2 // indirect + github.com/huandu/skiplist v1.2.1 // indirect + github.com/iancoleman/strcase v0.3.0 // indirect + github.com/improbable-eng/grpc-web v0.15.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/jmhodges/levigo v1.0.0 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/linxGnu/grocksdb v1.9.2 // indirect + github.com/manifoldco/promptui v0.9.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/minio/highwayhash v1.0.3 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mtibben/percent v0.2.1 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect + github.com/oklog/run v1.1.0 // indirect + github.com/onsi/ginkgo v1.16.5 // indirect + github.com/onsi/gomega v1.34.1 // indirect + github.com/opencontainers/image-spec v1.1.0-rc5 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.63.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/rs/cors v1.11.1 // indirect + github.com/rs/zerolog v1.34.0 // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/sasha-s/go-deadlock v0.3.5 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/pflag v1.0.9 // indirect + github.com/spf13/viper v1.20.1 // indirect + github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect + github.com/tendermint/go-amino v0.16.0 // indirect + github.com/tidwall/btree v1.7.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ulikunitz/xz v0.5.11 // indirect + github.com/zeebo/errs v1.4.0 // indirect + github.com/zondax/hid v0.9.2 // indirect + github.com/zondax/ledger-go v0.14.3 // indirect + go.etcd.io/bbolt v1.4.0-alpha.1 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.uber.org/mock v0.5.2 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/arch v0.17.0 // indirect + golang.org/x/crypto v0.39.0 // indirect + golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.15.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.26.0 // indirect + golang.org/x/time v0.10.0 // indirect + google.golang.org/api v0.222.0 // indirect + google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gotest.tools/v3 v3.5.2 // indirect + nhooyr.io/websocket v1.8.11 // indirect + pgregory.net/rapid v1.2.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..20eb1e2 --- /dev/null +++ b/go.sum @@ -0,0 +1,2443 @@ +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= +cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= +cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= +cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= +cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= +cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= +cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= +cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= +cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= +cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= +cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= +cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= +cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= +cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= +cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= +cloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k= +cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= +cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= +cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= +cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= +cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE= +cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= +cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= +cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= +cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= +cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= +cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= +cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= +cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= +cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= +cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= +cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= +cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= +cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= +cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= +cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= +cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= +cloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A= +cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= +cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= +cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= +cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY= +cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= +cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= +cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= +cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= +cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= +cloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI= +cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ= +cloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI= +cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= +cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= +cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= +cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= +cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= +cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= +cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= +cloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg= +cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= +cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= +cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= +cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= +cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= +cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= +cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= +cloud.google.com/go/auth v0.14.1 h1:AwoJbzUdxA/whv1qj3TLKwh3XX5sikny2fc40wUl+h0= +cloud.google.com/go/auth v0.14.1/go.mod h1:4JHUxlGXisL0AW8kXPtUF6ztuOksyfUQNFjfsOCXkPM= +cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= +cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= +cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= +cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= +cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= +cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= +cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= +cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= +cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= +cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= +cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= +cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= +cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= +cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= +cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= +cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= +cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= +cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= +cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= +cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E= +cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= +cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q= +cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= +cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= +cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= +cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= +cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= +cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= +cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= +cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= +cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= +cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= +cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= +cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= +cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= +cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= +cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= +cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= +cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= +cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= +cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= +cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= +cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= +cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= +cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg= +cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= +cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= +cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= +cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= +cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= +cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= +cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= +cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= +cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= +cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= +cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= +cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= +cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= +cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= +cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= +cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= +cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= +cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= +cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= +cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= +cloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM= +cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= +cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= +cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= +cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= +cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= +cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= +cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= +cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= +cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= +cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= +cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= +cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= +cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= +cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= +cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= +cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= +cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= +cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= +cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= +cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= +cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= +cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= +cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= +cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= +cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= +cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= +cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= +cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= +cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= +cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= +cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= +cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= +cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= +cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= +cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= +cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= +cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= +cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= +cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= +cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= +cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= +cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= +cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= +cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= +cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= +cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= +cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= +cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= +cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= +cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= +cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= +cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= +cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= +cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM= +cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= +cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= +cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= +cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= +cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= +cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= +cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= +cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= +cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= +cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= +cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= +cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= +cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= +cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= +cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= +cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= +cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= +cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= +cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= +cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= +cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= +cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= +cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= +cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= +cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= +cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= +cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= +cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= +cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= +cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= +cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= +cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= +cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= +cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw= +cloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA= +cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= +cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= +cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= +cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= +cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= +cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= +cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= +cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= +cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= +cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= +cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= +cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= +cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= +cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= +cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= +cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= +cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= +cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= +cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= +cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= +cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= +cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= +cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= +cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= +cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= +cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= +cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA= +cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= +cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= +cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= +cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= +cloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo= +cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= +cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= +cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= +cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= +cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= +cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= +cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= +cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= +cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= +cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= +cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= +cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg= +cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= +cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= +cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= +cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= +cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= +cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= +cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= +cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= +cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= +cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= +cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= +cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= +cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= +cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk= +cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM= +cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= +cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= +cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= +cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc= +cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= +cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= +cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= +cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= +cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= +cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= +cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= +cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= +cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= +cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= +cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= +cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= +cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= +cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= +cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= +cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= +cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= +cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= +cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= +cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= +cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= +cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= +cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= +cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= +cloud.google.com/go/monitoring v1.21.2 h1:FChwVtClH19E7pJ+e0xUhJPGksctZNVOk2UhMmblmdU= +cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= +cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= +cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= +cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= +cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= +cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= +cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= +cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= +cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= +cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= +cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= +cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= +cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= +cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= +cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= +cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= +cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= +cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= +cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= +cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= +cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= +cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= +cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= +cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= +cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= +cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= +cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= +cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= +cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= +cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= +cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= +cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= +cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= +cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= +cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= +cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= +cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= +cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= +cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= +cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= +cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= +cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= +cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= +cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= +cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= +cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= +cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= +cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= +cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= +cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= +cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= +cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= +cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= +cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= +cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= +cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= +cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= +cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= +cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= +cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= +cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= +cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= +cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= +cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= +cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= +cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= +cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= +cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= +cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= +cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= +cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= +cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= +cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= +cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= +cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= +cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= +cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= +cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= +cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= +cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= +cloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo= +cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= +cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= +cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= +cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= +cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= +cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= +cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= +cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= +cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= +cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= +cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= +cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= +cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= +cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= +cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= +cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= +cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= +cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= +cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= +cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= +cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= +cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= +cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= +cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= +cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= +cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= +cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= +cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= +cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= +cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= +cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= +cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= +cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= +cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= +cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= +cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= +cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= +cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= +cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= +cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc= +cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= +cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= +cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= +cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= +cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= +cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= +cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= +cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= +cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= +cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= +cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= +cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= +cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= +cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec= +cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= +cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= +cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= +cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= +cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= +cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= +cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= +cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= +cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= +cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= +cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= +cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= +cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= +cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= +cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= +cloud.google.com/go/storage v1.49.0 h1:zenOPBOWHCnojRd9aJZAyQXBYqkJkdQS42dxL55CIMw= +cloud.google.com/go/storage v1.49.0/go.mod h1:k1eHhhpLvrPjVGfo0mOUPEJ4Y2+a/Hv5PiwehZI9qGU= +cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= +cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= +cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= +cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= +cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= +cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= +cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= +cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= +cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= +cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= +cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= +cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= +cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= +cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= +cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= +cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= +cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= +cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= +cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= +cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI= +cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io= +cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= +cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= +cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= +cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= +cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= +cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= +cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk= +cloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= +cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= +cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= +cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= +cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= +cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= +cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= +cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= +cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= +cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= +cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= +cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= +cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= +cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= +cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= +cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= +cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= +cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= +cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= +cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= +cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= +cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= +cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= +cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= +cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= +cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= +cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= +cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= +cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= +cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= +cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= +cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= +cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= +cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= +cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= +cosmossdk.io/api v0.9.2 h1:9i9ptOBdmoIEVEVWLtYYHjxZonlF/aOVODLFaxpmNtg= +cosmossdk.io/api v0.9.2/go.mod h1:CWt31nVohvoPMTlPv+mMNCtC0a7BqRdESjCsstHcTkU= +cosmossdk.io/collections v1.2.1 h1:mAlNMs5vJwkda4TA+k5q/43p24RVAQ/qyDrjANu3BXE= +cosmossdk.io/collections v1.2.1/go.mod h1:PSsEJ/fqny0VPsHLFT6gXDj/2C1tBOTS9eByK0+PBFU= +cosmossdk.io/core v0.11.3 h1:mei+MVDJOwIjIniaKelE3jPDqShCc/F4LkNNHh+4yfo= +cosmossdk.io/core v0.11.3/go.mod h1:9rL4RE1uDt5AJ4Tg55sYyHWXA16VmpHgbe0PbJc6N2Y= +cosmossdk.io/depinject v1.2.1 h1:eD6FxkIjlVaNZT+dXTQuwQTKZrFZ4UrfCq1RKgzyhMw= +cosmossdk.io/depinject v1.2.1/go.mod h1:lqQEycz0H2JXqvOgVwTsjEdMI0plswI7p6KX+MVqFOM= +cosmossdk.io/errors v1.0.2 h1:wcYiJz08HThbWxd/L4jObeLaLySopyyuUFB5w4AGpCo= +cosmossdk.io/errors v1.0.2/go.mod h1:0rjgiHkftRYPj//3DrD6y8hcm40HcPv/dR4R/4efr0k= +cosmossdk.io/log v1.6.1 h1:YXNwAgbDwMEKwDlCdH8vPcoggma48MgZrTQXCfmMBeI= +cosmossdk.io/log v1.6.1/go.mod h1:gMwsWyyDBjpdG9u2avCFdysXqxq28WJapJvu+vF1y+E= +cosmossdk.io/math v1.5.3 h1:WH6tu6Z3AUCeHbeOSHg2mt9rnoiUWVWaQ2t6Gkll96U= +cosmossdk.io/math v1.5.3/go.mod h1:uqcZv7vexnhMFJF+6zh9EWdm/+Ylyln34IvPnBauPCQ= +cosmossdk.io/schema v1.1.0 h1:mmpuz3dzouCoyjjcMcA/xHBEmMChN+EHh8EHxHRHhzE= +cosmossdk.io/schema v1.1.0/go.mod h1:Gb7pqO+tpR+jLW5qDcNOSv0KtppYs7881kfzakguhhI= +cosmossdk.io/store v1.1.2 h1:3HOZG8+CuThREKv6cn3WSohAc6yccxO3hLzwK6rBC7o= +cosmossdk.io/store v1.1.2/go.mod h1:60rAGzTHevGm592kFhiUVkNC9w7gooSEn5iUBPzHQ6A= +cosmossdk.io/x/tx v0.14.0 h1:hB3O25kIcyDW/7kMTLMaO8Ripj3yqs5imceVd6c/heA= +cosmossdk.io/x/tx v0.14.0/go.mod h1:Tn30rSRA1PRfdGB3Yz55W4Sn6EIutr9xtMKSHij+9PM= +cosmossdk.io/x/upgrade v0.2.0 h1:ZHy0xny3wBCSLomyhE06+UmQHWO8cYlVYjfFAJxjz5g= +cosmossdk.io/x/upgrade v0.2.0/go.mod h1:DXDtkvi//TrFyHWSOaeCZGBoiGAE6Rs8/0ABt2pcDD0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= +git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= +github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0= +github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/datadog-go v4.8.3+incompatible h1:fNGaYSuObuQb5nzeTQqowRAd9bpDIRRV4/gUtIBjh8Q= +github.com/DataDog/datadog-go v4.8.3+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= +github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 h1:UQ0AhxogsIRZDkElkblfnwjc3IaltCm2HUMvezQaL7s= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1 h1:oTX4vsorBZo/Zdum6OKPA4o7544hm6smoRv1QjpTwGo= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1/go.mod h1:0wEl7vrAD8mehJyohS9HZy+WyEOaQO2mJx86Cvh93kM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 h1:8nn+rsCvTq9axyEh382S0PFLBeaFwNsT43IrPWzctRU= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE= +github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/adlio/schema v1.3.6 h1:k1/zc2jNfeiZBA5aFTRy37jlBIuCkXCm0XmvpzCKI9I= +github.com/adlio/schema v1.3.6/go.mod h1:qkxwLgPBd1FgLRHYVCmQT/rrBr3JH38J9LjmVzWNudg= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= +github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= +github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.49.0 h1:g9BkW1fo9GqKfwg2+zCD+TW/D36Ux+vtfJ8guF4AYmY= +github.com/aws/aws-sdk-go v1.49.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bgentry/speakeasy v0.2.0 h1:tgObeVOf8WAvtuAX6DhJ4xks4CFNwPDZiqzGqIHE51E= +github.com/bgentry/speakeasy v0.2.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4= +github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= +github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c= +github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE= +github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= +github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= +github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= +github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.12.0 h1:d7oCs6vuIMUQRVbi6jWWWEJZahLCfJpnJSVobd1/sUo= +github.com/cockroachdb/errors v1.12.0/go.mod h1:SvzfYNNBshAVbZ8wzNc/UPK3w1vf0dKDUP41ucAIf7g= +github.com/cockroachdb/fifo v0.0.0-20240616162244-4768e80dfb9a h1:f52TdbU4D5nozMAhO9TvTJ2ZMCXtN4VIAmfrrZ0JXQ4= +github.com/cockroachdb/fifo v0.0.0-20240616162244-4768e80dfb9a/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M= +github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 h1:ASDL+UJcILMqgNeV5jiqR4j+sTuvQNHdf2chuKj1M5k= +github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506/go.mod h1:Mw7HqKr2kdtu6aYGn3tPmAftiP3QPX63LdK/zcariIo= +github.com/cockroachdb/pebble v1.1.5 h1:5AAWCBWbat0uE0blr8qzufZP5tBjkRyy/jWe1QWLnvw= +github.com/cockroachdb/pebble v1.1.5/go.mod h1:17wO9el1YEigxkP/YtV8NtCivQDgoCyBg5c4VR/eOWo= +github.com/cockroachdb/redact v1.1.6 h1:zXJBwDZ84xJNlHl1rMyCojqyIxv+7YUpQiJLQ7n4314= +github.com/cockroachdb/redact v1.1.6/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/cometbft/cometbft v0.38.17 h1:FkrQNbAjiFqXydeAO81FUzriL4Bz0abYxN/eOHrQGOk= +github.com/cometbft/cometbft v0.38.17/go.mod h1:5l0SkgeLRXi6bBfQuevXjKqML1jjfJJlvI1Ulp02/o4= +github.com/cometbft/cometbft-db v0.14.1 h1:SxoamPghqICBAIcGpleHbmoPqy+crij/++eZz3DlerQ= +github.com/cometbft/cometbft-db v0.14.1/go.mod h1:KHP1YghilyGV/xjD5DP3+2hyigWx0WTp9X+0Gnx0RxQ= +github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= +github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk= +github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis= +github.com/cosmos/cosmos-db v1.1.3 h1:7QNT77+vkefostcKkhrzDK9uoIEryzFrU9eoMeaQOPY= +github.com/cosmos/cosmos-db v1.1.3/go.mod h1:kN+wGsnwUJZYn8Sy5Q2O0vCYA99MJllkKASbs6Unb9U= +github.com/cosmos/cosmos-proto v1.0.0-beta.5 h1:eNcayDLpip+zVLRLYafhzLvQlSmyab+RC5W7ZfmxJLA= +github.com/cosmos/cosmos-proto v1.0.0-beta.5/go.mod h1:hQGLpiIUloJBMdQMMWb/4wRApmI9hjHH05nefC0Ojec= +github.com/cosmos/cosmos-sdk v0.53.4 h1:kPF6vY68+/xi1/VebSZGpoxQqA52qkhUzqkrgeBn3Mg= +github.com/cosmos/cosmos-sdk v0.53.4/go.mod h1:7U3+WHZtI44dEOnU46+lDzBb2tFh1QlMvi8Z5JugopI= +github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= +github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= +github.com/cosmos/gogogateway v1.2.0 h1:Ae/OivNhp8DqBi/sh2A8a1D0y638GpL3tkmLQAiKxTE= +github.com/cosmos/gogogateway v1.2.0/go.mod h1:iQpLkGWxYcnCdz5iAdLcRBSw3h7NXeOkZ4GUkT+tbFI= +github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU= +github.com/cosmos/gogoproto v1.7.0 h1:79USr0oyXAbxg3rspGh/m4SWNyoz/GLaAh0QlCe2fro= +github.com/cosmos/gogoproto v1.7.0/go.mod h1:yWChEv5IUEYURQasfyBW5ffkMHR/90hiHgbNgrtp4j0= +github.com/cosmos/iavl v1.2.2 h1:qHhKW3I70w+04g5KdsdVSHRbFLgt3yY3qTMd4Xa4rC8= +github.com/cosmos/iavl v1.2.2/go.mod h1:GiM43q0pB+uG53mLxLDzimxM9l/5N9UuSY3/D0huuVw= +github.com/cosmos/ics23/go v0.11.0 h1:jk5skjT0TqX5e5QJbEnwXIS2yI2vnmLOgpQPeM5RtnU= +github.com/cosmos/ics23/go v0.11.0/go.mod h1:A8OjxPE67hHST4Icw94hOxxFEJMBG031xIGF/JHNIY0= +github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE= +github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs= +github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= +github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= +github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= +github.com/desertbit/timer v1.0.1 h1:yRpYNn5Vaaj6QXecdLMPMJsW81JLiI1eokUft5nBmeo= +github.com/desertbit/timer v1.0.1/go.mod h1:htRrYeY5V/t4iu1xCJ5XsQvp4xve8QulXXctAzxqcwE= +github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs= +github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/dvsekhvalnov/jose2go v1.7.0 h1:bnQc8+GMnidJZA8zc6lLEAb4xNrIqHwO+9TzqvtQZPo= +github.com/dvsekhvalnov/jose2go v1.7.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= +github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= +github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= +github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= +github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= +github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= +github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= +github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= +github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/ethereum/go-ethereum v1.15.11 h1:JK73WKeu0WC0O1eyX+mdQAVHUV+UR1a9VB/domDngBU= +github.com/ethereum/go-ethereum v1.15.11/go.mod h1:mf8YiHIb0GR4x4TipcvBUPxJLw1mFdmxzoDi11sDRoI= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/getsentry/sentry-go v0.32.0 h1:YKs+//QmwE3DcYtfKRH8/KyOOF/I6Qnx7qYGNHCGmCY= +github.com/getsentry/sentry-go v0.32.0/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= +github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= +github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= +github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= +github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= +github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= +github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.4.1-0.20201022092350-68b0159b7869/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= +github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= +github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I= +github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.5-0.20231225225746-43d5d4cd4e0e h1:4bw4WeyTYPp0smaXiJZCNnLrvVBqirQVreixayXezGc= +github.com/golang/snappy v0.0.5-0.20231225225746-43d5d4cd4e0e/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= +github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= +github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us= +github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= +github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= +github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-getter v1.7.8 h1:mshVHx1Fto0/MydBekWan5zUipGq7jO0novchgMmSiY= +github.com/hashicorp/go-getter v1.7.8/go.mod h1:2c6CboOEb9jG6YvmC9xdD+tyAFsrUaJPedwXDGr0TM4= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY= +github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= +github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= +github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= +github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= +github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= +github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= +github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= +github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= +github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= +github.com/huandu/skiplist v1.2.1 h1:dTi93MgjwErA/8idWTzIw4Y1kZsMWx35fmI2c8Rij7w= +github.com/huandu/skiplist v1.2.1/go.mod h1:7v3iFjLcSAzO4fN5B8dvebvo/qsfumiLiDXMrPiHF9w= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= +github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= +github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= +github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= +github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/linxGnu/grocksdb v1.9.2 h1:O3mzvO0wuzQ9mtlHbDrShixyVjVbmuqTjFrzlf43wZ8= +github.com/linxGnu/grocksdb v1.9.2/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA= +github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= +github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= +github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/grpc-proxy v0.0.0-20181017164139-0f1106ef9c76/go.mod h1:x5OoJHDHqxHS801UIuhqGl6QdSAEJvtausosHSdazIo= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a h1:dlRvE5fWabOchtH7znfiFCcOvmIYgOeAS5ifBXBlh9Q= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= +github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= +github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= +github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw= +github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= +github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= +github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= +github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU= +github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= +github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= +github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= +github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= +github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI= +github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= +github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= +github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= +github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= +github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= +github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.4.0-alpha.1 h1:3yrqQzbRRPFPdOMWS/QQIVxVnzSkAZQYeWlZFv1kbj4= +go.etcd.io/bbolt v1.4.0-alpha.1/go.mod h1:S/Z/Nm3iuOnyO1W4XuFfPci51Gj6F1Hv0z8hisyYYOw= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= +go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= +golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU= +golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= +golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= +gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= +gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= +google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= +google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= +google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= +google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= +google.golang.org/api v0.222.0 h1:Aiewy7BKLCuq6cUCeOUrsAlzjXPqBkEeQ/iwGHVQa/4= +google.golang.org/api v0.222.0/go.mod h1:efZia3nXpWELrwMlN5vyQrD4GmJN1Vw0x68Et3r+a9c= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= +google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= +google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= +google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= +google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= +google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= +google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= +google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= +modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= +modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= +modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= +modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= +modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= +modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= +modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= +modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= +modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= +modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= +nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= +nhooyr.io/websocket v1.8.11 h1:f/qXNc2/3DpoSZkHt1DQu6rj4zGC8JmkkLkWss0MgN0= +nhooyr.io/websocket v1.8.11/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/go.work.example b/go.work.example new file mode 100644 index 0000000..069aacd --- /dev/null +++ b/go.work.example @@ -0,0 +1,11 @@ +go 1.22.0 + +toolchain go1.22.3 + +use ( + ./ + ./modules/apps/callbacks + ./modules/light-clients/08-wasm + ./e2e + ./simapp +) diff --git a/internal/logging/logging.go b/internal/logging/logging.go new file mode 100644 index 0000000..fa6fa8e --- /dev/null +++ b/internal/logging/logging.go @@ -0,0 +1,23 @@ +package logging + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// SdkEventsToLogArguments converts a given sdk.Events and returns a slice of strings that provide human +// readable values for the event attributes. +func SdkEventsToLogArguments(events sdk.Events) []string { + logArgs := []string{"events"} + for _, e := range events { + logArgs = append(logArgs, fmt.Sprintf("type=%s", e.Type)) + for _, attr := range e.Attributes { + if len(attr.Value) == 0 { + continue + } + logArgs = append(logArgs, fmt.Sprintf("%s=%s", attr.Key, attr.Value)) + } + } + return logArgs +} diff --git a/internal/validate/validate.go b/internal/validate/validate.go new file mode 100644 index 0000000..72d941e --- /dev/null +++ b/internal/validate/validate.go @@ -0,0 +1,21 @@ +package validate + +import ( + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +// GRPCRequest validates that the portID and channelID of a gRPC Request are valid identifiers. +func GRPCRequest(portID, channelID string) error { + if err := host.PortIdentifierValidator(portID); err != nil { + return status.Error(codes.InvalidArgument, err.Error()) + } + + if err := host.ChannelIdentifierValidator(channelID); err != nil { + return status.Error(codes.InvalidArgument, err.Error()) + } + + return nil +} diff --git a/internal/validate/validate_test.go b/internal/validate/validate_test.go new file mode 100644 index 0000000..fa96d5c --- /dev/null +++ b/internal/validate/validate_test.go @@ -0,0 +1,57 @@ +package validate_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/cosmos/ibc-go/v10/internal/validate" +) + +func TestGRPCRequest(t *testing.T) { + const ( + validID = "validIdentifier" + invalidID = "" + ) + testCases := []struct { + msg string + portID string + channelID string + expErr error + }{ + { + "success", + validID, + validID, + nil, + }, + { + "invalid portID", + invalidID, + validID, + status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"), + }, + { + "invalid channelID", + validID, + invalidID, + status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"), + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("Case %s", tc.msg), func(t *testing.T) { + err := validate.GRPCRequest(tc.portID, tc.channelID) + + if tc.expErr == nil { + require.NoError(t, err, tc.msg) + } else { + require.Error(t, err, tc.msg) + require.EqualError(t, err, tc.expErr.Error(), tc.msg) + } + }) + } +} diff --git a/maintainership.png b/maintainership.png new file mode 100644 index 0000000..cdf40bf Binary files /dev/null and b/maintainership.png differ diff --git a/modules/apps/27-interchain-accounts/client/cli/cli.go b/modules/apps/27-interchain-accounts/client/cli/cli.go new file mode 100644 index 0000000..f05e553 --- /dev/null +++ b/modules/apps/27-interchain-accounts/client/cli/cli.go @@ -0,0 +1,44 @@ +package cli + +import ( + "github.com/spf13/cobra" + + controllercli "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/client/cli" + hostcli "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/client/cli" +) + +// GetQueryCmd returns the query commands for the interchain-accounts submodule +func GetQueryCmd() *cobra.Command { + icaQueryCmd := &cobra.Command{ + Use: "interchain-accounts", + Aliases: []string{"ica"}, + Short: "IBC interchain accounts query subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + } + + icaQueryCmd.AddCommand( + controllercli.GetQueryCmd(), + hostcli.GetQueryCmd(), + ) + + return icaQueryCmd +} + +// NewTxCmd returns the tx commands for the interchain-accounts submodule +func NewTxCmd() *cobra.Command { + icaTxCmd := &cobra.Command{ + Use: "interchain-accounts", + Aliases: []string{"ica"}, + Short: "IBC interchain accounts transaction subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + } + + icaTxCmd.AddCommand( + controllercli.NewTxCmd(), + hostcli.NewTxCmd(), + ) + + return icaTxCmd +} diff --git a/modules/apps/27-interchain-accounts/controller/client/cli/cli.go b/modules/apps/27-interchain-accounts/controller/client/cli/cli.go new file mode 100644 index 0000000..66eadad --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/client/cli/cli.go @@ -0,0 +1,42 @@ +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" +) + +// GetQueryCmd returns the query commands for the ICA controller submodule +func GetQueryCmd() *cobra.Command { + queryCmd := &cobra.Command{ + Use: "controller", + Short: "IBC interchain accounts controller query subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + } + + queryCmd.AddCommand( + GetCmdQueryInterchainAccount(), + GetCmdParams(), + ) + + return queryCmd +} + +// NewTxCmd creates and returns the tx command +func NewTxCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "controller", + Short: "IBC interchain accounts controller transaction subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand( + newRegisterInterchainAccountCmd(), + newSendTxCmd(), + ) + + return cmd +} diff --git a/modules/apps/27-interchain-accounts/controller/client/cli/query.go b/modules/apps/27-interchain-accounts/controller/client/cli/query.go new file mode 100644 index 0000000..da253c6 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/client/cli/query.go @@ -0,0 +1,76 @@ +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/version" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" +) + +// GetCmdQueryInterchainAccount returns the command handler for the controller submodule parameter querying. +func GetCmdQueryInterchainAccount() *cobra.Command { + cmd := &cobra.Command{ + Use: "interchain-account [owner] [connection-id]", + Short: "Query the interchain account address for a given owner on a particular connection", + Long: "Query the controller submodule for the interchain account address for a given owner on a particular connection", + Args: cobra.ExactArgs(2), + Example: fmt.Sprintf("%s query interchain-accounts controller interchain-account cosmos1layxcsmyye0dc0har9sdfzwckaz8sjwlfsj8zs connection-0", version.AppName), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + req := &types.QueryInterchainAccountRequest{ + Owner: args[0], + ConnectionId: args[1], + } + + res, err := queryClient.InterchainAccount(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdParams returns the command handler for the controller submodule parameter querying. +func GetCmdParams() *cobra.Command { + cmd := &cobra.Command{ + Use: "params", + Short: "Query the current interchain-accounts controller submodule parameters", + Long: "Query the current interchain-accounts controller submodule parameters", + Args: cobra.NoArgs, + Example: fmt.Sprintf("%s query interchain-accounts controller params", version.AppName), + RunE: func(cmd *cobra.Command, _ []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + res, err := queryClient.Params(cmd.Context(), &types.QueryParamsRequest{}) + if err != nil { + return err + } + + return clientCtx.PrintProto(res.Params) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/modules/apps/27-interchain-accounts/controller/client/cli/tx.go b/modules/apps/27-interchain-accounts/controller/client/cli/tx.go new file mode 100644 index 0000000..c375b7a --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/client/cli/tx.go @@ -0,0 +1,140 @@ +package cli + +import ( + "fmt" + "os" + "strings" + "time" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/codec" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" +) + +const ( + // The controller chain channel version + flagVersion = "version" + // The channel ordering + flagOrdering = "ordering" + flagPacketTimeoutTimestamp = "packet-timeout-timestamp" +) + +// defaultRelativePacketTimeoutTimestamp is the default packet timeout timestamp (in nanoseconds) +// relative to the current block timestamp of the counterparty chain provided by the client +// state. The timeout is disabled when set to 0. The default is currently set to a 10 minute +// timeout. +var defaultRelativePacketTimeoutTimestamp = uint64((time.Duration(10) * time.Minute).Nanoseconds()) + +func newRegisterInterchainAccountCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "register [connection-id]", + Short: "Register an interchain account on the provided connection.", + Long: strings.TrimSpace(`Register an account on the counterparty chain via the +connection id from the source chain. Connection identifier should be for the source chain +and the interchain account will be created on the counterparty chain. Callers are expected to +provide the appropriate application version string via {version} flag and the desired ordering +via the {ordering} flag. Generates a new port identifier using the provided owner string.`), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + connectionID := args[0] + owner := clientCtx.GetFromAddress().String() + version, err := cmd.Flags().GetString(flagVersion) + if err != nil { + return err + } + + order, err := parseOrdering(cmd) + if err != nil { + return err + } + + msg := types.NewMsgRegisterInterchainAccount(connectionID, owner, version, order) + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + cmd.Flags().String(flagVersion, "", "Controller chain channel version") + cmd.Flags().String(flagOrdering, channeltypes.UNORDERED.String(), fmt.Sprintf("Channel ordering, can be one of: %s", strings.Join(connectiontypes.SupportedOrderings, ", "))) + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + +func newSendTxCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "send-tx [connection-id] [path/to/packet_msg.json]", + Short: "Send an interchain account tx on the provided connection.", + Long: strings.TrimSpace(`Submits pre-built packet data containing messages to be executed on the host chain and attempts to send the packet. +Packet data is provided as json, file or string. A relative timeout timestamp can be provided using the flag {packet-timeout-timestamp}. +An absolute timeout is calculated on chain using the current block time. If no timeout value is set then a default relative timeout value of 10 minutes is used.`), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry) + + connectionID := args[0] + owner := clientCtx.GetFromAddress().String() + + // attempt to unmarshal ica msg data argument + var icaMsgData icatypes.InterchainAccountPacketData + msgContentOrFileName := args[1] + if err := cdc.UnmarshalJSON([]byte(msgContentOrFileName), &icaMsgData); err != nil { + // check for file path if JSON input is not provided + contents, err := os.ReadFile(msgContentOrFileName) + if err != nil { + return fmt.Errorf("neither JSON input nor path to .json file for packet data with messages were provided: %w", err) + } + + if err := cdc.UnmarshalJSON(contents, &icaMsgData); err != nil { + return fmt.Errorf("error unmarshalling packet data with messages file: %w", err) + } + } + + timeoutTimestamp, err := cmd.Flags().GetUint64(flagPacketTimeoutTimestamp) + if err != nil { + return err + } + + msg := types.NewMsgSendTx(owner, connectionID, timeoutTimestamp, icaMsgData) + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + cmd.Flags().Uint64(flagPacketTimeoutTimestamp, defaultRelativePacketTimeoutTimestamp, "Packet timeout timestamp in nanoseconds from now. Default is 10 minutes.") + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + +// parseOrdering gets the channel ordering from the flags. +func parseOrdering(cmd *cobra.Command) (channeltypes.Order, error) { + orderString, err := cmd.Flags().GetString(flagOrdering) + if err != nil { + return channeltypes.NONE, err + } + + order, found := channeltypes.Order_value[strings.ToUpper(orderString)] + if !found { + return channeltypes.NONE, fmt.Errorf("invalid channel ordering: %s", orderString) + } + + return channeltypes.Order(order), nil +} diff --git a/modules/apps/27-interchain-accounts/controller/ibc_middleware.go b/modules/apps/27-interchain-accounts/controller/ibc_middleware.go new file mode 100644 index 0000000..1e3b306 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/ibc_middleware.go @@ -0,0 +1,281 @@ +package controller + +import ( + "errors" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/keeper" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var ( + _ porttypes.Middleware = (*IBCMiddleware)(nil) + _ porttypes.PacketDataUnmarshaler = (*IBCMiddleware)(nil) +) + +// IBCMiddleware implements the ICS26 callbacks for the controller middleware given the +// ICA controller keeper and the underlying application. +type IBCMiddleware struct { + app porttypes.IBCModule + keeper keeper.Keeper +} + +// NewIBCMiddleware creates a new IBCMiddleware given the associated keeper. +// The underlying application is set to nil and authentication is assumed to +// be performed by a Cosmos SDK module that sends messages to controller message server. +func NewIBCMiddleware(k keeper.Keeper) IBCMiddleware { + return IBCMiddleware{ + app: nil, + keeper: k, + } +} + +// NewIBCMiddlewareWithAuth creates a new IBCMiddleware given the associated keeper and underlying application +func NewIBCMiddlewareWithAuth(app porttypes.IBCModule, k keeper.Keeper) IBCMiddleware { + return IBCMiddleware{ + app: app, + keeper: k, + } +} + +// OnChanOpenInit implements the IBCMiddleware interface +// +// Interchain Accounts is implemented to act as middleware for connected authentication modules on +// the controller side. The connected modules may not change the controller side portID or +// version. They will be allowed to perform custom logic without changing +// the parameters stored within a channel struct. +func (im IBCMiddleware) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + if !im.keeper.GetParams(ctx).ControllerEnabled { + return "", types.ErrControllerSubModuleDisabled + } + + version, err := im.keeper.OnChanOpenInit(ctx, order, connectionHops, portID, channelID, counterparty, version) + if err != nil { + return "", err + } + + // call underlying app's OnChanOpenInit callback with the passed in version + // the version returned is discarded as the ica-auth module does not have permission to edit the version string. + // ics27 will always return the version string containing the Metadata struct which is created during the `RegisterInterchainAccount` call. + if im.app != nil && im.keeper.IsMiddlewareEnabled(ctx, portID, connectionHops[0]) { + if _, err := im.app.OnChanOpenInit(ctx, order, connectionHops, portID, channelID, counterparty, version); err != nil { + return "", err + } + } + + return version, nil +} + +// OnChanOpenTry implements the IBCMiddleware interface +func (IBCMiddleware) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + return "", errorsmod.Wrap(icatypes.ErrInvalidChannelFlow, "channel handshake must be initiated by controller chain") +} + +// OnChanOpenAck implements the IBCMiddleware interface +// +// Interchain Accounts is implemented to act as middleware for connected authentication modules on +// the controller side. The connected modules may not change the portID or +// version. They will be allowed to perform custom logic without changing +// the parameters stored within a channel struct. +func (im IBCMiddleware) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyChannelID string, + counterpartyVersion string, +) error { + if !im.keeper.GetParams(ctx).ControllerEnabled { + return types.ErrControllerSubModuleDisabled + } + + if err := im.keeper.OnChanOpenAck(ctx, portID, channelID, counterpartyVersion); err != nil { + return err + } + + connectionID, err := im.keeper.GetConnectionID(ctx, portID, channelID) + if err != nil { + return err + } + + // call underlying app's OnChanOpenAck callback with the counterparty app version. + if im.app != nil && im.keeper.IsMiddlewareEnabled(ctx, portID, connectionID) { + return im.app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) + } + + return nil +} + +// OnChanOpenConfirm implements the IBCMiddleware interface +func (IBCMiddleware) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + return errorsmod.Wrap(icatypes.ErrInvalidChannelFlow, "channel handshake must be initiated by controller chain") +} + +// OnChanCloseInit implements the IBCMiddleware interface +func (IBCMiddleware) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + // Disallow user-initiated channel closing for interchain account channels + return errorsmod.Wrap(ibcerrors.ErrInvalidRequest, "user cannot close channel") +} + +// OnChanCloseConfirm implements the IBCMiddleware interface +func (im IBCMiddleware) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + if err := im.keeper.OnChanCloseConfirm(ctx, portID, channelID); err != nil { + return err + } + + connectionID, err := im.keeper.GetConnectionID(ctx, portID, channelID) + if err != nil { + return err + } + + if im.app != nil && im.keeper.IsMiddlewareEnabled(ctx, portID, connectionID) { + return im.app.OnChanCloseConfirm(ctx, portID, channelID) + } + + return nil +} + +// OnRecvPacket implements the IBCMiddleware interface +func (IBCMiddleware) OnRecvPacket( + ctx sdk.Context, + _ string, + packet channeltypes.Packet, + _ sdk.AccAddress, +) ibcexported.Acknowledgement { + err := errorsmod.Wrapf(icatypes.ErrInvalidChannelFlow, "cannot receive packet on controller chain") + ack := channeltypes.NewErrorAcknowledgement(err) + keeper.EmitAcknowledgementEvent(ctx, packet, ack, err) + return ack +} + +// OnAcknowledgementPacket implements the IBCMiddleware interface +func (im IBCMiddleware) OnAcknowledgementPacket( + ctx sdk.Context, + channelVersion string, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + if !im.keeper.GetParams(ctx).ControllerEnabled { + return types.ErrControllerSubModuleDisabled + } + + connectionID, err := im.keeper.GetConnectionID(ctx, packet.GetSourcePort(), packet.GetSourceChannel()) + if err != nil { + return err + } + + // call underlying app's OnAcknowledgementPacket callback. + if im.app != nil && im.keeper.IsMiddlewareEnabled(ctx, packet.GetSourcePort(), connectionID) { + return im.app.OnAcknowledgementPacket(ctx, channelVersion, packet, acknowledgement, relayer) + } + + return nil +} + +// OnTimeoutPacket implements the IBCMiddleware interface +func (im IBCMiddleware) OnTimeoutPacket( + ctx sdk.Context, + channelVersion string, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + if !im.keeper.GetParams(ctx).ControllerEnabled { + return types.ErrControllerSubModuleDisabled + } + + if err := im.keeper.OnTimeoutPacket(ctx, packet); err != nil { + return err + } + + connectionID, err := im.keeper.GetConnectionID(ctx, packet.GetSourcePort(), packet.GetSourceChannel()) + if err != nil { + return err + } + + if im.app != nil && im.keeper.IsMiddlewareEnabled(ctx, packet.GetSourcePort(), connectionID) { + return im.app.OnTimeoutPacket(ctx, channelVersion, packet, relayer) + } + + return nil +} + +// SendPacket implements the ICS4 Wrapper interface +func (IBCMiddleware) SendPacket( + ctx sdk.Context, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + data []byte, +) (uint64, error) { + panic(errors.New("SendPacket not supported for ICA controller module. Please use SendTx")) +} + +// WriteAcknowledgement implements the ICS4 Wrapper interface +func (IBCMiddleware) WriteAcknowledgement( + ctx sdk.Context, + packet ibcexported.PacketI, + ack ibcexported.Acknowledgement, +) error { + panic(errors.New("WriteAcknowledgement not supported for ICA controller module")) +} + +// GetAppVersion returns the interchain accounts metadata. +func (im IBCMiddleware) GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) { + return im.keeper.GetAppVersion(ctx, portID, channelID) +} + +// UnmarshalPacketData attempts to unmarshal the provided packet data bytes +// into an InterchainAccountPacketData. This function implements the optional +// PacketDataUnmarshaler interface required for ADR 008 support. +func (im IBCMiddleware) UnmarshalPacketData(ctx sdk.Context, portID string, channelID string, bz []byte) (any, string, error) { + var data icatypes.InterchainAccountPacketData + err := data.UnmarshalJSON(bz) + if err != nil { + return nil, "", err + } + + version, ok := im.GetAppVersion(ctx, portID, channelID) + if !ok { + return nil, "", errorsmod.Wrapf(ibcerrors.ErrNotFound, "app version not found for port %s and channel %s", portID, channelID) + } + + return data, version, nil +} diff --git a/modules/apps/27-interchain-accounts/controller/ibc_middleware_test.go b/modules/apps/27-interchain-accounts/controller/ibc_middleware_test.go new file mode 100644 index 0000000..5535651 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/ibc_middleware_test.go @@ -0,0 +1,936 @@ +package controller_test + +import ( + "errors" + "strconv" + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller" + controllerkeeper "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/keeper" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +const invalidVersion = "invalid|version" + +var ( + // TestOwnerAddress defines a reusable bech32 address for testing purposes + TestOwnerAddress = "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs" + + // TestPortID defines a reusable port identifier for testing purposes + TestPortID, _ = icatypes.NewControllerPortID(TestOwnerAddress) + + // TestVersion defines a reusable interchainaccounts version string for testing purposes + TestVersion = icatypes.NewDefaultMetadataString(ibctesting.FirstConnectionID, ibctesting.FirstConnectionID) +) + +type InterchainAccountsTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains used for convenience and readability + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain + chainC *ibctesting.TestChain +} + +func TestICATestSuite(t *testing.T) { + testifysuite.Run(t, new(InterchainAccountsTestSuite)) +} + +func (suite *InterchainAccountsTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 3) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) + suite.chainC = suite.coordinator.GetChain(ibctesting.GetChainID(3)) +} + +func NewICAPath(chainA, chainB *ibctesting.TestChain, ordering channeltypes.Order) *ibctesting.Path { + path := ibctesting.NewPath(chainA, chainB) + path.EndpointA.ChannelConfig.PortID = icatypes.HostPortID + path.EndpointB.ChannelConfig.PortID = icatypes.HostPortID + path.EndpointA.ChannelConfig.Order = ordering + path.EndpointB.ChannelConfig.Order = ordering + path.EndpointA.ChannelConfig.Version = TestVersion + path.EndpointB.ChannelConfig.Version = TestVersion + + return path +} + +func RegisterInterchainAccount(endpoint *ibctesting.Endpoint, owner string) error { + portID, err := icatypes.NewControllerPortID(owner) + if err != nil { + return err + } + + channelSequence := endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextChannelSequence(endpoint.Chain.GetContext()) + + if err := endpoint.Chain.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(endpoint.Chain.GetContext(), endpoint.ConnectionID, owner, TestVersion, endpoint.ChannelConfig.Order); err != nil { + return err + } + + // commit state changes for proof verification + endpoint.Chain.NextBlock() + + // update port/channel ids + endpoint.ChannelID = channeltypes.FormatChannelIdentifier(channelSequence) + endpoint.ChannelConfig.PortID = portID + endpoint.ChannelConfig.Version = TestVersion + + return nil +} + +// SetupICAPath invokes the InterchainAccounts entrypoint and subsequent channel handshake handlers +func SetupICAPath(path *ibctesting.Path, owner string) error { + if err := RegisterInterchainAccount(path.EndpointA, owner); err != nil { + return err + } + + if err := path.EndpointB.ChanOpenTry(); err != nil { + return err + } + + if err := path.EndpointA.ChanOpenAck(); err != nil { + return err + } + + return path.EndpointB.ChanOpenConfirm() +} + +func (suite *InterchainAccountsTestSuite) TestOnChanOpenInit() { + var ( + channel *channeltypes.Channel + isNilApp bool + path *ibctesting.Path + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", func() {}, nil, + }, + { + "ICA auth module modification of channel version is ignored", func() { + // NOTE: explicitly modify the channel version via the auth module callback, + // ensuring the expected JSON encoded metadata is not modified upon return + suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnChanOpenInit = func(ctx sdk.Context, order channeltypes.Order, connectionHops []string, + portID, channelID string, + counterparty channeltypes.Counterparty, version string, + ) (string, error) { + return "invalid-version", nil + } + }, nil, + }, + { + "controller submodule disabled", func() { + suite.chainA.GetSimApp().ICAControllerKeeper.SetParams(suite.chainA.GetContext(), types.NewParams(false)) + }, types.ErrControllerSubModuleDisabled, + }, + { + "ICA auth module callback fails", func() { + suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnChanOpenInit = func(ctx sdk.Context, order channeltypes.Order, connectionHops []string, + portID, channelID string, + counterparty channeltypes.Counterparty, version string, + ) (string, error) { + return "", errors.New("mock ica auth fails") + } + }, errors.New("mock ica auth fails"), + }, + { + "nil underlying app", func() { + isNilApp = true + }, nil, + }, + { + "middleware disabled", func() { + suite.chainA.GetSimApp().ICAControllerKeeper.DeleteMiddlewareEnabled(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ConnectionID) + + suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnChanOpenInit = func(ctx sdk.Context, order channeltypes.Order, connectionHops []string, + portID, channelID string, + counterparty channeltypes.Counterparty, version string, + ) (string, error) { + return "", errors.New("error should be unreachable") + } + }, nil, + }, + } + + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + isNilApp = false + + path = NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + // mock init interchain account + portID, err := icatypes.NewControllerPortID(TestOwnerAddress) + suite.Require().NoError(err) + + path.EndpointA.ChannelConfig.PortID = portID + path.EndpointA.ChannelID = ibctesting.FirstChannelID + + suite.chainA.GetSimApp().ICAControllerKeeper.SetMiddlewareEnabled(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ConnectionID) + + // default values + counterparty := channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + channel = &channeltypes.Channel{ + State: channeltypes.INIT, + Ordering: ordering, + Counterparty: counterparty, + ConnectionHops: []string{path.EndpointA.ConnectionID}, + Version: path.EndpointA.ChannelConfig.Version, + } + + tc.malleate() // malleate mutates test data + + // ensure channel on chainA is set in state + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.SetChannel(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, *channel) + + cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(path.EndpointA.ChannelConfig.PortID) + suite.Require().True(ok) + + if isNilApp { + cbs = controller.NewIBCMiddleware(suite.chainA.GetSimApp().ICAControllerKeeper) + } + + version, err := cbs.OnChanOpenInit(suite.chainA.GetContext(), channel.Ordering, channel.ConnectionHops, + path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, channel.Counterparty, channel.Version, + ) + + if tc.expErr == nil { + suite.Require().Equal(TestVersion, version) + suite.Require().NoError(err) + } else { + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } + } +} + +// Test initiating a ChanOpenTry using the controller chain instead of the host chain +// ChainA is the controller chain. ChainB creates a controller port as well, +// attempting to trick chainA. +// Sending a MsgChanOpenTry will never reach the application callback due to +// core IBC checks not passing, so a call to the application callback is also +// done directly. +func (suite *InterchainAccountsTestSuite) TestChanOpenTry() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + suite.SetupTest() // reset + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := RegisterInterchainAccount(path.EndpointA, TestOwnerAddress) + suite.Require().NoError(err) + + // chainB also creates a controller port + err = RegisterInterchainAccount(path.EndpointB, TestOwnerAddress) + suite.Require().NoError(err) + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + channelKey := host.ChannelKey(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + initProof, proofHeight := path.EndpointB.Chain.QueryProof(channelKey) + + // use chainA (controller) for ChanOpenTry + msg := channeltypes.NewMsgChannelOpenTry(path.EndpointA.ChannelConfig.PortID, TestVersion, ordering, []string{path.EndpointA.ConnectionID}, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, TestVersion, initProof, proofHeight, icatypes.ModuleName) + handler := suite.chainA.GetSimApp().MsgServiceRouter().Handler(msg) + _, err = handler(suite.chainA.GetContext(), msg) + + suite.Require().Error(err) + + cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(path.EndpointB.ChannelConfig.PortID) + suite.Require().True(ok) + + counterparty := channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + + version, err := cbs.OnChanOpenTry( + suite.chainA.GetContext(), path.EndpointA.ChannelConfig.Order, []string{path.EndpointA.ConnectionID}, + path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, + counterparty, path.EndpointB.ChannelConfig.Version, + ) + suite.Require().Error(err) + suite.Require().Equal("", version) + } +} + +func (suite *InterchainAccountsTestSuite) TestOnChanOpenAck() { + var ( + path *ibctesting.Path + isNilApp bool + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", func() {}, nil, + }, + { + "controller submodule disabled", func() { + suite.chainA.GetSimApp().ICAControllerKeeper.SetParams(suite.chainA.GetContext(), types.NewParams(false)) + }, types.ErrControllerSubModuleDisabled, + }, + { + "ICA OnChanOpenACK fails - invalid version", func() { + path.EndpointB.ChannelConfig.Version = invalidVersion + }, ibcerrors.ErrInvalidType, + }, + { + "ICA auth module callback fails", func() { + suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnChanOpenAck = func( + ctx sdk.Context, portID, channelID string, counterpartyChannelID string, counterpartyVersion string, + ) error { + return errors.New("mock ica auth fails") + } + }, errors.New("mock ica auth fails"), + }, + { + "nil underlying app", func() { + isNilApp = true + }, nil, + }, + { + "middleware disabled", func() { + suite.chainA.GetSimApp().ICAControllerKeeper.DeleteMiddlewareEnabled(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ConnectionID) + + suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnChanOpenAck = func( + ctx sdk.Context, portID, channelID string, counterpartyChannelID string, counterpartyVersion string, + ) error { + return errors.New("error should be unreachable") + } + }, nil, + }, + } + + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + isNilApp = false + + path = NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := RegisterInterchainAccount(path.EndpointA, TestOwnerAddress) + suite.Require().NoError(err) + + err = path.EndpointB.ChanOpenTry() + suite.Require().NoError(err) + + tc.malleate() // malleate mutates test data + + cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(path.EndpointA.ChannelConfig.PortID) + suite.Require().True(ok) + + err = cbs.OnChanOpenAck(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelID, path.EndpointB.ChannelConfig.Version) + + if isNilApp { + cbs = controller.NewIBCMiddleware(suite.chainA.GetSimApp().ICAControllerKeeper) + } + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } + } +} + +// Test initiating a ChanOpenConfirm using the controller chain instead of the host chain +// ChainA is the controller chain. ChainB is the host chain +// Sending a MsgChanOpenConfirm will never reach the application callback due to +// core IBC checks not passing, so a call to the application callback is also +// done directly. +func (suite *InterchainAccountsTestSuite) TestChanOpenConfirm() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + suite.SetupTest() // reset + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := RegisterInterchainAccount(path.EndpointA, TestOwnerAddress) + suite.Require().NoError(err) + + err = path.EndpointB.ChanOpenTry() + suite.Require().NoError(err) + + // chainB maliciously sets channel to OPEN + channel := channeltypes.NewChannel(channeltypes.OPEN, ordering, channeltypes.NewCounterparty(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID), []string{path.EndpointB.ConnectionID}, TestVersion) + suite.chainB.GetSimApp().GetIBCKeeper().ChannelKeeper.SetChannel(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, channel) + + // commit state changes so proof can be created + suite.chainB.NextBlock() + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + // query proof from ChainB + channelKey := host.ChannelKey(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + ackProof, proofHeight := path.EndpointB.Chain.QueryProof(channelKey) + + // use chainA (controller) for ChanOpenConfirm + msg := channeltypes.NewMsgChannelOpenConfirm(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, ackProof, proofHeight, icatypes.ModuleName) + handler := suite.chainA.GetSimApp().MsgServiceRouter().Handler(msg) + _, err = handler(suite.chainA.GetContext(), msg) + + suite.Require().Error(err) + + cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(path.EndpointA.ChannelConfig.PortID) + suite.Require().True(ok) + + err = cbs.OnChanOpenConfirm( + suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, + ) + suite.Require().Error(err) + } +} + +// OnChanCloseInit on controller (chainA) +func (suite *InterchainAccountsTestSuite) TestOnChanCloseInit() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + suite.SetupTest() // reset + + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(path.EndpointA.ChannelConfig.PortID) + suite.Require().True(ok) + + err = cbs.OnChanCloseInit( + suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, + ) + + suite.Require().Error(err) + } +} + +func (suite *InterchainAccountsTestSuite) TestOnChanCloseConfirm() { + var ( + path *ibctesting.Path + isNilApp bool + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", func() {}, nil, + }, + { + "nil underlying app", func() { + isNilApp = true + }, nil, + }, + } + + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + isNilApp = false + + path = NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + tc.malleate() // malleate mutates test data + + cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(path.EndpointA.ChannelConfig.PortID) + suite.Require().True(ok) + + if isNilApp { + cbs = controller.NewIBCMiddleware(suite.chainA.GetSimApp().ICAControllerKeeper) + } + + err = cbs.OnChanCloseConfirm( + suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } + } +} + +func (suite *InterchainAccountsTestSuite) TestOnRecvPacket() { + testCases := []struct { + name string + malleate func() + expSuccess bool + }{ + { + "ICA OnRecvPacket fails with ErrInvalidChannelFlow", func() {}, false, + }, + } + + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + tc.malleate() // malleate mutates test data + + cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(path.EndpointA.ChannelConfig.PortID) + suite.Require().True(ok) + + packet := channeltypes.NewPacket( + []byte("empty packet data"), + suite.chainB.SenderAccount.GetSequence(), + path.EndpointB.ChannelConfig.PortID, + path.EndpointB.ChannelID, + path.EndpointA.ChannelConfig.PortID, + path.EndpointA.ChannelID, + clienttypes.NewHeight(0, 100), + 0, + ) + + ctx := suite.chainA.GetContext() + ack := cbs.OnRecvPacket(ctx, path.EndpointA.GetChannel().Version, packet, nil) + suite.Require().Equal(tc.expSuccess, ack.Success()) + + expectedEvents := sdk.Events{ + sdk.NewEvent( + icatypes.EventTypePacket, + sdk.NewAttribute(sdk.AttributeKeyModule, icatypes.ModuleName), + sdk.NewAttribute(icatypes.AttributeKeyControllerChannelID, packet.GetDestChannel()), + sdk.NewAttribute(icatypes.AttributeKeyAckSuccess, strconv.FormatBool(false)), + sdk.NewAttribute(icatypes.AttributeKeyAckError, "cannot receive packet on controller chain: invalid message sent to channel end"), + ), + }.ToABCIEvents() + + expectedEvents = sdk.MarkEventsToIndex(expectedEvents, map[string]struct{}{}) + ibctesting.AssertEvents(&suite.Suite, expectedEvents, ctx.EventManager().Events().ToABCIEvents()) + }) + } + } +} + +func (suite *InterchainAccountsTestSuite) TestOnAcknowledgementPacket() { + var ( + path *ibctesting.Path + isNilApp bool + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "success", + func() {}, + nil, + }, + { + "controller submodule disabled", func() { + suite.chainA.GetSimApp().ICAControllerKeeper.SetParams(suite.chainA.GetContext(), types.NewParams(false)) + }, types.ErrControllerSubModuleDisabled, + }, + { + "ICA auth module callback fails", func() { + suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnAcknowledgementPacket = func( + ctx sdk.Context, channelVersion string, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress, + ) error { + return errors.New("mock ica auth fails") + } + }, errors.New("mock ica auth fails"), + }, + { + "nil underlying app", func() { + isNilApp = true + }, nil, + }, + { + "middleware disabled", func() { + suite.chainA.GetSimApp().ICAControllerKeeper.DeleteMiddlewareEnabled(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ConnectionID) + + suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnAcknowledgementPacket = func( + ctx sdk.Context, channelVersion string, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress, + ) error { + return errors.New("error should be unreachable") + } + }, nil, + }, + } + + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + for _, tc := range testCases { + suite.Run(tc.msg, func() { + suite.SetupTest() // reset + isNilApp = false + + path = NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + packet := channeltypes.NewPacket( + []byte("empty packet data"), + suite.chainA.SenderAccount.GetSequence(), + path.EndpointA.ChannelConfig.PortID, + path.EndpointA.ChannelID, + path.EndpointB.ChannelConfig.PortID, + path.EndpointB.ChannelID, + clienttypes.NewHeight(0, 100), + 0, + ) + + tc.malleate() // malleate mutates test data + + cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(path.EndpointA.ChannelConfig.PortID) + suite.Require().True(ok) + + if isNilApp { + cbs = controller.NewIBCMiddleware(suite.chainA.GetSimApp().ICAControllerKeeper) + } + + err = cbs.OnAcknowledgementPacket(suite.chainA.GetContext(), path.EndpointA.GetChannel().Version, packet, []byte("ack"), nil) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } + } +} + +func (suite *InterchainAccountsTestSuite) TestOnTimeoutPacket() { + var ( + path *ibctesting.Path + isNilApp bool + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "success", + func() {}, + nil, + }, + { + "controller submodule disabled", func() { + suite.chainA.GetSimApp().ICAControllerKeeper.SetParams(suite.chainA.GetContext(), types.NewParams(false)) + }, types.ErrControllerSubModuleDisabled, + }, + { + "ICA auth module callback fails", func() { + suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnTimeoutPacket = func( + ctx sdk.Context, channelVersion string, packet channeltypes.Packet, relayer sdk.AccAddress, + ) error { + return errors.New("mock ica auth fails") + } + }, errors.New("mock ica auth fails"), + }, + { + "nil underlying app", func() { + isNilApp = true + }, nil, + }, + { + "middleware disabled", func() { + suite.chainA.GetSimApp().ICAControllerKeeper.DeleteMiddlewareEnabled(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ConnectionID) + + suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnTimeoutPacket = func( + ctx sdk.Context, channelVersion string, packet channeltypes.Packet, relayer sdk.AccAddress, + ) error { + return errors.New("error should be unreachable") + } + }, nil, + }, + } + + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + for _, tc := range testCases { + suite.Run(tc.msg, func() { + suite.SetupTest() // reset + isNilApp = false + + path = NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + packet := channeltypes.NewPacket( + []byte("empty packet data"), + suite.chainA.SenderAccount.GetSequence(), + path.EndpointA.ChannelConfig.PortID, + path.EndpointA.ChannelID, + path.EndpointB.ChannelConfig.PortID, + path.EndpointB.ChannelID, + clienttypes.NewHeight(0, 100), + 0, + ) + + tc.malleate() // malleate mutates test data + + cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(path.EndpointA.ChannelConfig.PortID) + suite.Require().True(ok) + + if isNilApp { + cbs = controller.NewIBCMiddleware(suite.chainA.GetSimApp().ICAControllerKeeper) + } + + err = cbs.OnTimeoutPacket(suite.chainA.GetContext(), path.EndpointA.GetChannel().Version, packet, nil) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } + } +} + +func (suite *InterchainAccountsTestSuite) TestSingleHostMultipleControllers() { + var ( + pathAToB *ibctesting.Path + pathCToB *ibctesting.Path + ) + + testCases := []struct { + msg string + malleate func() + }{ + { + "success", + func() {}, + }, + } + + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + for _, tc := range testCases { + suite.Run(tc.msg, func() { + // reset + suite.SetupTest() + TestVersion = icatypes.NewDefaultMetadataString(ibctesting.FirstConnectionID, ibctesting.FirstConnectionID) + + // Setup a new path from A(controller) -> B(host) + pathAToB = NewICAPath(suite.chainA, suite.chainB, ordering) + pathAToB.SetupConnections() + + err := SetupICAPath(pathAToB, TestOwnerAddress) + suite.Require().NoError(err) + + // Setup a new path from C(controller) -> B(host) + pathCToB = NewICAPath(suite.chainC, suite.chainB, ordering) + pathCToB.SetupConnections() + + // NOTE: Here the version metadata is overridden to include to the next host connection sequence (i.e. chainB's connection to chainC) + // SetupICAPath() will set endpoint.ChannelConfig.Version to TestVersion + TestVersion = string(icatypes.ModuleCdc.MustMarshalJSON(&icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: pathCToB.EndpointA.ConnectionID, + HostConnectionId: pathCToB.EndpointB.ConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, + })) + + err = SetupICAPath(pathCToB, TestOwnerAddress) + suite.Require().NoError(err) + + tc.malleate() // malleate mutates test data + + accAddressChainA, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), pathAToB.EndpointB.ConnectionID, pathAToB.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + accAddressChainC, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), pathCToB.EndpointB.ConnectionID, pathCToB.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + suite.Require().NotEqual(accAddressChainA, accAddressChainC) + + chainAChannelID, found := suite.chainB.GetSimApp().ICAHostKeeper.GetActiveChannelID(suite.chainB.GetContext(), pathAToB.EndpointB.ConnectionID, pathAToB.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + chainCChannelID, found := suite.chainB.GetSimApp().ICAHostKeeper.GetActiveChannelID(suite.chainB.GetContext(), pathCToB.EndpointB.ConnectionID, pathCToB.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + suite.Require().NotEqual(chainAChannelID, chainCChannelID) + }) + } + } +} + +func (suite *InterchainAccountsTestSuite) TestGetAppVersion() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + suite.SetupTest() // reset + + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(path.EndpointA.ChannelConfig.PortID) + suite.Require().True(ok) + + controllerStack, ok := cbs.(porttypes.ICS4Wrapper) + suite.Require().True(ok) + + appVersion, found := controllerStack.GetAppVersion(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + suite.Require().True(found) + suite.Require().Equal(path.EndpointA.ChannelConfig.Version, appVersion) + } +} + +func (suite *InterchainAccountsTestSuite) TestInFlightHandshakeRespectsGoAPICaller() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + suite.SetupTest() // reset + + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + // initiate a channel handshake such that channel.State == INIT + err := RegisterInterchainAccount(path.EndpointA, suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + + // attempt to start a second handshake via the controller msg server + msgServer := controllerkeeper.NewMsgServerImpl(&suite.chainA.GetSimApp().ICAControllerKeeper) + msgRegisterInterchainAccount := types.NewMsgRegisterInterchainAccount(path.EndpointA.ConnectionID, suite.chainA.SenderAccount.GetAddress().String(), TestVersion, ordering) + + res, err := msgServer.RegisterInterchainAccount(suite.chainA.GetContext(), msgRegisterInterchainAccount) + suite.Require().Error(err) + suite.Require().Nil(res) + } +} + +func (suite *InterchainAccountsTestSuite) TestInFlightHandshakeRespectsMsgServerCaller() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + suite.SetupTest() // reset + + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + // initiate a channel handshake such that channel.State == INIT + msgServer := controllerkeeper.NewMsgServerImpl(&suite.chainA.GetSimApp().ICAControllerKeeper) + msgRegisterInterchainAccount := types.NewMsgRegisterInterchainAccount(path.EndpointA.ConnectionID, suite.chainA.SenderAccount.GetAddress().String(), TestVersion, ordering) + + res, err := msgServer.RegisterInterchainAccount(suite.chainA.GetContext(), msgRegisterInterchainAccount) + suite.Require().NotNil(res) + suite.Require().NoError(err) + + // attempt to start a second handshake via the legacy Go API + err = RegisterInterchainAccount(path.EndpointA, suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().Error(err) + } +} + +func (suite *InterchainAccountsTestSuite) TestClosedChannelReopensWithMsgServer() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + suite.SetupTest() // reset + + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := SetupICAPath(path, suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + + // set the channel state to closed + path.EndpointA.UpdateChannel(func(channel *channeltypes.Channel) { channel.State = channeltypes.CLOSED }) + path.EndpointB.UpdateChannel(func(channel *channeltypes.Channel) { channel.State = channeltypes.CLOSED }) + + // reset endpoint channel ids + path.EndpointA.ChannelID = "" + path.EndpointB.ChannelID = "" + + // fetch the next channel sequence before reinitiating the channel handshake + channelSeq := suite.chainA.GetSimApp().GetIBCKeeper().ChannelKeeper.GetNextChannelSequence(suite.chainA.GetContext()) + + // route a new MsgRegisterInterchainAccount in order to reopen the + msgServer := controllerkeeper.NewMsgServerImpl(&suite.chainA.GetSimApp().ICAControllerKeeper) + msgRegisterInterchainAccount := types.NewMsgRegisterInterchainAccount(path.EndpointA.ConnectionID, suite.chainA.SenderAccount.GetAddress().String(), path.EndpointA.ChannelConfig.Version, ordering) + + res, err := msgServer.RegisterInterchainAccount(suite.chainA.GetContext(), msgRegisterInterchainAccount) + suite.Require().NoError(err) + suite.Require().Equal(channeltypes.FormatChannelIdentifier(channelSeq), res.ChannelId) + + // assign the channel sequence to endpointA before generating proofs and initiating the TRY step + path.EndpointA.ChannelID = channeltypes.FormatChannelIdentifier(channelSeq) + + path.EndpointA.Chain.NextBlock() + + err = path.EndpointB.ChanOpenTry() + suite.Require().NoError(err) + + err = path.EndpointA.ChanOpenAck() + suite.Require().NoError(err) + + err = path.EndpointB.ChanOpenConfirm() + suite.Require().NoError(err) + } +} + +func (suite *InterchainAccountsTestSuite) TestPacketDataUnmarshalerInterface() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + suite.SetupTest() // reset + + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + expPacketData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: []byte("data"), + Memo: "", + } + + controllerMiddleware := controller.NewIBCMiddleware(suite.chainA.GetSimApp().ICAControllerKeeper) + packetData, version, err := controllerMiddleware.UnmarshalPacketData(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, expPacketData.GetBytes()) + suite.Require().NoError(err) + suite.Require().Equal(version, path.EndpointA.ChannelConfig.Version) + suite.Require().Equal(expPacketData, packetData) + + // test invalid packet data + invalidPacketData := []byte("invalid packet data") + // Context, port identifier and channel identifier are not used for controller. + packetData, version, err = controllerMiddleware.UnmarshalPacketData(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, invalidPacketData) + suite.Require().Error(err) + suite.Require().Empty(version) + suite.Require().Nil(packetData) + } +} diff --git a/modules/apps/27-interchain-accounts/controller/keeper/account.go b/modules/apps/27-interchain-accounts/controller/keeper/account.go new file mode 100644 index 0000000..97e4f2a --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/keeper/account.go @@ -0,0 +1,91 @@ +package keeper + +import ( + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + + "github.com/cosmos/ibc-go/v10/internal/logging" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +// RegisterInterchainAccount is the entry point to registering an interchain account: +// - It generates a new port identifier using the provided owner string. +// - Callers are expected to provide the appropriate application version string. +// - For example, this could be an ICS27 encoded metadata type or an ICS29 encoded metadata type with a nested application version. +// - A new MsgChannelOpenInit is routed through the MsgServiceRouter, executing the OnOpenChanInit callback stack as configured. +// - An error is returned if the port identifier is already in use. Gaining access to interchain accounts whose channels +// have closed cannot be done with this function. A regular MsgChannelOpenInit must be used. +// +// Deprecated: this is a legacy API that is only intended to function correctly in workflows where an underlying authentication application has been set. +// Calling this API will result in all packet callbacks being routed to the underlying application. + +// Please use MsgRegisterInterchainAccount for use cases which do not need to route to an underlying application. + +// Prior to v6.x.x of ibc-go, the controller module was only functional as middleware, with authentication performed +// by the underlying application. For a full summary of the changes in v6.x.x, please see ADR009. +// This API will be removed in later releases. +func (k Keeper) RegisterInterchainAccount(ctx sdk.Context, connectionID, owner, version string, + ordering channeltypes.Order, +) error { + portID, err := icatypes.NewControllerPortID(owner) + if err != nil { + return err + } + + if k.IsMiddlewareDisabled(ctx, portID, connectionID) && !k.IsActiveChannelClosed(ctx, connectionID, portID) { + return errorsmod.Wrap(icatypes.ErrInvalidChannelFlow, "channel is already active or a handshake is in flight") + } + + k.SetMiddlewareEnabled(ctx, portID, connectionID) + + // use ORDER_UNORDERED as default in case ordering is NONE + if ordering == channeltypes.NONE { + ordering = channeltypes.UNORDERED + } + + _, err = k.registerInterchainAccount(ctx, connectionID, portID, version, ordering) + if err != nil { + return err + } + + return nil +} + +// registerInterchainAccount registers an interchain account, returning the channel id of the MsgChannelOpenInitResponse +// and an error if one occurred. +func (k Keeper) registerInterchainAccount(ctx sdk.Context, connectionID, portID, version string, + ordering channeltypes.Order, +) (string, error) { + // if there is an active channel for this portID / connectionID return an error + activeChannelID, found := k.GetOpenActiveChannel(ctx, connectionID, portID) + if found { + return "", errorsmod.Wrapf(icatypes.ErrActiveChannelAlreadySet, "existing active channel %s for portID %s on connection %s", activeChannelID, portID, connectionID) + } + + k.setPort(ctx, portID) + + msg := channeltypes.NewMsgChannelOpenInit(portID, version, ordering, []string{connectionID}, icatypes.HostPortID, authtypes.NewModuleAddress(icatypes.ModuleName).String()) + handler := k.msgRouter.Handler(msg) + res, err := handler(ctx, msg) + if err != nil { + return "", err + } + + events := res.GetEvents() + k.Logger(ctx).Debug("emitting interchain account registration events", logging.SdkEventsToLogArguments(events)) + + // NOTE: The sdk msg handler creates a new EventManager, so events must be correctly propagated back to the current context + ctx.EventManager().EmitEvents(events) + + firstMsgResponse := res.MsgResponses[0] + channelOpenInitResponse, ok := firstMsgResponse.GetCachedValue().(*channeltypes.MsgChannelOpenInitResponse) + if !ok { + return "", errorsmod.Wrapf(ibcerrors.ErrInvalidType, "failed to convert %T message response to %T", firstMsgResponse.GetCachedValue(), &channeltypes.MsgChannelOpenInitResponse{}) + } + + return channelOpenInitResponse.ChannelId, nil +} diff --git a/modules/apps/27-interchain-accounts/controller/keeper/account_test.go b/modules/apps/27-interchain-accounts/controller/keeper/account_test.go new file mode 100644 index 0000000..7dc0d34 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/keeper/account_test.go @@ -0,0 +1,116 @@ +package keeper_test + +import ( + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *KeeperTestSuite) TestRegisterInterchainAccount() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + var ( + owner string + path *ibctesting.Path + err error + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", func() {}, nil, + }, + { + "fails to generate port-id", + func() { + owner = "" + }, + icatypes.ErrInvalidAccountAddress, + }, + { + "MsgChanOpenInit fails - channel is already active & in state OPEN", + func() { + portID, err := icatypes.NewControllerPortID(TestOwnerAddress) + suite.Require().NoError(err) + + channelID := channeltypes.FormatChannelIdentifier(suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.GetNextChannelSequence(suite.chainA.GetContext())) + path.EndpointA.ChannelID = channelID + + suite.chainA.GetSimApp().ICAControllerKeeper.SetActiveChannelID(suite.chainA.GetContext(), ibctesting.FirstConnectionID, portID, path.EndpointA.ChannelID) + + counterparty := channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + channel := channeltypes.Channel{ + State: channeltypes.OPEN, + Ordering: ordering, + Counterparty: counterparty, + ConnectionHops: []string{path.EndpointA.ConnectionID}, + Version: TestVersion, + } + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.SetChannel(suite.chainA.GetContext(), portID, path.EndpointA.ChannelID, channel) + }, + icatypes.ErrActiveChannelAlreadySet, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + owner = TestOwnerAddress // must be explicitly changed + + path = NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + tc.malleate() // malleate mutates test data + + err = suite.chainA.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(suite.chainA.GetContext(), path.EndpointA.ConnectionID, owner, TestVersion, ordering) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } + } +} + +func (suite *KeeperTestSuite) TestRegisterSameOwnerMultipleConnections() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + suite.SetupTest() + + owner := TestOwnerAddress + + pathAToB := NewICAPath(suite.chainA, suite.chainB, ordering) + pathAToB.SetupConnections() + + pathAToC := NewICAPath(suite.chainA, suite.chainC, ordering) + pathAToC.SetupConnections() + + // build ICS27 metadata with connection identifiers for path A->B + metadata := &icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: pathAToB.EndpointA.ConnectionID, + HostConnectionId: pathAToB.EndpointB.ConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, + } + + err := suite.chainA.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(suite.chainA.GetContext(), pathAToB.EndpointA.ConnectionID, owner, string(icatypes.ModuleCdc.MustMarshalJSON(metadata)), ordering) + suite.Require().NoError(err) + + // build ICS27 metadata with connection identifiers for path A->C + metadata = &icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: pathAToC.EndpointA.ConnectionID, + HostConnectionId: pathAToC.EndpointB.ConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, + } + + err = suite.chainA.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(suite.chainA.GetContext(), pathAToC.EndpointA.ConnectionID, owner, string(icatypes.ModuleCdc.MustMarshalJSON(metadata)), ordering) + suite.Require().NoError(err) + } +} diff --git a/modules/apps/27-interchain-accounts/controller/keeper/events.go b/modules/apps/27-interchain-accounts/controller/keeper/events.go new file mode 100644 index 0000000..cea2b0e --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/keeper/events.go @@ -0,0 +1,34 @@ +package keeper + +import ( + "strconv" + + sdk "github.com/cosmos/cosmos-sdk/types" + + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// EmitAcknowledgementEvent emits an event signalling a successful or failed acknowledgement and including the error +// details if any. +func EmitAcknowledgementEvent(ctx sdk.Context, packet channeltypes.Packet, ack exported.Acknowledgement, + err error, +) { + attributes := []sdk.Attribute{ + sdk.NewAttribute(sdk.AttributeKeyModule, icatypes.ModuleName), + sdk.NewAttribute(icatypes.AttributeKeyControllerChannelID, packet.GetDestChannel()), + sdk.NewAttribute(icatypes.AttributeKeyAckSuccess, strconv.FormatBool(ack.Success())), + } + + if err != nil { + attributes = append(attributes, sdk.NewAttribute(icatypes.AttributeKeyAckError, err.Error())) + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + icatypes.EventTypePacket, + attributes..., + ), + ) +} diff --git a/modules/apps/27-interchain-accounts/controller/keeper/export_test.go b/modules/apps/27-interchain-accounts/controller/keeper/export_test.go new file mode 100644 index 0000000..6335097 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/keeper/export_test.go @@ -0,0 +1,16 @@ +package keeper + +/* + This file is to allow for unexported functions and fields to be accessible to the testing package. +*/ + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" +) + +// GetAppMetadata is a wrapper around getAppMetadata to allow the function to be directly called in tests. +func (k Keeper) GetAppMetadata(ctx sdk.Context, portID, channelID string) (icatypes.Metadata, error) { + return k.getAppMetadata(ctx, portID, channelID) +} diff --git a/modules/apps/27-interchain-accounts/controller/keeper/genesis.go b/modules/apps/27-interchain-accounts/controller/keeper/genesis.go new file mode 100644 index 0000000..f7bfd04 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/keeper/genesis.go @@ -0,0 +1,40 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + genesistypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/genesis/types" +) + +// InitGenesis initializes the interchain accounts controller application state from a provided genesis state +func InitGenesis(ctx sdk.Context, keeper Keeper, state genesistypes.ControllerGenesisState) { + for _, portID := range state.Ports { + keeper.setPort(ctx, portID) + } + + for _, ch := range state.ActiveChannels { + keeper.SetActiveChannelID(ctx, ch.ConnectionId, ch.PortId, ch.ChannelId) + + if ch.IsMiddlewareEnabled { + keeper.SetMiddlewareEnabled(ctx, ch.PortId, ch.ConnectionId) + } else { + keeper.SetMiddlewareDisabled(ctx, ch.PortId, ch.ConnectionId) + } + } + + for _, acc := range state.InterchainAccounts { + keeper.SetInterchainAccountAddress(ctx, acc.ConnectionId, acc.PortId, acc.AccountAddress) + } + + keeper.SetParams(ctx, state.Params) +} + +// ExportGenesis returns the interchain accounts controller exported genesis +func ExportGenesis(ctx sdk.Context, keeper Keeper) genesistypes.ControllerGenesisState { + return genesistypes.NewControllerGenesisState( + keeper.GetAllActiveChannels(ctx), + keeper.GetAllInterchainAccounts(ctx), + keeper.GetAllPorts(ctx), + keeper.GetParams(ctx), + ) +} diff --git a/modules/apps/27-interchain-accounts/controller/keeper/genesis_test.go b/modules/apps/27-interchain-accounts/controller/keeper/genesis_test.go new file mode 100644 index 0000000..40f7734 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/keeper/genesis_test.go @@ -0,0 +1,110 @@ +package keeper_test + +import ( + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/keeper" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + genesistypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/genesis/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *KeeperTestSuite) TestInitGenesis() { + ports := []string{"port1", "port2", "port3"} + + testCases := []struct { + name string + malleate func() + }{ + { + "success", func() {}, + }, + } + + interchainAccAddr := icatypes.GenerateAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, TestPortID) + genesisState := genesistypes.ControllerGenesisState{ + ActiveChannels: []genesistypes.ActiveChannel{ + { + ConnectionId: ibctesting.FirstConnectionID, + PortId: TestPortID, + ChannelId: ibctesting.FirstChannelID, + IsMiddlewareEnabled: true, + }, + { + ConnectionId: "connection-1", + PortId: "test-port-1", + ChannelId: "channel-1", + IsMiddlewareEnabled: false, + }, + }, + InterchainAccounts: []genesistypes.RegisteredInterchainAccount{ + { + ConnectionId: ibctesting.FirstConnectionID, + PortId: TestPortID, + AccountAddress: interchainAccAddr.String(), + }, + }, + Ports: ports, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + tc.malleate() + + keeper.InitGenesis(suite.chainA.GetContext(), suite.chainA.GetSimApp().ICAControllerKeeper, genesisState) + + channelID, found := suite.chainA.GetSimApp().ICAControllerKeeper.GetActiveChannelID(suite.chainA.GetContext(), ibctesting.FirstConnectionID, TestPortID) + suite.Require().True(found) + suite.Require().Equal(ibctesting.FirstChannelID, channelID) + + isMiddlewareEnabled := suite.chainA.GetSimApp().ICAControllerKeeper.IsMiddlewareEnabled(suite.chainA.GetContext(), TestPortID, ibctesting.FirstConnectionID) + suite.Require().True(isMiddlewareEnabled) + + isMiddlewareDisabled := suite.chainA.GetSimApp().ICAControllerKeeper.IsMiddlewareDisabled(suite.chainA.GetContext(), "test-port-1", "connection-1") + suite.Require().True(isMiddlewareDisabled) + + accountAdrr, found := suite.chainA.GetSimApp().ICAControllerKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), ibctesting.FirstConnectionID, TestPortID) + suite.Require().True(found) + suite.Require().Equal(interchainAccAddr.String(), accountAdrr) + + expParams := types.NewParams(false) + params := suite.chainA.GetSimApp().ICAControllerKeeper.GetParams(suite.chainA.GetContext()) + suite.Require().Equal(expParams, params) + + for _, port := range ports { + store := suite.chainA.GetContext().KVStore(suite.chainA.GetSimApp().GetKey(types.StoreKey)) + suite.Require().True(store.Has(icatypes.KeyPort(port))) + } + }) + } +} + +func (suite *KeeperTestSuite) TestExportGenesis() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + suite.SetupTest() + + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + interchainAccAddr, exists := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointB.ConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(exists) + + genesisState := keeper.ExportGenesis(suite.chainA.GetContext(), suite.chainA.GetSimApp().ICAControllerKeeper) + + suite.Require().Equal(path.EndpointA.ChannelID, genesisState.ActiveChannels[0].ChannelId) + suite.Require().Equal(path.EndpointA.ChannelConfig.PortID, genesisState.ActiveChannels[0].PortId) + suite.Require().True(genesisState.ActiveChannels[0].IsMiddlewareEnabled) + + suite.Require().Equal(interchainAccAddr, genesisState.InterchainAccounts[0].AccountAddress) + suite.Require().Equal(path.EndpointA.ChannelConfig.PortID, genesisState.InterchainAccounts[0].PortId) + + suite.Require().Equal([]string{TestPortID}, genesisState.GetPorts()) + + expParams := types.DefaultParams() + suite.Require().Equal(expParams, genesisState.GetParams()) + } +} diff --git a/modules/apps/27-interchain-accounts/controller/keeper/grpc_query.go b/modules/apps/27-interchain-accounts/controller/keeper/grpc_query.go new file mode 100644 index 0000000..c356636 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/keeper/grpc_query.go @@ -0,0 +1,48 @@ +package keeper + +import ( + "context" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" +) + +var _ types.QueryServer = (*Keeper)(nil) + +// InterchainAccount implements the Query/InterchainAccount gRPC method +func (k Keeper) InterchainAccount(goCtx context.Context, req *types.QueryInterchainAccountRequest) (*types.QueryInterchainAccountResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + portID, err := icatypes.NewControllerPortID(req.Owner) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "failed to generate portID from owner address: %s", err) + } + + addr, found := k.GetInterchainAccountAddress(ctx, req.ConnectionId, portID) + if !found { + return nil, status.Errorf(codes.NotFound, "failed to retrieve account address for %s on connection %s", portID, req.ConnectionId) + } + + return &types.QueryInterchainAccountResponse{ + Address: addr, + }, nil +} + +// Params implements the Query/Params gRPC method +func (k Keeper) Params(goCtx context.Context, _ *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + params := k.GetParams(ctx) + + return &types.QueryParamsResponse{ + Params: ¶ms, + }, nil +} diff --git a/modules/apps/27-interchain-accounts/controller/keeper/grpc_query_test.go b/modules/apps/27-interchain-accounts/controller/keeper/grpc_query_test.go new file mode 100644 index 0000000..d09bf7e --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/keeper/grpc_query_test.go @@ -0,0 +1,84 @@ +package keeper_test + +import ( + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *KeeperTestSuite) TestQueryInterchainAccount() { + var req *types.QueryInterchainAccountRequest + + testCases := []struct { + name string + malleate func() + errMsg string + }{ + { + "success", + func() {}, + "", + }, + { + "empty request", + func() { + req = nil + }, + "empty request", + }, + { + "empty owner address", + func() { + req.Owner = "" + }, + "failed to generate portID from owner address: owner address cannot be empty: invalid account address", + }, + { + "invalid connection, account address not found", + func() { + req.ConnectionId = ibctesting.InvalidID + }, + "failed to retrieve account address", + }, + } + + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := SetupICAPath(path, ibctesting.TestAccAddress) + suite.Require().NoError(err) + + req = &types.QueryInterchainAccountRequest{ + ConnectionId: ibctesting.FirstConnectionID, + Owner: ibctesting.TestAccAddress, + } + + tc.malleate() + + res, err := suite.chainA.GetSimApp().ICAControllerKeeper.InterchainAccount(suite.chainA.GetContext(), req) + + if tc.errMsg == "" { + expAddress, exists := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointB.ConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(exists) + + suite.Require().NoError(err) + suite.Require().Equal(expAddress, res.Address) + } else { + suite.Require().ErrorContains(err, tc.errMsg) + } + }) + } + } +} + +func (suite *KeeperTestSuite) TestQueryParams() { + ctx := suite.chainA.GetContext() + expParams := types.DefaultParams() + res, _ := suite.chainA.GetSimApp().ICAControllerKeeper.Params(ctx, &types.QueryParamsRequest{}) + suite.Require().Equal(&expParams, res.Params) +} diff --git a/modules/apps/27-interchain-accounts/controller/keeper/handshake.go b/modules/apps/27-interchain-accounts/controller/keeper/handshake.go new file mode 100644 index 0000000..ba0828b --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/keeper/handshake.go @@ -0,0 +1,258 @@ +package keeper + +import ( + "fmt" + "strings" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" +) + +// OnChanOpenInit performs basic validation of channel initialization. +// The counterparty port identifier must be the host chain representation as defined in the types package, +// the channel version must be equal to the version in the types package, +// there must not be an active channel for the specified port identifier. +func (k Keeper) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + if !strings.HasPrefix(portID, icatypes.ControllerPortPrefix) { + return "", errorsmod.Wrapf(icatypes.ErrInvalidControllerPort, "expected %s{owner-account-address}, got %s", icatypes.ControllerPortPrefix, portID) + } + + if counterparty.PortId != icatypes.HostPortID { + return "", errorsmod.Wrapf(icatypes.ErrInvalidHostPort, "expected %s, got %s", icatypes.HostPortID, counterparty.PortId) + } + + var ( + err error + metadata icatypes.Metadata + ) + if strings.TrimSpace(version) == "" { + connection, err := k.channelKeeper.GetConnection(ctx, connectionHops[0]) + if err != nil { + return "", err + } + + metadata = icatypes.NewDefaultMetadata(connectionHops[0], connection.Counterparty.ConnectionId) + } else { + metadata, err = icatypes.MetadataFromVersion(version) + if err != nil { + return "", err + } + } + + if err := icatypes.ValidateControllerMetadata(ctx, k.channelKeeper, connectionHops, metadata); err != nil { + return "", err + } + + activeChannelID, found := k.GetActiveChannelID(ctx, connectionHops[0], portID) + if found { + channel, found := k.channelKeeper.GetChannel(ctx, portID, activeChannelID) + if !found { + panic(fmt.Errorf("active channel mapping set for %s but channel does not exist in channel store", activeChannelID)) + } + + if channel.State != channeltypes.CLOSED { + return "", errorsmod.Wrapf(icatypes.ErrActiveChannelAlreadySet, "existing active channel %s for portID %s must be %s", activeChannelID, portID, channeltypes.CLOSED) + } + + if channel.Ordering != order { + return "", errorsmod.Wrapf(channeltypes.ErrInvalidChannelOrdering, "order cannot change when reopening a channel expected %s, got %s", channel.Ordering, order) + } + + appVersion, found := k.GetAppVersion(ctx, portID, activeChannelID) + if !found { + panic(fmt.Errorf("active channel mapping set for %s, but channel does not exist in channel store", activeChannelID)) + } + + if !icatypes.IsPreviousMetadataEqual(appVersion, metadata) { + return "", errorsmod.Wrap(icatypes.ErrInvalidVersion, "previous active channel metadata does not match provided version") + } + } + + return string(icatypes.ModuleCdc.MustMarshalJSON(&metadata)), nil +} + +// OnChanOpenAck sets the active channel for the interchain account/owner pair +// and stores the associated interchain account address in state keyed by it's corresponding port identifier +func (k Keeper) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyVersion string, +) error { + if portID == icatypes.HostPortID { + return errorsmod.Wrapf(icatypes.ErrInvalidControllerPort, "portID cannot be host chain port ID: %s", icatypes.HostPortID) + } + + if !strings.HasPrefix(portID, icatypes.ControllerPortPrefix) { + return errorsmod.Wrapf(icatypes.ErrInvalidControllerPort, "expected %s{owner-account-address}, got %s", icatypes.ControllerPortPrefix, portID) + } + + metadata, err := icatypes.MetadataFromVersion(counterpartyVersion) + if err != nil { + return err + } + if activeChannelID, found := k.GetOpenActiveChannel(ctx, metadata.ControllerConnectionId, portID); found { + return errorsmod.Wrapf(icatypes.ErrActiveChannelAlreadySet, "existing active channel %s for portID %s", activeChannelID, portID) + } + + channel, found := k.channelKeeper.GetChannel(ctx, portID, channelID) + if !found { + return errorsmod.Wrapf(channeltypes.ErrChannelNotFound, "failed to retrieve channel %s on port %s", channelID, portID) + } + + if err := icatypes.ValidateControllerMetadata(ctx, k.channelKeeper, channel.ConnectionHops, metadata); err != nil { + return err + } + + if strings.TrimSpace(metadata.Address) == "" { + return errorsmod.Wrap(icatypes.ErrInvalidAccountAddress, "interchain account address cannot be empty") + } + + k.SetActiveChannelID(ctx, metadata.ControllerConnectionId, portID, channelID) + k.SetInterchainAccountAddress(ctx, metadata.ControllerConnectionId, portID, metadata.Address) + + return nil +} + +// OnChanCloseConfirm removes the active channel stored in state +func (Keeper) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + return nil +} + +// OnChanUpgradeInit performs the upgrade init step of the channel upgrade handshake. +// The upgrade init callback must verify the proposed changes to the order, connectionHops, and version. +// Within the version we have the tx type, encoding, interchain account address, host/controller connectionID's +// and the ICS27 protocol version. +// +// The following may be changed: +// - tx type (must be supported) +// - encoding (must be supported) +// - order +// +// The following may not be changed: +// - connectionHops (and subsequently host/controller connectionIDs) +// - interchain account address +// - ICS27 protocol version +func (k Keeper) OnChanUpgradeInit(ctx sdk.Context, portID, channelID string, proposedOrder channeltypes.Order, proposedConnectionHops []string, proposedversion string) (string, error) { + // verify connection hops has not changed + connectionID, err := k.GetConnectionID(ctx, portID, channelID) + if err != nil { + return "", err + } + + if len(proposedConnectionHops) != 1 || proposedConnectionHops[0] != connectionID { + return "", errorsmod.Wrapf(channeltypes.ErrInvalidUpgrade, "expected connection hops %s, got %s", []string{connectionID}, proposedConnectionHops) + } + + // verify proposed version only modifies tx type or encoding + if strings.TrimSpace(proposedversion) == "" { + return "", errorsmod.Wrap(icatypes.ErrInvalidVersion, "version cannot be empty") + } + + proposedMetadata, err := icatypes.MetadataFromVersion(proposedversion) + if err != nil { + return "", err + } + + currentMetadata, err := k.getAppMetadata(ctx, portID, channelID) + if err != nil { + return "", err + } + + // ValidateControllerMetadata will ensure the ICS27 protocol version has not changed and that the + // tx type and encoding are supported + if err := icatypes.ValidateControllerMetadata(ctx, k.channelKeeper, proposedConnectionHops, proposedMetadata); err != nil { + return "", errorsmod.Wrap(err, "invalid upgrade metadata") + } + + // the interchain account address on the host chain + // must remain the same after the upgrade. + if currentMetadata.Address != proposedMetadata.Address { + return "", errorsmod.Wrap(icatypes.ErrInvalidAccountAddress, "interchain account address cannot be changed") + } + + if currentMetadata.ControllerConnectionId != proposedMetadata.ControllerConnectionId { + return "", errorsmod.Wrap(connectiontypes.ErrInvalidConnection, "proposed controller connection ID must not change") + } + + if currentMetadata.HostConnectionId != proposedMetadata.HostConnectionId { + return "", errorsmod.Wrap(connectiontypes.ErrInvalidConnection, "proposed host connection ID must not change") + } + + return proposedversion, nil +} + +// OnChanUpgradeAck implements the ack setup of the channel upgrade handshake. +// The upgrade ack callback must verify the proposed changes to the channel version. +// Within the channel version we have the tx type, encoding, interchain account address, host/controller connectionID's +// and the ICS27 protocol version. +// +// The following may be changed: +// - tx type (must be supported) +// - encoding (must be supported) +// +// The following may not be changed: +// - controller connectionID +// - host connectionID +// - interchain account address +// - ICS27 protocol version +func (k Keeper) OnChanUpgradeAck(ctx sdk.Context, portID, channelID, counterpartyVersion string) error { + if strings.TrimSpace(counterpartyVersion) == "" { + return errorsmod.Wrap(channeltypes.ErrInvalidChannelVersion, "counterparty version cannot be empty") + } + + proposedMetadata, err := icatypes.MetadataFromVersion(counterpartyVersion) + if err != nil { + return err + } + + currentMetadata, err := k.getAppMetadata(ctx, portID, channelID) + if err != nil { + return err + } + + channel, found := k.channelKeeper.GetChannel(ctx, portID, channelID) + if !found { + return errorsmod.Wrapf(channeltypes.ErrChannelNotFound, "failed to retrieve channel %s on port %s", channelID, portID) + } + + // ValidateControllerMetadata will ensure the ICS27 protocol version has not changed and that the + // tx type and encoding are supported. Note, we pass in the current channel connection hops. The upgrade init + // step will verify that the proposed connection hops will not change. + if err := icatypes.ValidateControllerMetadata(ctx, k.channelKeeper, channel.ConnectionHops, proposedMetadata); err != nil { + return errorsmod.Wrap(err, "invalid upgrade metadata") + } + + // the interchain account address on the host chain + // must remain the same after the upgrade. + if currentMetadata.Address != proposedMetadata.Address { + return errorsmod.Wrap(icatypes.ErrInvalidAccountAddress, "address cannot be changed") + } + + if currentMetadata.ControllerConnectionId != proposedMetadata.ControllerConnectionId { + return errorsmod.Wrap(connectiontypes.ErrInvalidConnection, "proposed controller connection ID must not change") + } + + if currentMetadata.HostConnectionId != proposedMetadata.HostConnectionId { + return errorsmod.Wrap(connectiontypes.ErrInvalidConnection, "proposed host connection ID must not change") + } + + return nil +} diff --git a/modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go b/modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go new file mode 100644 index 0000000..6903f91 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/keeper/handshake_test.go @@ -0,0 +1,488 @@ +package keeper_test + +import ( + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *KeeperTestSuite) TestOnChanOpenInit() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + var ( + channel *channeltypes.Channel + path *ibctesting.Path + metadata icatypes.Metadata + expectedVersion string + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() {}, + nil, + }, + { + "success: previous active channel closed", + func() { + suite.chainA.GetSimApp().ICAControllerKeeper.SetActiveChannelID(suite.chainA.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + + counterparty := channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + channel := channeltypes.Channel{ + State: channeltypes.CLOSED, + Ordering: ordering, + Counterparty: counterparty, + ConnectionHops: []string{path.EndpointA.ConnectionID}, + Version: TestVersion, + } + + path.EndpointA.SetChannel(channel) + }, + nil, + }, + { + "success: empty channel version returns default metadata JSON string", + func() { + channel.Version = "" + expectedVersion = icatypes.NewDefaultMetadataString(path.EndpointA.ConnectionID, path.EndpointB.ConnectionID) + }, + nil, + }, + { + "success: channel reopening", + func() { + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + path.EndpointA.UpdateChannel(func(channel *channeltypes.Channel) { channel.State = channeltypes.CLOSED }) + path.EndpointB.UpdateChannel(func(channel *channeltypes.Channel) { channel.State = channeltypes.CLOSED }) + + path.EndpointA.ChannelID = "" + path.EndpointB.ChannelID = "" + }, + nil, + }, + { + "failure: different ordering from previous channel", + func() { + differentOrdering := channeltypes.UNORDERED + if ordering == channeltypes.UNORDERED { + differentOrdering = channeltypes.ORDERED + } + + suite.chainA.GetSimApp().ICAControllerKeeper.SetActiveChannelID(suite.chainA.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + + counterparty := channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + channel := channeltypes.Channel{ + State: channeltypes.CLOSED, + Ordering: differentOrdering, + Counterparty: counterparty, + ConnectionHops: []string{path.EndpointA.ConnectionID}, + Version: TestVersion, + } + + path.EndpointA.SetChannel(channel) + }, + channeltypes.ErrInvalidChannelOrdering, + }, + { + "invalid metadata - previous metadata is different", + func() { + // set active channel to closed + suite.chainA.GetSimApp().ICAControllerKeeper.SetActiveChannelID(suite.chainA.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + + // attempt to downgrade version by reinitializing channel with version 1, but setting channel to version 2 + metadata.Version = "ics27-2" + + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + + counterparty := channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + closedChannel := channeltypes.Channel{ + State: channeltypes.CLOSED, + Ordering: ordering, + Counterparty: counterparty, + ConnectionHops: []string{path.EndpointA.ConnectionID}, + Version: string(versionBytes), + } + path.EndpointA.SetChannel(closedChannel) + }, + icatypes.ErrInvalidVersion, + }, + { + "invalid port ID", + func() { + path.EndpointA.ChannelConfig.PortID = "invalid-port-id" //nolint:goconst + }, + icatypes.ErrInvalidControllerPort, + }, + { + "invalid counterparty port ID", + func() { + path.EndpointA.SetChannel(*channel) + channel.Counterparty.PortId = "invalid-port-id" //nolint:goconst + }, + icatypes.ErrInvalidHostPort, + }, + { + "invalid metadata bytestring", + func() { + path.EndpointA.SetChannel(*channel) + channel.Version = "invalid-metadata-bytestring" + }, + ibcerrors.ErrInvalidType, + }, + { + "unsupported encoding format", + func() { + metadata.Encoding = "invalid-encoding-format" + + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + + channel.Version = string(versionBytes) + path.EndpointA.SetChannel(*channel) + }, + icatypes.ErrInvalidCodec, + }, + { + "unsupported transaction type", + func() { + metadata.TxType = "invalid-tx-types" + + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + + channel.Version = string(versionBytes) + path.EndpointA.SetChannel(*channel) + }, + icatypes.ErrUnknownDataType, + }, + { + "connection not found", + func() { + channel.ConnectionHops = []string{ibctesting.InvalidID} + path.EndpointA.SetChannel(*channel) + }, + connectiontypes.ErrConnectionNotFound, + }, + { + "connection not found with default empty channel version", + func() { + channel.ConnectionHops = []string{"connection-10"} + channel.Version = "" + }, + connectiontypes.ErrConnectionNotFound, + }, + { + "invalid controller connection ID", + func() { + metadata.ControllerConnectionId = ibctesting.InvalidID + + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + + channel.Version = string(versionBytes) + path.EndpointA.SetChannel(*channel) + }, + connectiontypes.ErrInvalidConnection, + }, + { + "invalid host connection ID", + func() { + metadata.HostConnectionId = ibctesting.InvalidID + + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + + channel.Version = string(versionBytes) + path.EndpointA.SetChannel(*channel) + }, + connectiontypes.ErrInvalidConnection, + }, + { + "invalid version", + func() { + metadata.Version = "invalid-version" + + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + + channel.Version = string(versionBytes) + path.EndpointA.SetChannel(*channel) + }, + icatypes.ErrInvalidVersion, + }, + { + "channel is already active (OPEN state)", + func() { + suite.chainA.GetSimApp().ICAControllerKeeper.SetActiveChannelID(suite.chainA.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + + counterparty := channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + channel := channeltypes.Channel{ + State: channeltypes.OPEN, + Ordering: ordering, + Counterparty: counterparty, + ConnectionHops: []string{path.EndpointA.ConnectionID}, + Version: TestVersion, + } + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.SetChannel(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, channel) + }, + icatypes.ErrActiveChannelAlreadySet, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + // mock init interchain account + portID, err := icatypes.NewControllerPortID(TestOwnerAddress) + suite.Require().NoError(err) + + path.EndpointA.ChannelConfig.PortID = portID + + // default values + metadata = icatypes.NewMetadata(icatypes.Version, path.EndpointA.ConnectionID, path.EndpointB.ConnectionID, "", icatypes.EncodingProtobuf, icatypes.TxTypeSDKMultiMsg) + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + + expectedVersion = string(versionBytes) + + counterparty := channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + channel = &channeltypes.Channel{ + State: channeltypes.INIT, + Ordering: ordering, + Counterparty: counterparty, + ConnectionHops: []string{path.EndpointA.ConnectionID}, + Version: string(versionBytes), + } + + channelID := channeltypes.FormatChannelIdentifier(suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.GetNextChannelSequence(suite.chainA.GetContext())) + path.EndpointA.ChannelID = channelID + + tc.malleate() // malleate mutates test data + + version, err := suite.chainA.GetSimApp().ICAControllerKeeper.OnChanOpenInit(suite.chainA.GetContext(), channel.Ordering, channel.ConnectionHops, + path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, channel.Counterparty, channel.Version, + ) + + if tc.expError == nil { + suite.Require().NoError(err) + suite.Require().Equal(expectedVersion, version) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expError) + } + }) + } + } +} + +func (suite *KeeperTestSuite) TestOnChanOpenAck() { + var ( + path *ibctesting.Path + metadata icatypes.Metadata + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", func() {}, nil, + }, + { + "invalid port ID - host chain", + func() { + path.EndpointA.ChannelConfig.PortID = icatypes.HostPortID + }, + icatypes.ErrInvalidControllerPort, + }, + { + "invalid port ID - unexpected prefix", + func() { + path.EndpointA.ChannelConfig.PortID = "invalid-port-id" //nolint:goconst + }, + icatypes.ErrInvalidControllerPort, + }, + { + "invalid metadata bytestring", + func() { + path.EndpointA.Counterparty.ChannelConfig.Version = "invalid-metadata-bytestring" + }, + ibcerrors.ErrInvalidType, + }, + { + "unsupported encoding format", + func() { + metadata.Encoding = "invalid-encoding-format" + + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + + path.EndpointA.Counterparty.ChannelConfig.Version = string(versionBytes) + }, + icatypes.ErrInvalidCodec, + }, + { + "unsupported transaction type", + func() { + metadata.TxType = "invalid-tx-types" + + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + + path.EndpointA.Counterparty.ChannelConfig.Version = string(versionBytes) + }, + icatypes.ErrUnknownDataType, + }, + { + "invalid account address", + func() { + metadata.Address = "invalid-account-address" + + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + + path.EndpointA.Counterparty.ChannelConfig.Version = string(versionBytes) + }, + icatypes.ErrInvalidAccountAddress, + }, + { + "empty account address", + func() { + metadata.Address = "" + + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + + path.EndpointA.Counterparty.ChannelConfig.Version = string(versionBytes) + }, + icatypes.ErrInvalidAccountAddress, + }, + { + "invalid counterparty version", + func() { + metadata.Version = "invalid-version" + + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + + path.EndpointA.Counterparty.ChannelConfig.Version = string(versionBytes) + }, + icatypes.ErrInvalidVersion, + }, + { + "active channel already set", + func() { + // create a new channel and set it in state + ch := channeltypes.NewChannel(channeltypes.OPEN, channeltypes.ORDERED, channeltypes.NewCounterparty(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID), []string{path.EndpointB.ConnectionID}, ibctesting.DefaultChannelVersion) + suite.chainA.GetSimApp().GetIBCKeeper().ChannelKeeper.SetChannel(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, ch) + + // set the active channelID in state + suite.chainA.GetSimApp().ICAControllerKeeper.SetActiveChannelID(suite.chainA.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + }, icatypes.ErrActiveChannelAlreadySet, + }, + } + + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := RegisterInterchainAccount(path.EndpointA, TestOwnerAddress) + suite.Require().NoError(err) + + err = path.EndpointB.ChanOpenTry() + suite.Require().NoError(err) + + interchainAccAddr, exists := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointB.ConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(exists) + + metadata = icatypes.NewMetadata(icatypes.Version, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID, interchainAccAddr, icatypes.EncodingProtobuf, icatypes.TxTypeSDKMultiMsg) + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + + path.EndpointB.ChannelConfig.Version = string(versionBytes) + + tc.malleate() // malleate mutates test data + + err = suite.chainA.GetSimApp().ICAControllerKeeper.OnChanOpenAck(suite.chainA.GetContext(), + path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointA.Counterparty.ChannelConfig.Version, + ) + + if tc.expErr == nil { + suite.Require().NoError(err) + + activeChannelID, found := suite.chainA.GetSimApp().ICAControllerKeeper.GetActiveChannelID(suite.chainA.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + suite.Require().Equal(path.EndpointA.ChannelID, activeChannelID) + + interchainAccAddress, found := suite.chainA.GetSimApp().ICAControllerKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + suite.Require().Equal(metadata.Address, interchainAccAddress) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } + } +} + +func (suite *KeeperTestSuite) TestOnChanCloseConfirm() { + var path *ibctesting.Path + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", func() {}, nil, + }, + } + + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + tc.malleate() // malleate mutates test data + + err = suite.chainB.GetSimApp().ICAControllerKeeper.OnChanCloseConfirm(suite.chainB.GetContext(), + path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + + activeChannelID, found := suite.chainB.GetSimApp().ICAControllerKeeper.GetActiveChannelID(suite.chainB.GetContext(), ibctesting.FirstConnectionID, path.EndpointB.ChannelConfig.PortID) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().False(found) + suite.Require().Empty(activeChannelID) + } else { + suite.Require().Error(err) + } + }) + } + } +} diff --git a/modules/apps/27-interchain-accounts/controller/keeper/keeper.go b/modules/apps/27-interchain-accounts/controller/keeper/keeper.go new file mode 100644 index 0000000..01bda29 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/keeper/keeper.go @@ -0,0 +1,329 @@ +package keeper + +import ( + "bytes" + "errors" + "fmt" + "strings" + + corestore "cosmossdk.io/core/store" + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/log" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + genesistypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/genesis/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// Keeper defines the IBC interchain accounts controller keeper +type Keeper struct { + storeService corestore.KVStoreService + cdc codec.Codec + legacySubspace icatypes.ParamSubspace + ics4Wrapper porttypes.ICS4Wrapper + channelKeeper icatypes.ChannelKeeper + + msgRouter icatypes.MessageRouter + + // the address capable of executing a MsgUpdateParams message. Typically, this + // should be the x/gov module account. + authority string +} + +// NewKeeper creates a new interchain accounts controller Keeper instance +func NewKeeper( + cdc codec.Codec, storeService corestore.KVStoreService, legacySubspace icatypes.ParamSubspace, + ics4Wrapper porttypes.ICS4Wrapper, channelKeeper icatypes.ChannelKeeper, + msgRouter icatypes.MessageRouter, authority string, +) Keeper { + if strings.TrimSpace(authority) == "" { + panic(errors.New("authority must be non-empty")) + } + + return Keeper{ + storeService: storeService, + cdc: cdc, + legacySubspace: legacySubspace, + ics4Wrapper: ics4Wrapper, + channelKeeper: channelKeeper, + msgRouter: msgRouter, + authority: authority, + } +} + +// WithICS4Wrapper sets the ICS4Wrapper. This function may be used after +// the keepers creation to set the middleware which is above this module +// in the IBC application stack. +func (k *Keeper) WithICS4Wrapper(wrapper porttypes.ICS4Wrapper) { + k.ics4Wrapper = wrapper +} + +// GetICS4Wrapper returns the ICS4Wrapper. +func (k Keeper) GetICS4Wrapper() porttypes.ICS4Wrapper { + return k.ics4Wrapper +} + +// Logger returns the application logger, scoped to the associated module +func (Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", fmt.Sprintf("x/%s-%s", exported.ModuleName, icatypes.ModuleName)) +} + +// GetConnectionID returns the connection id for the given port and channelIDs. +func (k Keeper) GetConnectionID(ctx sdk.Context, portID, channelID string) (string, error) { + channel, found := k.channelKeeper.GetChannel(ctx, portID, channelID) + if !found { + return "", errorsmod.Wrapf(channeltypes.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID) + } + return channel.ConnectionHops[0], nil +} + +// GetAllPorts returns all ports to which the interchain accounts controller module is bound. Used in ExportGenesis +func (k Keeper) GetAllPorts(ctx sdk.Context) []string { + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + iterator := storetypes.KVStorePrefixIterator(store, []byte(icatypes.PortKeyPrefix)) + defer sdk.LogDeferred(k.Logger(ctx), func() error { return iterator.Close() }) + + var ports []string + for ; iterator.Valid(); iterator.Next() { + keySplit := strings.Split(string(iterator.Key()), "/") + + ports = append(ports, keySplit[1]) + } + + return ports +} + +// setPort sets the provided portID in state +func (k Keeper) setPort(ctx sdk.Context, portID string) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Set(icatypes.KeyPort(portID), []byte{0x01}); err != nil { + panic(err) + } +} + +// GetAppVersion calls the ICS4Wrapper GetAppVersion function. +func (k Keeper) GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) { + return k.ics4Wrapper.GetAppVersion(ctx, portID, channelID) +} + +// GetActiveChannelID retrieves the active channelID from the store, keyed by the provided connectionID and portID +func (k Keeper) GetActiveChannelID(ctx sdk.Context, connectionID, portID string) (string, bool) { + store := k.storeService.OpenKVStore(ctx) + key := icatypes.KeyActiveChannel(portID, connectionID) + + bz, err := store.Get(key) + if err != nil { + panic(err) + } + if len(bz) == 0 { + return "", false + } + + return string(bz), true +} + +// GetOpenActiveChannel retrieves the active channelID from the store, keyed by the provided connectionID and portID & checks if the channel in question is in state OPEN +func (k Keeper) GetOpenActiveChannel(ctx sdk.Context, connectionID, portID string) (string, bool) { + channelID, found := k.GetActiveChannelID(ctx, connectionID, portID) + if !found { + return "", false + } + + channel, found := k.channelKeeper.GetChannel(ctx, portID, channelID) + + if found && channel.State == channeltypes.OPEN { + return channelID, true + } + + return "", false +} + +// IsActiveChannelClosed retrieves the active channel from the store and returns true if the channel state is CLOSED, otherwise false +func (k Keeper) IsActiveChannelClosed(ctx sdk.Context, connectionID, portID string) bool { + channelID, found := k.GetActiveChannelID(ctx, connectionID, portID) + if !found { + return false + } + + channel, found := k.channelKeeper.GetChannel(ctx, portID, channelID) + return found && channel.State == channeltypes.CLOSED +} + +// GetAllActiveChannels returns a list of all active interchain accounts controller channels and their associated connection and port identifiers +func (k Keeper) GetAllActiveChannels(ctx sdk.Context) []genesistypes.ActiveChannel { + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + iterator := storetypes.KVStorePrefixIterator(store, []byte(icatypes.ActiveChannelKeyPrefix)) + defer sdk.LogDeferred(k.Logger(ctx), func() error { return iterator.Close() }) + + var activeChannels []genesistypes.ActiveChannel + for ; iterator.Valid(); iterator.Next() { + keySplit := strings.Split(string(iterator.Key()), "/") + + portID := keySplit[1] + connectionID := keySplit[2] + channelID := string(iterator.Value()) + + ch := genesistypes.ActiveChannel{ + ConnectionId: connectionID, + PortId: portID, + ChannelId: channelID, + IsMiddlewareEnabled: k.IsMiddlewareEnabled(ctx, portID, connectionID), + } + + activeChannels = append(activeChannels, ch) + } + + return activeChannels +} + +// SetActiveChannelID stores the active channelID, keyed by the provided connectionID and portID +func (k Keeper) SetActiveChannelID(ctx sdk.Context, connectionID, portID, channelID string) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Set(icatypes.KeyActiveChannel(portID, connectionID), []byte(channelID)); err != nil { + panic(err) + } +} + +// IsActiveChannel returns true if there exists an active channel for the provided connectionID and portID, otherwise false +func (k Keeper) IsActiveChannel(ctx sdk.Context, connectionID, portID string) bool { + _, ok := k.GetActiveChannelID(ctx, connectionID, portID) + return ok +} + +// GetInterchainAccountAddress retrieves the InterchainAccount address from the store associated with the provided connectionID and portID +func (k Keeper) GetInterchainAccountAddress(ctx sdk.Context, connectionID, portID string) (string, bool) { + store := k.storeService.OpenKVStore(ctx) + key := icatypes.KeyOwnerAccount(portID, connectionID) + + bz, err := store.Get(key) + if err != nil { + panic(err) + } + if len(bz) == 0 { + return "", false + } + + return string(bz), true +} + +// GetAllInterchainAccounts returns a list of all registered interchain account addresses and their associated connection and controller port identifiers +func (k Keeper) GetAllInterchainAccounts(ctx sdk.Context) []genesistypes.RegisteredInterchainAccount { + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + iterator := storetypes.KVStorePrefixIterator(store, []byte(icatypes.OwnerKeyPrefix)) + + var interchainAccounts []genesistypes.RegisteredInterchainAccount + for ; iterator.Valid(); iterator.Next() { + keySplit := strings.Split(string(iterator.Key()), "/") + + acc := genesistypes.RegisteredInterchainAccount{ + ConnectionId: keySplit[2], + PortId: keySplit[1], + AccountAddress: string(iterator.Value()), + } + + interchainAccounts = append(interchainAccounts, acc) + } + + return interchainAccounts +} + +// SetInterchainAccountAddress stores the InterchainAccount address, keyed by the associated connectionID and portID +func (k Keeper) SetInterchainAccountAddress(ctx sdk.Context, connectionID, portID, address string) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Set(icatypes.KeyOwnerAccount(portID, connectionID), []byte(address)); err != nil { + panic(err) + } +} + +// IsMiddlewareEnabled returns true if the underlying application callbacks are enabled for given port and connection identifier pair, otherwise false +func (k Keeper) IsMiddlewareEnabled(ctx sdk.Context, portID, connectionID string) bool { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(icatypes.KeyIsMiddlewareEnabled(portID, connectionID)) + if err != nil { + panic(err) + } + return bytes.Equal(icatypes.MiddlewareEnabled, bz) +} + +// IsMiddlewareDisabled returns true if the underlying application callbacks are disabled for the given port and connection identifier pair, otherwise false +func (k Keeper) IsMiddlewareDisabled(ctx sdk.Context, portID, connectionID string) bool { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(icatypes.KeyIsMiddlewareEnabled(portID, connectionID)) + if err != nil { + panic(err) + } + return bytes.Equal(icatypes.MiddlewareDisabled, bz) +} + +// SetMiddlewareEnabled stores a flag to indicate that the underlying application callbacks should be enabled for the given port and connection identifier pair +func (k Keeper) SetMiddlewareEnabled(ctx sdk.Context, portID, connectionID string) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Set(icatypes.KeyIsMiddlewareEnabled(portID, connectionID), icatypes.MiddlewareEnabled); err != nil { + panic(err) + } +} + +// SetMiddlewareDisabled stores a flag to indicate that the underlying application callbacks should be disabled for the given port and connection identifier pair +func (k Keeper) SetMiddlewareDisabled(ctx sdk.Context, portID, connectionID string) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Set(icatypes.KeyIsMiddlewareEnabled(portID, connectionID), icatypes.MiddlewareDisabled); err != nil { + panic(err) + } +} + +// DeleteMiddlewareEnabled deletes the middleware enabled flag stored in state +func (k Keeper) DeleteMiddlewareEnabled(ctx sdk.Context, portID, connectionID string) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Delete(icatypes.KeyIsMiddlewareEnabled(portID, connectionID)); err != nil { + panic(err) + } +} + +// GetAuthority returns the ica/controller submodule's authority. +func (k Keeper) GetAuthority() string { + return k.authority +} + +// getAppMetadata retrieves the interchain accounts channel metadata from the store associated with the provided portID and channelID +func (k Keeper) getAppMetadata(ctx sdk.Context, portID, channelID string) (icatypes.Metadata, error) { + appVersion, found := k.GetAppVersion(ctx, portID, channelID) + if !found { + return icatypes.Metadata{}, errorsmod.Wrapf(ibcerrors.ErrNotFound, "app version not found for port %s and channel %s", portID, channelID) + } + + return icatypes.MetadataFromVersion(appVersion) +} + +// GetParams returns the current ica/controller submodule parameters. +func (k Keeper) GetParams(ctx sdk.Context) types.Params { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get([]byte(types.ParamsKey)) + if err != nil { + panic(err) + } + if bz == nil { // only panic on unset params and not on empty params + panic(errors.New("ica/controller params are not set in store")) + } + + var params types.Params + k.cdc.MustUnmarshal(bz, ¶ms) + return params +} + +// SetParams sets the ica/controller submodule parameters. +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { + store := k.storeService.OpenKVStore(ctx) + bz := k.cdc.MustMarshal(¶ms) + if err := store.Set([]byte(types.ParamsKey), bz); err != nil { + panic(err) + } +} diff --git a/modules/apps/27-interchain-accounts/controller/keeper/keeper_test.go b/modules/apps/27-interchain-accounts/controller/keeper/keeper_test.go new file mode 100644 index 0000000..8307165 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/keeper/keeper_test.go @@ -0,0 +1,364 @@ +package keeper_test + +import ( + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + "github.com/cosmos/cosmos-sdk/runtime" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/keeper" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + genesistypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/genesis/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + channelkeeper "github.com/cosmos/ibc-go/v10/modules/core/04-channel/keeper" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +var ( + // TestOwnerAddress defines a reusable bech32 address for testing purposes + TestOwnerAddress = "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs" + + // TestPortID defines a reusable port identifier for testing purposes + TestPortID, _ = icatypes.NewControllerPortID(TestOwnerAddress) + + // TestVersion defines a reusable interchainaccounts version string for testing purposes + TestVersion = string(icatypes.ModuleCdc.MustMarshalJSON(&icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: ibctesting.FirstConnectionID, + HostConnectionId: ibctesting.FirstConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, + })) +) + +type KeeperTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains used for convenience and readability + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain + chainC *ibctesting.TestChain +} + +func (suite *KeeperTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 3) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) + suite.chainC = suite.coordinator.GetChain(ibctesting.GetChainID(3)) +} + +func NewICAPath(chainA, chainB *ibctesting.TestChain, ordering channeltypes.Order) *ibctesting.Path { + path := ibctesting.NewPath(chainA, chainB) + path.EndpointA.ChannelConfig.PortID = icatypes.HostPortID + path.EndpointB.ChannelConfig.PortID = icatypes.HostPortID + path.EndpointA.ChannelConfig.Order = ordering + path.EndpointB.ChannelConfig.Order = ordering + path.EndpointA.ChannelConfig.Version = TestVersion + path.EndpointB.ChannelConfig.Version = TestVersion + + return path +} + +// SetupICAPath invokes the InterchainAccounts entrypoint and subsequent channel handshake handlers +func SetupICAPath(path *ibctesting.Path, owner string) error { + if err := RegisterInterchainAccount(path.EndpointA, owner); err != nil { + return err + } + + if err := path.EndpointB.ChanOpenTry(); err != nil { + return err + } + + if err := path.EndpointA.ChanOpenAck(); err != nil { + return err + } + + return path.EndpointB.ChanOpenConfirm() +} + +// RegisterInterchainAccount is a helper function for starting the channel handshake +func RegisterInterchainAccount(endpoint *ibctesting.Endpoint, owner string) error { + portID, err := icatypes.NewControllerPortID(owner) + if err != nil { + return err + } + + channelSequence := endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextChannelSequence(endpoint.Chain.GetContext()) + + if err := endpoint.Chain.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(endpoint.Chain.GetContext(), endpoint.ConnectionID, owner, TestVersion, endpoint.ChannelConfig.Order); err != nil { + return err + } + + // commit state changes for proof verification + endpoint.Chain.NextBlock() + + // update port/channel ids + endpoint.ChannelID = channeltypes.FormatChannelIdentifier(channelSequence) + endpoint.ChannelConfig.PortID = portID + + return nil +} + +func TestKeeperTestSuite(t *testing.T) { + testifysuite.Run(t, new(KeeperTestSuite)) +} + +func (suite *KeeperTestSuite) TestNewKeeper() { + testCases := []struct { + name string + instantiateFn func() + errMsg string + }{ + {"success", func() { + keeper.NewKeeper( + suite.chainA.GetSimApp().AppCodec(), + runtime.NewKVStoreService(suite.chainA.GetSimApp().GetKey(types.StoreKey)), + suite.chainA.GetSimApp().GetSubspace(types.SubModuleName), + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, + suite.chainA.GetSimApp().MsgServiceRouter(), + suite.chainA.GetSimApp().ICAControllerKeeper.GetAuthority(), + ) + }, ""}, + {"failure: empty authority", func() { + keeper.NewKeeper( + suite.chainA.GetSimApp().AppCodec(), + runtime.NewKVStoreService(suite.chainA.GetSimApp().GetKey(types.StoreKey)), + suite.chainA.GetSimApp().GetSubspace(types.SubModuleName), + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, + suite.chainA.GetSimApp().MsgServiceRouter(), + "", // authority + ) + }, "authority must be non-empty"}, + } + + for _, tc := range testCases { + + suite.SetupTest() + + suite.Run(tc.name, func() { + if tc.errMsg == "" { + suite.Require().NotPanics( + tc.instantiateFn, + ) + } else { + suite.Require().PanicsWithError( + tc.errMsg, + tc.instantiateFn, + ) + } + }) + } +} + +func (suite *KeeperTestSuite) TestGetAllPorts() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + suite.SetupTest() + + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + expectedPorts := []string{TestPortID} + + ports := suite.chainA.GetSimApp().ICAControllerKeeper.GetAllPorts(suite.chainA.GetContext()) + suite.Require().Len(ports, len(expectedPorts)) + suite.Require().Equal(expectedPorts, ports) + } +} + +func (suite *KeeperTestSuite) TestGetInterchainAccountAddress() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + suite.SetupTest() + + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + counterpartyPortID := path.EndpointA.ChannelConfig.PortID + + retrievedAddr, found := suite.chainA.GetSimApp().ICAControllerKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), ibctesting.FirstConnectionID, counterpartyPortID) + suite.Require().True(found) + suite.Require().NotEmpty(retrievedAddr) + + retrievedAddr, found = suite.chainA.GetSimApp().ICAControllerKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), "invalid conn", "invalid port") + suite.Require().False(found) + suite.Require().Empty(retrievedAddr) + } +} + +func (suite *KeeperTestSuite) TestGetAllActiveChannels() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + var ( + expectedChannelID = "test-channel" + expectedPortID = "test-port" + ) + + suite.SetupTest() + + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + suite.chainA.GetSimApp().ICAControllerKeeper.SetActiveChannelID(suite.chainA.GetContext(), ibctesting.FirstConnectionID, expectedPortID, expectedChannelID) + + expectedChannels := []genesistypes.ActiveChannel{ + { + ConnectionId: ibctesting.FirstConnectionID, + PortId: TestPortID, + ChannelId: path.EndpointA.ChannelID, + IsMiddlewareEnabled: true, + }, + { + ConnectionId: ibctesting.FirstConnectionID, + PortId: expectedPortID, + ChannelId: expectedChannelID, + IsMiddlewareEnabled: false, + }, + } + + activeChannels := suite.chainA.GetSimApp().ICAControllerKeeper.GetAllActiveChannels(suite.chainA.GetContext()) + suite.Require().Len(activeChannels, len(expectedChannels)) + suite.Require().Equal(expectedChannels, activeChannels) + } +} + +func (suite *KeeperTestSuite) TestGetAllInterchainAccounts() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + var ( + expectedAccAddr = "test-acc-addr" + expectedPortID = "test-port" + ) + + suite.SetupTest() + + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + interchainAccAddr, exists := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointB.ConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(exists) + + suite.chainA.GetSimApp().ICAControllerKeeper.SetInterchainAccountAddress(suite.chainA.GetContext(), ibctesting.FirstConnectionID, expectedPortID, expectedAccAddr) + + expectedAccounts := []genesistypes.RegisteredInterchainAccount{ + { + ConnectionId: ibctesting.FirstConnectionID, + PortId: TestPortID, + AccountAddress: interchainAccAddr, + }, + { + ConnectionId: ibctesting.FirstConnectionID, + PortId: expectedPortID, + AccountAddress: expectedAccAddr, + }, + } + + interchainAccounts := suite.chainA.GetSimApp().ICAControllerKeeper.GetAllInterchainAccounts(suite.chainA.GetContext()) + suite.Require().Len(interchainAccounts, len(expectedAccounts)) + suite.Require().Equal(expectedAccounts, interchainAccounts) + } +} + +func (suite *KeeperTestSuite) TestIsActiveChannel() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + suite.SetupTest() + + path := NewICAPath(suite.chainA, suite.chainB, ordering) + owner := TestOwnerAddress + path.SetupConnections() + + err := SetupICAPath(path, owner) + suite.Require().NoError(err) + portID := path.EndpointA.ChannelConfig.PortID + + isActive := suite.chainA.GetSimApp().ICAControllerKeeper.IsActiveChannel(suite.chainA.GetContext(), ibctesting.FirstConnectionID, portID) + suite.Require().Equal(isActive, true) + } +} + +func (suite *KeeperTestSuite) TestSetInterchainAccountAddress() { + var ( + expectedAccAddr = "test-acc-addr" + expectedPortID = "test-port" + ) + + suite.chainA.GetSimApp().ICAControllerKeeper.SetInterchainAccountAddress(suite.chainA.GetContext(), ibctesting.FirstConnectionID, expectedPortID, expectedAccAddr) + + retrievedAddr, found := suite.chainA.GetSimApp().ICAControllerKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), ibctesting.FirstConnectionID, expectedPortID) + suite.Require().True(found) + suite.Require().Equal(expectedAccAddr, retrievedAddr) +} + +func (suite *KeeperTestSuite) TestSetAndGetParams() { + testCases := []struct { + name string + input types.Params + }{ + {"success: set params false", types.NewParams(false)}, + {"success: set params true", types.NewParams(true)}, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + ctx := suite.chainA.GetContext() + + suite.chainA.GetSimApp().ICAControllerKeeper.SetParams(ctx, tc.input) + expected := tc.input + p := suite.chainA.GetSimApp().ICAControllerKeeper.GetParams(ctx) + suite.Require().Equal(expected, p) + }) + } +} + +func (suite *KeeperTestSuite) TestUnsetParams() { + suite.SetupTest() + + ctx := suite.chainA.GetContext() + store := suite.chainA.GetContext().KVStore(suite.chainA.GetSimApp().GetKey(types.SubModuleName)) + store.Delete([]byte(types.ParamsKey)) + + suite.Require().Panics(func() { + suite.chainA.GetSimApp().ICAControllerKeeper.GetParams(ctx) + }) +} + +func (suite *KeeperTestSuite) TestGetAuthority() { + suite.SetupTest() + + authority := suite.chainA.GetSimApp().ICAControllerKeeper.GetAuthority() + expectedAuth := authtypes.NewModuleAddress(govtypes.ModuleName).String() + suite.Require().Equal(expectedAuth, authority) +} + +func (suite *KeeperTestSuite) TestWithICS4Wrapper() { + suite.SetupTest() + + // test if the ics4 wrapper is the channel keeper initially + ics4Wrapper := suite.chainA.GetSimApp().ICAControllerKeeper.GetICS4Wrapper() + + _, isChannelKeeper := ics4Wrapper.(*channelkeeper.Keeper) + suite.Require().True(isChannelKeeper) + suite.Require().IsType((*channelkeeper.Keeper)(nil), ics4Wrapper) + + // set the ics4 wrapper to the channel keeper + suite.chainA.GetSimApp().ICAControllerKeeper.WithICS4Wrapper(nil) + ics4Wrapper = suite.chainA.GetSimApp().ICAControllerKeeper.GetICS4Wrapper() + suite.Require().Nil(ics4Wrapper) +} diff --git a/modules/apps/27-interchain-accounts/controller/keeper/migrations.go b/modules/apps/27-interchain-accounts/controller/keeper/migrations.go new file mode 100644 index 0000000..e256859 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/keeper/migrations.go @@ -0,0 +1,32 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + controllertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" +) + +// Migrator is a struct for handling in-place store migrations. +type Migrator struct { + keeper *Keeper +} + +// NewMigrator returns Migrator instance for the state migration. +func NewMigrator(k *Keeper) Migrator { + return Migrator{ + keeper: k, + } +} + +// MigrateParams migrates the controller submodule's parameters from the x/params to self store. +func (m Migrator) MigrateParams(ctx sdk.Context) error { + if m.keeper != nil { + params := controllertypes.DefaultParams() + if m.keeper.legacySubspace != nil { + m.keeper.legacySubspace.GetParamSetIfExists(ctx, ¶ms) + } + m.keeper.SetParams(ctx, params) + m.keeper.Logger(ctx).Info("successfully migrated ica/controller submodule to self-manage params") + } + return nil +} diff --git a/modules/apps/27-interchain-accounts/controller/keeper/migrations_test.go b/modules/apps/27-interchain-accounts/controller/keeper/migrations_test.go new file mode 100644 index 0000000..7b706b8 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/keeper/migrations_test.go @@ -0,0 +1,58 @@ +package keeper_test + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/runtime" + + icacontrollerkeeper "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/keeper" + icacontrollertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" +) + +func (suite *KeeperTestSuite) TestMigratorMigrateParams() { + testCases := []struct { + msg string + malleate func() + expectedParams icacontrollertypes.Params + }{ + { + "success: default params", + func() { + params := icacontrollertypes.DefaultParams() + subspace := suite.chainA.GetSimApp().GetSubspace(icacontrollertypes.SubModuleName) // get subspace + subspace.SetParamSet(suite.chainA.GetContext(), ¶ms) // set params + }, + icacontrollertypes.DefaultParams(), + }, + { + "success: no legacy params pre-migration", + func() { + suite.chainA.GetSimApp().ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + suite.chainA.Codec, + runtime.NewKVStoreService(suite.chainA.GetSimApp().GetKey(icacontrollertypes.StoreKey)), + nil, // assign a nil legacy param subspace + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, + suite.chainA.GetSimApp().MsgServiceRouter(), + suite.chainA.GetSimApp().ICAControllerKeeper.GetAuthority(), + ) + }, + icacontrollertypes.DefaultParams(), + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() // explicitly set params + + migrator := icacontrollerkeeper.NewMigrator(&suite.chainA.GetSimApp().ICAControllerKeeper) + err := migrator.MigrateParams(suite.chainA.GetContext()) + suite.Require().NoError(err) + + params := suite.chainA.GetSimApp().ICAControllerKeeper.GetParams(suite.chainA.GetContext()) + suite.Require().Equal(tc.expectedParams, params) + }) + } +} diff --git a/modules/apps/27-interchain-accounts/controller/keeper/msg_server.go b/modules/apps/27-interchain-accounts/controller/keeper/msg_server.go new file mode 100644 index 0000000..1b1b277 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/keeper/msg_server.go @@ -0,0 +1,93 @@ +package keeper + +import ( + "context" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +var _ types.MsgServer = (*msgServer)(nil) + +type msgServer struct { + *Keeper +} + +// NewMsgServerImpl returns an implementation of the ICS27 MsgServer interface +// for the provided Keeper. +func NewMsgServerImpl(keeper *Keeper) types.MsgServer { + return &msgServer{Keeper: keeper} +} + +// RegisterInterchainAccount defines a rpc handler for MsgRegisterInterchainAccount +func (s msgServer) RegisterInterchainAccount(goCtx context.Context, msg *types.MsgRegisterInterchainAccount) (*types.MsgRegisterInterchainAccountResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + portID, err := icatypes.NewControllerPortID(msg.Owner) + if err != nil { + return nil, err + } + + if s.IsMiddlewareEnabled(ctx, portID, msg.ConnectionId) && !s.IsActiveChannelClosed(ctx, msg.ConnectionId, portID) { + return nil, errorsmod.Wrap(icatypes.ErrInvalidChannelFlow, "channel is already active or a handshake is in flight") + } + + s.SetMiddlewareDisabled(ctx, portID, msg.ConnectionId) + + // use ORDER_UNORDERED as default in case msg's ordering is NONE + order := msg.Ordering + if order == channeltypes.NONE { + order = channeltypes.UNORDERED + } + + channelID, err := s.registerInterchainAccount(ctx, msg.ConnectionId, portID, msg.Version, order) + if err != nil { + s.Logger(ctx).Error("error registering interchain account", "error", err.Error()) + return nil, err + } + + s.Logger(ctx).Info("successfully registered interchain account", "channel-id", channelID) + + return &types.MsgRegisterInterchainAccountResponse{ + ChannelId: channelID, + PortId: portID, + }, nil +} + +// SendTx defines a rpc handler for MsgSendTx +func (s msgServer) SendTx(goCtx context.Context, msg *types.MsgSendTx) (*types.MsgSendTxResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + portID, err := icatypes.NewControllerPortID(msg.Owner) + if err != nil { + return nil, err + } + + // the absolute timeout value is calculated using the controller chain block time + the relative timeout value + // this assumes time synchrony to a certain degree between the controller and counterparty host chain + absoluteTimeout := uint64(ctx.BlockTime().UnixNano()) + msg.RelativeTimeout + seq, err := s.sendTx(ctx, msg.ConnectionId, portID, msg.PacketData, absoluteTimeout) + if err != nil { + return nil, err + } + + return &types.MsgSendTxResponse{Sequence: seq}, nil +} + +// UpdateParams defines an rpc handler method for MsgUpdateParams. Updates the ica/controller submodule's parameters. +func (k Keeper) UpdateParams(goCtx context.Context, msg *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { + if k.GetAuthority() != msg.Signer { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "expected %s, got %s", k.GetAuthority(), msg.Signer) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + k.SetParams(ctx, msg.Params) + + return &types.MsgUpdateParamsResponse{}, nil +} diff --git a/modules/apps/27-interchain-accounts/controller/keeper/msg_server_test.go b/modules/apps/27-interchain-accounts/controller/keeper/msg_server_test.go new file mode 100644 index 0000000..4138314 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/keeper/msg_server_test.go @@ -0,0 +1,252 @@ +package keeper_test + +import ( + "time" + + "github.com/cosmos/gogoproto/proto" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/keeper" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *KeeperTestSuite) TestRegisterInterchainAccount_MsgServer() { + var ( + msg *types.MsgRegisterInterchainAccount + expectedOrderding channeltypes.Order + expectedChannelID = "channel-0" + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() {}, + nil, + }, + { + "success: ordering falls back to UNORDERED if not specified", + func() { + msg.Ordering = channeltypes.NONE + expectedOrderding = channeltypes.UNORDERED + }, + nil, + }, + { + "success: non-empty owner address is valid", + func() { + msg.Owner = "" + }, + nil, + }, + { + "invalid connection id", + func() { + msg.ConnectionId = "connection-100" + }, + connectiontypes.ErrConnectionNotFound, + }, + { + "empty address invalid", + func() { + msg.Owner = "" + }, + icatypes.ErrInvalidAccountAddress, + }, + } + + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + for _, tc := range testCases { + suite.Run(tc.name, func() { + expectedOrderding = ordering + + suite.SetupTest() + + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + msg = types.NewMsgRegisterInterchainAccount(ibctesting.FirstConnectionID, ibctesting.TestAccAddress, "", ordering) + + tc.malleate() + + ctx := suite.chainA.GetContext() + msgServer := keeper.NewMsgServerImpl(&suite.chainA.GetSimApp().ICAControllerKeeper) + res, err := msgServer.RegisterInterchainAccount(ctx, msg) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expectedChannelID, res.ChannelId) + + events := ctx.EventManager().Events() + suite.Require().Len(events, 2) + suite.Require().Equal(events[0].Type, channeltypes.EventTypeChannelOpenInit) + suite.Require().Equal(events[1].Type, sdk.EventTypeMessage) + + path.EndpointA.ChannelConfig.PortID = res.PortId + path.EndpointA.ChannelID = res.ChannelId + channel := path.EndpointA.GetChannel() + suite.Require().Equal(expectedOrderding, channel.Ordering) + } else { + suite.Require().ErrorIs(err, tc.expErr) + suite.Require().Nil(res) + } + }) + } + } +} + +func (suite *KeeperTestSuite) TestSubmitTx() { + var ( + path *ibctesting.Path + msg *types.MsgSendTx + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", func() { + }, + nil, + }, + { + "failure - owner address is empty", func() { + msg.Owner = "" + }, + icatypes.ErrInvalidAccountAddress, + }, + { + "failure - active channel does not exist for connection ID", func() { + msg.Owner = TestOwnerAddress + msg.ConnectionId = "connection-100" + }, + icatypes.ErrActiveChannelNotFound, + }, + { + "failure - active channel does not exist for port ID", func() { + msg.Owner = "invalid-owner" + }, + icatypes.ErrActiveChannelNotFound, + }, + } + + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + owner := TestOwnerAddress + path = NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := SetupICAPath(path, owner) + suite.Require().NoError(err) + + portID, err := icatypes.NewControllerPortID(TestOwnerAddress) + suite.Require().NoError(err) + + // get the address of the interchain account stored in state during handshake step + interchainAccountAddr, found := suite.chainA.GetSimApp().ICAControllerKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), path.EndpointA.ConnectionID, portID) + suite.Require().True(found) + + // create bank transfer message that will execute on the host chain + icaMsg := &banktypes.MsgSend{ + FromAddress: interchainAccountAddr, + ToAddress: suite.chainB.SenderAccount.GetAddress().String(), + Amount: sdk.NewCoins(ibctesting.TestCoin), + } + + data, err := icatypes.SerializeCosmosTx(suite.chainA.GetSimApp().AppCodec(), []proto.Message{icaMsg}, icatypes.EncodingProtobuf) + suite.Require().NoError(err) + + packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + Memo: "memo", + } + + timeoutTimestamp := uint64(suite.chainA.GetContext().BlockTime().Add(time.Minute).UnixNano()) + connectionID := path.EndpointA.ConnectionID + + msg = types.NewMsgSendTx(owner, connectionID, timeoutTimestamp, packetData) + + tc.malleate() // malleate mutates test data + + ctx := suite.chainA.GetContext() + msgServer := keeper.NewMsgServerImpl(&suite.chainA.GetSimApp().ICAControllerKeeper) + res, err := msgServer.SendTx(ctx, msg) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + } else { + suite.Require().ErrorIs(err, tc.expErr) + suite.Require().Nil(res) + } + }) + } + } +} + +// TestUpdateParams tests UpdateParams rpc handler +func (suite *KeeperTestSuite) TestUpdateParams() { + signer := suite.chainA.GetSimApp().TransferKeeper.GetAuthority() + testCases := []struct { + name string + msg *types.MsgUpdateParams + expErr error + }{ + { + "success: valid signer and default params", + types.NewMsgUpdateParams(signer, types.NewParams(!types.DefaultControllerEnabled)), + nil, + }, + { + "failure: malformed signer address", + types.NewMsgUpdateParams(ibctesting.InvalidID, types.DefaultParams()), + ibcerrors.ErrUnauthorized, + }, + { + "failure: empty signer address", + types.NewMsgUpdateParams("", types.DefaultParams()), + ibcerrors.ErrUnauthorized, + }, + { + "failure: whitespace signer address", + types.NewMsgUpdateParams(" ", types.DefaultParams()), + ibcerrors.ErrUnauthorized, + }, + { + "failure: unauthorized signer address", + types.NewMsgUpdateParams(ibctesting.TestAccAddress, types.DefaultParams()), + ibcerrors.ErrUnauthorized, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + _, err := suite.chainA.GetSimApp().ICAControllerKeeper.UpdateParams(suite.chainA.GetContext(), tc.msg) + if tc.expErr == nil { + suite.Require().NoError(err) + p := suite.chainA.GetSimApp().ICAControllerKeeper.GetParams(suite.chainA.GetContext()) + suite.Require().Equal(tc.msg.Params, p) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} diff --git a/modules/apps/27-interchain-accounts/controller/keeper/relay.go b/modules/apps/27-interchain-accounts/controller/keeper/relay.go new file mode 100644 index 0000000..a73b887 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/keeper/relay.go @@ -0,0 +1,60 @@ +package keeper + +import ( + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" +) + +// SendTx takes pre-built packet data containing messages to be executed on the host chain from an authentication module and attempts to send the packet. +// The packet sequence for the outgoing packet is returned as a result. An appropriate +// absolute timeoutTimestamp must be provided. If the packet is timed out, the channel will be closed. +// In the case of channel closure, a new channel may be reopened to reconnect to the host chain. +// +// Deprecated: this is a legacy API that is only intended to function correctly in workflows where an underlying application has been set. +// Prior to v6.x.x of ibc-go, the controller module was only functional as middleware, with authentication performed +// by the underlying application. For a full summary of the changes in v6.x.x, please see ADR009. +// This API will be removed in later releases. +func (k Keeper) SendTx(ctx sdk.Context, connectionID, portID string, + icaPacketData icatypes.InterchainAccountPacketData, timeoutTimestamp uint64, +) (uint64, error) { + return k.sendTx(ctx, connectionID, portID, icaPacketData, timeoutTimestamp) +} + +func (k Keeper) sendTx(ctx sdk.Context, connectionID, portID string, + icaPacketData icatypes.InterchainAccountPacketData, timeoutTimestamp uint64, +) (uint64, error) { + if !k.GetParams(ctx).ControllerEnabled { + return 0, types.ErrControllerSubModuleDisabled + } + + activeChannelID, found := k.GetOpenActiveChannel(ctx, connectionID, portID) + if !found { + return 0, errorsmod.Wrapf(icatypes.ErrActiveChannelNotFound, "failed to retrieve active channel on connection %s for port %s", connectionID, portID) + } + if uint64(ctx.BlockTime().UnixNano()) >= timeoutTimestamp { + return 0, icatypes.ErrInvalidTimeoutTimestamp + } + + if err := icaPacketData.ValidateBasic(); err != nil { + return 0, errorsmod.Wrap(err, "invalid interchain account packet data") + } + + sequence, err := k.ics4Wrapper.SendPacket(ctx, portID, activeChannelID, clienttypes.ZeroHeight(), timeoutTimestamp, icaPacketData.GetBytes()) + if err != nil { + return 0, err + } + + return sequence, nil +} + +// OnTimeoutPacket removes the active channel associated with the provided packet, the underlying channel end is closed +// due to the semantics of ORDERED channels +func (Keeper) OnTimeoutPacket(ctx sdk.Context, packet channeltypes.Packet) error { + return nil +} diff --git a/modules/apps/27-interchain-accounts/controller/keeper/relay_test.go b/modules/apps/27-interchain-accounts/controller/keeper/relay_test.go new file mode 100644 index 0000000..7756a81 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/keeper/relay_test.go @@ -0,0 +1,219 @@ +package keeper_test + +import ( + "github.com/cosmos/gogoproto/proto" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *KeeperTestSuite) TestSendTx() { + var ( + path *ibctesting.Path + packetData icatypes.InterchainAccountPacketData + timeoutTimestamp uint64 + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "success", + func() { + interchainAccountAddr, found := suite.chainA.GetSimApp().ICAControllerKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + msg := &banktypes.MsgSend{ + FromAddress: interchainAccountAddr, + ToAddress: suite.chainB.SenderAccount.GetAddress().String(), + Amount: sdk.NewCoins(ibctesting.TestCoin), + } + + data, err := icatypes.SerializeCosmosTx(suite.chainB.GetSimApp().AppCodec(), []proto.Message{msg}, icatypes.EncodingProtobuf) + suite.Require().NoError(err) + + packetData = icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + } + }, + nil, + }, + { + "success with multiple sdk.Msg", + func() { + interchainAccountAddr, found := suite.chainA.GetSimApp().ICAControllerKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + msgsBankSend := []proto.Message{ + &banktypes.MsgSend{ + FromAddress: interchainAccountAddr, + ToAddress: suite.chainB.SenderAccount.GetAddress().String(), + Amount: sdk.NewCoins(ibctesting.TestCoin), + }, + &banktypes.MsgSend{ + FromAddress: interchainAccountAddr, + ToAddress: suite.chainB.SenderAccount.GetAddress().String(), + Amount: sdk.NewCoins(ibctesting.TestCoin), + }, + } + + data, err := icatypes.SerializeCosmosTx(suite.chainB.GetSimApp().AppCodec(), msgsBankSend, icatypes.EncodingProtobuf) + suite.Require().NoError(err) + + packetData = icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + } + }, + nil, + }, + { + "data is nil", + func() { + packetData = icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: nil, + } + }, + icatypes.ErrInvalidOutgoingData, + }, + { + "active channel not found", + func() { + path.EndpointA.ChannelConfig.PortID = "invalid-port-id" //nolint:goconst + }, + icatypes.ErrActiveChannelNotFound, + }, + { + "channel in INIT state - optimistic packet sends fail", + func() { + path.EndpointA.UpdateChannel(func(channel *channeltypes.Channel) { channel.State = channeltypes.INIT }) + }, + icatypes.ErrActiveChannelNotFound, + }, + { + "sendPacket fails - channel closed", + func() { + path.EndpointA.UpdateChannel(func(channel *channeltypes.Channel) { channel.State = channeltypes.CLOSED }) + }, + icatypes.ErrActiveChannelNotFound, + }, + { + "controller submodule disabled", + func() { + suite.chainA.GetSimApp().ICAControllerKeeper.SetParams(suite.chainA.GetContext(), types.NewParams(false)) + }, + types.ErrControllerSubModuleDisabled, + }, + { + "timeout timestamp is not in the future", + func() { + interchainAccountAddr, found := suite.chainA.GetSimApp().ICAControllerKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + msg := &banktypes.MsgSend{ + FromAddress: interchainAccountAddr, + ToAddress: suite.chainB.SenderAccount.GetAddress().String(), + Amount: sdk.NewCoins(ibctesting.TestCoin), + } + + data, err := icatypes.SerializeCosmosTx(suite.chainB.GetSimApp().AppCodec(), []proto.Message{msg}, icatypes.EncodingProtobuf) + suite.Require().NoError(err) + + packetData = icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + } + + timeoutTimestamp = uint64(suite.chainA.GetContext().BlockTime().UnixNano()) + }, + icatypes.ErrInvalidTimeoutTimestamp, + }, + } + + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + for _, tc := range testCases { + suite.Run(tc.msg, func() { + suite.SetupTest() // reset + timeoutTimestamp = ^uint64(0) // default + + path = NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + tc.malleate() // malleate mutates test data + + // nolint: staticcheck // SA1019: ibctesting.FirstConnectionID is deprecated: use path.EndpointA.ConnectionID instead. (staticcheck) + _, err = suite.chainA.GetSimApp().ICAControllerKeeper.SendTx(suite.chainA.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID, packetData, timeoutTimestamp) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } + } +} + +func (suite *KeeperTestSuite) TestOnTimeoutPacket() { + var path *ibctesting.Path + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "success", + func() {}, + nil, + }, + } + + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + for _, tc := range testCases { + suite.Run(tc.msg, func() { + suite.SetupTest() // reset + + path = NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + tc.malleate() // malleate mutates test data + + packet := channeltypes.NewPacket( + []byte{}, + 1, + path.EndpointA.ChannelConfig.PortID, + path.EndpointA.ChannelID, + path.EndpointB.ChannelConfig.PortID, + path.EndpointB.ChannelID, + clienttypes.NewHeight(0, 100), + 0, + ) + + err = suite.chainA.GetSimApp().ICAControllerKeeper.OnTimeoutPacket(suite.chainA.GetContext(), packet) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } + } +} diff --git a/modules/apps/27-interchain-accounts/controller/types/codec.go b/modules/apps/27-interchain-accounts/controller/types/codec.go new file mode 100644 index 0000000..ce0ce66 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/types/codec.go @@ -0,0 +1,18 @@ +package types + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/msgservice" +) + +// RegisterInterfaces registers the interchain accounts controller message types using the provided InterfaceRegistry +func RegisterInterfaces(registry codectypes.InterfaceRegistry) { + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgRegisterInterchainAccount{}, + &MsgSendTx{}, + &MsgUpdateParams{}, + ) + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} diff --git a/modules/apps/27-interchain-accounts/controller/types/codec_test.go b/modules/apps/27-interchain-accounts/controller/types/codec_test.go new file mode 100644 index 0000000..09ddf00 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/types/codec_test.go @@ -0,0 +1,61 @@ +package types_test + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + + ica "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" +) + +func TestCodecTypeRegistration(t *testing.T) { + testCases := []struct { + name string + typeURL string + expErr error + }{ + { + "success: MsgRegisterInterchainAccount", + sdk.MsgTypeURL(&types.MsgRegisterInterchainAccount{}), + nil, + }, + { + "success: MsgSendTx", + sdk.MsgTypeURL(&types.MsgSendTx{}), + nil, + }, + { + "success: MsgUpdateParams", + sdk.MsgTypeURL(&types.MsgUpdateParams{}), + nil, + }, + { + "type not registered on codec", + "ibc.invalid.MsgTypeURL", + errors.New("unable to resolve type URL"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + encodingCfg := moduletestutil.MakeTestEncodingConfig(ica.AppModuleBasic{}) + msg, err := encodingCfg.Codec.InterfaceRegistry().Resolve(tc.typeURL) + + fmt.Printf("%+v\n", err) + + if tc.expErr == nil { + require.NotNil(t, msg) + require.NoError(t, err) + } else { + require.Nil(t, msg) + require.ErrorContains(t, err, tc.expErr.Error()) + } + }) + } +} diff --git a/modules/apps/27-interchain-accounts/controller/types/controller.pb.go b/modules/apps/27-interchain-accounts/controller/types/controller.pb.go new file mode 100644 index 0000000..a614114 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/types/controller.pb.go @@ -0,0 +1,313 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/applications/interchain_accounts/controller/v1/controller.proto + +package types + +import ( + fmt "fmt" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Params defines the set of on-chain interchain accounts parameters. +// The following parameters may be used to disable the controller submodule. +type Params struct { + // controller_enabled enables or disables the controller submodule. + ControllerEnabled bool `protobuf:"varint,1,opt,name=controller_enabled,json=controllerEnabled,proto3" json:"controller_enabled,omitempty"` +} + +func (m *Params) Reset() { *m = Params{} } +func (m *Params) String() string { return proto.CompactTextString(m) } +func (*Params) ProtoMessage() {} +func (*Params) Descriptor() ([]byte, []int) { + return fileDescriptor_177fd0fec5eb3400, []int{0} +} +func (m *Params) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Params) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params.Merge(m, src) +} +func (m *Params) XXX_Size() int { + return m.Size() +} +func (m *Params) XXX_DiscardUnknown() { + xxx_messageInfo_Params.DiscardUnknown(m) +} + +var xxx_messageInfo_Params proto.InternalMessageInfo + +func (m *Params) GetControllerEnabled() bool { + if m != nil { + return m.ControllerEnabled + } + return false +} + +func init() { + proto.RegisterType((*Params)(nil), "ibc.applications.interchain_accounts.controller.v1.Params") +} + +func init() { + proto.RegisterFile("ibc/applications/interchain_accounts/controller/v1/controller.proto", fileDescriptor_177fd0fec5eb3400) +} + +var fileDescriptor_177fd0fec5eb3400 = []byte{ + // 221 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x8f, 0xb1, 0x4e, 0xc3, 0x30, + 0x10, 0x86, 0xe3, 0xa5, 0x42, 0xd9, 0xc8, 0xc4, 0x64, 0x21, 0x26, 0x96, 0xf8, 0x48, 0x19, 0xba, + 0x83, 0xd8, 0x81, 0x91, 0xa5, 0xb2, 0xaf, 0x56, 0x7b, 0xc2, 0xf1, 0x45, 0x3e, 0x27, 0x12, 0x6f, + 0xc1, 0x63, 0x31, 0x76, 0x64, 0x44, 0xc9, 0x8b, 0x20, 0xe8, 0x90, 0x0c, 0x1d, 0x4f, 0x9f, 0xee, + 0xd3, 0xff, 0x95, 0x8f, 0xe4, 0x10, 0x6c, 0xd7, 0x05, 0x42, 0x9b, 0x89, 0xa3, 0x00, 0xc5, 0xec, + 0x13, 0x1e, 0x2c, 0xc5, 0xad, 0x45, 0xe4, 0x3e, 0x66, 0x01, 0xe4, 0x98, 0x13, 0x87, 0xe0, 0x13, + 0x0c, 0xcd, 0xe2, 0x32, 0x5d, 0xe2, 0xcc, 0xd5, 0x9a, 0x1c, 0x9a, 0xa5, 0xc4, 0x9c, 0x91, 0x98, + 0xc5, 0xdb, 0xd0, 0xdc, 0x6c, 0xca, 0xd5, 0xb3, 0x4d, 0xb6, 0x95, 0xaa, 0x2e, 0xab, 0x19, 0x6d, + 0x7d, 0xb4, 0x2e, 0xf8, 0xdd, 0x95, 0xba, 0x56, 0xb7, 0x17, 0xaf, 0x97, 0x33, 0x79, 0x3a, 0x81, + 0x87, 0xf7, 0xaf, 0x51, 0xab, 0xe3, 0xa8, 0xd5, 0xcf, 0xa8, 0xd5, 0xe7, 0xa4, 0x8b, 0xe3, 0xa4, + 0x8b, 0xef, 0x49, 0x17, 0x6f, 0x2f, 0x7b, 0xca, 0x87, 0xde, 0x19, 0xe4, 0x16, 0x90, 0xa5, 0x65, + 0x01, 0x72, 0x58, 0xef, 0x19, 0x86, 0xe6, 0x0e, 0x5a, 0xde, 0xf5, 0xc1, 0xcb, 0x5f, 0xac, 0xc0, + 0x7a, 0x53, 0xcf, 0x13, 0xeb, 0x73, 0x9d, 0xf9, 0xa3, 0xf3, 0xe2, 0x56, 0xff, 0x81, 0xf7, 0xbf, + 0x01, 0x00, 0x00, 0xff, 0xff, 0x68, 0x3d, 0xfa, 0x26, 0x27, 0x01, 0x00, 0x00, +} + +func (m *Params) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Params) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.ControllerEnabled { + i-- + if m.ControllerEnabled { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintController(dAtA []byte, offset int, v uint64) int { + offset -= sovController(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ControllerEnabled { + n += 2 + } + return n +} + +func sovController(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozController(x uint64) (n int) { + return sovController(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Params) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowController + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Params: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ControllerEnabled", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowController + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.ControllerEnabled = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipController(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthController + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipController(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowController + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowController + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowController + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthController + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupController + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthController + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthController = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowController = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupController = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/apps/27-interchain-accounts/controller/types/errors.go b/modules/apps/27-interchain-accounts/controller/types/errors.go new file mode 100644 index 0000000..dfc1f69 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/types/errors.go @@ -0,0 +1,10 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" +) + +// ICA Controller sentinel errors +var ( + ErrControllerSubModuleDisabled = errorsmod.Register(SubModuleName, 2, "controller submodule is disabled") +) diff --git a/modules/apps/27-interchain-accounts/controller/types/keys.go b/modules/apps/27-interchain-accounts/controller/types/keys.go new file mode 100644 index 0000000..934d70f --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/types/keys.go @@ -0,0 +1,12 @@ +package types + +const ( + // SubModuleName defines the interchain accounts controller module name + SubModuleName = "icacontroller" + + // StoreKey is the store key string for the interchain accounts controller module + StoreKey = SubModuleName + + // ParamsKey is the store key for the interchain accounts controller parameters + ParamsKey = "params" +) diff --git a/modules/apps/27-interchain-accounts/controller/types/msgs.go b/modules/apps/27-interchain-accounts/controller/types/msgs.go new file mode 100644 index 0000000..804fed5 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/types/msgs.go @@ -0,0 +1,111 @@ +package types + +import ( + "slices" + "strings" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +const MaximumOwnerLength = 2048 // maximum length of the owner in bytes (value chosen arbitrarily) + +var ( + _ sdk.Msg = (*MsgRegisterInterchainAccount)(nil) + _ sdk.Msg = (*MsgSendTx)(nil) + _ sdk.Msg = (*MsgUpdateParams)(nil) + + _ sdk.HasValidateBasic = (*MsgRegisterInterchainAccount)(nil) + _ sdk.HasValidateBasic = (*MsgSendTx)(nil) + _ sdk.HasValidateBasic = (*MsgUpdateParams)(nil) +) + +// NewMsgRegisterInterchainAccount creates a new instance of MsgRegisterInterchainAccount +func NewMsgRegisterInterchainAccount(connectionID, owner, version string, ordering channeltypes.Order) *MsgRegisterInterchainAccount { + return &MsgRegisterInterchainAccount{ + ConnectionId: connectionID, + Owner: owner, + Version: version, + Ordering: ordering, + } +} + +// ValidateBasic implements sdk.Msg +func (msg MsgRegisterInterchainAccount) ValidateBasic() error { + if err := host.ConnectionIdentifierValidator(msg.ConnectionId); err != nil { + return errorsmod.Wrap(err, "invalid connection ID") + } + + if !slices.Contains([]channeltypes.Order{channeltypes.ORDERED, channeltypes.UNORDERED}, msg.Ordering) { + return errorsmod.Wrap(channeltypes.ErrInvalidChannelOrdering, msg.Ordering.String()) + } + + if strings.TrimSpace(msg.Owner) == "" { + return errorsmod.Wrap(ibcerrors.ErrInvalidAddress, "owner address cannot be empty") + } + + if len(msg.Owner) > MaximumOwnerLength { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "owner address must not exceed %d bytes", MaximumOwnerLength) + } + + return nil +} + +// NewMsgSendTx creates a new instance of MsgSendTx +func NewMsgSendTx(owner, connectionID string, relativeTimeoutTimestamp uint64, packetData icatypes.InterchainAccountPacketData) *MsgSendTx { + return &MsgSendTx{ + ConnectionId: connectionID, + Owner: owner, + RelativeTimeout: relativeTimeoutTimestamp, + PacketData: packetData, + } +} + +// ValidateBasic implements sdk.Msg +func (msg MsgSendTx) ValidateBasic() error { + if err := host.ConnectionIdentifierValidator(msg.ConnectionId); err != nil { + return errorsmod.Wrap(err, "invalid connection ID") + } + + if strings.TrimSpace(msg.Owner) == "" { + return errorsmod.Wrap(ibcerrors.ErrInvalidAddress, "owner address cannot be empty") + } + + if len(msg.Owner) > MaximumOwnerLength { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "owner address must not exceed %d bytes", MaximumOwnerLength) + } + + if err := msg.PacketData.ValidateBasic(); err != nil { + return errorsmod.Wrap(err, "invalid interchain account packet data") + } + + if msg.RelativeTimeout == 0 { + return errorsmod.Wrap(ibcerrors.ErrInvalidRequest, "relative timeout cannot be zero") + } + + return nil +} + +// NewMsgUpdateParams creates a new MsgUpdateParams instance +func NewMsgUpdateParams(signer string, params Params) *MsgUpdateParams { + return &MsgUpdateParams{ + Signer: signer, + Params: params, + } +} + +// ValidateBasic implements sdk.Msg +func (msg MsgUpdateParams) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + + return nil +} diff --git a/modules/apps/27-interchain-accounts/controller/types/msgs_test.go b/modules/apps/27-interchain-accounts/controller/types/msgs_test.go new file mode 100644 index 0000000..30772de --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/types/msgs_test.go @@ -0,0 +1,269 @@ +package types_test + +import ( + "errors" + "testing" + + "github.com/cosmos/gogoproto/proto" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + ica "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func TestMsgRegisterInterchainAccountValidateBasic(t *testing.T) { + var msg *types.MsgRegisterInterchainAccount + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() {}, + nil, + }, + { + "success: with empty channel version", + func() { + msg.Version = "" + }, + nil, + }, + { + "connection id is invalid", + func() { + msg.ConnectionId = "" + }, + host.ErrInvalidID, + }, + { + "owner address is empty", + func() { + msg.Owner = "" + }, + ibcerrors.ErrInvalidAddress, + }, + { + "owner address is too long", + func() { + msg.Owner = ibctesting.GenerateString(types.MaximumOwnerLength + 1) + }, + ibcerrors.ErrInvalidAddress, + }, + { + "order is not valid", + func() { + msg.Ordering = channeltypes.NONE + }, + channeltypes.ErrInvalidChannelOrdering, + }, + } + + for i, tc := range testCases { + msg = types.NewMsgRegisterInterchainAccount( + ibctesting.FirstConnectionID, + ibctesting.TestAccAddress, + icatypes.NewDefaultMetadataString(ibctesting.FirstConnectionID, ibctesting.FirstConnectionID), + channeltypes.ORDERED, + ) + + tc.malleate() + + err := msg.ValidateBasic() + if tc.expErr == nil { + require.NoError(t, err, "valid test case %d failed: %s", i, tc.name) + } else { + require.ErrorIs(t, err, tc.expErr, "invalid test case %d passed: %s", i, tc.name) + } + } +} + +func TestMsgRegisterInterchainAccountGetSigners(t *testing.T) { + expSigner, err := sdk.AccAddressFromBech32(ibctesting.TestAccAddress) + require.NoError(t, err) + + msg := types.NewMsgRegisterInterchainAccount(ibctesting.FirstConnectionID, ibctesting.TestAccAddress, "", channeltypes.ORDERED) + encodingCfg := moduletestutil.MakeTestEncodingConfig(ica.AppModuleBasic{}) + signers, _, err := encodingCfg.Codec.GetMsgV1Signers(msg) + require.NoError(t, err) + require.Equal(t, expSigner.Bytes(), signers[0]) +} + +func TestMsgSendTxValidateBasic(t *testing.T) { + var msg *types.MsgSendTx + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() {}, + nil, + }, + { + "connection id is invalid", + func() { + msg.ConnectionId = "" + }, + host.ErrInvalidID, + }, + { + "owner address is empty", + func() { + msg.Owner = "" + }, + ibcerrors.ErrInvalidAddress, + }, + { + "owner address is too long", + func() { + msg.Owner = ibctesting.GenerateString(types.MaximumOwnerLength + 1) + }, + ibcerrors.ErrInvalidAddress, + }, + { + "relative timeout is not set", + func() { + msg.RelativeTimeout = 0 + }, + ibcerrors.ErrInvalidRequest, + }, + { + "messages array is empty", + func() { + msg.PacketData = icatypes.InterchainAccountPacketData{} + }, + icatypes.ErrInvalidOutgoingData, + }, + } + + for i, tc := range testCases { + msgBankSend := &banktypes.MsgSend{ + FromAddress: ibctesting.TestAccAddress, + ToAddress: ibctesting.TestAccAddress, + Amount: ibctesting.TestCoins, + } + + encodingConfig := moduletestutil.MakeTestEncodingConfig(ica.AppModuleBasic{}) + + data, err := icatypes.SerializeCosmosTx(encodingConfig.Codec, []proto.Message{msgBankSend}, icatypes.EncodingProtobuf) + require.NoError(t, err) + + packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + } + + msg = types.NewMsgSendTx( + ibctesting.TestAccAddress, + ibctesting.FirstConnectionID, + 100000, + packetData, + ) + + tc.malleate() + + err = msg.ValidateBasic() + if tc.expErr == nil { + require.NoError(t, err, "valid test case %d failed: %s", i, tc.name) + } else { + require.ErrorIs(t, err, tc.expErr, "invalid test case %d passed: %s", i, tc.name) + } + } +} + +func TestMsgSendTxGetSigners(t *testing.T) { + expSigner, err := sdk.AccAddressFromBech32(ibctesting.TestAccAddress) + require.NoError(t, err) + + msgBankSend := &banktypes.MsgSend{ + FromAddress: ibctesting.TestAccAddress, + ToAddress: ibctesting.TestAccAddress, + Amount: ibctesting.TestCoins, + } + + encodingConfig := moduletestutil.MakeTestEncodingConfig(ica.AppModuleBasic{}) + + data, err := icatypes.SerializeCosmosTx(encodingConfig.Codec, []proto.Message{msgBankSend}, icatypes.EncodingProtobuf) + require.NoError(t, err) + + packetData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + } + + msg := types.NewMsgSendTx( + ibctesting.TestAccAddress, + ibctesting.FirstConnectionID, + 100000, + packetData, + ) + signers, _, err := encodingConfig.Codec.GetMsgV1Signers(msg) + require.NoError(t, err) + require.Equal(t, expSigner.Bytes(), signers[0]) +} + +// TestMsgUpdateParamsValidateBasic tests ValidateBasic for MsgUpdateParams +func TestMsgUpdateParamsValidateBasic(t *testing.T) { + testCases := []struct { + name string + msg *types.MsgUpdateParams + expErr error + }{ + {"success: valid signer and valid params", types.NewMsgUpdateParams(ibctesting.TestAccAddress, types.DefaultParams()), nil}, + {"failure: invalid signer with valid params", types.NewMsgUpdateParams("invalidAddress", types.DefaultParams()), ibcerrors.ErrInvalidAddress}, + {"failure: empty signer with valid params", types.NewMsgUpdateParams("", types.DefaultParams()), ibcerrors.ErrInvalidAddress}, + } + + for i, tc := range testCases { + err := tc.msg.ValidateBasic() + if tc.expErr == nil { + require.NoError(t, err, "valid test case %d failed: %s", i, tc.name) + } else { + require.ErrorIs(t, err, tc.expErr, "invalid test case %d passed: %s", i, tc.name) + } + } +} + +// TestMsgUpdateParamsGetSigners tests GetSigners for MsgUpdateParams +func TestMsgUpdateParamsGetSigners(t *testing.T) { + testCases := []struct { + name string + address sdk.AccAddress + expErr error + }{ + {"success: valid address", sdk.AccAddress(ibctesting.TestAccAddress), nil}, + {"failure: nil address", nil, errors.New("empty address string is not allowed")}, + } + + for _, tc := range testCases { + + msg := types.MsgUpdateParams{ + Signer: tc.address.String(), + Params: types.DefaultParams(), + } + + encodingCfg := moduletestutil.MakeTestEncodingConfig(ica.AppModuleBasic{}) + signers, _, err := encodingCfg.Codec.GetMsgV1Signers(&msg) + if tc.expErr == nil { + require.NoError(t, err) + require.Equal(t, tc.address.Bytes(), signers[0]) + } else { + require.ErrorContains(t, err, tc.expErr.Error()) + } + + } +} diff --git a/modules/apps/27-interchain-accounts/controller/types/params.go b/modules/apps/27-interchain-accounts/controller/types/params.go new file mode 100644 index 0000000..3ae5383 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/types/params.go @@ -0,0 +1,18 @@ +package types + +const ( + // DefaultControllerEnabled is the default value for the controller param (set to true) + DefaultControllerEnabled = true +) + +// NewParams creates a new parameter configuration for the controller submodule +func NewParams(enableController bool) Params { + return Params{ + ControllerEnabled: enableController, + } +} + +// DefaultParams is the default parameter configuration for the controller submodule +func DefaultParams() Params { + return NewParams(DefaultControllerEnabled) +} diff --git a/modules/apps/27-interchain-accounts/controller/types/params_legacy.go b/modules/apps/27-interchain-accounts/controller/types/params_legacy.go new file mode 100644 index 0000000..98087a7 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/types/params_legacy.go @@ -0,0 +1,37 @@ +/* +NOTE: Usage of x/params to manage parameters is deprecated in favor of x/gov +controlled execution of MsgUpdateParams messages. These types remains solely +for migration purposes and will be removed in a future release. +[#3621](https://github.com/cosmos/ibc-go/issues/3621) +*/ +package types + +import ( + "fmt" + + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" +) + +// KeyControllerEnabled is the store key for ControllerEnabled Params +var KeyControllerEnabled = []byte("ControllerEnabled") + +// ParamKeyTable type declaration for parameters +func ParamKeyTable() paramtypes.KeyTable { + return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) +} + +// ParamSetPairs implements params.ParamSet +func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { + return paramtypes.ParamSetPairs{ + paramtypes.NewParamSetPair(KeyControllerEnabled, &p.ControllerEnabled, validateEnabledTypeLegacy), + } +} + +func validateEnabledTypeLegacy(i any) error { + _, ok := i.(bool) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + return nil +} diff --git a/modules/apps/27-interchain-accounts/controller/types/query.pb.go b/modules/apps/27-interchain-accounts/controller/types/query.pb.go new file mode 100644 index 0000000..8008027 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/types/query.pb.go @@ -0,0 +1,984 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/applications/interchain_accounts/controller/v1/query.proto + +package types + +import ( + context "context" + fmt "fmt" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// QueryInterchainAccountRequest is the request type for the Query/InterchainAccount RPC method. +type QueryInterchainAccountRequest struct { + Owner string `protobuf:"bytes,1,opt,name=owner,proto3" json:"owner,omitempty"` + ConnectionId string `protobuf:"bytes,2,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"` +} + +func (m *QueryInterchainAccountRequest) Reset() { *m = QueryInterchainAccountRequest{} } +func (m *QueryInterchainAccountRequest) String() string { return proto.CompactTextString(m) } +func (*QueryInterchainAccountRequest) ProtoMessage() {} +func (*QueryInterchainAccountRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_df0d8b259d72854e, []int{0} +} +func (m *QueryInterchainAccountRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryInterchainAccountRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryInterchainAccountRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryInterchainAccountRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryInterchainAccountRequest.Merge(m, src) +} +func (m *QueryInterchainAccountRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryInterchainAccountRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryInterchainAccountRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryInterchainAccountRequest proto.InternalMessageInfo + +func (m *QueryInterchainAccountRequest) GetOwner() string { + if m != nil { + return m.Owner + } + return "" +} + +func (m *QueryInterchainAccountRequest) GetConnectionId() string { + if m != nil { + return m.ConnectionId + } + return "" +} + +// QueryInterchainAccountResponse the response type for the Query/InterchainAccount RPC method. +type QueryInterchainAccountResponse struct { + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` +} + +func (m *QueryInterchainAccountResponse) Reset() { *m = QueryInterchainAccountResponse{} } +func (m *QueryInterchainAccountResponse) String() string { return proto.CompactTextString(m) } +func (*QueryInterchainAccountResponse) ProtoMessage() {} +func (*QueryInterchainAccountResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_df0d8b259d72854e, []int{1} +} +func (m *QueryInterchainAccountResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryInterchainAccountResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryInterchainAccountResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryInterchainAccountResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryInterchainAccountResponse.Merge(m, src) +} +func (m *QueryInterchainAccountResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryInterchainAccountResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryInterchainAccountResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryInterchainAccountResponse proto.InternalMessageInfo + +func (m *QueryInterchainAccountResponse) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +// QueryParamsRequest is the request type for the Query/Params RPC method. +type QueryParamsRequest struct { +} + +func (m *QueryParamsRequest) Reset() { *m = QueryParamsRequest{} } +func (m *QueryParamsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryParamsRequest) ProtoMessage() {} +func (*QueryParamsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_df0d8b259d72854e, []int{2} +} +func (m *QueryParamsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsRequest.Merge(m, src) +} +func (m *QueryParamsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsRequest proto.InternalMessageInfo + +// QueryParamsResponse is the response type for the Query/Params RPC method. +type QueryParamsResponse struct { + // params defines the parameters of the module. + Params *Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params,omitempty"` +} + +func (m *QueryParamsResponse) Reset() { *m = QueryParamsResponse{} } +func (m *QueryParamsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryParamsResponse) ProtoMessage() {} +func (*QueryParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_df0d8b259d72854e, []int{3} +} +func (m *QueryParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsResponse.Merge(m, src) +} +func (m *QueryParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsResponse proto.InternalMessageInfo + +func (m *QueryParamsResponse) GetParams() *Params { + if m != nil { + return m.Params + } + return nil +} + +func init() { + proto.RegisterType((*QueryInterchainAccountRequest)(nil), "ibc.applications.interchain_accounts.controller.v1.QueryInterchainAccountRequest") + proto.RegisterType((*QueryInterchainAccountResponse)(nil), "ibc.applications.interchain_accounts.controller.v1.QueryInterchainAccountResponse") + proto.RegisterType((*QueryParamsRequest)(nil), "ibc.applications.interchain_accounts.controller.v1.QueryParamsRequest") + proto.RegisterType((*QueryParamsResponse)(nil), "ibc.applications.interchain_accounts.controller.v1.QueryParamsResponse") +} + +func init() { + proto.RegisterFile("ibc/applications/interchain_accounts/controller/v1/query.proto", fileDescriptor_df0d8b259d72854e) +} + +var fileDescriptor_df0d8b259d72854e = []byte{ + // 440 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x93, 0x41, 0x8b, 0xd4, 0x30, + 0x14, 0xc7, 0xa7, 0x23, 0x3b, 0x62, 0xd4, 0x83, 0x71, 0x0f, 0x43, 0xd1, 0x22, 0xf5, 0xe2, 0x65, + 0x12, 0xa7, 0x0a, 0xc2, 0x1c, 0x04, 0x15, 0x94, 0xbd, 0x39, 0x3d, 0x88, 0xec, 0xc1, 0x25, 0x4d, + 0x43, 0x37, 0xd8, 0xe6, 0x75, 0x93, 0x74, 0x64, 0x59, 0xf6, 0xe2, 0x27, 0x10, 0xbc, 0xf9, 0x89, + 0x3c, 0x2e, 0x88, 0xe0, 0x51, 0x66, 0xfc, 0x18, 0x1e, 0x64, 0xd2, 0x68, 0x77, 0x70, 0x15, 0x67, + 0xdc, 0x53, 0xc9, 0x0b, 0xef, 0xf7, 0x7f, 0xef, 0xff, 0x4f, 0xd1, 0x43, 0x99, 0x71, 0xca, 0xea, + 0xba, 0x94, 0x9c, 0x59, 0x09, 0xca, 0x50, 0xa9, 0xac, 0xd0, 0x7c, 0x9f, 0x49, 0xb5, 0xc7, 0x38, + 0x87, 0x46, 0x59, 0x43, 0x39, 0x28, 0xab, 0xa1, 0x2c, 0x85, 0xa6, 0xb3, 0x31, 0x3d, 0x68, 0x84, + 0x3e, 0x24, 0xb5, 0x06, 0x0b, 0x38, 0x91, 0x19, 0x27, 0xa7, 0xfb, 0xc9, 0x19, 0xfd, 0xa4, 0xeb, + 0x27, 0xb3, 0x71, 0xf8, 0x64, 0x03, 0xcd, 0x53, 0x04, 0x27, 0x1c, 0xde, 0x28, 0x00, 0x8a, 0x52, + 0x50, 0x56, 0x4b, 0xca, 0x94, 0x02, 0xeb, 0xe5, 0xdd, 0x6d, 0xbc, 0x8b, 0x6e, 0x4e, 0x97, 0x53, + 0xee, 0xfc, 0x02, 0x3f, 0x6a, 0xb9, 0xa9, 0x38, 0x68, 0x84, 0xb1, 0x78, 0x1b, 0x6d, 0xc1, 0x1b, + 0x25, 0xf4, 0x30, 0xb8, 0x15, 0xdc, 0xb9, 0x94, 0xb6, 0x07, 0x7c, 0x1b, 0x5d, 0xe5, 0xa0, 0x94, + 0xe0, 0x4b, 0xd6, 0x9e, 0xcc, 0x87, 0x7d, 0x77, 0x7b, 0xa5, 0x2b, 0xee, 0xe4, 0xf1, 0x04, 0x45, + 0x7f, 0x62, 0x9b, 0x1a, 0x94, 0x11, 0x78, 0x88, 0x2e, 0xb2, 0x3c, 0xd7, 0xc2, 0x18, 0x8f, 0xff, + 0x79, 0x8c, 0xb7, 0x11, 0x76, 0xbd, 0xcf, 0x99, 0x66, 0x95, 0xf1, 0xc3, 0xc4, 0x12, 0x5d, 0x5f, + 0xa9, 0x7a, 0x4c, 0x8a, 0x06, 0xb5, 0xab, 0x38, 0xca, 0xe5, 0x64, 0x42, 0xd6, 0x37, 0x9b, 0x78, + 0xa6, 0x27, 0x25, 0xdf, 0x2f, 0xa0, 0x2d, 0xa7, 0x85, 0x3f, 0xf4, 0xd1, 0xb5, 0xdf, 0x56, 0xc0, + 0xd3, 0x4d, 0x34, 0xfe, 0x6a, 0x75, 0x98, 0x9e, 0x27, 0xb2, 0xb5, 0x26, 0x7e, 0xf5, 0xf6, 0xd3, + 0xb7, 0xf7, 0xfd, 0x97, 0xf8, 0x05, 0xf5, 0x6f, 0xe9, 0x5f, 0xde, 0x90, 0xcb, 0xd8, 0xd0, 0x23, + 0xf7, 0x3d, 0xa6, 0x5d, 0xa8, 0x86, 0x1e, 0xad, 0xc4, 0x7e, 0x8c, 0x3f, 0x07, 0x68, 0xd0, 0x3a, + 0x87, 0x9f, 0x6e, 0x3c, 0xfe, 0x4a, 0xc8, 0xe1, 0xb3, 0xff, 0xe6, 0xf8, 0xdd, 0x27, 0x6e, 0xf7, + 0xfb, 0x38, 0x59, 0x67, 0xf7, 0x36, 0xfe, 0xc7, 0xaf, 0x3f, 0xce, 0xa3, 0xe0, 0x64, 0x1e, 0x05, + 0x5f, 0xe7, 0x51, 0xf0, 0x6e, 0x11, 0xf5, 0x4e, 0x16, 0x51, 0xef, 0xcb, 0x22, 0xea, 0xed, 0x4e, + 0x0b, 0x69, 0xf7, 0x9b, 0x8c, 0x70, 0xa8, 0x28, 0x07, 0x53, 0x81, 0x59, 0xe2, 0x47, 0x05, 0xd0, + 0xd9, 0xf8, 0x2e, 0xad, 0x20, 0x6f, 0x4a, 0x61, 0x5a, 0xb5, 0xe4, 0xc1, 0xa8, 0x13, 0x1c, 0x9d, + 0x25, 0x68, 0x0f, 0x6b, 0x61, 0xb2, 0x81, 0xfb, 0x17, 0xef, 0xfd, 0x08, 0x00, 0x00, 0xff, 0xff, + 0x0b, 0xdf, 0x50, 0x39, 0x64, 0x04, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // InterchainAccount returns the interchain account address for a given owner address on a given connection + InterchainAccount(ctx context.Context, in *QueryInterchainAccountRequest, opts ...grpc.CallOption) (*QueryInterchainAccountResponse, error) + // Params queries all parameters of the ICA controller submodule. + Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) InterchainAccount(ctx context.Context, in *QueryInterchainAccountRequest, opts ...grpc.CallOption) (*QueryInterchainAccountResponse, error) { + out := new(QueryInterchainAccountResponse) + err := c.cc.Invoke(ctx, "/ibc.applications.interchain_accounts.controller.v1.Query/InterchainAccount", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) { + out := new(QueryParamsResponse) + err := c.cc.Invoke(ctx, "/ibc.applications.interchain_accounts.controller.v1.Query/Params", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // InterchainAccount returns the interchain account address for a given owner address on a given connection + InterchainAccount(context.Context, *QueryInterchainAccountRequest) (*QueryInterchainAccountResponse, error) + // Params queries all parameters of the ICA controller submodule. + Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) InterchainAccount(ctx context.Context, req *QueryInterchainAccountRequest) (*QueryInterchainAccountResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method InterchainAccount not implemented") +} +func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_InterchainAccount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryInterchainAccountRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).InterchainAccount(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.applications.interchain_accounts.controller.v1.Query/InterchainAccount", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).InterchainAccount(ctx, req.(*QueryInterchainAccountRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryParamsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Params(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.applications.interchain_accounts.controller.v1.Query/Params", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Params(ctx, req.(*QueryParamsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ibc.applications.interchain_accounts.controller.v1.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "InterchainAccount", + Handler: _Query_InterchainAccount_Handler, + }, + { + MethodName: "Params", + Handler: _Query_Params_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "ibc/applications/interchain_accounts/controller/v1/query.proto", +} + +func (m *QueryInterchainAccountRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryInterchainAccountRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryInterchainAccountRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ConnectionId) > 0 { + i -= len(m.ConnectionId) + copy(dAtA[i:], m.ConnectionId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ConnectionId))) + i-- + dAtA[i] = 0x12 + } + if len(m.Owner) > 0 { + i -= len(m.Owner) + copy(dAtA[i:], m.Owner) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Owner))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryInterchainAccountResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryInterchainAccountResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryInterchainAccountResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Address) > 0 { + i -= len(m.Address) + copy(dAtA[i:], m.Address) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Address))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryParamsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Params != nil { + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryInterchainAccountRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Owner) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.ConnectionId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryInterchainAccountResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Address) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryParamsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Params != nil { + l = m.Params.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryInterchainAccountRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryInterchainAccountRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryInterchainAccountRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Owner", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Owner = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectionId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ConnectionId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryInterchainAccountResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryInterchainAccountResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryInterchainAccountResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Address = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryParamsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Params == nil { + m.Params = &Params{} + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/apps/27-interchain-accounts/controller/types/query.pb.gw.go b/modules/apps/27-interchain-accounts/controller/types/query.pb.gw.go new file mode 100644 index 0000000..2aec630 --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/types/query.pb.gw.go @@ -0,0 +1,276 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: ibc/applications/interchain_accounts/controller/v1/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +func request_Query_InterchainAccount_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryInterchainAccountRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["owner"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "owner") + } + + protoReq.Owner, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "owner", err) + } + + val, ok = pathParams["connection_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "connection_id") + } + + protoReq.ConnectionId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "connection_id", err) + } + + msg, err := client.InterchainAccount(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_InterchainAccount_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryInterchainAccountRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["owner"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "owner") + } + + protoReq.Owner, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "owner", err) + } + + val, ok = pathParams["connection_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "connection_id") + } + + protoReq.ConnectionId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "connection_id", err) + } + + msg, err := server.InterchainAccount(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := client.Params(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := server.Params(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_InterchainAccount_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_InterchainAccount_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_InterchainAccount_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Params_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_InterchainAccount_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_InterchainAccount_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_InterchainAccount_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Params_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_InterchainAccount_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 2, 5, 1, 0, 4, 1, 5, 6, 2, 7, 1, 0, 4, 1, 5, 8}, []string{"ibc", "apps", "interchain_accounts", "controller", "v1", "owners", "owner", "connections", "connection_id"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 2, 5}, []string{"ibc", "apps", "interchain_accounts", "controller", "v1", "params"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Query_InterchainAccount_0 = runtime.ForwardResponseMessage + + forward_Query_Params_0 = runtime.ForwardResponseMessage +) diff --git a/modules/apps/27-interchain-accounts/controller/types/tx.pb.go b/modules/apps/27-interchain-accounts/controller/types/tx.pb.go new file mode 100644 index 0000000..b129b4c --- /dev/null +++ b/modules/apps/27-interchain-accounts/controller/types/tx.pb.go @@ -0,0 +1,1599 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/applications/interchain_accounts/controller/v1/tx.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/cosmos/cosmos-sdk/types/msgservice" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + types1 "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + types "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// MsgRegisterInterchainAccount defines the payload for Msg/RegisterAccount +type MsgRegisterInterchainAccount struct { + Owner string `protobuf:"bytes,1,opt,name=owner,proto3" json:"owner,omitempty"` + ConnectionId string `protobuf:"bytes,2,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"` + Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` + Ordering types.Order `protobuf:"varint,4,opt,name=ordering,proto3,enum=ibc.core.channel.v1.Order" json:"ordering,omitempty"` +} + +func (m *MsgRegisterInterchainAccount) Reset() { *m = MsgRegisterInterchainAccount{} } +func (m *MsgRegisterInterchainAccount) String() string { return proto.CompactTextString(m) } +func (*MsgRegisterInterchainAccount) ProtoMessage() {} +func (*MsgRegisterInterchainAccount) Descriptor() ([]byte, []int) { + return fileDescriptor_7def041328c84a30, []int{0} +} +func (m *MsgRegisterInterchainAccount) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgRegisterInterchainAccount) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgRegisterInterchainAccount.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgRegisterInterchainAccount) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgRegisterInterchainAccount.Merge(m, src) +} +func (m *MsgRegisterInterchainAccount) XXX_Size() int { + return m.Size() +} +func (m *MsgRegisterInterchainAccount) XXX_DiscardUnknown() { + xxx_messageInfo_MsgRegisterInterchainAccount.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgRegisterInterchainAccount proto.InternalMessageInfo + +// MsgRegisterInterchainAccountResponse defines the response for Msg/RegisterAccount +type MsgRegisterInterchainAccountResponse struct { + ChannelId string `protobuf:"bytes,1,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + PortId string `protobuf:"bytes,2,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` +} + +func (m *MsgRegisterInterchainAccountResponse) Reset() { *m = MsgRegisterInterchainAccountResponse{} } +func (m *MsgRegisterInterchainAccountResponse) String() string { return proto.CompactTextString(m) } +func (*MsgRegisterInterchainAccountResponse) ProtoMessage() {} +func (*MsgRegisterInterchainAccountResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_7def041328c84a30, []int{1} +} +func (m *MsgRegisterInterchainAccountResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgRegisterInterchainAccountResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgRegisterInterchainAccountResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgRegisterInterchainAccountResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgRegisterInterchainAccountResponse.Merge(m, src) +} +func (m *MsgRegisterInterchainAccountResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgRegisterInterchainAccountResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgRegisterInterchainAccountResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgRegisterInterchainAccountResponse proto.InternalMessageInfo + +// MsgSendTx defines the payload for Msg/SendTx +type MsgSendTx struct { + Owner string `protobuf:"bytes,1,opt,name=owner,proto3" json:"owner,omitempty"` + ConnectionId string `protobuf:"bytes,2,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"` + PacketData types1.InterchainAccountPacketData `protobuf:"bytes,3,opt,name=packet_data,json=packetData,proto3" json:"packet_data"` + // Relative timeout timestamp provided will be added to the current block time during transaction execution. + // The timeout timestamp must be non-zero. + RelativeTimeout uint64 `protobuf:"varint,4,opt,name=relative_timeout,json=relativeTimeout,proto3" json:"relative_timeout,omitempty"` +} + +func (m *MsgSendTx) Reset() { *m = MsgSendTx{} } +func (m *MsgSendTx) String() string { return proto.CompactTextString(m) } +func (*MsgSendTx) ProtoMessage() {} +func (*MsgSendTx) Descriptor() ([]byte, []int) { + return fileDescriptor_7def041328c84a30, []int{2} +} +func (m *MsgSendTx) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSendTx) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSendTx.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgSendTx) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSendTx.Merge(m, src) +} +func (m *MsgSendTx) XXX_Size() int { + return m.Size() +} +func (m *MsgSendTx) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSendTx.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSendTx proto.InternalMessageInfo + +// MsgSendTxResponse defines the response for MsgSendTx +type MsgSendTxResponse struct { + Sequence uint64 `protobuf:"varint,1,opt,name=sequence,proto3" json:"sequence,omitempty"` +} + +func (m *MsgSendTxResponse) Reset() { *m = MsgSendTxResponse{} } +func (m *MsgSendTxResponse) String() string { return proto.CompactTextString(m) } +func (*MsgSendTxResponse) ProtoMessage() {} +func (*MsgSendTxResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_7def041328c84a30, []int{3} +} +func (m *MsgSendTxResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSendTxResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSendTxResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgSendTxResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSendTxResponse.Merge(m, src) +} +func (m *MsgSendTxResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgSendTxResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSendTxResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSendTxResponse proto.InternalMessageInfo + +// MsgUpdateParams defines the payload for Msg/UpdateParams +type MsgUpdateParams struct { + // signer address + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // params defines the 27-interchain-accounts/controller parameters to update. + // + // NOTE: All parameters must be supplied. + Params Params `protobuf:"bytes,2,opt,name=params,proto3" json:"params"` +} + +func (m *MsgUpdateParams) Reset() { *m = MsgUpdateParams{} } +func (m *MsgUpdateParams) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParams) ProtoMessage() {} +func (*MsgUpdateParams) Descriptor() ([]byte, []int) { + return fileDescriptor_7def041328c84a30, []int{4} +} +func (m *MsgUpdateParams) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParams.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParams.Merge(m, src) +} +func (m *MsgUpdateParams) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParams) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParams.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParams proto.InternalMessageInfo + +// MsgUpdateParamsResponse defines the response for Msg/UpdateParams +type MsgUpdateParamsResponse struct { +} + +func (m *MsgUpdateParamsResponse) Reset() { *m = MsgUpdateParamsResponse{} } +func (m *MsgUpdateParamsResponse) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParamsResponse) ProtoMessage() {} +func (*MsgUpdateParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_7def041328c84a30, []int{5} +} +func (m *MsgUpdateParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParamsResponse.Merge(m, src) +} +func (m *MsgUpdateParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParamsResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*MsgRegisterInterchainAccount)(nil), "ibc.applications.interchain_accounts.controller.v1.MsgRegisterInterchainAccount") + proto.RegisterType((*MsgRegisterInterchainAccountResponse)(nil), "ibc.applications.interchain_accounts.controller.v1.MsgRegisterInterchainAccountResponse") + proto.RegisterType((*MsgSendTx)(nil), "ibc.applications.interchain_accounts.controller.v1.MsgSendTx") + proto.RegisterType((*MsgSendTxResponse)(nil), "ibc.applications.interchain_accounts.controller.v1.MsgSendTxResponse") + proto.RegisterType((*MsgUpdateParams)(nil), "ibc.applications.interchain_accounts.controller.v1.MsgUpdateParams") + proto.RegisterType((*MsgUpdateParamsResponse)(nil), "ibc.applications.interchain_accounts.controller.v1.MsgUpdateParamsResponse") +} + +func init() { + proto.RegisterFile("ibc/applications/interchain_accounts/controller/v1/tx.proto", fileDescriptor_7def041328c84a30) +} + +var fileDescriptor_7def041328c84a30 = []byte{ + // 669 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x4f, 0x4f, 0x13, 0x41, + 0x1c, 0xed, 0x4a, 0x29, 0x30, 0x20, 0xe8, 0x86, 0x48, 0xd9, 0x68, 0xc1, 0xea, 0x01, 0x49, 0x98, + 0xb1, 0xf5, 0x5f, 0x52, 0xe3, 0x41, 0xc0, 0x43, 0x63, 0x1a, 0x71, 0xc5, 0x84, 0x78, 0x69, 0xa6, + 0xb3, 0x93, 0x61, 0x42, 0x77, 0x66, 0x9d, 0x99, 0xae, 0x78, 0x33, 0x9e, 0x8c, 0x07, 0xe3, 0xc1, + 0x0f, 0xc0, 0x47, 0xe0, 0xee, 0x07, 0x90, 0x23, 0x47, 0x4f, 0xc6, 0xc0, 0x81, 0x9b, 0x9f, 0xc1, + 0xec, 0x9f, 0x6e, 0x51, 0x90, 0x60, 0xe1, 0xb6, 0xbf, 0xdf, 0xcc, 0x7b, 0xbf, 0xf7, 0xde, 0xcc, + 0x0e, 0x78, 0xc8, 0x5b, 0x04, 0xe1, 0x20, 0x68, 0x73, 0x82, 0x0d, 0x97, 0x42, 0x23, 0x2e, 0x0c, + 0x55, 0x64, 0x1d, 0x73, 0xd1, 0xc4, 0x84, 0xc8, 0x8e, 0x30, 0x1a, 0x11, 0x29, 0x8c, 0x92, 0xed, + 0x36, 0x55, 0x28, 0xac, 0x20, 0xb3, 0x09, 0x03, 0x25, 0x8d, 0xb4, 0xab, 0xbc, 0x45, 0xe0, 0x61, + 0x30, 0x3c, 0x06, 0x0c, 0x7b, 0x60, 0x18, 0x56, 0x9c, 0x49, 0x26, 0x99, 0x8c, 0xe1, 0x28, 0xfa, + 0x4a, 0x98, 0x9c, 0xbb, 0xa7, 0x92, 0x11, 0x56, 0x50, 0x80, 0xc9, 0x06, 0x35, 0x29, 0x6a, 0xa9, + 0x0f, 0xf1, 0x87, 0xd4, 0x24, 0x24, 0x53, 0x44, 0x6a, 0x5f, 0x6a, 0xe4, 0x6b, 0x16, 0xad, 0xfb, + 0x9a, 0xa5, 0x0b, 0xd7, 0x23, 0x76, 0x22, 0x15, 0x45, 0x64, 0x1d, 0x0b, 0x41, 0xdb, 0x31, 0x3c, + 0xf9, 0x4c, 0xb6, 0x94, 0xbf, 0x5a, 0xe0, 0x6a, 0x43, 0x33, 0x97, 0x32, 0xae, 0x0d, 0x55, 0xf5, + 0x6c, 0xfa, 0xe3, 0x64, 0xb8, 0x3d, 0x09, 0x06, 0xe5, 0x1b, 0x41, 0x55, 0xd1, 0x9a, 0xb5, 0xe6, + 0x46, 0xdc, 0xa4, 0xb0, 0x6f, 0x80, 0x8b, 0x44, 0x0a, 0x41, 0x49, 0x24, 0xba, 0xc9, 0xbd, 0xe2, + 0x85, 0x78, 0x75, 0xac, 0xd7, 0xac, 0x7b, 0x76, 0x11, 0x0c, 0x85, 0x54, 0x69, 0x2e, 0x45, 0x71, + 0x20, 0x5e, 0xee, 0x96, 0xf6, 0x7d, 0x30, 0x2c, 0x95, 0x47, 0x15, 0x17, 0xac, 0x98, 0x9f, 0xb5, + 0xe6, 0xc6, 0xab, 0x0e, 0x8c, 0x4e, 0x22, 0xd2, 0x0a, 0xbb, 0x02, 0xc3, 0x0a, 0x7c, 0x16, 0x6d, + 0x72, 0xb3, 0xbd, 0xb5, 0xf1, 0x0f, 0x5b, 0x33, 0xb9, 0xf7, 0x07, 0xdb, 0xf3, 0x89, 0x8c, 0xb2, + 0x07, 0x6e, 0x9e, 0x24, 0xde, 0xa5, 0x3a, 0x90, 0x42, 0x53, 0xfb, 0x1a, 0x00, 0x29, 0x6b, 0xa4, + 0x35, 0x71, 0x32, 0x92, 0x76, 0xea, 0x9e, 0x3d, 0x05, 0x86, 0x02, 0xa9, 0x4c, 0xcf, 0x47, 0x21, + 0x2a, 0xeb, 0x5e, 0x2d, 0x1f, 0xcd, 0x2b, 0xff, 0xb2, 0xc0, 0x48, 0x43, 0xb3, 0x17, 0x54, 0x78, + 0xab, 0x9b, 0x67, 0x09, 0x64, 0x03, 0x8c, 0x26, 0xa7, 0xdf, 0xf4, 0xb0, 0xc1, 0x71, 0x28, 0xa3, + 0xd5, 0x65, 0x78, 0xaa, 0x3b, 0x18, 0x56, 0xe0, 0x11, 0x7f, 0x2b, 0x31, 0xd9, 0x32, 0x36, 0x78, + 0x31, 0xbf, 0xf3, 0x63, 0x26, 0xe7, 0x82, 0x20, 0xeb, 0xd8, 0xb7, 0xc0, 0x25, 0x45, 0xdb, 0xd8, + 0xf0, 0x90, 0x36, 0x0d, 0xf7, 0xa9, 0xec, 0x98, 0x38, 0xeb, 0xbc, 0x3b, 0xd1, 0xed, 0xaf, 0x26, + 0xed, 0x23, 0xb1, 0xde, 0x03, 0x97, 0x33, 0xbf, 0x59, 0x86, 0x0e, 0x18, 0xd6, 0xf4, 0x75, 0x87, + 0x0a, 0x42, 0x63, 0xeb, 0x79, 0x37, 0xab, 0xd3, 0x9c, 0xbe, 0x58, 0x60, 0xa2, 0xa1, 0xd9, 0xcb, + 0xc0, 0xc3, 0x86, 0xae, 0x60, 0x85, 0x7d, 0x6d, 0x5f, 0x01, 0x05, 0xcd, 0x59, 0x2f, 0xae, 0xb4, + 0xb2, 0xd7, 0x40, 0x21, 0x88, 0x77, 0xc4, 0x41, 0x8d, 0x56, 0x6b, 0xf0, 0xff, 0xff, 0x44, 0x98, + 0xcc, 0x48, 0xbd, 0xa7, 0x7c, 0xb5, 0x89, 0xae, 0x99, 0x74, 0x54, 0x79, 0x1a, 0x4c, 0xfd, 0xa5, + 0xaa, 0xeb, 0xa9, 0xfa, 0x31, 0x0f, 0x06, 0x1a, 0x9a, 0xd9, 0xdf, 0x2c, 0x30, 0xfd, 0xef, 0x5f, + 0x60, 0xa5, 0x1f, 0x6d, 0x27, 0xdd, 0x4b, 0x67, 0xed, 0xbc, 0x19, 0xb3, 0x53, 0xfa, 0x64, 0x81, + 0x42, 0x7a, 0x51, 0x1f, 0xf5, 0x39, 0x24, 0x81, 0x3b, 0x4f, 0xce, 0x04, 0xcf, 0x04, 0x6d, 0x59, + 0x60, 0xec, 0x8f, 0x1b, 0xb1, 0xd4, 0x27, 0xef, 0x61, 0x12, 0xe7, 0xe9, 0x39, 0x90, 0x74, 0x25, + 0x3a, 0x83, 0xef, 0x0e, 0xb6, 0xe7, 0xad, 0xc5, 0x8d, 0x9d, 0xbd, 0x92, 0xb5, 0xbb, 0x57, 0xb2, + 0x7e, 0xee, 0x95, 0xac, 0xcf, 0xfb, 0xa5, 0xdc, 0xee, 0x7e, 0x29, 0xf7, 0x7d, 0xbf, 0x94, 0x7b, + 0xf5, 0x9c, 0x71, 0xb3, 0xde, 0x69, 0x41, 0x22, 0x7d, 0x94, 0xbe, 0xb5, 0xbc, 0x45, 0x16, 0x98, + 0x44, 0x61, 0xe5, 0x36, 0xf2, 0xa5, 0xd7, 0x69, 0x53, 0x1d, 0x3d, 0xe3, 0x1a, 0x55, 0x1f, 0x2c, + 0xf4, 0x84, 0x2c, 0x1c, 0xf7, 0x82, 0x9b, 0xb7, 0x01, 0xd5, 0xad, 0x42, 0xfc, 0xfc, 0xde, 0xf9, + 0x1d, 0x00, 0x00, 0xff, 0xff, 0xa7, 0x07, 0x43, 0x4f, 0xbe, 0x06, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MsgClient is the client API for Msg service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MsgClient interface { + // RegisterInterchainAccount defines a rpc handler for MsgRegisterInterchainAccount. + RegisterInterchainAccount(ctx context.Context, in *MsgRegisterInterchainAccount, opts ...grpc.CallOption) (*MsgRegisterInterchainAccountResponse, error) + // SendTx defines a rpc handler for MsgSendTx. + SendTx(ctx context.Context, in *MsgSendTx, opts ...grpc.CallOption) (*MsgSendTxResponse, error) + // UpdateParams defines a rpc handler for MsgUpdateParams. + UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) +} + +type msgClient struct { + cc grpc1.ClientConn +} + +func NewMsgClient(cc grpc1.ClientConn) MsgClient { + return &msgClient{cc} +} + +func (c *msgClient) RegisterInterchainAccount(ctx context.Context, in *MsgRegisterInterchainAccount, opts ...grpc.CallOption) (*MsgRegisterInterchainAccountResponse, error) { + out := new(MsgRegisterInterchainAccountResponse) + err := c.cc.Invoke(ctx, "/ibc.applications.interchain_accounts.controller.v1.Msg/RegisterInterchainAccount", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) SendTx(ctx context.Context, in *MsgSendTx, opts ...grpc.CallOption) (*MsgSendTxResponse, error) { + out := new(MsgSendTxResponse) + err := c.cc.Invoke(ctx, "/ibc.applications.interchain_accounts.controller.v1.Msg/SendTx", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { + out := new(MsgUpdateParamsResponse) + err := c.cc.Invoke(ctx, "/ibc.applications.interchain_accounts.controller.v1.Msg/UpdateParams", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MsgServer is the server API for Msg service. +type MsgServer interface { + // RegisterInterchainAccount defines a rpc handler for MsgRegisterInterchainAccount. + RegisterInterchainAccount(context.Context, *MsgRegisterInterchainAccount) (*MsgRegisterInterchainAccountResponse, error) + // SendTx defines a rpc handler for MsgSendTx. + SendTx(context.Context, *MsgSendTx) (*MsgSendTxResponse, error) + // UpdateParams defines a rpc handler for MsgUpdateParams. + UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) +} + +// UnimplementedMsgServer can be embedded to have forward compatible implementations. +type UnimplementedMsgServer struct { +} + +func (*UnimplementedMsgServer) RegisterInterchainAccount(ctx context.Context, req *MsgRegisterInterchainAccount) (*MsgRegisterInterchainAccountResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RegisterInterchainAccount not implemented") +} +func (*UnimplementedMsgServer) SendTx(ctx context.Context, req *MsgSendTx) (*MsgSendTxResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SendTx not implemented") +} +func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") +} + +func RegisterMsgServer(s grpc1.Server, srv MsgServer) { + s.RegisterService(&_Msg_serviceDesc, srv) +} + +func _Msg_RegisterInterchainAccount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgRegisterInterchainAccount) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).RegisterInterchainAccount(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.applications.interchain_accounts.controller.v1.Msg/RegisterInterchainAccount", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).RegisterInterchainAccount(ctx, req.(*MsgRegisterInterchainAccount)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_SendTx_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgSendTx) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).SendTx(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.applications.interchain_accounts.controller.v1.Msg/SendTx", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).SendTx(ctx, req.(*MsgSendTx)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUpdateParams) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).UpdateParams(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.applications.interchain_accounts.controller.v1.Msg/UpdateParams", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).UpdateParams(ctx, req.(*MsgUpdateParams)) + } + return interceptor(ctx, in, info, handler) +} + +var _Msg_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ibc.applications.interchain_accounts.controller.v1.Msg", + HandlerType: (*MsgServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "RegisterInterchainAccount", + Handler: _Msg_RegisterInterchainAccount_Handler, + }, + { + MethodName: "SendTx", + Handler: _Msg_SendTx_Handler, + }, + { + MethodName: "UpdateParams", + Handler: _Msg_UpdateParams_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "ibc/applications/interchain_accounts/controller/v1/tx.proto", +} + +func (m *MsgRegisterInterchainAccount) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgRegisterInterchainAccount) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgRegisterInterchainAccount) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Ordering != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.Ordering)) + i-- + dAtA[i] = 0x20 + } + if len(m.Version) > 0 { + i -= len(m.Version) + copy(dAtA[i:], m.Version) + i = encodeVarintTx(dAtA, i, uint64(len(m.Version))) + i-- + dAtA[i] = 0x1a + } + if len(m.ConnectionId) > 0 { + i -= len(m.ConnectionId) + copy(dAtA[i:], m.ConnectionId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ConnectionId))) + i-- + dAtA[i] = 0x12 + } + if len(m.Owner) > 0 { + i -= len(m.Owner) + copy(dAtA[i:], m.Owner) + i = encodeVarintTx(dAtA, i, uint64(len(m.Owner))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgRegisterInterchainAccountResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgRegisterInterchainAccountResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgRegisterInterchainAccountResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintTx(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0x12 + } + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgSendTx) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgSendTx) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSendTx) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.RelativeTimeout != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.RelativeTimeout)) + i-- + dAtA[i] = 0x20 + } + { + size, err := m.PacketData.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.ConnectionId) > 0 { + i -= len(m.ConnectionId) + copy(dAtA[i:], m.ConnectionId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ConnectionId))) + i-- + dAtA[i] = 0x12 + } + if len(m.Owner) > 0 { + i -= len(m.Owner) + copy(dAtA[i:], m.Owner) + i = encodeVarintTx(dAtA, i, uint64(len(m.Owner))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgSendTxResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgSendTxResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSendTxResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Sequence != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *MsgUpdateParams) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParams) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgUpdateParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgRegisterInterchainAccount) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Owner) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ConnectionId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Version) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.Ordering != 0 { + n += 1 + sovTx(uint64(m.Ordering)) + } + return n +} + +func (m *MsgRegisterInterchainAccountResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgSendTx) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Owner) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ConnectionId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.PacketData.Size() + n += 1 + l + sovTx(uint64(l)) + if m.RelativeTimeout != 0 { + n += 1 + sovTx(uint64(m.RelativeTimeout)) + } + return n +} + +func (m *MsgSendTxResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Sequence != 0 { + n += 1 + sovTx(uint64(m.Sequence)) + } + return n +} + +func (m *MsgUpdateParams) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Params.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgUpdateParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgRegisterInterchainAccount) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgRegisterInterchainAccount: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgRegisterInterchainAccount: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Owner", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Owner = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectionId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ConnectionId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Version = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Ordering", wireType) + } + m.Ordering = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Ordering |= types.Order(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgRegisterInterchainAccountResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgRegisterInterchainAccountResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgRegisterInterchainAccountResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgSendTx) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgSendTx: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSendTx: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Owner", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Owner = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectionId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ConnectionId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PacketData", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.PacketData.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RelativeTimeout", wireType) + } + m.RelativeTimeout = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.RelativeTimeout |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgSendTxResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgSendTxResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSendTxResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateParams: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParams: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTx(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/apps/27-interchain-accounts/doc.go b/modules/apps/27-interchain-accounts/doc.go new file mode 100644 index 0000000..1fd998d --- /dev/null +++ b/modules/apps/27-interchain-accounts/doc.go @@ -0,0 +1,8 @@ +/* +Package ica implements the packet data structure, state machine handling logic, +and encoding details for the account management system over an IBC channel +between separate chains. +This implementation is based off the ICS 27 specification +(https://github.com/cosmos/ibc/tree/main/spec/app/ics-027-interchain-accounts) +*/ +package ica diff --git a/modules/apps/27-interchain-accounts/genesis/types/genesis.go b/modules/apps/27-interchain-accounts/genesis/types/genesis.go new file mode 100644 index 0000000..9f388f0 --- /dev/null +++ b/modules/apps/27-interchain-accounts/genesis/types/genesis.go @@ -0,0 +1,128 @@ +package types + +import ( + controllertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + hosttypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +// DefaultGenesis creates and returns the interchain accounts GenesisState +func DefaultGenesis() *GenesisState { + return &GenesisState{ + ControllerGenesisState: DefaultControllerGenesis(), + HostGenesisState: DefaultHostGenesis(), + } +} + +// NewGenesisState creates and returns a new GenesisState instance from the provided controller and host genesis state types +func NewGenesisState(controllerGenesisState ControllerGenesisState, hostGenesisState HostGenesisState) *GenesisState { + return &GenesisState{ + ControllerGenesisState: controllerGenesisState, + HostGenesisState: hostGenesisState, + } +} + +// Validate performs basic validation of the interchain accounts GenesisState +func (gs GenesisState) Validate() error { + if err := gs.ControllerGenesisState.Validate(); err != nil { + return err + } + + return gs.HostGenesisState.Validate() +} + +// DefaultControllerGenesis creates and returns the default interchain accounts ControllerGenesisState +func DefaultControllerGenesis() ControllerGenesisState { + return ControllerGenesisState{ + Params: controllertypes.DefaultParams(), + } +} + +// NewControllerGenesisState creates a returns a new ControllerGenesisState instance +func NewControllerGenesisState(channels []ActiveChannel, accounts []RegisteredInterchainAccount, ports []string, controllerParams controllertypes.Params) ControllerGenesisState { + return ControllerGenesisState{ + ActiveChannels: channels, + InterchainAccounts: accounts, + Ports: ports, + Params: controllerParams, + } +} + +// Validate performs basic validation of the ControllerGenesisState +func (gs ControllerGenesisState) Validate() error { + for _, ch := range gs.ActiveChannels { + if err := host.ChannelIdentifierValidator(ch.ChannelId); err != nil { + return err + } + + if err := host.PortIdentifierValidator(ch.PortId); err != nil { + return err + } + } + + for _, acc := range gs.InterchainAccounts { + if err := host.PortIdentifierValidator(acc.PortId); err != nil { + return err + } + + if err := icatypes.ValidateAccountAddress(acc.AccountAddress); err != nil { + return err + } + } + + for _, port := range gs.Ports { + if err := host.PortIdentifierValidator(port); err != nil { + return err + } + } + + return nil +} + +// DefaultHostGenesis creates and returns the default interchain accounts HostGenesisState +func DefaultHostGenesis() HostGenesisState { + return HostGenesisState{ + Port: icatypes.HostPortID, + Params: hosttypes.DefaultParams(), + } +} + +// NewHostGenesisState creates a returns a new HostGenesisState instance +func NewHostGenesisState(channels []ActiveChannel, accounts []RegisteredInterchainAccount, port string, hostParams hosttypes.Params) HostGenesisState { + return HostGenesisState{ + ActiveChannels: channels, + InterchainAccounts: accounts, + Port: port, + Params: hostParams, + } +} + +// Validate performs basic validation of the HostGenesisState +func (gs HostGenesisState) Validate() error { + for _, ch := range gs.ActiveChannels { + if err := host.ChannelIdentifierValidator(ch.ChannelId); err != nil { + return err + } + + if err := host.PortIdentifierValidator(ch.PortId); err != nil { + return err + } + } + + for _, acc := range gs.InterchainAccounts { + if err := host.PortIdentifierValidator(acc.PortId); err != nil { + return err + } + + if err := icatypes.ValidateAccountAddress(acc.AccountAddress); err != nil { + return err + } + } + + if err := host.PortIdentifierValidator(gs.Port); err != nil { + return err + } + + return gs.Params.Validate() +} diff --git a/modules/apps/27-interchain-accounts/genesis/types/genesis.pb.go b/modules/apps/27-interchain-accounts/genesis/types/genesis.pb.go new file mode 100644 index 0000000..09c8b93 --- /dev/null +++ b/modules/apps/27-interchain-accounts/genesis/types/genesis.pb.go @@ -0,0 +1,1690 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/applications/interchain_accounts/genesis/v1/genesis.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + types "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + types1 "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// GenesisState defines the interchain accounts genesis state +type GenesisState struct { + ControllerGenesisState ControllerGenesisState `protobuf:"bytes,1,opt,name=controller_genesis_state,json=controllerGenesisState,proto3" json:"controller_genesis_state"` + HostGenesisState HostGenesisState `protobuf:"bytes,2,opt,name=host_genesis_state,json=hostGenesisState,proto3" json:"host_genesis_state"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_d4aa48c8e29a1947, []int{0} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetControllerGenesisState() ControllerGenesisState { + if m != nil { + return m.ControllerGenesisState + } + return ControllerGenesisState{} +} + +func (m *GenesisState) GetHostGenesisState() HostGenesisState { + if m != nil { + return m.HostGenesisState + } + return HostGenesisState{} +} + +// ControllerGenesisState defines the interchain accounts controller genesis state +type ControllerGenesisState struct { + ActiveChannels []ActiveChannel `protobuf:"bytes,1,rep,name=active_channels,json=activeChannels,proto3" json:"active_channels"` + InterchainAccounts []RegisteredInterchainAccount `protobuf:"bytes,2,rep,name=interchain_accounts,json=interchainAccounts,proto3" json:"interchain_accounts"` + Ports []string `protobuf:"bytes,3,rep,name=ports,proto3" json:"ports,omitempty"` + Params types.Params `protobuf:"bytes,4,opt,name=params,proto3" json:"params"` +} + +func (m *ControllerGenesisState) Reset() { *m = ControllerGenesisState{} } +func (m *ControllerGenesisState) String() string { return proto.CompactTextString(m) } +func (*ControllerGenesisState) ProtoMessage() {} +func (*ControllerGenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_d4aa48c8e29a1947, []int{1} +} +func (m *ControllerGenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ControllerGenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ControllerGenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ControllerGenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_ControllerGenesisState.Merge(m, src) +} +func (m *ControllerGenesisState) XXX_Size() int { + return m.Size() +} +func (m *ControllerGenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_ControllerGenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_ControllerGenesisState proto.InternalMessageInfo + +func (m *ControllerGenesisState) GetActiveChannels() []ActiveChannel { + if m != nil { + return m.ActiveChannels + } + return nil +} + +func (m *ControllerGenesisState) GetInterchainAccounts() []RegisteredInterchainAccount { + if m != nil { + return m.InterchainAccounts + } + return nil +} + +func (m *ControllerGenesisState) GetPorts() []string { + if m != nil { + return m.Ports + } + return nil +} + +func (m *ControllerGenesisState) GetParams() types.Params { + if m != nil { + return m.Params + } + return types.Params{} +} + +// HostGenesisState defines the interchain accounts host genesis state +type HostGenesisState struct { + ActiveChannels []ActiveChannel `protobuf:"bytes,1,rep,name=active_channels,json=activeChannels,proto3" json:"active_channels"` + InterchainAccounts []RegisteredInterchainAccount `protobuf:"bytes,2,rep,name=interchain_accounts,json=interchainAccounts,proto3" json:"interchain_accounts"` + Port string `protobuf:"bytes,3,opt,name=port,proto3" json:"port,omitempty"` + Params types1.Params `protobuf:"bytes,4,opt,name=params,proto3" json:"params"` +} + +func (m *HostGenesisState) Reset() { *m = HostGenesisState{} } +func (m *HostGenesisState) String() string { return proto.CompactTextString(m) } +func (*HostGenesisState) ProtoMessage() {} +func (*HostGenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_d4aa48c8e29a1947, []int{2} +} +func (m *HostGenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *HostGenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_HostGenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *HostGenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_HostGenesisState.Merge(m, src) +} +func (m *HostGenesisState) XXX_Size() int { + return m.Size() +} +func (m *HostGenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_HostGenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_HostGenesisState proto.InternalMessageInfo + +func (m *HostGenesisState) GetActiveChannels() []ActiveChannel { + if m != nil { + return m.ActiveChannels + } + return nil +} + +func (m *HostGenesisState) GetInterchainAccounts() []RegisteredInterchainAccount { + if m != nil { + return m.InterchainAccounts + } + return nil +} + +func (m *HostGenesisState) GetPort() string { + if m != nil { + return m.Port + } + return "" +} + +func (m *HostGenesisState) GetParams() types1.Params { + if m != nil { + return m.Params + } + return types1.Params{} +} + +// ActiveChannel contains a connection ID, port ID and associated active channel ID, as well as a boolean flag to +// indicate if the channel is middleware enabled +type ActiveChannel struct { + ConnectionId string `protobuf:"bytes,1,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"` + PortId string `protobuf:"bytes,2,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + ChannelId string `protobuf:"bytes,3,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + IsMiddlewareEnabled bool `protobuf:"varint,4,opt,name=is_middleware_enabled,json=isMiddlewareEnabled,proto3" json:"is_middleware_enabled,omitempty"` +} + +func (m *ActiveChannel) Reset() { *m = ActiveChannel{} } +func (m *ActiveChannel) String() string { return proto.CompactTextString(m) } +func (*ActiveChannel) ProtoMessage() {} +func (*ActiveChannel) Descriptor() ([]byte, []int) { + return fileDescriptor_d4aa48c8e29a1947, []int{3} +} +func (m *ActiveChannel) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ActiveChannel) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ActiveChannel.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ActiveChannel) XXX_Merge(src proto.Message) { + xxx_messageInfo_ActiveChannel.Merge(m, src) +} +func (m *ActiveChannel) XXX_Size() int { + return m.Size() +} +func (m *ActiveChannel) XXX_DiscardUnknown() { + xxx_messageInfo_ActiveChannel.DiscardUnknown(m) +} + +var xxx_messageInfo_ActiveChannel proto.InternalMessageInfo + +func (m *ActiveChannel) GetConnectionId() string { + if m != nil { + return m.ConnectionId + } + return "" +} + +func (m *ActiveChannel) GetPortId() string { + if m != nil { + return m.PortId + } + return "" +} + +func (m *ActiveChannel) GetChannelId() string { + if m != nil { + return m.ChannelId + } + return "" +} + +func (m *ActiveChannel) GetIsMiddlewareEnabled() bool { + if m != nil { + return m.IsMiddlewareEnabled + } + return false +} + +// RegisteredInterchainAccount contains a connection ID, port ID and associated interchain account address +type RegisteredInterchainAccount struct { + ConnectionId string `protobuf:"bytes,1,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"` + PortId string `protobuf:"bytes,2,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + AccountAddress string `protobuf:"bytes,3,opt,name=account_address,json=accountAddress,proto3" json:"account_address,omitempty"` +} + +func (m *RegisteredInterchainAccount) Reset() { *m = RegisteredInterchainAccount{} } +func (m *RegisteredInterchainAccount) String() string { return proto.CompactTextString(m) } +func (*RegisteredInterchainAccount) ProtoMessage() {} +func (*RegisteredInterchainAccount) Descriptor() ([]byte, []int) { + return fileDescriptor_d4aa48c8e29a1947, []int{4} +} +func (m *RegisteredInterchainAccount) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RegisteredInterchainAccount) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RegisteredInterchainAccount.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *RegisteredInterchainAccount) XXX_Merge(src proto.Message) { + xxx_messageInfo_RegisteredInterchainAccount.Merge(m, src) +} +func (m *RegisteredInterchainAccount) XXX_Size() int { + return m.Size() +} +func (m *RegisteredInterchainAccount) XXX_DiscardUnknown() { + xxx_messageInfo_RegisteredInterchainAccount.DiscardUnknown(m) +} + +var xxx_messageInfo_RegisteredInterchainAccount proto.InternalMessageInfo + +func (m *RegisteredInterchainAccount) GetConnectionId() string { + if m != nil { + return m.ConnectionId + } + return "" +} + +func (m *RegisteredInterchainAccount) GetPortId() string { + if m != nil { + return m.PortId + } + return "" +} + +func (m *RegisteredInterchainAccount) GetAccountAddress() string { + if m != nil { + return m.AccountAddress + } + return "" +} + +func init() { + proto.RegisterType((*GenesisState)(nil), "ibc.applications.interchain_accounts.genesis.v1.GenesisState") + proto.RegisterType((*ControllerGenesisState)(nil), "ibc.applications.interchain_accounts.genesis.v1.ControllerGenesisState") + proto.RegisterType((*HostGenesisState)(nil), "ibc.applications.interchain_accounts.genesis.v1.HostGenesisState") + proto.RegisterType((*ActiveChannel)(nil), "ibc.applications.interchain_accounts.genesis.v1.ActiveChannel") + proto.RegisterType((*RegisteredInterchainAccount)(nil), "ibc.applications.interchain_accounts.genesis.v1.RegisteredInterchainAccount") +} + +func init() { + proto.RegisterFile("ibc/applications/interchain_accounts/genesis/v1/genesis.proto", fileDescriptor_d4aa48c8e29a1947) +} + +var fileDescriptor_d4aa48c8e29a1947 = []byte{ + // 591 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x95, 0xcf, 0x8b, 0xd3, 0x40, + 0x14, 0xc7, 0x9b, 0x74, 0x5d, 0xed, 0xec, 0x0f, 0x97, 0xd9, 0x75, 0x0d, 0x2b, 0xc6, 0x52, 0x0f, + 0xf6, 0xd2, 0xc4, 0x56, 0x61, 0x41, 0x50, 0xe8, 0x16, 0x59, 0x0b, 0x2a, 0x12, 0x2f, 0xe2, 0x25, + 0x4c, 0x27, 0x43, 0x32, 0x90, 0x64, 0x42, 0xde, 0xb4, 0xe2, 0x59, 0xc1, 0xa3, 0xfe, 0x09, 0xfe, + 0x39, 0x7b, 0xdc, 0xa3, 0x27, 0x91, 0xf6, 0x0f, 0xf0, 0x2f, 0x10, 0x64, 0x26, 0xd9, 0xb6, 0xd6, + 0x2a, 0x2d, 0x1e, 0x3d, 0x65, 0xe6, 0x7d, 0xf3, 0xbe, 0xef, 0x93, 0xf7, 0x92, 0x0c, 0x7a, 0xc8, + 0x07, 0xd4, 0x25, 0x59, 0x16, 0x73, 0x4a, 0x24, 0x17, 0x29, 0xb8, 0x3c, 0x95, 0x2c, 0xa7, 0x11, + 0xe1, 0xa9, 0x4f, 0x28, 0x15, 0xc3, 0x54, 0x82, 0x1b, 0xb2, 0x94, 0x01, 0x07, 0x77, 0xd4, 0xbe, + 0x58, 0x3a, 0x59, 0x2e, 0xa4, 0xc0, 0x2e, 0x1f, 0x50, 0x67, 0x3e, 0xdd, 0x59, 0x92, 0xee, 0x5c, + 0xe4, 0x8c, 0xda, 0x47, 0x07, 0xa1, 0x08, 0x85, 0xce, 0x75, 0xd5, 0xaa, 0xb0, 0x39, 0xea, 0xad, + 0x44, 0x41, 0x45, 0x2a, 0x73, 0x11, 0xc7, 0x2c, 0x57, 0x20, 0xb3, 0x5d, 0x69, 0x72, 0xbc, 0x92, + 0x49, 0x24, 0x40, 0xaa, 0x74, 0x75, 0x2d, 0x12, 0x1b, 0x1f, 0x4d, 0xb4, 0x7d, 0x5a, 0x20, 0xbe, + 0x94, 0x44, 0x32, 0xfc, 0xc1, 0x40, 0xd6, 0xcc, 0xde, 0x2f, 0xf1, 0x7d, 0x50, 0xa2, 0x65, 0xd4, + 0x8d, 0xe6, 0x56, 0xe7, 0xd4, 0x59, 0xf3, 0xc9, 0x9d, 0xde, 0xd4, 0x70, 0xbe, 0xd6, 0xc9, 0xc6, + 0xd9, 0xd7, 0x5b, 0x15, 0xef, 0x90, 0x2e, 0x55, 0xf1, 0x10, 0x61, 0x05, 0xba, 0x80, 0x60, 0x6a, + 0x84, 0xee, 0xda, 0x08, 0x4f, 0x04, 0xc8, 0x25, 0xc5, 0xf7, 0xa2, 0x85, 0x78, 0xe3, 0x87, 0x89, + 0x0e, 0x97, 0xf3, 0xe2, 0x04, 0x5d, 0x25, 0x54, 0xf2, 0x11, 0xf3, 0x69, 0x44, 0xd2, 0x94, 0xc5, + 0x60, 0x19, 0xf5, 0x6a, 0x73, 0xab, 0xf3, 0x68, 0x6d, 0x9c, 0xae, 0xf6, 0xe9, 0x15, 0x36, 0x25, + 0xcb, 0x2e, 0x99, 0x0f, 0x02, 0x7e, 0x67, 0xa0, 0xfd, 0x25, 0x36, 0x96, 0xa9, 0x6b, 0x3e, 0x5d, + 0xbb, 0xa6, 0xc7, 0x42, 0x0e, 0x92, 0xe5, 0x2c, 0xe8, 0x4f, 0x6f, 0xec, 0x16, 0xf7, 0x95, 0x04, + 0x98, 0x2f, 0x0a, 0x80, 0x0f, 0xd0, 0xa5, 0x4c, 0xe4, 0x12, 0xac, 0x6a, 0xbd, 0xda, 0xac, 0x79, + 0xc5, 0x06, 0xbf, 0x42, 0x9b, 0x19, 0xc9, 0x49, 0x02, 0xd6, 0x86, 0x1e, 0xc8, 0x83, 0xd5, 0x68, + 0xe6, 0x5e, 0xdc, 0x51, 0xdb, 0x79, 0xa1, 0x1d, 0xca, 0xda, 0xa5, 0x5f, 0xe3, 0xbb, 0x89, 0xf6, + 0x16, 0x87, 0xf5, 0x7f, 0x76, 0x1e, 0xa3, 0x0d, 0xd5, 0x6c, 0xab, 0x5a, 0x37, 0x9a, 0x35, 0x4f, + 0xaf, 0xb1, 0xb7, 0xd0, 0xf7, 0xfb, 0xab, 0xb1, 0xe8, 0x2f, 0xfe, 0x4f, 0x1d, 0xff, 0x6c, 0xa0, + 0x9d, 0x5f, 0xba, 0x82, 0x6f, 0xa3, 0x1d, 0x2a, 0xd2, 0x94, 0x51, 0xe5, 0xe8, 0xf3, 0x40, 0x7f, + 0xf8, 0x35, 0x6f, 0x7b, 0x16, 0xec, 0x07, 0xf8, 0x3a, 0xba, 0xac, 0x90, 0x94, 0x6c, 0x6a, 0x79, + 0x53, 0x6d, 0xfb, 0x01, 0xbe, 0x89, 0x50, 0x39, 0x25, 0xa5, 0x15, 0xf4, 0xb5, 0x32, 0xd2, 0x0f, + 0x70, 0x07, 0x5d, 0xe3, 0xe0, 0x27, 0x3c, 0x08, 0x62, 0xf6, 0x86, 0xe4, 0xcc, 0x67, 0x29, 0x19, + 0xc4, 0x2c, 0xd0, 0x4f, 0x74, 0xc5, 0xdb, 0xe7, 0xf0, 0x6c, 0xaa, 0x3d, 0x2e, 0xa4, 0xc6, 0x7b, + 0x03, 0xdd, 0xf8, 0x4b, 0x13, 0xff, 0x11, 0xf8, 0x8e, 0x7a, 0xbb, 0xb4, 0x91, 0x4f, 0x82, 0x20, + 0x67, 0x00, 0x25, 0xf5, 0x6e, 0x19, 0xee, 0x16, 0xd1, 0x93, 0xe8, 0x6c, 0x6c, 0x1b, 0xe7, 0x63, + 0xdb, 0xf8, 0x36, 0xb6, 0x8d, 0x4f, 0x13, 0xbb, 0x72, 0x3e, 0xb1, 0x2b, 0x5f, 0x26, 0x76, 0xe5, + 0xf5, 0xf3, 0x90, 0xcb, 0x68, 0x38, 0x70, 0xa8, 0x48, 0x5c, 0x2a, 0x20, 0x11, 0xa0, 0x8e, 0x87, + 0x56, 0x28, 0xdc, 0x51, 0xfb, 0xae, 0x9b, 0x88, 0x60, 0x18, 0x33, 0x50, 0x7f, 0x68, 0x70, 0x3b, + 0xc7, 0xad, 0xd9, 0x88, 0x5a, 0xbf, 0x9d, 0x33, 0xf2, 0x6d, 0xc6, 0x60, 0xb0, 0xa9, 0x7f, 0xcf, + 0xf7, 0x7e, 0x06, 0x00, 0x00, 0xff, 0xff, 0x31, 0x77, 0x77, 0x27, 0xa4, 0x06, 0x00, 0x00, +} + +func (m *GenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.HostGenesisState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.ControllerGenesisState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *ControllerGenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ControllerGenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ControllerGenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + if len(m.Ports) > 0 { + for iNdEx := len(m.Ports) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Ports[iNdEx]) + copy(dAtA[i:], m.Ports[iNdEx]) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.Ports[iNdEx]))) + i-- + dAtA[i] = 0x1a + } + } + if len(m.InterchainAccounts) > 0 { + for iNdEx := len(m.InterchainAccounts) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.InterchainAccounts[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.ActiveChannels) > 0 { + for iNdEx := len(m.ActiveChannels) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ActiveChannels[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *HostGenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *HostGenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *HostGenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + if len(m.Port) > 0 { + i -= len(m.Port) + copy(dAtA[i:], m.Port) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.Port))) + i-- + dAtA[i] = 0x1a + } + if len(m.InterchainAccounts) > 0 { + for iNdEx := len(m.InterchainAccounts) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.InterchainAccounts[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.ActiveChannels) > 0 { + for iNdEx := len(m.ActiveChannels) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ActiveChannels[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *ActiveChannel) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ActiveChannel) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ActiveChannel) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.IsMiddlewareEnabled { + i-- + if m.IsMiddlewareEnabled { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x20 + } + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x1a + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0x12 + } + if len(m.ConnectionId) > 0 { + i -= len(m.ConnectionId) + copy(dAtA[i:], m.ConnectionId) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.ConnectionId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *RegisteredInterchainAccount) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RegisteredInterchainAccount) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RegisteredInterchainAccount) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.AccountAddress) > 0 { + i -= len(m.AccountAddress) + copy(dAtA[i:], m.AccountAddress) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.AccountAddress))) + i-- + dAtA[i] = 0x1a + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0x12 + } + if len(m.ConnectionId) > 0 { + i -= len(m.ConnectionId) + copy(dAtA[i:], m.ConnectionId) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.ConnectionId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ControllerGenesisState.Size() + n += 1 + l + sovGenesis(uint64(l)) + l = m.HostGenesisState.Size() + n += 1 + l + sovGenesis(uint64(l)) + return n +} + +func (m *ControllerGenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.ActiveChannels) > 0 { + for _, e := range m.ActiveChannels { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.InterchainAccounts) > 0 { + for _, e := range m.InterchainAccounts { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.Ports) > 0 { + for _, s := range m.Ports { + l = len(s) + n += 1 + l + sovGenesis(uint64(l)) + } + } + l = m.Params.Size() + n += 1 + l + sovGenesis(uint64(l)) + return n +} + +func (m *HostGenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.ActiveChannels) > 0 { + for _, e := range m.ActiveChannels { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.InterchainAccounts) > 0 { + for _, e := range m.InterchainAccounts { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + l = len(m.Port) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + l = m.Params.Size() + n += 1 + l + sovGenesis(uint64(l)) + return n +} + +func (m *ActiveChannel) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ConnectionId) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + if m.IsMiddlewareEnabled { + n += 2 + } + return n +} + +func (m *RegisteredInterchainAccount) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ConnectionId) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + l = len(m.AccountAddress) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ControllerGenesisState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ControllerGenesisState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field HostGenesisState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.HostGenesisState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ControllerGenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ControllerGenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ControllerGenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ActiveChannels", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ActiveChannels = append(m.ActiveChannels, ActiveChannel{}) + if err := m.ActiveChannels[len(m.ActiveChannels)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InterchainAccounts", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.InterchainAccounts = append(m.InterchainAccounts, RegisteredInterchainAccount{}) + if err := m.InterchainAccounts[len(m.InterchainAccounts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Ports", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Ports = append(m.Ports, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *HostGenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: HostGenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: HostGenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ActiveChannels", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ActiveChannels = append(m.ActiveChannels, ActiveChannel{}) + if err := m.ActiveChannels[len(m.ActiveChannels)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InterchainAccounts", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.InterchainAccounts = append(m.InterchainAccounts, RegisteredInterchainAccount{}) + if err := m.InterchainAccounts[len(m.InterchainAccounts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Port", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Port = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ActiveChannel) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ActiveChannel: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ActiveChannel: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectionId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ConnectionId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IsMiddlewareEnabled", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.IsMiddlewareEnabled = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *RegisteredInterchainAccount) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RegisteredInterchainAccount: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RegisteredInterchainAccount: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectionId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ConnectionId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AccountAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AccountAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/apps/27-interchain-accounts/genesis/types/genesis_test.go b/modules/apps/27-interchain-accounts/genesis/types/genesis_test.go new file mode 100644 index 0000000..e402093 --- /dev/null +++ b/modules/apps/27-interchain-accounts/genesis/types/genesis_test.go @@ -0,0 +1,332 @@ +package types_test + +import ( + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + controllertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + genesistypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/genesis/types" + hosttypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +var ( + // TestOwnerAddress defines a reusable bech32 address for testing purposes + TestOwnerAddress = "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs" + + // TestPortID defines a reusable port identifier for testing purposes + TestPortID, _ = icatypes.NewControllerPortID(TestOwnerAddress) +) + +type GenesisTypesTestSuite struct { + testifysuite.Suite +} + +func TestGenesisTypesTestSuite(t *testing.T) { + testifysuite.Run(t, new(GenesisTypesTestSuite)) +} + +func (suite *GenesisTypesTestSuite) TestValidateGenesisState() { + var genesisState genesistypes.GenesisState + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() {}, + nil, + }, + { + "failed to validate - empty value", + func() { + genesisState = genesistypes.GenesisState{} + }, + host.ErrInvalidID, + }, + { + "failed to validate - invalid controller genesis", + func() { + genesisState = *genesistypes.NewGenesisState(genesistypes.ControllerGenesisState{Ports: []string{"invalid|port"}}, genesistypes.DefaultHostGenesis()) + }, + host.ErrInvalidID, + }, + { + "failed to validate - invalid host genesis", + func() { + genesisState = *genesistypes.NewGenesisState(genesistypes.DefaultControllerGenesis(), genesistypes.HostGenesisState{}) + }, + host.ErrInvalidID, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + genesisState = *genesistypes.DefaultGenesis() + + tc.malleate() // malleate mutates test data + + err := genesisState.Validate() + + if tc.expErr == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().Error(err, tc.name) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *GenesisTypesTestSuite) TestValidateControllerGenesisState() { + var genesisState genesistypes.ControllerGenesisState + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() {}, + nil, + }, + { + "failed to validate active channel - invalid port identifier", + func() { + activeChannels := []genesistypes.ActiveChannel{ + { + PortId: "invalid|port", + ChannelId: ibctesting.FirstChannelID, + }, + } + + genesisState = genesistypes.NewControllerGenesisState(activeChannels, []genesistypes.RegisteredInterchainAccount{}, []string{}, controllertypes.DefaultParams()) + }, + host.ErrInvalidID, + }, + { + "failed to validate active channel - invalid channel identifier", + func() { + activeChannels := []genesistypes.ActiveChannel{ + { + PortId: TestPortID, + ChannelId: "invalid|channel", + }, + } + + genesisState = genesistypes.NewControllerGenesisState(activeChannels, []genesistypes.RegisteredInterchainAccount{}, []string{}, controllertypes.DefaultParams()) + }, + host.ErrInvalidID, + }, + { + "failed to validate registered account - invalid port identifier", + func() { + activeChannels := []genesistypes.ActiveChannel{ + { + PortId: TestPortID, + ChannelId: ibctesting.FirstChannelID, + }, + } + + registeredAccounts := []genesistypes.RegisteredInterchainAccount{ + { + PortId: "invalid|port", + AccountAddress: TestOwnerAddress, + }, + } + + genesisState = genesistypes.NewControllerGenesisState(activeChannels, registeredAccounts, []string{}, controllertypes.DefaultParams()) + }, + host.ErrInvalidID, + }, + { + "failed to validate registered account - invalid owner address", + func() { + activeChannels := []genesistypes.ActiveChannel{ + { + PortId: TestPortID, + ChannelId: ibctesting.FirstChannelID, + }, + } + + registeredAccounts := []genesistypes.RegisteredInterchainAccount{ + { + PortId: TestPortID, + AccountAddress: "", + }, + } + + genesisState = genesistypes.NewControllerGenesisState(activeChannels, registeredAccounts, []string{}, controllertypes.DefaultParams()) + }, + icatypes.ErrInvalidAccountAddress, + }, + { + "failed to validate controller ports - invalid port identifier", + func() { + activeChannels := []genesistypes.ActiveChannel{ + { + PortId: TestPortID, + ChannelId: ibctesting.FirstChannelID, + }, + } + + registeredAccounts := []genesistypes.RegisteredInterchainAccount{ + { + PortId: TestPortID, + AccountAddress: TestOwnerAddress, + }, + } + + genesisState = genesistypes.NewControllerGenesisState(activeChannels, registeredAccounts, []string{"invalid|port"}, controllertypes.DefaultParams()) + }, + host.ErrInvalidID, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + genesisState = genesistypes.DefaultControllerGenesis() + + tc.malleate() // malleate mutates test data + + err := genesisState.Validate() + + if tc.expErr == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().Error(err, tc.name) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *GenesisTypesTestSuite) TestValidateHostGenesisState() { + var genesisState genesistypes.HostGenesisState + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() {}, + nil, + }, + { + "failed to validate active channel - invalid port identifier", + func() { + activeChannels := []genesistypes.ActiveChannel{ + { + PortId: "invalid|port", + ChannelId: ibctesting.FirstChannelID, + }, + } + + genesisState = genesistypes.NewHostGenesisState(activeChannels, []genesistypes.RegisteredInterchainAccount{}, icatypes.HostPortID, hosttypes.DefaultParams()) + }, + host.ErrInvalidID, + }, + { + "failed to validate active channel - invalid channel identifier", + func() { + activeChannels := []genesistypes.ActiveChannel{ + { + PortId: TestPortID, + ChannelId: "invalid|channel", + }, + } + + genesisState = genesistypes.NewHostGenesisState(activeChannels, []genesistypes.RegisteredInterchainAccount{}, icatypes.HostPortID, hosttypes.DefaultParams()) + }, + host.ErrInvalidID, + }, + { + "failed to validate registered account - invalid port identifier", + func() { + activeChannels := []genesistypes.ActiveChannel{ + { + PortId: TestPortID, + ChannelId: ibctesting.FirstChannelID, + }, + } + + registeredAccounts := []genesistypes.RegisteredInterchainAccount{ + { + PortId: "invalid|port", + AccountAddress: TestOwnerAddress, + }, + } + + genesisState = genesistypes.NewHostGenesisState(activeChannels, registeredAccounts, icatypes.HostPortID, hosttypes.DefaultParams()) + }, + host.ErrInvalidID, + }, + { + "failed to validate registered account - invalid owner address", + func() { + activeChannels := []genesistypes.ActiveChannel{ + { + PortId: TestPortID, + ChannelId: ibctesting.FirstChannelID, + }, + } + + registeredAccounts := []genesistypes.RegisteredInterchainAccount{ + { + PortId: TestPortID, + AccountAddress: "", + }, + } + + genesisState = genesistypes.NewHostGenesisState(activeChannels, registeredAccounts, icatypes.HostPortID, hosttypes.DefaultParams()) + }, + icatypes.ErrInvalidAccountAddress, + }, + { + "failed to validate controller ports - invalid port identifier", + func() { + activeChannels := []genesistypes.ActiveChannel{ + { + PortId: TestPortID, + ChannelId: ibctesting.FirstChannelID, + }, + } + + registeredAccounts := []genesistypes.RegisteredInterchainAccount{ + { + PortId: TestPortID, + AccountAddress: TestOwnerAddress, + }, + } + + genesisState = genesistypes.NewHostGenesisState(activeChannels, registeredAccounts, "invalid|port", hosttypes.DefaultParams()) + }, + host.ErrInvalidID, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + genesisState = genesistypes.DefaultHostGenesis() + + tc.malleate() // malleate mutates test data + + err := genesisState.Validate() + + if tc.expErr == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().Error(err, tc.name) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} diff --git a/modules/apps/27-interchain-accounts/host/client/cli/cli.go b/modules/apps/27-interchain-accounts/host/client/cli/cli.go new file mode 100644 index 0000000..75d6450 --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/client/cli/cli.go @@ -0,0 +1,41 @@ +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" +) + +// GetQueryCmd returns the query commands for the ICA host submodule +func GetQueryCmd() *cobra.Command { + queryCmd := &cobra.Command{ + Use: "host", + Short: "IBC interchain accounts host query subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + } + + queryCmd.AddCommand( + GetCmdParams(), + GetCmdPacketEvents(), + ) + + return queryCmd +} + +// NewTxCmd creates and returns the tx command +func NewTxCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "host", + Short: "IBC interchain accounts host transaction subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand( + generatePacketDataCmd(), + ) + + return cmd +} diff --git a/modules/apps/27-interchain-accounts/host/client/cli/query.go b/modules/apps/27-interchain-accounts/host/client/cli/query.go new file mode 100644 index 0000000..e79f92c --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/client/cli/query.go @@ -0,0 +1,100 @@ +package cli + +import ( + "fmt" + "strconv" + "strings" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/x/auth/tx" + + abci "github.com/cometbft/cometbft/abci/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +// GetCmdParams returns the command handler for the host submodule parameter querying. +func GetCmdParams() *cobra.Command { + cmd := &cobra.Command{ + Use: "params", + Short: "Query the current interchain-accounts host submodule parameters", + Long: "Query the current interchain-accounts host submodule parameters", + Args: cobra.NoArgs, + Example: fmt.Sprintf("%s query interchain-accounts host params", version.AppName), + RunE: func(cmd *cobra.Command, _ []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + res, err := queryClient.Params(cmd.Context(), &types.QueryParamsRequest{}) + if err != nil { + return err + } + + return clientCtx.PrintProto(res.Params) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdPacketEvents returns the command handler for the host packet events querying. +func GetCmdPacketEvents() *cobra.Command { + cmd := &cobra.Command{ + Use: "packet-events [channel-id] [sequence]", + Short: "Query the interchain-accounts host submodule packet events", + Long: "Query the interchain-accounts host submodule packet events for a particular channel and sequence", + Args: cobra.ExactArgs(2), + Example: fmt.Sprintf("%s query interchain-accounts host packet-events channel-0 100", version.AppName), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + channelID, portID := args[0], icatypes.HostPortID + if err := host.ChannelIdentifierValidator(channelID); err != nil { + return err + } + + seq, err := strconv.ParseUint(args[1], 10, 64) + if err != nil { + return err + } + + searchEvents := []string{ + fmt.Sprintf("%s.%s='%s'", channeltypes.EventTypeRecvPacket, channeltypes.AttributeKeyDstChannel, channelID), + fmt.Sprintf("%s.%s='%s'", channeltypes.EventTypeRecvPacket, channeltypes.AttributeKeyDstPort, portID), + fmt.Sprintf("%s.%s='%d'", channeltypes.EventTypeRecvPacket, channeltypes.AttributeKeySequence, seq), + } + + result, err := tx.QueryTxsByEvents(clientCtx, 1, 1, strings.Join(searchEvents, " AND "), "") + if err != nil { + return err + } + + var resEvents []abci.Event + for _, r := range result.Txs { + resEvents = append(resEvents, r.Events...) + } + + return clientCtx.PrintString(sdk.StringifyEvents(resEvents).String()) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/modules/apps/27-interchain-accounts/host/client/cli/tx.go b/modules/apps/27-interchain-accounts/host/client/cli/tx.go new file mode 100644 index 0000000..013ab4e --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/client/cli/tx.go @@ -0,0 +1,162 @@ +package cli + +import ( + "encoding/json" + "fmt" + "slices" + + "github.com/cosmos/gogoproto/proto" + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/version" + + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" +) + +const ( + memoFlag string = "memo" + encodingFlag string = "encoding" +) + +func generatePacketDataCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "generate-packet-data [message]", + Short: "Generates protobuf or proto3 JSON encoded ICA packet data.", + Long: `generate-packet-data accepts a message string and serializes it (depending on the +encoding parameter) using protobuf or proto3 JSON into packet data which is outputted to stdout. +It can be used in conjunction with send-tx which submits pre-built packet data containing messages +to be executed on the host chain. The default encoding format is protobuf if none is specified; +otherwise the encoding flag can be used in combination with either "proto3" or "proto3json".`, + Example: fmt.Sprintf(`%s tx interchain-accounts host generate-packet-data '{ + "@type":"/cosmos.bank.v1beta1.MsgSend", + "from_address":"cosmos15ccshhmp0gsx29qpqq6g4zmltnnvgmyu9ueuadh9y2nc5zj0szls5gtddz", + "to_address":"cosmos10h9stc5v6ntgeygf5xf945njqq5h32r53uquvw", + "amount": [ + { + "denom": "stake", + "amount": "1000" + } + ] +}' --memo memo --encoding proto3json + + +%s tx interchain-accounts host generate-packet-data '[{ + "@type":"/cosmos.bank.v1beta1.MsgSend", + "from_address":"cosmos15ccshhmp0gsx29qpqq6g4zmltnnvgmyu9ueuadh9y2nc5zj0szls5gtddz", + "to_address":"cosmos10h9stc5v6ntgeygf5xf945njqq5h32r53uquvw", + "amount": [ + { + "denom": "stake", + "amount": "1000" + } + ] +}, +{ + "@type": "/cosmos.staking.v1beta1.MsgDelegate", + "delegator_address": "cosmos15ccshhmp0gsx29qpqq6g4zmltnnvgmyu9ueuadh9y2nc5zj0szls5gtddz", + "validator_address": "cosmosvaloper1qnk2n4nlkpw9xfqntladh74w6ujtulwnmxnh3k", + "amount": { + "denom": "stake", + "amount": "1000" + } +}]'`, version.AppName, version.AppName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry) + + memo, err := cmd.Flags().GetString(memoFlag) + if err != nil { + return err + } + + encoding, err := cmd.Flags().GetString(encodingFlag) + if err != nil { + return err + } + + if !slices.Contains([]string{icatypes.EncodingProtobuf, icatypes.EncodingProto3JSON}, encoding) { + return fmt.Errorf("unsupported encoding type: %s", encoding) + } + + packetDataBytes, err := generatePacketData(cdc, []byte(args[0]), memo, encoding) + if err != nil { + return err + } + + cmd.Println(string(packetDataBytes)) + + return nil + }, + } + + cmd.Flags().String(memoFlag, "", "optional memo to be included in the interchain accounts packet data") + cmd.Flags().String(encodingFlag, "", "optional encoding format of the messages in the interchain accounts packet data") + return cmd +} + +// generatePacketData takes in message bytes and a memo and serializes the message into an +// instance of InterchainAccountPacketData which is returned as bytes. +func generatePacketData(cdc *codec.ProtoCodec, msgBytes []byte, memo string, encoding string) ([]byte, error) { + protoMessages, err := convertBytesIntoProtoMessages(cdc, msgBytes) + if err != nil { + return nil, err + } + + return generateIcaPacketDataFromProtoMessages(cdc, protoMessages, memo, encoding) +} + +// convertBytesIntoProtoMessages returns a list of proto messages from bytes. The bytes can be in the form of a single +// message, or a json array of messages. +func convertBytesIntoProtoMessages(cdc *codec.ProtoCodec, msgBytes []byte) ([]proto.Message, error) { + var rawMessages []json.RawMessage + if err := json.Unmarshal(msgBytes, &rawMessages); err != nil { + // if we fail to unmarshal a list of messages, we assume we are just dealing with a single message. + // in this case we return a list of a single item. + var msg sdk.Msg + if err := cdc.UnmarshalInterfaceJSON(msgBytes, &msg); err != nil { + return nil, err + } + + return []proto.Message{msg}, nil + } + + sdkMessages := make([]proto.Message, len(rawMessages)) + for i, anyJSON := range rawMessages { + var msg sdk.Msg + if err := cdc.UnmarshalInterfaceJSON(anyJSON, &msg); err != nil { + return nil, err + } + + sdkMessages[i] = msg + } + + return sdkMessages, nil +} + +// generateIcaPacketDataFromProtoMessages generates ica packet data as bytes from a given set of proto encoded sdk messages and a memo. +func generateIcaPacketDataFromProtoMessages(cdc *codec.ProtoCodec, sdkMessages []proto.Message, memo string, encoding string) ([]byte, error) { + icaPacketDataBytes, err := icatypes.SerializeCosmosTx(cdc, sdkMessages, encoding) + if err != nil { + return nil, err + } + + icaPacketData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: icaPacketDataBytes, + Memo: memo, + } + + if err := icaPacketData.ValidateBasic(); err != nil { + return nil, err + } + + return cdc.MarshalJSON(&icaPacketData) +} diff --git a/modules/apps/27-interchain-accounts/host/client/cli/tx_test.go b/modules/apps/27-interchain-accounts/host/client/cli/tx_test.go new file mode 100644 index 0000000..6ad3b04 --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/client/cli/tx_test.go @@ -0,0 +1,163 @@ +package cli + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" +) + +const msgDelegateMessage = `{ + "@type": "/cosmos.staking.v1beta1.MsgDelegate", + "delegator_address": "cosmos15ccshhmp0gsx29qpqq6g4zmltnnvgmyu9ueuadh9y2nc5zj0szls5gtddz", + "validator_address": "cosmosvaloper1qnk2n4nlkpw9xfqntladh74w6ujtulwnmxnh3k", + "amount": { + "denom": "stake", + "amount": "1000" + } +}` + +const bankSendMessage = `{ + "@type":"/cosmos.bank.v1beta1.MsgSend", + "from_address":"cosmos15ccshhmp0gsx29qpqq6g4zmltnnvgmyu9ueuadh9y2nc5zj0szls5gtddz", + "to_address":"cosmos10h9stc5v6ntgeygf5xf945njqq5h32r53uquvw", + "amount": [ + { + "denom": "stake", + "amount": "1000" + } + ] +}` + +var multiMsg = fmt.Sprintf("[ %s, %s ]", msgDelegateMessage, bankSendMessage) + +func TestGeneratePacketData(t *testing.T) { + t.Helper() + tests := []struct { + name string + memo string + expectedPass bool + message string + registerInterfaceFn func(registry codectypes.InterfaceRegistry) + assertionFn func(t *testing.T, msgs []sdk.Msg) + }{ + { + name: "packet data generation succeeds (MsgDelegate & MsgSend)", + memo: "", + expectedPass: true, + message: multiMsg, + registerInterfaceFn: func(registry codectypes.InterfaceRegistry) { + stakingtypes.RegisterInterfaces(registry) + banktypes.RegisterInterfaces(registry) + }, + assertionFn: func(t *testing.T, msgs []sdk.Msg) { + t.Helper() + assertMsgDelegate(t, msgs[0]) + assertMsgBankSend(t, msgs[1]) + }, + }, + { + name: "packet data generation succeeds (MsgDelegate)", + memo: "non-empty-memo", + expectedPass: true, + message: msgDelegateMessage, + registerInterfaceFn: stakingtypes.RegisterInterfaces, + assertionFn: func(t *testing.T, msgs []sdk.Msg) { + t.Helper() + assertMsgDelegate(t, msgs[0]) + }, + }, + { + name: "packet data generation succeeds (MsgSend)", + memo: "non-empty-memo", + expectedPass: true, + message: bankSendMessage, + registerInterfaceFn: banktypes.RegisterInterfaces, + assertionFn: func(t *testing.T, msgs []sdk.Msg) { + t.Helper() + assertMsgBankSend(t, msgs[0]) + }, + }, + { + name: "empty memo is valid", + memo: "", + expectedPass: true, + message: msgDelegateMessage, + registerInterfaceFn: stakingtypes.RegisterInterfaces, + assertionFn: nil, + }, + { + name: "invalid message string", + expectedPass: false, + message: "", + }, + } + + encodings := []string{icatypes.EncodingProtobuf, icatypes.EncodingProto3JSON} + for _, encoding := range encodings { + for _, tc := range tests { + + ir := codectypes.NewInterfaceRegistry() + if tc.registerInterfaceFn != nil { + tc.registerInterfaceFn(ir) + } + + cdc := codec.NewProtoCodec(ir) + + t.Run(fmt.Sprintf("%s with %s encoding", tc.name, encoding), func(t *testing.T) { + bz, err := generatePacketData(cdc, []byte(tc.message), tc.memo, encoding) + + if tc.expectedPass { + require.NoError(t, err) + require.NotNil(t, bz) + + packetData := icatypes.InterchainAccountPacketData{} + err = cdc.UnmarshalJSON(bz, &packetData) + require.NoError(t, err) + + require.Equal(t, icatypes.EXECUTE_TX, packetData.Type) + require.Equal(t, tc.memo, packetData.Memo) + + data := packetData.Data + messages, err := icatypes.DeserializeCosmosTx(cdc, data, encoding) + + require.NoError(t, err) + require.NotNil(t, messages) + + if tc.assertionFn != nil { + tc.assertionFn(t, messages) + } + } else { + require.Error(t, err) + require.Nil(t, bz) + } + }) + } + } +} + +func assertMsgBankSend(t *testing.T, msg sdk.Msg) { //nolint:thelper + bankSendMsg, ok := msg.(*banktypes.MsgSend) + require.True(t, ok) + require.Equal(t, "cosmos15ccshhmp0gsx29qpqq6g4zmltnnvgmyu9ueuadh9y2nc5zj0szls5gtddz", bankSendMsg.FromAddress) + require.Equal(t, "cosmos10h9stc5v6ntgeygf5xf945njqq5h32r53uquvw", bankSendMsg.ToAddress) + require.Equal(t, "stake", bankSendMsg.Amount.GetDenomByIndex(0)) + require.Equal(t, uint64(1000), bankSendMsg.Amount[0].Amount.Uint64()) +} + +func assertMsgDelegate(t *testing.T, msg sdk.Msg) { //nolint:thelper + msgDelegate, ok := msg.(*stakingtypes.MsgDelegate) + require.True(t, ok) + require.Equal(t, "cosmos15ccshhmp0gsx29qpqq6g4zmltnnvgmyu9ueuadh9y2nc5zj0szls5gtddz", msgDelegate.DelegatorAddress) + require.Equal(t, "cosmosvaloper1qnk2n4nlkpw9xfqntladh74w6ujtulwnmxnh3k", msgDelegate.ValidatorAddress) + require.Equal(t, "stake", msgDelegate.Amount.Denom) + require.Equal(t, uint64(1000), msgDelegate.Amount.Amount.Uint64()) +} diff --git a/modules/apps/27-interchain-accounts/host/ibc_module.go b/modules/apps/27-interchain-accounts/host/ibc_module.go new file mode 100644 index 0000000..675b12d --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/ibc_module.go @@ -0,0 +1,175 @@ +package host + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/keeper" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var ( + _ porttypes.IBCModule = (*IBCModule)(nil) + _ porttypes.PacketDataUnmarshaler = (*IBCModule)(nil) +) + +// IBCModule implements the ICS26 interface for interchain accounts host chains +type IBCModule struct { + keeper keeper.Keeper +} + +// NewIBCModule creates a new IBCModule given the associated keeper +func NewIBCModule(k keeper.Keeper) IBCModule { + return IBCModule{ + keeper: k, + } +} + +// OnChanOpenInit implements the IBCModule interface +func (IBCModule) OnChanOpenInit( + _ sdk.Context, + _ channeltypes.Order, + _ []string, + _ string, + _ string, + _ channeltypes.Counterparty, + _ string, +) (string, error) { + return "", errorsmod.Wrap(icatypes.ErrInvalidChannelFlow, "channel handshake must be initiated by controller chain") +} + +// OnChanOpenTry implements the IBCModule interface +func (im IBCModule) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + if !im.keeper.GetParams(ctx).HostEnabled { + return "", types.ErrHostSubModuleDisabled + } + + return im.keeper.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, counterparty, counterpartyVersion) +} + +// OnChanOpenAck implements the IBCModule interface +func (IBCModule) OnChanOpenAck( + _ sdk.Context, + _, + _ string, + _ string, + _ string, +) error { + return errorsmod.Wrap(icatypes.ErrInvalidChannelFlow, "channel handshake must be initiated by controller chain") +} + +// OnChanOpenConfirm implements the IBCModule interface +func (im IBCModule) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + if !im.keeper.GetParams(ctx).HostEnabled { + return types.ErrHostSubModuleDisabled + } + + return im.keeper.OnChanOpenConfirm(ctx, portID, channelID) +} + +// OnChanCloseInit implements the IBCModule interface +func (IBCModule) OnChanCloseInit( + _ sdk.Context, + _ string, + _ string, +) error { + // Disallow user-initiated channel closing for interchain account channels + return errorsmod.Wrap(ibcerrors.ErrInvalidRequest, "user cannot close channel") +} + +// OnChanCloseConfirm implements the IBCModule interface +func (im IBCModule) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + return im.keeper.OnChanCloseConfirm(ctx, portID, channelID) +} + +// OnRecvPacket implements the IBCModule interface +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, + _ string, + packet channeltypes.Packet, + _ sdk.AccAddress, +) ibcexported.Acknowledgement { + if !im.keeper.GetParams(ctx).HostEnabled { + im.keeper.Logger(ctx).Info("host submodule is disabled") + keeper.EmitHostDisabledEvent(ctx, packet) + return channeltypes.NewErrorAcknowledgement(types.ErrHostSubModuleDisabled) + } + + txResponse, err := im.keeper.OnRecvPacket(ctx, packet) + ack := channeltypes.NewResultAcknowledgement(txResponse) + if err != nil { + ack = channeltypes.NewErrorAcknowledgement(err) + im.keeper.Logger(ctx).Error(fmt.Sprintf("%s sequence %d", err.Error(), packet.Sequence)) + } else { + im.keeper.Logger(ctx).Info("successfully handled packet", "sequence", packet.Sequence) + } + + // Emit an event indicating a successful or failed acknowledgement. + keeper.EmitAcknowledgementEvent(ctx, packet, ack, err) + + // NOTE: acknowledgement will be written synchronously during IBC handler execution. + return ack +} + +// OnAcknowledgementPacket implements the IBCModule interface +func (IBCModule) OnAcknowledgementPacket( + _ sdk.Context, + _ string, + _ channeltypes.Packet, + _ []byte, + _ sdk.AccAddress, +) error { + return errorsmod.Wrap(icatypes.ErrInvalidChannelFlow, "cannot receive acknowledgement on a host channel end, a host chain does not send a packet over the channel") +} + +// OnTimeoutPacket implements the IBCModule interface +func (IBCModule) OnTimeoutPacket( + _ sdk.Context, + _ string, + _ channeltypes.Packet, + _ sdk.AccAddress, +) error { + return errorsmod.Wrap(icatypes.ErrInvalidChannelFlow, "cannot cause a packet timeout on a host channel end, a host chain does not send a packet over the channel") +} + +// UnmarshalPacketData attempts to unmarshal the provided packet data bytes +// into an InterchainAccountPacketData. This function implements the optional +// PacketDataUnmarshaler interface required for ADR 008 support. +func (im IBCModule) UnmarshalPacketData(ctx sdk.Context, portID string, channelID string, bz []byte) (any, string, error) { + var data icatypes.InterchainAccountPacketData + err := data.UnmarshalJSON(bz) + if err != nil { + return nil, "", err + } + + version, ok := im.keeper.GetAppVersion(ctx, portID, channelID) + if !ok { + return nil, "", errorsmod.Wrapf(ibcerrors.ErrNotFound, "app version not found for port %s and channel %s", portID, channelID) + } + + return data, version, nil +} diff --git a/modules/apps/27-interchain-accounts/host/ibc_module_test.go b/modules/apps/27-interchain-accounts/host/ibc_module_test.go new file mode 100644 index 0000000..c49efda --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/ibc_module_test.go @@ -0,0 +1,755 @@ +package host_test + +import ( + "errors" + "strconv" + "testing" + + "github.com/cosmos/gogoproto/proto" + testifysuite "github.com/stretchr/testify/suite" + + sdkmath "cosmossdk.io/math" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + icahost "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +var ( + // TestOwnerAddress defines a reusable bech32 address for testing purposes + TestOwnerAddress = "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs" + + // TestPortID defines a reusable port identifier for testing purposes + TestPortID, _ = icatypes.NewControllerPortID(TestOwnerAddress) + + // TestVersion defines a reusable interchainaccounts version string for testing purposes + TestVersion = string(icatypes.ModuleCdc.MustMarshalJSON(&icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: ibctesting.FirstConnectionID, + HostConnectionId: ibctesting.FirstConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, + })) +) + +type InterchainAccountsTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains used for convenience and readability + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain +} + +func TestICATestSuite(t *testing.T) { + testifysuite.Run(t, new(InterchainAccountsTestSuite)) +} + +func (suite *InterchainAccountsTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) +} + +func NewICAPath(chainA, chainB *ibctesting.TestChain, ordering channeltypes.Order) *ibctesting.Path { + path := ibctesting.NewPath(chainA, chainB) + path.EndpointA.ChannelConfig.PortID = icatypes.HostPortID + path.EndpointB.ChannelConfig.PortID = icatypes.HostPortID + path.EndpointA.ChannelConfig.Order = ordering + path.EndpointB.ChannelConfig.Order = ordering + path.EndpointA.ChannelConfig.Version = TestVersion + path.EndpointB.ChannelConfig.Version = TestVersion + + return path +} + +func RegisterInterchainAccount(endpoint *ibctesting.Endpoint, owner string) error { + portID, err := icatypes.NewControllerPortID(owner) + if err != nil { + return err + } + + channelSequence := endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextChannelSequence(endpoint.Chain.GetContext()) + + if err := endpoint.Chain.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(endpoint.Chain.GetContext(), endpoint.ConnectionID, owner, endpoint.ChannelConfig.Version, endpoint.ChannelConfig.Order); err != nil { + return err + } + + // commit state changes for proof verification + endpoint.Chain.NextBlock() + + // update port/channel ids + endpoint.ChannelID = channeltypes.FormatChannelIdentifier(channelSequence) + endpoint.ChannelConfig.PortID = portID + + return nil +} + +// SetupICAPath invokes the InterchainAccounts entrypoint and subsequent channel handshake handlers +func SetupICAPath(path *ibctesting.Path, owner string) error { + if err := RegisterInterchainAccount(path.EndpointA, owner); err != nil { + return err + } + + if err := path.EndpointB.ChanOpenTry(); err != nil { + return err + } + + if err := path.EndpointA.ChanOpenAck(); err != nil { + return err + } + + return path.EndpointB.ChanOpenConfirm() +} + +// Test initiating a ChanOpenInit using the host chain instead of the controller chain +// ChainA is the controller chain. ChainB is the host chain +func (suite *InterchainAccountsTestSuite) TestChanOpenInit() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + suite.SetupTest() // reset + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + // use chainB (host) for ChanOpenInit + msg := channeltypes.NewMsgChannelOpenInit(path.EndpointB.ChannelConfig.PortID, icatypes.Version, ordering, []string{path.EndpointB.ConnectionID}, path.EndpointA.ChannelConfig.PortID, icatypes.ModuleName) + handler := suite.chainB.GetSimApp().MsgServiceRouter().Handler(msg) + _, err := handler(suite.chainB.GetContext(), msg) + + suite.Require().Error(err) + } +} + +func (suite *InterchainAccountsTestSuite) TestOnChanOpenTry() { + var ( + path *ibctesting.Path + channel *channeltypes.Channel + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", func() {}, nil, + }, + { + "account address generation is block dependent", func() { + icaHostAccount := icatypes.GenerateAddress(suite.chainB.GetContext(), path.EndpointB.ConnectionID, path.EndpointA.ChannelConfig.PortID) + err := suite.chainB.GetSimApp().BankKeeper.SendCoins(suite.chainB.GetContext(), suite.chainB.SenderAccount.GetAddress(), icaHostAccount, sdk.Coins{sdk.NewCoin("stake", sdkmath.NewInt(1))}) + suite.Require().NoError(err) + suite.Require().True(suite.chainB.GetSimApp().AccountKeeper.HasAccount(suite.chainB.GetContext(), icaHostAccount)) + + // ensure account registration is simulated in a separate block + suite.chainB.NextBlock() + }, nil, + }, + { + "success: ICA auth module callback returns error", func() { + // mock module callback should not be called on host side + suite.chainB.GetSimApp().ICAAuthModule.IBCApp.OnChanOpenTry = func(ctx sdk.Context, order channeltypes.Order, connectionHops []string, + portID, channelID string, + counterparty channeltypes.Counterparty, counterpartyVersion string, + ) (string, error) { + return "", errors.New("mock ica auth fails") + } + }, nil, + }, + { + "host submodule disabled", func() { + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), types.NewParams(false, []string{})) + }, types.ErrHostSubModuleDisabled, + }, + } + + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := RegisterInterchainAccount(path.EndpointA, TestOwnerAddress) + suite.Require().NoError(err) + path.EndpointB.ChannelID = ibctesting.FirstChannelID + + // default values + counterparty := channeltypes.NewCounterparty(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + channel = &channeltypes.Channel{ + State: channeltypes.TRYOPEN, + Ordering: ordering, + Counterparty: counterparty, + ConnectionHops: []string{path.EndpointB.ConnectionID}, + Version: path.EndpointB.ChannelConfig.Version, + } + + tc.malleate() + + // ensure channel on chainB is set in state + suite.chainB.GetSimApp().IBCKeeper.ChannelKeeper.SetChannel(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, *channel) + + cbs, ok := suite.chainB.App.GetIBCKeeper().PortKeeper.Route(path.EndpointB.ChannelConfig.PortID) + suite.Require().True(ok) + + version, err := cbs.OnChanOpenTry(suite.chainB.GetContext(), channel.Ordering, channel.ConnectionHops, + path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, channel.Counterparty, path.EndpointA.ChannelConfig.Version, + ) + + if tc.expErr == nil { + suite.Require().NoError(err) + + addr, exists := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointB.ConnectionID, counterparty.PortId) + suite.Require().True(exists) + suite.Require().NotNil(addr) + } else { + suite.Require().ErrorIs(err, tc.expErr) + suite.Require().Equal("", version) + } + }) + } + } +} + +// Test initiating a ChanOpenAck using the host chain instead of the controller chain +// ChainA is the controller chain. ChainB is the host chain +func (suite *InterchainAccountsTestSuite) TestChanOpenAck() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + suite.SetupTest() // reset + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := RegisterInterchainAccount(path.EndpointA, TestOwnerAddress) + suite.Require().NoError(err) + + err = path.EndpointB.ChanOpenTry() + suite.Require().NoError(err) + + // chainA maliciously sets channel to TRYOPEN + channel := channeltypes.NewChannel(channeltypes.TRYOPEN, channeltypes.ORDERED, channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID), []string{path.EndpointA.ConnectionID}, TestVersion) + suite.chainA.GetSimApp().GetIBCKeeper().ChannelKeeper.SetChannel(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, channel) + + // commit state changes so proof can be created + suite.chainA.NextBlock() + + err = path.EndpointB.UpdateClient() + suite.Require().NoError(err) + + // query proof from ChainA + channelKey := host.ChannelKey(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + tryProof, proofHeight := path.EndpointA.Chain.QueryProof(channelKey) + + // use chainB (host) for ChanOpenAck + msg := channeltypes.NewMsgChannelOpenAck(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, path.EndpointA.ChannelID, TestVersion, tryProof, proofHeight, icatypes.ModuleName) + handler := suite.chainB.GetSimApp().MsgServiceRouter().Handler(msg) + _, err = handler(suite.chainB.GetContext(), msg) + + suite.Require().Error(err) + } +} + +func (suite *InterchainAccountsTestSuite) TestOnChanOpenConfirm() { + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", func() {}, nil, + }, + { + "success: ICA auth module callback returns error", func() { + // mock module callback should not be called on host side + suite.chainB.GetSimApp().ICAAuthModule.IBCApp.OnChanOpenConfirm = func( + ctx sdk.Context, portID, channelID string, + ) error { + return errors.New("mock ica auth fails") + } + }, nil, + }, + { + "host submodule disabled", func() { + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), types.NewParams(false, []string{})) + }, types.ErrHostSubModuleDisabled, + }, + } + + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := RegisterInterchainAccount(path.EndpointA, TestOwnerAddress) + suite.Require().NoError(err) + + err = path.EndpointB.ChanOpenTry() + suite.Require().NoError(err) + + err = path.EndpointA.ChanOpenAck() + suite.Require().NoError(err) + + tc.malleate() + + cbs, ok := suite.chainB.App.GetIBCKeeper().PortKeeper.Route(path.EndpointB.ChannelConfig.PortID) + suite.Require().True(ok) + + err = cbs.OnChanOpenConfirm(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } + } +} + +// OnChanCloseInit on host (chainB) +func (suite *InterchainAccountsTestSuite) TestOnChanCloseInit() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + suite.SetupTest() // reset + + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + cbs, ok := suite.chainB.App.GetIBCKeeper().PortKeeper.Route(path.EndpointB.ChannelConfig.PortID) + suite.Require().True(ok) + + err = cbs.OnChanCloseInit( + suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, + ) + + suite.Require().Error(err) + } +} + +func (suite *InterchainAccountsTestSuite) TestOnChanCloseConfirm() { + var path *ibctesting.Path + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", func() {}, nil, + }, + } + + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + tc.malleate() // malleate mutates test data + + cbs, ok := suite.chainB.App.GetIBCKeeper().PortKeeper.Route(path.EndpointB.ChannelConfig.PortID) + suite.Require().True(ok) + + err = cbs.OnChanCloseConfirm( + suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } + } +} + +func (suite *InterchainAccountsTestSuite) TestOnRecvPacket() { + var packetData []byte + testCases := []struct { + name string + malleate func() + expAckSuccess bool + eventErrorMsg string + }{ + { + "success", func() {}, true, "", + }, + { + "host submodule disabled", func() { + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), types.NewParams(false, []string{})) + }, false, + types.ErrHostSubModuleDisabled.Error(), + }, + { + "success with ICA auth module callback failure", func() { + suite.chainB.GetSimApp().ICAAuthModule.IBCApp.OnRecvPacket = func( + ctx sdk.Context, channelVersion string, packet channeltypes.Packet, relayer sdk.AccAddress, + ) exported.Acknowledgement { + return channeltypes.NewErrorAcknowledgement(errors.New("failed OnRecvPacket mock callback")) + } + }, true, + "failed OnRecvPacket mock callback", + }, + { + "ICA OnRecvPacket fails - cannot unmarshal packet data", func() { + packetData = []byte("invalid data") + }, false, + "cannot unmarshal ICS-27 interchain account packet data: invalid type", + }, + } + + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + // send 100stake to interchain account wallet + amount, _ := sdk.ParseCoinsNormalized("100stake") + interchainAccountAddr, _ := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID) + bankMsg := &banktypes.MsgSend{FromAddress: suite.chainB.SenderAccount.GetAddress().String(), ToAddress: interchainAccountAddr, Amount: amount} + + _, err = suite.chainB.SendMsgs(bankMsg) + suite.Require().NoError(err) + + // build packet data + msg := &banktypes.MsgSend{ + FromAddress: interchainAccountAddr, + ToAddress: suite.chainB.SenderAccount.GetAddress().String(), + Amount: amount, + } + data, err := icatypes.SerializeCosmosTx(suite.chainA.GetSimApp().AppCodec(), []proto.Message{msg}, icatypes.EncodingProtobuf) + suite.Require().NoError(err) + + icaPacketData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + } + packetData = icaPacketData.GetBytes() + + // build expected ack + protoAny, err := codectypes.NewAnyWithValue(&banktypes.MsgSendResponse{}) + suite.Require().NoError(err) + + expectedTxResponse, err := proto.Marshal(&sdk.TxMsgData{ + MsgResponses: []*codectypes.Any{protoAny}, + }) + suite.Require().NoError(err) + + expectedAck := channeltypes.NewResultAcknowledgement(expectedTxResponse) + + params := types.NewParams(true, []string{sdk.MsgTypeURL(msg)}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + + // malleate packetData for test cases + tc.malleate() + + seq := uint64(1) + packet := channeltypes.NewPacket(packetData, seq, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.NewHeight(0, 100), 0) + + tc.malleate() + + cbs, ok := suite.chainB.App.GetIBCKeeper().PortKeeper.Route(path.EndpointB.ChannelConfig.PortID) + suite.Require().True(ok) + + ctx := suite.chainB.GetContext() + ack := cbs.OnRecvPacket(ctx, path.EndpointB.GetChannel().Version, packet, nil) + + expectedAttributes := []sdk.Attribute{ + sdk.NewAttribute(sdk.AttributeKeyModule, icatypes.ModuleName), + sdk.NewAttribute(icatypes.AttributeKeyHostChannelID, packet.GetDestChannel()), + sdk.NewAttribute(icatypes.AttributeKeyAckSuccess, strconv.FormatBool(ack.Success())), + } + + if tc.expAckSuccess { + suite.Require().True(ack.Success()) + suite.Require().Equal(expectedAck, ack) + + expectedEvents := sdk.Events{ + sdk.NewEvent( + icatypes.EventTypePacket, + expectedAttributes..., + ), + }.ToABCIEvents() + + expectedEvents = sdk.MarkEventsToIndex(expectedEvents, map[string]struct{}{}) + ibctesting.AssertEvents(&suite.Suite, expectedEvents, ctx.EventManager().Events().ToABCIEvents()) + + } else { + suite.Require().False(ack.Success()) + + expectedAttributes = append(expectedAttributes, sdk.NewAttribute(icatypes.AttributeKeyAckError, tc.eventErrorMsg)) + expectedEvents := sdk.Events{ + sdk.NewEvent( + icatypes.EventTypePacket, + expectedAttributes..., + ), + }.ToABCIEvents() + + expectedEvents = sdk.MarkEventsToIndex(expectedEvents, map[string]struct{}{}) + ibctesting.AssertEvents(&suite.Suite, expectedEvents, ctx.EventManager().Events().ToABCIEvents()) + } + }) + } + } +} + +func (suite *InterchainAccountsTestSuite) TestOnAcknowledgementPacket() { + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "ICA OnAcknowledgementPacket fails with ErrInvalidChannelFlow", func() {}, icatypes.ErrInvalidChannelFlow, + }, + } + + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + tc.malleate() // malleate mutates test data + + cbs, ok := suite.chainB.App.GetIBCKeeper().PortKeeper.Route(path.EndpointB.ChannelConfig.PortID) + suite.Require().True(ok) + + packet := channeltypes.NewPacket( + []byte("empty packet data"), + suite.chainA.SenderAccount.GetSequence(), + path.EndpointB.ChannelConfig.PortID, + path.EndpointB.ChannelID, + path.EndpointA.ChannelConfig.PortID, + path.EndpointA.ChannelID, + clienttypes.NewHeight(0, 100), + 0, + ) + + err = cbs.OnAcknowledgementPacket(suite.chainB.GetContext(), path.EndpointB.GetChannel().Version, packet, []byte("ackBytes"), nil) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } + } +} + +func (suite *InterchainAccountsTestSuite) TestOnTimeoutPacket() { + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "ICA OnTimeoutPacket fails with ErrInvalidChannelFlow", func() {}, icatypes.ErrInvalidChannelFlow, + }, + } + + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + tc.malleate() // malleate mutates test data + + cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(path.EndpointB.ChannelConfig.PortID) + suite.Require().True(ok) + + packet := channeltypes.NewPacket( + []byte("empty packet data"), + suite.chainA.SenderAccount.GetSequence(), + path.EndpointB.ChannelConfig.PortID, + path.EndpointB.ChannelID, + path.EndpointA.ChannelConfig.PortID, + path.EndpointA.ChannelID, + clienttypes.NewHeight(0, 100), + 0, + ) + + err = cbs.OnTimeoutPacket(suite.chainA.GetContext(), path.EndpointA.GetChannel().Version, packet, nil) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } + } +} + +func (suite *InterchainAccountsTestSuite) fundICAWallet(ctx sdk.Context, portID string, amount sdk.Coins) { + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(ctx, ibctesting.FirstConnectionID, portID) + suite.Require().True(found) + + msgBankSend := &banktypes.MsgSend{ + FromAddress: suite.chainB.SenderAccount.GetAddress().String(), + ToAddress: interchainAccountAddr, + Amount: amount, + } + + res, err := suite.chainB.SendMsgs(msgBankSend) + suite.Require().NotEmpty(res) + suite.Require().NoError(err) +} + +// TestControlAccountAfterChannelClose tests that a controller chain can control a registered interchain account after the currently active channel for that interchain account has been closed. +// A new channel will be opened for the controller portID. The interchain account address should remain unchanged. +func (suite *InterchainAccountsTestSuite) TestControlAccountAfterChannelClose() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + suite.SetupTest() // reset + + path := NewICAPath(suite.chainA, suite.chainB, ordering) + + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + // two sends will be performed, one after initial creation of the account and one after channel closure and reopening + var ( + startingBal = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(100000))) + tokenAmt = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(5000))) + expBalAfterFirstSend = startingBal.Sub(tokenAmt...) + expBalAfterSecondSend = expBalAfterFirstSend.Sub(tokenAmt...) + ) + + // check that the account is working as expected + suite.fundICAWallet(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID, startingBal) + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointB.ConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + msg := &banktypes.MsgSend{ + FromAddress: interchainAccountAddr, + ToAddress: suite.chainB.SenderAccount.GetAddress().String(), + Amount: tokenAmt, + } + + data, err := icatypes.SerializeCosmosTx(suite.chainA.GetSimApp().AppCodec(), []proto.Message{msg}, icatypes.EncodingProtobuf) + suite.Require().NoError(err) + + icaPacketData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + } + + params := types.NewParams(true, []string{sdk.MsgTypeURL(msg)}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + + // nolint: staticcheck // SA1019: ibctesting.FirstConnectionID is deprecated: use path.EndpointA.ConnectionID instead. (staticcheck) + _, err = suite.chainA.GetSimApp().ICAControllerKeeper.SendTx(suite.chainA.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID, icaPacketData, ^uint64(0)) + suite.Require().NoError(err) + err = path.EndpointB.UpdateClient() + suite.Require().NoError(err) + + // relay the packet + packetRelay := channeltypes.NewPacket(icaPacketData.GetBytes(), 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.ZeroHeight(), ^uint64(0)) + err = path.RelayPacket(packetRelay) + suite.Require().NoError(err) // relay committed + + // check that the ica balance is updated + icaAddr, err := sdk.AccAddressFromBech32(interchainAccountAddr) + suite.Require().NoError(err) + + suite.assertBalance(icaAddr, expBalAfterFirstSend) + + // close the channel + path.EndpointA.UpdateChannel(func(channel *channeltypes.Channel) { channel.State = channeltypes.CLOSED }) + path.EndpointB.UpdateChannel(func(channel *channeltypes.Channel) { channel.State = channeltypes.CLOSED }) + + // open a new channel on the same port + path.EndpointA.ChannelID = "" + path.EndpointB.ChannelID = "" + path.CreateChannels() + + // nolint: staticcheck // SA1019: ibctesting.FirstConnectionID is deprecated: use path.EndpointA.ConnectionID instead. (staticcheck) + _, err = suite.chainA.GetSimApp().ICAControllerKeeper.SendTx(suite.chainA.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID, icaPacketData, ^uint64(0)) + suite.Require().NoError(err) + err = path.EndpointB.UpdateClient() + suite.Require().NoError(err) + + // relay the packet + packetRelay = channeltypes.NewPacket(icaPacketData.GetBytes(), 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.ZeroHeight(), ^uint64(0)) + err = path.RelayPacket(packetRelay) + suite.Require().NoError(err) // relay committed + + suite.assertBalance(icaAddr, expBalAfterSecondSend) + } +} + +// assertBalance asserts that the provided address has exactly the expected balance. +// CONTRACT: the expected balance must only contain one coin denom. +func (suite *InterchainAccountsTestSuite) assertBalance(addr sdk.AccAddress, expBalance sdk.Coins) { + balance := suite.chainB.GetSimApp().BankKeeper.GetBalance(suite.chainB.GetContext(), addr, sdk.DefaultBondDenom) + suite.Require().Equal(expBalance[0], balance) +} + +func (suite *InterchainAccountsTestSuite) TestPacketDataUnmarshalerInterface() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + suite.SetupTest() // reset + + path := NewICAPath(suite.chainA, suite.chainB, ordering) + path.SetupConnections() + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + expPacketData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: []byte("data"), + Memo: "", + } + + // Context, port identifier and channel identifier are unused for host. + icaHostModule := icahost.NewIBCModule(suite.chainA.GetSimApp().ICAHostKeeper) + packetData, version, err := icaHostModule.UnmarshalPacketData(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, expPacketData.GetBytes()) + suite.Require().NoError(err) + suite.Require().Equal(version, path.EndpointA.ChannelConfig.Version) + suite.Require().Equal(expPacketData, packetData) + + // test invalid packet data + invalidPacketData := []byte("invalid packet data") + // Context, port identifier and channel identifier are unused for host. + packetData, version, err = icaHostModule.UnmarshalPacketData(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, invalidPacketData) + suite.Require().Error(err) + suite.Require().Empty(version) + suite.Require().Nil(packetData) + } +} diff --git a/modules/apps/27-interchain-accounts/host/keeper/account.go b/modules/apps/27-interchain-accounts/host/keeper/account.go new file mode 100644 index 0000000..642c38f --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/keeper/account.go @@ -0,0 +1,35 @@ +package keeper + +import ( + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" +) + +// createInterchainAccount creates a new interchain account. An address is generated using the host connectionID, the controller portID, +// and block dependent information. An error is returned if an account already exists for the generated account. +// An interchain account type is set in the account keeper and the interchain account address mapping is updated. +func (k Keeper) createInterchainAccount(ctx sdk.Context, connectionID, controllerPortID string) (sdk.AccAddress, + error, +) { + accAddress := icatypes.GenerateAddress(ctx, connectionID, controllerPortID) + + if acc := k.accountKeeper.GetAccount(ctx, accAddress); acc != nil { + return nil, errorsmod.Wrapf(icatypes.ErrAccountAlreadyExist, "existing account for newly generated interchain account address %s", accAddress) + } + + interchainAccount := icatypes.NewInterchainAccount( + authtypes.NewBaseAccountWithAddress(accAddress), + controllerPortID, + ) + + k.accountKeeper.NewAccount(ctx, interchainAccount) + k.accountKeeper.SetAccount(ctx, interchainAccount) + + k.SetInterchainAccountAddress(ctx, connectionID, controllerPortID, interchainAccount.Address) + + return accAddress, nil +} diff --git a/modules/apps/27-interchain-accounts/host/keeper/events.go b/modules/apps/27-interchain-accounts/host/keeper/events.go new file mode 100644 index 0000000..17d99d0 --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/keeper/events.go @@ -0,0 +1,45 @@ +package keeper + +import ( + "strconv" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// EmitAcknowledgementEvent emits an event signalling a successful or failed acknowledgement and including the error +// details if any. +func EmitAcknowledgementEvent(ctx sdk.Context, packet channeltypes.Packet, ack exported.Acknowledgement, err error) { + attributes := []sdk.Attribute{ + sdk.NewAttribute(sdk.AttributeKeyModule, icatypes.ModuleName), + sdk.NewAttribute(icatypes.AttributeKeyHostChannelID, packet.GetDestChannel()), + sdk.NewAttribute(icatypes.AttributeKeyAckSuccess, strconv.FormatBool(ack.Success())), + } + + if err != nil { + attributes = append(attributes, sdk.NewAttribute(icatypes.AttributeKeyAckError, err.Error())) + } + ctx.EventManager().EmitEvent( + sdk.NewEvent( + icatypes.EventTypePacket, + attributes..., + ), + ) +} + +// EmitHostDisabledEvent emits an event signalling that the host submodule is disabled. +func EmitHostDisabledEvent(ctx sdk.Context, packet channeltypes.Packet) { + ctx.EventManager().EmitEvent( + sdk.NewEvent( + icatypes.EventTypePacket, + sdk.NewAttribute(sdk.AttributeKeyModule, icatypes.ModuleName), + sdk.NewAttribute(icatypes.AttributeKeyHostChannelID, packet.GetDestChannel()), + sdk.NewAttribute(icatypes.AttributeKeyAckError, types.ErrHostSubModuleDisabled.Error()), + sdk.NewAttribute(icatypes.AttributeKeyAckSuccess, "false"), + ), + ) +} diff --git a/modules/apps/27-interchain-accounts/host/keeper/export_test.go b/modules/apps/27-interchain-accounts/host/keeper/export_test.go new file mode 100644 index 0000000..7bbcff4 --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/keeper/export_test.go @@ -0,0 +1,21 @@ +package keeper + +/* + This file is to allow for unexported functions and fields to be accessible to the testing package. +*/ + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" +) + +// GetAppMetadata is a wrapper around getAppMetadata to allow the function to be directly called in tests. +func (k Keeper) GetAppMetadata(ctx sdk.Context, portID, channelID string) (icatypes.Metadata, error) { + return k.getAppMetadata(ctx, portID, channelID) +} + +// NewModuleQuerySafeAllowList is a wrapper around newModuleQuerySafeAllowList to allow the function to be directly called in tests. +func NewModuleQuerySafeAllowList() []string { + return newModuleQuerySafeAllowList() +} diff --git a/modules/apps/27-interchain-accounts/host/keeper/genesis.go b/modules/apps/27-interchain-accounts/host/keeper/genesis.go new file mode 100644 index 0000000..a5d43ae --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/keeper/genesis.go @@ -0,0 +1,38 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + genesistypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/genesis/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" +) + +// InitGenesis initializes the interchain accounts host application state from a provided genesis state +func InitGenesis(ctx sdk.Context, keeper Keeper, state genesistypes.HostGenesisState) { + keeper.setPort(ctx, state.Port) + + for _, ch := range state.ActiveChannels { + keeper.SetActiveChannelID(ctx, ch.ConnectionId, ch.PortId, ch.ChannelId) + } + + for _, acc := range state.InterchainAccounts { + keeper.SetInterchainAccountAddress(ctx, acc.ConnectionId, acc.PortId, acc.AccountAddress) + } + + if err := state.Params.Validate(); err != nil { + panic(fmt.Errorf("could not set ica host params at genesis: %v", err)) + } + keeper.SetParams(ctx, state.Params) +} + +// ExportGenesis returns the interchain accounts host exported genesis +func ExportGenesis(ctx sdk.Context, keeper Keeper) genesistypes.HostGenesisState { + return genesistypes.NewHostGenesisState( + keeper.GetAllActiveChannels(ctx), + keeper.GetAllInterchainAccounts(ctx), + icatypes.HostPortID, + keeper.GetParams(ctx), + ) +} diff --git a/modules/apps/27-interchain-accounts/host/keeper/genesis_test.go b/modules/apps/27-interchain-accounts/host/keeper/genesis_test.go new file mode 100644 index 0000000..7e4af21 --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/keeper/genesis_test.go @@ -0,0 +1,134 @@ +package keeper_test + +import ( + genesistypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/genesis/types" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/keeper" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *KeeperTestSuite) TestInitGenesis() { + interchainAccAddr := icatypes.GenerateAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, TestPortID) + genesisState := genesistypes.HostGenesisState{ + ActiveChannels: []genesistypes.ActiveChannel{ + { + ConnectionId: ibctesting.FirstConnectionID, + PortId: TestPortID, + ChannelId: ibctesting.FirstChannelID, + }, + }, + InterchainAccounts: []genesistypes.RegisteredInterchainAccount{ + { + ConnectionId: ibctesting.FirstConnectionID, + PortId: TestPortID, + AccountAddress: interchainAccAddr.String(), + }, + }, + Port: icatypes.HostPortID, + } + + keeper.InitGenesis(suite.chainA.GetContext(), suite.chainA.GetSimApp().ICAHostKeeper, genesisState) + + channelID, found := suite.chainA.GetSimApp().ICAHostKeeper.GetActiveChannelID(suite.chainA.GetContext(), ibctesting.FirstConnectionID, TestPortID) + suite.Require().True(found) + suite.Require().Equal(ibctesting.FirstChannelID, channelID) + + accountAdrr, found := suite.chainA.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), ibctesting.FirstConnectionID, TestPortID) + suite.Require().True(found) + suite.Require().Equal(interchainAccAddr.String(), accountAdrr) + + expParams := genesisState.GetParams() + params := suite.chainA.GetSimApp().ICAHostKeeper.GetParams(suite.chainA.GetContext()) + suite.Require().Equal(expParams, params) + + store := suite.chainA.GetContext().KVStore(suite.chainA.GetSimApp().GetKey(types.StoreKey)) + suite.Require().True(store.Has(icatypes.KeyPort(icatypes.HostPortID))) +} + +func (suite *KeeperTestSuite) TestGenesisParams() { + testCases := []struct { + name string + input types.Params + expPanicMsg string + }{ + {"success: set default params", types.DefaultParams(), ""}, + {"success: non-default params", types.NewParams(!types.DefaultHostEnabled, []string{"/cosmos.staking.v1beta1.MsgDelegate"}), ""}, + {"success: set empty byte for allow messages", types.NewParams(true, nil), ""}, + {"failure: set empty string for allow messages", types.NewParams(true, []string{""}), "could not set ica host params at genesis: parameter must not contain empty strings: []"}, + {"failure: set space string for allow messages", types.NewParams(true, []string{" "}), "could not set ica host params at genesis: parameter must not contain empty strings: [ ]"}, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + interchainAccAddr := icatypes.GenerateAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, TestPortID) + genesisState := genesistypes.HostGenesisState{ + ActiveChannels: []genesistypes.ActiveChannel{ + { + ConnectionId: ibctesting.FirstConnectionID, + PortId: TestPortID, + ChannelId: ibctesting.FirstChannelID, + }, + }, + InterchainAccounts: []genesistypes.RegisteredInterchainAccount{ + { + ConnectionId: ibctesting.FirstConnectionID, + PortId: TestPortID, + AccountAddress: interchainAccAddr.String(), + }, + }, + Port: icatypes.HostPortID, + Params: tc.input, + } + if tc.expPanicMsg == "" { + keeper.InitGenesis(suite.chainA.GetContext(), suite.chainA.GetSimApp().ICAHostKeeper, genesisState) + + channelID, found := suite.chainA.GetSimApp().ICAHostKeeper.GetActiveChannelID(suite.chainA.GetContext(), ibctesting.FirstConnectionID, TestPortID) + suite.Require().True(found) + suite.Require().Equal(ibctesting.FirstChannelID, channelID) + + accountAdrr, found := suite.chainA.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainA.GetContext(), ibctesting.FirstConnectionID, TestPortID) + suite.Require().True(found) + suite.Require().Equal(interchainAccAddr.String(), accountAdrr) + + expParams := tc.input + params := suite.chainA.GetSimApp().ICAHostKeeper.GetParams(suite.chainA.GetContext()) + suite.Require().Equal(expParams, params) + } else { + suite.PanicsWithError(tc.expPanicMsg, func() { + keeper.InitGenesis(suite.chainA.GetContext(), suite.chainA.GetSimApp().ICAHostKeeper, genesisState) + }) + } + }) + } +} + +func (suite *KeeperTestSuite) TestExportGenesis() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + suite.SetupTest() + + path := NewICAPath(suite.chainA, suite.chainB, icatypes.EncodingProtobuf, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + interchainAccAddr, exists := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointB.ConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(exists) + + genesisState := keeper.ExportGenesis(suite.chainB.GetContext(), suite.chainB.GetSimApp().ICAHostKeeper) + + suite.Require().Equal(path.EndpointB.ChannelID, genesisState.ActiveChannels[0].ChannelId) + suite.Require().Equal(path.EndpointA.ChannelConfig.PortID, genesisState.ActiveChannels[0].PortId) + + suite.Require().Equal(interchainAccAddr, genesisState.InterchainAccounts[0].AccountAddress) + suite.Require().Equal(path.EndpointA.ChannelConfig.PortID, genesisState.InterchainAccounts[0].PortId) + + suite.Require().Equal(icatypes.HostPortID, genesisState.GetPort()) + + expParams := types.DefaultParams() + suite.Require().Equal(expParams, genesisState.GetParams()) + } +} diff --git a/modules/apps/27-interchain-accounts/host/keeper/grpc_query.go b/modules/apps/27-interchain-accounts/host/keeper/grpc_query.go new file mode 100644 index 0000000..f9d89d8 --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/keeper/grpc_query.go @@ -0,0 +1,21 @@ +package keeper + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" +) + +var _ types.QueryServer = (*Keeper)(nil) + +// Params implements the Query/Params gRPC method +func (k Keeper) Params(goCtx context.Context, _ *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + params := k.GetParams(ctx) + + return &types.QueryParamsResponse{ + Params: ¶ms, + }, nil +} diff --git a/modules/apps/27-interchain-accounts/host/keeper/grpc_query_test.go b/modules/apps/27-interchain-accounts/host/keeper/grpc_query_test.go new file mode 100644 index 0000000..03007d3 --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/keeper/grpc_query_test.go @@ -0,0 +1,12 @@ +package keeper_test + +import ( + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" +) + +func (suite *KeeperTestSuite) TestQueryParams() { + ctx := suite.chainA.GetContext() + expParams := types.DefaultParams() + res, _ := suite.chainA.GetSimApp().ICAHostKeeper.Params(ctx, &types.QueryParamsRequest{}) + suite.Require().Equal(&expParams, res.Params) +} diff --git a/modules/apps/27-interchain-accounts/host/keeper/handshake.go b/modules/apps/27-interchain-accounts/host/keeper/handshake.go new file mode 100644 index 0000000..b5daddf --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/keeper/handshake.go @@ -0,0 +1,121 @@ +package keeper + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" +) + +// OnChanOpenTry performs basic validation of the ICA channel +// and registers a new interchain account (if it doesn't exist). +// The version returned will include the registered interchain +// account address. +func (k Keeper) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + if portID != icatypes.HostPortID { + return "", errorsmod.Wrapf(icatypes.ErrInvalidHostPort, "expected %s, got %s", icatypes.HostPortID, portID) + } + + metadata, err := icatypes.MetadataFromVersion(counterpartyVersion) + if err != nil { + // Propose the default metadata if the counterparty version is invalid + connection, err := k.channelKeeper.GetConnection(ctx, connectionHops[0]) + if err != nil { + return "", errorsmod.Wrapf(err, "failed to retrieve connection %s", connectionHops[0]) + } + + k.Logger(ctx).Debug("counterparty version is invalid, proposing default metadata") + metadata = icatypes.NewDefaultMetadata(connection.Counterparty.ConnectionId, connectionHops[0]) + } + + // set here the HostConnectionId in case the controller did not set it + metadata.HostConnectionId = connectionHops[0] + + if err = icatypes.ValidateHostMetadata(ctx, k.channelKeeper, connectionHops, metadata); err != nil { + return "", err + } + + activeChannelID, found := k.GetActiveChannelID(ctx, connectionHops[0], counterparty.PortId) + if found { + channel, found := k.channelKeeper.GetChannel(ctx, portID, activeChannelID) + if !found { + panic(fmt.Errorf("active channel mapping set for %s but channel does not exist in channel store", activeChannelID)) + } + + if channel.State != channeltypes.CLOSED { + return "", errorsmod.Wrapf(icatypes.ErrActiveChannelAlreadySet, "existing active channel %s for portID %s must be %s", activeChannelID, portID, channeltypes.CLOSED) + } + + // if a channel is being reopened, we allow the controller to propose new fields + // which are not exactly the same as the previous. The provided address will + // be overwritten with the correct one before the metadata is returned. + } + + var accAddress sdk.AccAddress + + interchainAccAddr, found := k.GetInterchainAccountAddress(ctx, metadata.HostConnectionId, counterparty.PortId) + if found { + // reopening an interchain account + k.Logger(ctx).Info("reopening existing interchain account", "address", interchainAccAddr) + accAddress = sdk.MustAccAddressFromBech32(interchainAccAddr) + if _, ok := k.accountKeeper.GetAccount(ctx, accAddress).(*icatypes.InterchainAccount); !ok { + return "", errorsmod.Wrapf(icatypes.ErrInvalidAccountReopening, "existing account address %s, does not have interchain account type", accAddress) + } + + } else { + accAddress, err = k.createInterchainAccount(ctx, metadata.HostConnectionId, counterparty.PortId) + if err != nil { + return "", err + } + k.Logger(ctx).Info("successfully created new interchain account", "host-connection-id", metadata.HostConnectionId, "port-id", counterparty.PortId, "address", accAddress) + } + + metadata.Address = accAddress.String() + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) + if err != nil { + return "", err + } + + return string(versionBytes), nil +} + +// OnChanOpenConfirm completes the handshake process by setting the active channel in state on the host chain +func (k Keeper) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + channel, found := k.channelKeeper.GetChannel(ctx, portID, channelID) + if !found { + return errorsmod.Wrapf(channeltypes.ErrChannelNotFound, "failed to retrieve channel %s on port %s", channelID, portID) + } + + // It is assumed the controller chain will not allow multiple active channels to be created for the same connectionID/portID + // If the controller chain does allow multiple active channels to be created for the same connectionID/portID, + // disallowing overwriting the current active channel guarantees the channel can no longer be used as the controller + // and host will disagree on what the currently active channel is + k.SetActiveChannelID(ctx, channel.ConnectionHops[0], channel.Counterparty.PortId, channelID) + + return nil +} + +// OnChanCloseConfirm removes the active channel stored in state +func (Keeper) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + return nil +} diff --git a/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go b/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go new file mode 100644 index 0000000..d3b3049 --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/keeper/handshake_test.go @@ -0,0 +1,406 @@ +package keeper_test + +import ( + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + + hosttypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +// open and close channel is a helper function for TestOnChanOpenTry for reopening accounts +func (suite *KeeperTestSuite) openAndCloseChannel(path *ibctesting.Path) { + err := path.EndpointB.ChanOpenTry() + suite.Require().NoError(err) + + err = path.EndpointA.ChanOpenAck() + suite.Require().NoError(err) + + err = path.EndpointB.ChanOpenConfirm() + suite.Require().NoError(err) + + path.EndpointA.UpdateChannel(func(channel *channeltypes.Channel) { channel.State = channeltypes.CLOSED }) + path.EndpointB.UpdateChannel(func(channel *channeltypes.Channel) { channel.State = channeltypes.CLOSED }) + + path.EndpointA.ChannelID = "" + err = RegisterInterchainAccount(path.EndpointA, TestOwnerAddress) + suite.Require().NoError(err) + + // bump channel sequence as these test mock core IBC behaviour on ChanOpenTry + channelSequence := path.EndpointB.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextChannelSequence(path.EndpointB.Chain.GetContext()) + path.EndpointB.ChannelID = channeltypes.FormatChannelIdentifier(channelSequence) +} + +func (suite *KeeperTestSuite) TestOnChanOpenTry() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + var ( + channel *channeltypes.Channel + path *ibctesting.Path + metadata icatypes.Metadata + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() {}, + nil, + }, + { + "success - reopening closed active channel", + func() { + // create interchain account + // undo setup + path.EndpointB.ChannelID = "" + + suite.openAndCloseChannel(path) + }, + nil, + }, + { + "success - reopening account with new address", + func() { + // create interchain account + // undo setup + path.EndpointB.ChannelID = "" + + suite.openAndCloseChannel(path) + + // delete interchain account address + store := suite.chainB.GetContext().KVStore(suite.chainB.GetSimApp().GetKey(hosttypes.SubModuleName)) + store.Delete(icatypes.KeyOwnerAccount(path.EndpointA.ChannelConfig.PortID, path.EndpointB.ConnectionID)) + + // assert interchain account address mapping was deleted + _, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointB.ConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().False(found) + }, + nil, + }, + { + "success - empty host connection ID", + func() { + metadata.HostConnectionId = "" + + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + + path.EndpointA.ChannelConfig.Version = string(versionBytes) + }, + nil, + }, + { + "success - previous metadata is different", + func() { + // set the active channelID in state + suite.chainB.GetSimApp().ICAHostKeeper.SetActiveChannelID(suite.chainB.GetContext(), path.EndpointB.ConnectionID, path.EndpointA.ChannelConfig.PortID, path.EndpointB.ChannelID) + + // set the previous encoding to be proto3json. + // the new encoding is set to be protobuf in the test below. + metadata.Encoding = icatypes.EncodingProto3JSON + + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + + channel.State = channeltypes.CLOSED + channel.Version = string(versionBytes) + + path.EndpointB.SetChannel(*channel) + }, nil, + }, + { + "invalid metadata bytestring", + func() { + // the try step will propose a new valid version + path.EndpointA.ChannelConfig.Version = "invalid-metadata-bytestring" + }, + nil, + }, + { + "reopening account fails - no existing account", + func() { + // create interchain account + // undo setup + path.EndpointB.ChannelID = "" + + suite.openAndCloseChannel(path) + + // delete existing account + addr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointB.ConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + acc := suite.chainB.GetSimApp().AccountKeeper.GetAccount(suite.chainB.GetContext(), sdk.MustAccAddressFromBech32(addr)) + suite.chainB.GetSimApp().AccountKeeper.RemoveAccount(suite.chainB.GetContext(), acc) + }, + icatypes.ErrInvalidAccountReopening, + }, + { + "reopening account fails - existing account is not interchain account type", + func() { + // create interchain account + // undo setup + path.EndpointB.ChannelID = "" + + suite.openAndCloseChannel(path) + + addr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointB.ConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + accAddress := sdk.MustAccAddressFromBech32(addr) + acc := suite.chainB.GetSimApp().AccountKeeper.GetAccount(suite.chainB.GetContext(), accAddress) + + icaAcc, ok := acc.(*icatypes.InterchainAccount) + suite.Require().True(ok) + + // overwrite existing account with only base account type, not intercahin account type + suite.chainB.GetSimApp().AccountKeeper.SetAccount(suite.chainB.GetContext(), icaAcc.BaseAccount) + }, + icatypes.ErrInvalidAccountReopening, + }, + { + "account already exists", + func() { + interchainAccAddr := icatypes.GenerateAddress(suite.chainB.GetContext(), path.EndpointB.ConnectionID, path.EndpointA.ChannelConfig.PortID) + err := suite.chainB.GetSimApp().BankKeeper.SendCoins(suite.chainB.GetContext(), suite.chainB.SenderAccount.GetAddress(), interchainAccAddr, sdk.Coins{sdk.NewCoin("stake", sdkmath.NewInt(1))}) + suite.Require().NoError(err) + suite.Require().True(suite.chainB.GetSimApp().AccountKeeper.HasAccount(suite.chainB.GetContext(), interchainAccAddr)) + }, + icatypes.ErrAccountAlreadyExist, + }, + { + "invalid port ID", + func() { + path.EndpointB.ChannelConfig.PortID = "invalid-port-id" //nolint:goconst + }, + icatypes.ErrInvalidHostPort, + }, + { + "connection not found", + func() { + channel.ConnectionHops = []string{ibctesting.InvalidID} + path.EndpointB.SetChannel(*channel) + }, + connectiontypes.ErrConnectionNotFound, + }, + { + "unsupported encoding format", + func() { + metadata.Encoding = "invalid-encoding-format" + + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + + path.EndpointA.ChannelConfig.Version = string(versionBytes) + }, + icatypes.ErrInvalidCodec, + }, + { + "unsupported transaction type", + func() { + metadata.TxType = "invalid-tx-types" + + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + + path.EndpointA.ChannelConfig.Version = string(versionBytes) + }, + icatypes.ErrUnknownDataType, + }, + { + "invalid controller connection ID", + func() { + metadata.ControllerConnectionId = ibctesting.InvalidID + + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + + path.EndpointA.ChannelConfig.Version = string(versionBytes) + }, + connectiontypes.ErrInvalidConnection, + }, + { + "invalid counterparty version", + func() { + metadata.Version = "invalid-version" + + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + + path.EndpointA.ChannelConfig.Version = string(versionBytes) + }, + icatypes.ErrInvalidVersion, + }, + { + "active channel already set (OPEN state)", + func() { + // create a new channel and set it in state + ch := channeltypes.NewChannel(channeltypes.OPEN, ordering, channeltypes.NewCounterparty(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID), []string{path.EndpointA.ConnectionID}, ibctesting.DefaultChannelVersion) + suite.chainB.GetSimApp().GetIBCKeeper().ChannelKeeper.SetChannel(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, ch) + + // set the active channelID in state + suite.chainB.GetSimApp().ICAHostKeeper.SetActiveChannelID(suite.chainB.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID, path.EndpointB.ChannelID) + }, + icatypes.ErrActiveChannelAlreadySet, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = NewICAPath(suite.chainA, suite.chainB, icatypes.EncodingProtobuf, ordering) + path.SetupConnections() + + err := RegisterInterchainAccount(path.EndpointA, TestOwnerAddress) + suite.Require().NoError(err) + + // set the channel id on host + channelSequence := path.EndpointB.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextChannelSequence(path.EndpointB.Chain.GetContext()) + path.EndpointB.ChannelID = channeltypes.FormatChannelIdentifier(channelSequence) + + // default values + metadata = icatypes.NewMetadata(icatypes.Version, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID, "", icatypes.EncodingProtobuf, icatypes.TxTypeSDKMultiMsg) + versionBytes, err := icatypes.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + + expectedMetadata := metadata + + counterparty := channeltypes.NewCounterparty(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + channel = &channeltypes.Channel{ + State: channeltypes.TRYOPEN, + Ordering: ordering, + Counterparty: counterparty, + ConnectionHops: []string{path.EndpointB.ConnectionID}, + Version: string(versionBytes), + } + + tc.malleate() // malleate mutates test data + + version, err := suite.chainB.GetSimApp().ICAHostKeeper.OnChanOpenTry(suite.chainB.GetContext(), channel.Ordering, channel.ConnectionHops, + path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, channel.Counterparty, path.EndpointA.ChannelConfig.Version, + ) + + if tc.expErr == nil { + suite.Require().NoError(err) + + storedAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointB.ConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + interchainAccAddr, err := sdk.AccAddressFromBech32(storedAddr) + suite.Require().NoError(err) + + // Check if account is created + interchainAccount := suite.chainB.GetSimApp().AccountKeeper.GetAccount(suite.chainB.GetContext(), interchainAccAddr) + suite.Require().Equal(interchainAccount.GetAddress().String(), storedAddr) + + expectedMetadata.Address = storedAddr + expectedVersionBytes, err := icatypes.ModuleCdc.MarshalJSON(&expectedMetadata) + suite.Require().NoError(err) + + suite.Require().Equal(string(expectedVersionBytes), version) + } else { + suite.Require().ErrorIs(err, tc.expErr) + suite.Require().Equal("", version) + } + }) + } + } +} + +func (suite *KeeperTestSuite) TestOnChanOpenConfirm() { + var path *ibctesting.Path + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", func() {}, nil, + }, + { + "channel not found", + func() { + path.EndpointB.ChannelID = "invalid-channel-id" + path.EndpointB.ChannelConfig.PortID = "invalid-port-id" //nolint:goconst + }, + channeltypes.ErrChannelNotFound, + }, + } + + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = NewICAPath(suite.chainA, suite.chainB, icatypes.EncodingProtobuf, ordering) + path.SetupConnections() + + err := RegisterInterchainAccount(path.EndpointA, TestOwnerAddress) + suite.Require().NoError(err) + + err = path.EndpointB.ChanOpenTry() + suite.Require().NoError(err) + + err = path.EndpointA.ChanOpenAck() + suite.Require().NoError(err) + + tc.malleate() // malleate mutates test data + + err = suite.chainB.GetSimApp().ICAHostKeeper.OnChanOpenConfirm(suite.chainB.GetContext(), + path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } + } +} + +func (suite *KeeperTestSuite) TestOnChanCloseConfirm() { + var path *ibctesting.Path + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", func() {}, nil, + }, + } + + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = NewICAPath(suite.chainA, suite.chainB, icatypes.EncodingProtobuf, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + tc.malleate() // malleate mutates test data + + err = suite.chainB.GetSimApp().ICAHostKeeper.OnChanCloseConfirm(suite.chainB.GetContext(), + path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } + } +} diff --git a/modules/apps/27-interchain-accounts/host/keeper/keeper.go b/modules/apps/27-interchain-accounts/host/keeper/keeper.go new file mode 100644 index 0000000..e10571d --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/keeper/keeper.go @@ -0,0 +1,301 @@ +package keeper + +import ( + "errors" + "fmt" + "strings" + + gogoproto "github.com/cosmos/gogoproto/proto" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + + msgv1 "cosmossdk.io/api/cosmos/msg/v1" + queryv1 "cosmossdk.io/api/cosmos/query/v1" + corestore "cosmossdk.io/core/store" + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/log" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + + genesistypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/genesis/types" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// Keeper defines the IBC interchain accounts host keeper +type Keeper struct { + storeService corestore.KVStoreService + cdc codec.Codec + legacySubspace icatypes.ParamSubspace + + ics4Wrapper porttypes.ICS4Wrapper + channelKeeper icatypes.ChannelKeeper + accountKeeper icatypes.AccountKeeper + + msgRouter icatypes.MessageRouter + queryRouter icatypes.QueryRouter + + // mqsAllowList is a list of all module safe query paths + mqsAllowList []string + + // the address capable of executing a MsgUpdateParams message. Typically, this + // should be the x/gov module account. + authority string +} + +// NewKeeper creates a new interchain accounts host Keeper instance +func NewKeeper( + cdc codec.Codec, storeService corestore.KVStoreService, legacySubspace icatypes.ParamSubspace, + ics4Wrapper porttypes.ICS4Wrapper, channelKeeper icatypes.ChannelKeeper, + accountKeeper icatypes.AccountKeeper, msgRouter icatypes.MessageRouter, queryRouter icatypes.QueryRouter, authority string, +) Keeper { + // ensure ibc interchain accounts module account is set + if addr := accountKeeper.GetModuleAddress(icatypes.ModuleName); addr == nil { + panic(errors.New("the Interchain Accounts module account has not been set")) + } + + if strings.TrimSpace(authority) == "" { + panic(errors.New("authority must be non-empty")) + } + + return Keeper{ + storeService: storeService, + cdc: cdc, + legacySubspace: legacySubspace, + ics4Wrapper: ics4Wrapper, + channelKeeper: channelKeeper, + accountKeeper: accountKeeper, + msgRouter: msgRouter, + queryRouter: queryRouter, + mqsAllowList: newModuleQuerySafeAllowList(), + authority: authority, + } +} + +// WithICS4Wrapper sets the ICS4Wrapper. This function may be used after +// the keepers creation to set the middleware which is above this module +// in the IBC application stack. +func (k *Keeper) WithICS4Wrapper(wrapper porttypes.ICS4Wrapper) { + k.ics4Wrapper = wrapper +} + +// GetICS4Wrapper returns the ICS4Wrapper. +func (k Keeper) GetICS4Wrapper() porttypes.ICS4Wrapper { + return k.ics4Wrapper +} + +// Logger returns the application logger, scoped to the associated module +func (Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", fmt.Sprintf("x/%s-%s", exported.ModuleName, icatypes.ModuleName)) +} + +// setPort sets the provided portID in state. +func (k Keeper) setPort(ctx sdk.Context, portID string) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Set(icatypes.KeyPort(portID), []byte{0x01}); err != nil { + panic(err) + } +} + +// GetAppVersion calls the ICS4Wrapper GetAppVersion function. +func (k Keeper) GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) { + return k.ics4Wrapper.GetAppVersion(ctx, portID, channelID) +} + +// getAppMetadata retrieves the interchain accounts channel metadata from the store associated with the provided portID and channelID +func (k Keeper) getAppMetadata(ctx sdk.Context, portID, channelID string) (icatypes.Metadata, error) { + appVersion, found := k.GetAppVersion(ctx, portID, channelID) + if !found { + return icatypes.Metadata{}, errorsmod.Wrapf(ibcerrors.ErrNotFound, "app version not found for port %s and channel %s", portID, channelID) + } + + return icatypes.MetadataFromVersion(appVersion) +} + +// GetActiveChannelID retrieves the active channelID from the store keyed by the provided connectionID and portID +func (k Keeper) GetActiveChannelID(ctx sdk.Context, connectionID, portID string) (string, bool) { + store := k.storeService.OpenKVStore(ctx) + key := icatypes.KeyActiveChannel(portID, connectionID) + + bz, err := store.Get(key) + if err != nil { + panic(err) + } + if len(bz) == 0 { + return "", false + } + + return string(bz), true +} + +// GetOpenActiveChannel retrieves the active channelID from the store, keyed by the provided connectionID and portID & checks if the channel in question is in state OPEN +func (k Keeper) GetOpenActiveChannel(ctx sdk.Context, connectionID, portID string) (string, bool) { + channelID, found := k.GetActiveChannelID(ctx, connectionID, portID) + if !found { + return "", false + } + + channel, found := k.channelKeeper.GetChannel(ctx, portID, channelID) + + if found && channel.State == channeltypes.OPEN { + return channelID, true + } + + return "", false +} + +// GetAllActiveChannels returns a list of all active interchain accounts host channels and their associated connection and port identifiers +func (k Keeper) GetAllActiveChannels(ctx sdk.Context) []genesistypes.ActiveChannel { + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + iterator := storetypes.KVStorePrefixIterator(store, []byte(icatypes.ActiveChannelKeyPrefix)) + defer sdk.LogDeferred(k.Logger(ctx), func() error { return iterator.Close() }) + + var activeChannels []genesistypes.ActiveChannel + for ; iterator.Valid(); iterator.Next() { + keySplit := strings.Split(string(iterator.Key()), "/") + + ch := genesistypes.ActiveChannel{ + ConnectionId: keySplit[2], + PortId: keySplit[1], + ChannelId: string(iterator.Value()), + } + + activeChannels = append(activeChannels, ch) + } + + return activeChannels +} + +// SetActiveChannelID stores the active channelID, keyed by the provided connectionID and portID +func (k Keeper) SetActiveChannelID(ctx sdk.Context, connectionID, portID, channelID string) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Set(icatypes.KeyActiveChannel(portID, connectionID), []byte(channelID)); err != nil { + panic(err) + } +} + +// IsActiveChannel returns true if there exists an active channel for the provided connectionID and portID, otherwise false +func (k Keeper) IsActiveChannel(ctx sdk.Context, connectionID, portID string) bool { + _, ok := k.GetActiveChannelID(ctx, connectionID, portID) + return ok +} + +// GetInterchainAccountAddress retrieves the InterchainAccount address from the store associated with the provided connectionID and portID +func (k Keeper) GetInterchainAccountAddress(ctx sdk.Context, connectionID, portID string) (string, bool) { + store := k.storeService.OpenKVStore(ctx) + key := icatypes.KeyOwnerAccount(portID, connectionID) + + bz, err := store.Get(key) + if len(bz) == 0 { + return "", false + } + if err != nil { + panic(err) + } + + return string(bz), true +} + +// GetAllInterchainAccounts returns a list of all registered interchain account addresses and their associated connection and controller port identifiers +func (k Keeper) GetAllInterchainAccounts(ctx sdk.Context) []genesistypes.RegisteredInterchainAccount { + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + iterator := storetypes.KVStorePrefixIterator(store, []byte(icatypes.OwnerKeyPrefix)) + + var interchainAccounts []genesistypes.RegisteredInterchainAccount + for ; iterator.Valid(); iterator.Next() { + keySplit := strings.Split(string(iterator.Key()), "/") + + acc := genesistypes.RegisteredInterchainAccount{ + ConnectionId: keySplit[2], + PortId: keySplit[1], + AccountAddress: string(iterator.Value()), + } + + interchainAccounts = append(interchainAccounts, acc) + } + + return interchainAccounts +} + +// SetInterchainAccountAddress stores the InterchainAccount address, keyed by the associated connectionID and portID +func (k Keeper) SetInterchainAccountAddress(ctx sdk.Context, connectionID, portID, address string) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Set(icatypes.KeyOwnerAccount(portID, connectionID), []byte(address)); err != nil { + panic(err) + } +} + +// GetAuthority returns the 27-interchain-accounts host submodule's authority. +func (k Keeper) GetAuthority() string { + return k.authority +} + +// GetParams returns the total set of the host submodule parameters. +func (k Keeper) GetParams(ctx sdk.Context) types.Params { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get([]byte(types.ParamsKey)) + if err != nil { + panic(err) + } + if bz == nil { // only panic on unset params and not on empty params + panic(errors.New("ica/host params are not set in store")) + } + + var params types.Params + k.cdc.MustUnmarshal(bz, ¶ms) + return params +} + +// SetParams sets the total set of the host submodule parameters. +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { + store := k.storeService.OpenKVStore(ctx) + bz := k.cdc.MustMarshal(¶ms) + if err := store.Set([]byte(types.ParamsKey), bz); err != nil { + panic(err) + } +} + +// newModuleQuerySafeAllowList returns a list of all query paths labeled with module_query_safe in the proto files. +func newModuleQuerySafeAllowList() []string { + allowList := []string{} + gogoproto.GogoResolver.RangeFiles(func(fd protoreflect.FileDescriptor) bool { + for i := range fd.Services().Len() { + // Get the service descriptor + sd := fd.Services().Get(i) + + // Skip services that are annotated with the "cosmos.msg.v1.service" option. + if ext := proto.GetExtension(sd.Options(), msgv1.E_Service); ext != nil { + val, ok := ext.(bool) + if !ok { + panic(fmt.Errorf("cannot convert %T to %T", ext, ok)) + } + if val { + continue + } + } + + for j := range sd.Methods().Len() { + // Get the method descriptor + md := sd.Methods().Get(j) + + // Skip methods that are not annotated with the "cosmos.query.v1.module_query_safe" option. + if ext := proto.GetExtension(md.Options(), queryv1.E_ModuleQuerySafe); ext == nil || !ext.(bool) { + continue + } + + // Add the method to the whitelist + allowList = append(allowList, fmt.Sprintf("/%s/%s", sd.FullName(), md.Name())) + } + } + return true + }) + + return allowList +} diff --git a/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go b/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go new file mode 100644 index 0000000..4a63468 --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/keeper/keeper_test.go @@ -0,0 +1,421 @@ +package keeper_test + +import ( + "fmt" + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + "github.com/cosmos/cosmos-sdk/runtime" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + + genesistypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/genesis/types" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/keeper" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + channelkeeper "github.com/cosmos/ibc-go/v10/modules/core/04-channel/keeper" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +var ( + // TestOwnerAddress defines a reusable bech32 address for testing purposes + TestOwnerAddress = "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs" + + // TestPortID defines a reusable port identifier for testing purposes + TestPortID, _ = icatypes.NewControllerPortID(TestOwnerAddress) + + // TestVersion defines a reusable interchainaccounts version string for testing purposes + TestVersion = string(icatypes.ModuleCdc.MustMarshalJSON(&icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: ibctesting.FirstConnectionID, + HostConnectionId: ibctesting.FirstConnectionID, + Encoding: icatypes.EncodingProtobuf, + TxType: icatypes.TxTypeSDKMultiMsg, + })) + + // TestVersionWithJSONEncoding defines a reusable interchainaccounts version string that uses JSON encoding for testing purposes + TestVersionWithJSONEncoding = string(icatypes.ModuleCdc.MustMarshalJSON(&icatypes.Metadata{ + Version: icatypes.Version, + ControllerConnectionId: ibctesting.FirstConnectionID, + HostConnectionId: ibctesting.FirstConnectionID, + Encoding: icatypes.EncodingProto3JSON, + TxType: icatypes.TxTypeSDKMultiMsg, + })) +) + +type KeeperTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains used for convenience and readability + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain + chainC *ibctesting.TestChain +} + +func (suite *KeeperTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 3) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) + suite.chainC = suite.coordinator.GetChain(ibctesting.GetChainID(3)) +} + +func NewICAPath(chainA, chainB *ibctesting.TestChain, encoding string, ordering channeltypes.Order) *ibctesting.Path { + path := ibctesting.NewPath(chainA, chainB) + + var version string + switch encoding { + case icatypes.EncodingProtobuf: + version = TestVersion + case icatypes.EncodingProto3JSON: + version = TestVersionWithJSONEncoding + default: + panic(fmt.Errorf("unsupported encoding type: %s", encoding)) + } + + path.EndpointA.ChannelConfig.PortID = icatypes.HostPortID + path.EndpointB.ChannelConfig.PortID = icatypes.HostPortID + path.EndpointA.ChannelConfig.Order = ordering + path.EndpointB.ChannelConfig.Order = ordering + path.EndpointA.ChannelConfig.Version = version + path.EndpointB.ChannelConfig.Version = version + + return path +} + +// SetupICAPath invokes the InterchainAccounts entrypoint and subsequent channel handshake handlers +func SetupICAPath(path *ibctesting.Path, owner string) error { + path.EndpointA.IncrementNextChannelSequence() + + if err := RegisterInterchainAccount(path.EndpointA, owner); err != nil { + return err + } + + if err := path.EndpointB.ChanOpenTry(); err != nil { + return err + } + + if err := path.EndpointA.ChanOpenAck(); err != nil { + return err + } + + return path.EndpointB.ChanOpenConfirm() +} + +// RegisterInterchainAccount is a helper function for starting the channel handshake +func RegisterInterchainAccount(endpoint *ibctesting.Endpoint, owner string) error { + portID, err := icatypes.NewControllerPortID(owner) + if err != nil { + return err + } + + channelSequence := endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextChannelSequence(endpoint.Chain.GetContext()) + + if err := endpoint.Chain.GetSimApp().ICAControllerKeeper.RegisterInterchainAccount(endpoint.Chain.GetContext(), endpoint.ConnectionID, owner, endpoint.ChannelConfig.Version, endpoint.ChannelConfig.Order); err != nil { + return err + } + + // commit state changes for proof verification + endpoint.Chain.NextBlock() + + // update port/channel ids + endpoint.ChannelID = channeltypes.FormatChannelIdentifier(channelSequence) + endpoint.ChannelConfig.PortID = portID + + return nil +} + +func TestKeeperTestSuite(t *testing.T) { + testifysuite.Run(t, new(KeeperTestSuite)) +} + +func (suite *KeeperTestSuite) TestNewKeeper() { + testCases := []struct { + name string + instantiateFn func() + panicMsg string + }{ + {"success", func() { + keeper.NewKeeper( + suite.chainA.GetSimApp().AppCodec(), + runtime.NewKVStoreService(suite.chainA.GetSimApp().GetKey(types.StoreKey)), + suite.chainA.GetSimApp().GetSubspace(types.SubModuleName), + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, + suite.chainA.GetSimApp().AccountKeeper, + suite.chainA.GetSimApp().MsgServiceRouter(), + suite.chainA.GetSimApp().GRPCQueryRouter(), + suite.chainA.GetSimApp().ICAHostKeeper.GetAuthority(), + ) + }, ""}, + {"failure: interchain accounts module account does not exist", func() { + keeper.NewKeeper( + suite.chainA.GetSimApp().AppCodec(), + runtime.NewKVStoreService(suite.chainA.GetSimApp().GetKey(types.StoreKey)), + suite.chainA.GetSimApp().GetSubspace(types.SubModuleName), + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, + authkeeper.AccountKeeper{}, // empty account keeper + suite.chainA.GetSimApp().MsgServiceRouter(), + suite.chainA.GetSimApp().GRPCQueryRouter(), + suite.chainA.GetSimApp().ICAHostKeeper.GetAuthority(), + ) + }, "the Interchain Accounts module account has not been set"}, + {"failure: empty mock staking keeper", func() { + keeper.NewKeeper( + suite.chainA.GetSimApp().AppCodec(), + runtime.NewKVStoreService(suite.chainA.GetSimApp().GetKey(types.StoreKey)), + suite.chainA.GetSimApp().GetSubspace(types.SubModuleName), + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, + suite.chainA.GetSimApp().AccountKeeper, + suite.chainA.GetSimApp().MsgServiceRouter(), + suite.chainA.GetSimApp().GRPCQueryRouter(), + "", // authority + ) + }, "authority must be non-empty"}, + } + + for _, tc := range testCases { + + suite.SetupTest() + + suite.Run(tc.name, func() { + if tc.panicMsg == "" { + suite.Require().NotPanics( + tc.instantiateFn, + ) + } else { + suite.Require().PanicsWithError( + tc.panicMsg, + tc.instantiateFn, + ) + } + }) + } +} + +func (suite *KeeperTestSuite) TestNewModuleQuerySafeAllowList() { + // Currently, all queries in bank, staking, auth, and circuit are marked safe + // Notably, the gov and distribution modules are not marked safe + + var allowList []string + suite.Require().NotPanics(func() { + allowList = keeper.NewModuleQuerySafeAllowList() + }) + + suite.Require().NotEmpty(allowList) + suite.Require().Contains(allowList, "/cosmos.bank.v1beta1.Query/Balance") + suite.Require().Contains(allowList, "/cosmos.bank.v1beta1.Query/AllBalances") + suite.Require().Contains(allowList, "/cosmos.staking.v1beta1.Query/Validator") + suite.Require().Contains(allowList, "/cosmos.staking.v1beta1.Query/Validators") + suite.Require().Contains(allowList, "/cosmos.auth.v1beta1.Query/Accounts") + suite.Require().Contains(allowList, "/cosmos.auth.v1beta1.Query/ModuleAccountByName") + suite.Require().Contains(allowList, "/ibc.core.client.v1.Query/VerifyMembership") + suite.Require().NotContains(allowList, "/cosmos.gov.v1beta1.Query/Proposals") + suite.Require().NotContains(allowList, "/cosmos.gov.v1.Query/Proposals") + suite.Require().NotContains(allowList, "/cosmos.distribution.v1beta1.Query/Params") + suite.Require().NotContains(allowList, "/cosmos.distribution.v1beta1.Query/DelegationRewards") +} + +func (suite *KeeperTestSuite) TestGetInterchainAccountAddress() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + suite.SetupTest() + + path := NewICAPath(suite.chainA, suite.chainB, icatypes.EncodingProtobuf, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + counterpartyPortID := path.EndpointA.ChannelConfig.PortID + + retrievedAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, counterpartyPortID) + suite.Require().True(found) + suite.Require().NotEmpty(retrievedAddr) + + retrievedAddr, found = suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, "invalid port") + suite.Require().False(found) + suite.Require().Empty(retrievedAddr) + } +} + +func (suite *KeeperTestSuite) TestGetAllActiveChannels() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + var ( + expectedChannelID = "test-channel" + expectedPortID = "test-port" + ) + + suite.SetupTest() + + path := NewICAPath(suite.chainA, suite.chainB, icatypes.EncodingProtobuf, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + suite.chainB.GetSimApp().ICAHostKeeper.SetActiveChannelID(suite.chainB.GetContext(), ibctesting.FirstConnectionID, expectedPortID, expectedChannelID) + + expectedChannels := []genesistypes.ActiveChannel{ + { + ConnectionId: ibctesting.FirstConnectionID, + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointB.ChannelID, + }, + { + ConnectionId: ibctesting.FirstConnectionID, + PortId: expectedPortID, + ChannelId: expectedChannelID, + }, + } + + activeChannels := suite.chainB.GetSimApp().ICAHostKeeper.GetAllActiveChannels(suite.chainB.GetContext()) + suite.Require().Len(activeChannels, len(expectedChannels)) + suite.Require().Equal(expectedChannels, activeChannels) + } +} + +func (suite *KeeperTestSuite) TestGetAllInterchainAccounts() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + var ( + expectedAccAddr = "test-acc-addr" + expectedPortID = "test-port" + ) + + suite.SetupTest() + + path := NewICAPath(suite.chainA, suite.chainB, icatypes.EncodingProtobuf, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + interchainAccAddr, exists := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), path.EndpointB.ConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(exists) + + suite.chainB.GetSimApp().ICAHostKeeper.SetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, expectedPortID, expectedAccAddr) + + expectedAccounts := []genesistypes.RegisteredInterchainAccount{ + { + ConnectionId: ibctesting.FirstConnectionID, + PortId: TestPortID, + AccountAddress: interchainAccAddr, + }, + { + ConnectionId: ibctesting.FirstConnectionID, + PortId: expectedPortID, + AccountAddress: expectedAccAddr, + }, + } + + interchainAccounts := suite.chainB.GetSimApp().ICAHostKeeper.GetAllInterchainAccounts(suite.chainB.GetContext()) + suite.Require().Len(interchainAccounts, len(expectedAccounts)) + suite.Require().Equal(expectedAccounts, interchainAccounts) + } +} + +func (suite *KeeperTestSuite) TestIsActiveChannel() { + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + suite.SetupTest() + + path := NewICAPath(suite.chainA, suite.chainB, icatypes.EncodingProtobuf, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + isActive := suite.chainB.GetSimApp().ICAHostKeeper.IsActiveChannel(suite.chainB.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(isActive) + } +} + +func (suite *KeeperTestSuite) TestSetInterchainAccountAddress() { + var ( + expectedAccAddr = "test-acc-addr" + expectedPortID = "test-port" + ) + + suite.chainB.GetSimApp().ICAHostKeeper.SetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, expectedPortID, expectedAccAddr) + + retrievedAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, expectedPortID) + suite.Require().True(found) + suite.Require().Equal(expectedAccAddr, retrievedAddr) +} + +func (suite *KeeperTestSuite) TestMetadataNotFound() { + var ( + invalidPortID = "invalid-port" + invalidChannelID = "invalid-channel" + ) + + _, err := suite.chainB.GetSimApp().ICAHostKeeper.GetAppMetadata(suite.chainB.GetContext(), invalidPortID, invalidChannelID) + suite.Require().ErrorIs(err, ibcerrors.ErrNotFound) + suite.Require().Contains(err.Error(), fmt.Sprintf("app version not found for port %s and channel %s", invalidPortID, invalidChannelID)) +} + +func (suite *KeeperTestSuite) TestParams() { + expParams := types.DefaultParams() + + params := suite.chainA.GetSimApp().ICAHostKeeper.GetParams(suite.chainA.GetContext()) + suite.Require().Equal(expParams, params) + + testCases := []struct { + name string + input types.Params + errMsg string + }{ + {"success: set default params", types.DefaultParams(), ""}, + {"success: non-default params", types.NewParams(!types.DefaultHostEnabled, []string{"/cosmos.staking.v1beta1.MsgDelegate"}), ""}, + {"success: set empty byte for allow messages", types.NewParams(true, nil), ""}, + {"failure: set empty string for allow messages", types.NewParams(true, []string{""}), "parameter must not contain empty strings"}, + {"failure: set space string for allow messages", types.NewParams(true, []string{" "}), "parameter must not contain empty strings"}, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + ctx := suite.chainA.GetContext() + err := tc.input.Validate() + suite.chainA.GetSimApp().ICAHostKeeper.SetParams(ctx, tc.input) + if tc.errMsg == "" { + suite.Require().NoError(err) + expected := tc.input + p := suite.chainA.GetSimApp().ICAHostKeeper.GetParams(ctx) + suite.Require().Equal(expected, p) + } else { + suite.Require().ErrorContains(err, tc.errMsg) + } + }) + } +} + +func (suite *KeeperTestSuite) TestUnsetParams() { + suite.SetupTest() + ctx := suite.chainA.GetContext() + store := suite.chainA.GetContext().KVStore(suite.chainA.GetSimApp().GetKey(types.SubModuleName)) + store.Delete([]byte(types.ParamsKey)) + + suite.Require().Panics(func() { + suite.chainA.GetSimApp().ICAHostKeeper.GetParams(ctx) + }) +} + +func (suite *KeeperTestSuite) TestWithICS4Wrapper() { + suite.SetupTest() + + // test if the ics4 wrapper is the channel keeper initially + ics4Wrapper := suite.chainA.GetSimApp().ICAHostKeeper.GetICS4Wrapper() + + _, isChannelKeeper := ics4Wrapper.(*channelkeeper.Keeper) + suite.Require().True(isChannelKeeper) + suite.Require().IsType((*channelkeeper.Keeper)(nil), ics4Wrapper) + + // set the ics4 wrapper to the channel keeper + suite.chainA.GetSimApp().ICAHostKeeper.WithICS4Wrapper(nil) + ics4Wrapper = suite.chainA.GetSimApp().ICAHostKeeper.GetICS4Wrapper() + suite.Require().Nil(ics4Wrapper) +} diff --git a/modules/apps/27-interchain-accounts/host/keeper/migrations.go b/modules/apps/27-interchain-accounts/host/keeper/migrations.go new file mode 100644 index 0000000..5de7799 --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/keeper/migrations.go @@ -0,0 +1,35 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" +) + +// Migrator is a struct for handling in-place state migrations. +type Migrator struct { + keeper *Keeper +} + +// NewMigrator returns Migrator instance for the state migration. +func NewMigrator(k *Keeper) Migrator { + return Migrator{ + keeper: k, + } +} + +// MigrateParams migrates the host submodule's parameters from the x/params to self store. +func (m Migrator) MigrateParams(ctx sdk.Context) error { + if m.keeper != nil { + params := types.DefaultParams() + if m.keeper.legacySubspace != nil { + m.keeper.legacySubspace.GetParamSetIfExists(ctx, ¶ms) + } + if err := params.Validate(); err != nil { + return err + } + m.keeper.SetParams(ctx, params) + m.keeper.Logger(ctx).Info("successfully migrated ica/host submodule to self-manage params") + } + return nil +} diff --git a/modules/apps/27-interchain-accounts/host/keeper/migrations_test.go b/modules/apps/27-interchain-accounts/host/keeper/migrations_test.go new file mode 100644 index 0000000..1630370 --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/keeper/migrations_test.go @@ -0,0 +1,62 @@ +package keeper_test + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/runtime" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + icahostkeeper "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/keeper" + icahosttypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" +) + +func (suite *KeeperTestSuite) TestMigratorMigrateParams() { + testCases := []struct { + msg string + malleate func() + expectedParams icahosttypes.Params + }{ + { + "success: default params", + func() { + params := icahosttypes.DefaultParams() + subspace := suite.chainA.GetSimApp().GetSubspace(icahosttypes.SubModuleName) // get subspace + subspace.SetParamSet(suite.chainA.GetContext(), ¶ms) // set params + }, + icahosttypes.DefaultParams(), + }, + { + "success: no legacy params pre-migration", + func() { + suite.chainA.GetSimApp().ICAHostKeeper = icahostkeeper.NewKeeper( + suite.chainA.Codec, + runtime.NewKVStoreService(suite.chainA.GetSimApp().GetKey(icahosttypes.StoreKey)), + nil, // assign a nil legacy param subspace + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, + suite.chainA.GetSimApp().AccountKeeper, + suite.chainA.GetSimApp().MsgServiceRouter(), + suite.chainA.GetSimApp().GRPCQueryRouter(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + }, + icahosttypes.DefaultParams(), + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() // explicitly set params + + migrator := icahostkeeper.NewMigrator(&suite.chainA.GetSimApp().ICAHostKeeper) + err := migrator.MigrateParams(suite.chainA.GetContext()) + suite.Require().NoError(err) + + params := suite.chainA.GetSimApp().ICAHostKeeper.GetParams(suite.chainA.GetContext()) + suite.Require().Equal(tc.expectedParams, params) + }) + } +} diff --git a/modules/apps/27-interchain-accounts/host/keeper/msg_server.go b/modules/apps/27-interchain-accounts/host/keeper/msg_server.go new file mode 100644 index 0000000..a443e3b --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/keeper/msg_server.go @@ -0,0 +1,74 @@ +package keeper + +import ( + "context" + "slices" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + abci "github.com/cometbft/cometbft/abci/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +var _ types.MsgServer = (*msgServer)(nil) + +type msgServer struct { + *Keeper +} + +// NewMsgServerImpl returns an implementation of the ICS27 host MsgServer interface +// for the provided Keeper. +func NewMsgServerImpl(keeper *Keeper) types.MsgServer { + return &msgServer{Keeper: keeper} +} + +// ModuleQuerySafe routes the queries to the keeper's query router if they are module_query_safe. +// This handler doesn't use the signer. +func (m msgServer) ModuleQuerySafe(goCtx context.Context, msg *types.MsgModuleQuerySafe) (*types.MsgModuleQuerySafeResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + responses := make([][]byte, len(msg.Requests)) + for i, query := range msg.Requests { + isModuleQuerySafe := slices.Contains(m.mqsAllowList, query.Path) + if !isModuleQuerySafe { + return nil, errorsmod.Wrapf(ibcerrors.ErrInvalidRequest, "not module query safe: %s", query.Path) + } + + route := m.queryRouter.Route(query.Path) + if route == nil { + return nil, errorsmod.Wrapf(ibcerrors.ErrInvalidRequest, "no route to query: %s", query.Path) + } + + res, err := route(ctx, &abci.RequestQuery{ + Path: query.Path, + Data: query.Data, + }) + if err != nil { + m.Logger(ctx).Debug("query failed", "path", query.Path, "error", err) + return nil, err + } + if res == nil || res.Value == nil { + return nil, errorsmod.Wrapf(ibcerrors.ErrInvalidRequest, "no response for query: %s", query.Path) + } + + responses[i] = res.Value + } + + return &types.MsgModuleQuerySafeResponse{Responses: responses, Height: uint64(ctx.BlockHeight())}, nil +} + +// UpdateParams updates the host submodule's params. +func (m msgServer) UpdateParams(goCtx context.Context, msg *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { + if m.GetAuthority() != msg.Signer { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "expected %s, got %s", m.GetAuthority(), msg.Signer) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + m.SetParams(ctx, msg.Params) + + return &types.MsgUpdateParamsResponse{}, nil +} diff --git a/modules/apps/27-interchain-accounts/host/keeper/msg_server_test.go b/modules/apps/27-interchain-accounts/host/keeper/msg_server_test.go new file mode 100644 index 0000000..a137d3f --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/keeper/msg_server_test.go @@ -0,0 +1,188 @@ +package keeper_test + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/keeper" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +func (suite *KeeperTestSuite) TestModuleQuerySafe() { + var ( + msg *types.MsgModuleQuerySafe + expResponses [][]byte + ) + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() { + balanceQueryBz, err := banktypes.NewQueryBalanceRequest(suite.chainA.SenderAccount.GetAddress(), sdk.DefaultBondDenom).Marshal() + suite.Require().NoError(err) + + queryReq := types.QueryRequest{ + Path: "/cosmos.bank.v1beta1.Query/Balance", + Data: balanceQueryBz, + } + + msg = types.NewMsgModuleQuerySafe(suite.chainA.GetSimApp().ICAHostKeeper.GetAuthority(), []types.QueryRequest{queryReq}) + + balance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), sdk.DefaultBondDenom) + + expResp := banktypes.QueryBalanceResponse{Balance: &balance} + expRespBz, err := expResp.Marshal() + suite.Require().NoError(err) + + expResponses = [][]byte{expRespBz} + }, + nil, + }, + { + "success: multiple queries", + func() { + balanceQueryBz, err := banktypes.NewQueryBalanceRequest(suite.chainA.SenderAccount.GetAddress(), sdk.DefaultBondDenom).Marshal() + suite.Require().NoError(err) + + queryReq := types.QueryRequest{ + Path: "/cosmos.bank.v1beta1.Query/Balance", + Data: balanceQueryBz, + } + + paramsQuery := stakingtypes.QueryParamsRequest{} + paramsQueryBz, err := paramsQuery.Marshal() + suite.Require().NoError(err) + + paramsQueryReq := types.QueryRequest{ + Path: "/cosmos.staking.v1beta1.Query/Params", + Data: paramsQueryBz, + } + + msg = types.NewMsgModuleQuerySafe(suite.chainA.GetSimApp().ICAHostKeeper.GetAuthority(), []types.QueryRequest{queryReq, paramsQueryReq}) + + balance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), sdk.DefaultBondDenom) + + expResp := banktypes.QueryBalanceResponse{Balance: &balance} + expRespBz, err := expResp.Marshal() + suite.Require().NoError(err) + + params, err := suite.chainA.GetSimApp().StakingKeeper.GetParams(suite.chainA.GetContext()) + suite.Require().NoError(err) + expParamsResp := stakingtypes.QueryParamsResponse{Params: params} + expParamsRespBz, err := expParamsResp.Marshal() + suite.Require().NoError(err) + + expResponses = [][]byte{expRespBz, expParamsRespBz} + }, + nil, + }, + { + "failure: not module query safe", + func() { + balanceQueryBz, err := banktypes.NewQueryBalanceRequest(suite.chainA.SenderAccount.GetAddress(), sdk.DefaultBondDenom).Marshal() + suite.Require().NoError(err) + + queryReq := types.QueryRequest{ + Path: "/cosmos.bank.v1beta1.Query/Balance", + Data: balanceQueryBz, + } + + paramsQuery := transfertypes.QueryParamsRequest{} + paramsQueryBz, err := paramsQuery.Marshal() + suite.Require().NoError(err) + + paramsQueryReq := types.QueryRequest{ + Path: "/ibc.applications.transfer.v1.Query/Params", + Data: paramsQueryBz, + } + + msg = types.NewMsgModuleQuerySafe(suite.chainA.GetSimApp().ICAHostKeeper.GetAuthority(), []types.QueryRequest{queryReq, paramsQueryReq}) + }, + ibcerrors.ErrInvalidRequest, + }, + { + "failure: invalid query path", + func() { + balanceQueryBz, err := banktypes.NewQueryBalanceRequest(suite.chainA.SenderAccount.GetAddress(), sdk.DefaultBondDenom).Marshal() + suite.Require().NoError(err) + + queryReq := types.QueryRequest{ + Path: "/cosmos.invalid.Query/Invalid", + Data: balanceQueryBz, + } + + msg = types.NewMsgModuleQuerySafe(suite.chainA.GetSimApp().ICAHostKeeper.GetAuthority(), []types.QueryRequest{queryReq}) + }, + ibcerrors.ErrInvalidRequest, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + // reset + msg = nil + expResponses = nil + + tc.malleate() + + ctx := suite.chainA.GetContext() + msgServer := keeper.NewMsgServerImpl(&suite.chainA.GetSimApp().ICAHostKeeper) + res, err := msgServer.ModuleQuerySafe(ctx, msg) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + + suite.Require().ElementsMatch(expResponses, res.Responses) + } else { + suite.Require().ErrorIs(err, tc.expErr) + suite.Require().Nil(res) + } + }) + } +} + +func (suite *KeeperTestSuite) TestUpdateParams() { + testCases := []struct { + name string + msg *types.MsgUpdateParams + expErr error + }{ + { + "success", + types.NewMsgUpdateParams(suite.chainA.GetSimApp().ICAHostKeeper.GetAuthority(), types.DefaultParams()), + nil, + }, + { + "invalid signer address", + types.NewMsgUpdateParams("signer", types.DefaultParams()), + ibcerrors.ErrUnauthorized, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + ctx := suite.chainA.GetContext() + msgServer := keeper.NewMsgServerImpl(&suite.chainA.GetSimApp().ICAHostKeeper) + res, err := msgServer.UpdateParams(ctx, tc.msg) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + } else { + suite.Require().ErrorIs(err, tc.expErr) + suite.Require().Nil(res) + } + }) + } +} diff --git a/modules/apps/27-interchain-accounts/host/keeper/relay.go b/modules/apps/27-interchain-accounts/host/keeper/relay.go new file mode 100644 index 0000000..72f376a --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/keeper/relay.go @@ -0,0 +1,152 @@ +package keeper + +import ( + "github.com/cosmos/gogoproto/proto" + + errorsmod "cosmossdk.io/errors" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +// OnRecvPacket handles a given interchain accounts packet on a destination host chain. +// If the transaction is successfully executed, the transaction response bytes will be returned. +func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet) ([]byte, error) { + var data icatypes.InterchainAccountPacketData + err := data.UnmarshalJSON(packet.GetData()) + if err != nil { + // UnmarshalJSON errors are indeterminate and therefore are not wrapped and included in failed acks + return nil, errorsmod.Wrapf(ibcerrors.ErrInvalidType, "cannot unmarshal ICS-27 interchain account packet data") + } + + metadata, err := k.getAppMetadata(ctx, packet.DestinationPort, packet.DestinationChannel) + if err != nil { + return nil, err + } + + switch data.Type { + case icatypes.EXECUTE_TX: + msgs, err := icatypes.DeserializeCosmosTx(k.cdc, data.Data, metadata.Encoding) + if err != nil { + return nil, errorsmod.Wrapf(err, "failed to deserialize interchain account transaction") + } + + txResponse, err := k.executeTx(ctx, packet.SourcePort, packet.DestinationPort, packet.DestinationChannel, msgs) + if err != nil { + return nil, errorsmod.Wrapf(err, "failed to execute interchain account transaction") + } + return txResponse, nil + default: + return nil, icatypes.ErrUnknownDataType + } +} + +// executeTx attempts to execute the provided transaction. It begins by authenticating the transaction signer. +// If authentication succeeds, it does basic validation of the messages before attempting to deliver each message +// into state. The state changes will only be committed if all messages in the transaction succeed. Thus the +// execution of the transaction is atomic, all state changes are reverted if a single message fails. +func (k Keeper) executeTx(ctx sdk.Context, sourcePort, destPort, destChannel string, msgs []sdk.Msg) ([]byte, error) { + channel, found := k.channelKeeper.GetChannel(ctx, destPort, destChannel) + if !found { + return nil, channeltypes.ErrChannelNotFound + } + + if err := k.authenticateTx(ctx, msgs, channel.ConnectionHops[0], sourcePort); err != nil { + return nil, err + } + + txMsgData := &sdk.TxMsgData{ + MsgResponses: make([]*codectypes.Any, len(msgs)), + } + + // CacheContext returns a new context with the multi-store branched into a cached storage object + // writeCache is called only if all msgs succeed, performing state transitions atomically + cacheCtx, writeCache := ctx.CacheContext() + for i, msg := range msgs { + if m, ok := msg.(sdk.HasValidateBasic); ok { + if err := m.ValidateBasic(); err != nil { + return nil, err + } + } + + protoAny, err := k.executeMsg(cacheCtx, msg) + if err != nil { + return nil, err + } + + txMsgData.MsgResponses[i] = protoAny + } + + writeCache() + + txResponse, err := proto.Marshal(txMsgData) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to marshal tx data") + } + + return txResponse, nil +} + +// authenticateTx ensures the provided msgs contain the correct interchain account signer address retrieved +// from state using the provided controller port identifier +func (k Keeper) authenticateTx(ctx sdk.Context, msgs []sdk.Msg, connectionID, portID string) error { + interchainAccountAddr, found := k.GetInterchainAccountAddress(ctx, connectionID, portID) + if !found { + return errorsmod.Wrapf(icatypes.ErrInterchainAccountNotFound, "failed to retrieve interchain account on port %s", portID) + } + + allowMsgs := k.GetParams(ctx).AllowMessages + for _, msg := range msgs { + if !types.ContainsMsgType(allowMsgs, msg) { + return errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "message type not allowed: %s", sdk.MsgTypeURL(msg)) + } + + // obtain the message signers using the proto signer annotations + // the msgv2 return value is discarded as it is not used + signers, _, err := k.cdc.GetMsgV1Signers(msg) + if err != nil { + return errorsmod.Wrapf(err, "failed to obtain message signers for message type %s", sdk.MsgTypeURL(msg)) + } + + for _, signer := range signers { + // the interchain account address is stored as the string value of the sdk.AccAddress type + // thus we must cast the signer to a sdk.AccAddress to obtain the comparison value + // the stored interchain account address must match the signer for every message to be executed + if interchainAccountAddr != sdk.AccAddress(signer).String() { + return errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "unexpected signer address: expected %s, got %s", interchainAccountAddr, sdk.AccAddress(signer).String()) + } + } + } + + return nil +} + +// Attempts to get the message handler from the router and if found will then execute the message. +// If the message execution is successful, the proto marshaled message response will be returned. +func (k Keeper) executeMsg(ctx sdk.Context, msg sdk.Msg) (*codectypes.Any, error) { + handler := k.msgRouter.Handler(msg) + if handler == nil { + return nil, icatypes.ErrInvalidRoute + } + + res, err := handler(ctx, msg) + if err != nil { + return nil, err + } + + // NOTE: The sdk msg handler creates a new EventManager, so events must be correctly propagated back to the current context + ctx.EventManager().EmitEvents(res.GetEvents()) + + // Each individual sdk.Result has exactly one Msg response. We aggregate here. + msgResponse := res.MsgResponses[0] + if msgResponse == nil { + return nil, errorsmod.Wrapf(ibcerrors.ErrLogic, "got nil Msg response for msg %s", sdk.MsgTypeURL(msg)) + } + + return msgResponse, nil +} diff --git a/modules/apps/27-interchain-accounts/host/keeper/relay_test.go b/modules/apps/27-interchain-accounts/host/keeper/relay_test.go new file mode 100644 index 0000000..b81233b --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/keeper/relay_test.go @@ -0,0 +1,898 @@ +package keeper_test + +import ( + "fmt" + "strings" + "time" + + "github.com/cosmos/gogoproto/proto" + + sdkmath "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + disttypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + govtypesv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *KeeperTestSuite) TestOnRecvPacket() { + testedOrderings := []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} + testedEncodings := []string{icatypes.EncodingProtobuf, icatypes.EncodingProto3JSON} + + var ( + path *ibctesting.Path + packetData []byte + ) + + testCases := []struct { + msg string + malleate func(encoding string) + expErr error + }{ + { + "interchain account successfully executes an arbitrary message type using the * (allow all message types) param", + func(encoding string) { + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + proposal, err := govtypesv1.NewProposal([]sdk.Msg{getTestProposalMessage()}, govtypesv1.DefaultStartingProposalID, time.Now(), time.Now().Add(time.Hour), "test proposal", "title", "Description", sdk.AccAddress(interchainAccountAddr), false) + suite.Require().NoError(err) + + err = suite.chainB.GetSimApp().GovKeeper.SetProposal(suite.chainB.GetContext(), proposal) + suite.Require().NoError(err) + err = suite.chainB.GetSimApp().GovKeeper.ActivateVotingPeriod(suite.chainB.GetContext(), proposal) + suite.Require().NoError(err) + + msg := &govtypesv1.MsgVote{ + ProposalId: govtypesv1.DefaultStartingProposalID, + Voter: interchainAccountAddr, + Option: govtypesv1.OptionYes, + } + + data, err := icatypes.SerializeCosmosTx(suite.chainA.GetSimApp().AppCodec(), []proto.Message{msg}, encoding) + suite.Require().NoError(err) + + icaPacketData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + } + + packetData = icaPacketData.GetBytes() + + params := types.NewParams(true, []string{"*"}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + }, + nil, + }, + { + "interchain account successfully executes banktypes.MsgSend", + func(encoding string) { + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + msg := &banktypes.MsgSend{ + FromAddress: interchainAccountAddr, + ToAddress: suite.chainB.SenderAccount.GetAddress().String(), + Amount: sdk.NewCoins(ibctesting.TestCoin), + } + + data, err := icatypes.SerializeCosmosTx(suite.chainA.GetSimApp().AppCodec(), []proto.Message{msg}, encoding) + suite.Require().NoError(err) + + icaPacketData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + } + + packetData = icaPacketData.GetBytes() + + params := types.NewParams(true, []string{sdk.MsgTypeURL(msg)}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + }, + nil, + }, + { + "interchain account successfully executes stakingtypes.MsgDelegate", + func(encoding string) { + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + validatorAddr := (sdk.ValAddress)(suite.chainB.Vals.Validators[0].Address) + msg := &stakingtypes.MsgDelegate{ + DelegatorAddress: interchainAccountAddr, + ValidatorAddress: validatorAddr.String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(5000)), + } + + data, err := icatypes.SerializeCosmosTx(suite.chainA.GetSimApp().AppCodec(), []proto.Message{msg}, encoding) + suite.Require().NoError(err) + + icaPacketData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + } + + packetData = icaPacketData.GetBytes() + + params := types.NewParams(true, []string{sdk.MsgTypeURL(msg)}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + }, + nil, + }, + { + "interchain account successfully executes stakingtypes.MsgDelegate and stakingtypes.MsgUndelegate sequentially", + func(encoding string) { + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + validatorAddr := (sdk.ValAddress)(suite.chainB.Vals.Validators[0].Address) + msgDelegate := &stakingtypes.MsgDelegate{ + DelegatorAddress: interchainAccountAddr, + ValidatorAddress: validatorAddr.String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(5000)), + } + + msgUndelegate := &stakingtypes.MsgUndelegate{ + DelegatorAddress: interchainAccountAddr, + ValidatorAddress: validatorAddr.String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(5000)), + } + + data, err := icatypes.SerializeCosmosTx(suite.chainA.GetSimApp().AppCodec(), []proto.Message{msgDelegate, msgUndelegate}, encoding) + suite.Require().NoError(err) + + icaPacketData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + } + + packetData = icaPacketData.GetBytes() + + params := types.NewParams(true, []string{sdk.MsgTypeURL(msgDelegate), sdk.MsgTypeURL(msgUndelegate)}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + }, + nil, + }, + { + "interchain account successfully executes govtypesv1.MsgSubmitProposal", + func(encoding string) { + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + msg, err := govtypesv1.NewMsgSubmitProposal([]sdk.Msg{}, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(100000))), interchainAccountAddr, "metadata", "title", "summary", false) + suite.Require().NoError(err) + + data, err := icatypes.SerializeCosmosTx(suite.chainA.GetSimApp().AppCodec(), []proto.Message{msg}, encoding) + suite.Require().NoError(err) + + icaPacketData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + } + + packetData = icaPacketData.GetBytes() + + params := types.NewParams(true, []string{sdk.MsgTypeURL(msg)}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + }, + nil, + }, + { + "interchain account successfully executes govtypesv1.MsgVote", + func(encoding string) { + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + proposal, err := govtypesv1.NewProposal([]sdk.Msg{getTestProposalMessage()}, govtypesv1.DefaultStartingProposalID, time.Now(), time.Now().Add(time.Hour), "test proposal", "title", "Description", sdk.AccAddress(interchainAccountAddr), false) + suite.Require().NoError(err) + + err = suite.chainB.GetSimApp().GovKeeper.SetProposal(suite.chainB.GetContext(), proposal) + suite.Require().NoError(err) + err = suite.chainB.GetSimApp().GovKeeper.ActivateVotingPeriod(suite.chainB.GetContext(), proposal) + suite.Require().NoError(err) + + msg := &govtypesv1.MsgVote{ + ProposalId: govtypesv1.DefaultStartingProposalID, + Voter: interchainAccountAddr, + Option: govtypesv1.OptionYes, + } + + data, err := icatypes.SerializeCosmosTx(suite.chainA.GetSimApp().AppCodec(), []proto.Message{msg}, encoding) + suite.Require().NoError(err) + + icaPacketData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + } + + packetData = icaPacketData.GetBytes() + + params := types.NewParams(true, []string{sdk.MsgTypeURL(msg)}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + }, + nil, + }, + { + "interchain account successfully executes disttypes.MsgFundCommunityPool", + func(encoding string) { + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + msg := &disttypes.MsgFundCommunityPool{ + Amount: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(5000))), + Depositor: interchainAccountAddr, + } + + data, err := icatypes.SerializeCosmosTx(suite.chainA.GetSimApp().AppCodec(), []proto.Message{msg}, encoding) + suite.Require().NoError(err) + + icaPacketData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + } + + packetData = icaPacketData.GetBytes() + + params := types.NewParams(true, []string{sdk.MsgTypeURL(msg)}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + }, + nil, + }, + { + "interchain account successfully executes icahosttypes.MsgModuleQuerySafe", + func(encoding string) { + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + balanceQuery := banktypes.NewQueryBalanceRequest(suite.chainB.SenderAccount.GetAddress(), sdk.DefaultBondDenom) + queryBz, err := balanceQuery.Marshal() + suite.Require().NoError(err) + + msg := types.NewMsgModuleQuerySafe(interchainAccountAddr, []types.QueryRequest{ + { + Path: "/cosmos.bank.v1beta1.Query/Balance", + Data: queryBz, + }, + }) + + data, err := icatypes.SerializeCosmosTx(suite.chainA.GetSimApp().AppCodec(), []proto.Message{msg}, encoding) + suite.Require().NoError(err) + + icaPacketData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + } + + packetData = icaPacketData.GetBytes() + + params := types.NewParams(true, []string{sdk.MsgTypeURL(msg)}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + }, + nil, + }, + { + "interchain account successfully executes disttypes.MsgSetWithdrawAddress", + func(encoding string) { + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + msg := &disttypes.MsgSetWithdrawAddress{ + DelegatorAddress: interchainAccountAddr, + WithdrawAddress: suite.chainB.SenderAccount.GetAddress().String(), + } + + data, err := icatypes.SerializeCosmosTx(suite.chainA.GetSimApp().AppCodec(), []proto.Message{msg}, encoding) + suite.Require().NoError(err) + + icaPacketData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + } + + packetData = icaPacketData.GetBytes() + + params := types.NewParams(true, []string{sdk.MsgTypeURL(msg)}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + }, + nil, + }, + { + "interchain account successfully executes transfertypes.MsgTransfer", + func(encoding string) { + transferPath := ibctesting.NewTransferPath(suite.chainB, suite.chainC) + + transferPath.Setup() + + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + msg := transfertypes.NewMsgTransfer( + transferPath.EndpointA.ChannelConfig.PortID, + transferPath.EndpointA.ChannelID, + ibctesting.TestCoin, + interchainAccountAddr, + suite.chainA.SenderAccount.GetAddress().String(), + suite.chainB.GetTimeoutHeight(), + 0, + "", + ) + + data, err := icatypes.SerializeCosmosTx(suite.chainA.GetSimApp().AppCodec(), []proto.Message{msg}, encoding) + suite.Require().NoError(err) + + icaPacketData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + } + + packetData = icaPacketData.GetBytes() + + params := types.NewParams(true, []string{sdk.MsgTypeURL(msg)}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + }, + nil, + }, + { + "Msg fails its ValidateBasic: MsgTransfer has an empty receiver", + func(encoding string) { + transferPath := ibctesting.NewTransferPath(suite.chainB, suite.chainC) + transferPath.Setup() + + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, path.EndpointA.ChannelConfig.PortID) + suite.Require().True(found) + + msg := transfertypes.NewMsgTransfer( + transferPath.EndpointA.ChannelConfig.PortID, + transferPath.EndpointA.ChannelID, + ibctesting.TestCoin, + interchainAccountAddr, + "", + suite.chainB.GetTimeoutHeight(), + 0, + "", + ) + + data, err := icatypes.SerializeCosmosTx(suite.chainA.GetSimApp().AppCodec(), []proto.Message{msg}, encoding) + suite.Require().NoError(err) + + icaPacketData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + } + + packetData = icaPacketData.GetBytes() + + params := types.NewParams(true, []string{sdk.MsgTypeURL(msg)}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + }, + ibcerrors.ErrInvalidAddress, + }, + { + "unregistered sdk.Msg", + func(encoding string) { + msg := &banktypes.MsgSendResponse{} + + data, err := icatypes.SerializeCosmosTx(suite.chainA.GetSimApp().AppCodec(), []proto.Message{msg}, encoding) + suite.Require().NoError(err) + + icaPacketData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + } + + packetData = icaPacketData.GetBytes() + + params := types.NewParams(true, []string{"/" + proto.MessageName(msg)}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + }, + ibcerrors.ErrInvalidType, + }, + { + "cannot unmarshal interchain account packet data", + func(encoding string) { + packetData = []byte{} + }, + ibcerrors.ErrInvalidType, + }, + { + "cannot deserialize interchain account packet data messages", + func(encoding string) { + data := []byte("invalid packet data") + + icaPacketData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + } + + packetData = icaPacketData.GetBytes() + }, + ibcerrors.ErrInvalidType, + }, + { + "invalid packet type - UNSPECIFIED", + func(encoding string) { + data, err := icatypes.SerializeCosmosTx(suite.chainA.GetSimApp().AppCodec(), []proto.Message{&banktypes.MsgSend{}}, encoding) + suite.Require().NoError(err) + + icaPacketData := icatypes.InterchainAccountPacketData{ + Type: icatypes.UNSPECIFIED, + Data: data, + } + + packetData = icaPacketData.GetBytes() + }, + icatypes.ErrUnknownDataType, + }, + { + "unauthorised: interchain account not found for controller port ID", + func(encoding string) { + path.EndpointA.ChannelConfig.PortID = "invalid-port-id" //nolint:goconst + + data, err := icatypes.SerializeCosmosTx(suite.chainA.GetSimApp().AppCodec(), []proto.Message{&banktypes.MsgSend{}}, encoding) + suite.Require().NoError(err) + + icaPacketData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + } + + packetData = icaPacketData.GetBytes() + }, + icatypes.ErrInterchainAccountNotFound, + }, + { + "unauthorised: message type not allowed", // NOTE: do not update params to explicitly force the error + func(encoding string) { + msg := &banktypes.MsgSend{ + FromAddress: suite.chainB.SenderAccount.GetAddress().String(), + ToAddress: suite.chainB.SenderAccount.GetAddress().String(), + Amount: sdk.NewCoins(ibctesting.TestCoin), + } + + data, err := icatypes.SerializeCosmosTx(suite.chainA.GetSimApp().AppCodec(), []proto.Message{msg}, encoding) + suite.Require().NoError(err) + + icaPacketData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + } + + packetData = icaPacketData.GetBytes() + }, + ibcerrors.ErrUnauthorized, + }, + { + "unauthorised: signer address is not the interchain account associated with the controller portID", + func(encoding string) { + msg := &banktypes.MsgSend{ + FromAddress: suite.chainB.SenderAccount.GetAddress().String(), // unexpected signer + ToAddress: suite.chainB.SenderAccount.GetAddress().String(), + Amount: sdk.NewCoins(ibctesting.TestCoin), + } + + data, err := icatypes.SerializeCosmosTx(suite.chainA.GetSimApp().AppCodec(), []proto.Message{msg}, encoding) + suite.Require().NoError(err) + + icaPacketData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + } + + packetData = icaPacketData.GetBytes() + + params := types.NewParams(true, []string{sdk.MsgTypeURL(msg)}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + }, + ibcerrors.ErrUnauthorized, + }, + } + + for _, ordering := range testedOrderings { + for _, encoding := range testedEncodings { + for _, tc := range testCases { + suite.Run(tc.msg, func() { + suite.SetupTest() // reset + + path = NewICAPath(suite.chainA, suite.chainB, encoding, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + portID, err := icatypes.NewControllerPortID(TestOwnerAddress) + suite.Require().NoError(err) + + // Get the address of the interchain account stored in state during handshake step + storedAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, portID) + suite.Require().True(found) + + icaAddr, err := sdk.AccAddressFromBech32(storedAddr) + suite.Require().NoError(err) + + // Check if account is created + interchainAccount := suite.chainB.GetSimApp().AccountKeeper.GetAccount(suite.chainB.GetContext(), icaAddr) + suite.Require().Equal(interchainAccount.GetAddress().String(), storedAddr) + + suite.fundICAWallet(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(1000000)))) + + tc.malleate(encoding) // malleate mutates test data + + packet := channeltypes.NewPacket( + packetData, + suite.chainA.SenderAccount.GetSequence(), + path.EndpointA.ChannelConfig.PortID, + path.EndpointA.ChannelID, + path.EndpointB.ChannelConfig.PortID, + path.EndpointB.ChannelID, + suite.chainB.GetTimeoutHeight(), + 0, + ) + + txResponse, err := suite.chainB.GetSimApp().ICAHostKeeper.OnRecvPacket(suite.chainB.GetContext(), packet) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(txResponse) + } else { + suite.Require().ErrorIs(err, tc.expErr) + suite.Require().Nil(txResponse) + } + }) + } + } + } +} + +func (suite *KeeperTestSuite) TestJSONOnRecvPacket() { + var ( + path *ibctesting.Path + packetData []byte + ) + interchainAccountAddr := "cosmos15ulrf36d4wdtrtqzkgaan9ylwuhs7k7qz753uk" + + testCases := []struct { + msg string + malleate func(icaAddress string) + expErr error + }{ + { + "interchain account successfully executes an arbitrary message type using the * (allow all message types) param", + func(icaAddress string) { + proposal, err := govtypesv1.NewProposal([]sdk.Msg{getTestProposalMessage()}, govtypesv1.DefaultStartingProposalID, suite.chainA.GetContext().BlockTime(), suite.chainA.GetContext().BlockTime(), "test proposal", "title", "Description", sdk.AccAddress(interchainAccountAddr), false) + suite.Require().NoError(err) + + err = suite.chainB.GetSimApp().GovKeeper.SetProposal(suite.chainB.GetContext(), proposal) + suite.Require().NoError(err) + err = suite.chainB.GetSimApp().GovKeeper.ActivateVotingPeriod(suite.chainB.GetContext(), proposal) + suite.Require().NoError(err) + + msgBytes := []byte(`{ + "messages": [ + { + "@type": "/cosmos.gov.v1.MsgVote", + "voter": "` + icaAddress + `", + "proposal_id": 1, + "option": 1 + } + ] + }`) + // this is the way cosmwasm encodes byte arrays by default + // golang doesn't use this encoding by default, but it can still deserialize: + byteArrayString := strings.Join(strings.Fields(fmt.Sprint(msgBytes)), ",") //nolint:staticcheck + + packetData = []byte(`{ + "type": 1, + "data":` + byteArrayString + ` + }`) + + params := types.NewParams(true, []string{"*"}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + }, + nil, + }, + { + "interchain account successfully executes banktypes.MsgSend", + func(icaAddress string) { + msgBytes := []byte(`{ + "messages": [ + { + "@type": "/cosmos.bank.v1beta1.MsgSend", + "from_address": "` + icaAddress + `", + "to_address": "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs", + "amount": [{ "denom": "stake", "amount": "100" }] + } + ] + }`) + byteArrayString := strings.Join(strings.Fields(fmt.Sprint(msgBytes)), ",") //nolint:staticcheck + + packetData = []byte(`{ + "type": 1, + "data":` + byteArrayString + ` + }`) + + params := types.NewParams(true, []string{sdk.MsgTypeURL((*banktypes.MsgSend)(nil))}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + }, + nil, + }, + { + "interchain account successfully executes govtypesv1.MsgSubmitProposal", + func(icaAddress string) { + msgBytes := []byte(`{ + "messages": [ + { + "@type": "/cosmos.gov.v1.MsgSubmitProposal", + "messages": [], + "metadata": "ipfs://CID", + "title": "IBC Gov Proposal", + "summary": "tokens for all!", + "expedited": false, + "initial_deposit": [{ "denom": "stake", "amount": "100000" }], + "proposer": "` + icaAddress + `" + } + ] + }`) + byteArrayString := strings.Join(strings.Fields(fmt.Sprint(msgBytes)), ",") //nolint:staticcheck + + packetData = []byte(`{ + "type": 1, + "data":` + byteArrayString + ` + }`) + + params := types.NewParams(true, []string{sdk.MsgTypeURL((*govtypesv1.MsgSubmitProposal)(nil))}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + }, + nil, + }, + { + "interchain account successfully executes govtypesv1.MsgVote", + func(icaAddress string) { + proposal, err := govtypesv1.NewProposal([]sdk.Msg{getTestProposalMessage()}, govtypesv1.DefaultStartingProposalID, suite.chainA.GetContext().BlockTime(), suite.chainA.GetContext().BlockTime(), "test proposal", "title", "Description", sdk.AccAddress(interchainAccountAddr), false) + suite.Require().NoError(err) + + err = suite.chainB.GetSimApp().GovKeeper.SetProposal(suite.chainB.GetContext(), proposal) + suite.Require().NoError(err) + err = suite.chainB.GetSimApp().GovKeeper.ActivateVotingPeriod(suite.chainB.GetContext(), proposal) + suite.Require().NoError(err) + + msgBytes := []byte(`{ + "messages": [ + { + "@type": "/cosmos.gov.v1.MsgVote", + "voter": "` + icaAddress + `", + "proposal_id": 1, + "option": 1 + } + ] + }`) + byteArrayString := strings.Join(strings.Fields(fmt.Sprint(msgBytes)), ",") //nolint:staticcheck + + packetData = []byte(`{ + "type": 1, + "data":` + byteArrayString + ` + }`) + + params := types.NewParams(true, []string{sdk.MsgTypeURL((*govtypesv1.MsgVote)(nil))}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + }, + nil, + }, + { + "interchain account successfully executes govtypesv1.MsgSubmitProposal, govtypesv1.MsgDeposit, and then govtypesv1.MsgVote sequentially", + func(icaAddress string) { + msgBytes := []byte(`{ + "messages": [ + { + "@type": "/cosmos.gov.v1.MsgSubmitProposal", + "messages": [], + "metadata": "ipfs://CID", + "title": "IBC Gov Proposal", + "summary": "tokens for all!", + "expedited": false, + "initial_deposit": [{ "denom": "stake", "amount": "100000" }], + "proposer": "` + icaAddress + `" + }, + { + "@type": "/cosmos.gov.v1.MsgDeposit", + "proposal_id": 1, + "depositor": "` + icaAddress + `", + "amount": [{ "denom": "stake", "amount": "10000000" }] + }, + { + "@type": "/cosmos.gov.v1.MsgVote", + "voter": "` + icaAddress + `", + "proposal_id": 1, + "option": 1 + } + ] + }`) + byteArrayString := strings.Join(strings.Fields(fmt.Sprint(msgBytes)), ",") //nolint:staticcheck + + packetData = []byte(`{ + "type": 1, + "data":` + byteArrayString + ` + }`) + + params := types.NewParams(true, []string{sdk.MsgTypeURL((*govtypesv1.MsgSubmitProposal)(nil)), sdk.MsgTypeURL((*govtypesv1.MsgDeposit)(nil)), sdk.MsgTypeURL((*govtypesv1.MsgVote)(nil))}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + }, + nil, + }, + { + "interchain account successfully executes transfertypes.MsgTransfer", + func(icaAddress string) { + transferPath := ibctesting.NewTransferPath(suite.chainB, suite.chainC) + + transferPath.Setup() + + msgBytes := []byte(`{ + "messages": [ + { + "@type": "/ibc.applications.transfer.v1.MsgTransfer", + "source_port": "transfer", + "source_channel": "` + transferPath.EndpointA.ChannelID + `", + "token": { "denom": "stake", "amount": "100" }, + "sender": "` + icaAddress + `", + "receiver": "cosmos15ulrf36d4wdtrtqzkgaan9ylwuhs7k7qz753uk", + "timeout_height": { "revision_number": 1, "revision_height": 100 }, + "timeout_timestamp": 0, + "memo": "" + } + ] + }`) + byteArrayString := strings.Join(strings.Fields(fmt.Sprint(msgBytes)), ",") //nolint:staticcheck + + packetData = []byte(`{ + "type": 1, + "data":` + byteArrayString + ` + }`) + + params := types.NewParams(true, []string{sdk.MsgTypeURL((*transfertypes.MsgTransfer)(nil))}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + }, + nil, + }, + { + "unregistered sdk.Msg", + func(icaAddress string) { + msgBytes := []byte(`{"messages":[{}]}`) + byteArrayString := strings.Join(strings.Fields(fmt.Sprint(msgBytes)), ",") //nolint:staticcheck + + packetData = []byte(`{ + "type": 1, + "data":` + byteArrayString + ` + }`) + + params := types.NewParams(true, []string{"*"}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + }, + ibcerrors.ErrInvalidType, + }, + { + "message type not allowed banktypes.MsgSend", + func(icaAddress string) { + msgBytes := []byte(`{ + "messages": [ + { + "@type": "/cosmos.bank.v1beta1.MsgSend", + "from_address": "` + icaAddress + `", + "to_address": "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs", + "amount": [{ "denom": "stake", "amount": "100" }] + } + ] + }`) + byteArrayString := strings.Join(strings.Fields(fmt.Sprint(msgBytes)), ",") //nolint:staticcheck + + packetData = []byte(`{ + "type": 1, + "data":` + byteArrayString + ` + }`) + + params := types.NewParams(true, []string{sdk.MsgTypeURL((*transfertypes.MsgTransfer)(nil))}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + }, + ibcerrors.ErrUnauthorized, + }, + { + "unauthorised: signer address is not the interchain account associated with the controller portID", + func(icaAddress string) { + msgBytes := []byte(`{ + "messages": [ + { + "@type": "/cosmos.bank.v1beta1.MsgSend", + "from_address": "` + suite.chainB.SenderAccount.GetAddress().String() + `", // unexpected signer + "to_address": "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs", + "amount": [{ "denom": "stake", "amount": "100" }] + } + ] + }`) + byteArrayString := strings.Join(strings.Fields(fmt.Sprint(msgBytes)), ",") //nolint:staticcheck + + packetData = []byte(`{ + "type": 1, + "data":` + byteArrayString + ` + }`) + + params := types.NewParams(true, []string{sdk.MsgTypeURL((*banktypes.MsgSend)(nil))}) + suite.chainB.GetSimApp().ICAHostKeeper.SetParams(suite.chainB.GetContext(), params) + }, + ibcerrors.ErrInvalidType, + }, + } + + for _, ordering := range []channeltypes.Order{channeltypes.UNORDERED, channeltypes.ORDERED} { + for _, tc := range testCases { + suite.Run(tc.msg, func() { + suite.SetupTest() // reset + + path = NewICAPath(suite.chainA, suite.chainB, icatypes.EncodingProto3JSON, ordering) + path.SetupConnections() + + err := SetupICAPath(path, TestOwnerAddress) + suite.Require().NoError(err) + + portID, err := icatypes.NewControllerPortID(TestOwnerAddress) + suite.Require().NoError(err) + + // Get the address of the interchain account stored in state during handshake step + icaAddress, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(suite.chainB.GetContext(), ibctesting.FirstConnectionID, portID) + suite.Require().True(found) + + suite.fundICAWallet(suite.chainB.GetContext(), path.EndpointA.ChannelConfig.PortID, sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(100000000)))) + + tc.malleate(icaAddress) // malleate mutates test data + + packet := channeltypes.NewPacket( + packetData, + suite.chainA.SenderAccount.GetSequence(), + path.EndpointA.ChannelConfig.PortID, + path.EndpointA.ChannelID, + path.EndpointB.ChannelConfig.PortID, + path.EndpointB.ChannelID, + suite.chainB.GetTimeoutHeight(), + 0, + ) + + txResponse, err := suite.chainB.GetSimApp().ICAHostKeeper.OnRecvPacket(suite.chainB.GetContext(), packet) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(txResponse) + } else { + suite.Require().ErrorIs(err, tc.expErr) + suite.Require().Nil(txResponse) + } + }) + } + } +} + +func (suite *KeeperTestSuite) fundICAWallet(ctx sdk.Context, portID string, amount sdk.Coins) { + interchainAccountAddr, found := suite.chainB.GetSimApp().ICAHostKeeper.GetInterchainAccountAddress(ctx, ibctesting.FirstConnectionID, portID) + suite.Require().True(found) + + msgBankSend := &banktypes.MsgSend{ + FromAddress: suite.chainB.SenderAccount.GetAddress().String(), + ToAddress: interchainAccountAddr, + Amount: amount, + } + + res, err := suite.chainB.SendMsgs(msgBankSend) + suite.Require().NotEmpty(res) + suite.Require().NoError(err) +} + +func getTestProposalMessage() sdk.Msg { + _, _, addr := testdata.KeyTestPubAddr() + return banktypes.NewMsgSend(authtypes.NewModuleAddress("gov"), addr, sdk.NewCoins(sdk.NewCoin("stake", sdkmath.NewInt(1000)))) +} diff --git a/modules/apps/27-interchain-accounts/host/types/codec.go b/modules/apps/27-interchain-accounts/host/types/codec.go new file mode 100644 index 0000000..a044f3d --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/types/codec.go @@ -0,0 +1,18 @@ +package types + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/msgservice" +) + +// RegisterInterfaces registers the interchain accounts host message types using the provided InterfaceRegistry +func RegisterInterfaces(registry codectypes.InterfaceRegistry) { + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgUpdateParams{}, + &MsgModuleQuerySafe{}, + ) + + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} diff --git a/modules/apps/27-interchain-accounts/host/types/codec_test.go b/modules/apps/27-interchain-accounts/host/types/codec_test.go new file mode 100644 index 0000000..643d179 --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/types/codec_test.go @@ -0,0 +1,52 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + + ica "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" +) + +func TestCodecTypeRegistration(t *testing.T) { + testCases := []struct { + name string + typeURL string + errMsg string + }{ + { + "success: MsgUpdateParams", + sdk.MsgTypeURL(&types.MsgUpdateParams{}), + "", + }, + { + "success: MsgModuleQuerySafe", + sdk.MsgTypeURL(&types.MsgModuleQuerySafe{}), + "", + }, + { + "type not registered on codec", + "ibc.invalid.MsgTypeURL", + "unable to resolve type URL", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + encodingCfg := moduletestutil.MakeTestEncodingConfig(ica.AppModuleBasic{}) + msg, err := encodingCfg.Codec.InterfaceRegistry().Resolve(tc.typeURL) + + if tc.errMsg == "" { + require.NotNil(t, msg) + require.NoError(t, err) + } else { + require.Nil(t, msg) + require.ErrorContains(t, err, tc.errMsg) + } + }) + } +} diff --git a/modules/apps/27-interchain-accounts/host/types/errors.go b/modules/apps/27-interchain-accounts/host/types/errors.go new file mode 100644 index 0000000..e2c715a --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/types/errors.go @@ -0,0 +1,10 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" +) + +// ICA Host sentinel errors +var ( + ErrHostSubModuleDisabled = errorsmod.Register(SubModuleName, 2, "host submodule is disabled") +) diff --git a/modules/apps/27-interchain-accounts/host/types/host.pb.go b/modules/apps/27-interchain-accounts/host/types/host.pb.go new file mode 100644 index 0000000..f3a743f --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/types/host.pb.go @@ -0,0 +1,602 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/applications/interchain_accounts/host/v1/host.proto + +package types + +import ( + fmt "fmt" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Params defines the set of on-chain interchain accounts parameters. +// The following parameters may be used to disable the host submodule. +type Params struct { + // host_enabled enables or disables the host submodule. + HostEnabled bool `protobuf:"varint,1,opt,name=host_enabled,json=hostEnabled,proto3" json:"host_enabled,omitempty"` + // allow_messages defines a list of sdk message typeURLs allowed to be executed on a host chain. + AllowMessages []string `protobuf:"bytes,2,rep,name=allow_messages,json=allowMessages,proto3" json:"allow_messages,omitempty"` +} + +func (m *Params) Reset() { *m = Params{} } +func (m *Params) String() string { return proto.CompactTextString(m) } +func (*Params) ProtoMessage() {} +func (*Params) Descriptor() ([]byte, []int) { + return fileDescriptor_48e202774f13d08e, []int{0} +} +func (m *Params) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Params) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params.Merge(m, src) +} +func (m *Params) XXX_Size() int { + return m.Size() +} +func (m *Params) XXX_DiscardUnknown() { + xxx_messageInfo_Params.DiscardUnknown(m) +} + +var xxx_messageInfo_Params proto.InternalMessageInfo + +func (m *Params) GetHostEnabled() bool { + if m != nil { + return m.HostEnabled + } + return false +} + +func (m *Params) GetAllowMessages() []string { + if m != nil { + return m.AllowMessages + } + return nil +} + +// QueryRequest defines the parameters for a particular query request +// by an interchain account. +type QueryRequest struct { + // path defines the path of the query request as defined by ADR-021. + // https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-021-protobuf-query-encoding.md#custom-query-registration-and-routing + Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` + // data defines the payload of the query request as defined by ADR-021. + // https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-021-protobuf-query-encoding.md#custom-query-registration-and-routing + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` +} + +func (m *QueryRequest) Reset() { *m = QueryRequest{} } +func (m *QueryRequest) String() string { return proto.CompactTextString(m) } +func (*QueryRequest) ProtoMessage() {} +func (*QueryRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_48e202774f13d08e, []int{1} +} +func (m *QueryRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryRequest.Merge(m, src) +} +func (m *QueryRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryRequest proto.InternalMessageInfo + +func (m *QueryRequest) GetPath() string { + if m != nil { + return m.Path + } + return "" +} + +func (m *QueryRequest) GetData() []byte { + if m != nil { + return m.Data + } + return nil +} + +func init() { + proto.RegisterType((*Params)(nil), "ibc.applications.interchain_accounts.host.v1.Params") + proto.RegisterType((*QueryRequest)(nil), "ibc.applications.interchain_accounts.host.v1.QueryRequest") +} + +func init() { + proto.RegisterFile("ibc/applications/interchain_accounts/host/v1/host.proto", fileDescriptor_48e202774f13d08e) +} + +var fileDescriptor_48e202774f13d08e = []byte{ + // 284 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0xd0, 0x31, 0x4b, 0xfc, 0x30, + 0x18, 0x06, 0xf0, 0xcb, 0xfd, 0xff, 0x1c, 0x5e, 0x3c, 0x1d, 0x3a, 0xdd, 0x14, 0xce, 0x03, 0xe1, + 0x06, 0xdb, 0x58, 0x05, 0x6f, 0x17, 0x9c, 0x44, 0xd0, 0x8c, 0x2e, 0xe5, 0x4d, 0x1a, 0xda, 0x40, + 0xdb, 0xd4, 0xbe, 0x69, 0xe5, 0xbe, 0x85, 0x1f, 0xcb, 0xf1, 0x46, 0x47, 0x69, 0xbf, 0x88, 0x34, + 0x0a, 0x2a, 0x38, 0xe5, 0xe1, 0x07, 0x4f, 0xe0, 0x7d, 0xe8, 0xd6, 0x48, 0xc5, 0xa1, 0xae, 0x0b, + 0xa3, 0xc0, 0x19, 0x5b, 0x21, 0x37, 0x95, 0xd3, 0x8d, 0xca, 0xc1, 0x54, 0x09, 0x28, 0x65, 0xdb, + 0xca, 0x21, 0xcf, 0x2d, 0x3a, 0xde, 0xc5, 0xfe, 0x8d, 0xea, 0xc6, 0x3a, 0x1b, 0x9c, 0x19, 0xa9, + 0xa2, 0x9f, 0xc5, 0xe8, 0x8f, 0x62, 0xe4, 0x0b, 0x5d, 0xbc, 0x16, 0x74, 0x76, 0x0f, 0x0d, 0x94, + 0x18, 0x9c, 0xd0, 0xc5, 0x88, 0x89, 0xae, 0x40, 0x16, 0x3a, 0x5d, 0x92, 0x15, 0xd9, 0x1c, 0x88, + 0xc3, 0xd1, 0x6e, 0x3e, 0x29, 0x38, 0xa5, 0xc7, 0x50, 0x14, 0xf6, 0x39, 0x29, 0x35, 0x22, 0x64, + 0x1a, 0x97, 0xd3, 0xd5, 0xbf, 0xcd, 0x5c, 0x1c, 0x79, 0xbd, 0xfb, 0xc2, 0xf5, 0x15, 0x5d, 0x3c, + 0xb4, 0xba, 0xd9, 0x09, 0xfd, 0xd4, 0x6a, 0x74, 0x41, 0x40, 0xff, 0xd7, 0xe0, 0x72, 0xff, 0xe3, + 0x5c, 0xf8, 0x3c, 0x5a, 0x0a, 0x0e, 0x96, 0xd3, 0x15, 0xd9, 0x2c, 0x84, 0xcf, 0xd7, 0xfa, 0xb5, + 0x67, 0x64, 0xdf, 0x33, 0xf2, 0xde, 0x33, 0xf2, 0x32, 0xb0, 0xc9, 0x7e, 0x60, 0x93, 0xb7, 0x81, + 0x4d, 0x1e, 0x6f, 0x33, 0xe3, 0xf2, 0x56, 0x46, 0xca, 0x96, 0x5c, 0x59, 0x2c, 0x2d, 0x72, 0x23, + 0x55, 0x98, 0x59, 0xde, 0xc5, 0xe7, 0xbc, 0xb4, 0x69, 0x5b, 0x68, 0x1c, 0xd7, 0x42, 0x7e, 0xb1, + 0x0d, 0xbf, 0xef, 0x0d, 0x7f, 0x0f, 0xe5, 0x76, 0xb5, 0x46, 0x39, 0xf3, 0x3b, 0x5d, 0x7e, 0x04, + 0x00, 0x00, 0xff, 0xff, 0x6c, 0x06, 0x2e, 0x0a, 0x62, 0x01, 0x00, 0x00, +} + +func (m *Params) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Params) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.AllowMessages) > 0 { + for iNdEx := len(m.AllowMessages) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.AllowMessages[iNdEx]) + copy(dAtA[i:], m.AllowMessages[iNdEx]) + i = encodeVarintHost(dAtA, i, uint64(len(m.AllowMessages[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + if m.HostEnabled { + i-- + if m.HostEnabled { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *QueryRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintHost(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0x12 + } + if len(m.Path) > 0 { + i -= len(m.Path) + copy(dAtA[i:], m.Path) + i = encodeVarintHost(dAtA, i, uint64(len(m.Path))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintHost(dAtA []byte, offset int, v uint64) int { + offset -= sovHost(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.HostEnabled { + n += 2 + } + if len(m.AllowMessages) > 0 { + for _, s := range m.AllowMessages { + l = len(s) + n += 1 + l + sovHost(uint64(l)) + } + } + return n +} + +func (m *QueryRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Path) + if l > 0 { + n += 1 + l + sovHost(uint64(l)) + } + l = len(m.Data) + if l > 0 { + n += 1 + l + sovHost(uint64(l)) + } + return n +} + +func sovHost(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozHost(x uint64) (n int) { + return sovHost(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Params) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHost + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Params: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field HostEnabled", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHost + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.HostEnabled = bool(v != 0) + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowMessages", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHost + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHost + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthHost + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AllowMessages = append(m.AllowMessages, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHost(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthHost + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHost + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Path", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHost + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHost + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthHost + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Path = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHost + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHost + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthHost + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHost(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthHost + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipHost(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowHost + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowHost + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowHost + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthHost + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupHost + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthHost + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthHost = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowHost = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupHost = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/apps/27-interchain-accounts/host/types/keys.go b/modules/apps/27-interchain-accounts/host/types/keys.go new file mode 100644 index 0000000..c976442 --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/types/keys.go @@ -0,0 +1,31 @@ +package types + +import ( + "slices" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + // SubModuleName defines the interchain accounts host module name + SubModuleName = "icahost" + + // StoreKey is the store key string for the interchain accounts host module + StoreKey = SubModuleName + + // ParamsKey is the key to use for the storing params. + ParamsKey = "params" + + // AllowAllHostMsgs holds the string key that allows all message types on interchain accounts host module + AllowAllHostMsgs = "*" +) + +// ContainsMsgType returns true if the sdk.Msg TypeURL is present in allowMsgs, otherwise false +func ContainsMsgType(allowMsgs []string, msg sdk.Msg) bool { + // check that wildcard * option for allowing all message types is the only string in the array, if so, return true + if len(allowMsgs) == 1 && allowMsgs[0] == AllowAllHostMsgs { + return true + } + + return slices.Contains(allowMsgs, sdk.MsgTypeURL(msg)) +} diff --git a/modules/apps/27-interchain-accounts/host/types/msgs.go b/modules/apps/27-interchain-accounts/host/types/msgs.go new file mode 100644 index 0000000..5e5cd3e --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/types/msgs.go @@ -0,0 +1,57 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +var ( + _ sdk.Msg = (*MsgUpdateParams)(nil) + _ sdk.HasValidateBasic = (*MsgUpdateParams)(nil) + + _ sdk.Msg = (*MsgModuleQuerySafe)(nil) + _ sdk.HasValidateBasic = (*MsgModuleQuerySafe)(nil) +) + +// NewMsgUpdateParams creates a new MsgUpdateParams instance +func NewMsgUpdateParams(signer string, params Params) *MsgUpdateParams { + return &MsgUpdateParams{ + Signer: signer, + Params: params, + } +} + +// ValidateBasic implements sdk.HasValidateBasic +func (msg MsgUpdateParams) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + + return msg.Params.Validate() +} + +// NewMsgModuleQuerySafe creates a new MsgModuleQuerySafe instance +func NewMsgModuleQuerySafe(signer string, requests []QueryRequest) *MsgModuleQuerySafe { + return &MsgModuleQuerySafe{ + Signer: signer, + Requests: requests, + } +} + +// ValidateBasic implements sdk.HasValidateBasic +func (msg MsgModuleQuerySafe) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + + if len(msg.Requests) == 0 { + return errorsmod.Wrapf(ibcerrors.ErrInvalidRequest, "no queries provided") + } + + return nil +} diff --git a/modules/apps/27-interchain-accounts/host/types/msgs_test.go b/modules/apps/27-interchain-accounts/host/types/msgs_test.go new file mode 100644 index 0000000..7371898 --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/types/msgs_test.go @@ -0,0 +1,142 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + + ica "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func TestMsgUpdateParamsValidateBasic(t *testing.T) { + testCases := []struct { + name string + msg *types.MsgUpdateParams + expErr error + }{ + { + "success: valid signer address", + types.NewMsgUpdateParams(sdk.AccAddress(ibctesting.TestAccAddress).String(), types.DefaultParams()), + nil, + }, + { + "failure: invalid signer address", + types.NewMsgUpdateParams("signer", types.DefaultParams()), + ibcerrors.ErrInvalidAddress, + }, + { + "failure: invalid allowed message", + types.NewMsgUpdateParams("signer", types.Params{ + AllowMessages: []string{""}, + }), + ibcerrors.ErrInvalidAddress, + }, + } + + for _, tc := range testCases { + + err := tc.msg.ValidateBasic() + if tc.expErr == nil { + require.NoError(t, err) + } else { + require.ErrorIs(t, err, tc.expErr) + } + } +} + +func TestMsgUpdateParamsGetSigners(t *testing.T) { + testCases := []struct { + name string + address sdk.AccAddress + errMsg string + }{ + {"success: valid address", sdk.AccAddress(ibctesting.TestAccAddress), ""}, + {"failure: nil address", nil, "empty address string is not allowed"}, + } + + for _, tc := range testCases { + + msg := types.NewMsgUpdateParams(tc.address.String(), types.DefaultParams()) + encodingCfg := moduletestutil.MakeTestEncodingConfig(ica.AppModuleBasic{}) + signers, _, err := encodingCfg.Codec.GetMsgV1Signers(msg) + if tc.errMsg == "" { + require.NoError(t, err) + require.Equal(t, tc.address.Bytes(), signers[0]) + } else { + require.ErrorContains(t, err, tc.errMsg) + } + } +} + +func TestMsgModuleQuerySafeValidateBasic(t *testing.T) { + queryRequest := types.QueryRequest{ + Path: "/cosmos.bank.v1beta1.Query/Balance", + Data: []byte{}, + } + + testCases := []struct { + name string + msg *types.MsgModuleQuerySafe + expErr error + }{ + { + "success: valid signer address", + types.NewMsgModuleQuerySafe(sdk.AccAddress(ibctesting.TestAccAddress).String(), []types.QueryRequest{queryRequest}), + nil, + }, + { + "failure: invalid signer address", + types.NewMsgModuleQuerySafe("signer", []types.QueryRequest{queryRequest}), + ibcerrors.ErrInvalidAddress, + }, + { + "failure: empty query requests", + types.NewMsgModuleQuerySafe(sdk.AccAddress(ibctesting.TestAccAddress).String(), []types.QueryRequest{}), + ibcerrors.ErrInvalidRequest, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + + if tc.expErr == nil { + require.NoError(t, err) + } else { + require.Error(t, err) + require.ErrorIs(t, err, tc.expErr) + } + }) + } +} + +func TestMsgModuleQuerySafeGetSigners(t *testing.T) { + testCases := []struct { + name string + address sdk.AccAddress + errMsg string + }{ + {"success: valid address", sdk.AccAddress(ibctesting.TestAccAddress), ""}, + {"failure: nil address", nil, "empty address string is not allowed"}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + msg := types.NewMsgModuleQuerySafe(tc.address.String(), []types.QueryRequest{}) + encodingCfg := moduletestutil.MakeTestEncodingConfig(ica.AppModuleBasic{}) + signers, _, err := encodingCfg.Codec.GetMsgV1Signers(msg) + if tc.errMsg == "" { + require.NoError(t, err) + require.Equal(t, tc.address.Bytes(), signers[0]) + } else { + require.ErrorContains(t, err, tc.errMsg) + } + }) + } +} diff --git a/modules/apps/27-interchain-accounts/host/types/params.go b/modules/apps/27-interchain-accounts/host/types/params.go new file mode 100644 index 0000000..3394d50 --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/types/params.go @@ -0,0 +1,50 @@ +package types + +import ( + "fmt" + "slices" + "strings" +) + +const ( + // DefaultHostEnabled is the default value for the host param (set to true) + DefaultHostEnabled = true + // Maximum length of the allowlist + MaxAllowListLength = 500 +) + +// NewParams creates a new parameter configuration for the host submodule +func NewParams(enableHost bool, allowMsgs []string) Params { + return Params{ + HostEnabled: enableHost, + AllowMessages: allowMsgs, + } +} + +// DefaultParams is the default parameter configuration for the host submodule +func DefaultParams() Params { + return NewParams(DefaultHostEnabled, []string{AllowAllHostMsgs}) +} + +// Validate validates all host submodule parameters +func (p Params) Validate() error { + return validateAllowlist(p.AllowMessages) +} + +func validateAllowlist(allowMsgs []string) error { + if len(allowMsgs) > MaxAllowListLength { + return fmt.Errorf("allow list length must not exceed %d items", MaxAllowListLength) + } + + if slices.Contains(allowMsgs, AllowAllHostMsgs) && len(allowMsgs) > 1 { + return fmt.Errorf("allow list must have only one element because the allow all host messages wildcard (%s) is present", AllowAllHostMsgs) + } + + for _, typeURL := range allowMsgs { + if strings.TrimSpace(typeURL) == "" { + return fmt.Errorf("parameter must not contain empty strings: %s", allowMsgs) + } + } + + return nil +} diff --git a/modules/apps/27-interchain-accounts/host/types/params_legacy.go b/modules/apps/27-interchain-accounts/host/types/params_legacy.go new file mode 100644 index 0000000..6c2601d --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/types/params_legacy.go @@ -0,0 +1,59 @@ +/* +NOTE: Usage of x/params to manage parameters is deprecated in favor of x/gov +controlled execution of MsgUpdateParams messages. These types remains solely +for migration purposes and will be removed in a future release. +[#3621](https://github.com/cosmos/ibc-go/issues/3621) +*/ + +package types + +import ( + "fmt" + "strings" + + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" +) + +var ( + // KeyHostEnabled is the store key for HostEnabled Params + KeyHostEnabled = []byte("HostEnabled") + // KeyAllowMessages is the store key for the AllowMessages Params + KeyAllowMessages = []byte("AllowMessages") +) + +// ParamKeyTable type declaration for parameters +func ParamKeyTable() paramtypes.KeyTable { + return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) +} + +// ParamSetPairs implements params.ParamSet +func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { + return paramtypes.ParamSetPairs{ + paramtypes.NewParamSetPair(KeyHostEnabled, &p.HostEnabled, validateEnabledType), + paramtypes.NewParamSetPair(KeyAllowMessages, &p.AllowMessages, validateAllowlistLegacy), + } +} + +func validateEnabledType(i any) error { + _, ok := i.(bool) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + return nil +} + +func validateAllowlistLegacy(i any) error { + allowMsgs, ok := i.([]string) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + for _, typeURL := range allowMsgs { + if strings.TrimSpace(typeURL) == "" { + return fmt.Errorf("parameter must not contain empty strings: %s", allowMsgs) + } + } + + return nil +} diff --git a/modules/apps/27-interchain-accounts/host/types/params_test.go b/modules/apps/27-interchain-accounts/host/types/params_test.go new file mode 100644 index 0000000..612082e --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/types/params_test.go @@ -0,0 +1,18 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" +) + +func TestValidateParams(t *testing.T) { + require.NoError(t, types.DefaultParams().Validate()) + require.NoError(t, types.NewParams(false, []string{}).Validate()) + require.Error(t, types.NewParams(true, []string{""}).Validate()) + require.Error(t, types.NewParams(true, []string{" "}).Validate()) + require.Error(t, types.NewParams(true, []string{"*", "/cosmos.bank.v1beta1.MsgSend"}).Validate()) + require.Error(t, types.NewParams(true, make([]string, types.MaxAllowListLength+1)).Validate()) +} diff --git a/modules/apps/27-interchain-accounts/host/types/query.pb.go b/modules/apps/27-interchain-accounts/host/types/query.pb.go new file mode 100644 index 0000000..423c0ed --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/types/query.pb.go @@ -0,0 +1,545 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/applications/interchain_accounts/host/v1/query.proto + +package types + +import ( + context "context" + fmt "fmt" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// QueryParamsRequest is the request type for the Query/Params RPC method. +type QueryParamsRequest struct { +} + +func (m *QueryParamsRequest) Reset() { *m = QueryParamsRequest{} } +func (m *QueryParamsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryParamsRequest) ProtoMessage() {} +func (*QueryParamsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_e6b7e23fc90c353a, []int{0} +} +func (m *QueryParamsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsRequest.Merge(m, src) +} +func (m *QueryParamsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsRequest proto.InternalMessageInfo + +// QueryParamsResponse is the response type for the Query/Params RPC method. +type QueryParamsResponse struct { + // params defines the parameters of the module. + Params *Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params,omitempty"` +} + +func (m *QueryParamsResponse) Reset() { *m = QueryParamsResponse{} } +func (m *QueryParamsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryParamsResponse) ProtoMessage() {} +func (*QueryParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_e6b7e23fc90c353a, []int{1} +} +func (m *QueryParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsResponse.Merge(m, src) +} +func (m *QueryParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsResponse proto.InternalMessageInfo + +func (m *QueryParamsResponse) GetParams() *Params { + if m != nil { + return m.Params + } + return nil +} + +func init() { + proto.RegisterType((*QueryParamsRequest)(nil), "ibc.applications.interchain_accounts.host.v1.QueryParamsRequest") + proto.RegisterType((*QueryParamsResponse)(nil), "ibc.applications.interchain_accounts.host.v1.QueryParamsResponse") +} + +func init() { + proto.RegisterFile("ibc/applications/interchain_accounts/host/v1/query.proto", fileDescriptor_e6b7e23fc90c353a) +} + +var fileDescriptor_e6b7e23fc90c353a = []byte{ + // 311 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x91, 0x31, 0x4b, 0x03, 0x31, + 0x14, 0xc7, 0x1b, 0xc1, 0x0e, 0x71, 0x8b, 0x0e, 0x52, 0x24, 0x48, 0x27, 0x87, 0x36, 0xb1, 0xb5, + 0x50, 0x47, 0x75, 0xd5, 0x41, 0x1d, 0x5d, 0x24, 0x17, 0xc3, 0x35, 0xd0, 0xcb, 0x4b, 0x2f, 0xb9, + 0x83, 0xae, 0x7e, 0x02, 0xc1, 0x8f, 0xe4, 0xe2, 0x22, 0x14, 0x5c, 0x1c, 0xe5, 0xce, 0x0f, 0x22, + 0x97, 0x3b, 0xd0, 0xa2, 0x08, 0x87, 0xeb, 0x0b, 0xbf, 0xdf, 0xff, 0xfd, 0x5f, 0xf0, 0xb1, 0x8e, + 0x24, 0x17, 0xd6, 0xce, 0xb5, 0x14, 0x5e, 0x83, 0x71, 0x5c, 0x1b, 0xaf, 0x52, 0x39, 0x13, 0xda, + 0xdc, 0x0a, 0x29, 0x21, 0x33, 0xde, 0xf1, 0x19, 0x38, 0xcf, 0xf3, 0x11, 0x5f, 0x64, 0x2a, 0x5d, + 0x32, 0x9b, 0x82, 0x07, 0x32, 0xd0, 0x91, 0x64, 0xdf, 0x49, 0xf6, 0x0b, 0xc9, 0x2a, 0x92, 0xe5, + 0xa3, 0xde, 0x5e, 0x0c, 0x10, 0xcf, 0x15, 0x17, 0x56, 0x73, 0x61, 0x0c, 0xf8, 0x86, 0x09, 0xae, + 0xde, 0xb4, 0xd5, 0x16, 0xc1, 0x19, 0xc0, 0xfe, 0x0e, 0x26, 0x57, 0xd5, 0x4e, 0x97, 0x22, 0x15, + 0x89, 0xbb, 0x56, 0x8b, 0x4c, 0x39, 0xdf, 0x97, 0x78, 0x7b, 0x6d, 0xea, 0x2c, 0x18, 0xa7, 0xc8, + 0x05, 0xee, 0xda, 0x30, 0xd9, 0x45, 0xfb, 0xe8, 0x60, 0x6b, 0x3c, 0x61, 0x6d, 0x2a, 0xb0, 0xc6, + 0xd6, 0x38, 0xc6, 0x2f, 0x08, 0x6f, 0x86, 0x14, 0xf2, 0x84, 0x70, 0xb7, 0x7e, 0x24, 0x27, 0xed, + 0x94, 0x3f, 0x77, 0xef, 0x9d, 0xfe, 0xc3, 0x50, 0xf7, 0xec, 0x4f, 0xee, 0x5f, 0x3f, 0x1e, 0x37, + 0x18, 0x19, 0xf0, 0xe6, 0xac, 0x7f, 0x9f, 0xb3, 0xee, 0x73, 0xa6, 0x9e, 0x0b, 0x8a, 0x56, 0x05, + 0x45, 0xef, 0x05, 0x45, 0x0f, 0x25, 0xed, 0xac, 0x4a, 0xda, 0x79, 0x2b, 0x69, 0xe7, 0xe6, 0x3c, + 0xd6, 0x7e, 0x96, 0x45, 0x4c, 0x42, 0xc2, 0x25, 0xb8, 0x04, 0x5c, 0x25, 0x1e, 0xc6, 0xc0, 0xf3, + 0xd1, 0x21, 0x4f, 0xe0, 0x2e, 0x9b, 0x2b, 0x57, 0xe7, 0x8c, 0xa7, 0xc3, 0xaf, 0xa8, 0xe1, 0x7a, + 0x94, 0x5f, 0x5a, 0xe5, 0xa2, 0x6e, 0xf8, 0xb8, 0xa3, 0xcf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x93, + 0x58, 0xd7, 0x4a, 0x79, 0x02, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // Params queries all parameters of the ICA host submodule. + Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) { + out := new(QueryParamsResponse) + err := c.cc.Invoke(ctx, "/ibc.applications.interchain_accounts.host.v1.Query/Params", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // Params queries all parameters of the ICA host submodule. + Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryParamsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Params(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.applications.interchain_accounts.host.v1.Query/Params", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Params(ctx, req.(*QueryParamsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ibc.applications.interchain_accounts.host.v1.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Params", + Handler: _Query_Params_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "ibc/applications/interchain_accounts/host/v1/query.proto", +} + +func (m *QueryParamsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Params != nil { + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryParamsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Params != nil { + l = m.Params.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryParamsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Params == nil { + m.Params = &Params{} + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/apps/27-interchain-accounts/host/types/query.pb.gw.go b/modules/apps/27-interchain-accounts/host/types/query.pb.gw.go new file mode 100644 index 0000000..b2918f6 --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/types/query.pb.gw.go @@ -0,0 +1,153 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: ibc/applications/interchain_accounts/host/v1/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +func request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := client.Params(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := server.Params(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Params_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Params_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 2, 5}, []string{"ibc", "apps", "interchain_accounts", "host", "v1", "params"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Query_Params_0 = runtime.ForwardResponseMessage +) diff --git a/modules/apps/27-interchain-accounts/host/types/tx.pb.go b/modules/apps/27-interchain-accounts/host/types/tx.pb.go new file mode 100644 index 0000000..8facda4 --- /dev/null +++ b/modules/apps/27-interchain-accounts/host/types/tx.pb.go @@ -0,0 +1,1059 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/applications/interchain_accounts/host/v1/tx.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/cosmos/cosmos-sdk/types/msgservice" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// MsgUpdateParams defines the payload for Msg/UpdateParams +type MsgUpdateParams struct { + // signer address + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // params defines the 27-interchain-accounts/host parameters to update. + // + // NOTE: All parameters must be supplied. + Params Params `protobuf:"bytes,2,opt,name=params,proto3" json:"params"` +} + +func (m *MsgUpdateParams) Reset() { *m = MsgUpdateParams{} } +func (m *MsgUpdateParams) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParams) ProtoMessage() {} +func (*MsgUpdateParams) Descriptor() ([]byte, []int) { + return fileDescriptor_fa437afde7f1e7ae, []int{0} +} +func (m *MsgUpdateParams) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParams.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParams.Merge(m, src) +} +func (m *MsgUpdateParams) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParams) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParams.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParams proto.InternalMessageInfo + +// MsgUpdateParamsResponse defines the response for Msg/UpdateParams +type MsgUpdateParamsResponse struct { +} + +func (m *MsgUpdateParamsResponse) Reset() { *m = MsgUpdateParamsResponse{} } +func (m *MsgUpdateParamsResponse) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParamsResponse) ProtoMessage() {} +func (*MsgUpdateParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_fa437afde7f1e7ae, []int{1} +} +func (m *MsgUpdateParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParamsResponse.Merge(m, src) +} +func (m *MsgUpdateParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParamsResponse proto.InternalMessageInfo + +// MsgModuleQuerySafe defines the payload for Msg/ModuleQuerySafe +type MsgModuleQuerySafe struct { + // signer address + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // requests defines the module safe queries to execute. + Requests []QueryRequest `protobuf:"bytes,2,rep,name=requests,proto3" json:"requests"` +} + +func (m *MsgModuleQuerySafe) Reset() { *m = MsgModuleQuerySafe{} } +func (m *MsgModuleQuerySafe) String() string { return proto.CompactTextString(m) } +func (*MsgModuleQuerySafe) ProtoMessage() {} +func (*MsgModuleQuerySafe) Descriptor() ([]byte, []int) { + return fileDescriptor_fa437afde7f1e7ae, []int{2} +} +func (m *MsgModuleQuerySafe) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgModuleQuerySafe) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgModuleQuerySafe.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgModuleQuerySafe) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgModuleQuerySafe.Merge(m, src) +} +func (m *MsgModuleQuerySafe) XXX_Size() int { + return m.Size() +} +func (m *MsgModuleQuerySafe) XXX_DiscardUnknown() { + xxx_messageInfo_MsgModuleQuerySafe.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgModuleQuerySafe proto.InternalMessageInfo + +// MsgModuleQuerySafeResponse defines the response for Msg/ModuleQuerySafe +type MsgModuleQuerySafeResponse struct { + // height at which the responses were queried + Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` + // protobuf encoded responses for each query + Responses [][]byte `protobuf:"bytes,2,rep,name=responses,proto3" json:"responses,omitempty"` +} + +func (m *MsgModuleQuerySafeResponse) Reset() { *m = MsgModuleQuerySafeResponse{} } +func (m *MsgModuleQuerySafeResponse) String() string { return proto.CompactTextString(m) } +func (*MsgModuleQuerySafeResponse) ProtoMessage() {} +func (*MsgModuleQuerySafeResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_fa437afde7f1e7ae, []int{3} +} +func (m *MsgModuleQuerySafeResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgModuleQuerySafeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgModuleQuerySafeResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgModuleQuerySafeResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgModuleQuerySafeResponse.Merge(m, src) +} +func (m *MsgModuleQuerySafeResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgModuleQuerySafeResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgModuleQuerySafeResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgModuleQuerySafeResponse proto.InternalMessageInfo + +func (m *MsgModuleQuerySafeResponse) GetHeight() uint64 { + if m != nil { + return m.Height + } + return 0 +} + +func (m *MsgModuleQuerySafeResponse) GetResponses() [][]byte { + if m != nil { + return m.Responses + } + return nil +} + +func init() { + proto.RegisterType((*MsgUpdateParams)(nil), "ibc.applications.interchain_accounts.host.v1.MsgUpdateParams") + proto.RegisterType((*MsgUpdateParamsResponse)(nil), "ibc.applications.interchain_accounts.host.v1.MsgUpdateParamsResponse") + proto.RegisterType((*MsgModuleQuerySafe)(nil), "ibc.applications.interchain_accounts.host.v1.MsgModuleQuerySafe") + proto.RegisterType((*MsgModuleQuerySafeResponse)(nil), "ibc.applications.interchain_accounts.host.v1.MsgModuleQuerySafeResponse") +} + +func init() { + proto.RegisterFile("ibc/applications/interchain_accounts/host/v1/tx.proto", fileDescriptor_fa437afde7f1e7ae) +} + +var fileDescriptor_fa437afde7f1e7ae = []byte{ + // 458 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x93, 0x41, 0x6b, 0xd4, 0x40, + 0x14, 0xc7, 0x33, 0x6d, 0x5d, 0xec, 0xb4, 0x50, 0x08, 0x62, 0x6b, 0x90, 0xb4, 0xec, 0x69, 0x29, + 0xee, 0x8c, 0xbb, 0x2a, 0x85, 0x82, 0x20, 0x05, 0x41, 0x90, 0x05, 0x1d, 0xf1, 0x22, 0x82, 0x4c, + 0xa6, 0xe3, 0x64, 0xa0, 0xc9, 0xc4, 0xbc, 0xc9, 0x62, 0x6f, 0xe2, 0xc9, 0x93, 0x78, 0xf0, 0x26, + 0x82, 0x1f, 0xa1, 0xdf, 0xc1, 0x4b, 0x8f, 0x3d, 0x7a, 0x12, 0xd9, 0x3d, 0xf4, 0x6b, 0x48, 0x66, + 0xa7, 0xad, 0x4d, 0xed, 0x21, 0xf4, 0x96, 0x49, 0xe6, 0xff, 0x7b, 0xbf, 0x97, 0x99, 0x87, 0x1f, + 0xe8, 0x44, 0x50, 0x5e, 0x14, 0x7b, 0x5a, 0x70, 0xab, 0x4d, 0x0e, 0x54, 0xe7, 0x56, 0x96, 0x22, + 0xe5, 0x3a, 0x7f, 0xc3, 0x85, 0x30, 0x55, 0x6e, 0x81, 0xa6, 0x06, 0x2c, 0x1d, 0x0f, 0xa8, 0x7d, + 0x4f, 0x8a, 0xd2, 0x58, 0x13, 0xde, 0xd1, 0x89, 0x20, 0xff, 0xc6, 0xc8, 0x7f, 0x62, 0xa4, 0x8e, + 0x91, 0xf1, 0x20, 0xba, 0xa1, 0x8c, 0x32, 0x2e, 0x48, 0xeb, 0xa7, 0x19, 0x23, 0x5a, 0x15, 0x06, + 0x32, 0x03, 0x34, 0x03, 0x55, 0xb3, 0x33, 0x50, 0xfe, 0xc3, 0x56, 0x2b, 0x27, 0x57, 0xc4, 0x05, + 0xbb, 0x9f, 0x11, 0x5e, 0x19, 0x81, 0x7a, 0x59, 0xec, 0x72, 0x2b, 0x9f, 0xf1, 0x92, 0x67, 0x10, + 0xde, 0xc4, 0x1d, 0xd0, 0x2a, 0x97, 0xe5, 0x1a, 0xda, 0x40, 0xbd, 0x45, 0xe6, 0x57, 0x21, 0xc3, + 0x9d, 0xc2, 0xed, 0x58, 0x9b, 0xdb, 0x40, 0xbd, 0xa5, 0xe1, 0x7d, 0xd2, 0xa6, 0x25, 0x32, 0xa3, + 0xef, 0x2c, 0x1c, 0xfe, 0x5e, 0x0f, 0x98, 0x27, 0x6d, 0xaf, 0x7c, 0xfa, 0xb1, 0x1e, 0x7c, 0x3c, + 0x3e, 0xd8, 0xf4, 0x45, 0xba, 0xb7, 0xf0, 0x6a, 0xc3, 0x87, 0x49, 0x28, 0x4c, 0x0e, 0xb2, 0xfb, + 0x0d, 0xe1, 0x70, 0x04, 0x6a, 0x64, 0x76, 0xab, 0x3d, 0xf9, 0xbc, 0x92, 0xe5, 0xfe, 0x0b, 0xfe, + 0x56, 0x5e, 0xaa, 0xfb, 0x1a, 0x5f, 0x2f, 0xe5, 0xbb, 0x4a, 0x82, 0xad, 0x85, 0xe7, 0x7b, 0x4b, + 0xc3, 0xed, 0x76, 0xc2, 0xae, 0x04, 0x9b, 0x21, 0xbc, 0xf6, 0x29, 0xf1, 0xa2, 0x38, 0xc3, 0xd1, + 0x45, 0xb9, 0x13, 0xf7, 0x5a, 0x32, 0x95, 0x5a, 0xa5, 0xd6, 0x49, 0x2e, 0x30, 0xbf, 0x0a, 0x6f, + 0xe3, 0xc5, 0xd2, 0xef, 0x99, 0x59, 0x2e, 0xb3, 0xb3, 0x17, 0xc3, 0x9f, 0x73, 0x78, 0x7e, 0x04, + 0x2a, 0xfc, 0x8a, 0xf0, 0xf2, 0xb9, 0x23, 0x7a, 0xd8, 0xae, 0x93, 0xc6, 0x1f, 0x8d, 0x1e, 0x5f, + 0x29, 0x7e, 0xda, 0xd4, 0xf7, 0xfa, 0xf2, 0x34, 0x4e, 0xe3, 0x51, 0x6b, 0x74, 0x83, 0x10, 0x3d, + 0xb9, 0x2a, 0xe1, 0xc4, 0x2f, 0xba, 0xf6, 0xe1, 0xf8, 0x60, 0x13, 0xed, 0xc8, 0xc3, 0x49, 0x8c, + 0x8e, 0x26, 0x31, 0xfa, 0x33, 0x89, 0xd1, 0x97, 0x69, 0x1c, 0x1c, 0x4d, 0xe3, 0xe0, 0xd7, 0x34, + 0x0e, 0x5e, 0x3d, 0x55, 0xda, 0xa6, 0x55, 0x42, 0x84, 0xc9, 0xa8, 0x1f, 0x2d, 0x9d, 0x88, 0xbe, + 0x32, 0x74, 0x3c, 0xb8, 0x4b, 0x33, 0x87, 0x85, 0x7a, 0xae, 0x80, 0x0e, 0xb7, 0xfa, 0x67, 0x16, + 0xfd, 0xf3, 0x23, 0x65, 0xf7, 0x0b, 0x09, 0x49, 0xc7, 0x4d, 0xd4, 0xbd, 0xbf, 0x01, 0x00, 0x00, + 0xff, 0xff, 0x2a, 0x4c, 0x2b, 0x97, 0x20, 0x04, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MsgClient is the client API for Msg service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MsgClient interface { + // UpdateParams defines a rpc handler for MsgUpdateParams. + UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) + // ModuleQuerySafe defines a rpc handler for MsgModuleQuerySafe. + ModuleQuerySafe(ctx context.Context, in *MsgModuleQuerySafe, opts ...grpc.CallOption) (*MsgModuleQuerySafeResponse, error) +} + +type msgClient struct { + cc grpc1.ClientConn +} + +func NewMsgClient(cc grpc1.ClientConn) MsgClient { + return &msgClient{cc} +} + +func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { + out := new(MsgUpdateParamsResponse) + err := c.cc.Invoke(ctx, "/ibc.applications.interchain_accounts.host.v1.Msg/UpdateParams", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) ModuleQuerySafe(ctx context.Context, in *MsgModuleQuerySafe, opts ...grpc.CallOption) (*MsgModuleQuerySafeResponse, error) { + out := new(MsgModuleQuerySafeResponse) + err := c.cc.Invoke(ctx, "/ibc.applications.interchain_accounts.host.v1.Msg/ModuleQuerySafe", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MsgServer is the server API for Msg service. +type MsgServer interface { + // UpdateParams defines a rpc handler for MsgUpdateParams. + UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) + // ModuleQuerySafe defines a rpc handler for MsgModuleQuerySafe. + ModuleQuerySafe(context.Context, *MsgModuleQuerySafe) (*MsgModuleQuerySafeResponse, error) +} + +// UnimplementedMsgServer can be embedded to have forward compatible implementations. +type UnimplementedMsgServer struct { +} + +func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") +} +func (*UnimplementedMsgServer) ModuleQuerySafe(ctx context.Context, req *MsgModuleQuerySafe) (*MsgModuleQuerySafeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ModuleQuerySafe not implemented") +} + +func RegisterMsgServer(s grpc1.Server, srv MsgServer) { + s.RegisterService(&_Msg_serviceDesc, srv) +} + +func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUpdateParams) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).UpdateParams(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.applications.interchain_accounts.host.v1.Msg/UpdateParams", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).UpdateParams(ctx, req.(*MsgUpdateParams)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_ModuleQuerySafe_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgModuleQuerySafe) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).ModuleQuerySafe(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.applications.interchain_accounts.host.v1.Msg/ModuleQuerySafe", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).ModuleQuerySafe(ctx, req.(*MsgModuleQuerySafe)) + } + return interceptor(ctx, in, info, handler) +} + +var _Msg_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ibc.applications.interchain_accounts.host.v1.Msg", + HandlerType: (*MsgServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "UpdateParams", + Handler: _Msg_UpdateParams_Handler, + }, + { + MethodName: "ModuleQuerySafe", + Handler: _Msg_ModuleQuerySafe_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "ibc/applications/interchain_accounts/host/v1/tx.proto", +} + +func (m *MsgUpdateParams) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParams) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgUpdateParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgModuleQuerySafe) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgModuleQuerySafe) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgModuleQuerySafe) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Requests) > 0 { + for iNdEx := len(m.Requests) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Requests[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgModuleQuerySafeResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgModuleQuerySafeResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgModuleQuerySafeResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Responses) > 0 { + for iNdEx := len(m.Responses) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Responses[iNdEx]) + copy(dAtA[i:], m.Responses[iNdEx]) + i = encodeVarintTx(dAtA, i, uint64(len(m.Responses[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + if m.Height != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgUpdateParams) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Params.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgUpdateParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgModuleQuerySafe) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if len(m.Requests) > 0 { + for _, e := range m.Requests { + l = e.Size() + n += 1 + l + sovTx(uint64(l)) + } + } + return n +} + +func (m *MsgModuleQuerySafeResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Height != 0 { + n += 1 + sovTx(uint64(m.Height)) + } + if len(m.Responses) > 0 { + for _, b := range m.Responses { + l = len(b) + n += 1 + l + sovTx(uint64(l)) + } + } + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateParams: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParams: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgModuleQuerySafe) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgModuleQuerySafe: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgModuleQuerySafe: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Requests", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Requests = append(m.Requests, QueryRequest{}) + if err := m.Requests[len(m.Requests)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgModuleQuerySafeResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgModuleQuerySafeResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgModuleQuerySafeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Responses", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Responses = append(m.Responses, make([]byte, postIndex-iNdEx)) + copy(m.Responses[len(m.Responses)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTx(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/apps/27-interchain-accounts/module.go b/modules/apps/27-interchain-accounts/module.go new file mode 100644 index 0000000..7b24fe5 --- /dev/null +++ b/modules/apps/27-interchain-accounts/module.go @@ -0,0 +1,207 @@ +package ica + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + + "cosmossdk.io/core/appmodule" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/client/cli" + controllerkeeper "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/keeper" + controllertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + genesistypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/genesis/types" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host" + hostkeeper "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/keeper" + hosttypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/simulation" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" +) + +var ( + _ module.AppModule = (*AppModule)(nil) + _ module.AppModuleBasic = (*AppModuleBasic)(nil) + _ module.AppModuleSimulation = (*AppModule)(nil) + _ module.HasGenesis = (*AppModule)(nil) + _ module.HasName = (*AppModule)(nil) + _ module.HasConsensusVersion = (*AppModule)(nil) + _ module.HasServices = (*AppModule)(nil) + _ module.HasProposalMsgs = (*AppModule)(nil) + _ appmodule.AppModule = (*AppModule)(nil) + + _ porttypes.IBCModule = (*host.IBCModule)(nil) +) + +// AppModuleBasic is the IBC interchain accounts AppModuleBasic +type AppModuleBasic struct{} + +// Name implements AppModuleBasic interface +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (AppModule) IsOnePerModuleType() {} + +// IsAppModule implements the appmodule.AppModule interface. +func (AppModule) IsAppModule() {} + +// RegisterLegacyAminoCodec implements AppModuleBasic. +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {} + +// RegisterInterfaces registers module concrete types into protobuf Any +func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) { + controllertypes.RegisterInterfaces(registry) + hosttypes.RegisterInterfaces(registry) + types.RegisterInterfaces(registry) +} + +// DefaultGenesis returns default genesis state as raw bytes for the IBC +// interchain accounts module +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(genesistypes.DefaultGenesis()) +} + +// ValidateGenesis performs genesis state validation for the IBC interchain accounts module +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { + var gs genesistypes.GenesisState + if err := cdc.UnmarshalJSON(bz, &gs); err != nil { + return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) + } + + return gs.Validate() +} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the interchain accounts module. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { + err := controllertypes.RegisterQueryHandlerClient(context.Background(), mux, controllertypes.NewQueryClient(clientCtx)) + if err != nil { + panic(err) + } + + err = hosttypes.RegisterQueryHandlerClient(context.Background(), mux, hosttypes.NewQueryClient(clientCtx)) + if err != nil { + panic(err) + } +} + +// GetTxCmd implements AppModuleBasic interface +func (AppModuleBasic) GetTxCmd() *cobra.Command { + return cli.NewTxCmd() +} + +// GetQueryCmd implements AppModuleBasic interface +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd() +} + +// AppModule is the application module for the IBC interchain accounts module +type AppModule struct { + AppModuleBasic + controllerKeeper *controllerkeeper.Keeper + hostKeeper *hostkeeper.Keeper +} + +// NewAppModule creates a new IBC interchain accounts module +func NewAppModule(controllerKeeper *controllerkeeper.Keeper, hostKeeper *hostkeeper.Keeper) AppModule { + return AppModule{ + controllerKeeper: controllerKeeper, + hostKeeper: hostKeeper, + } +} + +// RegisterServices registers module services +func (am AppModule) RegisterServices(cfg module.Configurator) { + if am.controllerKeeper != nil { + controllertypes.RegisterMsgServer(cfg.MsgServer(), controllerkeeper.NewMsgServerImpl(am.controllerKeeper)) + controllertypes.RegisterQueryServer(cfg.QueryServer(), am.controllerKeeper) + } + + if am.hostKeeper != nil { + hosttypes.RegisterMsgServer(cfg.MsgServer(), hostkeeper.NewMsgServerImpl(am.hostKeeper)) + hosttypes.RegisterQueryServer(cfg.QueryServer(), am.hostKeeper) + } + + controllerMigrator := controllerkeeper.NewMigrator(am.controllerKeeper) + hostMigrator := hostkeeper.NewMigrator(am.hostKeeper) + if err := cfg.RegisterMigration(types.ModuleName, 2, func(ctx sdk.Context) error { + if err := hostMigrator.MigrateParams(ctx); err != nil { + return err + } + return controllerMigrator.MigrateParams(ctx) + }); err != nil { + panic(fmt.Errorf("failed to migrate interchainaccounts app from version 2 to 3 (self-managed params migration): %v", err)) + } +} + +// InitGenesis performs genesis initialization for the interchain accounts module. +// It returns no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) { + var genesisState genesistypes.GenesisState + cdc.MustUnmarshalJSON(data, &genesisState) + + if am.controllerKeeper != nil { + controllerkeeper.InitGenesis(ctx, *am.controllerKeeper, genesisState.ControllerGenesisState) + } + + if am.hostKeeper != nil { + hostkeeper.InitGenesis(ctx, *am.hostKeeper, genesisState.HostGenesisState) + } +} + +// ExportGenesis returns the exported genesis state as raw bytes for the interchain accounts module +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + var ( + controllerGenesisState = genesistypes.DefaultControllerGenesis() + hostGenesisState = genesistypes.DefaultHostGenesis() + ) + + if am.controllerKeeper != nil { + controllerGenesisState = controllerkeeper.ExportGenesis(ctx, *am.controllerKeeper) + } + + if am.hostKeeper != nil { + hostGenesisState = hostkeeper.ExportGenesis(ctx, *am.hostKeeper) + } + + gs := genesistypes.NewGenesisState(controllerGenesisState, hostGenesisState) + + return cdc.MustMarshalJSON(gs) +} + +// ConsensusVersion implements AppModule/ConsensusVersion. +func (AppModule) ConsensusVersion() uint64 { return 3 } + +// AppModuleSimulation functions + +// GenerateGenesisState creates a randomized GenState of the ics27 module. +func (AppModule) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) +} + +// ProposalMsgs returns msgs used for governance proposals for simulations. +func (am AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { + return simulation.ProposalMsgs(am.controllerKeeper, am.hostKeeper) +} + +// WeightedOperations is unimplemented. +func (AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { + return nil +} + +// RegisterStoreDecoder registers a decoder for interchain accounts module's types +func (AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { + sdr[controllertypes.StoreKey] = simulation.NewDecodeStore() + sdr[hosttypes.StoreKey] = simulation.NewDecodeStore() +} diff --git a/modules/apps/27-interchain-accounts/module_test.go b/modules/apps/27-interchain-accounts/module_test.go new file mode 100644 index 0000000..d638743 --- /dev/null +++ b/modules/apps/27-interchain-accounts/module_test.go @@ -0,0 +1,23 @@ +package ica_test + +import ( + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +type InterchainAccountsTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator +} + +func TestICATestSuite(t *testing.T) { + testifysuite.Run(t, new(InterchainAccountsTestSuite)) +} + +func (suite *InterchainAccountsTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) +} diff --git a/modules/apps/27-interchain-accounts/simulation/decoder.go b/modules/apps/27-interchain-accounts/simulation/decoder.go new file mode 100644 index 0000000..3e6a530 --- /dev/null +++ b/modules/apps/27-interchain-accounts/simulation/decoder.go @@ -0,0 +1,30 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" +) + +// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's +// Value to the corresponding DenomTrace type. +func NewDecodeStore() func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + switch { + case bytes.Equal(kvA.Key[:len(types.PortKeyPrefix)], []byte(types.PortKeyPrefix)): + return fmt.Sprintf("Port A: %s\nPort B: %s", string(kvA.Value), string(kvB.Value)) + case bytes.Equal(kvA.Key[:len(types.OwnerKeyPrefix)], []byte(types.OwnerKeyPrefix)): + return fmt.Sprintf("Owner A: %s\nOwner B: %s", string(kvA.Value), string(kvB.Value)) + case bytes.Equal(kvA.Key[:len(types.ActiveChannelKeyPrefix)], []byte(types.ActiveChannelKeyPrefix)): + return fmt.Sprintf("ActiveChannel A: %s\nActiveChannel B: %s", string(kvA.Value), string(kvB.Value)) + case bytes.Equal(kvA.Key[:len(types.IsMiddlewareEnabledPrefix)], []byte(types.IsMiddlewareEnabledPrefix)): + return fmt.Sprintf("IsMiddlewareEnabled A: %s\nIsMiddlewareEnabled B: %s", string(kvA.Value), string(kvB.Value)) + + default: + panic(fmt.Errorf("invalid %s key prefix %s", types.ModuleName, kvA.Key)) + } + } +} diff --git a/modules/apps/27-interchain-accounts/simulation/decoder_test.go b/modules/apps/27-interchain-accounts/simulation/decoder_test.go new file mode 100644 index 0000000..b006e75 --- /dev/null +++ b/modules/apps/27-interchain-accounts/simulation/decoder_test.go @@ -0,0 +1,64 @@ +package simulation_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/simulation" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func TestDecodeStore(t *testing.T) { + var ( + owner = "owner" + channelID = ibctesting.FirstChannelID + ) + + dec := simulation.NewDecodeStore() + + kvPairs := kv.Pairs{ + Pairs: []kv.Pair{ + { + Key: []byte(types.PortKeyPrefix), + Value: []byte(types.HostPortID), + }, + { + Key: []byte(types.OwnerKeyPrefix), + Value: []byte("owner"), + }, + { + Key: []byte(types.ActiveChannelKeyPrefix), + Value: []byte("channel-0"), + }, + { + Key: []byte(types.IsMiddlewareEnabledPrefix), + Value: []byte("false"), + }, + }, + } + tests := []struct { + name string + expectedLog string + }{ + {"PortID", fmt.Sprintf("Port A: %s\nPort B: %s", types.HostPortID, types.HostPortID)}, + {"Owner", fmt.Sprintf("Owner A: %s\nOwner B: %s", owner, owner)}, + {"ActiveChannel", fmt.Sprintf("ActiveChannel A: %s\nActiveChannel B: %s", channelID, channelID)}, + {"IsMiddlewareEnabled", fmt.Sprintf("IsMiddlewareEnabled A: %s\nIsMiddlewareEnabled B: %s", "false", "false")}, + {"other", ""}, + } + + for i, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if i == len(tests)-1 { + require.Panics(t, func() { dec(kvPairs.Pairs[i], kvPairs.Pairs[i]) }, tt.name) + } else { + require.Equal(t, tt.expectedLog, dec(kvPairs.Pairs[i], kvPairs.Pairs[i]), tt.name) + } + }) + } +} diff --git a/modules/apps/27-interchain-accounts/simulation/genesis.go b/modules/apps/27-interchain-accounts/simulation/genesis.go new file mode 100644 index 0000000..7f12d69 --- /dev/null +++ b/modules/apps/27-interchain-accounts/simulation/genesis.go @@ -0,0 +1,70 @@ +package simulation + +import ( + "encoding/json" + "fmt" + "math/rand" + + "github.com/cosmos/cosmos-sdk/types/module" + + controllertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + genesistypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/genesis/types" + hosttypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" +) + +// RandomEnabled randomized controller or host enabled param with 75% prob of being true. +func RandomEnabled(r *rand.Rand) bool { + return r.Int63n(101) <= 75 +} + +// RandomizedGenState generates a random GenesisState for ics27. +// Only the params are non nil +func RandomizedGenState(simState *module.SimulationState) { + var controllerEnabled bool + simState.AppParams.GetOrGenerate( + string(controllertypes.KeyControllerEnabled), &controllerEnabled, simState.Rand, + func(r *rand.Rand) { controllerEnabled = RandomEnabled(r) }, + ) + + controllerParams := controllertypes.Params{ + ControllerEnabled: controllerEnabled, + } + + controllerGenesisState := genesistypes.ControllerGenesisState{ + ActiveChannels: nil, + InterchainAccounts: nil, + Ports: []string{}, + Params: controllerParams, + } + + var hostEnabled bool + simState.AppParams.GetOrGenerate( + string(hosttypes.KeyHostEnabled), &hostEnabled, simState.Rand, + func(r *rand.Rand) { hostEnabled = RandomEnabled(r) }, + ) + + hostParams := hosttypes.Params{ + HostEnabled: hostEnabled, + AllowMessages: []string{"*"}, // allow all messages + } + + hostGenesisState := genesistypes.HostGenesisState{ + ActiveChannels: nil, + InterchainAccounts: nil, + Port: types.HostPortID, + Params: hostParams, + } + + icaGenesis := genesistypes.GenesisState{ + ControllerGenesisState: controllerGenesisState, + HostGenesisState: hostGenesisState, + } + + bz, err := json.MarshalIndent(&icaGenesis, "", " ") + if err != nil { + panic(err) + } + fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, bz) + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&icaGenesis) +} diff --git a/modules/apps/27-interchain-accounts/simulation/genesis_test.go b/modules/apps/27-interchain-accounts/simulation/genesis_test.go new file mode 100644 index 0000000..6afa7ce --- /dev/null +++ b/modules/apps/27-interchain-accounts/simulation/genesis_test.go @@ -0,0 +1,58 @@ +package simulation_test + +import ( + "encoding/json" + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + + sdkmath "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + genesistypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/genesis/types" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/simulation" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" +) + +// TestRandomizedGenState tests the normal scenario of applying RandomizedGenState. +// Abonormal scenarios are not tested here. +func TestRandomizedGenState(t *testing.T) { + interfaceRegistry := codectypes.NewInterfaceRegistry() + cryptocodec.RegisterInterfaces(interfaceRegistry) + cdc := codec.NewProtoCodec(interfaceRegistry) + + s := rand.NewSource(1) // 1 is the seed + r := rand.New(s) + + simState := module.SimulationState{ + AppParams: make(simtypes.AppParams), + Cdc: cdc, + Rand: r, + NumBonded: 3, + Accounts: simtypes.RandomAccounts(r, 3), + InitialStake: sdkmath.NewInt(1000), + GenState: make(map[string]json.RawMessage), + } + + simulation.RandomizedGenState(&simState) + + var icaGenesis genesistypes.GenesisState + simState.Cdc.MustUnmarshalJSON(simState.GenState[types.ModuleName], &icaGenesis) + + require.True(t, icaGenesis.ControllerGenesisState.Params.ControllerEnabled) + require.Empty(t, icaGenesis.ControllerGenesisState.ActiveChannels) + require.Empty(t, icaGenesis.ControllerGenesisState.InterchainAccounts) + require.Empty(t, icaGenesis.ControllerGenesisState.Ports) + + require.True(t, icaGenesis.HostGenesisState.Params.HostEnabled) + require.Equal(t, []string{"*"}, icaGenesis.HostGenesisState.Params.AllowMessages) + require.Equal(t, types.HostPortID, icaGenesis.HostGenesisState.Port) + require.Empty(t, icaGenesis.ControllerGenesisState.ActiveChannels) + require.Empty(t, icaGenesis.ControllerGenesisState.InterchainAccounts) +} diff --git a/modules/apps/27-interchain-accounts/simulation/proposals.go b/modules/apps/27-interchain-accounts/simulation/proposals.go new file mode 100644 index 0000000..e0ea334 --- /dev/null +++ b/modules/apps/27-interchain-accounts/simulation/proposals.go @@ -0,0 +1,66 @@ +package simulation + +import ( + "math/rand" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + + controllerkeeper "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/keeper" + controllertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + hostkeeper "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/keeper" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" +) + +// Simulation operation weights constants +const ( + DefaultWeightMsgUpdateParams int = 100 + + OpWeightMsgUpdateParams = "op_weight_msg_update_params" // #nosec +) + +// ProposalMsgs defines the module weighted proposals' contents +func ProposalMsgs(controllerKeeper *controllerkeeper.Keeper, hostKeeper *hostkeeper.Keeper) []simtypes.WeightedProposalMsg { + msgs := make([]simtypes.WeightedProposalMsg, 0, 2) + if hostKeeper != nil { + msgs = append(msgs, simulation.NewWeightedProposalMsg( + OpWeightMsgUpdateParams, + DefaultWeightMsgUpdateParams, + SimulateHostMsgUpdateParams, + )) + } + if controllerKeeper != nil { + msgs = append(msgs, simulation.NewWeightedProposalMsg( + OpWeightMsgUpdateParams, + DefaultWeightMsgUpdateParams, + SimulateControllerMsgUpdateParams, + )) + } + return msgs +} + +// SimulateHostMsgUpdateParams returns a MsgUpdateParams for the host module +func SimulateHostMsgUpdateParams(_ *rand.Rand, _ sdk.Context, _ []simtypes.Account) sdk.Msg { + var signer sdk.AccAddress = address.Module("gov") + params := types.DefaultParams() + params.HostEnabled = false + + return &types.MsgUpdateParams{ + Signer: signer.String(), + Params: params, + } +} + +// SimulateControllerMsgUpdateParams returns a MsgUpdateParams for the controller module +func SimulateControllerMsgUpdateParams(_ *rand.Rand, _ sdk.Context, _ []simtypes.Account) sdk.Msg { + var signer sdk.AccAddress = address.Module("gov") + params := controllertypes.DefaultParams() + params.ControllerEnabled = false + + return &controllertypes.MsgUpdateParams{ + Signer: signer.String(), + Params: params, + } +} diff --git a/modules/apps/27-interchain-accounts/simulation/proposals_test.go b/modules/apps/27-interchain-accounts/simulation/proposals_test.go new file mode 100644 index 0000000..c47ba04 --- /dev/null +++ b/modules/apps/27-interchain-accounts/simulation/proposals_test.go @@ -0,0 +1,101 @@ +package simulation_test + +import ( + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + + controllerkeeper "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/keeper" + controllertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + hostkeeper "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/keeper" + hosttypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/simulation" +) + +func TestProposalMsgs(t *testing.T) { + // initialize parameters + s := rand.NewSource(1) + r := rand.New(s) + + ctx := sdk.NewContext(nil, cmtproto.Header{}, true, nil) + accounts := simtypes.RandomAccounts(r, 3) + + tests := []struct { + name string + controller *controllerkeeper.Keeper + host *hostkeeper.Keeper + expMsgs []sdk.Msg + }{ + { + name: "host and controller keepers are both enabled", + controller: &controllerkeeper.Keeper{}, + host: &hostkeeper.Keeper{}, + expMsgs: []sdk.Msg{ + hosttypes.NewMsgUpdateParams( + sdk.AccAddress(address.Module("gov")).String(), + hosttypes.NewParams(false, []string{hosttypes.AllowAllHostMsgs}), + ), + controllertypes.NewMsgUpdateParams( + sdk.AccAddress(address.Module("gov")).String(), + controllertypes.NewParams(false), + ), + }, + }, + { + name: "host and controller keepers are not enabled", + controller: nil, + host: nil, + }, + { + name: "only controller keeper is enabled", + controller: &controllerkeeper.Keeper{}, + expMsgs: []sdk.Msg{ + controllertypes.NewMsgUpdateParams( + sdk.AccAddress(address.Module("gov")).String(), + controllertypes.NewParams(false), + ), + }, + }, + { + name: "only host keeper is enabled", + host: &hostkeeper.Keeper{}, + expMsgs: []sdk.Msg{ + hosttypes.NewMsgUpdateParams( + sdk.AccAddress(address.Module("gov")).String(), + hosttypes.NewParams(false, []string{hosttypes.AllowAllHostMsgs}), + ), + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // execute ProposalMsgs function + weightedProposalMsgs := simulation.ProposalMsgs(tc.controller, tc.host) + require.Equal(t, len(tc.expMsgs), len(weightedProposalMsgs)) + + for idx, weightedMsg := range weightedProposalMsgs { + // tests weighted interface: + require.Equal(t, simulation.OpWeightMsgUpdateParams, weightedMsg.AppParamsKey()) + require.Equal(t, simulation.DefaultWeightMsgUpdateParams, weightedMsg.DefaultWeight()) + + msg := weightedMsg.MsgSimulatorFn()(r, ctx, accounts) + + if msgUpdateHostParams, ok := msg.(*hosttypes.MsgUpdateParams); ok { + require.Equal(t, tc.expMsgs[idx], msgUpdateHostParams) + } else { + msgUpdateControllerParams, ok := msg.(*controllertypes.MsgUpdateParams) + require.True(t, ok) + require.Equal(t, tc.expMsgs[idx], msgUpdateControllerParams) + } + } + }) + } +} diff --git a/modules/apps/27-interchain-accounts/types/account.go b/modules/apps/27-interchain-accounts/types/account.go new file mode 100644 index 0000000..a080d03 --- /dev/null +++ b/modules/apps/27-interchain-accounts/types/account.go @@ -0,0 +1,156 @@ +package types + +import ( + "encoding/json" + "regexp" + "strings" + + yaml "gopkg.in/yaml.v2" + + errorsmod "cosmossdk.io/errors" + + crypto "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkaddress "github.com/cosmos/cosmos-sdk/types/address" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +var ( + _ authtypes.GenesisAccount = (*InterchainAccount)(nil) + _ InterchainAccountI = (*InterchainAccount)(nil) +) + +// DefaultMaxAddrLength defines the default maximum character length used in validation of addresses +var DefaultMaxAddrLength = 128 + +// isValidAddr defines a regular expression to check if the provided string consists of +// strictly alphanumeric characters and is non empty. +var isValidAddr = regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString + +// InterchainAccountI wraps the sdk.AccountI interface +type InterchainAccountI interface { + sdk.AccountI +} + +// interchainAccountPretty defines an unexported struct used for encoding the InterchainAccount details +type interchainAccountPretty struct { + Address sdk.AccAddress `json:"address" yaml:"address"` + PubKey string `json:"public_key" yaml:"public_key"` + AccountNumber uint64 `json:"account_number" yaml:"account_number"` + Sequence uint64 `json:"sequence" yaml:"sequence"` + AccountOwner string `json:"account_owner" yaml:"account_owner"` +} + +// GenerateAddress returns an sdk.AccAddress derived using a host module account address, host connection ID, the controller portID, +// the current block app hash, and the current block data hash. The sdk.AccAddress returned is a sub-address of the host module account. +func GenerateAddress(ctx sdk.Context, connectionID, portID string) sdk.AccAddress { + hostModuleAcc := sdkaddress.Module(ModuleName, []byte(hostAccountsKey)) + header := ctx.BlockHeader() + + buf := []byte(connectionID + portID) + buf = append(buf, header.AppHash...) + + return sdkaddress.Derive(hostModuleAcc, buf) +} + +// ValidateAccountAddress performs basic validation of interchain account addresses, enforcing constraints +// on address length and character set +func ValidateAccountAddress(addr string) error { + if !isValidAddr(addr) || len(addr) > DefaultMaxAddrLength { + return errorsmod.Wrapf( + ErrInvalidAccountAddress, + "address must contain strictly alphanumeric characters, not exceeding %d characters in length", + DefaultMaxAddrLength, + ) + } + + return nil +} + +// NewInterchainAccount creates and returns a new InterchainAccount type +func NewInterchainAccount(ba *authtypes.BaseAccount, accountOwner string) *InterchainAccount { + return &InterchainAccount{ + BaseAccount: ba, + AccountOwner: accountOwner, + } +} + +// SetPubKey implements the authtypes.AccountI interface +func (InterchainAccount) SetPubKey(pubkey crypto.PubKey) error { + return errorsmod.Wrap(ErrUnsupported, "cannot set public key for interchain account") +} + +// SetSequence implements the authtypes.AccountI interface +func (InterchainAccount) SetSequence(seq uint64) error { + return errorsmod.Wrap(ErrUnsupported, "cannot set sequence number for interchain account") +} + +// Validate implements basic validation of the InterchainAccount +func (ia InterchainAccount) Validate() error { + if strings.TrimSpace(ia.AccountOwner) == "" { + return errorsmod.Wrap(ErrInvalidAccountAddress, "AccountOwner cannot be empty") + } + + return ia.BaseAccount.Validate() +} + +// String returns a string representation of the InterchainAccount +func (ia InterchainAccount) String() string { + out, _ := ia.MarshalYAML() + return string(out) +} + +// MarshalYAML returns the YAML representation of the InterchainAccount +func (ia InterchainAccount) MarshalYAML() ([]byte, error) { + accAddr, err := sdk.AccAddressFromBech32(ia.Address) + if err != nil { + return nil, err + } + + bz, err := yaml.Marshal(interchainAccountPretty{ + Address: accAddr, + PubKey: "", + AccountNumber: ia.AccountNumber, + Sequence: ia.Sequence, + AccountOwner: ia.AccountOwner, + }) + if err != nil { + return nil, err + } + + return bz, nil +} + +// MarshalJSON returns the JSON representation of the InterchainAccount +func (ia InterchainAccount) MarshalJSON() ([]byte, error) { + accAddr, err := sdk.AccAddressFromBech32(ia.Address) + if err != nil { + return nil, err + } + + bz, err := json.Marshal(interchainAccountPretty{ + Address: accAddr, + PubKey: "", + AccountNumber: ia.AccountNumber, + Sequence: ia.Sequence, + AccountOwner: ia.AccountOwner, + }) + if err != nil { + return nil, err + } + + return bz, nil +} + +// UnmarshalJSON unmarshals raw JSON bytes into the InterchainAccount +func (ia *InterchainAccount) UnmarshalJSON(bz []byte) error { + var alias interchainAccountPretty + if err := json.Unmarshal(bz, &alias); err != nil { + return err + } + + ia.BaseAccount = authtypes.NewBaseAccount(alias.Address, nil, alias.AccountNumber, alias.Sequence) + ia.AccountOwner = alias.AccountOwner + + return nil +} diff --git a/modules/apps/27-interchain-accounts/types/account.pb.go b/modules/apps/27-interchain-accounts/types/account.pb.go new file mode 100644 index 0000000..e8c3be5 --- /dev/null +++ b/modules/apps/27-interchain-accounts/types/account.pb.go @@ -0,0 +1,376 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/applications/interchain_accounts/v1/account.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + types "github.com/cosmos/cosmos-sdk/x/auth/types" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// An InterchainAccount is defined as a BaseAccount & the address of the account owner on the controller chain +type InterchainAccount struct { + *types.BaseAccount `protobuf:"bytes,1,opt,name=base_account,json=baseAccount,proto3,embedded=base_account" json:"base_account,omitempty"` + AccountOwner string `protobuf:"bytes,2,opt,name=account_owner,json=accountOwner,proto3" json:"account_owner,omitempty"` +} + +func (m *InterchainAccount) Reset() { *m = InterchainAccount{} } +func (*InterchainAccount) ProtoMessage() {} +func (*InterchainAccount) Descriptor() ([]byte, []int) { + return fileDescriptor_5561bd92625bf7da, []int{0} +} +func (m *InterchainAccount) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *InterchainAccount) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_InterchainAccount.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *InterchainAccount) XXX_Merge(src proto.Message) { + xxx_messageInfo_InterchainAccount.Merge(m, src) +} +func (m *InterchainAccount) XXX_Size() int { + return m.Size() +} +func (m *InterchainAccount) XXX_DiscardUnknown() { + xxx_messageInfo_InterchainAccount.DiscardUnknown(m) +} + +var xxx_messageInfo_InterchainAccount proto.InternalMessageInfo + +func init() { + proto.RegisterType((*InterchainAccount)(nil), "ibc.applications.interchain_accounts.v1.InterchainAccount") +} + +func init() { + proto.RegisterFile("ibc/applications/interchain_accounts/v1/account.proto", fileDescriptor_5561bd92625bf7da) +} + +var fileDescriptor_5561bd92625bf7da = []byte{ + // 321 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x32, 0xcd, 0x4c, 0x4a, 0xd6, + 0x4f, 0x2c, 0x28, 0xc8, 0xc9, 0x4c, 0x4e, 0x2c, 0xc9, 0xcc, 0xcf, 0x2b, 0xd6, 0xcf, 0xcc, 0x2b, + 0x49, 0x2d, 0x4a, 0xce, 0x48, 0xcc, 0xcc, 0x8b, 0x4f, 0x4c, 0x4e, 0xce, 0x2f, 0xcd, 0x2b, 0x29, + 0xd6, 0x2f, 0x33, 0xd4, 0x87, 0xb2, 0xf5, 0x0a, 0x8a, 0xf2, 0x4b, 0xf2, 0x85, 0xd4, 0x33, 0x93, + 0x92, 0xf5, 0x90, 0xb5, 0xe9, 0x61, 0xd1, 0xa6, 0x57, 0x66, 0x28, 0x25, 0x99, 0x9c, 0x5f, 0x9c, + 0x9b, 0x5f, 0x1c, 0x0f, 0xd6, 0xa6, 0x0f, 0xe1, 0x40, 0xcc, 0x90, 0x12, 0x49, 0xcf, 0x4f, 0xcf, + 0x87, 0x88, 0x83, 0x58, 0x50, 0x51, 0x39, 0x88, 0x1a, 0xfd, 0xc4, 0xd2, 0x92, 0x0c, 0xfd, 0x32, + 0xc3, 0xa4, 0xd4, 0x92, 0x44, 0x43, 0x30, 0x07, 0x22, 0xaf, 0x74, 0x9a, 0x91, 0x4b, 0xd0, 0x13, + 0x6e, 0x97, 0x23, 0xc4, 0x2a, 0x21, 0x4f, 0x2e, 0x9e, 0xa4, 0xc4, 0xe2, 0x54, 0x98, 0xd5, 0x12, + 0x8c, 0x0a, 0x8c, 0x1a, 0xdc, 0x46, 0x0a, 0x7a, 0x50, 0x0b, 0xc1, 0xfa, 0xa1, 0x86, 0xe9, 0x39, + 0x25, 0x16, 0xa7, 0x42, 0xf5, 0x39, 0xb1, 0x5c, 0xb8, 0x27, 0xcf, 0x18, 0xc4, 0x9d, 0x84, 0x10, + 0x12, 0x52, 0xe6, 0xe2, 0x85, 0x9a, 0x12, 0x9f, 0x5f, 0x9e, 0x97, 0x5a, 0x24, 0xc1, 0xa4, 0xc0, + 0xa8, 0xc1, 0x19, 0xc4, 0x03, 0x15, 0xf4, 0x07, 0x89, 0x59, 0xb9, 0x75, 0x2c, 0x90, 0x67, 0x98, + 0xb1, 0x40, 0x9e, 0xe1, 0xd4, 0x16, 0x5d, 0x2b, 0x22, 0xc3, 0x42, 0x0f, 0xc3, 0xd9, 0x9e, 0x4e, + 0x09, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x84, 0xc7, + 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0xe5, 0x96, 0x9e, 0x59, 0x92, + 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0x0b, 0x0d, 0x36, 0xfd, 0xcc, 0xa4, 0x64, 0xdd, 0xf4, 0x7c, + 0xfd, 0x32, 0x43, 0x03, 0xfd, 0xdc, 0xfc, 0x94, 0xd2, 0x9c, 0xd4, 0x62, 0x50, 0xcc, 0x15, 0xeb, + 0x1b, 0x99, 0xeb, 0x22, 0x6c, 0xd4, 0x85, 0x47, 0x5a, 0x49, 0x65, 0x41, 0x6a, 0x71, 0x12, 0x1b, + 0x38, 0xd8, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x5e, 0xfb, 0x05, 0x4d, 0xe9, 0x01, 0x00, + 0x00, +} + +func (m *InterchainAccount) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *InterchainAccount) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *InterchainAccount) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.AccountOwner) > 0 { + i -= len(m.AccountOwner) + copy(dAtA[i:], m.AccountOwner) + i = encodeVarintAccount(dAtA, i, uint64(len(m.AccountOwner))) + i-- + dAtA[i] = 0x12 + } + if m.BaseAccount != nil { + { + size, err := m.BaseAccount.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintAccount(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintAccount(dAtA []byte, offset int, v uint64) int { + offset -= sovAccount(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *InterchainAccount) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.BaseAccount != nil { + l = m.BaseAccount.Size() + n += 1 + l + sovAccount(uint64(l)) + } + l = len(m.AccountOwner) + if l > 0 { + n += 1 + l + sovAccount(uint64(l)) + } + return n +} + +func sovAccount(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozAccount(x uint64) (n int) { + return sovAccount(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *InterchainAccount) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAccount + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: InterchainAccount: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: InterchainAccount: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BaseAccount", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAccount + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAccount + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthAccount + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.BaseAccount == nil { + m.BaseAccount = &types.BaseAccount{} + } + if err := m.BaseAccount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AccountOwner", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAccount + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAccount + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAccount + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AccountOwner = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipAccount(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAccount + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipAccount(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAccount + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAccount + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAccount + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthAccount + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupAccount + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthAccount + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthAccount = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowAccount = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupAccount = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/apps/27-interchain-accounts/types/account_test.go b/modules/apps/27-interchain-accounts/types/account_test.go new file mode 100644 index 0000000..ed95467 --- /dev/null +++ b/modules/apps/27-interchain-accounts/types/account_test.go @@ -0,0 +1,178 @@ +package types_test + +import ( + "encoding/json" + "fmt" + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +var ( + // TestOwnerAddress defines a reusable bech32 address for testing purposes + TestOwnerAddress = "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs" + + // TestPortID defines a reusable port identifier for testing purposes + TestPortID, _ = types.NewControllerPortID(TestOwnerAddress) +) + +type TypesTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain +} + +func (suite *TypesTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) +} + +func TestTypesTestSuite(t *testing.T) { + testifysuite.Run(t, new(TypesTestSuite)) +} + +func (suite *TypesTestSuite) TestGenerateAddress() { + addr := types.GenerateAddress(suite.chainA.GetContext(), "test-connection-id", "test-port-id") + accAddr, err := sdk.AccAddressFromBech32(addr.String()) + + suite.Require().NoError(err, "TestGenerateAddress failed") + suite.Require().NotEmpty(accAddr) +} + +func (suite *TypesTestSuite) TestValidateAccountAddress() { + testCases := []struct { + name string + address string + expError error + }{ + { + "success", + TestOwnerAddress, + nil, + }, + { + "success with single character", + "a", + nil, + }, + { + "empty string", + "", + types.ErrInvalidAccountAddress, + }, + { + "only spaces", + " ", + types.ErrInvalidAccountAddress, + }, + { + "address is too long", + ibctesting.GenerateString(uint(types.DefaultMaxAddrLength) + 1), + types.ErrInvalidAccountAddress, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := types.ValidateAccountAddress(tc.address) + + if tc.expError == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().ErrorIs(err, tc.expError, tc.name) + } + }) + } +} + +func (suite *TypesTestSuite) TestInterchainAccount() { + pubkey := secp256k1.GenPrivKey().PubKey() + addr := sdk.AccAddress(pubkey.Address()) + baseAcc := authtypes.NewBaseAccountWithAddress(addr) + interchainAcc := types.NewInterchainAccount(baseAcc, TestOwnerAddress) + + // should fail when trying to set the public key or sequence of an interchain account + err := interchainAcc.SetPubKey(pubkey) + suite.Require().Error(err) + err = interchainAcc.SetSequence(1) + suite.Require().Error(err) +} + +func (suite *TypesTestSuite) TestGenesisAccountValidate() { + pubkey := secp256k1.GenPrivKey().PubKey() + addr := sdk.AccAddress(pubkey.Address()) + baseAcc := authtypes.NewBaseAccountWithAddress(addr) + pubkey = secp256k1.GenPrivKey().PubKey() + ownerAddr := sdk.AccAddress(pubkey.Address()) + + testCases := []struct { + name string + acc authtypes.GenesisAccount + expErr error + }{ + { + "success", + types.NewInterchainAccount(baseAcc, ownerAddr.String()), + nil, + }, + { + "interchain account with empty AccountOwner field", + types.NewInterchainAccount(baseAcc, ""), + types.ErrInvalidAccountAddress, + }, + } + + for _, tc := range testCases { + + err := tc.acc.Validate() + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + } +} + +func (suite *TypesTestSuite) TestInterchainAccountMarshalYAML() { + addr := suite.chainA.SenderAccount.GetAddress() + baseAcc := authtypes.NewBaseAccountWithAddress(addr) + + interchainAcc := types.NewInterchainAccount(baseAcc, suite.chainB.SenderAccount.GetAddress().String()) + bz, err := interchainAcc.MarshalYAML() + suite.Require().NoError(err) + + expected := fmt.Sprintf("address: %s\npublic_key: \"\"\naccount_number: 0\nsequence: 0\naccount_owner: %s\n", suite.chainA.SenderAccount.GetAddress(), suite.chainB.SenderAccount.GetAddress()) + suite.Require().Equal(expected, string(bz)) +} + +func (suite *TypesTestSuite) TestInterchainAccountJSON() { + addr := suite.chainA.SenderAccount.GetAddress() + ba := authtypes.NewBaseAccountWithAddress(addr) + + interchainAcc := types.NewInterchainAccount(ba, suite.chainB.SenderAccount.GetAddress().String()) + + bz, err := json.Marshal(interchainAcc) + suite.Require().NoError(err) + + bz1, err := interchainAcc.MarshalJSON() + suite.Require().NoError(err) + suite.Require().Equal(string(bz), string(bz1)) + + var a types.InterchainAccount + suite.Require().NoError(json.Unmarshal(bz, &a)) + suite.Require().Equal(a.String(), interchainAcc.String()) +} diff --git a/modules/apps/27-interchain-accounts/types/codec.go b/modules/apps/27-interchain-accounts/types/codec.go new file mode 100644 index 0000000..8cfd97b --- /dev/null +++ b/modules/apps/27-interchain-accounts/types/codec.go @@ -0,0 +1,111 @@ +package types + +import ( + "github.com/cosmos/gogoproto/proto" + + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +// ModuleCdc references the global interchain accounts module codec. Note, the codec +// should ONLY be used in certain instances of tests and for JSON encoding. +// +// The actual codec used for serialization should be provided to interchain accounts and +// defined at the application level. +var ModuleCdc = codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) + +// RegisterInterfaces registers the interchain accounts controller types and the concrete InterchainAccount implementation +// against the associated x/auth AccountI and GenesisAccount interfaces. +func RegisterInterfaces(registry codectypes.InterfaceRegistry) { + registry.RegisterImplementations((*sdk.AccountI)(nil), &InterchainAccount{}) + registry.RegisterImplementations((*authtypes.GenesisAccount)(nil), &InterchainAccount{}) +} + +// SerializeCosmosTx serializes a slice of sdk.Msg's using the CosmosTx type. The sdk.Msg's are +// packed into Any's and inserted into the Messages field of a CosmosTx. The CosmosTx is marshaled +// depending on the encoding type passed in. The marshaled bytes are returned. Only the ProtoCodec +// is supported for serializing messages. Both protobuf and proto3 JSON are supported. +func SerializeCosmosTx(cdc codec.Codec, msgs []proto.Message, encoding string) ([]byte, error) { + // this is a defensive check to ensure only the ProtoCodec is used for message serialization + if _, ok := cdc.(*codec.ProtoCodec); !ok { + return nil, errorsmod.Wrap(ErrInvalidCodec, "only the ProtoCodec may be used for receiving messages on the host chain") + } + + var bz []byte + var err error + + msgAnys := make([]*codectypes.Any, len(msgs)) + + for i, msg := range msgs { + msgAnys[i], err = codectypes.NewAnyWithValue(msg) + if err != nil { + return nil, err + } + } + + cosmosTx := &CosmosTx{ + Messages: msgAnys, + } + + switch encoding { + case EncodingProtobuf: + bz, err = cdc.Marshal(cosmosTx) + if err != nil { + return nil, errorsmod.Wrapf(err, "cannot marshal CosmosTx with protobuf") + } + case EncodingProto3JSON: + bz, err = cdc.MarshalJSON(cosmosTx) + if err != nil { + return nil, errorsmod.Wrapf(ibcerrors.ErrInvalidType, "cannot marshal CosmosTx with proto3 json") + } + default: + return nil, errorsmod.Wrapf(ErrInvalidCodec, "unsupported encoding format %s", encoding) + } + + return bz, nil +} + +// DeserializeCosmosTx unmarshals and unpacks a slice of transaction bytes into a slice of sdk.Msg's. +// The transaction bytes are unmarshaled depending on the encoding type passed in. The sdk.Msg's are +// unpacked from Any's and returned. Only the ProtoCodec is supported for serializing messages. Both +// protobuf and proto3 JSON are supported. +func DeserializeCosmosTx(cdc codec.Codec, data []byte, encoding string) ([]sdk.Msg, error) { + // this is a defensive check to ensure only the ProtoCodec is used for message deserialization + if _, ok := cdc.(*codec.ProtoCodec); !ok { + return nil, errorsmod.Wrap(ErrInvalidCodec, "only the ProtoCodec may be used for receiving messages on the host chain") + } + + var cosmosTx CosmosTx + + switch encoding { + case EncodingProtobuf: + if err := cdc.Unmarshal(data, &cosmosTx); err != nil { + return nil, errorsmod.Wrapf(ibcerrors.ErrInvalidType, "cannot unmarshal CosmosTx with protobuf: %v", err) + } + case EncodingProto3JSON: + if err := cdc.UnmarshalJSON(data, &cosmosTx); err != nil { + return nil, errorsmod.Wrapf(ibcerrors.ErrInvalidType, "cannot unmarshal CosmosTx with proto3 json: %v", err) + } + default: + return nil, errorsmod.Wrapf(ErrInvalidCodec, "unsupported encoding format %s", encoding) + } + + msgs := make([]sdk.Msg, len(cosmosTx.Messages)) + + for i, protoAny := range cosmosTx.Messages { + var msg sdk.Msg + err := cdc.UnpackAny(protoAny, &msg) + if err != nil { + return nil, err + } + msgs[i] = msg + } + + return msgs, nil +} diff --git a/modules/apps/27-interchain-accounts/types/codec_test.go b/modules/apps/27-interchain-accounts/types/codec_test.go new file mode 100644 index 0000000..7213a6e --- /dev/null +++ b/modules/apps/27-interchain-accounts/types/codec_test.go @@ -0,0 +1,470 @@ +package types_test + +import ( + "github.com/cosmos/gogoproto/proto" + + sdkmath "cosmossdk.io/math" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + govtypesv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +// mockSdkMsg defines a mock struct, used for testing codec error scenarios +type mockSdkMsg struct{} + +// Reset implements sdk.Msg +func (mockSdkMsg) Reset() { +} + +// String implements sdk.Msg +func (mockSdkMsg) String() string { + return "" +} + +// ProtoMessage implements sdk.Msg +func (mockSdkMsg) ProtoMessage() { +} + +// ValidateBasic implements sdk.Msg +func (mockSdkMsg) ValidateBasic() error { + return nil +} + +// TestSerializeAndDeserializeCosmosTx tests the SerializeCosmosTx and DeserializeCosmosTx functions +// for all supported encoding types. +// +// expPass set to false means that: +// - the test case is expected to fail on deserialization for protobuf encoding. +// - the test case is expected to fail on serialization for proto3 json encoding. +func (suite *TypesTestSuite) TestSerializeAndDeserializeCosmosTx() { + testedEncodings := []string{types.EncodingProtobuf, types.EncodingProto3JSON} + // each test case will have a corresponding expected errors in case of failures: + expSerializeErrorStrings := make([]string, len(testedEncodings)) + expDeserializeErrorStrings := make([]string, len(testedEncodings)) + + var msgs []proto.Message + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "single msg", + func() { + msgs = []proto.Message{ + &banktypes.MsgSend{ + FromAddress: TestOwnerAddress, + ToAddress: TestOwnerAddress, + Amount: sdk.NewCoins(sdk.NewCoin("bananas", sdkmath.NewInt(100))), + }, + } + }, + nil, + }, + { + "multiple msgs, same types", + func() { + msgs = []proto.Message{ + &banktypes.MsgSend{ + FromAddress: TestOwnerAddress, + ToAddress: TestOwnerAddress, + Amount: sdk.NewCoins(sdk.NewCoin("bananas", sdkmath.NewInt(100))), + }, + &banktypes.MsgSend{ + FromAddress: TestOwnerAddress, + ToAddress: TestOwnerAddress, + Amount: sdk.NewCoins(sdk.NewCoin("bananas", sdkmath.NewInt(200))), + }, + } + }, + nil, + }, + { + "success: multiple msgs, different types", + func() { + msgs = []proto.Message{ + &banktypes.MsgSend{ + FromAddress: TestOwnerAddress, + ToAddress: TestOwnerAddress, + Amount: sdk.NewCoins(sdk.NewCoin("bananas", sdkmath.NewInt(100))), + }, + &stakingtypes.MsgDelegate{ + DelegatorAddress: TestOwnerAddress, + ValidatorAddress: TestOwnerAddress, + Amount: sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(5000)), + }, + } + }, + nil, + }, + { + "success: msg with nested any", + func() { + testProposal := &govtypes.TextProposal{ + Title: "IBC Gov Proposal", + Description: "tokens for all!", + } + content, err := codectypes.NewAnyWithValue(testProposal) + suite.Require().NoError(err) + + msgs = []proto.Message{ + &govtypes.MsgSubmitProposal{ + Content: content, + InitialDeposit: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(5000))), + Proposer: TestOwnerAddress, + }, + } + }, + nil, + }, + { + "success: msg with nested array of any", + func() { + sendMsg := &banktypes.MsgSend{ + FromAddress: TestOwnerAddress, + ToAddress: TestOwnerAddress, + Amount: sdk.NewCoins(sdk.NewCoin("bananas", sdkmath.NewInt(100))), + } + sendAny, err := codectypes.NewAnyWithValue(sendMsg) + suite.Require().NoError(err) + + testProposal := &govtypes.TextProposal{ + Title: "IBC Gov Proposal", + Description: "tokens for all!", + } + content, err := codectypes.NewAnyWithValue(testProposal) + suite.Require().NoError(err) + legacyPropMsg := &govtypes.MsgSubmitProposal{ + Content: content, + InitialDeposit: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(5000))), + Proposer: TestOwnerAddress, + } + legacyPropAny, err := codectypes.NewAnyWithValue(legacyPropMsg) + suite.Require().NoError(err) + + delegateMsg := &stakingtypes.MsgDelegate{ + DelegatorAddress: TestOwnerAddress, + ValidatorAddress: TestOwnerAddress, + Amount: sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(5000)), + } + delegateAny, err := codectypes.NewAnyWithValue(delegateMsg) + suite.Require().NoError(err) + + messages := []*codectypes.Any{sendAny, legacyPropAny, delegateAny} + + propMsg := &govtypesv1.MsgSubmitProposal{ + Messages: messages, + InitialDeposit: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(5000))), + Proposer: TestOwnerAddress, + Metadata: "", + Title: "New IBC Gov Proposal", + Summary: "more tokens for all!", + } + + msgs = []proto.Message{propMsg} + }, + nil, + }, + { + "success: empty messages", + func() { + msgs = []proto.Message{} + }, + nil, + }, + { + "failure: unregistered msg type", + func() { + msgs = []proto.Message{ + &mockSdkMsg{}, + } + + expSerializeErrorStrings = []string{"NO_ERROR_EXPECTED", "cannot marshal CosmosTx with proto3 json"} + expDeserializeErrorStrings = []string{"cannot unmarshal CosmosTx with protobuf", "cannot unmarshal CosmosTx with proto3 json"} + }, + ibcerrors.ErrInvalidType, + }, + { + "failure: multiple unregistered msg types", + func() { + msgs = []proto.Message{ + &mockSdkMsg{}, + &mockSdkMsg{}, + &mockSdkMsg{}, + } + + expSerializeErrorStrings = []string{"NO_ERROR_EXPECTED", "cannot marshal CosmosTx with proto3 json"} + expDeserializeErrorStrings = []string{"cannot unmarshal CosmosTx with protobuf", "cannot unmarshal CosmosTx with proto3 json"} + }, + ibcerrors.ErrInvalidType, + }, + { + "failure: nested unregistered msg", + func() { + mockMsg := &mockSdkMsg{} + mockAny, err := codectypes.NewAnyWithValue(mockMsg) + suite.Require().NoError(err) + + msgs = []proto.Message{ + &govtypes.MsgSubmitProposal{ + Content: mockAny, + InitialDeposit: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(5000))), + Proposer: TestOwnerAddress, + }, + } + + expSerializeErrorStrings = []string{"NO_ERROR_EXPECTED", "cannot marshal CosmosTx with proto3 json"} + expDeserializeErrorStrings = []string{"cannot unmarshal CosmosTx with protobuf", "cannot unmarshal CosmosTx with proto3 json"} + }, + ibcerrors.ErrInvalidType, + }, + { + "failure: nested array of unregistered msg", + func() { + mockMsg := &mockSdkMsg{} + mockAny, err := codectypes.NewAnyWithValue(mockMsg) + suite.Require().NoError(err) + + messages := []*codectypes.Any{mockAny, mockAny, mockAny} + + propMsg := &govtypesv1.MsgSubmitProposal{ + Messages: messages, + InitialDeposit: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(5000))), + Proposer: TestOwnerAddress, + Metadata: "", + Title: "New IBC Gov Proposal", + Summary: "more tokens for all!", + } + + msgs = []proto.Message{propMsg} + + expSerializeErrorStrings = []string{"NO_ERROR_EXPECTED", "cannot marshal CosmosTx with proto3 json"} + expDeserializeErrorStrings = []string{"cannot unmarshal CosmosTx with protobuf", "cannot unmarshal CosmosTx with proto3 json"} + }, + ibcerrors.ErrInvalidType, + }, + } + + for i, encoding := range testedEncodings { + for _, tc := range testCases { + suite.Run(tc.name, func() { + tc.malleate() + + expPass := tc.expErr == nil + bz, err := types.SerializeCosmosTx(suite.chainA.Codec, msgs, encoding) + if encoding == types.EncodingProto3JSON && !expPass { + suite.Require().Error(err, tc.name) + suite.Require().Contains(err.Error(), expSerializeErrorStrings[1], tc.name) + } else { + suite.Require().NoError(err, tc.name) + } + + deserializedMsgs, err := types.DeserializeCosmosTx(suite.chainA.Codec, bz, encoding) + if expPass { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().Error(err, tc.name) + suite.Require().Contains(err.Error(), expDeserializeErrorStrings[i], tc.name) + suite.Require().ErrorIs(err, tc.expErr) + } + + if expPass { + for i, msg := range msgs { + // We're using proto.CompactTextString() for comparison instead of suite.Require().Equal() or proto.Equal() + // for two main reasons: + // + // 1. When deserializing from JSON, the `Any` type has private fields and cached values + // that do not match the original message, causing equality checks to fail. + // + // 2. proto.Equal() does not have built-in support for comparing sdk's math.Int types. + // + // Using proto.CompactTextString() mitigates these issues by focusing on serialized string representation, + // rather than internal details of the types. + suite.Require().Equal(proto.CompactTextString(msg), proto.CompactTextString(deserializedMsgs[i])) + } + } + }) + } + + // test serializing non sdk.Msg type + bz, err := types.SerializeCosmosTx(suite.chainA.Codec, []proto.Message{&banktypes.MsgSendResponse{}}, encoding) + suite.Require().NoError(err) + suite.Require().NotEmpty(bz) + + // test deserializing unknown bytes + msgs, err := types.DeserializeCosmosTx(suite.chainA.Codec, bz, encoding) + suite.Require().Error(err) // unregistered type + suite.Require().Contains(err.Error(), expDeserializeErrorStrings[i]) + suite.Require().Empty(msgs) + + // test deserializing unknown bytes + msgs, err = types.DeserializeCosmosTx(suite.chainA.Codec, []byte("invalid"), encoding) + suite.Require().Error(err) + suite.Require().Contains(err.Error(), expDeserializeErrorStrings[i]) + suite.Require().Empty(msgs) + } +} + +func (suite *TypesTestSuite) TestJSONDeserializeCosmosTx() { + testCases := []struct { + name string + jsonBytes []byte + expMsgs []proto.Message + expError error + }{ + { + "success: single msg", + []byte(`{ + "messages": [ + { + "@type": "/cosmos.bank.v1beta1.MsgSend", + "from_address": "` + TestOwnerAddress + `", + "to_address": "` + TestOwnerAddress + `", + "amount": [{ "denom": "bananas", "amount": "100" }] + } + ] + }`), + []proto.Message{ + &banktypes.MsgSend{ + FromAddress: TestOwnerAddress, + ToAddress: TestOwnerAddress, + Amount: sdk.NewCoins(sdk.NewCoin("bananas", sdkmath.NewInt(100))), + }, + }, + nil, + }, + { + "success: multiple msgs, same types", + []byte(`{ + "messages": [ + { + "@type": "/cosmos.bank.v1beta1.MsgSend", + "from_address": "` + TestOwnerAddress + `", + "to_address": "` + TestOwnerAddress + `", + "amount": [{ "denom": "bananas", "amount": "100" }] + }, + { + "@type": "/cosmos.bank.v1beta1.MsgSend", + "from_address": "` + TestOwnerAddress + `", + "to_address": "` + TestOwnerAddress + `", + "amount": [{ "denom": "bananas", "amount": "100" }] + } + ] + }`), + []proto.Message{ + &banktypes.MsgSend{ + FromAddress: TestOwnerAddress, + ToAddress: TestOwnerAddress, + Amount: sdk.NewCoins(sdk.NewCoin("bananas", sdkmath.NewInt(100))), + }, + &banktypes.MsgSend{ + FromAddress: TestOwnerAddress, + ToAddress: TestOwnerAddress, + Amount: sdk.NewCoins(sdk.NewCoin("bananas", sdkmath.NewInt(100))), + }, + }, + nil, + }, + { + "success: multiple msgs, different types", + []byte(`{ + "messages": [ + { + "@type": "/cosmos.bank.v1beta1.MsgSend", + "from_address": "` + TestOwnerAddress + `", + "to_address": "` + TestOwnerAddress + `", + "amount": [{ "denom": "bananas", "amount": "100" }] + }, + { + "@type": "/cosmos.staking.v1beta1.MsgDelegate", + "delegator_address": "` + TestOwnerAddress + `", + "validator_address": "` + TestOwnerAddress + `", + "amount": { "denom": "stake", "amount": "5000" } + } + ] + }`), + []proto.Message{ + &banktypes.MsgSend{ + FromAddress: TestOwnerAddress, + ToAddress: TestOwnerAddress, + Amount: sdk.NewCoins(sdk.NewCoin("bananas", sdkmath.NewInt(100))), + }, + &stakingtypes.MsgDelegate{ + DelegatorAddress: TestOwnerAddress, + ValidatorAddress: TestOwnerAddress, + Amount: sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(5000)), + }, + }, + nil, + }, + { + "failure: unregistered msg type", + []byte(`{"messages":[{}]}`), + []proto.Message{ + &mockSdkMsg{}, + }, + ibcerrors.ErrInvalidType, + }, + { + "failure: multiple unregistered msg types", + []byte(`{"messages":[{},{},{}]}`), + []proto.Message{ + &mockSdkMsg{}, + &mockSdkMsg{}, + &mockSdkMsg{}, + }, + ibcerrors.ErrInvalidType, + }, + { + "failure: empty bytes", + []byte{}, + nil, + ibcerrors.ErrInvalidType, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + msgs, errDeserialize := types.DeserializeCosmosTx(suite.chainA.Codec, tc.jsonBytes, types.EncodingProto3JSON) + if tc.expError == nil { + suite.Require().NoError(errDeserialize, tc.name) + for i, msg := range msgs { + suite.Require().Equal(tc.expMsgs[i], msg) + } + } else { + suite.Require().ErrorIs(errDeserialize, tc.expError, tc.name) + } + }) + } +} + +func (suite *TypesTestSuite) TestUnsupportedEncodingType() { + msgs := []proto.Message{ + &banktypes.MsgSend{ + FromAddress: TestOwnerAddress, + ToAddress: TestOwnerAddress, + Amount: sdk.NewCoins(sdk.NewCoin("bananas", sdkmath.NewInt(100))), + }, + } + + bz, err := types.SerializeCosmosTx(suite.chainA.Codec, msgs, "unsupported") + suite.Require().ErrorIs(err, types.ErrInvalidCodec) + suite.Require().Nil(bz) + + data, err := types.SerializeCosmosTx(suite.chainA.Codec, msgs, types.EncodingProtobuf) + suite.Require().NoError(err) + + _, err = types.DeserializeCosmosTx(suite.chainA.Codec, data, "unsupported") + suite.Require().ErrorIs(err, types.ErrInvalidCodec) + + // verify that protobuf encoding still works otherwise: + _, err = types.DeserializeCosmosTx(suite.chainA.Codec, data, types.EncodingProtobuf) + suite.Require().NoError(err) +} diff --git a/modules/apps/27-interchain-accounts/types/errors.go b/modules/apps/27-interchain-accounts/types/errors.go new file mode 100644 index 0000000..e2e248a --- /dev/null +++ b/modules/apps/27-interchain-accounts/types/errors.go @@ -0,0 +1,26 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" +) + +var ( + ErrUnknownDataType = errorsmod.Register(ModuleName, 2, "unknown data type") + ErrAccountAlreadyExist = errorsmod.Register(ModuleName, 3, "account already exist") + ErrPortAlreadyBound = errorsmod.Register(ModuleName, 4, "port is already bound") + ErrInvalidChannelFlow = errorsmod.Register(ModuleName, 5, "invalid message sent to channel end") + ErrInvalidOutgoingData = errorsmod.Register(ModuleName, 6, "invalid outgoing data") + ErrInvalidRoute = errorsmod.Register(ModuleName, 7, "invalid route") + ErrInterchainAccountNotFound = errorsmod.Register(ModuleName, 8, "interchain account not found") + ErrInterchainAccountAlreadySet = errorsmod.Register(ModuleName, 9, "interchain account is already set") + ErrActiveChannelAlreadySet = errorsmod.Register(ModuleName, 10, "active channel already set for this owner") + ErrActiveChannelNotFound = errorsmod.Register(ModuleName, 11, "no active channel for this owner") + ErrInvalidVersion = errorsmod.Register(ModuleName, 12, "invalid interchain accounts version") + ErrInvalidAccountAddress = errorsmod.Register(ModuleName, 13, "invalid account address") + ErrUnsupported = errorsmod.Register(ModuleName, 14, "interchain account does not support this action") + ErrInvalidControllerPort = errorsmod.Register(ModuleName, 15, "invalid controller port") + ErrInvalidHostPort = errorsmod.Register(ModuleName, 16, "invalid host port") + ErrInvalidTimeoutTimestamp = errorsmod.Register(ModuleName, 17, "timeout timestamp must be in the future") + ErrInvalidCodec = errorsmod.Register(ModuleName, 18, "codec is not supported") + ErrInvalidAccountReopening = errorsmod.Register(ModuleName, 19, "invalid account reopening") +) diff --git a/modules/apps/27-interchain-accounts/types/events.go b/modules/apps/27-interchain-accounts/types/events.go new file mode 100644 index 0000000..cb1a9ef --- /dev/null +++ b/modules/apps/27-interchain-accounts/types/events.go @@ -0,0 +1,11 @@ +package types + +// ICS27 Interchain Accounts events +const ( + EventTypePacket = "ics27_packet" + + AttributeKeyAckError = "error" + AttributeKeyHostChannelID = "host_channel_id" + AttributeKeyControllerChannelID = "controller_channel_id" + AttributeKeyAckSuccess = "success" +) diff --git a/modules/apps/27-interchain-accounts/types/expected_keepers.go b/modules/apps/27-interchain-accounts/types/expected_keepers.go new file mode 100644 index 0000000..ee8553b --- /dev/null +++ b/modules/apps/27-interchain-accounts/types/expected_keepers.go @@ -0,0 +1,34 @@ +package types + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" +) + +// AccountKeeper defines the expected account keeper +type AccountKeeper interface { + NewAccount(ctx context.Context, acc sdk.AccountI) sdk.AccountI + GetAccount(ctx context.Context, addr sdk.AccAddress) sdk.AccountI + SetAccount(ctx context.Context, acc sdk.AccountI) + GetModuleAccount(ctx context.Context, name string) sdk.ModuleAccountI + GetModuleAddress(name string) sdk.AccAddress +} + +// ChannelKeeper defines the expected IBC channel keeper +type ChannelKeeper interface { + GetChannel(ctx sdk.Context, srcPort, srcChan string) (channel channeltypes.Channel, found bool) + GetNextSequenceSend(ctx sdk.Context, portID, channelID string) (uint64, bool) + GetConnection(ctx sdk.Context, connectionID string) (connectiontypes.ConnectionEnd, error) + GetAllChannelsWithPortPrefix(ctx sdk.Context, portPrefix string) []channeltypes.IdentifiedChannel +} + +// ParamSubspace defines the expected Subspace interface for module parameters. +type ParamSubspace interface { + GetParamSet(ctx sdk.Context, ps paramtypes.ParamSet) + GetParamSetIfExists(ctx sdk.Context, ps paramtypes.ParamSet) +} diff --git a/modules/apps/27-interchain-accounts/types/keys.go b/modules/apps/27-interchain-accounts/types/keys.go new file mode 100644 index 0000000..0f85fa7 --- /dev/null +++ b/modules/apps/27-interchain-accounts/types/keys.go @@ -0,0 +1,68 @@ +package types + +import ( + "fmt" +) + +const ( + // ModuleName defines the interchain accounts module name + ModuleName = "interchainaccounts" + + // HostPortID is the default port id that the interchain accounts host submodule binds to + HostPortID = "icahost" + + // ControllerPortPrefix is the default port prefix that the interchain accounts controller submodule binds to + ControllerPortPrefix = "icacontroller-" + + // Version defines the current version for interchain accounts + Version = "ics27-1" + + // RouterKey is the message route for interchain accounts + RouterKey = ModuleName + + // QuerierRoute is the querier route for interchain accounts + QuerierRoute = ModuleName + + // hostAccountKey is the key used when generating a module address for the host submodule + hostAccountsKey = "icahost-accounts" +) + +var ( + // ActiveChannelKeyPrefix defines the key prefix used to store active channels + ActiveChannelKeyPrefix = "activeChannel" + + // OwnerKeyPrefix defines the key prefix used to store interchain accounts + OwnerKeyPrefix = "owner" + + // PortKeyPrefix defines the key prefix used to store ports + PortKeyPrefix = "port" + + // IsMiddlewareEnabledPrefix defines the key prefix used to store a flag for legacy API callback routing via ibc middleware + IsMiddlewareEnabledPrefix = "isMiddlewareEnabled" + + // MiddlewareEnabled is the value used to signal that controller middleware is enabled + MiddlewareEnabled = []byte{0x01} + + // MiddlewareDisabled is the value used to signal that controller midleware is disabled + MiddlewareDisabled = []byte{0x02} +) + +// KeyActiveChannel creates and returns a new key used for active channels store operations +func KeyActiveChannel(portID, connectionID string) []byte { + return fmt.Appendf(nil, "%s/%s/%s", ActiveChannelKeyPrefix, portID, connectionID) +} + +// KeyOwnerAccount creates and returns a new key used for interchain account store operations +func KeyOwnerAccount(portID, connectionID string) []byte { + return fmt.Appendf(nil, "%s/%s/%s", OwnerKeyPrefix, portID, connectionID) +} + +// KeyPort creates and returns a new key used for port store operations +func KeyPort(portID string) []byte { + return fmt.Appendf(nil, "%s/%s", PortKeyPrefix, portID) +} + +// KeyIsMiddlewareEnabled creates and returns a new key used for signaling legacy API callback routing via ibc middleware +func KeyIsMiddlewareEnabled(portID, connectionID string) []byte { + return fmt.Appendf(nil, "%s/%s/%s", IsMiddlewareEnabledPrefix, portID, connectionID) +} diff --git a/modules/apps/27-interchain-accounts/types/keys_test.go b/modules/apps/27-interchain-accounts/types/keys_test.go new file mode 100644 index 0000000..20cd848 --- /dev/null +++ b/modules/apps/27-interchain-accounts/types/keys_test.go @@ -0,0 +1,23 @@ +package types_test + +import ( + "fmt" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *TypesTestSuite) TestKeyActiveChannel() { + key := types.KeyActiveChannel("port-id", "connection-id") + suite.Require().Equal("activeChannel/port-id/connection-id", string(key)) +} + +func (suite *TypesTestSuite) TestKeyOwnerAccount() { + key := types.KeyOwnerAccount("port-id", "connection-id") + suite.Require().Equal("owner/port-id/connection-id", string(key)) +} + +func (suite *TypesTestSuite) TestKeyIsMiddlewareEnabled() { + key := types.KeyIsMiddlewareEnabled(ibctesting.MockPort, ibctesting.FirstChannelID) + suite.Require().Equal(fmt.Sprintf("%s/%s/%s", types.IsMiddlewareEnabledPrefix, ibctesting.MockPort, ibctesting.FirstChannelID), string(key)) +} diff --git a/modules/apps/27-interchain-accounts/types/metadata.go b/modules/apps/27-interchain-accounts/types/metadata.go new file mode 100644 index 0000000..67c4b81 --- /dev/null +++ b/modules/apps/27-interchain-accounts/types/metadata.go @@ -0,0 +1,182 @@ +package types + +import ( + "slices" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +const ( + // EncodingProtobuf defines the protocol buffers proto3 encoding format + EncodingProtobuf = "proto3" + // EncodingProto3JSON defines the proto3 JSON encoding format + EncodingProto3JSON = "proto3json" + + // TxTypeSDKMultiMsg defines the multi message transaction type supported by the Cosmos SDK + TxTypeSDKMultiMsg = "sdk_multi_msg" +) + +// NewMetadata creates and returns a new ICS27 Metadata instance +func NewMetadata(version, controllerConnectionID, hostConnectionID, accAddress, encoding, txType string) Metadata { + return Metadata{ + Version: version, + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Address: accAddress, + Encoding: encoding, + TxType: txType, + } +} + +// NewDefaultMetadata creates and returns a new ICS27 Metadata instance containing the default ICS27 Metadata values +// with the provided controller and host connection identifiers. The host connection identifier may be an empty string. +func NewDefaultMetadata(controllerConnectionID, hostConnectionID string) Metadata { + metadata := Metadata{ + ControllerConnectionId: controllerConnectionID, + HostConnectionId: hostConnectionID, + Encoding: EncodingProtobuf, + TxType: TxTypeSDKMultiMsg, + Version: Version, + } + + return metadata +} + +// NewDefaultMetadataString creates and returns a new JSON encoded version string containing the default ICS27 Metadata values +// with the provided controller and host connection identifiers. The host connection identifier may be an empty string. +func NewDefaultMetadataString(controllerConnectionID, hostConnectionID string) string { + metadata := NewDefaultMetadata(controllerConnectionID, hostConnectionID) + + return string(ModuleCdc.MustMarshalJSON(&metadata)) +} + +// MetadataFromVersion parses Metadata from a json encoded version string. +func MetadataFromVersion(versionString string) (Metadata, error) { + var metadata Metadata + if err := ModuleCdc.UnmarshalJSON([]byte(versionString), &metadata); err != nil { + return Metadata{}, errorsmod.Wrapf(ibcerrors.ErrInvalidType, "cannot unmarshal ICS-27 interchain accounts metadata") + } + return metadata, nil +} + +// IsPreviousMetadataEqual compares a metadata to a previous version string set in a channel struct. +// It ensures all fields are equal except the Address string +func IsPreviousMetadataEqual(previousVersion string, metadata Metadata) bool { + previousMetadata, err := MetadataFromVersion(previousVersion) + if err != nil { + return false + } + + return (previousMetadata.Version == metadata.Version && + previousMetadata.ControllerConnectionId == metadata.ControllerConnectionId && + previousMetadata.HostConnectionId == metadata.HostConnectionId && + previousMetadata.Encoding == metadata.Encoding && + previousMetadata.TxType == metadata.TxType) +} + +// ValidateControllerMetadata performs validation of the provided ICS27 controller metadata parameters as well +// as the connection params against the provided metadata +func ValidateControllerMetadata(ctx sdk.Context, channelKeeper ChannelKeeper, connectionHops []string, + metadata Metadata, +) error { + if !isSupportedEncoding(metadata.Encoding) { + return errorsmod.Wrapf(ErrInvalidCodec, "unsupported encoding format %s", metadata.Encoding) + } + + if !isSupportedTxType(metadata.TxType) { + return errorsmod.Wrapf(ErrUnknownDataType, "unsupported transaction type %s", metadata.TxType) + } + + connection, err := channelKeeper.GetConnection(ctx, connectionHops[0]) + if err != nil { + return err + } + + if err := validateConnectionParams(metadata, connectionHops[0], connection.Counterparty.ConnectionId); err != nil { + return err + } + + if metadata.Address != "" { + if err := ValidateAccountAddress(metadata.Address); err != nil { + return err + } + } + + if metadata.Version != Version { + return errorsmod.Wrapf(ErrInvalidVersion, "expected %s, got %s", Version, metadata.Version) + } + + return nil +} + +// ValidateHostMetadata performs validation of the provided ICS27 host metadata parameters +func ValidateHostMetadata(ctx sdk.Context, channelKeeper ChannelKeeper, connectionHops []string, + metadata Metadata, +) error { + if !isSupportedEncoding(metadata.Encoding) { + return errorsmod.Wrapf(ErrInvalidCodec, "unsupported encoding format %s", metadata.Encoding) + } + + if !isSupportedTxType(metadata.TxType) { + return errorsmod.Wrapf(ErrUnknownDataType, "unsupported transaction type %s", metadata.TxType) + } + + connection, err := channelKeeper.GetConnection(ctx, connectionHops[0]) + if err != nil { + return err + } + + if err := validateConnectionParams(metadata, connection.Counterparty.ConnectionId, connectionHops[0]); err != nil { + return err + } + + if metadata.Address != "" { + if err := ValidateAccountAddress(metadata.Address); err != nil { + return err + } + } + + if metadata.Version != Version { + return errorsmod.Wrapf(ErrInvalidVersion, "expected %s, got %s", Version, metadata.Version) + } + + return nil +} + +// isSupportedEncoding returns true if the provided encoding is supported, otherwise false +func isSupportedEncoding(encoding string) bool { + return slices.Contains(getSupportedEncoding(), encoding) +} + +// getSupportedEncoding returns a string slice of supported encoding formats +func getSupportedEncoding() []string { + return []string{EncodingProtobuf, EncodingProto3JSON} +} + +// isSupportedTxType returns true if the provided transaction type is supported, otherwise false +func isSupportedTxType(txType string) bool { + return slices.Contains(getSupportedTxTypes(), txType) +} + +// getSupportedTxTypes returns a string slice of supported transaction types +func getSupportedTxTypes() []string { + return []string{TxTypeSDKMultiMsg} +} + +// validateConnectionParams compares the given the controller and host connection IDs to those set in the provided ICS27 Metadata +func validateConnectionParams(metadata Metadata, controllerConnectionID, hostConnectionID string) error { + if metadata.ControllerConnectionId != controllerConnectionID { + return errorsmod.Wrapf(connectiontypes.ErrInvalidConnection, "expected %s, got %s", controllerConnectionID, metadata.ControllerConnectionId) + } + + if metadata.HostConnectionId != hostConnectionID { + return errorsmod.Wrapf(connectiontypes.ErrInvalidConnection, "expected %s, got %s", hostConnectionID, metadata.HostConnectionId) + } + + return nil +} diff --git a/modules/apps/27-interchain-accounts/types/metadata.pb.go b/modules/apps/27-interchain-accounts/types/metadata.pb.go new file mode 100644 index 0000000..beec707 --- /dev/null +++ b/modules/apps/27-interchain-accounts/types/metadata.pb.go @@ -0,0 +1,590 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/applications/interchain_accounts/v1/metadata.proto + +package types + +import ( + fmt "fmt" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Metadata defines a set of protocol specific data encoded into the ICS27 channel version bytestring +// See ICS004: https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#Versioning +type Metadata struct { + // version defines the ICS27 protocol version + Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` + // controller_connection_id is the connection identifier associated with the controller chain + ControllerConnectionId string `protobuf:"bytes,2,opt,name=controller_connection_id,json=controllerConnectionId,proto3" json:"controller_connection_id,omitempty"` + // host_connection_id is the connection identifier associated with the host chain + HostConnectionId string `protobuf:"bytes,3,opt,name=host_connection_id,json=hostConnectionId,proto3" json:"host_connection_id,omitempty"` + // address defines the interchain account address to be fulfilled upon the OnChanOpenTry handshake step + // NOTE: the address field is empty on the OnChanOpenInit handshake step + Address string `protobuf:"bytes,4,opt,name=address,proto3" json:"address,omitempty"` + // encoding defines the supported codec format + Encoding string `protobuf:"bytes,5,opt,name=encoding,proto3" json:"encoding,omitempty"` + // tx_type defines the type of transactions the interchain account can execute + TxType string `protobuf:"bytes,6,opt,name=tx_type,json=txType,proto3" json:"tx_type,omitempty"` +} + +func (m *Metadata) Reset() { *m = Metadata{} } +func (m *Metadata) String() string { return proto.CompactTextString(m) } +func (*Metadata) ProtoMessage() {} +func (*Metadata) Descriptor() ([]byte, []int) { + return fileDescriptor_c29c32e397d1f21e, []int{0} +} +func (m *Metadata) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Metadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Metadata.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Metadata) XXX_Merge(src proto.Message) { + xxx_messageInfo_Metadata.Merge(m, src) +} +func (m *Metadata) XXX_Size() int { + return m.Size() +} +func (m *Metadata) XXX_DiscardUnknown() { + xxx_messageInfo_Metadata.DiscardUnknown(m) +} + +var xxx_messageInfo_Metadata proto.InternalMessageInfo + +func (m *Metadata) GetVersion() string { + if m != nil { + return m.Version + } + return "" +} + +func (m *Metadata) GetControllerConnectionId() string { + if m != nil { + return m.ControllerConnectionId + } + return "" +} + +func (m *Metadata) GetHostConnectionId() string { + if m != nil { + return m.HostConnectionId + } + return "" +} + +func (m *Metadata) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +func (m *Metadata) GetEncoding() string { + if m != nil { + return m.Encoding + } + return "" +} + +func (m *Metadata) GetTxType() string { + if m != nil { + return m.TxType + } + return "" +} + +func init() { + proto.RegisterType((*Metadata)(nil), "ibc.applications.interchain_accounts.v1.Metadata") +} + +func init() { + proto.RegisterFile("ibc/applications/interchain_accounts/v1/metadata.proto", fileDescriptor_c29c32e397d1f21e) +} + +var fileDescriptor_c29c32e397d1f21e = []byte{ + // 306 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x90, 0xb1, 0x4e, 0xf3, 0x30, + 0x14, 0x85, 0x9b, 0xff, 0x87, 0xb6, 0x78, 0x42, 0x1e, 0xc0, 0x62, 0x88, 0x10, 0x0b, 0x0c, 0x34, + 0xa6, 0x20, 0x01, 0x33, 0x48, 0x48, 0x0c, 0x2c, 0x88, 0x89, 0x25, 0x38, 0xd7, 0x56, 0x6b, 0x29, + 0xf1, 0x8d, 0xec, 0xdb, 0xa8, 0x7d, 0x0b, 0x1e, 0x8b, 0xb1, 0x23, 0x12, 0x0b, 0x6a, 0x5f, 0x04, + 0x25, 0x94, 0x14, 0x10, 0xe3, 0xd1, 0x77, 0x3e, 0xfb, 0xea, 0xb0, 0x73, 0x9b, 0x81, 0x54, 0x65, + 0x99, 0x5b, 0x50, 0x64, 0xd1, 0x05, 0x69, 0x1d, 0x19, 0x0f, 0x63, 0x65, 0x5d, 0xaa, 0x00, 0x70, + 0xe2, 0x28, 0xc8, 0x6a, 0x28, 0x0b, 0x43, 0x4a, 0x2b, 0x52, 0x49, 0xe9, 0x91, 0x90, 0x1f, 0xda, + 0x0c, 0x92, 0xef, 0x5e, 0xf2, 0x87, 0x97, 0x54, 0xc3, 0x83, 0xb7, 0x88, 0xf5, 0xef, 0x56, 0x2e, + 0x17, 0xac, 0x57, 0x19, 0x1f, 0x2c, 0x3a, 0x11, 0xed, 0x47, 0x47, 0x5b, 0xf7, 0x5f, 0x91, 0x5f, + 0x32, 0x01, 0xe8, 0xc8, 0x63, 0x9e, 0x1b, 0x9f, 0x02, 0x3a, 0x67, 0xa0, 0x7e, 0x37, 0xb5, 0x5a, + 0xfc, 0x6b, 0xaa, 0x3b, 0x6b, 0x7e, 0xdd, 0xe2, 0x5b, 0xcd, 0x8f, 0x19, 0x1f, 0x63, 0xa0, 0x5f, + 0xce, 0xff, 0xc6, 0xd9, 0xae, 0xc9, 0x8f, 0xb6, 0x60, 0x3d, 0xa5, 0xb5, 0x37, 0x21, 0x88, 0x8d, + 0xcf, 0x0b, 0x56, 0x91, 0xef, 0xb1, 0xbe, 0x71, 0x80, 0xda, 0xba, 0x91, 0xd8, 0x6c, 0x50, 0x9b, + 0xf9, 0x2e, 0xeb, 0xd1, 0x34, 0xa5, 0x59, 0x69, 0x44, 0xb7, 0x41, 0x5d, 0x9a, 0x3e, 0xcc, 0x4a, + 0x73, 0xf5, 0xf4, 0xb2, 0x88, 0xa3, 0xf9, 0x22, 0x8e, 0xde, 0x17, 0x71, 0xf4, 0xbc, 0x8c, 0x3b, + 0xf3, 0x65, 0xdc, 0x79, 0x5d, 0xc6, 0x9d, 0xc7, 0x9b, 0x91, 0xa5, 0xf1, 0x24, 0x4b, 0x00, 0x0b, + 0x09, 0x18, 0x0a, 0x0c, 0xd2, 0x66, 0x30, 0x18, 0xa1, 0xac, 0x86, 0x27, 0xb2, 0x40, 0x3d, 0xc9, + 0x4d, 0xa8, 0x97, 0x0f, 0xf2, 0xf4, 0x62, 0xb0, 0x1e, 0x6f, 0xd0, 0x8e, 0x5e, 0x7f, 0x17, 0xb2, + 0x6e, 0xb3, 0xf7, 0xd9, 0x47, 0x00, 0x00, 0x00, 0xff, 0xff, 0x3c, 0xd5, 0x89, 0xb6, 0xa9, 0x01, + 0x00, 0x00, +} + +func (m *Metadata) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Metadata) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Metadata) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.TxType) > 0 { + i -= len(m.TxType) + copy(dAtA[i:], m.TxType) + i = encodeVarintMetadata(dAtA, i, uint64(len(m.TxType))) + i-- + dAtA[i] = 0x32 + } + if len(m.Encoding) > 0 { + i -= len(m.Encoding) + copy(dAtA[i:], m.Encoding) + i = encodeVarintMetadata(dAtA, i, uint64(len(m.Encoding))) + i-- + dAtA[i] = 0x2a + } + if len(m.Address) > 0 { + i -= len(m.Address) + copy(dAtA[i:], m.Address) + i = encodeVarintMetadata(dAtA, i, uint64(len(m.Address))) + i-- + dAtA[i] = 0x22 + } + if len(m.HostConnectionId) > 0 { + i -= len(m.HostConnectionId) + copy(dAtA[i:], m.HostConnectionId) + i = encodeVarintMetadata(dAtA, i, uint64(len(m.HostConnectionId))) + i-- + dAtA[i] = 0x1a + } + if len(m.ControllerConnectionId) > 0 { + i -= len(m.ControllerConnectionId) + copy(dAtA[i:], m.ControllerConnectionId) + i = encodeVarintMetadata(dAtA, i, uint64(len(m.ControllerConnectionId))) + i-- + dAtA[i] = 0x12 + } + if len(m.Version) > 0 { + i -= len(m.Version) + copy(dAtA[i:], m.Version) + i = encodeVarintMetadata(dAtA, i, uint64(len(m.Version))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintMetadata(dAtA []byte, offset int, v uint64) int { + offset -= sovMetadata(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Metadata) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Version) + if l > 0 { + n += 1 + l + sovMetadata(uint64(l)) + } + l = len(m.ControllerConnectionId) + if l > 0 { + n += 1 + l + sovMetadata(uint64(l)) + } + l = len(m.HostConnectionId) + if l > 0 { + n += 1 + l + sovMetadata(uint64(l)) + } + l = len(m.Address) + if l > 0 { + n += 1 + l + sovMetadata(uint64(l)) + } + l = len(m.Encoding) + if l > 0 { + n += 1 + l + sovMetadata(uint64(l)) + } + l = len(m.TxType) + if l > 0 { + n += 1 + l + sovMetadata(uint64(l)) + } + return n +} + +func sovMetadata(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozMetadata(x uint64) (n int) { + return sovMetadata(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Metadata) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetadata + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Metadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Metadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetadata + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthMetadata + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMetadata + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Version = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ControllerConnectionId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetadata + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthMetadata + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMetadata + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ControllerConnectionId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field HostConnectionId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetadata + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthMetadata + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMetadata + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.HostConnectionId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetadata + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthMetadata + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMetadata + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Address = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Encoding", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetadata + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthMetadata + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMetadata + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Encoding = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TxType", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowMetadata + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthMetadata + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthMetadata + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.TxType = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipMetadata(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthMetadata + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipMetadata(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowMetadata + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowMetadata + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowMetadata + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthMetadata + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupMetadata + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthMetadata + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthMetadata = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowMetadata = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupMetadata = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/apps/27-interchain-accounts/types/metadata_test.go b/modules/apps/27-interchain-accounts/types/metadata_test.go new file mode 100644 index 0000000..ca27b0e --- /dev/null +++ b/modules/apps/27-interchain-accounts/types/metadata_test.go @@ -0,0 +1,446 @@ +package types_test + +import ( + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +// use TestVersion as metadata being compared against +func (suite *TypesTestSuite) TestIsPreviousMetadataEqual() { + var ( + metadata types.Metadata + previousVersion string + ) + + testCases := []struct { + name string + malleate func() + expEqual bool + }{ + { + "success", + func() { + versionBytes, err := types.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + previousVersion = string(versionBytes) + }, + true, + }, + { + "success with empty account address", + func() { + metadata.Address = "" + + versionBytes, err := types.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + previousVersion = string(versionBytes) + }, + true, + }, + { + "cannot decode previous version", + func() { + previousVersion = "invalid previous version" + }, + false, + }, + { + "unequal and invalid encoding format", + func() { + metadata.Encoding = "invalid-encoding-format" + + versionBytes, err := types.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + previousVersion = string(versionBytes) + }, + false, + }, + { + "unequal encoding format", + func() { + metadata.Encoding = types.EncodingProto3JSON + + versionBytes, err := types.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + previousVersion = string(versionBytes) + }, + false, + }, + { + "unequal transaction type", + func() { + metadata.TxType = "invalid-tx-type" + + versionBytes, err := types.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + previousVersion = string(versionBytes) + }, + false, + }, + { + "unequal controller connection", + func() { + metadata.ControllerConnectionId = "connection-10" + + versionBytes, err := types.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + previousVersion = string(versionBytes) + }, + false, + }, + { + "unequal host connection", + func() { + metadata.HostConnectionId = "connection-10" + + versionBytes, err := types.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + previousVersion = string(versionBytes) + }, + false, + }, + { + "unequal version", + func() { + metadata.Version = "invalid version" + + versionBytes, err := types.ModuleCdc.MarshalJSON(&metadata) + suite.Require().NoError(err) + previousVersion = string(versionBytes) + }, + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupConnections() + + expectedMetadata := types.NewMetadata(types.Version, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID, TestOwnerAddress, types.EncodingProtobuf, types.TxTypeSDKMultiMsg) + metadata = expectedMetadata // default success case + + tc.malleate() // malleate mutates test data + + equal := types.IsPreviousMetadataEqual(previousVersion, expectedMetadata) + + if tc.expEqual { + suite.Require().True(equal) + } else { + suite.Require().False(equal) + } + }) + } +} + +func (suite *TypesTestSuite) TestValidateControllerMetadata() { + var metadata types.Metadata + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() {}, + nil, + }, + { + "success with empty account address", + func() { + metadata = types.Metadata{ + Version: types.Version, + ControllerConnectionId: ibctesting.FirstConnectionID, + HostConnectionId: ibctesting.FirstConnectionID, + Address: "", + Encoding: types.EncodingProtobuf, + TxType: types.TxTypeSDKMultiMsg, + } + }, + nil, + }, + { + "success with EncodingProto3JSON", + func() { + metadata = types.Metadata{ + Version: types.Version, + ControllerConnectionId: ibctesting.FirstConnectionID, + HostConnectionId: ibctesting.FirstConnectionID, + Address: TestOwnerAddress, + Encoding: types.EncodingProto3JSON, + TxType: types.TxTypeSDKMultiMsg, + } + }, + nil, + }, + { + "unsupported encoding format", + func() { + metadata = types.Metadata{ + Version: types.Version, + ControllerConnectionId: ibctesting.FirstConnectionID, + HostConnectionId: ibctesting.FirstConnectionID, + Address: TestOwnerAddress, + Encoding: "invalid-encoding-format", + TxType: types.TxTypeSDKMultiMsg, + } + }, + types.ErrInvalidCodec, + }, + { + "unsupported transaction type", + func() { + metadata = types.Metadata{ + Version: types.Version, + ControllerConnectionId: ibctesting.FirstConnectionID, + HostConnectionId: ibctesting.FirstConnectionID, + Address: TestOwnerAddress, + Encoding: types.EncodingProtobuf, + TxType: "invalid-tx-type", + } + }, + types.ErrUnknownDataType, + }, + { + "invalid controller connection", + func() { + metadata = types.Metadata{ + Version: types.Version, + ControllerConnectionId: "connection-10", + HostConnectionId: ibctesting.FirstConnectionID, + Address: TestOwnerAddress, + Encoding: types.EncodingProtobuf, + TxType: types.TxTypeSDKMultiMsg, + } + }, + connectiontypes.ErrInvalidConnection, + }, + { + "invalid host connection", + func() { + metadata = types.Metadata{ + Version: types.Version, + ControllerConnectionId: ibctesting.FirstConnectionID, + HostConnectionId: "connection-10", + Address: TestOwnerAddress, + Encoding: types.EncodingProtobuf, + TxType: types.TxTypeSDKMultiMsg, + } + }, + connectiontypes.ErrInvalidConnection, + }, + { + "invalid address", + func() { + metadata = types.Metadata{ + Version: types.Version, + ControllerConnectionId: ibctesting.FirstConnectionID, + HostConnectionId: ibctesting.FirstConnectionID, + Address: " ", + Encoding: types.EncodingProtobuf, + TxType: types.TxTypeSDKMultiMsg, + } + }, + types.ErrInvalidAccountAddress, + }, + { + "invalid version", + func() { + metadata = types.Metadata{ + Version: "invalid version", + ControllerConnectionId: ibctesting.FirstConnectionID, + HostConnectionId: ibctesting.FirstConnectionID, + Address: TestOwnerAddress, + Encoding: types.EncodingProtobuf, + TxType: types.TxTypeSDKMultiMsg, + } + }, + types.ErrInvalidVersion, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupConnections() + + metadata = types.NewMetadata(types.Version, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID, TestOwnerAddress, types.EncodingProtobuf, types.TxTypeSDKMultiMsg) + + tc.malleate() // malleate mutates test data + + err := types.ValidateControllerMetadata( + suite.chainA.GetContext(), + suite.chainA.App.GetIBCKeeper().ChannelKeeper, + []string{ibctesting.FirstConnectionID}, + metadata, + ) + + if tc.expErr == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().Error(err, tc.name) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *TypesTestSuite) TestValidateHostMetadata() { + var metadata types.Metadata + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() {}, + nil, + }, + { + "success with empty account address", + func() { + metadata = types.Metadata{ + Version: types.Version, + ControllerConnectionId: ibctesting.FirstConnectionID, + HostConnectionId: ibctesting.FirstConnectionID, + Address: "", + Encoding: types.EncodingProtobuf, + TxType: types.TxTypeSDKMultiMsg, + } + }, + nil, + }, + { + "success with EncodingProto3JSON", + func() { + metadata = types.Metadata{ + Version: types.Version, + ControllerConnectionId: ibctesting.FirstConnectionID, + HostConnectionId: ibctesting.FirstConnectionID, + Address: TestOwnerAddress, + Encoding: types.EncodingProto3JSON, + TxType: types.TxTypeSDKMultiMsg, + } + }, + nil, + }, + { + "unsupported encoding format", + func() { + metadata = types.Metadata{ + Version: types.Version, + ControllerConnectionId: ibctesting.FirstConnectionID, + HostConnectionId: ibctesting.FirstConnectionID, + Address: TestOwnerAddress, + Encoding: "invalid-encoding-format", + TxType: types.TxTypeSDKMultiMsg, + } + }, + types.ErrInvalidCodec, + }, + { + "unsupported transaction type", + func() { + metadata = types.Metadata{ + Version: types.Version, + ControllerConnectionId: ibctesting.FirstConnectionID, + HostConnectionId: ibctesting.FirstConnectionID, + Address: TestOwnerAddress, + Encoding: types.EncodingProtobuf, + TxType: "invalid-tx-type", + } + }, + types.ErrUnknownDataType, + }, + { + "invalid controller connection", + func() { + metadata = types.Metadata{ + Version: types.Version, + ControllerConnectionId: "connection-10", + HostConnectionId: ibctesting.FirstConnectionID, + Address: TestOwnerAddress, + Encoding: types.EncodingProtobuf, + TxType: types.TxTypeSDKMultiMsg, + } + }, + connectiontypes.ErrInvalidConnection, + }, + { + "invalid host connection", + func() { + metadata = types.Metadata{ + Version: types.Version, + ControllerConnectionId: ibctesting.FirstConnectionID, + HostConnectionId: "connection-10", + Address: TestOwnerAddress, + Encoding: types.EncodingProtobuf, + TxType: types.TxTypeSDKMultiMsg, + } + }, + connectiontypes.ErrInvalidConnection, + }, + { + "invalid address", + func() { + metadata = types.Metadata{ + Version: types.Version, + ControllerConnectionId: ibctesting.FirstConnectionID, + HostConnectionId: ibctesting.FirstConnectionID, + Address: " ", + Encoding: types.EncodingProtobuf, + TxType: types.TxTypeSDKMultiMsg, + } + }, + types.ErrInvalidAccountAddress, + }, + { + "invalid version", + func() { + metadata = types.Metadata{ + Version: "invalid version", + ControllerConnectionId: ibctesting.FirstConnectionID, + HostConnectionId: ibctesting.FirstConnectionID, + Address: TestOwnerAddress, + Encoding: types.EncodingProtobuf, + TxType: types.TxTypeSDKMultiMsg, + } + }, + types.ErrInvalidVersion, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupConnections() + + metadata = types.NewMetadata(types.Version, ibctesting.FirstConnectionID, ibctesting.FirstConnectionID, TestOwnerAddress, types.EncodingProtobuf, types.TxTypeSDKMultiMsg) + + tc.malleate() // malleate mutates test data + + err := types.ValidateHostMetadata( + suite.chainA.GetContext(), + suite.chainA.App.GetIBCKeeper().ChannelKeeper, + []string{ibctesting.FirstConnectionID}, + metadata, + ) + + if tc.expError == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} diff --git a/modules/apps/27-interchain-accounts/types/packet.go b/modules/apps/27-interchain-accounts/types/packet.go new file mode 100644 index 0000000..35a5fa9 --- /dev/null +++ b/modules/apps/27-interchain-accounts/types/packet.go @@ -0,0 +1,99 @@ +package types + +import ( + "encoding/json" + "strings" + + errorsmod "cosmossdk.io/errors" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var ( + _ ibcexported.PacketData = (*InterchainAccountPacketData)(nil) + _ ibcexported.PacketDataProvider = (*InterchainAccountPacketData)(nil) +) + +// MaxMemoCharLength defines the maximum length for the InterchainAccountPacketData memo field +const MaxMemoCharLength = 32768 + +// ValidateBasic performs basic validation of the interchain account packet data. +// The memo may be empty. +func (iapd InterchainAccountPacketData) ValidateBasic() error { + if iapd.Type == UNSPECIFIED { + return errorsmod.Wrap(ErrInvalidOutgoingData, "packet data type cannot be unspecified") + } + + if len(iapd.Data) == 0 { + return errorsmod.Wrap(ErrInvalidOutgoingData, "packet data cannot be empty") + } + + if len(iapd.Memo) > MaxMemoCharLength { + return errorsmod.Wrapf(ErrInvalidOutgoingData, "packet data memo cannot be greater than %d characters", MaxMemoCharLength) + } + + return nil +} + +// GetBytes returns the JSON marshalled interchain account packet data. +func (iapd InterchainAccountPacketData) GetBytes() []byte { + return ModuleCdc.MustMarshalJSON(&iapd) +} + +// UnmarshalJSON unmarshals raw JSON bytes into an InterchainAccountPacketData. +func (iapd *InterchainAccountPacketData) UnmarshalJSON(bz []byte) error { + return ModuleCdc.UnmarshalJSON(bz, iapd) +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (ct CosmosTx) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + for _, protoAny := range ct.Messages { + err := unpacker.UnpackAny(protoAny, new(sdk.Msg)) + if err != nil { + return err + } + } + + return nil +} + +// GetPacketSender returns the sender address of the interchain accounts packet data. +// It is obtained from the source port ID by cutting off the ControllerPortPrefix. +// If the source port ID does not have the ControllerPortPrefix, then an empty string is returned. +// +// NOTE: +// - The sender address is set by the packet sender and may not have been validated a signature +// check if the packet sender isn't the interchain accounts module. +// - The sender address must only be used by modules on the sending chain. +func (InterchainAccountPacketData) GetPacketSender(sourcePortID string) string { + icaOwner, found := strings.CutPrefix(sourcePortID, ControllerPortPrefix) + if !found { + return "" + } + return icaOwner +} + +// GetCustomPacketData interprets the memo field of the packet data as a JSON object +// and returns the value associated with the given key. +// If the key is missing or the memo is not properly formatted, then nil is returned. +func (iapd InterchainAccountPacketData) GetCustomPacketData(key string) any { + if len(iapd.Memo) == 0 { + return nil + } + + jsonObject := make(map[string]any) + err := json.Unmarshal([]byte(iapd.Memo), &jsonObject) + if err != nil { + return nil + } + + memoData, found := jsonObject[key] + if !found { + return nil + } + + return memoData +} diff --git a/modules/apps/27-interchain-accounts/types/packet.pb.go b/modules/apps/27-interchain-accounts/types/packet.pb.go new file mode 100644 index 0000000..ee3a787 --- /dev/null +++ b/modules/apps/27-interchain-accounts/types/packet.pb.go @@ -0,0 +1,634 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/applications/interchain_accounts/v1/packet.proto + +package types + +import ( + fmt "fmt" + types "github.com/cosmos/cosmos-sdk/codec/types" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Type defines a classification of message issued from a controller chain to its associated interchain accounts +// host +type Type int32 + +const ( + // Default zero value enumeration + UNSPECIFIED Type = 0 + // Execute a transaction on an interchain accounts host chain + EXECUTE_TX Type = 1 +) + +var Type_name = map[int32]string{ + 0: "TYPE_UNSPECIFIED", + 1: "TYPE_EXECUTE_TX", +} + +var Type_value = map[string]int32{ + "TYPE_UNSPECIFIED": 0, + "TYPE_EXECUTE_TX": 1, +} + +func (x Type) String() string { + return proto.EnumName(Type_name, int32(x)) +} + +func (Type) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_89a080d7401cd393, []int{0} +} + +// InterchainAccountPacketData is comprised of a raw transaction, type of transaction and optional memo field. +type InterchainAccountPacketData struct { + Type Type `protobuf:"varint,1,opt,name=type,proto3,enum=ibc.applications.interchain_accounts.v1.Type" json:"type,omitempty"` + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + Memo string `protobuf:"bytes,3,opt,name=memo,proto3" json:"memo,omitempty"` +} + +func (m *InterchainAccountPacketData) Reset() { *m = InterchainAccountPacketData{} } +func (m *InterchainAccountPacketData) String() string { return proto.CompactTextString(m) } +func (*InterchainAccountPacketData) ProtoMessage() {} +func (*InterchainAccountPacketData) Descriptor() ([]byte, []int) { + return fileDescriptor_89a080d7401cd393, []int{0} +} +func (m *InterchainAccountPacketData) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *InterchainAccountPacketData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_InterchainAccountPacketData.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *InterchainAccountPacketData) XXX_Merge(src proto.Message) { + xxx_messageInfo_InterchainAccountPacketData.Merge(m, src) +} +func (m *InterchainAccountPacketData) XXX_Size() int { + return m.Size() +} +func (m *InterchainAccountPacketData) XXX_DiscardUnknown() { + xxx_messageInfo_InterchainAccountPacketData.DiscardUnknown(m) +} + +var xxx_messageInfo_InterchainAccountPacketData proto.InternalMessageInfo + +func (m *InterchainAccountPacketData) GetType() Type { + if m != nil { + return m.Type + } + return UNSPECIFIED +} + +func (m *InterchainAccountPacketData) GetData() []byte { + if m != nil { + return m.Data + } + return nil +} + +func (m *InterchainAccountPacketData) GetMemo() string { + if m != nil { + return m.Memo + } + return "" +} + +// CosmosTx contains a list of sdk.Msg's. It should be used when sending transactions to an SDK host chain. +type CosmosTx struct { + Messages []*types.Any `protobuf:"bytes,1,rep,name=messages,proto3" json:"messages,omitempty"` +} + +func (m *CosmosTx) Reset() { *m = CosmosTx{} } +func (m *CosmosTx) String() string { return proto.CompactTextString(m) } +func (*CosmosTx) ProtoMessage() {} +func (*CosmosTx) Descriptor() ([]byte, []int) { + return fileDescriptor_89a080d7401cd393, []int{1} +} +func (m *CosmosTx) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *CosmosTx) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_CosmosTx.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *CosmosTx) XXX_Merge(src proto.Message) { + xxx_messageInfo_CosmosTx.Merge(m, src) +} +func (m *CosmosTx) XXX_Size() int { + return m.Size() +} +func (m *CosmosTx) XXX_DiscardUnknown() { + xxx_messageInfo_CosmosTx.DiscardUnknown(m) +} + +var xxx_messageInfo_CosmosTx proto.InternalMessageInfo + +func (m *CosmosTx) GetMessages() []*types.Any { + if m != nil { + return m.Messages + } + return nil +} + +func init() { + proto.RegisterEnum("ibc.applications.interchain_accounts.v1.Type", Type_name, Type_value) + proto.RegisterType((*InterchainAccountPacketData)(nil), "ibc.applications.interchain_accounts.v1.InterchainAccountPacketData") + proto.RegisterType((*CosmosTx)(nil), "ibc.applications.interchain_accounts.v1.CosmosTx") +} + +func init() { + proto.RegisterFile("ibc/applications/interchain_accounts/v1/packet.proto", fileDescriptor_89a080d7401cd393) +} + +var fileDescriptor_89a080d7401cd393 = []byte{ + // 394 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x51, 0x41, 0x8b, 0xd3, 0x40, + 0x18, 0xcd, 0xb8, 0x41, 0xd6, 0x59, 0xd9, 0x2d, 0x61, 0x0f, 0x31, 0x42, 0x08, 0x2b, 0x62, 0x10, + 0x32, 0xb3, 0xad, 0x82, 0x17, 0x2f, 0xb5, 0x4d, 0xa1, 0x17, 0x29, 0x31, 0x85, 0xea, 0xa5, 0x4e, + 0xa6, 0x63, 0x3a, 0xd8, 0x64, 0x42, 0x67, 0x52, 0xcc, 0x3f, 0x28, 0x3d, 0xf9, 0x07, 0x7a, 0xf2, + 0xcf, 0x78, 0xec, 0xd1, 0xa3, 0xb4, 0x7f, 0x44, 0x32, 0xc1, 0xb6, 0x07, 0x0f, 0xde, 0x1e, 0x8f, + 0xef, 0xbd, 0xef, 0x7b, 0xdf, 0x83, 0xaf, 0x79, 0x42, 0x31, 0x29, 0x8a, 0x05, 0xa7, 0x44, 0x71, + 0x91, 0x4b, 0xcc, 0x73, 0xc5, 0x96, 0x74, 0x4e, 0x78, 0x3e, 0x25, 0x94, 0x8a, 0x32, 0x57, 0x12, + 0xaf, 0xda, 0xb8, 0x20, 0xf4, 0x2b, 0x53, 0xa8, 0x58, 0x0a, 0x25, 0xac, 0x17, 0x3c, 0xa1, 0xe8, + 0x5c, 0x85, 0xfe, 0xa1, 0x42, 0xab, 0xb6, 0xf3, 0x24, 0x15, 0x22, 0x5d, 0x30, 0xac, 0x65, 0x49, + 0xf9, 0x05, 0x93, 0xbc, 0x6a, 0x3c, 0x9c, 0xdb, 0x54, 0xa4, 0x42, 0x43, 0x5c, 0xa3, 0x86, 0xbd, + 0x5b, 0x03, 0xf8, 0x74, 0x78, 0xf4, 0xea, 0x36, 0x56, 0x23, 0xbd, 0xbb, 0x4f, 0x14, 0xb1, 0xba, + 0xd0, 0x54, 0x55, 0xc1, 0x6c, 0xe0, 0x01, 0xff, 0xba, 0x13, 0xa0, 0xff, 0x3c, 0x04, 0xc5, 0x55, + 0xc1, 0x22, 0x2d, 0xb5, 0x2c, 0x68, 0xce, 0x88, 0x22, 0xf6, 0x03, 0x0f, 0xf8, 0x8f, 0x23, 0x8d, + 0x6b, 0x2e, 0x63, 0x99, 0xb0, 0x2f, 0x3c, 0xe0, 0x3f, 0x8a, 0x34, 0xbe, 0x7b, 0x0b, 0x2f, 0x7b, + 0x42, 0x66, 0x42, 0xc6, 0xdf, 0xac, 0x7b, 0x78, 0x99, 0x31, 0x29, 0x49, 0xca, 0xa4, 0x0d, 0xbc, + 0x0b, 0xff, 0xaa, 0x73, 0x8b, 0x9a, 0x68, 0xe8, 0x6f, 0x34, 0xd4, 0xcd, 0xab, 0xe8, 0x38, 0xf5, + 0x72, 0x02, 0xcd, 0x7a, 0xa7, 0xf5, 0x1c, 0xb6, 0xe2, 0x8f, 0xa3, 0x70, 0x3a, 0x7e, 0xff, 0x61, + 0x14, 0xf6, 0x86, 0x83, 0x61, 0xd8, 0x6f, 0x19, 0xce, 0xcd, 0x66, 0xeb, 0x5d, 0x9d, 0x51, 0xd6, + 0x33, 0x78, 0xa3, 0xc7, 0xc2, 0x49, 0xd8, 0x1b, 0xc7, 0xe1, 0x34, 0x9e, 0xb4, 0x80, 0x73, 0xbd, + 0xd9, 0x7a, 0xf0, 0xc4, 0x38, 0xe6, 0xfa, 0x87, 0x6b, 0xbc, 0xfb, 0xfc, 0x73, 0xef, 0x82, 0xdd, + 0xde, 0x05, 0xbf, 0xf7, 0x2e, 0xf8, 0x7e, 0x70, 0x8d, 0xdd, 0xc1, 0x35, 0x7e, 0x1d, 0x5c, 0xe3, + 0xd3, 0x20, 0xe5, 0x6a, 0x5e, 0x26, 0x88, 0x8a, 0x0c, 0x53, 0x7d, 0x3a, 0xe6, 0x09, 0x0d, 0x52, + 0x81, 0x57, 0xed, 0x7b, 0x9c, 0x89, 0x59, 0xb9, 0x60, 0xb2, 0x6e, 0x5b, 0xe2, 0xce, 0x9b, 0xe0, + 0xf4, 0xa9, 0xe0, 0x58, 0x74, 0xfd, 0x20, 0x99, 0x3c, 0xd4, 0x99, 0x5e, 0xfd, 0x09, 0x00, 0x00, + 0xff, 0xff, 0xea, 0x6b, 0xc7, 0x3f, 0x1d, 0x02, 0x00, 0x00, +} + +func (m *InterchainAccountPacketData) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *InterchainAccountPacketData) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *InterchainAccountPacketData) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Memo) > 0 { + i -= len(m.Memo) + copy(dAtA[i:], m.Memo) + i = encodeVarintPacket(dAtA, i, uint64(len(m.Memo))) + i-- + dAtA[i] = 0x1a + } + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintPacket(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0x12 + } + if m.Type != 0 { + i = encodeVarintPacket(dAtA, i, uint64(m.Type)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *CosmosTx) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CosmosTx) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *CosmosTx) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Messages) > 0 { + for iNdEx := len(m.Messages) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Messages[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintPacket(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintPacket(dAtA []byte, offset int, v uint64) int { + offset -= sovPacket(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *InterchainAccountPacketData) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Type != 0 { + n += 1 + sovPacket(uint64(m.Type)) + } + l = len(m.Data) + if l > 0 { + n += 1 + l + sovPacket(uint64(l)) + } + l = len(m.Memo) + if l > 0 { + n += 1 + l + sovPacket(uint64(l)) + } + return n +} + +func (m *CosmosTx) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Messages) > 0 { + for _, e := range m.Messages { + l = e.Size() + n += 1 + l + sovPacket(uint64(l)) + } + } + return n +} + +func sovPacket(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozPacket(x uint64) (n int) { + return sovPacket(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *InterchainAccountPacketData) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: InterchainAccountPacketData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: InterchainAccountPacketData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + m.Type = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Type |= Type(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthPacket + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthPacket + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Memo", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthPacket + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthPacket + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Memo = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipPacket(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthPacket + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CosmosTx) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CosmosTx: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CosmosTx: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Messages", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthPacket + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthPacket + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Messages = append(m.Messages, &types.Any{}) + if err := m.Messages[len(m.Messages)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipPacket(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthPacket + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipPacket(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowPacket + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowPacket + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowPacket + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthPacket + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupPacket + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthPacket + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthPacket = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowPacket = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupPacket = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/apps/27-interchain-accounts/types/packet_test.go b/modules/apps/27-interchain-accounts/types/packet_test.go new file mode 100644 index 0000000..4684656 --- /dev/null +++ b/modules/apps/27-interchain-accounts/types/packet_test.go @@ -0,0 +1,203 @@ +package types_test + +import ( + "fmt" + + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *TypesTestSuite) TestValidateBasic() { + testCases := []struct { + name string + packetData types.InterchainAccountPacketData + expErr error + }{ + { + "success", + types.InterchainAccountPacketData{ + Type: types.EXECUTE_TX, + Data: []byte("data"), + Memo: "memo", + }, + nil, + }, + { + "success, empty memo", + types.InterchainAccountPacketData{ + Type: types.EXECUTE_TX, + Data: []byte("data"), + }, + nil, + }, + { + "type unspecified", + types.InterchainAccountPacketData{ + Type: types.UNSPECIFIED, + Data: []byte("data"), + Memo: "memo", + }, + types.ErrInvalidOutgoingData, + }, + { + "empty data", + types.InterchainAccountPacketData{ + Type: types.EXECUTE_TX, + Data: []byte{}, + Memo: "memo", + }, + types.ErrInvalidOutgoingData, + }, + { + "nil data", + types.InterchainAccountPacketData{ + Type: types.EXECUTE_TX, + Data: nil, + Memo: "memo", + }, + types.ErrInvalidOutgoingData, + }, + { + "memo too large", + types.InterchainAccountPacketData{ + Type: types.EXECUTE_TX, + Data: []byte("data"), + Memo: ibctesting.GenerateString(types.MaxMemoCharLength + 1), + }, + types.ErrInvalidOutgoingData, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + err := tc.packetData.ValidateBasic() + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *TypesTestSuite) TestGetPacketSender() { + testCases := []struct { + name string + srcPortID string + expSender string + }{ + { + "success: port id has prefix", + types.ControllerPortPrefix + ibctesting.TestAccAddress, + ibctesting.TestAccAddress, + }, + { + "failure: missing prefix", + ibctesting.TestAccAddress, + "", + }, + { + "failure: empty port id", + "", + "", + }, + } + + for _, tc := range testCases { + + packetData := types.InterchainAccountPacketData{} + suite.Require().Equal(tc.expSender, packetData.GetPacketSender(tc.srcPortID)) + } +} + +func (suite *TypesTestSuite) TestPacketDataProvider() { + expCallbackAddr := ibctesting.TestAccAddress + + testCases := []struct { + name string + packetData types.InterchainAccountPacketData + expCustomData any + }{ + { + "success: src_callback key in memo", + types.InterchainAccountPacketData{ + Type: types.EXECUTE_TX, + Data: []byte("data"), + Memo: fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, expCallbackAddr), + }, + map[string]any{ + "address": expCallbackAddr, + }, + }, + { + "success: src_callback key in memo with additional fields", + types.InterchainAccountPacketData{ + Type: types.EXECUTE_TX, + Data: []byte("data"), + Memo: fmt.Sprintf(`{"src_callback": {"address": "%s", "gas_limit": "200000"}}`, expCallbackAddr), + }, + map[string]any{ + "address": expCallbackAddr, + "gas_limit": "200000", + }, + }, + { + "success: src_callback has string value", + types.InterchainAccountPacketData{ + Type: types.EXECUTE_TX, + Data: []byte("data"), + Memo: `{"src_callback": "string"}`, + }, + "string", + }, + { + "failure: empty memo", + types.InterchainAccountPacketData{ + Type: types.EXECUTE_TX, + Data: []byte("data"), + Memo: "", + }, + nil, + }, + { + "failure: non-json memo", + types.InterchainAccountPacketData{ + Type: types.EXECUTE_TX, + Data: []byte("data"), + Memo: "invalid", + }, + nil, + }, + } + + for _, tc := range testCases { + + customData := tc.packetData.GetCustomPacketData("src_callback") + suite.Require().Equal(tc.expCustomData, customData) + } +} + +func (suite *TypesTestSuite) TestPacketDataUnmarshalerInterface() { + expPacketData := types.InterchainAccountPacketData{ + Type: types.EXECUTE_TX, + Data: []byte("data"), + Memo: "some memo", + } + + var packetData types.InterchainAccountPacketData + err := packetData.UnmarshalJSON(expPacketData.GetBytes()) + suite.Require().NoError(err) + suite.Require().Equal(expPacketData, packetData) + + // test invalid packet data + invalidPacketDataBytes := []byte("invalid packet data") + + var invalidPacketData types.InterchainAccountPacketData + err = packetData.UnmarshalJSON(invalidPacketDataBytes) + suite.Require().Error(err) + suite.Require().Equal(types.InterchainAccountPacketData{}, invalidPacketData) +} diff --git a/modules/apps/27-interchain-accounts/types/port.go b/modules/apps/27-interchain-accounts/types/port.go new file mode 100644 index 0000000..d2d10c1 --- /dev/null +++ b/modules/apps/27-interchain-accounts/types/port.go @@ -0,0 +1,17 @@ +package types + +import ( + "strings" + + errorsmod "cosmossdk.io/errors" +) + +// NewControllerPortID creates and returns a new prefixed controller port identifier using the provided owner string +func NewControllerPortID(owner string) (string, error) { + if strings.TrimSpace(owner) == "" { + return "", errorsmod.Wrap(ErrInvalidAccountAddress, "owner address cannot be empty") + } + + ownerWithPrefix := ControllerPortPrefix + owner + return ownerWithPrefix, nil +} diff --git a/modules/apps/27-interchain-accounts/types/port_test.go b/modules/apps/27-interchain-accounts/types/port_test.go new file mode 100644 index 0000000..131908b --- /dev/null +++ b/modules/apps/27-interchain-accounts/types/port_test.go @@ -0,0 +1,57 @@ +package types_test + +import ( + "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *TypesTestSuite) TestNewControllerPortID() { + var ( + path *ibctesting.Path + owner = TestOwnerAddress + ) + + testCases := []struct { + name string + malleate func() + expValue string + expErr error + }{ + { + "success", + func() {}, + types.ControllerPortPrefix + TestOwnerAddress, + nil, + }, + { + "invalid owner address", + func() { + owner = " " + }, + "", + types.ErrInvalidAccountAddress, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + tc.malleate() // malleate mutates test data + + portID, err := types.NewControllerPortID(owner) + + if tc.expErr == nil { + suite.Require().NoError(err, tc.name) + suite.Require().Equal(tc.expValue, portID) + } else { + suite.Require().Error(err, tc.name) + suite.Require().Empty(portID) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} diff --git a/modules/apps/27-interchain-accounts/types/router.go b/modules/apps/27-interchain-accounts/types/router.go new file mode 100644 index 0000000..62b4fa2 --- /dev/null +++ b/modules/apps/27-interchain-accounts/types/router.go @@ -0,0 +1,20 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// MessageRouter ADR 031 request type routing +// https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-031-msg-service.md +type MessageRouter interface { + Handler(msg sdk.Msg) baseapp.MsgServiceHandler +} + +// QueryRouter ADR 021 query type routing +// https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-021-protobuf-query-encoding.md +type QueryRouter interface { + // Route returns the GRPCQueryHandler for a given query route path or nil + // if not found + Route(path string) baseapp.GRPCQueryHandler +} diff --git a/modules/apps/callbacks/CHANGELOG.md b/modules/apps/callbacks/CHANGELOG.md new file mode 100644 index 0000000..9d1ce71 --- /dev/null +++ b/modules/apps/callbacks/CHANGELOG.md @@ -0,0 +1,69 @@ + + +# Changelog + +## [Unreleased] + +### Dependencies + +* [\#6828](https://github.com/cosmos/ibc-go/pull/6828) Bump Cosmos SDK to v0.50.9. +* [\#6193](https://github.com/cosmos/ibc-go/pull/6193) Bump `cosmossdk.io/store` to v1.1.0. +* [\#7247](https://github.com/cosmos/ibc-go/pull/7247) Bump CometBFT to v0.38.12. + +### API Breaking + +* (apps/callbacks) [\#7000](https://github.com/cosmos/ibc-go/pull/7000) Add base application version to contract keeper callbacks. + +### State Machine Breaking + +### Improvements + +### Features + +### Bug Fixes + + +## [v0.2.0+ibc-go-v8.0](https://github.com/cosmos/ibc-go/releases/tag/modules%2Fapps%2Fcallbacks%2Fv0.2.0%2Bibc-go-v8.0) - 2023-11-15 + +### Bug Fixes + +* [\#4568](https://github.com/cosmos/ibc-go/pull/4568) Include error in event that is emitted when the callback cannot be executed due to a panic or an out of gas error. Packet is only sent if the `IBCSendPacketCallback` returns nil explicitly. + + +## [v0.1.0+ibc-go-v7.3](https://github.com/cosmos/ibc-go/releases/tag/modules%2Fapps%2Fcallbacks%2Fv0.1.0%2Bibc-go-v7.3) - 2023-08-31 + +### Features + +* [\#3939](https://github.com/cosmos/ibc-go/pull/3939) feat(callbacks): ADR8 implementation. diff --git a/modules/apps/callbacks/README.md b/modules/apps/callbacks/README.md new file mode 100644 index 0000000..13886c2 --- /dev/null +++ b/modules/apps/callbacks/README.md @@ -0,0 +1,19 @@ +# Callbacks Middleware + +IBC was designed with callbacks between core IBC and IBC applications. IBC apps would send a packet to core IBC, and receive a callback on every step of that packet's lifecycle. This allows IBC applications to be built on top of core IBC, and to be able to execute custom logic on packet lifecycle events (e.g. unescrow tokens for ICS-20). + +This setup worked well for off-chain users interacting with IBC applications. However, we are now seeing the desire for secondary applications (e.g. smart contracts, modules) to call into IBC apps as part of their state machine logic and then do some actions on packet lifecycle events. + +The Callbacks Middleware allows for this functionality by allowing the packets of the underlying IBC applications to register callbacks to secondary applications for lifecycle events. These callbacks are then executed by the Callbacks Middleware when the corresponding packet lifecycle event occurs. + +After much discussion, the design was expanded to [an ADR](/architecture/adr-008-app-caller-cbs), and the Callbacks Middleware is an implementation of that ADR. + +## Version Matrix + +The callbacks middleware has no stable releases yet. To use it, you need to import the git commit that contains the module with the compatible version of `ibc-go`. To do so, run the following command with the desired git commit in your project: + +```sh +go get github.com/cosmos/ibc-go/modules/apps/callbacks@342c00b0f8bd7feeebf0780f208a820b0faf90d1 +``` + +You can find the version matrix for the callbacks middleware [here](https://github.com/cosmos/ibc-go/blob/main/RELEASES.md#callbacks-middleware-1) diff --git a/modules/apps/callbacks/callbacks_test.go b/modules/apps/callbacks/callbacks_test.go new file mode 100644 index 0000000..99f8de5 --- /dev/null +++ b/modules/apps/callbacks/callbacks_test.go @@ -0,0 +1,250 @@ +package ibccallbacks_test + +import ( + "encoding/json" + "errors" + "fmt" + "testing" + + dbm "github.com/cosmos/cosmos-db" + "github.com/stretchr/testify/suite" + + "cosmossdk.io/log" + sdkmath "cosmossdk.io/math" + storetypes "cosmossdk.io/store/types" + + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + abci "github.com/cometbft/cometbft/abci/types" + + icacontrollertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/testing/simapp" + "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/types" + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +const maxCallbackGas = uint64(1000000) + +// SetupTestingApp provides the duplicated simapp which is specific to the callbacks module on chain creation. +func SetupTestingApp() (ibctesting.TestingApp, map[string]json.RawMessage) { + db := dbm.NewMemDB() + app := simapp.NewSimApp(log.NewNopLogger(), db, nil, true, simtestutil.EmptyAppOptions{}) + return app, app.DefaultGenesis() +} + +// GetSimApp returns the duplicated SimApp from within the callbacks directory. +// This must be used instead of chain.GetSimApp() for tests within this directory. +func GetSimApp(chain *ibctesting.TestChain) *simapp.SimApp { + app, ok := chain.App.(*simapp.SimApp) + if !ok { + panic(errors.New("chain is not a simapp.SimApp")) + } + return app +} + +// CallbacksTestSuite defines the needed instances and methods to test callbacks +type CallbacksTestSuite struct { + suite.Suite + + coordinator *ibctesting.Coordinator + + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain + + path *ibctesting.Path +} + +// setupChains sets up a coordinator with 2 test chains. +func (s *CallbacksTestSuite) setupChains() { + s.coordinator = ibctesting.NewCustomAppCoordinator(s.T(), 2, SetupTestingApp) + s.chainA = s.coordinator.GetChain(ibctesting.GetChainID(1)) + s.chainB = s.coordinator.GetChain(ibctesting.GetChainID(2)) + s.path = ibctesting.NewPath(s.chainA, s.chainB) +} + +// SetupTransferTest sets up a transfer channel between chainA and chainB +func (s *CallbacksTestSuite) SetupTransferTest() { + s.setupChains() + + s.path.EndpointA.ChannelConfig.PortID = ibctesting.TransferPort + s.path.EndpointB.ChannelConfig.PortID = ibctesting.TransferPort + s.path.EndpointA.ChannelConfig.Version = transfertypes.V1 + s.path.EndpointB.ChannelConfig.Version = transfertypes.V1 + + s.path.Setup() +} + +// SetupICATest sets up an interchain accounts channel between chainA (controller) and chainB (host). +// It funds and returns the interchain account address owned by chainA's SenderAccount. +func (s *CallbacksTestSuite) SetupICATest() string { + s.setupChains() + s.path.SetupConnections() + + icaOwner := s.chainA.SenderAccount.GetAddress().String() + // ICAVersion defines an interchain accounts version string + icaVersion := icatypes.NewDefaultMetadataString(s.path.EndpointA.ConnectionID, s.path.EndpointB.ConnectionID) + icaControllerPortID, err := icatypes.NewControllerPortID(icaOwner) + s.Require().NoError(err) + + s.path.SetChannelOrdered() + s.path.EndpointA.ChannelConfig.PortID = icaControllerPortID + s.path.EndpointB.ChannelConfig.PortID = icatypes.HostPortID + s.path.EndpointA.ChannelConfig.Version = icaVersion + s.path.EndpointB.ChannelConfig.Version = icaVersion + + s.RegisterInterchainAccount(icaOwner) + // open chan init must be skipped. So we cannot use .CreateChannels() + err = s.path.EndpointB.ChanOpenTry() + s.Require().NoError(err) + err = s.path.EndpointA.ChanOpenAck() + s.Require().NoError(err) + err = s.path.EndpointB.ChanOpenConfirm() + s.Require().NoError(err) + + interchainAccountAddr, found := GetSimApp(s.chainB).ICAHostKeeper.GetInterchainAccountAddress(s.chainB.GetContext(), s.path.EndpointA.ConnectionID, s.path.EndpointA.ChannelConfig.PortID) + s.Require().True(found) + + // fund the interchain account on chainB + msgBankSend := &banktypes.MsgSend{ + FromAddress: s.chainB.SenderAccount.GetAddress().String(), + ToAddress: interchainAccountAddr, + Amount: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(100000))), + } + res, err := s.chainB.SendMsgs(msgBankSend) + s.Require().NotEmpty(res) + s.Require().NoError(err) + + return interchainAccountAddr +} + +// RegisterInterchainAccount submits a MsgRegisterInterchainAccount and updates the controller endpoint with the +// channel created. +func (s *CallbacksTestSuite) RegisterInterchainAccount(owner string) { + msgRegister := icacontrollertypes.NewMsgRegisterInterchainAccount(s.path.EndpointA.ConnectionID, owner, s.path.EndpointA.ChannelConfig.Version, channeltypes.ORDERED) + + res, err := s.chainA.SendMsgs(msgRegister) + s.Require().NotEmpty(res) + s.Require().NoError(err) + + channelID, err := ibctesting.ParseChannelIDFromEvents(res.Events) + s.Require().NoError(err) + + s.path.EndpointA.ChannelID = channelID +} + +// AssertHasExecutedExpectedCallback checks the stateful entries and counters based on callbacktype. +// It assumes that the source chain is chainA and the destination chain is chainB. +func (s *CallbacksTestSuite) AssertHasExecutedExpectedCallback(callbackType types.CallbackType, expSuccess bool) { + var expStatefulEntries uint8 + if expSuccess { + // if the callback is expected to be successful, + // we expect at least one state entry + expStatefulEntries = 1 + } + + sourceStatefulCounter := GetSimApp(s.chainA).MockContractKeeper.GetStateEntryCounter(s.chainA.GetContext()) + destStatefulCounter := GetSimApp(s.chainB).MockContractKeeper.GetStateEntryCounter(s.chainB.GetContext()) + + switch callbackType { + case "none": + s.Require().Equal(uint8(0), sourceStatefulCounter) + s.Require().Equal(uint8(0), destStatefulCounter) + + case types.CallbackTypeSendPacket: + s.Require().Equal(expStatefulEntries, sourceStatefulCounter, "unexpected stateful entry amount for source send packet callback") + s.Require().Equal(uint8(0), destStatefulCounter) + + case types.CallbackTypeAcknowledgementPacket, types.CallbackTypeTimeoutPacket: + expStatefulEntries *= 2 // expect OnAcknowledgement/OnTimeout to be successful as well as the initial SendPacket + s.Require().Equal(expStatefulEntries, sourceStatefulCounter, "unexpected stateful entry amount for source acknowledgement/timeout callbacks") + s.Require().Equal(uint8(0), destStatefulCounter) + + case types.CallbackTypeReceivePacket: + s.Require().Equal(uint8(0), sourceStatefulCounter) + s.Require().Equal(expStatefulEntries, destStatefulCounter) + + default: + s.FailNow(fmt.Sprintf("invalid callback type %s", callbackType)) + } + + s.AssertCallbackCounters(callbackType) +} + +func (s *CallbacksTestSuite) AssertCallbackCounters(callbackType types.CallbackType) { + sourceCounters := GetSimApp(s.chainA).MockContractKeeper.Counters + destCounters := GetSimApp(s.chainB).MockContractKeeper.Counters + + switch callbackType { + case "none": + s.Require().Len(sourceCounters, 0) + s.Require().Len(destCounters, 0) + + case types.CallbackTypeSendPacket: + s.Require().Len(sourceCounters, 1) + s.Require().Equal(1, sourceCounters[types.CallbackTypeSendPacket]) + + case types.CallbackTypeAcknowledgementPacket: + s.Require().Len(sourceCounters, 2) + s.Require().Equal(1, sourceCounters[types.CallbackTypeSendPacket]) + s.Require().Equal(1, sourceCounters[types.CallbackTypeAcknowledgementPacket]) + + s.Require().Len(destCounters, 0) + + case types.CallbackTypeReceivePacket: + s.Require().Len(sourceCounters, 0) + s.Require().Len(destCounters, 1) + s.Require().Equal(1, destCounters[types.CallbackTypeReceivePacket]) + + case types.CallbackTypeTimeoutPacket: + s.Require().Len(sourceCounters, 2) + s.Require().Equal(1, sourceCounters[types.CallbackTypeSendPacket]) + s.Require().Equal(1, sourceCounters[types.CallbackTypeTimeoutPacket]) + + s.Require().Len(destCounters, 0) + + default: + s.FailNow(fmt.Sprintf("invalid callback type %s", callbackType)) + } +} + +func TestIBCCallbacksTestSuite(t *testing.T) { + suite.Run(t, new(CallbacksTestSuite)) +} + +// GetExpectedEvent returns the expected event for a callback. +func GetExpectedEvent( + ctx sdk.Context, packetDataUnmarshaler porttypes.PacketDataUnmarshaler, remainingGas uint64, data []byte, + eventPortID, eventChannelID string, seq uint64, callbackType types.CallbackType, expError error, +) (abci.Event, bool) { + var ( + callbackData types.CallbackData + isCbPacket bool + err error + ) + + // Set up gas meter with remainingGas. + gasMeter := storetypes.NewGasMeter(remainingGas) + ctx = ctx.WithGasMeter(gasMeter) + + if callbackType == types.CallbackTypeReceivePacket { + packet := channeltypes.NewPacket(data, seq, "", "", eventPortID, eventChannelID, clienttypes.ZeroHeight(), 0) + callbackData, isCbPacket, err = types.GetDestCallbackData(ctx, packetDataUnmarshaler, packet, maxCallbackGas) + } else { + packet := channeltypes.NewPacket(data, seq, eventPortID, eventChannelID, "", "", clienttypes.ZeroHeight(), 0) + callbackData, isCbPacket, err = types.GetSourceCallbackData(ctx, packetDataUnmarshaler, packet, maxCallbackGas) + } + if !isCbPacket || err != nil { + return abci.Event{}, false + } + + newCtx := sdk.Context{}.WithEventManager(sdk.NewEventManager()) + types.EmitCallbackEvent(newCtx, eventPortID, eventChannelID, seq, callbackType, callbackData, expError) + return newCtx.EventManager().Events().ToABCIEvents()[0], true +} diff --git a/modules/apps/callbacks/ibc_middleware.go b/modules/apps/callbacks/ibc_middleware.go new file mode 100644 index 0000000..00b9660 --- /dev/null +++ b/modules/apps/callbacks/ibc_middleware.go @@ -0,0 +1,366 @@ +package ibccallbacks + +import ( + "errors" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/internal" + "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var ( + _ porttypes.Middleware = (*IBCMiddleware)(nil) + _ porttypes.PacketDataUnmarshaler = (*IBCMiddleware)(nil) +) + +// IBCMiddleware implements the ICS26 callbacks for the ibc-callbacks middleware given +// the underlying application. +type IBCMiddleware struct { + app types.CallbacksCompatibleModule + ics4Wrapper porttypes.ICS4Wrapper + + contractKeeper types.ContractKeeper + + // maxCallbackGas defines the maximum amount of gas that a callback actor can ask the + // relayer to pay for. If a callback fails due to insufficient gas, the entire tx + // is reverted if the relayer hadn't provided the minimum(userDefinedGas, maxCallbackGas). + // If the actor hasn't defined a gas limit, then it is assumed to be the maxCallbackGas. + maxCallbackGas uint64 +} + +// NewIBCMiddleware creates a new IBCMiddleware given the keeper and underlying application. +// The underlying application must implement the required callback interfaces. +func NewIBCMiddleware( + app porttypes.IBCModule, ics4Wrapper porttypes.ICS4Wrapper, + contractKeeper types.ContractKeeper, maxCallbackGas uint64, +) IBCMiddleware { + packetDataUnmarshalerApp, ok := app.(types.CallbacksCompatibleModule) + if !ok { + panic(fmt.Errorf("underlying application does not implement %T", (*types.CallbacksCompatibleModule)(nil))) + } + + if ics4Wrapper == nil { + panic(errors.New("ICS4Wrapper cannot be nil")) + } + + if contractKeeper == nil { + panic(errors.New("contract keeper cannot be nil")) + } + + if maxCallbackGas == 0 { + panic(errors.New("maxCallbackGas cannot be zero")) + } + + return IBCMiddleware{ + app: packetDataUnmarshalerApp, + ics4Wrapper: ics4Wrapper, + contractKeeper: contractKeeper, + maxCallbackGas: maxCallbackGas, + } +} + +// WithICS4Wrapper sets the ICS4Wrapper. This function may be used after the +// middleware's creation to set the middleware which is above this module in +// the IBC application stack. +func (im *IBCMiddleware) WithICS4Wrapper(wrapper porttypes.ICS4Wrapper) { + im.ics4Wrapper = wrapper +} + +// GetICS4Wrapper returns the ICS4Wrapper. +func (im *IBCMiddleware) GetICS4Wrapper() porttypes.ICS4Wrapper { + return im.ics4Wrapper +} + +// SendPacket implements source callbacks for sending packets. +// It defers to the underlying application and then calls the contract callback. +// If the contract callback returns an error, panics, or runs out of gas, then +// the packet send is rejected. +func (im IBCMiddleware) SendPacket( + ctx sdk.Context, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + data []byte, +) (uint64, error) { + seq, err := im.ics4Wrapper.SendPacket(ctx, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data) + if err != nil { + return 0, err + } + + // packet is created without destination information present, GetSourceCallbackData does not use these. + packet := channeltypes.NewPacket(data, seq, sourcePort, sourceChannel, "", "", timeoutHeight, timeoutTimestamp) + + callbackData, isCbPacket, err := types.GetSourceCallbackData(ctx, im.app, packet, im.maxCallbackGas) + // SendPacket is not blocked if the packet does not opt-in to callbacks + if !isCbPacket { + return seq, nil + } + // if the packet does opt-in to callbacks but the callback data is malformed, + // then the packet send is rejected. + if err != nil { + return 0, err + } + + callbackExecutor := func(cachedCtx sdk.Context) error { + return im.contractKeeper.IBCSendPacketCallback( + cachedCtx, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data, callbackData.CallbackAddress, callbackData.SenderAddress, callbackData.ApplicationVersion, + ) + } + + err = internal.ProcessCallback(ctx, types.CallbackTypeSendPacket, callbackData, callbackExecutor) + // contract keeper is allowed to reject the packet send. + if err != nil { + return 0, err + } + + types.EmitCallbackEvent(ctx, sourcePort, sourceChannel, seq, types.CallbackTypeSendPacket, callbackData, nil) + return seq, nil +} + +// OnAcknowledgementPacket implements source callbacks for acknowledgement packets. +// It defers to the underlying application and then calls the contract callback. +// If the contract callback runs out of gas and may be retried with a higher gas limit then the state changes are +// reverted via a panic. +func (im IBCMiddleware) OnAcknowledgementPacket( + ctx sdk.Context, + channelVersion string, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + // we first call the underlying app to handle the acknowledgement + err := im.app.OnAcknowledgementPacket(ctx, channelVersion, packet, acknowledgement, relayer) + if err != nil { + return err + } + + callbackData, isCbPacket, err := types.GetSourceCallbackData( + ctx, im.app, packet, im.maxCallbackGas, + ) + // OnAcknowledgementPacket is not blocked if the packet does not opt-in to callbacks + if !isCbPacket { + return nil + } + // if the packet does opt-in to callbacks but the callback data is malformed, + // then the packet acknowledgement is rejected. + // This should never occur, since this error is already checked on `SendPacket` + if err != nil { + return err + } + + callbackExecutor := func(cachedCtx sdk.Context) error { + return im.contractKeeper.IBCOnAcknowledgementPacketCallback( + cachedCtx, packet, acknowledgement, relayer, callbackData.CallbackAddress, callbackData.SenderAddress, callbackData.ApplicationVersion, + ) + } + + // callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions + err = internal.ProcessCallback(ctx, types.CallbackTypeAcknowledgementPacket, callbackData, callbackExecutor) + types.EmitCallbackEvent( + ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(), + types.CallbackTypeAcknowledgementPacket, callbackData, err, + ) + + return nil +} + +// OnTimeoutPacket implements timeout source callbacks for the ibc-callbacks middleware. +// It defers to the underlying application and then calls the contract callback. +// If the contract callback runs out of gas and may be retried with a higher gas limit then the state changes are +// reverted via a panic. +func (im IBCMiddleware) OnTimeoutPacket(ctx sdk.Context, channelVersion string, packet channeltypes.Packet, relayer sdk.AccAddress) error { + err := im.app.OnTimeoutPacket(ctx, channelVersion, packet, relayer) + if err != nil { + return err + } + + callbackData, isCbPacket, err := types.GetSourceCallbackData( + ctx, im.app, packet, im.maxCallbackGas, + ) + // OnTimeoutPacket is not blocked if the packet does not opt-in to callbacks + if !isCbPacket { + return nil + } + // if the packet does opt-in to callbacks but the callback data is malformed, + // then the packet timeout is rejected. + // This should never occur, since this error is already checked on `SendPacket` + if err != nil { + return err + } + + callbackExecutor := func(cachedCtx sdk.Context) error { + return im.contractKeeper.IBCOnTimeoutPacketCallback(cachedCtx, packet, relayer, callbackData.CallbackAddress, callbackData.SenderAddress, callbackData.ApplicationVersion) + } + + // callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions + err = internal.ProcessCallback(ctx, types.CallbackTypeTimeoutPacket, callbackData, callbackExecutor) + types.EmitCallbackEvent( + ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(), + types.CallbackTypeTimeoutPacket, callbackData, err, + ) + + return nil +} + +// OnRecvPacket implements the ReceivePacket destination callbacks for the ibc-callbacks middleware during +// synchronous packet acknowledgement. +// It defers to the underlying application and then calls the contract callback. +// If the contract callback runs out of gas and may be retried with a higher gas limit then the state changes are +// reverted via a panic. +func (im IBCMiddleware) OnRecvPacket(ctx sdk.Context, channelVersion string, packet channeltypes.Packet, relayer sdk.AccAddress) ibcexported.Acknowledgement { + ack := im.app.OnRecvPacket(ctx, channelVersion, packet, relayer) + // if ack is nil (asynchronous acknowledgements), then the callback will be handled in WriteAcknowledgement + // if ack is not successful, all state changes are reverted. If a packet cannot be received, then there is + // no need to execute a callback on the receiving chain. + if ack == nil || !ack.Success() { + return ack + } + + callbackData, isCbPacket, err := types.GetDestCallbackData( + ctx, im.app, packet, im.maxCallbackGas, + ) + // OnRecvPacket is not blocked if the packet does not opt-in to callbacks + if !isCbPacket { + return ack + } + // if the packet does opt-in to callbacks but the callback data is malformed, + // then the packet receive is rejected. + if err != nil { + return channeltypes.NewErrorAcknowledgement(err) + } + + callbackExecutor := func(cachedCtx sdk.Context) error { + return im.contractKeeper.IBCReceivePacketCallback(cachedCtx, packet, ack, callbackData.CallbackAddress, callbackData.ApplicationVersion) + } + + // callback execution errors in RecvPacket are allowed to write an error acknowledgement + // in this case, the receive logic of the underlying app is reverted + // and the error acknowledgement is processed on the sending chain + // Thus the sending application MUST be capable of processing the standard channel acknowledgement + err = internal.ProcessCallback(ctx, types.CallbackTypeReceivePacket, callbackData, callbackExecutor) + types.EmitCallbackEvent( + ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), + types.CallbackTypeReceivePacket, callbackData, err, + ) + if err != nil { + return channeltypes.NewErrorAcknowledgement(err) + } + + return ack +} + +// WriteAcknowledgement implements the ReceivePacket destination callbacks for the ibc-callbacks middleware +// during asynchronous packet acknowledgement. +// It defers to the underlying application and then calls the contract callback. +// If the contract callback runs out of gas and may be retried with a higher gas limit then the state changes are +// reverted via a panic. +func (im IBCMiddleware) WriteAcknowledgement( + ctx sdk.Context, + packet ibcexported.PacketI, + ack ibcexported.Acknowledgement, +) error { + err := im.ics4Wrapper.WriteAcknowledgement(ctx, packet, ack) + if err != nil { + return err + } + + chanPacket, ok := packet.(channeltypes.Packet) + if !ok { + panic(fmt.Errorf("expected type %T, got %T", &channeltypes.Packet{}, packet)) + } + + callbackData, isCbPacket, err := types.GetDestCallbackData( + ctx, im.app, chanPacket, im.maxCallbackGas, + ) + // WriteAcknowledgement is not blocked if the packet does not opt-in to callbacks + if !isCbPacket { + return nil + } + // This should never occur, since this error is already checked on `OnRecvPacket` + if err != nil { + return err + } + + callbackExecutor := func(cachedCtx sdk.Context) error { + return im.contractKeeper.IBCReceivePacketCallback(cachedCtx, packet, ack, callbackData.CallbackAddress, callbackData.ApplicationVersion) + } + + // callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions + err = internal.ProcessCallback(ctx, types.CallbackTypeReceivePacket, callbackData, callbackExecutor) + types.EmitCallbackEvent( + ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), + types.CallbackTypeReceivePacket, callbackData, err, + ) + + return nil +} + +// OnChanOpenInit defers to the underlying application +func (im IBCMiddleware) OnChanOpenInit( + ctx sdk.Context, + channelOrdering channeltypes.Order, + connectionHops []string, + portID, + channelID string, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + return im.app.OnChanOpenInit(ctx, channelOrdering, connectionHops, portID, channelID, counterparty, version) +} + +// OnChanOpenTry defers to the underlying application +func (im IBCMiddleware) OnChanOpenTry( + ctx sdk.Context, + channelOrdering channeltypes.Order, + connectionHops []string, portID, + channelID string, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + return im.app.OnChanOpenTry(ctx, channelOrdering, connectionHops, portID, channelID, counterparty, counterpartyVersion) +} + +// OnChanOpenAck defers to the underlying application +func (im IBCMiddleware) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID, + counterpartyChannelID, + counterpartyVersion string, +) error { + return im.app.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) +} + +// OnChanOpenConfirm defers to the underlying application +func (im IBCMiddleware) OnChanOpenConfirm(ctx sdk.Context, portID, channelID string) error { + return im.app.OnChanOpenConfirm(ctx, portID, channelID) +} + +// OnChanCloseInit defers to the underlying application +func (im IBCMiddleware) OnChanCloseInit(ctx sdk.Context, portID, channelID string) error { + return im.app.OnChanCloseInit(ctx, portID, channelID) +} + +// OnChanCloseConfirm defers to the underlying application +func (im IBCMiddleware) OnChanCloseConfirm(ctx sdk.Context, portID, channelID string) error { + return im.app.OnChanCloseConfirm(ctx, portID, channelID) +} + +// GetAppVersion implements the ICS4Wrapper interface. Callbacks has no version, +// so the call is deferred to the underlying application. +func (im IBCMiddleware) GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) { + return im.ics4Wrapper.GetAppVersion(ctx, portID, channelID) +} + +// UnmarshalPacketData defers to the underlying app to unmarshal the packet data. +// This function implements the optional PacketDataUnmarshaler interface. +func (im IBCMiddleware) UnmarshalPacketData(ctx sdk.Context, portID string, channelID string, bz []byte) (any, string, error) { + return im.app.UnmarshalPacketData(ctx, portID, channelID, bz) +} diff --git a/modules/apps/callbacks/ibc_middleware_test.go b/modules/apps/callbacks/ibc_middleware_test.go new file mode 100644 index 0000000..1975f08 --- /dev/null +++ b/modules/apps/callbacks/ibc_middleware_test.go @@ -0,0 +1,1143 @@ +package ibccallbacks_test + +import ( + "encoding/json" + "errors" + "fmt" + + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + icacontrollertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + ibccallbacks "github.com/cosmos/ibc-go/v10/modules/apps/callbacks" + "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/internal" + "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/testing/simapp" + "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/types" + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channelkeeper "github.com/cosmos/ibc-go/v10/modules/core/04-channel/keeper" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + ibcmock "github.com/cosmos/ibc-go/v10/testing/mock" +) + +func (s *CallbacksTestSuite) TestNewIBCMiddleware() { + testCases := []struct { + name string + instantiateFn func() + expError error + }{ + { + "success", + func() { + _ = ibccallbacks.NewIBCMiddleware(ibcmock.IBCModule{}, &channelkeeper.Keeper{}, simapp.ContractKeeper{}, maxCallbackGas) + }, + nil, + }, + { + "panics with nil underlying app", + func() { + _ = ibccallbacks.NewIBCMiddleware(nil, &channelkeeper.Keeper{}, simapp.ContractKeeper{}, maxCallbackGas) + }, + fmt.Errorf("underlying application does not implement %T", (*types.CallbacksCompatibleModule)(nil)), + }, + { + "panics with nil contract keeper", + func() { + _ = ibccallbacks.NewIBCMiddleware(ibcmock.IBCModule{}, &channelkeeper.Keeper{}, nil, maxCallbackGas) + }, + errors.New("contract keeper cannot be nil"), + }, + { + "panics with nil ics4Wrapper", + func() { + _ = ibccallbacks.NewIBCMiddleware(ibcmock.IBCModule{}, nil, simapp.ContractKeeper{}, maxCallbackGas) + }, + errors.New("ICS4Wrapper cannot be nil"), + }, + { + "panics with zero maxCallbackGas", + func() { + _ = ibccallbacks.NewIBCMiddleware(ibcmock.IBCModule{}, &channelkeeper.Keeper{}, simapp.ContractKeeper{}, uint64(0)) + }, + errors.New("maxCallbackGas cannot be zero"), + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + if tc.expError == nil { + s.Require().NotPanics(tc.instantiateFn, "unexpected panic: NewIBCMiddleware") + } else { + s.Require().PanicsWithError(tc.expError.Error(), tc.instantiateFn, "expected panic with error: ", tc.expError.Error()) + } + }) + } +} + +func (s *CallbacksTestSuite) TestWithICS4Wrapper() { + s.setupChains() + + cbsMiddleware := ibccallbacks.IBCMiddleware{} + s.Require().Nil(cbsMiddleware.GetICS4Wrapper()) + + cbsMiddleware.WithICS4Wrapper(s.chainA.App.GetIBCKeeper().ChannelKeeper) + ics4Wrapper := cbsMiddleware.GetICS4Wrapper() + + s.Require().IsType((*channelkeeper.Keeper)(nil), ics4Wrapper) +} + +func (s *CallbacksTestSuite) TestSendPacket() { + var packetData transfertypes.FungibleTokenPacketData + var callbackExecuted bool + + testCases := []struct { + name string + malleate func() + callbackType types.CallbackType + expPanic bool + expValue any + }{ + { + "success", + func() {}, + types.CallbackTypeSendPacket, + false, + nil, + }, + { + "success: callback data doesn't exist", + func() { + //nolint:goconst + packetData.Memo = "" + }, + "none", // nonexistent callback data should result in no callback execution + false, + nil, + }, + { + "failure: callback data is not valid", + func() { + //nolint:goconst + packetData.Memo = `{"src_callback": {"address": ""}}` + }, + "none", // improperly formatted callback data should result in no callback execution + false, + types.ErrInvalidCallbackData, + }, + { + "failure: ics4Wrapper SendPacket call fails", + func() { + s.path.EndpointA.ChannelID = "invalid-channel" + }, + "none", // ics4wrapper failure should result in no callback execution + false, + channeltypes.ErrChannelNotFound, + }, + { + "failure: callback execution fails", + func() { + packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s"}}`, simapp.ErrorContract) + }, + types.CallbackTypeSendPacket, + false, + ibcmock.MockApplicationCallbackError, // execution failure on SendPacket should prevent packet sends + }, + { + "failure: callback execution reach out of gas panic, but sufficient gas provided", + func() { + packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"400000"}}`, simapp.OogPanicContract) + }, + types.CallbackTypeSendPacket, + true, + storetypes.ErrorOutOfGas{Descriptor: fmt.Sprintf("mock %s callback oog panic", types.CallbackTypeSendPacket)}, + }, + { + "failure: callback execution reach out of gas error, but sufficient gas provided", + func() { + packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"400000"}}`, simapp.OogErrorContract) + }, + types.CallbackTypeSendPacket, + false, + errorsmod.Wrapf(types.ErrCallbackOutOfGas, "ibc %s callback out of gas", types.CallbackTypeSendPacket), + }, + { + "failure: callback address invalid", + func() { + packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":%d}}`, 50) + callbackExecuted = false // callback should not be executed + }, + types.CallbackTypeSendPacket, + false, + types.ErrInvalidCallbackData, + }, + { + "failure: callback gas limit invalid", + func() { + packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":%d}}`, simapp.SuccessContract, 50) + callbackExecuted = false // callback should not be executed + }, + types.CallbackTypeSendPacket, + false, + types.ErrInvalidCallbackData, + }, + { + "failure: callback calldata invalid", + func() { + packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"%d", "calldata":%d}}`, simapp.SuccessContract, 50, 50) + callbackExecuted = false // callback should not be executed + }, + types.CallbackTypeSendPacket, + false, + types.ErrInvalidCallbackData, + }, + { + "failure: callback calldata hex invalid", + func() { + packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"%d", "calldata":"%s"}}`, simapp.SuccessContract, 50, "calldata") + callbackExecuted = false // callback should not be executed + }, + types.CallbackTypeSendPacket, + false, + types.ErrInvalidCallbackData, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTransferTest() + + transferICS4Wrapper := GetSimApp(s.chainA).TransferKeeper.GetICS4Wrapper() + + packetData = transfertypes.NewFungibleTokenPacketData( + ibctesting.TestCoin.Denom, + ibctesting.TestCoin.Amount.String(), + ibctesting.TestAccAddress, + ibctesting.TestAccAddress, + fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, simapp.SuccessContract), + ) + callbackExecuted = true + + tc.malleate() + + ctx := s.chainA.GetContext() + gasLimit := ctx.GasMeter().Limit() + + var ( + seq uint64 + err error + ) + sendPacket := func() { + seq, err = transferICS4Wrapper.SendPacket(ctx, s.path.EndpointA.ChannelConfig.PortID, s.path.EndpointA.ChannelID, s.chainB.GetTimeoutHeight(), 0, packetData.GetBytes()) + } + + expPass := tc.expValue == nil + switch { + case expPass: + sendPacket() + s.Require().Nil(err) + s.Require().Equal(uint64(1), seq) + + expEvent, exists := GetExpectedEvent( + ctx, transferICS4Wrapper.(porttypes.PacketDataUnmarshaler), gasLimit, packetData.GetBytes(), + s.path.EndpointA.ChannelConfig.PortID, s.path.EndpointA.ChannelID, seq, types.CallbackTypeSendPacket, nil, + ) + if exists { + s.Require().Contains(ctx.EventManager().Events().ToABCIEvents(), expEvent) + } + + case tc.expPanic: + s.Require().PanicsWithValue(tc.expValue, sendPacket) + + default: + sendPacket() + s.Require().ErrorIs(tc.expValue.(error), err) + s.Require().Equal(uint64(0), seq) + } + + if callbackExecuted { + s.AssertHasExecutedExpectedCallback(tc.callbackType, expPass) + } + }) + } +} + +func (s *CallbacksTestSuite) TestOnAcknowledgementPacket() { + type expResult uint8 + const ( + noExecution expResult = iota + callbackFailed + callbackSuccess + ) + + var ( + packetData transfertypes.FungibleTokenPacketData + packet channeltypes.Packet + ack []byte + ctx sdk.Context + userGasLimit uint64 + ) + + panicError := errors.New("panic error") + + testCases := []struct { + name string + malleate func() + expResult expResult + expError error + }{ + { + "success", + func() {}, + callbackSuccess, + nil, + }, + { + "success: callback data doesn't exist", + func() { + //nolint:goconst + packetData.Memo = "" + packet.Data = packetData.GetBytes() + }, + noExecution, + nil, + }, + { + "failure: underlying app OnAcknowledgePacket fails", + func() { + ack = []byte("invalid ack") + }, + noExecution, + ibcerrors.ErrUnknownRequest, + }, + { + "failure: no-op on callback data is not valid", + func() { + //nolint:goconst + packetData.Memo = `{"src_callback": {"address": ""}}` + packet.Data = packetData.GetBytes() + }, + noExecution, + types.ErrInvalidCallbackData, + }, + { + "failure: callback execution reach out of gas, but sufficient gas provided by relayer", + func() { + packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"%d"}}`, simapp.OogPanicContract, userGasLimit) + packet.Data = packetData.GetBytes() + }, + callbackFailed, + nil, + }, + { + "failure: callback execution panics on insufficient gas provided by relayer", + func() { + packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"%d"}}`, simapp.OogPanicContract, userGasLimit) + packet.Data = packetData.GetBytes() + + ctx = ctx.WithGasMeter(storetypes.NewGasMeter(300_000)) + }, + callbackFailed, + panicError, + }, + { + "failure: callback execution fails", + func() { + packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s"}}`, simapp.ErrorContract) + packet.Data = packetData.GetBytes() + }, + callbackFailed, + nil, // execution failure in OnAcknowledgement should not block acknowledgement processing + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTransferTest() + + userGasLimit = 600000 + packetData = transfertypes.NewFungibleTokenPacketData( + ibctesting.TestCoin.Denom, + ibctesting.TestCoin.Amount.String(), + ibctesting.TestAccAddress, + ibctesting.TestAccAddress, + fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"%d"}}`, simapp.SuccessContract, userGasLimit), + ) + + packet = channeltypes.Packet{ + Sequence: 1, + SourcePort: s.path.EndpointA.ChannelConfig.PortID, + SourceChannel: s.path.EndpointA.ChannelID, + DestinationPort: s.path.EndpointB.ChannelConfig.PortID, + DestinationChannel: s.path.EndpointB.ChannelID, + Data: packetData.GetBytes(), + TimeoutHeight: s.chainB.GetTimeoutHeight(), + TimeoutTimestamp: 0, + } + + ack = channeltypes.NewResultAcknowledgement([]byte{1}).Acknowledgement() + + ctx = s.chainA.GetContext() + gasLimit := ctx.GasMeter().Limit() + + tc.malleate() + + // callbacks module is routed as top level middleware + transferStack, ok := s.chainA.App.GetIBCKeeper().PortKeeper.Route(transfertypes.ModuleName) + s.Require().True(ok) + + onAcknowledgementPacket := func() error { + return transferStack.OnAcknowledgementPacket(ctx, s.path.EndpointA.GetChannel().Version, packet, ack, s.chainA.SenderAccount.GetAddress()) + } + + switch tc.expError { + case nil: + err := onAcknowledgementPacket() + s.Require().Nil(err) + + case panicError: + s.Require().PanicsWithValue(storetypes.ErrorOutOfGas{ + Descriptor: fmt.Sprintf("ibc %s callback out of gas; commitGasLimit: %d", types.CallbackTypeAcknowledgementPacket, userGasLimit), + }, func() { + _ = onAcknowledgementPacket() + }) + + default: + err := onAcknowledgementPacket() + s.Require().ErrorIs(err, tc.expError) + } + + sourceStatefulCounter := GetSimApp(s.chainA).MockContractKeeper.GetStateEntryCounter(s.chainA.GetContext()) + sourceCounters := GetSimApp(s.chainA).MockContractKeeper.Counters + + switch tc.expResult { + case noExecution: + s.Require().Len(sourceCounters, 0) + s.Require().Equal(uint8(0), sourceStatefulCounter) + + case callbackFailed: + s.Require().Len(sourceCounters, 1) + s.Require().Equal(1, sourceCounters[types.CallbackTypeAcknowledgementPacket]) + s.Require().Equal(uint8(0), sourceStatefulCounter) + + case callbackSuccess: + s.Require().Len(sourceCounters, 1) + s.Require().Equal(1, sourceCounters[types.CallbackTypeAcknowledgementPacket]) + s.Require().Equal(uint8(1), sourceStatefulCounter) + + expEvent, exists := GetExpectedEvent( + ctx, transferStack.(porttypes.PacketDataUnmarshaler), gasLimit, packet.Data, + packet.SourcePort, packet.SourceChannel, packet.Sequence, types.CallbackTypeAcknowledgementPacket, nil, + ) + s.Require().True(exists) + s.Require().Contains(ctx.EventManager().Events().ToABCIEvents(), expEvent) + } + }) + } +} + +func (s *CallbacksTestSuite) TestOnTimeoutPacket() { + type expResult uint8 + const ( + noExecution expResult = iota + callbackFailed + callbackSuccess + ) + + var ( + packetData transfertypes.FungibleTokenPacketData + packet channeltypes.Packet + ctx sdk.Context + ) + + testCases := []struct { + name string + malleate func() + expResult expResult + expValue any + }{ + { + "success", + func() {}, + callbackSuccess, + nil, + }, + { + "success: callback data doesn't exist", + func() { + //nolint:goconst + packetData.Memo = "" + packet.Data = packetData.GetBytes() + }, + noExecution, + nil, + }, + { + "failure: underlying app OnTimeoutPacket fails", + func() { + packet.Data = []byte("invalid packet data") + }, + noExecution, + ibcerrors.ErrInvalidType, + }, + { + "failure: no-op on callback data is not valid", + func() { + //nolint:goconst + packetData.Memo = `{"src_callback": {"address": ""}}` + packet.Data = packetData.GetBytes() + }, + noExecution, + types.ErrInvalidCallbackData, + }, + { + "failure: callback execution reach out of gas, but sufficient gas provided by relayer", + func() { + packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"400000"}}`, simapp.OogPanicContract) + packet.Data = packetData.GetBytes() + }, + callbackFailed, + nil, + }, + { + "failure: callback execution panics on insufficient gas provided by relayer", + func() { + packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s"}}`, simapp.OogPanicContract) + packet.Data = packetData.GetBytes() + + ctx = ctx.WithGasMeter(storetypes.NewGasMeter(300_000)) + }, + callbackFailed, + storetypes.ErrorOutOfGas{ + Descriptor: fmt.Sprintf("ibc %s callback out of gas; commitGasLimit: %d", types.CallbackTypeTimeoutPacket, maxCallbackGas), + }, + }, + { + "failure: callback execution fails", + func() { + packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s"}}`, simapp.ErrorContract) + packet.Data = packetData.GetBytes() + }, + callbackFailed, + nil, // execution failure in OnTimeout should not block timeout processing + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTransferTest() + + // NOTE: we call send packet so transfer is setup with the correct logic to + // succeed on timeout + userGasLimit := 600_000 + timeoutTimestamp := uint64(s.chainB.GetContext().BlockTime().UnixNano()) + msg := transfertypes.NewMsgTransfer( + s.path.EndpointA.ChannelConfig.PortID, s.path.EndpointA.ChannelID, + ibctesting.TestCoin, s.chainA.SenderAccount.GetAddress().String(), + s.chainB.SenderAccount.GetAddress().String(), clienttypes.ZeroHeight(), timeoutTimestamp, + fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"%d"}}`, ibctesting.TestAccAddress, userGasLimit), // set user gas limit above panic level in mock contract keeper + ) + + res, err := s.chainA.SendMsgs(msg) + s.Require().NoError(err) + s.Require().NotNil(res) + + packet, err = ibctesting.ParseV1PacketFromEvents(res.GetEvents()) + s.Require().NoError(err) + s.Require().NotNil(packet) + + err = json.Unmarshal(packet.Data, &packetData) + s.Require().NoError(err) + + ctx = s.chainA.GetContext() + gasLimit := ctx.GasMeter().Limit() + + tc.malleate() + + // callbacks module is routed as top level middleware + transferStack, ok := s.chainA.App.GetIBCKeeper().PortKeeper.Route(transfertypes.ModuleName) + s.Require().True(ok) + + onTimeoutPacket := func() error { + return transferStack.OnTimeoutPacket(ctx, s.path.EndpointA.GetChannel().Version, packet, s.chainA.SenderAccount.GetAddress()) + } + + switch expValue := tc.expValue.(type) { + case nil: + err := onTimeoutPacket() + s.Require().Nil(err) + case error: + err := onTimeoutPacket() + s.Require().ErrorIs(err, expValue) + default: + s.Require().PanicsWithValue(tc.expValue, func() { + _ = onTimeoutPacket() + }) + } + + sourceStatefulCounter := GetSimApp(s.chainA).MockContractKeeper.GetStateEntryCounter(s.chainA.GetContext()) + sourceCounters := GetSimApp(s.chainA).MockContractKeeper.Counters + + // account for SendPacket succeeding + switch tc.expResult { + case noExecution: + s.Require().Len(sourceCounters, 1) + s.Require().Equal(uint8(1), sourceStatefulCounter) + + case callbackFailed: + s.Require().Len(sourceCounters, 2) + s.Require().Equal(1, sourceCounters[types.CallbackTypeTimeoutPacket]) + s.Require().Equal(1, sourceCounters[types.CallbackTypeSendPacket]) + s.Require().Equal(uint8(1), sourceStatefulCounter) + + case callbackSuccess: + s.Require().Len(sourceCounters, 2) + s.Require().Equal(1, sourceCounters[types.CallbackTypeTimeoutPacket]) + s.Require().Equal(1, sourceCounters[types.CallbackTypeSendPacket]) + s.Require().Equal(uint8(2), sourceStatefulCounter) + + expEvent, exists := GetExpectedEvent( + ctx, transferStack.(porttypes.PacketDataUnmarshaler), gasLimit, packet.Data, + packet.SourcePort, packet.SourceChannel, packet.Sequence, types.CallbackTypeTimeoutPacket, nil, + ) + s.Require().True(exists) + s.Require().Contains(ctx.EventManager().Events().ToABCIEvents(), expEvent) + } + }) + } +} + +func (s *CallbacksTestSuite) TestOnRecvPacket() { + type expResult uint8 + const ( + noExecution expResult = iota + callbackFailed + callbackSuccess + ) + + var ( + packetData transfertypes.FungibleTokenPacketData + packet channeltypes.Packet + ctx sdk.Context + userGasLimit uint64 + ) + + successAck := channeltypes.NewResultAcknowledgement([]byte{byte(1)}) + panicAck := channeltypes.NewErrorAcknowledgement(errors.New("panic")) + + testCases := []struct { + name string + malleate func() + expResult expResult + expAck ibcexported.Acknowledgement + }{ + { + "success", + func() {}, + callbackSuccess, + successAck, + }, + { + "success: callback data doesn't exist", + func() { + //nolint:goconst + packetData.Memo = "" + packet.Data = packetData.GetBytes() + }, + noExecution, + successAck, + }, + { + "failure: underlying app OnRecvPacket fails", + func() { + packet.Data = []byte("invalid packet data") + }, + noExecution, + channeltypes.NewErrorAcknowledgement(ibcerrors.ErrInvalidType), + }, + { + "failure: callback data is not valid", + func() { + //nolint:goconst + packetData.Memo = `{"dest_callback": {"address": ""}}` + packet.Data = packetData.GetBytes() + }, + noExecution, + channeltypes.NewErrorAcknowledgement(types.ErrInvalidCallbackData), + }, + { + "failure: callback execution reach out of gas, but sufficient gas provided by relayer", + func() { + packetData.Memo = fmt.Sprintf(`{"dest_callback": {"address":"%s", "gas_limit":"%d"}}`, simapp.OogPanicContract, userGasLimit) + packet.Data = packetData.GetBytes() + }, + callbackFailed, + successAck, + }, + { + "failure: callback execution panics on insufficient gas provided by relayer", + func() { + packetData.Memo = fmt.Sprintf(`{"dest_callback": {"address":"%s", "gas_limit":"%d"}}`, simapp.OogPanicContract, userGasLimit) + packet.Data = packetData.GetBytes() + + ctx = ctx.WithGasMeter(storetypes.NewGasMeter(300_000)) + }, + callbackFailed, + panicAck, + }, + { + "failure: callback execution fails", + func() { + packetData.Memo = fmt.Sprintf(`{"dest_callback": {"address":"%s"}}`, simapp.ErrorContract) + packet.Data = packetData.GetBytes() + }, + callbackFailed, + successAck, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTransferTest() + + // set user gas limit above panic level in mock contract keeper + userGasLimit = 600_000 + packetData = transfertypes.NewFungibleTokenPacketData( + ibctesting.TestCoin.Denom, + ibctesting.TestCoin.Amount.String(), + ibctesting.TestAccAddress, + s.chainB.SenderAccount.GetAddress().String(), + fmt.Sprintf(`{"dest_callback": {"address":"%s", "gas_limit":"%d"}}`, ibctesting.TestAccAddress, userGasLimit), + ) + + packet = channeltypes.Packet{ + Sequence: 1, + SourcePort: s.path.EndpointA.ChannelConfig.PortID, + SourceChannel: s.path.EndpointA.ChannelID, + DestinationPort: s.path.EndpointB.ChannelConfig.PortID, + DestinationChannel: s.path.EndpointB.ChannelID, + Data: packetData.GetBytes(), + TimeoutHeight: s.chainB.GetTimeoutHeight(), + TimeoutTimestamp: 0, + } + + ctx = s.chainB.GetContext() + gasLimit := ctx.GasMeter().Limit() + + tc.malleate() + + // callbacks module is routed as top level middleware + transferStack, ok := s.chainB.App.GetIBCKeeper().PortKeeper.Route(transfertypes.ModuleName) + s.Require().True(ok) + + onRecvPacket := func() ibcexported.Acknowledgement { + return transferStack.OnRecvPacket(ctx, s.path.EndpointA.GetChannel().Version, packet, s.chainB.SenderAccount.GetAddress()) + } + + switch tc.expAck { + case successAck: + ack := onRecvPacket() + s.Require().NotNil(ack) + + case panicAck: + s.Require().PanicsWithValue(storetypes.ErrorOutOfGas{ + Descriptor: fmt.Sprintf("ibc %s callback out of gas; commitGasLimit: %d", types.CallbackTypeReceivePacket, userGasLimit), + }, func() { + _ = onRecvPacket() + }) + + default: + ack := onRecvPacket() + s.Require().Equal(tc.expAck, ack) + } + + destStatefulCounter := GetSimApp(s.chainB).MockContractKeeper.GetStateEntryCounter(s.chainB.GetContext()) + destCounters := GetSimApp(s.chainB).MockContractKeeper.Counters + + switch tc.expResult { + case noExecution: + s.Require().Len(destCounters, 0) + s.Require().Equal(uint8(0), destStatefulCounter) + + case callbackFailed: + s.Require().Len(destCounters, 1) + s.Require().Equal(1, destCounters[types.CallbackTypeReceivePacket]) + s.Require().Equal(uint8(0), destStatefulCounter) + + case callbackSuccess: + s.Require().Len(destCounters, 1) + s.Require().Equal(1, destCounters[types.CallbackTypeReceivePacket]) + s.Require().Equal(uint8(1), destStatefulCounter) + + expEvent, exists := GetExpectedEvent( + ctx, transferStack.(porttypes.PacketDataUnmarshaler), gasLimit, packet.Data, + packet.DestinationPort, packet.DestinationChannel, packet.Sequence, types.CallbackTypeReceivePacket, nil, + ) + s.Require().True(exists) + s.Require().Contains(ctx.EventManager().Events().ToABCIEvents(), expEvent) + } + }) + } +} + +func (s *CallbacksTestSuite) TestWriteAcknowledgement() { + var ( + packetData transfertypes.FungibleTokenPacketData + packet channeltypes.Packet + ctx sdk.Context + ack ibcexported.Acknowledgement + ) + + successAck := channeltypes.NewResultAcknowledgement([]byte{byte(1)}) + + testCases := []struct { + name string + malleate func() + callbackType types.CallbackType + expError error + }{ + { + "success", + func() { + ack = successAck + }, + types.CallbackTypeReceivePacket, + nil, + }, + { + "success: callback data doesn't exist", + func() { + //nolint:goconst + packetData.Memo = "" + packet.Data = packetData.GetBytes() + }, + "none", // nonexistent callback data should result in no callback execution + nil, + }, + { + "failure: callback data is not valid", + func() { + packetData.Memo = `{"dest_callback": {"address": ""}}` + packet.Data = packetData.GetBytes() + }, + "none", // improperly formatted callback data should result in no callback execution + types.ErrInvalidCallbackData, + }, + { + "failure: ics4Wrapper WriteAcknowledgement call fails", + func() { + packet.DestinationChannel = "invalid-channel" + }, + "none", + channeltypes.ErrChannelNotFound, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTransferTest() + + // set user gas limit above panic level in mock contract keeper + packetData = transfertypes.NewFungibleTokenPacketData( + ibctesting.TestCoin.Denom, + ibctesting.TestCoin.Amount.String(), + ibctesting.TestAccAddress, + s.chainB.SenderAccount.GetAddress().String(), + fmt.Sprintf(`{"dest_callback": {"address":"%s", "gas_limit":"600000"}}`, ibctesting.TestAccAddress), + ) + + packet = channeltypes.Packet{ + Sequence: 1, + SourcePort: s.path.EndpointA.ChannelConfig.PortID, + SourceChannel: s.path.EndpointA.ChannelID, + DestinationPort: s.path.EndpointB.ChannelConfig.PortID, + DestinationChannel: s.path.EndpointB.ChannelID, + Data: packetData.GetBytes(), + TimeoutHeight: s.chainB.GetTimeoutHeight(), + TimeoutTimestamp: 0, + } + + ctx = s.chainB.GetContext() + gasLimit := ctx.GasMeter().Limit() + + tc.malleate() + + // callbacks module is routed as top level middleware + transferICS4Wrapper := GetSimApp(s.chainB).TransferKeeper.GetICS4Wrapper() + + err := transferICS4Wrapper.WriteAcknowledgement(ctx, packet, ack) + + expPass := tc.expError == nil + s.AssertHasExecutedExpectedCallback(tc.callbackType, expPass) + + if expPass { + s.Require().NoError(err) + + expEvent, exists := GetExpectedEvent( + ctx, transferICS4Wrapper.(porttypes.PacketDataUnmarshaler), gasLimit, packet.Data, + packet.DestinationPort, packet.DestinationChannel, packet.Sequence, types.CallbackTypeReceivePacket, nil, + ) + if exists { + s.Require().Contains(ctx.EventManager().Events().ToABCIEvents(), expEvent) + } + + } else { + s.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +func (s *CallbacksTestSuite) TestProcessCallback() { + var ( + callbackType types.CallbackType + callbackData types.CallbackData + ctx sdk.Context + callbackExecutor func(sdk.Context) error + expGasConsumed uint64 + ) + + callbackError := errors.New("callbackExecutor error") + + testCases := []struct { + name string + malleate func() + expPanic bool + expValue any + }{ + { + "success", + func() {}, + false, + nil, + }, + { + "success: callbackExecutor panic, but not out of gas", + func() { + callbackExecutor = func(cachedCtx sdk.Context) error { + cachedCtx.GasMeter().ConsumeGas(expGasConsumed, "callbackExecutor gas consumption") + panic("callbackExecutor panic") + } + }, + false, + errorsmod.Wrapf(types.ErrCallbackPanic, "ibc %s callback panicked with: %v", callbackType, "callbackExecutor panic"), + }, + { + "success: callbackExecutor oog panic, but retry is not allowed", + func() { + executionGas := callbackData.ExecutionGasLimit + expGasConsumed = executionGas + callbackExecutor = func(cachedCtx sdk.Context) error { //nolint:unparam + cachedCtx.GasMeter().ConsumeGas(expGasConsumed+1, "callbackExecutor gas consumption") + return nil + } + }, + false, + errorsmod.Wrapf(types.ErrCallbackOutOfGas, "ibc %s callback out of gas", callbackType), + }, + { + "failure: callbackExecutor error", + func() { + callbackExecutor = func(cachedCtx sdk.Context) error { + cachedCtx.GasMeter().ConsumeGas(expGasConsumed, "callbackExecutor gas consumption") + return callbackError + } + }, + false, + callbackError, + }, + { + "failure: callbackExecutor panic, not out of gas, and SendPacket", + func() { + callbackType = types.CallbackTypeSendPacket + callbackExecutor = func(cachedCtx sdk.Context) error { + cachedCtx.GasMeter().ConsumeGas(expGasConsumed, "callbackExecutor gas consumption") + panic("callbackExecutor panic") + } + }, + true, + "callbackExecutor panic", + }, + { + "failure: callbackExecutor oog panic, but retry is allowed", + func() { + executionGas := callbackData.ExecutionGasLimit + callbackData.CommitGasLimit = executionGas + 1 + expGasConsumed = executionGas + callbackExecutor = func(cachedCtx sdk.Context) error { //nolint:unparam + cachedCtx.GasMeter().ConsumeGas(executionGas+1, "callbackExecutor oog panic") + return nil + } + }, + true, + storetypes.ErrorOutOfGas{Descriptor: fmt.Sprintf("ibc %s callback out of gas; commitGasLimit: %d", types.CallbackTypeReceivePacket, 1000000+1)}, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.setupChains() + + // set a callback data that does not allow retry + callbackData = types.CallbackData{ + CallbackAddress: s.chainB.SenderAccount.GetAddress().String(), + ExecutionGasLimit: 1_000_000, + SenderAddress: s.chainB.SenderAccount.GetAddress().String(), + CommitGasLimit: 600_000, + } + + // this only makes a difference if it is SendPacket + callbackType = types.CallbackTypeReceivePacket + + // expGasConsumed can be overwritten in malleate + expGasConsumed = 300_000 + + ctx = s.chainB.GetContext() + + // set a callback executor that will always succeed after consuming expGasConsumed + callbackExecutor = func(cachedCtx sdk.Context) error { //nolint:unparam + cachedCtx.GasMeter().ConsumeGas(expGasConsumed, "callbackExecutor gas consumption") + return nil + } + + tc.malleate() + var err error + + processCallback := func() { + err = internal.ProcessCallback(ctx, callbackType, callbackData, callbackExecutor) + } + + expPass := tc.expValue == nil + switch { + case expPass: + processCallback() + s.Require().NoError(err) + case tc.expPanic: + s.Require().PanicsWithValue(tc.expValue, processCallback) + default: + processCallback() + s.Require().ErrorIs(err, tc.expValue.(error)) + } + + s.Require().Equal(expGasConsumed, ctx.GasMeter().GasConsumed()) + }) + } +} + +func (s *CallbacksTestSuite) TestUnmarshalPacketDataV1() { + s.setupChains() + s.path.EndpointA.ChannelConfig.PortID = ibctesting.TransferPort + s.path.EndpointB.ChannelConfig.PortID = ibctesting.TransferPort + s.path.EndpointA.ChannelConfig.Version = transfertypes.V1 + s.path.EndpointB.ChannelConfig.Version = transfertypes.V1 + s.path.Setup() + + // We will pass the function call down the transfer stack to the transfer module + // transfer stack UnmarshalPacketData call order: callbacks -> transfer + transferStack, ok := s.chainA.App.GetIBCKeeper().PortKeeper.Route(transfertypes.ModuleName) + s.Require().True(ok) + + unmarshalerStack, ok := transferStack.(types.CallbacksCompatibleModule) + s.Require().True(ok) + + expPacketDataICS20V1 := transfertypes.FungibleTokenPacketData{ + Denom: ibctesting.TestCoin.Denom, + Amount: ibctesting.TestCoin.Amount.String(), + Sender: ibctesting.TestAccAddress, + Receiver: ibctesting.TestAccAddress, + Memo: fmt.Sprintf(`{"src_callback": {"address": "%s"}, "dest_callback": {"address":"%s"}}`, ibctesting.TestAccAddress, ibctesting.TestAccAddress), + } + + expPacketDataICS20V2 := transfertypes.InternalTransferRepresentation{ + Token: transfertypes.Token{ + Denom: transfertypes.NewDenom(ibctesting.TestCoin.Denom), + Amount: ibctesting.TestCoin.Amount.String(), + }, + Sender: ibctesting.TestAccAddress, + Receiver: ibctesting.TestAccAddress, + Memo: fmt.Sprintf(`{"src_callback": {"address": "%s"}, "dest_callback": {"address":"%s"}}`, ibctesting.TestAccAddress, ibctesting.TestAccAddress), + } + + portID := s.path.EndpointA.ChannelConfig.PortID + channelID := s.path.EndpointA.ChannelID + + // Unmarshal ICS20 v1 packet data into v2 packet data + data := expPacketDataICS20V1.GetBytes() + packetData, version, err := unmarshalerStack.UnmarshalPacketData(s.chainA.GetContext(), portID, channelID, data) + s.Require().NoError(err) + s.Require().Equal(s.path.EndpointA.ChannelConfig.Version, version) + s.Require().Equal(expPacketDataICS20V2, packetData) +} + +func (s *CallbacksTestSuite) TestGetAppVersion() { + s.SetupICATest() + + // Obtain an IBC stack for testing. The function call will use the top of the stack which calls + // directly to the channel keeper. Calling from a further down module in the stack is not necessary + // for this test. + icaControllerStack, ok := s.chainA.App.GetIBCKeeper().PortKeeper.Route(icacontrollertypes.SubModuleName) + s.Require().True(ok) + + controllerStack, ok := icaControllerStack.(porttypes.ICS4Wrapper) + s.Require().True(ok) + appVersion, found := controllerStack.GetAppVersion(s.chainA.GetContext(), s.path.EndpointA.ChannelConfig.PortID, s.path.EndpointA.ChannelID) + s.Require().True(found) + s.Require().Equal(s.path.EndpointA.ChannelConfig.Version, appVersion) +} + +func (s *CallbacksTestSuite) TestOnChanCloseInit() { + s.SetupICATest() + + // We will pass the function call down the icacontroller stack to the icacontroller module + // icacontroller stack OnChanCloseInit call order: callbacks -> icacontroller + icaControllerStack, ok := s.chainA.App.GetIBCKeeper().PortKeeper.Route(icacontrollertypes.SubModuleName) + s.Require().True(ok) + + controllerStack, ok := icaControllerStack.(porttypes.Middleware) + s.Require().True(ok) + err := controllerStack.OnChanCloseInit(s.chainA.GetContext(), s.path.EndpointA.ChannelConfig.PortID, s.path.EndpointA.ChannelID) + // we just check that this call is passed down to the icacontroller to return an error + s.Require().ErrorIs(err, errorsmod.Wrap(ibcerrors.ErrInvalidRequest, "user cannot close channel")) +} + +func (s *CallbacksTestSuite) TestOnChanCloseConfirm() { + s.SetupICATest() + + // We will pass the function call down the icacontroller stack to the icacontroller module + // icacontroller stack OnChanCloseConfirm call order: callbacks -> icacontroller + icaControllerStack, ok := s.chainA.App.GetIBCKeeper().PortKeeper.Route(icacontrollertypes.SubModuleName) + s.Require().True(ok) + + controllerStack, ok := icaControllerStack.(porttypes.Middleware) + s.Require().True(ok) + err := controllerStack.OnChanCloseConfirm(s.chainA.GetContext(), s.path.EndpointA.ChannelConfig.PortID, s.path.EndpointA.ChannelID) + // we just check that this call is passed down to the icacontroller + s.Require().NoError(err) +} + +func (s *CallbacksTestSuite) TestOnRecvPacketAsyncAck() { + s.setupChains() + + cbs, ok := s.chainA.App.GetIBCKeeper().PortKeeper.Route(ibctesting.MockPort) + s.Require().True(ok) + + packet := channeltypes.NewPacket( + ibcmock.MockAsyncPacketData, + s.chainA.SenderAccount.GetSequence(), + s.path.EndpointA.ChannelConfig.PortID, + s.path.EndpointA.ChannelID, + s.path.EndpointB.ChannelConfig.PortID, + s.path.EndpointB.ChannelID, + clienttypes.NewHeight(0, 100), + 0, + ) + + ack := cbs.OnRecvPacket(s.chainA.GetContext(), ibcmock.Version, packet, s.chainA.SenderAccount.GetAddress()) + s.Require().Nil(ack) + s.AssertHasExecutedExpectedCallback("none", true) +} diff --git a/modules/apps/callbacks/ica_test.go b/modules/apps/callbacks/ica_test.go new file mode 100644 index 0000000..c87a5ad --- /dev/null +++ b/modules/apps/callbacks/ica_test.go @@ -0,0 +1,210 @@ +package ibccallbacks_test + +import ( + "fmt" + "time" + + "github.com/cosmos/gogoproto/proto" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + icacontrollertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + icahosttypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/testing/simapp" + "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (s *CallbacksTestSuite) TestICACallbacks() { + // Destination callbacks are not supported for ICA packets + testCases := []struct { + name string + icaMemo string + expCallback types.CallbackType + expSuccess bool + }{ + { + "success: send ica tx with no memo", + "", + "none", + true, + }, + { + "success: dest callback", + fmt.Sprintf(`{"dest_callback": {"address": "%s"}}`, simapp.SuccessContract), + "none", + true, + }, + { + "success: source callback", + fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, simapp.SuccessContract), + types.CallbackTypeAcknowledgementPacket, + true, + }, + { + "success: source callback with other json fields", + fmt.Sprintf(`{"src_callback": {"address": "%s"}, "something_else": {}}`, simapp.SuccessContract), + types.CallbackTypeAcknowledgementPacket, + true, + }, + { + "success: source callback with malformed json", + fmt.Sprintf(`{"src_callback": {"address": "%s"}, malformed}`, simapp.SuccessContract), + "none", + true, + }, + { + "success: source callback with missing address", + `{"src_callback": {"address": ""}}`, + "none", + true, + }, + { + "failure: source callback with low gas (panic)", + fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, simapp.OogPanicContract), + types.CallbackTypeSendPacket, + false, + }, + { + "failure: source callback with low gas (error)", + fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, simapp.OogErrorContract), + types.CallbackTypeSendPacket, + false, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + icaAddr := s.SetupICATest() + + s.ExecuteICATx(icaAddr, tc.icaMemo) + s.AssertHasExecutedExpectedCallback(tc.expCallback, tc.expSuccess) + }) + } +} + +func (s *CallbacksTestSuite) TestICATimeoutCallbacks() { + // ICA channels are closed after a timeout packet is executed + testCases := []struct { + name string + icaMemo string + expCallback types.CallbackType + expSuccess bool + }{ + { + "success: send ica tx timeout with no memo", + "", + "none", + true, + }, + { + "success: dest callback", + fmt.Sprintf(`{"dest_callback": {"address": "%s"}}`, simapp.SuccessContract), + "none", + true, + }, + { + "success: source callback", + fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, simapp.SuccessContract), + types.CallbackTypeTimeoutPacket, + true, + }, + { + "failure: source callback with low gas (panic)", + fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, simapp.OogPanicContract), + types.CallbackTypeSendPacket, + false, + }, + { + "failure: source callback with low gas (error)", + fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, simapp.OogErrorContract), + types.CallbackTypeSendPacket, + false, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + icaAddr := s.SetupICATest() + + s.ExecuteICATimeout(icaAddr, tc.icaMemo) + s.AssertHasExecutedExpectedCallback(tc.expCallback, tc.expSuccess) + }) + } +} + +// ExecuteICATx executes a stakingtypes.MsgDelegate on chainB by sending a packet containing the msg to chainB +func (s *CallbacksTestSuite) ExecuteICATx(icaAddress, memo string) { + timeoutTimestamp := uint64(s.chainA.GetContext().BlockTime().Add(time.Minute).UnixNano()) + icaOwner := s.chainA.SenderAccount.GetAddress().String() + connectionID := s.path.EndpointA.ConnectionID + // build the interchain accounts packet data + packetData := s.buildICAMsgDelegatePacketData(icaAddress, memo) + msg := icacontrollertypes.NewMsgSendTx(icaOwner, connectionID, timeoutTimestamp, packetData) + + res, err := s.chainA.SendMsgs(msg) + if err != nil { + return // we return if send packet is rejected + } + + packet, err := ibctesting.ParseV1PacketFromEvents(res.GetEvents()) + s.Require().NoError(err) + + err = s.path.RelayPacket(packet) + s.Require().NoError(err) +} + +// ExecuteICATimeout sends and times out an ICA tx +func (s *CallbacksTestSuite) ExecuteICATimeout(icaAddress, memo string) { + relativeTimeout := uint64(1) + icaOwner := s.chainA.SenderAccount.GetAddress().String() + connectionID := s.path.EndpointA.ConnectionID + // build the interchain accounts packet data + packetData := s.buildICAMsgDelegatePacketData(icaAddress, memo) + msg := icacontrollertypes.NewMsgSendTx(icaOwner, connectionID, relativeTimeout, packetData) + + res, err := s.chainA.SendMsgs(msg) + if err != nil { + return // we return if send packet is rejected + } + + packet, err := ibctesting.ParseV1PacketFromEvents(res.GetEvents()) + s.Require().NoError(err) + + // proof query requires up to date client + err = s.path.EndpointA.UpdateClient() + s.Require().NoError(err) + + err = s.path.EndpointA.TimeoutPacket(packet) + s.Require().NoError(err) +} + +// buildICAMsgDelegatePacketData builds a packetData containing a stakingtypes.MsgDelegate to be executed on chainB +func (s *CallbacksTestSuite) buildICAMsgDelegatePacketData(icaAddress string, memo string) icatypes.InterchainAccountPacketData { + // prepare a simple stakingtypes.MsgDelegate to be used as the interchain account msg executed on chainB + validatorAddr := (sdk.ValAddress)(s.chainB.Vals.Validators[0].Address) + msgDelegate := &stakingtypes.MsgDelegate{ + DelegatorAddress: icaAddress, + ValidatorAddress: validatorAddr.String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(5000)), + } + + // ensure chainB is allowed to execute stakingtypes.MsgDelegate + params := icahosttypes.NewParams(true, []string{sdk.MsgTypeURL(msgDelegate)}) + GetSimApp(s.chainB).ICAHostKeeper.SetParams(s.chainB.GetContext(), params) + + data, err := icatypes.SerializeCosmosTx(GetSimApp(s.chainA).AppCodec(), []proto.Message{msgDelegate}, icatypes.EncodingProtobuf) + s.Require().NoError(err) + + icaPacketData := icatypes.InterchainAccountPacketData{ + Type: icatypes.EXECUTE_TX, + Data: data, + Memo: memo, + } + + return icaPacketData +} diff --git a/modules/apps/callbacks/internal/process.go b/modules/apps/callbacks/internal/process.go new file mode 100644 index 0000000..f00d7cb --- /dev/null +++ b/modules/apps/callbacks/internal/process.go @@ -0,0 +1,61 @@ +package internal + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/types" +) + +// ProcessCallback executes the callbackExecutor and reverts contract changes if the callbackExecutor fails. +// +// Error Precedence and Returns: +// - oogErr: Takes the highest precedence. If the callback runs out of gas, an error wrapped with types.ErrCallbackOutOfGas is returned. +// - panicErr: Takes the second-highest precedence. If a panic occurs and it is not propagated, an error wrapped with types.ErrCallbackPanic is returned. +// - callbackErr: If the callbackExecutor returns an error, it is returned as-is. +// +// panics if +// - the contractExecutor panics for any reason, and the callbackType is SendPacket, or +// - the contractExecutor runs out of gas and the relayer has not reserved gas grater than or equal to +// CommitGasLimit. +func ProcessCallback( + ctx sdk.Context, callbackType types.CallbackType, + callbackData types.CallbackData, callbackExecutor func(sdk.Context) error, +) (err error) { + cachedCtx, writeFn := ctx.CacheContext() + cachedCtx = cachedCtx.WithGasMeter(storetypes.NewGasMeter(callbackData.ExecutionGasLimit)) + + defer func() { + // consume the minimum of g.consumed and g.limit + ctx.GasMeter().ConsumeGas(cachedCtx.GasMeter().GasConsumedToLimit(), fmt.Sprintf("ibc %s callback", callbackType)) + + // recover from all panics except during SendPacket callbacks + if r := recover(); r != nil { + if callbackType == types.CallbackTypeSendPacket { + panic(r) + } + err = errorsmod.Wrapf(types.ErrCallbackPanic, "ibc %s callback panicked with: %v", callbackType, r) + } + + // if the callback ran out of gas and the relayer has not reserved enough gas, then revert the state + if cachedCtx.GasMeter().IsPastLimit() { + if callbackData.AllowRetry() { + panic(storetypes.ErrorOutOfGas{Descriptor: fmt.Sprintf("ibc %s callback out of gas; commitGasLimit: %d", callbackType, callbackData.CommitGasLimit)}) + } + err = errorsmod.Wrapf(types.ErrCallbackOutOfGas, "ibc %s callback out of gas", callbackType) + } + + // allow the transaction to be committed, continuing the packet lifecycle + }() + + err = callbackExecutor(cachedCtx) + if err == nil { + writeFn() + } + + return err +} diff --git a/modules/apps/callbacks/replay_test.go b/modules/apps/callbacks/replay_test.go new file mode 100644 index 0000000..6e2a1b8 --- /dev/null +++ b/modules/apps/callbacks/replay_test.go @@ -0,0 +1,344 @@ +package ibccallbacks_test + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/testing/simapp" + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (s *CallbacksTestSuite) TestTransferTimeoutReplayProtection() { + testCases := []struct { + name string + transferMemo string + }{ + { + "success: REPLAY TIMEOUT", + fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, simapp.SuccessContract), + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTransferTest() + + callbackCount := 0 // used to count the number of times the timeout callback is called. + + // This simulates a contract which submits a replay timeout packet: + GetSimApp(s.chainA).MockContractKeeper.IBCOnTimeoutPacketCallbackFn = func( + cachedCtx sdk.Context, + packet channeltypes.Packet, + _ sdk.AccAddress, + _, _, _ string, + ) error { + // only replay the timeout packet twice. We could replay it more times + callbackCount++ + if callbackCount == 2 { + return nil + } + + // construct the timeoutMsg + packetKey := host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + counterparty := s.path.EndpointA.Counterparty + proof, proofHeight := counterparty.QueryProof(packetKey) + nextSeqRecv, found := counterparty.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextSequenceRecv(counterparty.Chain.GetContext(), counterparty.ChannelConfig.PortID, counterparty.ChannelID) + s.Require().True(found) + + timeoutMsg := channeltypes.NewMsgTimeout( + packet, nextSeqRecv, + proof, proofHeight, s.chainA.SenderAccount.GetAddress().String(), + ) + + // in a real scenario, this should be s.chainA.SendMsg + // but I couldn't get it to work due to the way our testsuite is setup + // (we increment the account sequence after full block execution) + // (we also don't support sending messages from other accounts) + res, err := GetSimApp(s.chainA).IBCKeeper.Timeout(cachedCtx, timeoutMsg) + s.Require().NoError(err) + s.Require().Equal(channeltypes.NOOP, res.Result) + + return nil + } + + // fund escrow account + fund := ibctesting.TestCoins.Add(ibctesting.TestCoin).Add(ibctesting.TestCoin) + escrowAddress := transfertypes.GetEscrowAddress(s.path.EndpointA.ChannelConfig.PortID, s.path.EndpointA.ChannelID) + err := GetSimApp(s.chainA).BankKeeper.SendCoins(s.chainA.GetContext(), s.chainA.SenderAccount.GetAddress(), escrowAddress, fund) + s.Require().NoError(err) + + // set total escrow for denom + GetSimApp(s.chainA).TransferKeeper.SetTotalEscrowForDenom(s.chainA.GetContext(), fund[0]) + + // save initial balance of sender + initialBalance := GetSimApp(s.chainA).BankKeeper.GetBalance(s.chainA.GetContext(), s.chainA.SenderAccount.GetAddress(), sdk.DefaultBondDenom) + + // amountTransferred := ibctesting.TestCoin + s.ExecuteTransferTimeout(tc.transferMemo) + + // check that the callback is executed 1 times + s.Require().Equal(1, callbackCount) + + afterBalance := GetSimApp(s.chainA).BankKeeper.GetBalance(s.chainA.GetContext(), s.chainA.SenderAccount.GetAddress(), sdk.DefaultBondDenom) + + // expected is not a malicious amount + s.Require().Equal(initialBalance.Amount, afterBalance.Amount) + }) + } +} + +func (s *CallbacksTestSuite) TestTransferErrorAcknowledgementReplayProtection() { + testCases := []struct { + name string + transferMemo string + }{ + { + "success: REPLAY ERROR ACK", + fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, simapp.SuccessContract), + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTransferTest() + + callbackCount := 0 // used to count the number of times the ack callback is called. + + // This simulates a contract which submits a replay ack packet: + GetSimApp(s.chainA).MockContractKeeper.IBCOnAcknowledgementPacketCallbackFn = func( + cachedCtx sdk.Context, + packet channeltypes.Packet, + ack []byte, + _ sdk.AccAddress, + _, _, _ string, + ) error { + // only replay the ack packet twice. We could replay it more times + callbackCount++ + if callbackCount == 2 { + return nil + } + + // construct the ackMsg + packetKey := host.PacketAcknowledgementKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + proof, proofHeight := s.path.EndpointA.Counterparty.QueryProof(packetKey) + + ackMsg := channeltypes.NewMsgAcknowledgement(packet, ack, proof, proofHeight, s.chainA.SenderAccount.GetAddress().String()) + + // in a real scenario, this should be s.chainA.SendMsg + // but I couldn't get it to work due to the way our testsuite is setup + // (we increment the account sequence after full block execution) + // (we also don't support sending messages from other accounts) + res, err := GetSimApp(s.chainA).IBCKeeper.Acknowledgement(cachedCtx, ackMsg) + s.Require().NoError(err) + s.Require().Equal(channeltypes.NOOP, res.Result) // no-op because this is a redundant replay + + return nil + } + + // fund escrow account + fund := ibctesting.TestCoins.Add(ibctesting.TestCoin).Add(ibctesting.TestCoin) + escrowAddress := transfertypes.GetEscrowAddress(s.path.EndpointA.ChannelConfig.PortID, s.path.EndpointA.ChannelID) + err := GetSimApp(s.chainA).BankKeeper.SendCoins(s.chainA.GetContext(), s.chainA.SenderAccount.GetAddress(), escrowAddress, fund) + s.Require().NoError(err) + + // set total escrow for denom + GetSimApp(s.chainA).TransferKeeper.SetTotalEscrowForDenom(s.chainA.GetContext(), fund[0]) + + // save initial balance of sender + initialBalance := GetSimApp(s.chainA).BankKeeper.GetBalance(s.chainA.GetContext(), s.chainA.SenderAccount.GetAddress(), sdk.DefaultBondDenom) + + // amountTransferred := ibctesting.TestCoin + s.ExecuteFailedTransfer(tc.transferMemo) + + // check that the callback is executed 1 times + s.Require().Equal(1, callbackCount) + + expBalance := initialBalance + + afterBalance := GetSimApp(s.chainA).BankKeeper.GetBalance(s.chainA.GetContext(), s.chainA.SenderAccount.GetAddress(), sdk.DefaultBondDenom) + + // expected is not a malicious amount + s.Require().Equal(expBalance.Amount, afterBalance.Amount) + }) + } +} + +func (s *CallbacksTestSuite) TestTransferSuccessAcknowledgementReplayProtection() { + testCases := []struct { + name string + transferMemo string + }{ + { + "success: REPLAY SUCCESS ACK", + fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, simapp.SuccessContract), + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTransferTest() + + callbackCount := 0 // used to count the number of times the ack callback is called. + + // This simulates a contract which submits a replay ack packet: + GetSimApp(s.chainA).MockContractKeeper.IBCOnAcknowledgementPacketCallbackFn = func( + cachedCtx sdk.Context, + packet channeltypes.Packet, + ack []byte, + _ sdk.AccAddress, + _, _, _ string, + ) error { + // only replay the ack packet twice. We could replay it more times + callbackCount++ + if callbackCount == 2 { + return nil + } + + // construct the ackMsg + packetKey := host.PacketAcknowledgementKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + proof, proofHeight := s.path.EndpointA.Counterparty.QueryProof(packetKey) + + ackMsg := channeltypes.NewMsgAcknowledgement(packet, ack, proof, proofHeight, s.chainA.SenderAccount.GetAddress().String()) + + // in a real scenario, this should be s.chainA.SendMsg + // but I couldn't get it to work due to the way our testsuite is setup + // (we increment the account sequence after full block execution) + // (we also don't support sending messages from other accounts) + res, err := GetSimApp(s.chainA).IBCKeeper.Acknowledgement(cachedCtx, ackMsg) + s.Require().NoError(err) + s.Require().Equal(channeltypes.NOOP, res.Result) // no-op because this is a redundant replay + + return nil + } + + // save initial balance of sender + initialBalance := GetSimApp(s.chainA).BankKeeper.GetBalance(s.chainA.GetContext(), s.chainA.SenderAccount.GetAddress(), sdk.DefaultBondDenom) + + // amountTransferred := ibctesting.TestCoin + s.ExecuteTransfer(tc.transferMemo, true) + + // check that the callback is executed 1 times + s.Require().Equal(1, callbackCount) + + expBalance := initialBalance.Sub(ibctesting.TestCoin) + + afterBalance := GetSimApp(s.chainA).BankKeeper.GetBalance(s.chainA.GetContext(), s.chainA.SenderAccount.GetAddress(), sdk.DefaultBondDenom) + + // expected is not a malicious amount + s.Require().Equal(expBalance.Amount, afterBalance.Amount) + }) + } +} + +func (s *CallbacksTestSuite) TestTransferRecvPacketReplayProtection() { + testCases := []struct { + name string + transferMemo string + }{ + { + "success: REPLAY RECV PACKET", + fmt.Sprintf(`{"dest_callback": {"address": "%s"}}`, simapp.SuccessContract), + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTransferTest() + + callbackCount := 0 // used to count the number of times the RecvPacket callback is called. + + // Write a contract in Chain B that tries to execute receive packet 2 times! + GetSimApp(s.chainB).MockContractKeeper.IBCReceivePacketCallbackFn = func( + cachedCtx sdk.Context, + packet ibcexported.PacketI, + _ ibcexported.Acknowledgement, + _, _ string, + ) error { + callbackCount++ + if callbackCount == 2 { + return nil + } + + packetKey := host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + proof, proofHeight := s.path.EndpointB.Counterparty.Chain.QueryProof(packetKey) + + recvMsg := channeltypes.NewMsgRecvPacket(packet.(channeltypes.Packet), proof, proofHeight, s.chainB.SenderAccount.GetAddress().String()) + + // send again + res, err := GetSimApp(s.chainB).IBCKeeper.RecvPacket(cachedCtx, recvMsg) + s.Require().NoError(err) + s.Require().Equal(channeltypes.NOOP, res.Result) // no-op because this is a redundant replay + + return nil + } + + // save initial balance of receiver + denom := transfertypes.NewDenom(sdk.DefaultBondDenom, transfertypes.NewHop(s.path.EndpointB.ChannelConfig.PortID, s.path.EndpointB.ChannelID)) + initialBalance := GetSimApp(s.chainB).BankKeeper.GetBalance(s.chainB.GetContext(), s.chainB.SenderAccount.GetAddress(), denom.IBCDenom()) + + // execute the transfer + s.ExecuteTransfer(tc.transferMemo, true) + + // check that the callback is executed 1 times + s.Require().Equal(1, callbackCount) + + // expected is not a malicious amount + expBalance := initialBalance.Add(sdk.NewCoin(denom.IBCDenom(), ibctesting.TestCoin.Amount)) + + afterBalance := GetSimApp(s.chainB).BankKeeper.GetBalance(s.chainB.GetContext(), s.chainB.SenderAccount.GetAddress(), denom.IBCDenom()) + + s.Require().Equal(expBalance.Amount, afterBalance.Amount) + }) + } +} + +// ExecuteFailedTransfer executes a transfer message on chainA for ibctesting.TestCoin (100 "stake"). +// The transfer will fail on RecvPacket and an error acknowledgement will be sent back to chainA. +func (s *CallbacksTestSuite) ExecuteFailedTransfer(memo string) { + GetSimApp(s.chainB).TransferKeeper.SetParams(s.chainB.GetContext(), transfertypes.Params{ + ReceiveEnabled: false, + SendEnabled: true, + }) + + defer GetSimApp(s.chainB).TransferKeeper.SetParams(s.chainB.GetContext(), transfertypes.DefaultParams()) + + escrowAddress := transfertypes.GetEscrowAddress(s.path.EndpointA.ChannelConfig.PortID, s.path.EndpointA.ChannelID) + // record the balance of the escrow address before the transfer + escrowBalance := GetSimApp(s.chainA).BankKeeper.GetBalance(s.chainA.GetContext(), escrowAddress, sdk.DefaultBondDenom) + // record the balance of the receiving address before the transfer + denom := transfertypes.NewDenom(sdk.DefaultBondDenom, transfertypes.NewHop(s.path.EndpointB.ChannelConfig.PortID, s.path.EndpointB.ChannelID)) + receiverBalance := GetSimApp(s.chainB).BankKeeper.GetBalance(s.chainB.GetContext(), s.chainB.SenderAccount.GetAddress(), denom.IBCDenom()) + + amount := ibctesting.TestCoin + msg := transfertypes.NewMsgTransfer( + s.path.EndpointA.ChannelConfig.PortID, + s.path.EndpointA.ChannelID, + amount, + s.chainA.SenderAccount.GetAddress().String(), + s.chainB.SenderAccount.GetAddress().String(), + clienttypes.NewHeight(1, 100), 0, memo, + ) + + res, err := s.chainA.SendMsgs(msg) + if err != nil { + return // we return if send packet is rejected + } + + packet, err := ibctesting.ParseV1PacketFromEvents(res.GetEvents()) + s.Require().NoError(err) + + // relay send + err = s.path.RelayPacket(packet) + s.Require().NoError(err) // relay committed + + // check that the escrow address balance hasn't changed + s.Require().Equal(escrowBalance, GetSimApp(s.chainA).BankKeeper.GetBalance(s.chainA.GetContext(), escrowAddress, sdk.DefaultBondDenom)) + // check that the receiving address balance hasn't changed + s.Require().Equal(receiverBalance, GetSimApp(s.chainB).BankKeeper.GetBalance(s.chainB.GetContext(), s.chainB.SenderAccount.GetAddress(), denom.IBCDenom())) +} diff --git a/modules/apps/callbacks/testing/simapp/README.md b/modules/apps/callbacks/testing/simapp/README.md new file mode 100644 index 0000000..95e3d96 --- /dev/null +++ b/modules/apps/callbacks/testing/simapp/README.md @@ -0,0 +1,5 @@ +# Callbacks Testing SimApp + +This testing directory is a duplicate of the ibc-go testing directory. +It is only here as a way of creating a separate SimApp binary to avoid introducing a dependency on the callbacks +module from within ibc-go. diff --git a/modules/apps/callbacks/testing/simapp/ante_handler.go b/modules/apps/callbacks/testing/simapp/ante_handler.go new file mode 100644 index 0000000..012a71f --- /dev/null +++ b/modules/apps/callbacks/testing/simapp/ante_handler.go @@ -0,0 +1,52 @@ +package simapp + +import ( + "errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + + ibcante "github.com/cosmos/ibc-go/v10/modules/core/ante" + "github.com/cosmos/ibc-go/v10/modules/core/keeper" +) + +// HandlerOptions are the options required for constructing a default SDK AnteHandler. +type HandlerOptions struct { + ante.HandlerOptions + IBCKeeper *keeper.Keeper +} + +// NewAnteHandler returns an AnteHandler that checks and increments sequence +// numbers, checks signatures & account numbers, and deducts fees from the first +// signer. +func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { + if options.AccountKeeper == nil { + return nil, errors.New("account keeper is required for ante builder") + } + + if options.BankKeeper == nil { + return nil, errors.New("bank keeper is required for ante builder") + } + + if options.SignModeHandler == nil { + return nil, errors.New("sign mode handler is required for ante builder") + } + + anteDecorators := []sdk.AnteDecorator{ + ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first + ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker), + ante.NewValidateBasicDecorator(), + ante.NewTxTimeoutHeightDecorator(), + ante.NewValidateMemoDecorator(options.AccountKeeper), + ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), + ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker), + ante.NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators + ante.NewValidateSigCountDecorator(options.AccountKeeper), + ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer), + ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), + ante.NewIncrementSequenceDecorator(options.AccountKeeper), + ibcante.NewRedundantRelayDecorator(options.IBCKeeper), + } + + return sdk.ChainAnteDecorators(anteDecorators...), nil +} diff --git a/modules/apps/callbacks/testing/simapp/app.go b/modules/apps/callbacks/testing/simapp/app.go new file mode 100644 index 0000000..e02264a --- /dev/null +++ b/modules/apps/callbacks/testing/simapp/app.go @@ -0,0 +1,880 @@ +package simapp + +import ( + "encoding/json" + "fmt" + "io" + "maps" + "os" + "path/filepath" + + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/gogoproto/proto" + "github.com/spf13/cast" + + "cosmossdk.io/log" + storetypes "cosmossdk.io/store/types" + "cosmossdk.io/x/tx/signing" + "cosmossdk.io/x/upgrade" + upgradekeeper "cosmossdk.io/x/upgrade/keeper" + upgradetypes "cosmossdk.io/x/upgrade/types" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/grpc/cmtservice" + nodeservice "github.com/cosmos/cosmos-sdk/client/grpc/node" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/address" + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/runtime" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/server/api" + "github.com/cosmos/cosmos-sdk/server/config" + servertypes "github.com/cosmos/cosmos-sdk/server/types" + "github.com/cosmos/cosmos-sdk/std" + "github.com/cosmos/cosmos-sdk/testutil/testdata/testpb" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/types/msgservice" + "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + "github.com/cosmos/cosmos-sdk/x/auth/posthandler" + authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/auth/vesting" + vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + "github.com/cosmos/cosmos-sdk/x/bank" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/cosmos-sdk/x/consensus" + consensusparamkeeper "github.com/cosmos/cosmos-sdk/x/consensus/keeper" + consensusparamtypes "github.com/cosmos/cosmos-sdk/x/consensus/types" + distr "github.com/cosmos/cosmos-sdk/x/distribution" + distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/cosmos/cosmos-sdk/x/genutil" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + "github.com/cosmos/cosmos-sdk/x/gov" + govclient "github.com/cosmos/cosmos-sdk/x/gov/client" + govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/cosmos/cosmos-sdk/x/mint" + mintkeeper "github.com/cosmos/cosmos-sdk/x/mint/keeper" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + "github.com/cosmos/cosmos-sdk/x/params" + paramsclient "github.com/cosmos/cosmos-sdk/x/params/client" + paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/cosmos/cosmos-sdk/x/slashing" + slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + abci "github.com/cometbft/cometbft/abci/types" + + ica "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts" + icacontroller "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller" + icacontrollerkeeper "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/keeper" + icacontrollertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + icahost "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host" + icahostkeeper "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/keeper" + icahosttypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + ibccallbacks "github.com/cosmos/ibc-go/v10/modules/apps/callbacks" + ibccallbacksv2 "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/v2" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer" + ibctransferkeeper "github.com/cosmos/ibc-go/v10/modules/apps/transfer/keeper" + ibctransfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + transferv2 "github.com/cosmos/ibc-go/v10/modules/apps/transfer/v2" + ibc "github.com/cosmos/ibc-go/v10/modules/core" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + ibcapi "github.com/cosmos/ibc-go/v10/modules/core/api" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibckeeper "github.com/cosmos/ibc-go/v10/modules/core/keeper" + solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibcmock "github.com/cosmos/ibc-go/v10/testing/mock" +) + +const appName = "SimApp" + +var ( + // DefaultNodeHome default home directories for the application daemon + DefaultNodeHome string + + // module account permissions + maccPerms = map[string][]string{ + authtypes.FeeCollectorName: nil, + distrtypes.ModuleName: nil, + minttypes.ModuleName: {authtypes.Minter}, + stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking}, + stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking}, + govtypes.ModuleName: {authtypes.Burner}, + ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + icatypes.ModuleName: nil, + ibcmock.ModuleName: nil, + } +) + +var ( + _ runtime.AppI = (*SimApp)(nil) + _ servertypes.Application = (*SimApp)(nil) +) + +// SimApp extends an ABCI application, but with most of its parameters exported. +// They are exported for convenience in creating helper functions. +type SimApp struct { + *baseapp.BaseApp + legacyAmino *codec.LegacyAmino + appCodec codec.Codec + txConfig client.TxConfig + interfaceRegistry types.InterfaceRegistry + + // keys to access the substores + keys map[string]*storetypes.KVStoreKey + tkeys map[string]*storetypes.TransientStoreKey + memKeys map[string]*storetypes.MemoryStoreKey + + // keepers + AccountKeeper authkeeper.AccountKeeper + BankKeeper bankkeeper.Keeper + StakingKeeper *stakingkeeper.Keeper + SlashingKeeper slashingkeeper.Keeper + MintKeeper mintkeeper.Keeper + DistrKeeper distrkeeper.Keeper + GovKeeper govkeeper.Keeper + UpgradeKeeper *upgradekeeper.Keeper + ParamsKeeper paramskeeper.Keeper + IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly + ICAControllerKeeper icacontrollerkeeper.Keeper + ICAHostKeeper icahostkeeper.Keeper + TransferKeeper ibctransferkeeper.Keeper + ConsensusParamsKeeper consensusparamkeeper.Keeper + + // mock contract keeper used for testing + MockContractKeeper *ContractKeeper + + // make IBC modules public for test purposes + // these modules are never directly routed to by the IBC Router + ICAAuthModule ibcmock.IBCModule + + // the module manager + ModuleManager *module.Manager + BasicModuleManager module.BasicManager + + // simulation manager + simulationManager *module.SimulationManager + + // module configurator + configurator module.Configurator +} + +func init() { + userHomeDir, err := os.UserHomeDir() + if err != nil { + panic(err) + } + + DefaultNodeHome = filepath.Join(userHomeDir, ".simapp") +} + +// NewSimApp returns a reference to an initialized SimApp. +func NewSimApp( + logger log.Logger, + db dbm.DB, + traceStore io.Writer, + loadLatest bool, + appOpts servertypes.AppOptions, + baseAppOptions ...func(*baseapp.BaseApp), +) *SimApp { + interfaceRegistry, _ := types.NewInterfaceRegistryWithOptions(types.InterfaceRegistryOptions{ + ProtoFiles: proto.HybridResolver, + SigningOptions: signing.Options{ + AddressCodec: address.Bech32Codec{ + Bech32Prefix: sdk.GetConfig().GetBech32AccountAddrPrefix(), + }, + ValidatorAddressCodec: address.Bech32Codec{ + Bech32Prefix: sdk.GetConfig().GetBech32ValidatorAddrPrefix(), + }, + }, + }) + appCodec := codec.NewProtoCodec(interfaceRegistry) + legacyAmino := codec.NewLegacyAmino() + txConfig := authtx.NewTxConfig(appCodec, authtx.DefaultSignModes) + + std.RegisterLegacyAminoCodec(legacyAmino) + std.RegisterInterfaces(interfaceRegistry) + + // Below we could construct and set an application specific mempool and + // ABCI 1.0 PrepareProposal and ProcessProposal handlers. These defaults are + // already set in the SDK's BaseApp, this shows an example of how to override + // them. + // + // Example: + // + // bApp := baseapp.NewBaseApp(...) + // nonceMempool := mempool.NewSenderNonceMempool() + // abciPropHandler := NewDefaultProposalHandler(nonceMempool, bApp) + // + // bApp.SetMempool(nonceMempool) + // bApp.SetPrepareProposal(abciPropHandler.PrepareProposalHandler()) + // bApp.SetProcessProposal(abciPropHandler.ProcessProposalHandler()) + // + // Alternatively, you can construct BaseApp options, append those to + // baseAppOptions and pass them to NewBaseApp. + // + // Example: + // + // prepareOpt = func(app *baseapp.BaseApp) { + // abciPropHandler := baseapp.NewDefaultProposalHandler(nonceMempool, app) + // app.SetPrepareProposal(abciPropHandler.PrepareProposalHandler()) + // } + // baseAppOptions = append(baseAppOptions, prepareOpt) + + bApp := baseapp.NewBaseApp(appName, logger, db, txConfig.TxDecoder(), baseAppOptions...) + bApp.SetCommitMultiStoreTracer(traceStore) + bApp.SetVersion(version.Version) + bApp.SetInterfaceRegistry(interfaceRegistry) + bApp.SetTxEncoder(txConfig.TxEncoder()) + + keys := storetypes.NewKVStoreKeys( + authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, + minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey, + govtypes.StoreKey, paramstypes.StoreKey, ibcexported.StoreKey, upgradetypes.StoreKey, + ibctransfertypes.StoreKey, icacontrollertypes.StoreKey, icahosttypes.StoreKey, + consensusparamtypes.StoreKey, + ) + + // register streaming services + if err := bApp.RegisterStreamingServices(appOpts, keys); err != nil { + panic(err) + } + + tkeys := storetypes.NewTransientStoreKeys(paramstypes.TStoreKey) + memKeys := storetypes.NewMemoryStoreKeys(ibcmock.MemStoreKey) + + app := &SimApp{ + BaseApp: bApp, + legacyAmino: legacyAmino, + appCodec: appCodec, + txConfig: txConfig, + interfaceRegistry: interfaceRegistry, + keys: keys, + tkeys: tkeys, + memKeys: memKeys, + } + + app.ParamsKeeper = initParamsKeeper(appCodec, legacyAmino, keys[paramstypes.StoreKey], tkeys[paramstypes.TStoreKey]) + + // set the BaseApp's parameter store + app.ConsensusParamsKeeper = consensusparamkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[consensusparamtypes.StoreKey]), authtypes.NewModuleAddress(govtypes.ModuleName).String(), runtime.EventService{}) + bApp.SetParamStore(app.ConsensusParamsKeeper.ParamsStore) + + // SDK module keepers + + // add keepers + app.AccountKeeper = authkeeper.NewAccountKeeper(appCodec, runtime.NewKVStoreService(keys[authtypes.StoreKey]), authtypes.ProtoBaseAccount, maccPerms, authcodec.NewBech32Codec(sdk.Bech32MainPrefix), sdk.Bech32MainPrefix, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + + app.BankKeeper = bankkeeper.NewBaseKeeper( + appCodec, + runtime.NewKVStoreService(keys[banktypes.StoreKey]), + app.AccountKeeper, + BlockedAddresses(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + logger, + ) + app.StakingKeeper = stakingkeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[stakingtypes.StoreKey]), app.AccountKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), authcodec.NewBech32Codec(sdk.Bech32PrefixValAddr), authcodec.NewBech32Codec(sdk.Bech32PrefixConsAddr), + ) + app.MintKeeper = mintkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[minttypes.StoreKey]), app.StakingKeeper, app.AccountKeeper, app.BankKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + + app.DistrKeeper = distrkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[distrtypes.StoreKey]), app.AccountKeeper, app.BankKeeper, app.StakingKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + + app.SlashingKeeper = slashingkeeper.NewKeeper( + appCodec, legacyAmino, runtime.NewKVStoreService(keys[slashingtypes.StoreKey]), app.StakingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // register the staking hooks + // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks + app.StakingKeeper.SetHooks( + stakingtypes.NewMultiStakingHooks(app.DistrKeeper.Hooks(), app.SlashingKeeper.Hooks()), + ) + + // get skipUpgradeHeights from the app options + skipUpgradeHeights := map[int64]bool{} + for _, h := range cast.ToIntSlice(appOpts.Get(server.FlagUnsafeSkipUpgrades)) { + skipUpgradeHeights[int64(h)] = true + } + homePath := cast.ToString(appOpts.Get(flags.FlagHome)) + // set the governance module account as the authority for conducting upgrades + app.UpgradeKeeper = upgradekeeper.NewKeeper(skipUpgradeHeights, runtime.NewKVStoreService(keys[upgradetypes.StoreKey]), appCodec, homePath, app.BaseApp, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + + app.IBCKeeper = ibckeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[ibcexported.StoreKey]), app.GetSubspace(ibcexported.ModuleName), app.UpgradeKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // NOTE: The mock ContractKeeper is only created for testing. + // Real applications should not use the mock ContractKeeper + app.MockContractKeeper = NewContractKeeper(memKeys[ibcmock.MemStoreKey]) + + govConfig := govtypes.DefaultConfig() + /* + Example of setting gov params: + govConfig.MaxMetadataLen = 10000 + */ + govKeeper := govkeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[govtypes.StoreKey]), app.AccountKeeper, app.BankKeeper, + app.StakingKeeper, app.DistrKeeper, app.MsgServiceRouter(), govConfig, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + app.GovKeeper = *govKeeper.SetHooks( + govtypes.NewMultiGovHooks( + // register the governance hooks + ), + ) + + // ICA Controller keeper + app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[icacontrollertypes.StoreKey]), app.GetSubspace(icacontrollertypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeper, + app.MsgServiceRouter(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // ICA Host keeper + app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[icahosttypes.StoreKey]), app.GetSubspace(icahosttypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeper, + app.AccountKeeper, app.MsgServiceRouter(), + app.GRPCQueryRouter(), authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // Create IBC Router + ibcRouter := porttypes.NewRouter() + ibcRouterV2 := ibcapi.NewRouter() + + // Middleware Stacks + maxCallbackGas := uint64(1_000_000) + + // Create Transfer Keeper + // NOTE: the Transfer Keeper's ICS4Wrapper can later be replaced. + app.TransferKeeper = ibctransferkeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]), app.GetSubspace(ibctransfertypes.ModuleName), + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeper, + app.MsgServiceRouter(), + app.AccountKeeper, app.BankKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // Mock Module Stack + + // Mock Module setup for testing IBC and also acts as the interchain accounts authentication module + // NOTE: the IBC mock keeper and application module is used only for testing core IBC. Do + // not replicate if you do not need to test core IBC or light clients. + mockModule := ibcmock.NewAppModule() + + // The mock module is used for testing IBC + mockIBCModule := ibcmock.NewIBCModule(&mockModule, ibcmock.NewIBCApp(ibcmock.ModuleName)) + ibcRouter.AddRoute(ibcmock.ModuleName, mockIBCModule) + + // Create Transfer Stack + // SendPacket, since it is originating from the application to core IBC: + // transferKeeper.SendPacket -> callbacks.SendPacket -> channel.SendPacket + + // RecvPacket, message that originates from core IBC and goes down to app, the flow is the other way + // channel.RecvPacket -> callbacks.OnRecvPacket -> transfer.OnRecvPacket + + // transfer stack contains (from top to bottom): + // - IBC Callbacks Middleware + // - Transfer + + // create IBC module from bottom to top of stack + var transferStack porttypes.IBCModule + transferStack = transfer.NewIBCModule(app.TransferKeeper) + transferStack = ibccallbacks.NewIBCMiddleware(transferStack, app.IBCKeeper.ChannelKeeper, app.MockContractKeeper, maxCallbackGas) + var transferICS4Wrapper porttypes.ICS4Wrapper + transferICS4Wrapper, ok := transferStack.(porttypes.ICS4Wrapper) + if !ok { + panic(fmt.Errorf("cannot convert %T to %T", transferStack, transferICS4Wrapper)) + } + + // Since the callbacks middleware itself is an ics4wrapper, it needs to be passed to the transfer keeper + app.TransferKeeper.WithICS4Wrapper(transferICS4Wrapper) + + // Add transfer stack to IBC Router + ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) + + // Create Interchain Accounts Stack + // SendPacket, since it is originating from the application to core IBC: + // icaControllerKeeper.SendTx -> callbacks.SendPacket -> channel.SendPacket + + // initialize ICA module with mock module as the authentication module on the controller side + var icaControllerStack porttypes.IBCModule + icaControllerStack = ibcmock.NewIBCModule(&mockModule, ibcmock.NewIBCApp("")) + app.ICAAuthModule, ok = icaControllerStack.(ibcmock.IBCModule) + if !ok { + panic(fmt.Errorf("cannot convert %T to %T", icaControllerStack, app.ICAAuthModule)) + } + icaControllerStack = icacontroller.NewIBCMiddlewareWithAuth(icaControllerStack, app.ICAControllerKeeper) + icaControllerStack = ibccallbacks.NewIBCMiddleware(icaControllerStack, app.IBCKeeper.ChannelKeeper, app.MockContractKeeper, maxCallbackGas) + var icaICS4Wrapper porttypes.ICS4Wrapper + icaICS4Wrapper, ok = icaControllerStack.(porttypes.ICS4Wrapper) + if !ok { + panic(fmt.Errorf("cannot convert %T to %T", icaControllerStack, icaICS4Wrapper)) + } + // Since the callbacks middleware itself is an ics4wrapper, it needs to be passed to the ica controller keeper + app.ICAControllerKeeper.WithICS4Wrapper(icaICS4Wrapper) + + // RecvPacket, message that originates from core IBC and goes down to app, the flow is: + // channel.RecvPacket -> icaHost.OnRecvPacket + + var icaHostStack porttypes.IBCModule = icahost.NewIBCModule(app.ICAHostKeeper) + + // Add host, controller & ica auth modules to IBC router + ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icahosttypes.SubModuleName, icaHostStack). + AddRoute(ibcmock.ModuleName+icacontrollertypes.SubModuleName, icaControllerStack) // ica with mock auth module stack route to ica (top level of middleware stack) + + // OnRecvPacket, message that originates from core IBC and goes down to app, the flow is the otherway + // channel.RecvPacket -> callbacks.OnRecvPacket -> mockModule.OnRecvPacket + + // OnAcknowledgementPacket flow: + // mockModule.OnAcknowledgementPacket -> callbacks.OnAcknowledgementPacket -> channel.OnAcknowledgementPacket + + // add transfer v2 module wrapped by callbacks v2 middleware + cbTransferModulev2 := ibccallbacksv2.NewIBCMiddleware(transferv2.NewIBCModule(app.TransferKeeper), app.IBCKeeper.ChannelKeeperV2, app.MockContractKeeper, app.IBCKeeper.ChannelKeeperV2, maxCallbackGas) + ibcRouterV2.AddRoute(ibctransfertypes.PortID, cbTransferModulev2) + + // Seal the IBC Router + app.IBCKeeper.SetRouter(ibcRouter) + app.IBCKeeper.SetRouterV2(ibcRouterV2) + + clientKeeper := app.IBCKeeper.ClientKeeper + storeProvider := app.IBCKeeper.ClientKeeper.GetStoreProvider() + + tmLightClientModule := ibctm.NewLightClientModule(appCodec, storeProvider) + clientKeeper.AddRoute(ibctm.ModuleName, &tmLightClientModule) + + smLightClientModule := solomachine.NewLightClientModule(appCodec, storeProvider) + clientKeeper.AddRoute(solomachine.ModuleName, &smLightClientModule) + + // **** Module Options **** + + // NOTE: Any module instantiated in the module manager that is later modified + // must be passed by reference here. + app.ModuleManager = module.NewManager( + genutil.NewAppModule( + app.AccountKeeper, app.StakingKeeper, app, + txConfig, + ), + auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, app.GetSubspace(authtypes.ModuleName)), + vesting.NewAppModule(app.AccountKeeper, app.BankKeeper), + bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper, app.GetSubspace(banktypes.ModuleName)), + gov.NewAppModule(appCodec, &app.GovKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(govtypes.ModuleName)), + mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper, nil, app.GetSubspace(minttypes.ModuleName)), + slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(slashingtypes.ModuleName), app.interfaceRegistry), + distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(distrtypes.ModuleName)), + staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName)), + upgrade.NewAppModule(app.UpgradeKeeper, app.AccountKeeper.AddressCodec()), + params.NewAppModule(app.ParamsKeeper), + consensus.NewAppModule(appCodec, app.ConsensusParamsKeeper), + + // IBC modules + ibc.NewAppModule(app.IBCKeeper), + transfer.NewAppModule(app.TransferKeeper), + ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper), + mockModule, + + // IBC light clients + ibctm.NewAppModule(tmLightClientModule), + solomachine.NewAppModule(smLightClientModule), + ) + + // BasicModuleManager defines the module BasicManager is in charge of setting up basic, + // non-dependant module elements, such as codec registration and genesis verification. + // By default it is composed of all the module from the module manager. + // Additionally, app module basics can be overwritten by passing them as argument. + app.BasicModuleManager = module.NewBasicManagerFromManager( + app.ModuleManager, + map[string]module.AppModuleBasic{ + genutiltypes.ModuleName: genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator), + govtypes.ModuleName: gov.NewAppModuleBasic( + []govclient.ProposalHandler{ + paramsclient.ProposalHandler, + }, + ), + }) + app.BasicModuleManager.RegisterLegacyAminoCodec(legacyAmino) + app.BasicModuleManager.RegisterInterfaces(interfaceRegistry) + + // NOTE: upgrade module is required to be prioritized + app.ModuleManager.SetOrderPreBlockers( + upgradetypes.ModuleName, + authtypes.ModuleName, + ) + + // During begin block slashing happens after distr.BeginBlocker so that + // there is nothing left over in the validator fee pool, so as to keep the + // CanWithdrawInvariant invariant. + // NOTE: staking module is required if HistoricalEntries param > 0 + app.ModuleManager.SetOrderBeginBlockers( + minttypes.ModuleName, + distrtypes.ModuleName, + slashingtypes.ModuleName, + stakingtypes.ModuleName, + ibcexported.ModuleName, + ibctransfertypes.ModuleName, + genutiltypes.ModuleName, + icatypes.ModuleName, + ibcmock.ModuleName, + ) + app.ModuleManager.SetOrderEndBlockers( + govtypes.ModuleName, + stakingtypes.ModuleName, + ibcexported.ModuleName, + ibctransfertypes.ModuleName, + genutiltypes.ModuleName, + icatypes.ModuleName, + ibcmock.ModuleName, + ) + + // NOTE: The genutils module must occur after staking so that pools are + // properly initialized with tokens from genesis accounts. + // NOTE: The genutils module must also occur after auth so that it can access the params from auth. + genesisModuleOrder := []string{ + authtypes.ModuleName, + banktypes.ModuleName, distrtypes.ModuleName, stakingtypes.ModuleName, + slashingtypes.ModuleName, govtypes.ModuleName, minttypes.ModuleName, + ibcexported.ModuleName, genutiltypes.ModuleName, ibctransfertypes.ModuleName, + icatypes.ModuleName, ibcmock.ModuleName, paramstypes.ModuleName, upgradetypes.ModuleName, + vestingtypes.ModuleName, consensusparamtypes.ModuleName, + } + app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...) + app.ModuleManager.SetOrderExportGenesis(genesisModuleOrder...) + + // Uncomment if you want to set a custom migration order here. + // app.ModuleManager.SetOrderMigrations(custom order) + + app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter()) + err := app.ModuleManager.RegisterServices(app.configurator) + if err != nil { + panic(err) + } + + // add test gRPC service for testing gRPC queries in isolation + testpb.RegisterQueryServer(app.GRPCQueryRouter(), testpb.QueryImpl{}) + + // create the simulation manager and define the order of the modules for deterministic simulations + // + // NOTE: this is not required apps that don't use the simulator for fuzz testing + // transactions + overrideModules := map[string]module.AppModuleSimulation{ + authtypes.ModuleName: auth.NewAppModule(app.appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, app.GetSubspace(authtypes.ModuleName)), + } + app.simulationManager = module.NewSimulationManagerFromAppModules(app.ModuleManager.Modules, overrideModules) + + app.simulationManager.RegisterStoreDecoders() + + // initialize stores + app.MountKVStores(keys) + app.MountTransientStores(tkeys) + app.MountMemoryStores(memKeys) + + // initialize BaseApp + app.SetInitChainer(app.InitChainer) + app.SetPreBlocker(app.PreBlocker) + app.SetBeginBlocker(app.BeginBlocker) + app.SetEndBlocker(app.EndBlocker) + app.setAnteHandler(txConfig) + + // In v0.46, the SDK introduces _postHandlers_. PostHandlers are like + // antehandlers, but are run _after_ the `runMsgs` execution. They are also + // defined as a chain, and have the same signature as antehandlers. + // + // In baseapp, postHandlers are run in the same store branch as `runMsgs`, + // meaning that both `runMsgs` and `postHandler` state will be committed if + // both are successful, and both will be reverted if any of the two fails. + // + // The SDK exposes a default postHandlers chain, which is comprised of only + // one decorator: the Transaction Tips decorator. However, some chains do + // not need it by default, so feel free to comment the next line if you do + // not need tips. + // To read more about tips: + // https://docs.cosmos.network/main/core/tips.html + // + // Please note that changing any of the anteHandler or postHandler chain is + // likely to be a state-machine breaking change, which needs a coordinated + // upgrade. + app.setPostHandler() + + // At startup, after all modules have been registered, check that all proto + // annotations are correct. + protoFiles, err := proto.MergedRegistry() + if err != nil { + panic(err) + } + err = msgservice.ValidateProtoAnnotations(protoFiles) + if err != nil { + // Once we switch to using protoreflect-based antehandlers, we might + // want to panic here instead of logging a warning. + _, err := fmt.Fprintln(os.Stderr, err.Error()) + if err != nil { + fmt.Println("could not write to stderr") + } + } + + if loadLatest { + if err := app.LoadLatestVersion(); err != nil { + panic(fmt.Errorf("error loading last version: %w", err)) + } + } + + return app +} + +func (app *SimApp) setAnteHandler(txConfig client.TxConfig) { + anteHandler, err := NewAnteHandler( + HandlerOptions{ + ante.HandlerOptions{ + AccountKeeper: app.AccountKeeper, + BankKeeper: app.BankKeeper, + SignModeHandler: txConfig.SignModeHandler(), + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + }, + app.IBCKeeper, + }, + ) + if err != nil { + panic(err) + } + + // Set the AnteHandler for the app + app.SetAnteHandler(anteHandler) +} + +func (app *SimApp) setPostHandler() { + postHandler, err := posthandler.NewPostHandler( + posthandler.HandlerOptions{}, + ) + if err != nil { + panic(err) + } + + app.SetPostHandler(postHandler) +} + +// Name returns the name of the App +func (app *SimApp) Name() string { return app.BaseApp.Name() } + +// PreBlocker application updates every pre block +func (app *SimApp) PreBlocker(ctx sdk.Context, _ *abci.RequestFinalizeBlock) (*sdk.ResponsePreBlock, error) { + return app.ModuleManager.PreBlock(ctx) +} + +// BeginBlocker application updates every begin block +func (app *SimApp) BeginBlocker(ctx sdk.Context) (sdk.BeginBlock, error) { + return app.ModuleManager.BeginBlock(ctx) +} + +// EndBlocker application updates every end block +func (app *SimApp) EndBlocker(ctx sdk.Context) (sdk.EndBlock, error) { + return app.ModuleManager.EndBlock(ctx) +} + +// Configurator returns the configurator for the app +func (app *SimApp) Configurator() module.Configurator { + return app.configurator +} + +// InitChainer application update at chain initialization +func (app *SimApp) InitChainer(ctx sdk.Context, req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { + var genesisState GenesisState + if err := json.Unmarshal(req.AppStateBytes, &genesisState); err != nil { + panic(err) + } + if err := app.UpgradeKeeper.SetModuleVersionMap(ctx, app.ModuleManager.GetVersionMap()); err != nil { + panic(err) + } + return app.ModuleManager.InitGenesis(ctx, app.appCodec, genesisState) +} + +// LoadHeight loads a particular height +func (app *SimApp) LoadHeight(height int64) error { + return app.LoadVersion(height) +} + +// LegacyAmino returns SimApp's amino codec. +// +// NOTE: This is solely to be used for testing purposes as it may be desirable +// for modules to register their own custom testing types. +func (app *SimApp) LegacyAmino() *codec.LegacyAmino { + return app.legacyAmino +} + +// AppCodec returns SimApp's app codec. +// +// NOTE: This is solely to be used for testing purposes as it may be desirable +// for modules to register their own custom testing types. +func (app *SimApp) AppCodec() codec.Codec { + return app.appCodec +} + +// InterfaceRegistry returns SimApp's InterfaceRegistry +func (app *SimApp) InterfaceRegistry() types.InterfaceRegistry { + return app.interfaceRegistry +} + +// TxConfig returns SimApp's TxConfig +func (app *SimApp) TxConfig() client.TxConfig { + return app.txConfig +} + +// DefaultGenesis returns a default genesis from the registered AppModuleBasic's. +func (app *SimApp) DefaultGenesis() map[string]json.RawMessage { + return app.BasicModuleManager.DefaultGenesis(app.appCodec) +} + +// GetKey returns the KVStoreKey for the provided store key. +// +// NOTE: This is solely to be used for testing purposes. +func (app *SimApp) GetKey(storeKey string) *storetypes.KVStoreKey { + return app.keys[storeKey] +} + +// GetStoreKeys returns all the stored store keys. +func (app *SimApp) GetStoreKeys() []storetypes.StoreKey { + keys := make([]storetypes.StoreKey, 0, len(app.keys)) + for _, key := range app.keys { + keys = append(keys, key) + } + + return keys +} + +// GetSubspace returns a param subspace for a given module name. +// +// NOTE: This is solely to be used for testing purposes. +func (app *SimApp) GetSubspace(moduleName string) paramstypes.Subspace { + subspace, _ := app.ParamsKeeper.GetSubspace(moduleName) + return subspace +} + +// SimulationManager implements the SimulationApp interface +func (app *SimApp) SimulationManager() *module.SimulationManager { + return app.simulationManager +} + +// RegisterAPIRoutes registers all application module routes with the provided +// API server. +func (app *SimApp) RegisterAPIRoutes(apiSvr *api.Server, apiConfig config.APIConfig) { + clientCtx := apiSvr.ClientCtx + // Register new tx routes from grpc-gateway. + authtx.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // Register new CometBFT queries routes from grpc-gateway. + cmtservice.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // Register node gRPC service for grpc-gateway. + nodeservice.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // Register grpc-gateway routes for all modules. + app.BasicModuleManager.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // register swagger API from root so that other applications can override easily + if err := server.RegisterSwaggerAPI(apiSvr.ClientCtx, apiSvr.Router, apiConfig.Swagger); err != nil { + panic(err) + } +} + +// RegisterTxService implements the Application.RegisterTxService method. +func (app *SimApp) RegisterTxService(clientCtx client.Context) { + authtx.RegisterTxService(app.GRPCQueryRouter(), clientCtx, app.Simulate, app.interfaceRegistry) +} + +// RegisterTendermintService implements the Application.RegisterTendermintService method. +func (app *SimApp) RegisterTendermintService(clientCtx client.Context) { + cmtApp := server.NewCometABCIWrapper(app) + cmtservice.RegisterTendermintService( + clientCtx, + app.GRPCQueryRouter(), + app.interfaceRegistry, + cmtApp.Query, + ) +} + +func (app *SimApp) RegisterNodeService(clientCtx client.Context, cfg config.Config) { + nodeservice.RegisterNodeService(clientCtx, app.GRPCQueryRouter(), cfg) +} + +// GetMaccPerms returns a copy of the module account permissions +// +// NOTE: This is solely to be used for testing purposes. +func GetMaccPerms() map[string][]string { + dupMaccPerms := make(map[string][]string) + maps.Copy(dupMaccPerms, maccPerms) + + return dupMaccPerms +} + +// BlockedAddresses returns all the app's blocked account addresses. +func BlockedAddresses() map[string]bool { + modAccAddrs := make(map[string]bool) + for acc := range GetMaccPerms() { + modAccAddrs[authtypes.NewModuleAddress(acc).String()] = true + } + + // allow the following addresses to receive funds + delete(modAccAddrs, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + delete(modAccAddrs, authtypes.NewModuleAddress(ibcmock.ModuleName).String()) + + return modAccAddrs +} + +// initParamsKeeper init params keeper and its subspaces +func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey storetypes.StoreKey) paramskeeper.Keeper { + paramsKeeper := paramskeeper.NewKeeper(appCodec, legacyAmino, key, tkey) + + // TODO: ibc module subspaces can be removed after migration of params + // https://github.com/cosmos/ibc-go/issues/2010 + paramsKeeper.Subspace(ibctransfertypes.ModuleName) + paramsKeeper.Subspace(ibcexported.ModuleName) + paramsKeeper.Subspace(icacontrollertypes.SubModuleName) + paramsKeeper.Subspace(icahosttypes.SubModuleName) + + return paramsKeeper +} + +// IBC TestingApp functions + +// GetBaseApp implements the TestingApp interface. +func (app *SimApp) GetBaseApp() *baseapp.BaseApp { + return app.BaseApp +} + +// GetIBCKeeper implements the TestingApp interface. +func (app *SimApp) GetIBCKeeper() *ibckeeper.Keeper { + return app.IBCKeeper +} + +// GetTxConfig implements the TestingApp interface. +func (app *SimApp) GetTxConfig() client.TxConfig { + return app.txConfig +} + +// GetMemKey returns the MemStoreKey for the provided mem key. +// +// NOTE: This is solely used for testing purposes. +func (app *SimApp) GetMemKey(storeKey string) *storetypes.MemoryStoreKey { + return app.memKeys[storeKey] +} diff --git a/modules/apps/callbacks/testing/simapp/contract_keeper.go b/modules/apps/callbacks/testing/simapp/contract_keeper.go new file mode 100644 index 0000000..18d55e6 --- /dev/null +++ b/modules/apps/callbacks/testing/simapp/contract_keeper.go @@ -0,0 +1,261 @@ +package simapp + +import ( + "fmt" + + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + callbacktypes "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibcmock "github.com/cosmos/ibc-go/v10/testing/mock" +) + +// MockKeeper implements callbacktypes.ContractKeeper +var _ callbacktypes.ContractKeeper = (*ContractKeeper)(nil) + +var StatefulCounterKey = "stateful-callback-counter" + +const ( + // OogPanicContract is a contract address that will panic out of gas + OogPanicContract = "panics out of gas" + // OogErrorContract is a contract address that will error out of gas + OogErrorContract = "errors out of gas" + // PanicContract is a contract address that will panic + PanicContract = "panics" + // ErrorContract is a contract address that will return an error + ErrorContract = "errors" + // SuccessContract is a contract address that will return nil + SuccessContract = "success" +) + +// This is a mock contract keeper used for testing. It is not wired up to any modules. +// It implements the interface functions expected by the ibccallbacks middleware +// so that it can be tested with simapp. The keeper is responsible for tracking +// two metrics: +// - number of callbacks called per callback type +// - stateful entry attempts +// +// The counter for callbacks allows us to ensure the correct callbacks were routed to +// and the stateful entries allows us to track state reversals or reverted state upon +// contract execution failure or out of gas errors. +type ContractKeeper struct { + key storetypes.StoreKey + + Counters map[callbacktypes.CallbackType]int + + IBCSendPacketCallbackFn func( + cachedCtx sdk.Context, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + packetData []byte, + contractAddress, + packetSenderAddress string, + version string, + ) error + + IBCOnAcknowledgementPacketCallbackFn func( + cachedCtx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, + contractAddress, + packetSenderAddress string, + version string, + ) error + + IBCOnTimeoutPacketCallbackFn func( + cachedCtx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, + contractAddress, + packetSenderAddress string, + version string, + ) error + + IBCReceivePacketCallbackFn func( + cachedCtx sdk.Context, + packet ibcexported.PacketI, + ack ibcexported.Acknowledgement, + contractAddress string, + version string, + ) error +} + +// SetStateEntryCounter sets state entry counter. The number of stateful +// entries is tracked as a uint8. This function is used to test state reversals. +func (k ContractKeeper) SetStateEntryCounter(ctx sdk.Context, count uint8) { + store := ctx.KVStore(k.key) + store.Set([]byte(StatefulCounterKey), []byte{count}) +} + +// GetStateEntryCounter returns the state entry counter stored in state. +func (k ContractKeeper) GetStateEntryCounter(ctx sdk.Context) uint8 { + store := ctx.KVStore(k.key) + bz := store.Get([]byte(StatefulCounterKey)) + if bz == nil { + return 0 + } + return bz[0] +} + +// IncrementStateEntryCounter increments the stateful callback counter in state. +func (k ContractKeeper) IncrementStateEntryCounter(ctx sdk.Context) { + count := k.GetStateEntryCounter(ctx) + k.SetStateEntryCounter(ctx, count+1) +} + +// NewContractKeeper creates a new mock ContractKeeper. +func NewContractKeeper(key storetypes.StoreKey) *ContractKeeper { + k := &ContractKeeper{ + key: key, + Counters: make(map[callbacktypes.CallbackType]int), + } + + k.IBCSendPacketCallbackFn = func(ctx sdk.Context, _, _ string, _ clienttypes.Height, _ uint64, _ []byte, contractAddress, _, _ string) error { + return k.ProcessMockCallback(ctx, callbacktypes.CallbackTypeSendPacket, contractAddress) + } + + k.IBCOnAcknowledgementPacketCallbackFn = func(ctx sdk.Context, _ channeltypes.Packet, _ []byte, _ sdk.AccAddress, contractAddress, _, _ string) error { + return k.ProcessMockCallback(ctx, callbacktypes.CallbackTypeAcknowledgementPacket, contractAddress) + } + + k.IBCOnTimeoutPacketCallbackFn = func(ctx sdk.Context, _ channeltypes.Packet, _ sdk.AccAddress, contractAddress, _, _ string) error { + return k.ProcessMockCallback(ctx, callbacktypes.CallbackTypeTimeoutPacket, contractAddress) + } + + k.IBCReceivePacketCallbackFn = func(ctx sdk.Context, _ ibcexported.PacketI, _ ibcexported.Acknowledgement, contractAddress, _ string) error { + return k.ProcessMockCallback(ctx, callbacktypes.CallbackTypeReceivePacket, contractAddress) + } + + return k +} + +// IBCSendPacketCallback increments the stateful entry counter and the send_packet callback counter. +// This function: +// - returns MockApplicationCallbackError and consumes half the remaining gas if the contract address is ErrorContract +// - Oog panics and consumes all the remaining gas + 1 if the contract address is OogPanicContract +// - returns MockApplicationCallbackError and consumes all the remaining gas + 1 if the contract address is OogErrorContract +// - Panics and consumes half the remaining gas if the contract address is PanicContract +// - returns nil and consumes half the remaining gas if the contract address is SuccessContract or any other value +func (k ContractKeeper) IBCSendPacketCallback( + ctx sdk.Context, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + packetData []byte, + contractAddress, + packetSenderAddress, + version string, +) error { + return k.IBCSendPacketCallbackFn(ctx, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, packetData, contractAddress, packetSenderAddress, version) +} + +// IBCOnAcknowledgementPacketCallback increments the stateful entry counter and the acknowledgement_packet callback counter. +// This function: +// - returns MockApplicationCallbackError and consumes half the remaining gas if the contract address is ErrorContract +// - Oog panics and consumes all the remaining gas + 1 if the contract address is OogPanicContract +// - returns MockApplicationCallbackError and consumes all the remaining gas + 1 if the contract address is OogErrorContract +// - Panics and consumes half the remaining gas if the contract address is PanicContract +// - returns nil and consumes half the remaining gas if the contract address is SuccessContract or any other value +func (k ContractKeeper) IBCOnAcknowledgementPacketCallback( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, + contractAddress, + packetSenderAddress, + version string, +) error { + return k.IBCOnAcknowledgementPacketCallbackFn(ctx, packet, acknowledgement, relayer, contractAddress, packetSenderAddress, version) +} + +// IBCOnTimeoutPacketCallback increments the stateful entry counter and the timeout_packet callback counter. +// This function: +// - returns MockApplicationCallbackError and consumes half the remaining gas if the contract address is ErrorContract +// - Oog panics and consumes all the remaining gas + 1 if the contract address is OogPanicContract +// - returns MockApplicationCallbackError and consumes all the remaining gas + 1 if the contract address is OogErrorContract +// - Panics and consumes half the remaining gas if the contract address is PanicContract +// - returns nil and consumes half the remaining gas if the contract address is SuccessContract or any other value +func (k ContractKeeper) IBCOnTimeoutPacketCallback( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, + contractAddress, + packetSenderAddress, + version string, +) error { + return k.IBCOnTimeoutPacketCallbackFn(ctx, packet, relayer, contractAddress, packetSenderAddress, version) +} + +// IBCReceivePacketCallback increments the stateful entry counter and the receive_packet callback counter. +// This function: +// - returns MockApplicationCallbackError and consumes half the remaining gas if the contract address is ErrorContract +// - Oog panics and consumes all the remaining gas + 1 if the contract address is OogPanicContract +// - returns MockApplicationCallbackError and consumes all the remaining gas + 1 if the contract address is OogErrorContract +// - Panics and consumes half the remaining gas if the contract address is PanicContract +// - returns nil and consumes half the remaining gas if the contract address is SuccessContract or any other value +func (k ContractKeeper) IBCReceivePacketCallback( + ctx sdk.Context, + packet ibcexported.PacketI, + ack ibcexported.Acknowledgement, + contractAddress, + version string, +) error { + return k.IBCReceivePacketCallbackFn(ctx, packet, ack, contractAddress, version) +} + +// ProcessMockCallback processes a mock callback. +// It increments the stateful entry counter and the callback counter. +// This function: +// - returns MockApplicationCallbackError and consumes half the remaining gas if the contract address is ErrorContract +// - Oog panics and consumes all the remaining gas + 1 if the contract address is OogPanicContract +// - returns MockApplicationCallbackError and consumes all the remaining gas + 1 if the contract address is OogErrorContract +// - Panics and consumes half the remaining gas if the contract address is PanicContract +// - returns nil and consumes half the remaining gas if the contract address is SuccessContract or any other value +func (k ContractKeeper) ProcessMockCallback( + ctx sdk.Context, + callbackType callbacktypes.CallbackType, + contractAddress string, +) (err error) { + gasRemaining := ctx.GasMeter().GasRemaining() + + // increment stateful entries, if the callbacks module handler + // reverts state, we can check by querying for the counter + // currently stored. + k.IncrementStateEntryCounter(ctx) + + // increment callback execution attempts + k.Counters[callbackType]++ + + switch contractAddress { + case ErrorContract: + // consume half of the remaining gas so that ConsumeGas cannot oog panic + ctx.GasMeter().ConsumeGas(gasRemaining/2, fmt.Sprintf("mock %s callback unauthorized", callbackType)) + return ibcmock.MockApplicationCallbackError + case OogPanicContract: + ctx.GasMeter().ConsumeGas(gasRemaining+1, fmt.Sprintf("mock %s callback oog panic", callbackType)) + return nil // unreachable + case OogErrorContract: + defer func() { + _ = recover() + err = ibcmock.MockApplicationCallbackError + }() + ctx.GasMeter().ConsumeGas(gasRemaining+1, fmt.Sprintf("mock %s callback oog error", callbackType)) + return nil // unreachable + case PanicContract: + // consume half of the remaining gas so that ConsumeGas cannot oog panic + ctx.GasMeter().ConsumeGas(gasRemaining/2, fmt.Sprintf("mock %s callback panic", callbackType)) + panic(ibcmock.MockApplicationCallbackError) + default: + // consume half of the remaining gas so that ConsumeGas cannot oog panic + ctx.GasMeter().ConsumeGas(gasRemaining/2, fmt.Sprintf("mock %s callback success", callbackType)) + return nil // success + } +} diff --git a/modules/apps/callbacks/testing/simapp/encoding.go b/modules/apps/callbacks/testing/simapp/encoding.go new file mode 100644 index 0000000..1c4648d --- /dev/null +++ b/modules/apps/callbacks/testing/simapp/encoding.go @@ -0,0 +1,16 @@ +package simapp + +import ( + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" +) + +// EncodingConfig specifies the concrete encoding types to use for a given app. +// This is provided for compatibility between protobuf and amino implementations. +type EncodingConfig struct { + InterfaceRegistry types.InterfaceRegistry + Codec codec.Codec + TxConfig client.TxConfig + Amino *codec.LegacyAmino +} diff --git a/modules/apps/callbacks/testing/simapp/export.go b/modules/apps/callbacks/testing/simapp/export.go new file mode 100644 index 0000000..c3abafc --- /dev/null +++ b/modules/apps/callbacks/testing/simapp/export.go @@ -0,0 +1,243 @@ +package simapp + +import ( + "encoding/json" + "errors" + "log" + + storetypes "cosmossdk.io/store/types" + + servertypes "github.com/cosmos/cosmos-sdk/server/types" + sdk "github.com/cosmos/cosmos-sdk/types" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +// ExportAppStateAndValidators exports the state of the application for a genesis +// file. +func (app *SimApp) ExportAppStateAndValidators( + forZeroHeight bool, jailAllowedAddrs []string, modulesToExport []string, +) (servertypes.ExportedApp, error) { + // as if they could withdraw from the start of the next block + ctx := app.NewContext(true) + + // We export at last height + 1, because that's the height at which + // Tendermint will start InitChain. + height := app.LastBlockHeight() + 1 + if forZeroHeight { + height = 0 + app.prepForZeroHeightGenesis(ctx, jailAllowedAddrs) + } + + genState, err := app.ModuleManager.ExportGenesis(ctx, app.appCodec) + if err != nil { + return servertypes.ExportedApp{}, err + } + appState, err := json.MarshalIndent(genState, "", " ") + if err != nil { + return servertypes.ExportedApp{}, err + } + + validators, err := staking.WriteValidators(ctx, app.StakingKeeper) + return servertypes.ExportedApp{ + AppState: appState, + Validators: validators, + Height: height, + ConsensusParams: app.GetConsensusParams(ctx), + }, err +} + +// prepare for fresh start at zero height +// NOTE zero height genesis is a temporary feature which will be deprecated +// in favour of export at a block height +func (app *SimApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []string) { + applyAllowedAddrs := len(jailAllowedAddrs) > 0 + + // check if there is an allowed address list + + allowedAddrsMap := make(map[string]bool) + + for _, addr := range jailAllowedAddrs { + _, err := sdk.ValAddressFromBech32(addr) + if err != nil { + log.Fatal(err) + } + allowedAddrsMap[addr] = true + } + + /* Handle fee distribution state. */ + + // withdraw all validator commission + err := app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { + valBz, err := app.StakingKeeper.ValidatorAddressCodec().StringToBytes(val.GetOperator()) + if err != nil { + panic(err) + } + _, _ = app.DistrKeeper.WithdrawValidatorCommission(ctx, valBz) + return false + }) + if err != nil { + panic(err) + } + + // withdraw all delegator rewards + dels, err := app.StakingKeeper.GetAllDelegations(ctx) + if err != nil { + panic(err) + } + + for _, delegation := range dels { + valAddr, err := sdk.ValAddressFromBech32(delegation.ValidatorAddress) + if err != nil { + panic(err) + } + + delAddr, err := sdk.AccAddressFromBech32(delegation.DelegatorAddress) + if err != nil { + panic(err) + } + _, _ = app.DistrKeeper.WithdrawDelegationRewards(ctx, delAddr, valAddr) + } + + // clear validator slash events + app.DistrKeeper.DeleteAllValidatorSlashEvents(ctx) + + // clear validator historical rewards + app.DistrKeeper.DeleteAllValidatorHistoricalRewards(ctx) + + // set context height to zero + height := ctx.BlockHeight() + ctx = ctx.WithBlockHeight(0) + + // reinitialize all validators + err = app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { + valBz, err := sdk.ValAddressFromBech32(val.GetOperator()) + if err != nil { + panic(err) + } + // donate any unwithdrawn outstanding reward fraction tokens to the community pool + scraps, err := app.DistrKeeper.GetValidatorOutstandingRewardsCoins(ctx, valBz) + if err != nil { + panic(err) + } + feePool, err := app.DistrKeeper.FeePool.Get(ctx) + if err != nil { + panic(err) + } + feePool.CommunityPool = feePool.CommunityPool.Add(scraps...) + if err := app.DistrKeeper.FeePool.Set(ctx, feePool); err != nil { + panic(err) + } + + if err := app.DistrKeeper.Hooks().AfterValidatorCreated(ctx, valBz); err != nil { + panic(err) + } + return false + }) + + // reinitialize all delegations + for _, del := range dels { + valAddr, err := sdk.ValAddressFromBech32(del.ValidatorAddress) + if err != nil { + panic(err) + } + delAddr, err := sdk.AccAddressFromBech32(del.DelegatorAddress) + if err != nil { + panic(err) + } + err = app.DistrKeeper.Hooks().BeforeDelegationCreated(ctx, delAddr, valAddr) + if err != nil { + panic(err) + } + err = app.DistrKeeper.Hooks().AfterDelegationModified(ctx, delAddr, valAddr) + if err != nil { + panic(err) + } + } + + // reset context height + ctx = ctx.WithBlockHeight(height) + + /* Handle staking state. */ + + // iterate through redelegations, reset creation height + err = app.StakingKeeper.IterateRedelegations(ctx, func(_ int64, red stakingtypes.Redelegation) (stop bool) { + for i := range red.Entries { + red.Entries[i].CreationHeight = 0 + } + err = app.StakingKeeper.SetRedelegation(ctx, red) + if err != nil { + panic(err) + } + return false + }) + if err != nil { + panic(err) + } + + // iterate through unbonding delegations, reset creation height + err = app.StakingKeeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd stakingtypes.UnbondingDelegation) (stop bool) { + for i := range ubd.Entries { + ubd.Entries[i].CreationHeight = 0 + } + err = app.StakingKeeper.SetUnbondingDelegation(ctx, ubd) + if err != nil { + panic(err) + } + return false + }) + if err != nil { + panic(err) + } + + // Iterate through validators by power descending, reset bond heights, and + // update bond intra-tx counters. + store := ctx.KVStore(app.keys[stakingtypes.StoreKey]) + iter := storetypes.KVStoreReversePrefixIterator(store, stakingtypes.ValidatorsKey) + counter := int16(0) + + for ; iter.Valid(); iter.Next() { + addr := sdk.ValAddress(stakingtypes.AddressFromValidatorsKey(iter.Key())) + validator, err := app.StakingKeeper.GetValidator(ctx, addr) + if err != nil { + panic(errors.New("expected validator, not found")) + } + + validator.UnbondingHeight = 0 + if applyAllowedAddrs && !allowedAddrsMap[addr.String()] { + validator.Jailed = true + } + + err = app.StakingKeeper.SetValidator(ctx, validator) + if err != nil { + panic(errors.New("couldn't set validator")) + } + counter++ + } + + iter.Close() + + _, err = app.StakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx) + if err != nil { + log.Fatal(err) + } + + /* Handle slashing state. */ + + // reset start height on signing infos + err = app.SlashingKeeper.IterateValidatorSigningInfos( + ctx, + func(addr sdk.ConsAddress, info slashingtypes.ValidatorSigningInfo) (stop bool) { + info.StartHeight = 0 + err = app.SlashingKeeper.SetValidatorSigningInfo(ctx, addr, info) + if err != nil { + panic(err) + } + return false + }, + ) + if err != nil { + log.Fatal(err) + } +} diff --git a/modules/apps/callbacks/testing/simapp/genesis.go b/modules/apps/callbacks/testing/simapp/genesis.go new file mode 100644 index 0000000..b267f13 --- /dev/null +++ b/modules/apps/callbacks/testing/simapp/genesis.go @@ -0,0 +1,14 @@ +package simapp + +import ( + "encoding/json" +) + +// GenesisState of the blockchain is represented here as a map of raw json +// messages key'd by an identifier string. +// The identifier is used to determine which module genesis information belongs +// to so it may be appropriately routed during init chain. +// Within this application default genesis information is retrieved from +// the ModuleBasicManager which populates json from each BasicModule +// object provided to it during init. +type GenesisState map[string]json.RawMessage diff --git a/modules/apps/callbacks/testing/simapp/genesis_account.go b/modules/apps/callbacks/testing/simapp/genesis_account.go new file mode 100644 index 0000000..0af1360 --- /dev/null +++ b/modules/apps/callbacks/testing/simapp/genesis_account.go @@ -0,0 +1,47 @@ +package simapp + +import ( + "errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +var _ authtypes.GenesisAccount = (*SimGenesisAccount)(nil) + +// SimGenesisAccount defines a type that implements the GenesisAccount interface +// to be used for simulation accounts in the genesis state. +type SimGenesisAccount struct { + *authtypes.BaseAccount + + // vesting account fields + OriginalVesting sdk.Coins `json:"original_vesting" yaml:"original_vesting"` // total vesting coins upon initialization + DelegatedFree sdk.Coins `json:"delegated_free" yaml:"delegated_free"` // delegated vested coins at time of delegation + DelegatedVesting sdk.Coins `json:"delegated_vesting" yaml:"delegated_vesting"` // delegated vesting coins at time of delegation + StartTime int64 `json:"start_time" yaml:"start_time"` // vesting start time (UNIX Epoch time) + EndTime int64 `json:"end_time" yaml:"end_time"` // vesting end time (UNIX Epoch time) + + // module account fields + ModuleName string `json:"module_name" yaml:"module_name"` // name of the module account + ModulePermissions []string `json:"module_permissions" yaml:"module_permissions"` // permissions of module account +} + +// Validate checks for errors on the vesting and module account parameters +func (sga SimGenesisAccount) Validate() error { + if !sga.OriginalVesting.IsZero() { + if sga.StartTime >= sga.EndTime { + return errors.New("vesting start-time cannot be before end-time") + } + } + + if sga.ModuleName != "" { + ma := authtypes.ModuleAccount{ + BaseAccount: sga.BaseAccount, Name: sga.ModuleName, Permissions: sga.ModulePermissions, + } + if err := ma.Validate(); err != nil { + return err + } + } + + return sga.BaseAccount.Validate() +} diff --git a/modules/apps/callbacks/testing/simapp/params/amino.go b/modules/apps/callbacks/testing/simapp/params/amino.go new file mode 100644 index 0000000..93d644a --- /dev/null +++ b/modules/apps/callbacks/testing/simapp/params/amino.go @@ -0,0 +1,27 @@ +//go:build test_amino +// +build test_amino + +package params + +import ( + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" +) + +// MakeTestEncodingConfig creates an EncodingConfig for an amino based test configuration. +// This function should be used only internally (in the SDK). +// App user shouldn't create new codecs - use the app.AppCodec instead. +// [DEPRECATED] +func MakeTestEncodingConfig() EncodingConfig { + cdc := codec.NewLegacyAmino() + interfaceRegistry := types.NewInterfaceRegistry() + marshaler := codec.NewAminoCodec(cdc) + + return EncodingConfig{ + InterfaceRegistry: interfaceRegistry, + Marshaler: marshaler, + TxConfig: legacytx.StdTxConfig{Cdc: cdc}, + Amino: cdc, + } +} diff --git a/modules/apps/callbacks/testing/simapp/params/doc.go b/modules/apps/callbacks/testing/simapp/params/doc.go new file mode 100644 index 0000000..e7278cb --- /dev/null +++ b/modules/apps/callbacks/testing/simapp/params/doc.go @@ -0,0 +1,19 @@ +/* +Package params defines the simulation parameters in the simapp. + +It contains the default weights used for each transaction used on the module's +simulation. These weights define the chance for a transaction to be simulated at +any given operation. + +You can replace the default values for the weights by providing a params.json +file with the weights defined for each of the transaction operations: + + { + "op_weight_msg_send": 60, + "op_weight_msg_delegate": 100, + } + +In the example above, the `MsgSend` has 60% chance to be simulated, while the +`MsgDelegate` will always be simulated. +*/ +package params diff --git a/modules/apps/callbacks/testing/simapp/params/encoding.go b/modules/apps/callbacks/testing/simapp/params/encoding.go new file mode 100644 index 0000000..2612bb8 --- /dev/null +++ b/modules/apps/callbacks/testing/simapp/params/encoding.go @@ -0,0 +1,16 @@ +package params + +import ( + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" +) + +// EncodingConfig specifies the concrete encoding types to use for a given app. +// This is provided for compatibility between protobuf and amino implementations. +type EncodingConfig struct { + InterfaceRegistry types.InterfaceRegistry + Codec codec.Codec + TxConfig client.TxConfig + Amino *codec.LegacyAmino +} diff --git a/modules/apps/callbacks/testing/simapp/params/proto.go b/modules/apps/callbacks/testing/simapp/params/proto.go new file mode 100644 index 0000000..32a95f1 --- /dev/null +++ b/modules/apps/callbacks/testing/simapp/params/proto.go @@ -0,0 +1,26 @@ +//go:build !test_amino + +package params + +import ( + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/x/auth/tx" +) + +// MakeTestEncodingConfig creates an EncodingConfig for a non-amino based test configuration. +// This function should be used only internally (in the SDK). +// App user shouldn't create new codecs - use the app.AppCodec instead. +// [DEPRECATED] +func MakeTestEncodingConfig() EncodingConfig { + cdc := codec.NewLegacyAmino() + interfaceRegistry := types.NewInterfaceRegistry() + protoCdc := codec.NewProtoCodec(interfaceRegistry) + + return EncodingConfig{ + InterfaceRegistry: interfaceRegistry, + Codec: protoCdc, + TxConfig: tx.NewTxConfig(protoCdc, tx.DefaultSignModes), + Amino: cdc, + } +} diff --git a/modules/apps/callbacks/transfer_test.go b/modules/apps/callbacks/transfer_test.go new file mode 100644 index 0000000..707b992 --- /dev/null +++ b/modules/apps/callbacks/transfer_test.go @@ -0,0 +1,273 @@ +package ibccallbacks_test + +import ( + "fmt" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/testing/simapp" + "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/types" + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (s *CallbacksTestSuite) TestTransferCallbacks() { + testCases := []struct { + name string + transferMemo string + expCallback types.CallbackType + expSuccess bool + }{ + { + "success: transfer with no memo", + "", + "none", + true, + }, + { + "success: dest callback", + fmt.Sprintf(`{"dest_callback": {"address": "%s"}}`, simapp.SuccessContract), + types.CallbackTypeReceivePacket, + true, + }, + { + "success: dest callback with other json fields", + fmt.Sprintf(`{"dest_callback": {"address": "%s"}, "something_else": {}}`, simapp.SuccessContract), + types.CallbackTypeReceivePacket, + true, + }, + { + "success: dest callback with malformed json", + fmt.Sprintf(`{"dest_callback": {"address": "%s"}, malformed}`, simapp.SuccessContract), + "none", + true, + }, + { + "failure: dest callback with missing address", + `{"dest_callback": {"address": ""}}`, + "none", + false, + }, + { + "success: source callback", + fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, simapp.SuccessContract), + types.CallbackTypeAcknowledgementPacket, + true, + }, + { + "success: source callback with other json fields", + fmt.Sprintf(`{"src_callback": {"address": "%s"}, "something_else": {}}`, simapp.SuccessContract), + types.CallbackTypeAcknowledgementPacket, + true, + }, + { + "success: source callback with malformed json", + fmt.Sprintf(`{"src_callback": {"address": "%s"}, malformed}`, simapp.SuccessContract), + "none", + true, + }, + { + "success: source callback with missing address", + `{"src_callback": {"address": ""}}`, + "none", + true, + }, + { + "failure: dest callback with low gas (panic)", + fmt.Sprintf(`{"dest_callback": {"address": "%s"}}`, simapp.OogPanicContract), + types.CallbackTypeReceivePacket, + false, + }, + { + "failure: source callback with low gas (panic)", + fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, simapp.OogPanicContract), + types.CallbackTypeSendPacket, + false, + }, + { + "failure: dest callback with low gas (error)", + fmt.Sprintf(`{"dest_callback": {"address": "%s"}}`, simapp.OogErrorContract), + types.CallbackTypeReceivePacket, + false, + }, + { + "failure: source callback with low gas (error)", + fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, simapp.OogErrorContract), + types.CallbackTypeSendPacket, + false, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTransferTest() + + s.ExecuteTransfer(tc.transferMemo, tc.expSuccess) + s.AssertHasExecutedExpectedCallback(tc.expCallback, tc.expSuccess) + }) + } +} + +func (s *CallbacksTestSuite) TestTransferTimeoutCallbacks() { + testCases := []struct { + name string + transferMemo string + expCallback types.CallbackType + expSuccess bool + }{ + { + "success: transfer with no memo", + "", + "none", + true, + }, + { + "success: dest callback", + fmt.Sprintf(`{"dest_callback": {"address": "%s"}}`, simapp.SuccessContract), + "none", // timeouts don't reach destination chain execution + true, + }, + { + "success: source callback", + fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, simapp.SuccessContract), + types.CallbackTypeTimeoutPacket, + true, + }, + { + "success: dest callback with low gas (panic)", + fmt.Sprintf(`{"dest_callback": {"address": "%s"}}`, simapp.OogPanicContract), + "none", // timeouts don't reach destination chain execution + true, + }, + { + "success: dest callback with low gas (error)", + fmt.Sprintf(`{"dest_callback": {"address": "%s"}}`, simapp.OogErrorContract), + "none", // timeouts don't reach destination chain execution + true, + }, + { + "failure: source callback with low gas (panic)", + fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, simapp.OogPanicContract), + types.CallbackTypeSendPacket, + false, + }, + { + "failure: source callback with low gas (error)", + fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, simapp.OogErrorContract), + types.CallbackTypeSendPacket, + false, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTransferTest() + + s.ExecuteTransferTimeout(tc.transferMemo) + s.AssertHasExecutedExpectedCallback(tc.expCallback, tc.expSuccess) + }) + } +} + +// ExecuteTransfer executes a transfer message on chainA for ibctesting.TestCoin (100 "stake"). +// It checks that the transfer is successful and that the packet is relayed to chainB. +func (s *CallbacksTestSuite) ExecuteTransfer(memo string, recvSuccess bool) { + escrowAddress := transfertypes.GetEscrowAddress(s.path.EndpointA.ChannelConfig.PortID, s.path.EndpointA.ChannelID) + // record the balance of the escrow address before the transfer + escrowBalance := GetSimApp(s.chainA).BankKeeper.GetBalance(s.chainA.GetContext(), escrowAddress, sdk.DefaultBondDenom) + // record the balance of the receiving address before the transfer + denom := transfertypes.NewDenom(sdk.DefaultBondDenom, transfertypes.NewHop(s.path.EndpointB.ChannelConfig.PortID, s.path.EndpointB.ChannelID)) + receiverBalance := GetSimApp(s.chainB).BankKeeper.GetBalance(s.chainB.GetContext(), s.chainB.SenderAccount.GetAddress(), denom.IBCDenom()) + // record the balance of the sending address before the transfer + senderBalance := GetSimApp(s.chainA).BankKeeper.GetBalance(s.chainA.GetContext(), s.chainA.SenderAccount.GetAddress(), sdk.DefaultBondDenom) + + amount := ibctesting.TestCoin + msg := transfertypes.NewMsgTransfer( + s.path.EndpointA.ChannelConfig.PortID, + s.path.EndpointA.ChannelID, + amount, + s.chainA.SenderAccount.GetAddress().String(), + s.chainB.SenderAccount.GetAddress().String(), + clienttypes.NewHeight(1, 100), 0, memo, + ) + + res, err := s.chainA.SendMsgs(msg) + if err != nil { + return // we return if send packet is rejected + } + + // packet found, relay from A to B + err = s.path.EndpointB.UpdateClient() + s.Require().NoError(err) + + packet, err := ibctesting.ParseV1PacketFromEvents(res.GetEvents()) + s.Require().NoError(err) + + res, err = s.path.EndpointB.RecvPacketWithResult(packet) + s.Require().NoError(err) + + acknowledgement, err := ibctesting.ParseAckFromEvents(res.Events) + s.Require().NoError(err) + + err = s.path.EndpointA.AcknowledgePacket(packet, acknowledgement) + s.Require().NoError(err) + + var ack channeltypes.Acknowledgement + err = transfertypes.ModuleCdc.UnmarshalJSON(acknowledgement, &ack) + s.Require().NoError(err) + + s.Require().Equal(recvSuccess, ack.Success(), "acknowledgement success is not as expected") + + if recvSuccess { + // check that the escrow address balance increased by 100 + s.Require().Equal(escrowBalance.Add(amount), GetSimApp(s.chainA).BankKeeper.GetBalance(s.chainA.GetContext(), escrowAddress, sdk.DefaultBondDenom)) + // check that the receiving address balance increased by 100 + s.Require().Equal(receiverBalance.AddAmount(sdkmath.NewInt(100)), GetSimApp(s.chainB).BankKeeper.GetBalance(s.chainB.GetContext(), s.chainB.SenderAccount.GetAddress(), denom.IBCDenom())) + // check that the sending address balance decreased by 100 + s.Require().Equal(senderBalance.Sub(amount), GetSimApp(s.chainA).BankKeeper.GetBalance(s.chainA.GetContext(), s.chainA.SenderAccount.GetAddress(), sdk.DefaultBondDenom)) + } else { + // check that the escrow address balance is the same as before the transfer + s.Require().Equal(escrowBalance, GetSimApp(s.chainA).BankKeeper.GetBalance(s.chainA.GetContext(), escrowAddress, sdk.DefaultBondDenom)) + // check that the receiving address balance is the same as before the transfer + s.Require().Equal(receiverBalance, GetSimApp(s.chainB).BankKeeper.GetBalance(s.chainB.GetContext(), s.chainB.SenderAccount.GetAddress(), denom.IBCDenom())) + // check that the sending address balance is the same as before the transfer + s.Require().Equal(senderBalance, GetSimApp(s.chainA).BankKeeper.GetBalance(s.chainA.GetContext(), s.chainA.SenderAccount.GetAddress(), sdk.DefaultBondDenom)) + } +} + +// ExecuteTransferTimeout executes a transfer message on chainA for 100 denom. +// This message is not relayed to chainB, and it times out on chainA. +func (s *CallbacksTestSuite) ExecuteTransferTimeout(memo string) { + timeoutHeight := clienttypes.GetSelfHeight(s.chainB.GetContext()) + timeoutTimestamp := uint64(s.chainB.GetContext().BlockTime().UnixNano()) + + amount := ibctesting.TestCoin + msg := transfertypes.NewMsgTransfer( + s.path.EndpointA.ChannelConfig.PortID, + s.path.EndpointA.ChannelID, + amount, + s.chainA.SenderAccount.GetAddress().String(), + s.chainB.SenderAccount.GetAddress().String(), + timeoutHeight, timeoutTimestamp, memo, + ) + + res, err := s.chainA.SendMsgs(msg) + if err != nil { + return // we return if send packet is rejected + } + + packet, err := ibctesting.ParseV1PacketFromEvents(res.GetEvents()) + s.Require().NoError(err) // packet committed + s.Require().NotNil(packet) + + // need to update chainA's client representing chainB to prove missing ack + err = s.path.EndpointA.UpdateClient() + s.Require().NoError(err) + + err = s.path.EndpointA.TimeoutPacket(packet) + s.Require().NoError(err) // timeout committed +} diff --git a/modules/apps/callbacks/types/callbacks.go b/modules/apps/callbacks/types/callbacks.go new file mode 100644 index 0000000..97c928a --- /dev/null +++ b/modules/apps/callbacks/types/callbacks.go @@ -0,0 +1,264 @@ +package types + +import ( + "encoding/hex" + "strconv" + "strings" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + "github.com/cosmos/ibc-go/v10/modules/core/api" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +/* + +ADR-8 implementation + +The Memo is used to ensure that the callback is desired by the user. This allows a user to send a packet to an ADR-8 enabled contract. + +The Memo format is defined like so: + +```json +{ + // ... other memo fields we don't care about + "src_callback": { + "address": {stringCallbackAddress}, + + // optional fields + "gas_limit": {stringForCallback} + }, + "dest_callback": { + "address": {stringCallbackAddress}, + + // optional fields + "gas_limit": {stringForCallback} + } +} +``` + +We will pass the packet sender info (if available) to the contract keeper for source callback executions. This will allow the contract +keeper to verify that the packet sender is the same as the callback address if desired. + +*/ + +// CallbacksCompatibleModule is an interface that combines the IBCModule and PacketDataUnmarshaler +// interfaces to assert that the underlying application supports both. +type CallbacksCompatibleModule interface { + porttypes.IBCModule + porttypes.PacketDataUnmarshaler +} + +// CallbacksCompatibleModuleV2 is an interface that combines the IBCModuleV2 and PacketDataUnmarshaler +// interfaces to assert that the underlying application supports both. +type CallbacksCompatibleModuleV2 interface { + api.IBCModule + api.PacketDataUnmarshaler +} + +// CallbackData is the callback data parsed from the packet. +type CallbackData struct { + // CallbackAddress is the address of the callback actor. + CallbackAddress string + // ExecutionGasLimit is the gas limit which will be used for the callback execution. + ExecutionGasLimit uint64 + // SenderAddress is the sender of the packet. This is passed to the contract keeper + // to verify that the packet sender is the same as the callback address if desired. + // This address is empty during destination callback execution. + // This address may be empty if the sender is unknown or undefined. + SenderAddress string + // CommitGasLimit is the gas needed to commit the callback even if the callback + // execution fails due to out of gas. + // This parameter is only used in event emissions, or logging. + CommitGasLimit uint64 + // ApplicationVersion is the base application version. + ApplicationVersion string + // Calldata is the calldata to be passed to the callback actor. + // This may be empty but if it is not empty, it should be the calldata sent to the callback actor. + Calldata []byte +} + +// GetSourceCallbackData parses the packet data and returns the source callback data. +func GetSourceCallbackData( + ctx sdk.Context, + packetDataUnmarshaler porttypes.PacketDataUnmarshaler, + packet channeltypes.Packet, + maxGas uint64, +) (CallbackData, bool, error) { + packetData, version, err := packetDataUnmarshaler.UnmarshalPacketData(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetData()) + if err != nil { + return CallbackData{}, false, nil + } + + return GetCallbackData(packetData, version, packet.GetSourcePort(), ctx.GasMeter().GasRemaining(), maxGas, SourceCallbackKey) +} + +// GetDestCallbackData parses the packet data and returns the destination callback data. +func GetDestCallbackData( + ctx sdk.Context, + packetDataUnmarshaler porttypes.PacketDataUnmarshaler, + packet channeltypes.Packet, maxGas uint64, +) (CallbackData, bool, error) { + packetData, version, err := packetDataUnmarshaler.UnmarshalPacketData(ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetData()) + if err != nil { + return CallbackData{}, false, nil + } + + return GetCallbackData(packetData, version, packet.GetSourcePort(), ctx.GasMeter().GasRemaining(), maxGas, DestinationCallbackKey) +} + +// GetCallbackData parses the packet data and returns the callback data. +// If the packet data does not have callback data set, it will return false for `isCbPacket` and an error. +// If the packet data does have callback data set but the callback data is malformed, +// it will return true for `isCbPacket` and an error +// It also checks that the remaining gas is greater than the gas limit specified in the packet data. +// The addressGetter and gasLimitGetter functions are used to retrieve the callback +// address and gas limit from the callback data. +func GetCallbackData( + packetData any, + version, srcPortID string, + remainingGas, maxGas uint64, + callbackKey string, +) (cbData CallbackData, isCbPacket bool, err error) { + packetDataProvider, ok := packetData.(ibcexported.PacketDataProvider) + if !ok { + return CallbackData{}, false, ErrNotPacketDataProvider + } + + callbackData, ok := packetDataProvider.GetCustomPacketData(callbackKey).(map[string]any) + if callbackData == nil || !ok { + return CallbackData{}, false, ErrCallbackKeyNotFound + } + + // get the callback address from the callback data + callbackAddress, err := getCallbackAddress(callbackData) + if err != nil || strings.TrimSpace(callbackAddress) == "" { + return CallbackData{}, true, ErrInvalidCallbackData + } + + // retrieve packet sender from packet data if possible and if needed + var packetSender string + if callbackKey == SourceCallbackKey { + packetData, ok := packetData.(ibcexported.PacketData) + if ok { + packetSender = packetData.GetPacketSender(srcPortID) + } + } + + // get the gas limit from the callback data + executionGasLimit, commitGasLimit, err := computeExecAndCommitGasLimit(callbackData, remainingGas, maxGas) + if err != nil { + return CallbackData{}, true, err + } + + callData, err := getCalldata(callbackData) + if err != nil { + return CallbackData{}, true, err + } + + return CallbackData{ + CallbackAddress: callbackAddress, + ExecutionGasLimit: executionGasLimit, + SenderAddress: packetSender, + CommitGasLimit: commitGasLimit, + ApplicationVersion: version, + Calldata: callData, + }, true, nil +} + +func computeExecAndCommitGasLimit(callbackData map[string]any, remainingGas, maxGas uint64) (uint64, uint64, error) { + // get the gas limit from the callback data + commitGasLimit, err := getUserDefinedGasLimit(callbackData) + if err != nil { + return 0, 0, err + } + + // ensure user defined gas limit does not exceed the max gas limit + if commitGasLimit == 0 || commitGasLimit > maxGas { + commitGasLimit = maxGas + } + + // account for the remaining gas in the context being less than the desired gas limit for the callback execution + // in this case, the callback execution may be retried upon failure + executionGasLimit := min(remainingGas, commitGasLimit) + + return executionGasLimit, commitGasLimit, nil +} + +// getUserDefinedGasLimit returns the custom gas limit provided for callbacks if it is +// in the callback data. It is assumed that callback data is not nil. +// If no gas limit is specified or the gas limit is improperly formatted, 0 is returned. +// +// The memo is expected to specify the user defined gas limit in the following format: +// { "{callbackKey}": { ... , "gas_limit": {stringForCallback} } +// +// Note: the user defined gas limit must be set as a string and not a json number. +func getUserDefinedGasLimit(callbackData map[string]any) (uint64, error) { + // the gas limit must be specified as a string and not a json number + gasLimit, ok := callbackData[UserDefinedGasLimitKey] + if !ok { + return 0, nil + } + gasLimitStr, ok := gasLimit.(string) + if !ok { + return 0, errorsmod.Wrapf(ErrInvalidCallbackData, "gas limit [%v] must be a string", gasLimit) + } + if gasLimitStr == "" { + return 0, nil + } + + userGas, err := strconv.ParseUint(gasLimitStr, 10, 64) + if err != nil { + return 0, errorsmod.Wrapf(ErrInvalidCallbackData, "gas limit must be a valid uint64: %s", err) + } + + return userGas, nil +} + +// getCallbackAddress returns the callback address if it is specified in the callback data. +// It is assumed that callback data is not nil. +// If no callback address is specified or the memo is improperly formatted, an empty string is returned. +// +// The memo is expected to contain the callback address in the following format: +// { "{callbackKey}": { "address": {stringCallbackAddress}} +// +// ADR-8 middleware should callback on the returned address if it is a PacketActor +// (i.e. smart contract that accepts IBC callbacks). +func getCallbackAddress(callbackData map[string]any) (string, error) { + callbackAddress, ok := callbackData[CallbackAddressKey].(string) + if !ok { + return "", errorsmod.Wrapf(ErrInvalidCallbackData, "callback address must be a string") + } + + return callbackAddress, nil +} + +// getCalldata returns the calldata if it is specified in the callback data. +func getCalldata(callbackData map[string]any) ([]byte, error) { + calldataAny, ok := callbackData[CalldataKey] + if !ok { + return nil, nil + } + calldataStr, ok := calldataAny.(string) + if !ok { + return nil, errorsmod.Wrapf(ErrInvalidCallbackData, "calldata must be a string") + } + if calldataStr == "" { + return nil, nil + } + + calldata, err := hex.DecodeString(calldataStr) + if err != nil { + return nil, errorsmod.Wrapf(ErrInvalidCallbackData, "calldata must be a valid hex string: %s", err) + } + return calldata, nil +} + +// AllowRetry returns true if the callback execution gas limit is less than the commit gas limit. +func (c CallbackData) AllowRetry() bool { + return c.ExecutionGasLimit < c.CommitGasLimit +} diff --git a/modules/apps/callbacks/types/callbacks_test.go b/modules/apps/callbacks/types/callbacks_test.go new file mode 100644 index 0000000..8f1b5e4 --- /dev/null +++ b/modules/apps/callbacks/types/callbacks_test.go @@ -0,0 +1,881 @@ +package types_test + +import ( + "fmt" + + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cometbft/cometbft/crypto/secp256k1" + + "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/types" + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + ibcmock "github.com/cosmos/ibc-go/v10/testing/mock" +) + +func (s *CallbacksTypesTestSuite) TestGetCallbackData() { + var ( + sender = sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()).String() + receiver = sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()).String() + packetData any + remainingGas uint64 + callbackKey string + version string + ) + + // max gas is 1_000_000 + testCases := []struct { + name string + malleate func() + expCallbackData types.CallbackData + expIsCallbackData bool + expError error + }{ + { + "success: source callback", + func() { + remainingGas = 2_000_000 + version = transfertypes.V1 + packetData = transfertypes.FungibleTokenPacketData{ + Denom: ibctesting.TestCoin.Denom, + Amount: ibctesting.TestCoin.Amount.String(), + Sender: sender, + Receiver: receiver, + Memo: fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, sender), + } + }, + types.CallbackData{ + CallbackAddress: sender, + SenderAddress: sender, + ExecutionGasLimit: 1_000_000, + CommitGasLimit: 1_000_000, + ApplicationVersion: transfertypes.V1, + }, + true, + nil, + }, + { + "success: destination callback", + func() { + callbackKey = types.DestinationCallbackKey + version = transfertypes.V1 + + remainingGas = 2_000_000 + packetData = transfertypes.FungibleTokenPacketData{ + Denom: ibctesting.TestCoin.Denom, + Amount: ibctesting.TestCoin.Amount.String(), + Sender: sender, + Receiver: receiver, + Memo: fmt.Sprintf(`{"dest_callback": {"address": "%s"}}`, sender), + } + }, + types.CallbackData{ + CallbackAddress: sender, + SenderAddress: "", + ExecutionGasLimit: 1_000_000, + CommitGasLimit: 1_000_000, + ApplicationVersion: transfertypes.V1, + }, + true, + nil, + }, + { + "success: destination callback with 0 user defined gas limit", + func() { + callbackKey = types.DestinationCallbackKey + version = transfertypes.V1 + + remainingGas = 2_000_000 + packetData = transfertypes.FungibleTokenPacketData{ + Denom: ibctesting.TestCoin.Denom, + Amount: ibctesting.TestCoin.Amount.String(), + Sender: sender, + Receiver: receiver, + Memo: fmt.Sprintf(`{"dest_callback": {"address": "%s", "gas_limit":"0"}}`, sender), + } + }, + types.CallbackData{ + CallbackAddress: sender, + SenderAddress: "", + ExecutionGasLimit: 1_000_000, + CommitGasLimit: 1_000_000, + ApplicationVersion: transfertypes.V1, + }, + true, + nil, + }, + { + "success: source callback with gas limit < remaining gas < max gas", + func() { + version = transfertypes.V1 + packetData = transfertypes.FungibleTokenPacketData{ + Denom: ibctesting.TestCoin.Denom, + Amount: ibctesting.TestCoin.Amount.String(), + Sender: sender, + Receiver: receiver, + Memo: fmt.Sprintf(`{"src_callback": {"address": "%s", "gas_limit": "50000"}}`, sender), + } + + remainingGas = 100_000 + }, + types.CallbackData{ + CallbackAddress: sender, + SenderAddress: sender, + ExecutionGasLimit: 50_000, + CommitGasLimit: 50_000, + ApplicationVersion: transfertypes.V1, + }, + true, + nil, + }, + { + "success: source callback with remaining gas < gas limit < max gas", + func() { + remainingGas = 100_000 + version = transfertypes.V1 + packetData = transfertypes.FungibleTokenPacketData{ + Denom: ibctesting.TestCoin.Denom, + Amount: ibctesting.TestCoin.Amount.String(), + Sender: sender, + Receiver: receiver, + Memo: fmt.Sprintf(`{"src_callback": {"address": "%s", "gas_limit": "200000"}}`, sender), + } + }, + types.CallbackData{ + CallbackAddress: sender, + SenderAddress: sender, + ExecutionGasLimit: 100_000, + CommitGasLimit: 200_000, + ApplicationVersion: transfertypes.V1, + }, + true, + nil, + }, + { + "success: source callback with remaining gas < max gas < gas limit", + func() { + remainingGas = 100_000 + version = transfertypes.V1 + packetData = transfertypes.FungibleTokenPacketData{ + Denom: ibctesting.TestCoin.Denom, + Amount: ibctesting.TestCoin.Amount.String(), + Sender: sender, + Receiver: receiver, + Memo: fmt.Sprintf(`{"src_callback": {"address": "%s", "gas_limit": "2000000"}}`, sender), + } + }, + types.CallbackData{ + CallbackAddress: sender, + SenderAddress: sender, + ExecutionGasLimit: 100_000, + CommitGasLimit: 1_000_000, + ApplicationVersion: transfertypes.V1, + }, + true, + nil, + }, + { + "success: destination callback with remaining gas < max gas < gas limit", + func() { + callbackKey = types.DestinationCallbackKey + version = transfertypes.V1 + + remainingGas = 100_000 + packetData = transfertypes.FungibleTokenPacketData{ + Denom: ibctesting.TestCoin.Denom, + Amount: ibctesting.TestCoin.Amount.String(), + Sender: sender, + Receiver: receiver, + Memo: fmt.Sprintf(`{"dest_callback": {"address": "%s", "gas_limit": "2000000"}}`, sender), + } + }, + types.CallbackData{ + CallbackAddress: sender, + SenderAddress: "", + ExecutionGasLimit: 100_000, + CommitGasLimit: 1_000_000, + ApplicationVersion: transfertypes.V1, + }, + true, + nil, + }, + { + "success: source callback with max gas < remaining gas < gas limit", + func() { + remainingGas = 2_000_000 + version = transfertypes.V1 + packetData = transfertypes.FungibleTokenPacketData{ + Denom: ibctesting.TestCoin.Denom, + Amount: ibctesting.TestCoin.Amount.String(), + Sender: sender, + Receiver: receiver, + Memo: fmt.Sprintf(`{"src_callback": {"address": "%s", "gas_limit": "3000000"}}`, sender), + } + }, + types.CallbackData{ + CallbackAddress: sender, + SenderAddress: sender, + ExecutionGasLimit: 1_000_000, + CommitGasLimit: 1_000_000, + ApplicationVersion: transfertypes.V1, + }, + true, + nil, + }, + { + "success: source callback with calldata", + func() { + remainingGas = 2_000_000 + version = transfertypes.V1 + packetData = transfertypes.FungibleTokenPacketData{ + Denom: ibctesting.TestCoin.Denom, + Amount: ibctesting.TestCoin.Amount.String(), + Sender: sender, + Receiver: receiver, + Memo: fmt.Sprintf(`{"src_callback": {"address": "%s", "calldata": "%x"}}`, sender, []byte("calldata")), + } + }, + types.CallbackData{ + CallbackAddress: sender, + SenderAddress: sender, + ExecutionGasLimit: 1_000_000, + CommitGasLimit: 1_000_000, + ApplicationVersion: transfertypes.V1, + Calldata: []byte("calldata"), + }, + true, + nil, + }, + { + "success: source callback with empty calldata", + func() { + remainingGas = 2_000_000 + version = transfertypes.V1 + packetData = transfertypes.FungibleTokenPacketData{ + Denom: ibctesting.TestCoin.Denom, + Amount: ibctesting.TestCoin.Amount.String(), + Sender: sender, + Receiver: receiver, + Memo: fmt.Sprintf(`{"src_callback": {"address": "%s", "calldata": ""}}`, sender), + } + }, + types.CallbackData{ + CallbackAddress: sender, + SenderAddress: sender, + ExecutionGasLimit: 1_000_000, + CommitGasLimit: 1_000_000, + ApplicationVersion: transfertypes.V1, + Calldata: nil, + }, + true, + nil, + }, + { + "success: dest callback with calldata", + func() { + callbackKey = types.DestinationCallbackKey + remainingGas = 2_000_000 + version = transfertypes.V1 + packetData = transfertypes.FungibleTokenPacketData{ + Denom: ibctesting.TestCoin.Denom, + Amount: ibctesting.TestCoin.Amount.String(), + Sender: sender, + Receiver: receiver, + Memo: fmt.Sprintf(`{"dest_callback": {"address": "%s", "calldata": "%x"}}`, sender, []byte("calldata")), + } + }, + types.CallbackData{ + CallbackAddress: sender, + SenderAddress: "", + ExecutionGasLimit: 1_000_000, + CommitGasLimit: 1_000_000, + ApplicationVersion: transfertypes.V1, + Calldata: []byte("calldata"), + }, + true, + nil, + }, + { + "failure: packet data does not implement PacketDataProvider", + func() { + packetData = ibcmock.MockPacketData + }, + types.CallbackData{}, + false, + types.ErrNotPacketDataProvider, + }, + { + "failure: empty memo", + func() { + version = transfertypes.V1 + packetData = transfertypes.FungibleTokenPacketData{ + Denom: ibctesting.TestCoin.Denom, + Amount: ibctesting.TestCoin.Amount.String(), + Sender: sender, + Receiver: receiver, + Memo: "", + } + }, + types.CallbackData{}, + false, + types.ErrCallbackKeyNotFound, + }, + { + "failure: empty address", + func() { + version = transfertypes.V1 + packetData = transfertypes.FungibleTokenPacketData{ + Denom: ibctesting.TestCoin.Denom, + Amount: ibctesting.TestCoin.Amount.String(), + Sender: sender, + Receiver: receiver, + Memo: `{"src_callback": {"address": ""}}`, + } + }, + types.CallbackData{}, + true, + types.ErrInvalidCallbackData, + }, + { + "failure: space address", + func() { + version = transfertypes.V1 + packetData = transfertypes.FungibleTokenPacketData{ + Denom: ibctesting.TestCoin.Denom, + Amount: ibctesting.TestCoin.Amount.String(), + Sender: sender, + Receiver: receiver, + Memo: `{"src_callback": {"address": " "}}`, + } + }, + types.CallbackData{}, + true, + types.ErrInvalidCallbackData, + }, + + { + "success: source callback", + func() { + remainingGas = 2_000_000 + version = transfertypes.V1 + packetData = transfertypes.FungibleTokenPacketData{ + Denom: ibctesting.TestCoin.Denom, + Amount: ibctesting.TestCoin.Amount.String(), + Sender: sender, + Receiver: receiver, + Memo: fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, sender), + } + }, + types.CallbackData{ + CallbackAddress: sender, + SenderAddress: sender, + ExecutionGasLimit: 1_000_000, + CommitGasLimit: 1_000_000, + ApplicationVersion: transfertypes.V1, + }, + true, + nil, + }, + { + "success: destination callback", + func() { + callbackKey = types.DestinationCallbackKey + + remainingGas = 2_000_000 + version = transfertypes.V1 + packetData = transfertypes.FungibleTokenPacketData{ + Denom: ibctesting.TestCoin.Denom, + Amount: ibctesting.TestCoin.Amount.String(), + Sender: sender, + Receiver: receiver, + Memo: fmt.Sprintf(`{"dest_callback": {"address": "%s"}}`, sender), + } + }, + types.CallbackData{ + CallbackAddress: sender, + SenderAddress: "", + ExecutionGasLimit: 1_000_000, + CommitGasLimit: 1_000_000, + ApplicationVersion: transfertypes.V1, + }, + true, + nil, + }, + { + "failure: packet data does not implement PacketDataProvider", + func() { + packetData = ibcmock.MockPacketData + }, + types.CallbackData{}, + false, + types.ErrNotPacketDataProvider, + }, + { + "failure: invalid gasLimit", + func() { + remainingGas = 2_000_000 + version = transfertypes.V1 + packetData = transfertypes.FungibleTokenPacketData{ + Denom: ibctesting.TestCoin.Denom, + Amount: ibctesting.TestCoin.Amount.String(), + Sender: sender, + Receiver: receiver, + Memo: fmt.Sprintf(`{"src_callback": {"address": "%s", "gas_limit": "invalid"}}`, sender), + } + }, + types.CallbackData{}, + true, + types.ErrInvalidCallbackData, + }, + { + "failure: invalid calldata", + func() { + remainingGas = 2_000_000 + version = transfertypes.V1 + packetData = transfertypes.FungibleTokenPacketData{ + Denom: ibctesting.TestCoin.Denom, + Amount: ibctesting.TestCoin.Amount.String(), + Sender: sender, + Receiver: receiver, + Memo: fmt.Sprintf(`{"src_callback": {"address": "%s", "calldata": "invalid"}}`, sender), + } + }, + types.CallbackData{}, + true, + types.ErrInvalidCallbackData, + }, + { + "failure: invalid calldata is number", + func() { + remainingGas = 2_000_000 + version = transfertypes.V1 + packetData = transfertypes.FungibleTokenPacketData{ + Denom: ibctesting.TestCoin.Denom, + Amount: ibctesting.TestCoin.Amount.String(), + Sender: sender, + Receiver: receiver, + Memo: fmt.Sprintf(`{"src_callback": {"address": "%s", "calldata": 10}}`, sender), + } + }, + types.CallbackData{}, + true, + types.ErrInvalidCallbackData, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() + + callbackKey = types.SourceCallbackKey + version = transfertypes.V1 + + tc.malleate() + + callbackData, isCbPacket, err := types.GetCallbackData(packetData, version, transfertypes.PortID, remainingGas, uint64(1_000_000), callbackKey) + + // check if GetCallbackData correctly determines if callback data is present in the packet data + s.Require().Equal(tc.expIsCallbackData, isCbPacket, tc.name) + + if tc.expError == nil { + s.Require().NoError(err, tc.name) + s.Require().Equal(tc.expCallbackData, callbackData, tc.name) + + expAllowRetry := tc.expCallbackData.ExecutionGasLimit < tc.expCallbackData.CommitGasLimit + s.Require().Equal(expAllowRetry, callbackData.AllowRetry(), tc.name) + + // check if the callback calldata is correctly unmarshalled + if len(tc.expCallbackData.Calldata) > 0 { + s.Require().Equal([]byte("calldata"), callbackData.Calldata, tc.name) + } + + } else { + s.Require().ErrorIs(err, tc.expError, tc.name) + } + }) + } +} + +// bytesProvider defines an interface that both packet data types implement in order to fetch the bytes. +type bytesProvider interface { + GetBytes() []byte +} + +func (s *CallbacksTypesTestSuite) TestGetDestSourceCallbackDataTransfer() { + sender := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()).String() + receiver := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()).String() + var ( + packetData bytesProvider + expCallbackData types.CallbackData + ) + + expSrcCallBack := types.CallbackData{ + CallbackAddress: sender, + SenderAddress: sender, + ExecutionGasLimit: 1_000_000, + CommitGasLimit: 1_000_000, + ApplicationVersion: transfertypes.V1, + } + + expDstCallBack := types.CallbackData{ + CallbackAddress: sender, + SenderAddress: "", + ExecutionGasLimit: 1_000_000, + CommitGasLimit: 1_000_000, + ApplicationVersion: transfertypes.V1, + } + + testCases := []struct { + name string + malleate func() + callbackFn func( + ctx sdk.Context, + packetDataUnmarshaler porttypes.PacketDataUnmarshaler, + packet channeltypes.Packet, + maxGas uint64, + ) (types.CallbackData, bool, error) + getSrc bool + }{ + { + "success: src_callback v1", + func() { + packetData = transfertypes.FungibleTokenPacketData{ + Denom: ibctesting.TestCoin.Denom, + Amount: ibctesting.TestCoin.Amount.String(), + Sender: sender, + Receiver: receiver, + Memo: fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, sender), + } + + expCallbackData = expSrcCallBack + + s.path.EndpointA.ChannelConfig.Version = transfertypes.V1 + s.path.EndpointA.ChannelConfig.PortID = transfertypes.ModuleName + s.path.EndpointB.ChannelConfig.Version = transfertypes.V1 + s.path.EndpointB.ChannelConfig.PortID = transfertypes.ModuleName + }, + types.GetSourceCallbackData, + true, + }, + { + "success: dest_callback v1", + func() { + packetData = transfertypes.FungibleTokenPacketData{ + Denom: ibctesting.TestCoin.Denom, + Amount: ibctesting.TestCoin.Amount.String(), + Sender: sender, + Receiver: receiver, + Memo: fmt.Sprintf(`{"dest_callback": {"address": "%s"}}`, sender), + } + + expCallbackData = expDstCallBack + + s.path.EndpointA.ChannelConfig.Version = transfertypes.V1 + s.path.EndpointA.ChannelConfig.PortID = transfertypes.ModuleName + s.path.EndpointB.ChannelConfig.Version = transfertypes.V1 + s.path.EndpointB.ChannelConfig.PortID = transfertypes.ModuleName + }, + types.GetDestCallbackData, + false, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() + + tc.malleate() + + transferStack, ok := s.chainA.App.GetIBCKeeper().PortKeeper.Route(transfertypes.ModuleName) + s.Require().True(ok) + + packetUnmarshaler, ok := transferStack.(types.CallbacksCompatibleModule) + s.Require().True(ok) + + s.path.Setup() + + gasMeter := storetypes.NewGasMeter(2_000_000) + ctx := s.chainA.GetContext().WithGasMeter(gasMeter) + var packet channeltypes.Packet + if tc.getSrc { + packet = channeltypes.NewPacket(packetData.GetBytes(), 0, transfertypes.PortID, s.path.EndpointA.ChannelID, transfertypes.PortID, s.path.EndpointB.ChannelID, clienttypes.ZeroHeight(), 0) + } else { + packet = channeltypes.NewPacket(packetData.GetBytes(), 0, transfertypes.PortID, s.path.EndpointB.ChannelID, transfertypes.PortID, s.path.EndpointA.ChannelID, clienttypes.ZeroHeight(), 0) + } + callbackData, isCbPacket, err := tc.callbackFn(ctx, packetUnmarshaler, packet, 1_000_000) + s.Require().NoError(err) + s.Require().True(isCbPacket) + s.Require().Equal(expCallbackData, callbackData) + }) + } +} + +func (s *CallbacksTypesTestSuite) TestGetCallbackAddress() { + denom := transfertypes.NewDenom(ibctesting.TestCoin.Denom) + amount := ibctesting.TestCoin.Amount.String() + sender := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()).String() + receiver := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()).String() + + testCases := []struct { + name string + packetData ibcexported.PacketDataProvider + expAddress string + expError error + }{ + { + "success: memo has callbacks in json struct and properly formatted src_callback_address which does not match packet sender", + transfertypes.FungibleTokenPacketData{ + Denom: denom.Base, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, receiver), + }, + receiver, + nil, + }, + { + "success: valid src_callback address specified in memo that matches sender", + transfertypes.FungibleTokenPacketData{ + Denom: denom.Base, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, sender), + }, + sender, + nil, + }, + { + "failure: memo is empty", + transfertypes.FungibleTokenPacketData{ + Denom: denom.Base, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: "", + }, + "", + nil, + }, + { + "failure: memo is not json string", + transfertypes.FungibleTokenPacketData{ + Denom: denom.Base, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: "memo", + }, + "", + nil, + }, + { + "failure: memo has empty src_callback object", + transfertypes.FungibleTokenPacketData{ + Denom: denom.Base, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: `{"src_callback": {}}`, + }, + "", + types.ErrInvalidCallbackData, + }, + { + "failure: memo does not have callbacks in json struct", + transfertypes.FungibleTokenPacketData{ + Denom: denom.Base, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: `{"Key": 10}`, + }, + "", + nil, + }, + { + "failure: memo has src_callback in json struct but does not have address key", + transfertypes.FungibleTokenPacketData{ + Denom: denom.Base, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: `{"src_callback": {"Key": 10}}`, + }, + "", + types.ErrInvalidCallbackData, + }, + { + "failure: memo has src_callback in json struct but does not have string value for address key", + transfertypes.FungibleTokenPacketData{ + Denom: denom.Base, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: `{"src_callback": {"address": 10}}`, + }, + "", + types.ErrInvalidCallbackData, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + callbackData, ok := tc.packetData.GetCustomPacketData(types.SourceCallbackKey).(map[string]any) + s.Require().Equal(ok, callbackData != nil) + if ok { + address, err := types.GetCallbackAddress(callbackData) + if tc.expError != nil { + s.Require().ErrorIs(err, tc.expError, tc.name) + } else { + s.Require().NoError(err, tc.name) + s.Require().Equal(tc.expAddress, address, tc.name) + } + } + }) + } +} + +func (s *CallbacksTypesTestSuite) TestUserDefinedGasLimit() { + denom := transfertypes.NewDenom(ibctesting.TestCoin.Denom) + amount := ibctesting.TestCoin.Amount.String() + sender := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()).String() + receiver := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()).String() + + testCases := []struct { + name string + packetData ibcexported.PacketDataProvider + expUserGas uint64 + expError error + }{ + { + "success: memo is empty", + transfertypes.FungibleTokenPacketData{ + Denom: denom.Base, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: "", + }, + 0, + nil, + }, + { + "success: memo has user defined gas limit", + transfertypes.FungibleTokenPacketData{ + Denom: denom.Base, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: `{"src_callback": {"gas_limit": "100"}}`, + }, + 100, + nil, + }, + { + "success: user defined gas limit is zero", + transfertypes.FungibleTokenPacketData{ + Denom: denom.Base, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: `{"src_callback": {"gas_limit": "0"}}`, + }, + 0, + nil, + }, + { + "failure: memo has empty src_callback object", + transfertypes.FungibleTokenPacketData{ + Denom: denom.Base, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: `{"src_callback": {}}`, + }, + 0, + nil, + }, + { + "failure: memo has user defined gas limit as json number", + transfertypes.FungibleTokenPacketData{ + Denom: denom.Base, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: `{"src_callback": {"gas_limit": 100}}`, + }, + 0, + types.ErrInvalidCallbackData, + }, + { + "failure: memo has user defined gas limit as negative", + transfertypes.FungibleTokenPacketData{ + Denom: denom.Base, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: `{"src_callback": {"gas_limit": "-100"}}`, + }, + 0, + types.ErrInvalidCallbackData, + }, + { + "failure: memo has user defined gas limit as string", + transfertypes.FungibleTokenPacketData{ + Denom: denom.Base, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: `{"src_callback": {"gas_limit": "invalid"}}`, + }, + 0, + types.ErrInvalidCallbackData, + }, + { + "failure: memo has user defined gas limit as empty string", + transfertypes.FungibleTokenPacketData{ + Denom: denom.Base, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: `{"src_callback": {"gas_limit": ""}}`, + }, + 0, + nil, + }, + { + "failure: malformed memo", + transfertypes.FungibleTokenPacketData{ + Denom: denom.Base, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: `invalid`, + }, + 0, + nil, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + callbackData, ok := tc.packetData.GetCustomPacketData(types.SourceCallbackKey).(map[string]any) + s.Require().Equal(ok, callbackData != nil) + userGas, err := types.GetUserDefinedGasLimit(callbackData) + if tc.expError != nil { + s.Require().ErrorIs(err, tc.expError, tc.name) + } else { + s.Require().NoError(err, tc.name) + s.Require().Equal(tc.expUserGas, userGas, tc.name) + } + }) + } +} diff --git a/modules/apps/callbacks/types/errors.go b/modules/apps/callbacks/types/errors.go new file mode 100644 index 0000000..ec8b65f --- /dev/null +++ b/modules/apps/callbacks/types/errors.go @@ -0,0 +1,15 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" +) + +var ( + ErrCannotUnmarshalPacketData = errorsmod.Register(ModuleName, 2, "cannot unmarshal packet data") + ErrNotPacketDataProvider = errorsmod.Register(ModuleName, 3, "packet is not a PacketDataProvider") + ErrCallbackKeyNotFound = errorsmod.Register(ModuleName, 4, "callback key not found in packet data") + ErrCallbackAddressNotFound = errorsmod.Register(ModuleName, 5, "callback address not found in packet data") + ErrCallbackOutOfGas = errorsmod.Register(ModuleName, 6, "callback out of gas") + ErrCallbackPanic = errorsmod.Register(ModuleName, 7, "callback panic") + ErrInvalidCallbackData = errorsmod.Register(ModuleName, 8, "invalid callback data") +) diff --git a/modules/apps/callbacks/types/events.go b/modules/apps/callbacks/types/events.go new file mode 100644 index 0000000..3fdce03 --- /dev/null +++ b/modules/apps/callbacks/types/events.go @@ -0,0 +1,104 @@ +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + // EventTypeSourceCallback is the event type for a source callback + EventTypeSourceCallback = "ibc_src_callback" + // EventTypeDestinationCallback is the event type for a destination callback + EventTypeDestinationCallback = "ibc_dest_callback" + + // AttributeKeyCallbackType denotes the condition that the callback is executed on: + // "acknowledgement": the callback is executed on the acknowledgement of the packet + // "timeout": the callback is executed on the timeout of the packet + // "recv_packet": the callback is executed on the reception of the packet + AttributeKeyCallbackType = "callback_type" + // AttributeKeyCallbackAddress denotes the callback address + AttributeKeyCallbackAddress = "callback_address" + // AttributeKeyCallbackResult denotes the callback result: + // AttributeValueCallbackSuccess: the callback is successfully executed + // AttributeValueCallbackFailure: the callback has failed to execute + AttributeKeyCallbackResult = "callback_result" + // AttributeKeyCallbackError denotes the callback error message + // if no error is returned, then this key will not be included in the event + AttributeKeyCallbackError = "callback_error" + // AttributeKeyCallbackGasLimit denotes the custom gas limit for the callback execution + // if custom gas limit is not in effect, then this key will not be included in the event + AttributeKeyCallbackGasLimit = "callback_exec_gas_limit" + // AttributeKeyCallbackCommitGasLimit denotes the gas needed to commit the callback even + // if the callback execution fails due to out of gas. + AttributeKeyCallbackCommitGasLimit = "callback_commit_gas_limit" + // AttributeKeyCallbackSourcePortID denotes the source port ID of the packet + AttributeKeyCallbackSourcePortID = "packet_src_port" + // AttributeKeyCallbackSourceChannelID denotes the source channel ID of the packet + AttributeKeyCallbackSourceChannelID = "packet_src_channel" + // AttributeKeyCallbackDestPortID denotes the destination port ID of the packet + AttributeKeyCallbackDestPortID = "packet_dest_port" + // AttributeKeyCallbackDestChannelID denotes the destination channel ID of the packet + AttributeKeyCallbackDestChannelID = "packet_dest_channel" + // AttributeKeyCallbackSequence denotes the sequence of the packet + AttributeKeyCallbackSequence = "packet_sequence" + // AttributeKeyCallbackBaseApplicationVersion denotes the callback base application version + AttributeKeyCallbackBaseApplicationVersion = "callback_base_application_version" + // AttributeValueCallbackSuccess denotes that the callback is successfully executed + AttributeValueCallbackSuccess = "success" + // AttributeValueCallbackFailure denotes that the callback has failed to execute + AttributeValueCallbackFailure = "failure" +) + +// EmitCallbackEvent emits an event for a callback +func EmitCallbackEvent( + ctx sdk.Context, + portID, + channelID string, + sequence uint64, + callbackType CallbackType, + callbackData CallbackData, + err error, +) { + attributes := []sdk.Attribute{ + sdk.NewAttribute(sdk.AttributeKeyModule, ModuleName), + sdk.NewAttribute(AttributeKeyCallbackType, string(callbackType)), + sdk.NewAttribute(AttributeKeyCallbackAddress, callbackData.CallbackAddress), + sdk.NewAttribute(AttributeKeyCallbackGasLimit, fmt.Sprintf("%d", callbackData.ExecutionGasLimit)), + sdk.NewAttribute(AttributeKeyCallbackCommitGasLimit, fmt.Sprintf("%d", callbackData.CommitGasLimit)), + sdk.NewAttribute(AttributeKeyCallbackSequence, fmt.Sprintf("%d", sequence)), + sdk.NewAttribute(AttributeKeyCallbackBaseApplicationVersion, callbackData.ApplicationVersion), + } + if err == nil { + attributes = append(attributes, sdk.NewAttribute(AttributeKeyCallbackResult, AttributeValueCallbackSuccess)) + } else { + attributes = append( + attributes, + sdk.NewAttribute(AttributeKeyCallbackError, err.Error()), + sdk.NewAttribute(AttributeKeyCallbackResult, AttributeValueCallbackFailure), + ) + } + + var eventType string + switch callbackType { + case CallbackTypeReceivePacket: + eventType = EventTypeDestinationCallback + attributes = append( + attributes, sdk.NewAttribute(AttributeKeyCallbackDestPortID, portID), + sdk.NewAttribute(AttributeKeyCallbackDestChannelID, channelID), + ) + default: + eventType = EventTypeSourceCallback + attributes = append( + attributes, sdk.NewAttribute(AttributeKeyCallbackSourcePortID, portID), + sdk.NewAttribute(AttributeKeyCallbackSourceChannelID, channelID), + ) + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + eventType, + attributes..., + ), + ) +} diff --git a/modules/apps/callbacks/types/events_test.go b/modules/apps/callbacks/types/events_test.go new file mode 100644 index 0000000..a55675f --- /dev/null +++ b/modules/apps/callbacks/types/events_test.go @@ -0,0 +1,242 @@ +package types_test + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + abci "github.com/cometbft/cometbft/abci/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/types" + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (s *CallbacksTypesTestSuite) TestEvents() { + testCases := []struct { + name string + packet channeltypes.Packet + callbackType types.CallbackType + callbackData types.CallbackData + callbackError error + expectedEvents func() []abci.Event + }{ + { + "success: ack callback", + channeltypes.NewPacket( + ibctesting.MockPacketData, 1, ibctesting.MockPort, ibctesting.FirstChannelID, + transfertypes.PortID, ibctesting.InvalidID, clienttypes.NewHeight(1, 100), 0, + ), + types.CallbackTypeAcknowledgementPacket, + types.CallbackData{ + CallbackAddress: ibctesting.TestAccAddress, + ExecutionGasLimit: 100_000, + CommitGasLimit: 200_000, + ApplicationVersion: transfertypes.V1, + }, + nil, + func() []abci.Event { + return sdk.Events{ + sdk.NewEvent( + types.EventTypeSourceCallback, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(types.AttributeKeyCallbackType, string(types.CallbackTypeAcknowledgementPacket)), + sdk.NewAttribute(types.AttributeKeyCallbackAddress, ibctesting.TestAccAddress), + sdk.NewAttribute(types.AttributeKeyCallbackGasLimit, "100000"), + sdk.NewAttribute(types.AttributeKeyCallbackCommitGasLimit, "200000"), + sdk.NewAttribute(types.AttributeKeyCallbackSourcePortID, ibctesting.MockPort), + sdk.NewAttribute(types.AttributeKeyCallbackSourceChannelID, ibctesting.FirstChannelID), + sdk.NewAttribute(types.AttributeKeyCallbackSequence, "1"), + sdk.NewAttribute(types.AttributeKeyCallbackResult, types.AttributeValueCallbackSuccess), + sdk.NewAttribute(types.AttributeKeyCallbackBaseApplicationVersion, transfertypes.V1), + ), + }.ToABCIEvents() + }, + }, + { + "success: send packet callback", + channeltypes.NewPacket( + ibctesting.MockPacketData, 1, ibctesting.MockPort, ibctesting.FirstChannelID, + transfertypes.PortID, ibctesting.InvalidID, clienttypes.NewHeight(1, 100), 0, + ), + types.CallbackTypeSendPacket, + types.CallbackData{ + CallbackAddress: ibctesting.TestAccAddress, + ExecutionGasLimit: 100_000, + CommitGasLimit: 200_000, + ApplicationVersion: transfertypes.V1, + }, + nil, + func() []abci.Event { + return sdk.Events{ + sdk.NewEvent( + types.EventTypeSourceCallback, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(types.AttributeKeyCallbackType, string(types.CallbackTypeSendPacket)), + sdk.NewAttribute(types.AttributeKeyCallbackAddress, ibctesting.TestAccAddress), + sdk.NewAttribute(types.AttributeKeyCallbackGasLimit, "100000"), + sdk.NewAttribute(types.AttributeKeyCallbackCommitGasLimit, "200000"), + sdk.NewAttribute(types.AttributeKeyCallbackSourcePortID, ibctesting.MockPort), + sdk.NewAttribute(types.AttributeKeyCallbackSourceChannelID, ibctesting.FirstChannelID), + sdk.NewAttribute(types.AttributeKeyCallbackSequence, "1"), + sdk.NewAttribute(types.AttributeKeyCallbackResult, types.AttributeValueCallbackSuccess), + sdk.NewAttribute(types.AttributeKeyCallbackBaseApplicationVersion, transfertypes.V1), + ), + }.ToABCIEvents() + }, + }, + { + "success: timeout callback", + channeltypes.NewPacket( + ibctesting.MockPacketData, 1, ibctesting.MockPort, ibctesting.FirstChannelID, + transfertypes.PortID, ibctesting.InvalidID, clienttypes.NewHeight(1, 100), 0, + ), + types.CallbackTypeTimeoutPacket, + types.CallbackData{ + CallbackAddress: ibctesting.TestAccAddress, + ExecutionGasLimit: 100_000, + CommitGasLimit: 200_000, + ApplicationVersion: transfertypes.V1, + }, + nil, + func() []abci.Event { + return sdk.Events{ + sdk.NewEvent( + types.EventTypeSourceCallback, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(types.AttributeKeyCallbackType, string(types.CallbackTypeTimeoutPacket)), + sdk.NewAttribute(types.AttributeKeyCallbackAddress, ibctesting.TestAccAddress), + sdk.NewAttribute(types.AttributeKeyCallbackGasLimit, "100000"), + sdk.NewAttribute(types.AttributeKeyCallbackCommitGasLimit, "200000"), + sdk.NewAttribute(types.AttributeKeyCallbackSourcePortID, ibctesting.MockPort), + sdk.NewAttribute(types.AttributeKeyCallbackSourceChannelID, ibctesting.FirstChannelID), + sdk.NewAttribute(types.AttributeKeyCallbackSequence, "1"), + sdk.NewAttribute(types.AttributeKeyCallbackResult, types.AttributeValueCallbackSuccess), + sdk.NewAttribute(types.AttributeKeyCallbackBaseApplicationVersion, transfertypes.V1), + ), + }.ToABCIEvents() + }, + }, + { + "success: receive packet callback", + channeltypes.NewPacket( + ibctesting.MockPacketData, 1, ibctesting.MockPort, ibctesting.FirstChannelID, + transfertypes.PortID, ibctesting.InvalidID, clienttypes.NewHeight(1, 100), 0, + ), + types.CallbackTypeReceivePacket, + types.CallbackData{ + CallbackAddress: ibctesting.TestAccAddress, + ExecutionGasLimit: 100_000, + CommitGasLimit: 200_000, + ApplicationVersion: transfertypes.V1, + }, + nil, + func() []abci.Event { + return sdk.Events{ + sdk.NewEvent( + types.EventTypeDestinationCallback, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(types.AttributeKeyCallbackType, string(types.CallbackTypeReceivePacket)), + sdk.NewAttribute(types.AttributeKeyCallbackAddress, ibctesting.TestAccAddress), + sdk.NewAttribute(types.AttributeKeyCallbackGasLimit, "100000"), + sdk.NewAttribute(types.AttributeKeyCallbackCommitGasLimit, "200000"), + sdk.NewAttribute(types.AttributeKeyCallbackDestPortID, transfertypes.PortID), + sdk.NewAttribute(types.AttributeKeyCallbackDestChannelID, ibctesting.InvalidID), + sdk.NewAttribute(types.AttributeKeyCallbackSequence, "1"), + sdk.NewAttribute(types.AttributeKeyCallbackResult, types.AttributeValueCallbackSuccess), + sdk.NewAttribute(types.AttributeKeyCallbackBaseApplicationVersion, transfertypes.V1), + ), + }.ToABCIEvents() + }, + }, + { + "success: unknown callback", + channeltypes.NewPacket( + ibctesting.MockPacketData, 1, ibctesting.MockPort, ibctesting.FirstChannelID, + transfertypes.PortID, ibctesting.InvalidID, clienttypes.NewHeight(1, 100), 0, + ), + "something", + types.CallbackData{ + CallbackAddress: ibctesting.TestAccAddress, + ExecutionGasLimit: 100_000, + CommitGasLimit: 200_000, + ApplicationVersion: transfertypes.V1, + }, + nil, + func() []abci.Event { + return sdk.Events{ + sdk.NewEvent( + types.EventTypeSourceCallback, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(types.AttributeKeyCallbackType, "something"), + sdk.NewAttribute(types.AttributeKeyCallbackAddress, ibctesting.TestAccAddress), + sdk.NewAttribute(types.AttributeKeyCallbackGasLimit, "100000"), + sdk.NewAttribute(types.AttributeKeyCallbackCommitGasLimit, "200000"), + sdk.NewAttribute(types.AttributeKeyCallbackSourcePortID, ibctesting.MockPort), + sdk.NewAttribute(types.AttributeKeyCallbackSourceChannelID, ibctesting.FirstChannelID), + sdk.NewAttribute(types.AttributeKeyCallbackSequence, "1"), + sdk.NewAttribute(types.AttributeKeyCallbackResult, types.AttributeValueCallbackSuccess), + sdk.NewAttribute(types.AttributeKeyCallbackBaseApplicationVersion, transfertypes.V1), + ), + }.ToABCIEvents() + }, + }, + { + "failure: ack callback with error", + channeltypes.NewPacket( + ibctesting.MockPacketData, 1, ibctesting.MockPort, ibctesting.FirstChannelID, + transfertypes.PortID, ibctesting.InvalidID, clienttypes.NewHeight(1, 100), 0, + ), + types.CallbackTypeAcknowledgementPacket, + types.CallbackData{ + CallbackAddress: ibctesting.TestAccAddress, + ExecutionGasLimit: 100_000, + CommitGasLimit: 200_000, + ApplicationVersion: transfertypes.V1, + }, + types.ErrNotPacketDataProvider, + func() []abci.Event { + return sdk.Events{ + sdk.NewEvent( + types.EventTypeSourceCallback, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + sdk.NewAttribute(types.AttributeKeyCallbackType, string(types.CallbackTypeAcknowledgementPacket)), + sdk.NewAttribute(types.AttributeKeyCallbackAddress, ibctesting.TestAccAddress), + sdk.NewAttribute(types.AttributeKeyCallbackGasLimit, "100000"), + sdk.NewAttribute(types.AttributeKeyCallbackCommitGasLimit, "200000"), + sdk.NewAttribute(types.AttributeKeyCallbackSourcePortID, ibctesting.MockPort), + sdk.NewAttribute(types.AttributeKeyCallbackSourceChannelID, ibctesting.FirstChannelID), + sdk.NewAttribute(types.AttributeKeyCallbackSequence, "1"), + sdk.NewAttribute(types.AttributeKeyCallbackResult, types.AttributeValueCallbackFailure), + sdk.NewAttribute(types.AttributeKeyCallbackError, types.ErrNotPacketDataProvider.Error()), + sdk.NewAttribute(types.AttributeKeyCallbackBaseApplicationVersion, transfertypes.V1), + ), + }.ToABCIEvents() + }, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + newCtx := sdk.Context{}.WithEventManager(sdk.NewEventManager()) + switch tc.callbackType { + case types.CallbackTypeReceivePacket: + types.EmitCallbackEvent( + newCtx, tc.packet.GetDestPort(), tc.packet.GetDestChannel(), + tc.packet.GetSequence(), tc.callbackType, tc.callbackData, tc.callbackError, + ) + + default: + types.EmitCallbackEvent( + newCtx, tc.packet.GetSourcePort(), tc.packet.GetSourceChannel(), + tc.packet.GetSequence(), tc.callbackType, tc.callbackData, tc.callbackError, + ) + } + + actualEvents := newCtx.EventManager().Events().ToABCIEvents() + expectedEvents := tc.expectedEvents() + + ibctesting.AssertEvents(&s.Suite, expectedEvents, actualEvents) + }) + } +} diff --git a/modules/apps/callbacks/types/expected_keepers.go b/modules/apps/callbacks/types/expected_keepers.go new file mode 100644 index 0000000..d9e7d17 --- /dev/null +++ b/modules/apps/callbacks/types/expected_keepers.go @@ -0,0 +1,108 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// ContractKeeper defines the entry points exposed to the VM module which invokes a smart contract +type ContractKeeper interface { + // IBCSendPacketCallback is called in the source chain when a PacketSend is executed. The + // packetSenderAddress is determined by the underlying module, and may be empty if the sender is + // unknown or undefined. The contract is expected to handle the callback within the user defined + // gas limit, and handle any errors, or panics gracefully. + // This entry point is called with a cached context. If an error is returned, then the changes in + // this context will not be persisted, and the error will be propagated to the underlying IBC + // application, resulting in a packet send failure. + // + // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform + // validation on the origin of a given packet. It is recommended to perform the same validation + // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This + // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. + // + // The version provided is the base application version for the given packet send. This allows + // contracts to determine how to unmarshal the packetData. + IBCSendPacketCallback( + cachedCtx sdk.Context, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + packetData []byte, + contractAddress, + packetSenderAddress string, + version string, + ) error + // IBCOnAcknowledgementPacketCallback is called in the source chain when a packet acknowledgement + // is received. The packetSenderAddress is determined by the underlying module, and may be empty if + // the sender is unknown or undefined. The contract is expected to handle the callback within the + // user defined gas limit, and handle any errors, or panics gracefully. + // This entry point is called with a cached context. If an error is returned, then the changes in + // this context will not be persisted, but the packet lifecycle will not be blocked. + // + // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform + // validation on the origin of a given packet. It is recommended to perform the same validation + // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This + // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. + // + // The version provided is the base application version for the given packet send. This allows + // contracts to determine how to unmarshal the packetData. + IBCOnAcknowledgementPacketCallback( + cachedCtx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, + contractAddress, + packetSenderAddress string, + version string, + ) error + // IBCOnTimeoutPacketCallback is called in the source chain when a packet is not received before + // the timeout height. The packetSenderAddress is determined by the underlying module, and may be + // empty if the sender is unknown or undefined. The contract is expected to handle the callback + // within the user defined gas limit, and handle any error, out of gas, or panics gracefully. + // This entry point is called with a cached context. If an error is returned, then the changes in + // this context will not be persisted, but the packet lifecycle will not be blocked. + // + // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform + // validation on the origin of a given packet. It is recommended to perform the same validation + // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This + // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks. + // + // The version provided is the base application version for the given packet send. This allows + // contracts to determine how to unmarshal the packetData. + IBCOnTimeoutPacketCallback( + cachedCtx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, + contractAddress, + packetSenderAddress string, + version string, + ) error + // IBCReceivePacketCallback is called in the destination chain when a packet acknowledgement is written. + // The contract is expected to handle the callback within the user defined gas limit, and handle any errors, + // out of gas, or panics gracefully. + // This entry point is called with a cached context. If an error is returned, then the changes in + // this context will not be persisted, but the packet lifecycle will not be blocked. + // + // The version provided is the base application version for the given packet send. This allows + // contracts to determine how to unmarshal the packetData. + IBCReceivePacketCallback( + cachedCtx sdk.Context, + packet ibcexported.PacketI, + ack ibcexported.Acknowledgement, + contractAddress string, + version string, + ) error +} + +type ChannelKeeperV2 interface { + GetAsyncPacket( + ctx sdk.Context, + clientID string, + sequence uint64, + ) (channeltypesv2.Packet, bool) +} diff --git a/modules/apps/callbacks/types/export_test.go b/modules/apps/callbacks/types/export_test.go new file mode 100644 index 0000000..ddde973 --- /dev/null +++ b/modules/apps/callbacks/types/export_test.go @@ -0,0 +1,15 @@ +package types + +/* + This file is to allow for unexported functions to be accessible to the testing package. +*/ + +// GetCallbackAddress is a wrapper around getCallbackAddress to allow the function to be directly called in tests. +func GetCallbackAddress(callbackData map[string]any) (string, error) { + return getCallbackAddress(callbackData) +} + +// GetUserDefinedGasLimit is a wrapper around getUserDefinedGasLimit to allow the function to be directly called in tests. +func GetUserDefinedGasLimit(callbackData map[string]any) (uint64, error) { + return getUserDefinedGasLimit(callbackData) +} diff --git a/modules/apps/callbacks/types/keys.go b/modules/apps/callbacks/types/keys.go new file mode 100644 index 0000000..dfd542b --- /dev/null +++ b/modules/apps/callbacks/types/keys.go @@ -0,0 +1,33 @@ +package types + +type CallbackType string + +const ( + ModuleName = "ibccallbacks" + + CallbackTypeSendPacket CallbackType = "send_packet" + CallbackTypeAcknowledgementPacket CallbackType = "acknowledgement_packet" + CallbackTypeTimeoutPacket CallbackType = "timeout_packet" + CallbackTypeReceivePacket CallbackType = "receive_packet" + + // Source callback packet data is set inside the underlying packet data using the this key. + // ICS20 and ICS27 will store the callback packet data in the memo field as a json object. + // The expected format is as follows: + // {"src_callback": { ... }} + SourceCallbackKey = "src_callback" + // Destination callback packet data is set inside the underlying packet data using the this key. + // ICS20 and ICS27 will store the callback packet data in the memo field as a json object. + // The expected format is as follows: + // {"dest_callback": { ... }} + DestinationCallbackKey = "dest_callback" + // Callbacks' packet data is expected to contain the callback address under this key. + // The expected format for ICS20 and ICS27 memo field is as follows: + // { "{callbackKey}": { "address": {stringCallbackAddress}} + CallbackAddressKey = "address" + // Callbacks' packet data is expected to specify the user defined gas limit under this key. + // The expected format for ICS20 and ICS27 memo field is as follows: + // { "{callbackKey}": { ... , "gas_limit": {stringForCallback} } + UserDefinedGasLimitKey = "gas_limit" + // CalldataKey is the key used to store the calldata in the callback packet data. + CalldataKey = "calldata" +) diff --git a/modules/apps/callbacks/types/types_test.go b/modules/apps/callbacks/types/types_test.go new file mode 100644 index 0000000..d3379a2 --- /dev/null +++ b/modules/apps/callbacks/types/types_test.go @@ -0,0 +1,32 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" + + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +// CallbacksTypesTestSuite defines the needed instances and methods to test callbacks +type CallbacksTypesTestSuite struct { + suite.Suite + + coord *ibctesting.Coordinator + + chainA, chainB *ibctesting.TestChain + + path *ibctesting.Path +} + +// SetupTest creates a coordinator with 2 test chains. +func (s *CallbacksTypesTestSuite) SetupTest() { + s.coord = ibctesting.NewCoordinator(s.T(), 2) + s.chainA = s.coord.GetChain(ibctesting.GetChainID(1)) + s.chainB = s.coord.GetChain(ibctesting.GetChainID(2)) + s.path = ibctesting.NewPath(s.chainA, s.chainB) +} + +func TestCallbacksTypesTestSuite(t *testing.T) { + suite.Run(t, new(CallbacksTypesTestSuite)) +} diff --git a/modules/apps/callbacks/v2/ibc_middleware.go b/modules/apps/callbacks/v2/ibc_middleware.go new file mode 100644 index 0000000..a66055e --- /dev/null +++ b/modules/apps/callbacks/v2/ibc_middleware.go @@ -0,0 +1,454 @@ +package v2 + +import ( + "bytes" + "errors" + "fmt" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/internal" + "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + "github.com/cosmos/ibc-go/v10/modules/core/api" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var ( + _ api.IBCModule = (*IBCMiddleware)(nil) + _ exported.Acknowledgement = (*RecvAcknowledgement)(nil) +) + +// Create internal implementation of exported.Acknowledgement +// to pass the app acknowledgement bytes to contractKeeper IBCReceivePacketCallback +// interface +type RecvAcknowledgement []byte + +// RecvPacket only passes callback to contract if the acknowledgement +// is successful. Thus, we can just return true here. +func (rack RecvAcknowledgement) Success() bool { + return !bytes.Equal(rack, channeltypesv2.ErrorAcknowledgement[:]) +} + +// RecvPacket passes the application acknowledgment directly to contract +func (rack RecvAcknowledgement) Acknowledgement() []byte { + return rack +} + +// IBCMiddleware implements the IBC v2 middleware interface +// with the underlying application. +type IBCMiddleware struct { + app types.CallbacksCompatibleModuleV2 + writeAckWrapper api.WriteAcknowledgementWrapper + + contractKeeper types.ContractKeeper + chanKeeperV2 types.ChannelKeeperV2 + + // maxCallbackGas defines the maximum amount of gas that a callback actor can ask the + // relayer to pay for. If a callback fails due to insufficient gas, the entire tx + // is reverted if the relayer hadn't provided the minimum(userDefinedGas, maxCallbackGas). + // If the actor hasn't defined a gas limit, then it is assumed to be the maxCallbackGas. + maxCallbackGas uint64 +} + +// NewIBCMiddleware creates a new IBCMiddleware instance given the keeper and underlying application. +// The underlying application must implement the required callback interfaces. +func NewIBCMiddleware( + app api.IBCModule, writeAckWrapper api.WriteAcknowledgementWrapper, + contractKeeper types.ContractKeeper, chanKeeperV2 types.ChannelKeeperV2, maxCallbackGas uint64, +) IBCMiddleware { + packetDataUnmarshalerApp, ok := app.(types.CallbacksCompatibleModuleV2) + if !ok { + panic(fmt.Errorf("underlying application does not implement %T", (*types.CallbacksCompatibleModule)(nil))) + } + + if contractKeeper == nil { + panic(errors.New("contract keeper cannot be nil")) + } + + if writeAckWrapper == nil { + panic(errors.New("write acknowledgement wrapper cannot be nil")) + } + + if chanKeeperV2 == nil { + panic(errors.New("channel keeper v2 cannot be nil")) + } + + if maxCallbackGas == 0 { + panic(errors.New("maxCallbackGas cannot be zero")) + } + + return IBCMiddleware{ + app: packetDataUnmarshalerApp, + writeAckWrapper: writeAckWrapper, + contractKeeper: contractKeeper, + chanKeeperV2: chanKeeperV2, + maxCallbackGas: maxCallbackGas, + } +} + +// WithWriteAckWrapper sets the WriteAcknowledgementWrapper for the middleware. +func (im *IBCMiddleware) WithWriteAckWrapper(writeAckWrapper api.WriteAcknowledgementWrapper) { + im.writeAckWrapper = writeAckWrapper +} + +// GetWriteAckWrapper returns the WriteAckWrapper +func (im *IBCMiddleware) GetWriteAckWrapper() api.WriteAcknowledgementWrapper { + return im.writeAckWrapper +} + +// OnSendPacket implements source callbacks for sending packets. +// It defers to the underlying application and then calls the contract callback. +// If the contract callback returns an error, panics, or runs out of gas, then +// the packet send is rejected. +func (im IBCMiddleware) OnSendPacket( + ctx sdk.Context, + sourceClient string, + destinationClient string, + sequence uint64, + payload channeltypesv2.Payload, + signer sdk.AccAddress, +) error { + err := im.app.OnSendPacket(ctx, sourceClient, destinationClient, sequence, payload, signer) + if err != nil { + return err + } + + packetData, err := im.app.UnmarshalPacketData(payload) + // OnSendPacket is not blocked if the packet does not opt-in to callbacks + if err != nil { + return nil + } + + cbData, isCbPacket, err := types.GetCallbackData( + packetData, payload.GetVersion(), payload.GetSourcePort(), + ctx.GasMeter().GasRemaining(), im.maxCallbackGas, types.SourceCallbackKey, + ) + // OnSendPacket is not blocked if the packet does not opt-in to callbacks + if !isCbPacket { + return nil + } + // OnSendPacket is blocked if the packet opts-in to callbacks but the callback data is invalid + if err != nil { + return err + } + + callbackExecutor := func(cachedCtx sdk.Context) error { + return im.contractKeeper.IBCSendPacketCallback( + cachedCtx, payload.SourcePort, sourceClient, clienttypes.Height{}, 0, payload.Value, cbData.CallbackAddress, cbData.SenderAddress, payload.Version, + ) + } + + err = internal.ProcessCallback(ctx, types.CallbackTypeSendPacket, cbData, callbackExecutor) + // contract keeper is allowed to reject the packet send. + if err != nil { + return err + } + + types.EmitCallbackEvent(ctx, payload.SourcePort, sourceClient, sequence, types.CallbackTypeSendPacket, cbData, nil) + return nil +} + +// OnRecvPacket implements the ReceivePacket destination callbacks for the ibc-callbacks middleware during +// synchronous packet acknowledgement. +// It defers to the underlying application and then calls the contract callback. +// If the contract callback runs out of gas and may be retried with a higher gas limit then the state changes are +// reverted via a panic. +func (im IBCMiddleware) OnRecvPacket( + ctx sdk.Context, + sourceClient string, + destinationClient string, + sequence uint64, + payload channeltypesv2.Payload, + relayer sdk.AccAddress, +) channeltypesv2.RecvPacketResult { + recvResult := im.app.OnRecvPacket(ctx, sourceClient, destinationClient, sequence, payload, relayer) + // if ack is nil (asynchronous acknowledgements), then the callback will be handled in WriteAcknowledgement + // if ack is not successful, all state changes are reverted. If a packet cannot be received, then there is + // no need to execute a callback on the receiving chain. + if recvResult.Status == channeltypesv2.PacketStatus_Async || recvResult.Status == channeltypesv2.PacketStatus_Failure { + return recvResult + } + + packetData, err := im.app.UnmarshalPacketData(payload) + // OnRecvPacket is not blocked if the packet does not opt-in to callbacks + if err != nil { + return recvResult + } + + cbData, isCbPacket, err := types.GetCallbackData( + packetData, payload.GetVersion(), payload.GetDestinationPort(), + ctx.GasMeter().GasRemaining(), im.maxCallbackGas, types.DestinationCallbackKey, + ) + // OnRecvPacket is not blocked if the packet does not opt-in to callbacks + if !isCbPacket { + return recvResult + } + // OnRecvPacket is blocked if the packet opts-in to callbacks but the callback data is invalid + if err != nil { + return channeltypesv2.RecvPacketResult{ + Status: channeltypesv2.PacketStatus_Failure, + } + } + + callbackExecutor := func(cachedCtx sdk.Context) error { + // reconstruct a channel v1 packet from the v2 packet + // in order to preserve the same interface for the contract keeper + packetv1 := channeltypes.Packet{ + Sequence: sequence, + SourcePort: payload.SourcePort, + SourceChannel: sourceClient, + DestinationPort: payload.DestinationPort, + DestinationChannel: destinationClient, + Data: payload.Value, + TimeoutHeight: clienttypes.Height{}, + TimeoutTimestamp: 0, + } + // wrap the individual acknowledgement into the RecvAcknowledgement since it implements the exported.Acknowledgement interface + // since we return early on failure, we are guaranteed that the ack is a successful acknowledgement + ack := RecvAcknowledgement(recvResult.Acknowledgement) + return im.contractKeeper.IBCReceivePacketCallback(cachedCtx, packetv1, ack, cbData.CallbackAddress, payload.Version) + } + + // callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions + err = internal.ProcessCallback(ctx, types.CallbackTypeReceivePacket, cbData, callbackExecutor) + types.EmitCallbackEvent( + ctx, payload.DestinationPort, destinationClient, sequence, + types.CallbackTypeReceivePacket, cbData, err, + ) + if err != nil { + return channeltypesv2.RecvPacketResult{ + Status: channeltypesv2.PacketStatus_Failure, + } + } + + return recvResult +} + +// OnAcknowledgementPacket implements source callbacks for acknowledgement packets. +// It defers to the underlying application and then calls the contract callback. +// If the contract callback runs out of gas and may be retried with a higher gas limit then the state changes are +// reverted via a panic. +func (im IBCMiddleware) OnAcknowledgementPacket( + ctx sdk.Context, + sourceClient string, + destinationClient string, + sequence uint64, + acknowledgement []byte, + payload channeltypesv2.Payload, + relayer sdk.AccAddress, +) error { + // we first call the underlying app to handle the acknowledgement + err := im.app.OnAcknowledgementPacket(ctx, sourceClient, destinationClient, sequence, acknowledgement, payload, relayer) + if err != nil { + return err + } + + packetData, err := im.app.UnmarshalPacketData(payload) + // OnAcknowledgementPacket is not blocked if the packet does not opt-in to callbacks + if err != nil { + return nil + } + + cbData, isCbPacket, err := types.GetCallbackData( + packetData, payload.GetVersion(), payload.GetSourcePort(), + ctx.GasMeter().GasRemaining(), im.maxCallbackGas, types.SourceCallbackKey, + ) + // OnAcknowledgementPacket is not blocked if the packet does not opt-in to callbacks + if !isCbPacket { + return nil + } + // OnAcknowledgementPacket is blocked if the packet opts-in to callbacks but the callback data is invalid + // This should never occur since this error is already checked `OnSendPacket` + if err != nil { + return err + } + + callbackExecutor := func(cachedCtx sdk.Context) error { + // reconstruct a channel v1 packet from the v2 packet + // in order to preserve the same interface for the contract keeper + packetv1 := channeltypes.Packet{ + Sequence: sequence, + SourcePort: payload.SourcePort, + SourceChannel: sourceClient, + DestinationPort: payload.DestinationPort, + DestinationChannel: destinationClient, + Data: payload.Value, + TimeoutHeight: clienttypes.Height{}, + TimeoutTimestamp: 0, + } + // NOTE: The callback is receiving the acknowledgement that the application received for its particular payload. + // In the case of a successful acknowledgement, this will be the acknowledgement sent by the counterparty application for the given payload + // In the case of an error acknowledgement, this will be the sentinel error acknowledgement bytes defined by IBC v2 protocol. + // Thus, the contract must be aware that the sentinel error acknowledgement signals a failed receive + // and the contract must handle this error case and the corresponding success case (ie ack != ErrorAcknowledgement) accordingly. + return im.contractKeeper.IBCOnAcknowledgementPacketCallback( + cachedCtx, packetv1, acknowledgement, relayer, cbData.CallbackAddress, cbData.SenderAddress, payload.Version, + ) + } + + // callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions + err = internal.ProcessCallback(ctx, types.CallbackTypeAcknowledgementPacket, cbData, callbackExecutor) + types.EmitCallbackEvent( + ctx, payload.SourcePort, sourceClient, sequence, + types.CallbackTypeAcknowledgementPacket, cbData, err, + ) + + return nil +} + +// OnTimeoutPacket implements timeout source callbacks for the ibc-callbacks middleware. +// It defers to the underlying application and then calls the contract callback. +// If the contract callback runs out of gas and may be retried with a higher gas limit then the state changes are +// reverted via a panic. +// OnTimeoutPacket is executed when a packet has timed out on the receiving chain. +func (im IBCMiddleware) OnTimeoutPacket( + ctx sdk.Context, + sourceClient string, + destinationClient string, + sequence uint64, + payload channeltypesv2.Payload, + relayer sdk.AccAddress, +) error { + err := im.app.OnTimeoutPacket(ctx, sourceClient, destinationClient, sequence, payload, relayer) + if err != nil { + return err + } + + packetData, err := im.app.UnmarshalPacketData(payload) + if err != nil { + return err + } + + cbData, isCbPacket, err := types.GetCallbackData( + packetData, payload.GetVersion(), payload.GetSourcePort(), + ctx.GasMeter().GasRemaining(), im.maxCallbackGas, types.SourceCallbackKey, + ) + // OnTimeoutPacket is not blocked if the packet does not opt-in to callbacks + if !isCbPacket { + return nil + } + // OnTimeoutPacket is blocked if the packet opts-in to callbacks but the callback data is invalid + // This should never occur since this error is already checked `OnSendPacket` + if err != nil { + return err + } + + callbackExecutor := func(cachedCtx sdk.Context) error { + // reconstruct a channel v1 packet from the v2 packet + // in order to preserve the same interface for the contract keeper + packetv1 := channeltypes.Packet{ + Sequence: sequence, + SourcePort: payload.SourcePort, + SourceChannel: sourceClient, + DestinationPort: payload.DestinationPort, + DestinationChannel: destinationClient, + Data: payload.Value, + TimeoutHeight: clienttypes.Height{}, + TimeoutTimestamp: 0, + } + return im.contractKeeper.IBCOnTimeoutPacketCallback( + cachedCtx, packetv1, relayer, cbData.CallbackAddress, cbData.SenderAddress, payload.Version, + ) + } + + // callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions + err = internal.ProcessCallback(ctx, types.CallbackTypeTimeoutPacket, cbData, callbackExecutor) + types.EmitCallbackEvent( + ctx, payload.SourcePort, sourceClient, sequence, + types.CallbackTypeTimeoutPacket, cbData, err, + ) + + return nil +} + +// WriteAcknowledgement implements the ReceivePacket destination callbacks for the ibc-callbacks middleware +// during asynchronous packet acknowledgement. +// It defers to the underlying application and then calls the contract callback. +// If the contract callback runs out of gas and may be retried with a higher gas limit then the state changes are +// reverted via a panic. +func (im IBCMiddleware) WriteAcknowledgement( + ctx sdk.Context, + clientID string, + sequence uint64, + ack channeltypesv2.Acknowledgement, +) error { + packet, found := im.chanKeeperV2.GetAsyncPacket(ctx, clientID, sequence) + if !found { + return errorsmod.Wrapf(channeltypesv2.ErrInvalidAcknowledgement, "async packet not found for clientID (%s) and sequence (%d)", clientID, sequence) + } + + err := im.writeAckWrapper.WriteAcknowledgement(ctx, clientID, sequence, ack) + if err != nil { + return err + } + + // NOTE: use first payload as the payload that is being handled by callbacks middleware + // must reconsider if multipacket data gets supported with async packets + // TRACKING ISSUE: https://github.com/cosmos/ibc-go/issues/7950 + if len(packet.Payloads) != 1 { + return errorsmod.Wrapf(channeltypesv2.ErrInvalidAcknowledgement, "async packet has multiple payloads") + } + payload := packet.Payloads[0] + + packetData, err := im.app.UnmarshalPacketData(payload) + if err != nil { + return err + } + + cbData, isCbPacket, err := types.GetCallbackData( + packetData, payload.GetVersion(), payload.GetDestinationPort(), + ctx.GasMeter().GasRemaining(), im.maxCallbackGas, types.DestinationCallbackKey, + ) + // WriteAcknowledgement is not blocked if the packet does not opt-in to callbacks + if !isCbPacket { + return nil + } + // WriteAcknowledgement is blocked if the packet opts-in to callbacks but the callback data is invalid + // This should never occur since this error is already checked `OnRecvPacket` + if err != nil { + return err + } + + recvResult := channeltypesv2.RecvPacketResult{ + Status: channeltypesv2.PacketStatus_Success, + Acknowledgement: ack.AppAcknowledgements[0], + } + callbackExecutor := func(cachedCtx sdk.Context) error { + // reconstruct a channel v1 packet from the v2 packet + // in order to preserve the same interface for the contract keeper + packetv1 := channeltypes.Packet{ + Sequence: sequence, + SourcePort: payload.SourcePort, + SourceChannel: packet.SourceClient, + DestinationPort: payload.DestinationPort, + DestinationChannel: packet.DestinationClient, + Data: payload.Value, + TimeoutHeight: clienttypes.Height{}, + TimeoutTimestamp: 0, + } + // wrap the individual acknowledgement into the channeltypesv2.Acknowledgement since it implements the exported.Acknowledgement interface + var ack channeltypesv2.Acknowledgement + if recvResult.Status == channeltypesv2.PacketStatus_Failure { + ack = channeltypesv2.NewAcknowledgement(channeltypesv2.ErrorAcknowledgement[:]) + } else { + ack = channeltypesv2.NewAcknowledgement(recvResult.Acknowledgement) + } + return im.contractKeeper.IBCReceivePacketCallback( + cachedCtx, packetv1, ack, cbData.CallbackAddress, payload.Version, + ) + } + + // callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions + err = internal.ProcessCallback(ctx, types.CallbackTypeReceivePacket, cbData, callbackExecutor) + types.EmitCallbackEvent( + ctx, payload.DestinationPort, clientID, sequence, + types.CallbackTypeReceivePacket, cbData, err, + ) + + return nil +} diff --git a/modules/apps/callbacks/v2/ibc_middleware_test.go b/modules/apps/callbacks/v2/ibc_middleware_test.go new file mode 100644 index 0000000..2349e8e --- /dev/null +++ b/modules/apps/callbacks/v2/ibc_middleware_test.go @@ -0,0 +1,848 @@ +package v2_test + +import ( + "errors" + "fmt" + "time" + + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/testing/simapp" + "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/types" + v2 "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/v2" + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + channelkeeperv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/keeper" + channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + "github.com/cosmos/ibc-go/v10/modules/core/api" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + ibcmock "github.com/cosmos/ibc-go/v10/testing/mock" + ibcmockv2 "github.com/cosmos/ibc-go/v10/testing/mock/v2" +) + +func (s *CallbacksTestSuite) TestNewIBCMiddleware() { + testCases := []struct { + name string + instantiateFn func() + expError error + }{ + { + "success", + func() { + _ = v2.NewIBCMiddleware(ibcmockv2.IBCModule{}, &channelkeeperv2.Keeper{}, simapp.ContractKeeper{}, &channelkeeperv2.Keeper{}, maxCallbackGas) + }, + nil, + }, + { + "panics with nil ics4wrapper", + func() { + _ = v2.NewIBCMiddleware(ibcmockv2.IBCModule{}, nil, simapp.ContractKeeper{}, &channelkeeperv2.Keeper{}, maxCallbackGas) + }, + errors.New("write acknowledgement wrapper cannot be nil"), + }, + { + "panics with nil underlying app", + func() { + _ = v2.NewIBCMiddleware(nil, &channelkeeperv2.Keeper{}, simapp.ContractKeeper{}, &channelkeeperv2.Keeper{}, maxCallbackGas) + }, + fmt.Errorf("underlying application does not implement %T", (*types.CallbacksCompatibleModule)(nil)), + }, + { + "panics with nil contract keeper", + func() { + _ = v2.NewIBCMiddleware(ibcmockv2.IBCModule{}, &channelkeeperv2.Keeper{}, nil, &channelkeeperv2.Keeper{}, maxCallbackGas) + }, + errors.New("contract keeper cannot be nil"), + }, + { + "panics with nil channel v2 keeper", + func() { + _ = v2.NewIBCMiddleware(ibcmockv2.IBCModule{}, &channelkeeperv2.Keeper{}, simapp.ContractKeeper{}, nil, maxCallbackGas) + }, + errors.New("channel keeper v2 cannot be nil"), + }, + { + "panics with zero maxCallbackGas", + func() { + _ = v2.NewIBCMiddleware(ibcmockv2.IBCModule{}, &channelkeeperv2.Keeper{}, simapp.ContractKeeper{}, &channelkeeperv2.Keeper{}, uint64(0)) + }, + errors.New("maxCallbackGas cannot be zero"), + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + if tc.expError == nil { + s.Require().NotPanics(tc.instantiateFn, "unexpected panic: NewIBCMiddleware") + } else { + s.Require().PanicsWithError(tc.expError.Error(), tc.instantiateFn, "expected panic with error: ", tc.expError.Error()) + } + }) + } +} + +func (s *CallbacksTestSuite) TestWithWriteAckWrapper() { + s.setupChains() + + cbsMiddleware := v2.IBCMiddleware{} + s.Require().Nil(cbsMiddleware.GetWriteAckWrapper()) + + cbsMiddleware.WithWriteAckWrapper(s.chainA.App.GetIBCKeeper().ChannelKeeperV2) + writeAckWrapper := cbsMiddleware.GetWriteAckWrapper() + + s.Require().IsType((*channelkeeperv2.Keeper)(nil), writeAckWrapper) +} + +func (s *CallbacksTestSuite) TestSendPacket() { + var packetData transfertypes.FungibleTokenPacketData + + testCases := []struct { + name string + malleate func() + callbackType types.CallbackType + expPanic bool + expValue any + }{ + { + "success", + func() {}, + types.CallbackTypeSendPacket, + false, + nil, + }, + { + "success: callback data nonexistent", + func() { + //nolint:goconst + packetData.Memo = "" + }, + "none", + false, + nil, + }, + { + "failure: no-op on callback data is not valid", + func() { + //nolint:goconst + packetData.Memo = `{"src_callback": {"address": ""}}` + }, + "none", // improperly formatted callback data should result in no callback execution + false, + types.ErrInvalidCallbackData, + }, + { + "failure: callback execution fails", + func() { + packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s"}}`, simapp.ErrorContract) + }, + types.CallbackTypeSendPacket, + false, + ibcmock.MockApplicationCallbackError, // execution failure on SendPacket should prevent packet sends + }, + { + "failure: callback execution reach out of gas panic, but sufficient gas provided", + func() { + packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"400000"}}`, simapp.OogPanicContract) + }, + types.CallbackTypeSendPacket, + true, + storetypes.ErrorOutOfGas{Descriptor: fmt.Sprintf("mock %s callback oog panic", types.CallbackTypeSendPacket)}, + }, + { + "failure: callback execution reach out of gas error, but sufficient gas provided", + func() { + packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"400000"}}`, simapp.OogErrorContract) + }, + types.CallbackTypeSendPacket, + false, + errorsmod.Wrapf(types.ErrCallbackOutOfGas, "ibc %s callback out of gas", types.CallbackTypeSendPacket), + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() + + packetData = transfertypes.NewFungibleTokenPacketData( + ibctesting.TestCoin.Denom, + ibctesting.TestCoin.Amount.String(), + s.chainA.SenderAccount.GetAddress().String(), + ibctesting.TestAccAddress, + fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, simapp.SuccessContract), + ) + + tc.malleate() + + payload := channeltypesv2.NewPayload( + transfertypes.PortID, transfertypes.PortID, + transfertypes.V1, transfertypes.EncodingJSON, + packetData.GetBytes(), + ) + + ctx := s.chainA.GetContext() + gasLimit := ctx.GasMeter().Limit() + + var err error + sendPacket := func() { + cbs := s.chainA.App.GetIBCKeeper().ChannelKeeperV2.Router.Route(ibctesting.TransferPort) + + err = cbs.OnSendPacket(ctx, s.path.EndpointA.ClientID, s.path.EndpointB.ClientID, + 1, payload, s.chainA.SenderAccount.GetAddress()) + } + + expPass := tc.expValue == nil + switch { + case expPass: + sendPacket() + s.Require().Nil(err) + + expEvent, exists := GetExpectedEvent( + ctx, packetData, gasLimit, payload.Version, + transfertypes.PortID, s.path.EndpointA.ClientID, 1, types.CallbackTypeSendPacket, nil, + ) + if exists { + s.Require().Contains(ctx.EventManager().Events().ToABCIEvents(), expEvent) + } + + case tc.expPanic: + s.Require().PanicsWithValue(tc.expValue, sendPacket) + + default: + sendPacket() + s.Require().ErrorIs(err, tc.expValue.(error)) + } + + s.AssertHasExecutedExpectedCallback(tc.callbackType, expPass) + }) + } +} + +func (s *CallbacksTestSuite) TestOnAcknowledgementPacket() { + type expResult uint8 + const ( + noExecution expResult = iota + callbackFailed + callbackSuccess + ) + + var ( + packetData transfertypes.FungibleTokenPacketData + ack []byte + ctx sdk.Context + userGasLimit uint64 + ) + + panicError := errors.New("panic error") + + testCases := []struct { + name string + malleate func() + expResult expResult + expError error + }{ + { + "success", + func() {}, + callbackSuccess, + nil, + }, + { + "success: callback data nonexistent", + func() { + //nolint:goconst + packetData.Memo = "" + }, + noExecution, + nil, + }, + { + "failure: underlying app OnAcknowledgePacket fails", + func() { + ack = []byte("invalid ack") + }, + noExecution, + ibcerrors.ErrUnknownRequest, + }, + { + "failure: callback data is not valid", + func() { + //nolint:goconst + packetData.Memo = `{"src_callback": {"address": ""}}` + }, + noExecution, + types.ErrInvalidCallbackData, + }, + { + "failure: callback execution reach out of gas, but sufficient gas provided by relayer", + func() { + packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"%d"}}`, simapp.OogPanicContract, userGasLimit) + }, + callbackFailed, + nil, + }, + { + "failure: callback execution panics on insufficient gas provided by relayer", + func() { + packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"%d"}}`, simapp.OogPanicContract, userGasLimit) + + ctx = ctx.WithGasMeter(storetypes.NewGasMeter(300_000)) + }, + callbackFailed, + panicError, + }, + { + "failure: callback execution fails", + func() { + packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s"}}`, simapp.ErrorContract) + }, + callbackFailed, + nil, // execution failure in OnAcknowledgement should not block acknowledgement processing + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() + + userGasLimit = 600000 + packetData = transfertypes.NewFungibleTokenPacketData( + ibctesting.TestCoin.Denom, + ibctesting.TestCoin.Amount.String(), + ibctesting.TestAccAddress, + ibctesting.TestAccAddress, + fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"%d"}}`, simapp.SuccessContract, userGasLimit), + ) + + ack = channeltypes.NewResultAcknowledgement([]byte{1}).Acknowledgement() + ctx = s.chainA.GetContext() + + // may malleate packetData, ack, and ctx + tc.malleate() + + payload := channeltypesv2.NewPayload( + transfertypes.PortID, transfertypes.PortID, + transfertypes.V1, transfertypes.EncodingJSON, + packetData.GetBytes(), + ) + + gasLimit := ctx.GasMeter().Limit() + + // callbacks module is routed as top level middleware + cbs := s.chainA.App.GetIBCKeeper().ChannelKeeperV2.Router.Route(ibctesting.TransferPort) + + onAcknowledgementPacket := func() error { + return cbs.OnAcknowledgementPacket(ctx, s.path.EndpointA.ClientID, s.path.EndpointB.ClientID, 1, ack, payload, s.chainA.SenderAccount.GetAddress()) + } + + switch tc.expError { + case nil: + err := onAcknowledgementPacket() + s.Require().Nil(err) + + case panicError: + s.Require().PanicsWithValue(storetypes.ErrorOutOfGas{ + Descriptor: fmt.Sprintf("ibc %s callback out of gas; commitGasLimit: %d", types.CallbackTypeAcknowledgementPacket, userGasLimit), + }, func() { + _ = onAcknowledgementPacket() + }) + + default: + err := onAcknowledgementPacket() + s.Require().ErrorIs(err, tc.expError) + } + + sourceStatefulCounter := GetSimApp(s.chainA).MockContractKeeper.GetStateEntryCounter(s.chainA.GetContext()) + sourceCounters := GetSimApp(s.chainA).MockContractKeeper.Counters + + switch tc.expResult { + case noExecution: + s.Require().Len(sourceCounters, 0) + s.Require().Equal(uint8(0), sourceStatefulCounter) + + case callbackFailed: + s.Require().Len(sourceCounters, 1) + s.Require().Equal(1, sourceCounters[types.CallbackTypeAcknowledgementPacket]) + s.Require().Equal(uint8(0), sourceStatefulCounter) + + case callbackSuccess: + s.Require().Len(sourceCounters, 1) + s.Require().Equal(1, sourceCounters[types.CallbackTypeAcknowledgementPacket]) + s.Require().Equal(uint8(1), sourceStatefulCounter) + + expEvent, exists := GetExpectedEvent( + ctx, packetData, gasLimit, payload.Version, + payload.SourcePort, s.path.EndpointA.ClientID, 1, types.CallbackTypeAcknowledgementPacket, nil, + ) + s.Require().True(exists) + s.Require().Contains(ctx.EventManager().Events().ToABCIEvents(), expEvent) + } + }) + } +} + +func (s *CallbacksTestSuite) TestOnTimeoutPacket() { + type expResult uint8 + const ( + noExecution expResult = iota + callbackFailed + callbackSuccess + ) + + var ( + packetData transfertypes.FungibleTokenPacketData + ctx sdk.Context + ) + + testCases := []struct { + name string + malleate func() + expResult expResult + expValue any + }{ + { + "success", + func() {}, + callbackSuccess, + nil, + }, + { + "success: callback data nonexistent", + func() { + //nolint:goconst + packetData.Memo = "" + }, + noExecution, + nil, + }, + { + "failure: underlying app OnTimeoutPacket fails", + func() { + packetData.Amount = "invalid amount" + }, + noExecution, + transfertypes.ErrInvalidAmount, + }, + { + "failure: callback data is not valid", + func() { + //nolint:goconst + packetData.Memo = `{"src_callback": {"address": ""}}` + }, + noExecution, + types.ErrInvalidCallbackData, + }, + { + "failure: callback execution reach out of gas, but sufficient gas provided by relayer", + func() { + packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"400000"}}`, simapp.OogPanicContract) + }, + callbackFailed, + nil, + }, + { + "failure: callback execution panics on insufficient gas provided by relayer", + func() { + packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s"}}`, simapp.OogPanicContract) + + ctx = ctx.WithGasMeter(storetypes.NewGasMeter(300_000)) + }, + callbackFailed, + storetypes.ErrorOutOfGas{ + Descriptor: fmt.Sprintf("ibc %s callback out of gas; commitGasLimit: %d", types.CallbackTypeTimeoutPacket, maxCallbackGas), + }, + }, + { + "failure: callback execution fails", + func() { + packetData.Memo = fmt.Sprintf(`{"src_callback": {"address":"%s"}}`, simapp.ErrorContract) + }, + callbackFailed, + nil, // execution failure in OnTimeout should not block timeout processing + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() + + // NOTE: we call send packet so transfer is setup with the correct logic to + // succeed on timeout + userGasLimit := 600_000 + timeoutTimestamp := uint64(s.chainB.GetContext().BlockTime().Add(time.Second).Unix()) + packetData = transfertypes.NewFungibleTokenPacketData( + ibctesting.TestCoin.Denom, + ibctesting.TestCoin.Amount.String(), + s.chainA.SenderAccount.GetAddress().String(), + ibctesting.TestAccAddress, + fmt.Sprintf(`{"src_callback": {"address":"%s", "gas_limit":"%d"}}`, simapp.SuccessContract, userGasLimit), + ) + + payload := channeltypesv2.NewPayload( + transfertypes.PortID, transfertypes.PortID, + transfertypes.V1, transfertypes.EncodingJSON, + packetData.GetBytes(), + ) + + packet, err := s.path.EndpointA.MsgSendPacket(timeoutTimestamp, payload) + s.Require().NoError(err) + + ctx = s.chainA.GetContext() + gasLimit := ctx.GasMeter().Limit() + + tc.malleate() + + // update packet data in payload after malleate + payload.Value = packetData.GetBytes() + + // callbacks module is routed as top level middleware + cbs := s.chainA.App.GetIBCKeeper().ChannelKeeperV2.Router.Route(ibctesting.TransferPort) + + onTimeoutPacket := func() error { + return cbs.OnTimeoutPacket(ctx, s.path.EndpointA.ClientID, s.path.EndpointB.ClientID, 1, payload, s.chainA.SenderAccount.GetAddress()) + } + + switch expValue := tc.expValue.(type) { + case nil: + err := onTimeoutPacket() + s.Require().Nil(err) + case error: + err := onTimeoutPacket() + s.Require().ErrorIs(err, expValue) + default: + s.Require().PanicsWithValue(tc.expValue, func() { + _ = onTimeoutPacket() + }) + } + + sourceStatefulCounter := GetSimApp(s.chainA).MockContractKeeper.GetStateEntryCounter(s.chainA.GetContext()) + sourceCounters := GetSimApp(s.chainA).MockContractKeeper.Counters + + // account for SendPacket succeeding + switch tc.expResult { + case noExecution: + s.Require().Len(sourceCounters, 1) + s.Require().Equal(uint8(1), sourceStatefulCounter) + + case callbackFailed: + s.Require().Len(sourceCounters, 2) + s.Require().Equal(1, sourceCounters[types.CallbackTypeTimeoutPacket]) + s.Require().Equal(1, sourceCounters[types.CallbackTypeSendPacket]) + s.Require().Equal(uint8(1), sourceStatefulCounter) + + case callbackSuccess: + s.Require().Len(sourceCounters, 2) + s.Require().Equal(1, sourceCounters[types.CallbackTypeTimeoutPacket]) + s.Require().Equal(1, sourceCounters[types.CallbackTypeSendPacket]) + s.Require().Equal(uint8(2), sourceStatefulCounter) + + expEvent, exists := GetExpectedEvent( + ctx, packetData, gasLimit, payload.Version, + payload.SourcePort, s.path.EndpointA.ClientID, packet.Sequence, types.CallbackTypeTimeoutPacket, nil, + ) + s.Require().True(exists) + s.Require().Contains(ctx.EventManager().Events().ToABCIEvents(), expEvent) + } + }) + } +} + +func (s *CallbacksTestSuite) TestOnRecvPacket() { + type expResult uint8 + type expRecvStatus uint8 + const ( + noExecution expResult = iota + callbackFailed + callbackPanic + callbackSuccess + ) + const ( + success expRecvStatus = iota + panics + failure + ) + + var ( + packetData transfertypes.FungibleTokenPacketData + ctx sdk.Context + userGasLimit uint64 + ) + + testCases := []struct { + name string + malleate func() + expResult expResult + expRecvStatus expRecvStatus + }{ + { + "success", + func() {}, + callbackSuccess, + success, + }, + { + "success: callback data nonexistent", + func() { + //nolint:goconst + packetData.Memo = "" + }, + noExecution, + success, + }, + { + "failure: underlying app OnRecvPacket fails", + func() { + packetData.Denom = "" + }, + noExecution, + failure, + }, + { + "failure: no-op on callback data is not valid", + func() { + //nolint:goconst + packetData.Memo = `{"dest_callback": {"address": ""}}` + }, + noExecution, + failure, + }, + { + "failure: callback execution reach out of gas, but sufficient gas provided by relayer", + func() { + packetData.Memo = fmt.Sprintf(`{"dest_callback": {"address":"%s", "gas_limit":"%d"}}`, simapp.OogPanicContract, userGasLimit) + }, + callbackFailed, + failure, + }, + { + "failure: callback execution panics on insufficient gas provided by relayer", + func() { + packetData.Memo = fmt.Sprintf(`{"dest_callback": {"address":"%s", "gas_limit":"%d"}}`, simapp.OogPanicContract, userGasLimit) + + ctx = ctx.WithGasMeter(storetypes.NewGasMeter(300_000)) + }, + callbackFailed, + panics, + }, + { + "failure: callback execution fails", + func() { + packetData.Memo = fmt.Sprintf(`{"dest_callback": {"address":"%s"}}`, simapp.ErrorContract) + }, + callbackFailed, + failure, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() + + // set user gas limit above panic level in mock contract keeper + userGasLimit = 600_000 + packetData = transfertypes.NewFungibleTokenPacketData( + ibctesting.TestCoin.Denom, + ibctesting.TestCoin.Amount.String(), + ibctesting.TestAccAddress, + s.chainB.SenderAccount.GetAddress().String(), + fmt.Sprintf(`{"dest_callback": {"address":"%s", "gas_limit":"%d"}}`, ibctesting.TestAccAddress, userGasLimit), + ) + + payload := channeltypesv2.NewPayload( + transfertypes.PortID, transfertypes.PortID, + transfertypes.V1, transfertypes.EncodingJSON, + packetData.GetBytes(), + ) + + ctx = s.chainB.GetContext() + gasLimit := ctx.GasMeter().Limit() + + tc.malleate() + + // update packet data in payload after malleate + payload.Value = packetData.GetBytes() + + // callbacks module is routed as top level middleware + cbs := s.chainB.App.GetIBCKeeper().ChannelKeeperV2.Router.Route(ibctesting.TransferPort) + + onRecvPacket := func() channeltypesv2.RecvPacketResult { + return cbs.OnRecvPacket(ctx, s.path.EndpointA.ClientID, s.path.EndpointB.ClientID, 1, payload, s.chainB.SenderAccount.GetAddress()) + } + + switch tc.expRecvStatus { + case success: + recvResult := onRecvPacket() + s.Require().Equal(channeltypesv2.PacketStatus_Success, recvResult.Status) + + case panics: + s.Require().PanicsWithValue(storetypes.ErrorOutOfGas{ + Descriptor: fmt.Sprintf("ibc %s callback out of gas; commitGasLimit: %d", types.CallbackTypeReceivePacket, userGasLimit), + }, func() { + _ = onRecvPacket() + }) + + default: + recvResult := onRecvPacket() + s.Require().Equal(channeltypesv2.PacketStatus_Failure, recvResult.Status) + } + + destStatefulCounter := GetSimApp(s.chainB).MockContractKeeper.GetStateEntryCounter(s.chainB.GetContext()) + destCounters := GetSimApp(s.chainB).MockContractKeeper.Counters + + switch tc.expResult { + case noExecution: + s.Require().Len(destCounters, 0) + s.Require().Equal(uint8(0), destStatefulCounter) + + case callbackFailed: + s.Require().Len(destCounters, 1) + s.Require().Equal(1, destCounters[types.CallbackTypeReceivePacket]) + s.Require().Equal(uint8(0), destStatefulCounter) + + case callbackSuccess: + s.Require().Len(destCounters, 1) + s.Require().Equal(1, destCounters[types.CallbackTypeReceivePacket]) + s.Require().Equal(uint8(1), destStatefulCounter) + + expEvent, exists := GetExpectedEvent( + ctx, packetData, gasLimit, payload.Version, + payload.DestinationPort, s.path.EndpointB.ClientID, 1, types.CallbackTypeReceivePacket, nil, + ) + s.Require().True(exists) + s.Require().Contains(ctx.EventManager().Events().ToABCIEvents(), expEvent) + } + }) + } +} + +func (s *CallbacksTestSuite) TestWriteAcknowledgement() { + var ( + packetData transfertypes.FungibleTokenPacketData + destClient string + ctx sdk.Context + ack channeltypesv2.Acknowledgement + multiPayload bool + ) + + successAck := channeltypesv2.NewAcknowledgement(channeltypes.NewResultAcknowledgement([]byte{byte(1)}).Acknowledgement()) + + testCases := []struct { + name string + malleate func() + callbackType types.CallbackType + expError error + }{ + { + "success", + func() { + ack = successAck + }, + types.CallbackTypeReceivePacket, + nil, + }, + { + "success: callback data nonexistent", + func() { + packetData.Memo = "" + ack = successAck + }, + "none", + nil, + }, + { + "failure: callback data is not valid", + func() { + packetData.Memo = `{"dest_callback": {"address": ""}}` + }, + "none", // improperly formatted callback data should result in no callback execution + types.ErrInvalidCallbackData, + }, + { + "failure: ics4Wrapper WriteAcknowledgement call fails", + func() { + destClient = "invalid-client" + }, + "none", + channeltypesv2.ErrInvalidAcknowledgement, + }, + { + "failure: multipayload should fail", + func() { + multiPayload = true + }, + "none", + channeltypesv2.ErrInvalidAcknowledgement, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() + + // set user gas limit above panic level in mock contract keeper + packetData = transfertypes.NewFungibleTokenPacketData( + ibctesting.TestCoin.Denom, + ibctesting.TestCoin.Amount.String(), + ibctesting.TestAccAddress, + s.chainB.SenderAccount.GetAddress().String(), + fmt.Sprintf(`{"dest_callback": {"address":"%s", "gas_limit":"600000"}}`, ibctesting.TestAccAddress), + ) + + ctx = s.chainB.GetContext() + gasLimit := ctx.GasMeter().Limit() + destClient = s.path.EndpointB.ClientID + + tc.malleate() + + payload := channeltypesv2.NewPayload( + transfertypes.PortID, transfertypes.PortID, + transfertypes.V1, transfertypes.EncodingJSON, + packetData.GetBytes(), + ) + timeoutTimestamp := uint64(s.chainB.GetContext().BlockTime().Unix()) + var packet channeltypesv2.Packet + if multiPayload { + packet = channeltypesv2.NewPacket( + 1, s.path.EndpointA.ClientID, s.path.EndpointB.ClientID, + timeoutTimestamp, payload, payload, + ) + } else { + packet = channeltypesv2.NewPacket( + 1, s.path.EndpointA.ClientID, s.path.EndpointB.ClientID, + timeoutTimestamp, payload, + ) + } + // mock async receive manually so WriteAcknowledgement can pass + s.chainB.App.GetIBCKeeper().ChannelKeeperV2.SetAsyncPacket(ctx, packet.DestinationClient, packet.Sequence, packet) + s.chainB.App.GetIBCKeeper().ChannelKeeperV2.SetPacketReceipt(ctx, packet.DestinationClient, packet.Sequence) + + // callbacks module is routed as top level middleware + cbs := s.chainB.App.GetIBCKeeper().ChannelKeeperV2.Router.Route(ibctesting.TransferPort) + mw, ok := cbs.(api.WriteAcknowledgementWrapper) + s.Require().True(ok) + + err := mw.WriteAcknowledgement(ctx, destClient, packet.Sequence, ack) + + expPass := tc.expError == nil + s.AssertHasExecutedExpectedCallback(tc.callbackType, expPass) + + if expPass { + s.Require().NoError(err) + + expEvent, exists := GetExpectedEvent( + ctx, packetData, gasLimit, payload.Version, + payload.DestinationPort, packet.DestinationClient, packet.Sequence, types.CallbackTypeReceivePacket, nil, + ) + if exists { + s.Require().Contains(ctx.EventManager().Events().ToABCIEvents(), expEvent) + } + + } else { + s.Require().ErrorIs(err, tc.expError) + } + }) + } +} diff --git a/modules/apps/callbacks/v2/v2_test.go b/modules/apps/callbacks/v2/v2_test.go new file mode 100644 index 0000000..1be9815 --- /dev/null +++ b/modules/apps/callbacks/v2/v2_test.go @@ -0,0 +1,166 @@ +package v2_test + +import ( + "encoding/json" + "errors" + "fmt" + "testing" + + dbm "github.com/cosmos/cosmos-db" + "github.com/stretchr/testify/suite" + + "cosmossdk.io/log" + + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + + abci "github.com/cometbft/cometbft/abci/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/testing/simapp" + "github.com/cosmos/ibc-go/v10/modules/apps/callbacks/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +const maxCallbackGas = uint64(1000000) + +// SetupTestingApp provides the duplicated simapp which is specific to the callbacks module on chain creation. +func SetupTestingApp() (ibctesting.TestingApp, map[string]json.RawMessage) { + db := dbm.NewMemDB() + app := simapp.NewSimApp(log.NewNopLogger(), db, nil, true, simtestutil.AppOptionsMap{}) + return app, app.DefaultGenesis() +} + +// GetSimApp returns the duplicated SimApp from within the callbacks directory. +// This must be used instead of chain.GetSimApp() for tests within this directory. +func GetSimApp(chain *ibctesting.TestChain) *simapp.SimApp { + app, ok := chain.App.(*simapp.SimApp) + if !ok { + panic(errors.New("chain is not a simapp.SimApp")) + } + return app +} + +// CallbacksTestSuite defines the needed instances and methods to test callbacks +type CallbacksTestSuite struct { + suite.Suite + + coordinator *ibctesting.Coordinator + + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain + + path *ibctesting.Path +} + +// setupChains sets up a coordinator with 2 test chains. +func (s *CallbacksTestSuite) setupChains() { + s.coordinator = ibctesting.NewCustomAppCoordinator(s.T(), 2, SetupTestingApp) + s.chainA = s.coordinator.GetChain(ibctesting.GetChainID(1)) + s.chainB = s.coordinator.GetChain(ibctesting.GetChainID(2)) + s.path = ibctesting.NewPath(s.chainA, s.chainB) +} + +// SetupTransferTest sets up a IBC v2 path between chainA and chainB +func (s *CallbacksTestSuite) SetupTest() { + s.setupChains() + + s.path.SetupV2() +} + +// AssertHasExecutedExpectedCallback checks the stateful entries and counters based on callbacktype. +// It assumes that the source chain is chainA and the destination chain is chainB. +func (s *CallbacksTestSuite) AssertHasExecutedExpectedCallback(callbackType types.CallbackType, expSuccess bool) { + var expStatefulEntries uint8 + if expSuccess { + // if the callback is expected to be successful, + // we expect at least one state entry + expStatefulEntries = 1 + } + + sourceStatefulCounter := GetSimApp(s.chainA).MockContractKeeper.GetStateEntryCounter(s.chainA.GetContext()) + destStatefulCounter := GetSimApp(s.chainB).MockContractKeeper.GetStateEntryCounter(s.chainB.GetContext()) + + switch callbackType { + case "none": + s.Require().Equal(uint8(0), sourceStatefulCounter) + s.Require().Equal(uint8(0), destStatefulCounter) + + case types.CallbackTypeSendPacket: + s.Require().Equal(expStatefulEntries, sourceStatefulCounter, "unexpected stateful entry amount for source send packet callback") + s.Require().Equal(uint8(0), destStatefulCounter) + + case types.CallbackTypeAcknowledgementPacket, types.CallbackTypeTimeoutPacket: + expStatefulEntries *= 2 // expect OnAcknowledgement/OnTimeout to be successful as well as the initial SendPacket + s.Require().Equal(expStatefulEntries, sourceStatefulCounter, "unexpected stateful entry amount for source acknowledgement/timeout callbacks") + s.Require().Equal(uint8(0), destStatefulCounter) + + case types.CallbackTypeReceivePacket: + s.Require().Equal(uint8(0), sourceStatefulCounter) + s.Require().Equal(expStatefulEntries, destStatefulCounter) + + default: + s.FailNow(fmt.Sprintf("invalid callback type %s", callbackType)) + } + + s.AssertCallbackCounters(callbackType) +} + +func (s *CallbacksTestSuite) AssertCallbackCounters(callbackType types.CallbackType) { + sourceCounters := GetSimApp(s.chainA).MockContractKeeper.Counters + destCounters := GetSimApp(s.chainB).MockContractKeeper.Counters + + switch callbackType { + case "none": + s.Require().Len(sourceCounters, 0) + s.Require().Len(destCounters, 0) + + case types.CallbackTypeSendPacket: + s.Require().Len(sourceCounters, 1) + s.Require().Equal(1, sourceCounters[types.CallbackTypeSendPacket]) + + case types.CallbackTypeAcknowledgementPacket: + s.Require().Len(sourceCounters, 2) + s.Require().Equal(1, sourceCounters[types.CallbackTypeSendPacket]) + s.Require().Equal(1, sourceCounters[types.CallbackTypeAcknowledgementPacket]) + + s.Require().Len(destCounters, 0) + + case types.CallbackTypeReceivePacket: + s.Require().Len(sourceCounters, 0) + s.Require().Len(destCounters, 1) + s.Require().Equal(1, destCounters[types.CallbackTypeReceivePacket]) + + case types.CallbackTypeTimeoutPacket: + s.Require().Len(sourceCounters, 2) + s.Require().Equal(1, sourceCounters[types.CallbackTypeSendPacket]) + s.Require().Equal(1, sourceCounters[types.CallbackTypeTimeoutPacket]) + + s.Require().Len(destCounters, 0) + + default: + s.FailNow(fmt.Sprintf("invalid callback type %s", callbackType)) + } +} + +// GetExpectedEvent returns the expected event for a callback. +func GetExpectedEvent( + ctx sdk.Context, packetData any, remainingGas uint64, version string, + eventPortID, eventChannelID string, seq uint64, callbackType types.CallbackType, expError error, +) (abci.Event, bool) { + callbackKey := types.SourceCallbackKey + if callbackType == types.CallbackTypeReceivePacket { + callbackKey = types.DestinationCallbackKey + } + callbackData, isCbPacket, err := types.GetCallbackData(packetData, version, eventPortID, remainingGas, maxCallbackGas, callbackKey) + if !isCbPacket || err != nil { + return abci.Event{}, false + } + + newCtx := sdk.Context{}.WithEventManager(sdk.NewEventManager()) + types.EmitCallbackEvent(newCtx, eventPortID, eventChannelID, seq, callbackType, callbackData, expError) + return newCtx.EventManager().Events().ToABCIEvents()[0], true +} + +func TestIBCCallbacksTestSuite(t *testing.T) { + suite.Run(t, new(CallbacksTestSuite)) +} diff --git a/modules/apps/transfer/client/cli/cli.go b/modules/apps/transfer/client/cli/cli.go new file mode 100644 index 0000000..4230048 --- /dev/null +++ b/modules/apps/transfer/client/cli/cli.go @@ -0,0 +1,45 @@ +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" +) + +// GetQueryCmd returns the query commands for IBC connections +func GetQueryCmd() *cobra.Command { + queryCmd := &cobra.Command{ + Use: "ibc-transfer", + Short: "IBC fungible token transfer query subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + } + + queryCmd.AddCommand( + GetCmdQueryDenom(), + GetCmdQueryDenoms(), + GetCmdParams(), + GetCmdQueryEscrowAddress(), + GetCmdQueryDenomHash(), + GetCmdQueryTotalEscrowForDenom(), + ) + + return queryCmd +} + +// NewTxCmd returns the transaction commands for IBC fungible token transfer +func NewTxCmd() *cobra.Command { + txCmd := &cobra.Command{ + Use: "ibc-transfer", + Short: "IBC fungible token transfer transaction subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + txCmd.AddCommand( + NewTransferTxCmd(), + ) + + return txCmd +} diff --git a/modules/apps/transfer/client/cli/query.go b/modules/apps/transfer/client/cli/query.go new file mode 100644 index 0000000..c297df6 --- /dev/null +++ b/modules/apps/transfer/client/cli/query.go @@ -0,0 +1,202 @@ +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/version" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +) + +// GetCmdQueryDenom defines the command to query a denomination from a given hash or ibc denom. +func GetCmdQueryDenom() *cobra.Command { + cmd := &cobra.Command{ + Use: "denom [hash/denom]", + Short: "Query the denom trace info from a given hash or ibc denom", + Long: "Query the denom trace info from a given hash or ibc denom", + Example: fmt.Sprintf("%s query ibc-transfer denom 27A6394C3F9FF9C9DCF5DFFADF9BB5FE9A37C7E92B006199894CF1824DF9AC7C", version.AppName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + req := &types.QueryDenomRequest{ + Hash: args[0], + } + + res, err := queryClient.Denom(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + return cmd +} + +// GetCmdQueryDenoms defines the command to query all the denominations that this chain maintains. +func GetCmdQueryDenoms() *cobra.Command { + cmd := &cobra.Command{ + Use: "denoms", + Short: "Query for all token denominations", + Long: "Query for all token denominations", + Example: fmt.Sprintf("%s query ibc-transfer denoms", version.AppName), + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + req := &types.QueryDenomsRequest{ + Pagination: pageReq, + } + + res, err := queryClient.Denoms(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "denominations") + + return cmd +} + +// GetCmdParams returns the command handler for ibc-transfer parameter querying. +func GetCmdParams() *cobra.Command { + cmd := &cobra.Command{ + Use: "params", + Short: "Query the current ibc-transfer parameters", + Long: "Query the current ibc-transfer parameters", + Args: cobra.NoArgs, + Example: fmt.Sprintf("%s query ibc-transfer params", version.AppName), + RunE: func(cmd *cobra.Command, _ []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + res, err := queryClient.Params(cmd.Context(), &types.QueryParamsRequest{}) + if err != nil { + return err + } + + return clientCtx.PrintProto(res.Params) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdQueryEscrowAddress returns the command handler for ibc-transfer parameter querying. +func GetCmdQueryEscrowAddress() *cobra.Command { + cmd := &cobra.Command{ + Use: "escrow-address", + Short: "Get the escrow address for a channel", + Long: "Get the escrow address for a channel", + Args: cobra.ExactArgs(2), + Example: fmt.Sprintf("%s query ibc-transfer escrow-address [port] [channel-id]", version.AppName), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + port := args[0] + channel := args[1] + addr := types.GetEscrowAddress(port, channel) + return clientCtx.PrintString(fmt.Sprintf("%s\n", addr.String())) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdQueryDenomHash defines the command to query a denomination hash from a given trace. +func GetCmdQueryDenomHash() *cobra.Command { + cmd := &cobra.Command{ + Use: "denom-hash [trace]", + Short: "Query the denom hash info from a given denom trace", + Long: "Query the denom hash info from a given denom trace", + Example: fmt.Sprintf("%s query ibc-transfer denom-hash transfer/channel-0/uatom", version.AppName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + req := &types.QueryDenomHashRequest{ + Trace: args[0], + } + + res, err := queryClient.DenomHash(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + return cmd +} + +// GetCmdQueryTotalEscrowForDenom defines the command to query the total amount of tokens in escrow for a denom +func GetCmdQueryTotalEscrowForDenom() *cobra.Command { + cmd := &cobra.Command{ + Use: "total-escrow [denom]", + Short: "Query the total amount of tokens in escrow for a denom", + Long: "Query the total amount of tokens in escrow for a denom", + Example: fmt.Sprintf("%s query ibc-transfer total-escrow uosmo", version.AppName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + + req := &types.QueryTotalEscrowForDenomRequest{ + Denom: args[0], + } + + res, err := queryClient.TotalEscrowForDenom(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + return cmd +} diff --git a/modules/apps/transfer/client/cli/tx.go b/modules/apps/transfer/client/cli/tx.go new file mode 100644 index 0000000..7214be3 --- /dev/null +++ b/modules/apps/transfer/client/cli/tx.go @@ -0,0 +1,128 @@ +package cli + +import ( + "errors" + "fmt" + "strings" + "time" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/version" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" +) + +const ( + flagPacketTimeoutHeight = "packet-timeout-height" + flagPacketTimeoutTimestamp = "packet-timeout-timestamp" + flagAbsoluteTimeouts = "absolute-timeouts" + flagMemo = "memo" +) + +// defaultRelativePacketTimeoutTimestamp is the default packet timeout timestamp (in nanoseconds) +// relative to the current block timestamp of the counterparty chain provided by the client +// state. For IBC v1 protocol, either timeout timestamp or timeout height must be set. +// If you are sending with IBC v2 protocol, timeout timestamp must be set. +// The default is currently set to a 10 minute timeout. +var defaultRelativePacketTimeoutTimestamp = uint64((time.Duration(10) * time.Minute).Nanoseconds()) + +// NewTransferTxCmd returns the command to create a NewMsgTransfer transaction +func NewTransferTxCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "transfer [src-port] [src-channel] [receiver] [coin]", + Short: "Transfer a fungible tokens through IBC", + Long: strings.TrimSpace(`Transfer one fungible tokens through IBC. Timeouts can be specified as absolute using the {absolute-timeouts} flag. +Timeout height can be set by passing in the height string in the form {revision}-{height} using the {packet-timeout-height} flag. +Note, relative timeout height is not supported. Relative timeout timestamp is added to the value of the user's local system clock time +using the {packet-timeout-timestamp} flag. If no timeout value is set then a default relative timeout value of 10 minutes is used.`), + Example: fmt.Sprintf("%s tx ibc-transfer transfer [src-port] [src-channel] [receiver] [coin]", version.AppName), + Args: cobra.RangeArgs(2, 4), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + sender := clientCtx.GetFromAddress().String() + + srcPort := args[0] + srcChannel := args[1] + receiver := args[2] + + coin, err := sdk.ParseCoinNormalized(args[3]) + if err != nil { + return err + } + + if !strings.HasPrefix(coin.Denom, "ibc/") { + denom := types.ExtractDenomFromPath(coin.Denom) + coin.Denom = denom.IBCDenom() + } + + timeoutHeightStr, err := cmd.Flags().GetString(flagPacketTimeoutHeight) + if err != nil { + return err + } + + timeoutHeight, err := clienttypes.ParseHeight(timeoutHeightStr) + if err != nil { + return err + } + + timeoutTimestamp, err := cmd.Flags().GetUint64(flagPacketTimeoutTimestamp) + if err != nil { + return err + } + + absoluteTimeouts, err := cmd.Flags().GetBool(flagAbsoluteTimeouts) + if err != nil { + return err + } + + memo, err := cmd.Flags().GetString(flagMemo) + if err != nil { + return err + } + + // NOTE: relative timeouts using block height are not supported. + // if the timeouts are not absolute, CLI users rely solely on local clock time in order to calculate relative timestamps. + if !absoluteTimeouts { + if !timeoutHeight.IsZero() { + return errors.New("relative timeouts using block height is not supported") + } + + if timeoutTimestamp == 0 { + return errors.New("relative timeouts must provide a non zero value timestamp") + } + + // use local clock time as reference time for calculating timeout timestamp. + now := time.Now().UnixNano() + if now <= 0 { + return errors.New("local clock time is not greater than Jan 1st, 1970 12:00 AM") + } + + timeoutTimestamp = uint64(now) + timeoutTimestamp + } + + msg := types.NewMsgTransfer( + srcPort, srcChannel, coin, sender, receiver, timeoutHeight, timeoutTimestamp, memo, + ) + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + cmd.Flags().String(flagPacketTimeoutHeight, "0-0", "Packet timeout block height in the format {revision}-{height}.") + cmd.Flags().Uint64(flagPacketTimeoutTimestamp, defaultRelativePacketTimeoutTimestamp, "Packet timeout timestamp in nanoseconds from now. Default is 10 minutes. On IBC v1 protocol, either timeout timestamp or timeout height must be set. On IBC v2 protocol timeout timestamp must be set.") + cmd.Flags().Bool(flagAbsoluteTimeouts, false, "Timeout flags are used as absolute timeouts.") + cmd.Flags().String(flagMemo, "", "Memo to be sent along with the packet.") + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} diff --git a/modules/apps/transfer/doc.go b/modules/apps/transfer/doc.go new file mode 100644 index 0000000..e29f419 --- /dev/null +++ b/modules/apps/transfer/doc.go @@ -0,0 +1,8 @@ +/* +Package transfer implements the packet data structure, state machine handling logic, +and encoding details for the transfer of fungible tokens over an IBC channel between +two modules on separate chains. +This implementation is based off the ICS 20 specification +(https://github.com/cosmos/ibc/blob/main/spec/app/ics-020-fungible-token-transfer) +*/ +package transfer diff --git a/modules/apps/transfer/ibc_module.go b/modules/apps/transfer/ibc_module.go new file mode 100644 index 0000000..dffb45d --- /dev/null +++ b/modules/apps/transfer/ibc_module.go @@ -0,0 +1,282 @@ +package transfer + +import ( + "bytes" + "fmt" + "math" + "slices" + "strings" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/internal/events" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/internal/telemetry" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/keeper" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var ( + _ porttypes.IBCModule = (*IBCModule)(nil) + _ porttypes.PacketDataUnmarshaler = (*IBCModule)(nil) +) + +// IBCModule implements the ICS26 interface for transfer given the transfer keeper. +type IBCModule struct { + keeper keeper.Keeper +} + +// NewIBCModule creates a new IBCModule given the keeper +func NewIBCModule(k keeper.Keeper) IBCModule { + return IBCModule{ + keeper: k, + } +} + +// ValidateTransferChannelParams does validation of a newly created transfer channel. A transfer +// channel must be UNORDERED, use the correct port (by default 'transfer'), and use the current +// supported version. Only 2^32 channels are allowed to be created. +func ValidateTransferChannelParams( + ctx sdk.Context, + transferkeeper keeper.Keeper, + order channeltypes.Order, + portID string, + channelID string, +) error { + // NOTE: for escrow address security only 2^32 channels are allowed to be created + // Issue: https://github.com/cosmos/cosmos-sdk/issues/7737 + channelSequence, err := channeltypes.ParseChannelSequence(channelID) + if err != nil { + return err + } + if channelSequence > uint64(math.MaxUint32) { + return errorsmod.Wrapf(types.ErrMaxTransferChannels, "channel sequence %d is greater than max allowed transfer channels %d", channelSequence, uint64(math.MaxUint32)) + } + if order != channeltypes.UNORDERED { + return errorsmod.Wrapf(channeltypes.ErrInvalidChannelOrdering, "expected %s channel, got %s ", channeltypes.UNORDERED, order) + } + + // Require portID is the portID transfer module is bound to + boundPort := transferkeeper.GetPort(ctx) + if boundPort != portID { + return errorsmod.Wrapf(porttypes.ErrInvalidPort, "invalid port: %s, expected %s", portID, boundPort) + } + + return nil +} + +// OnChanOpenInit implements the IBCModule interface +func (im IBCModule) OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + counterparty channeltypes.Counterparty, + version string, +) (string, error) { + if err := ValidateTransferChannelParams(ctx, im.keeper, order, portID, channelID); err != nil { + return "", err + } + + // default to latest supported version + if strings.TrimSpace(version) == "" { + version = types.V1 + } + + if !slices.Contains(types.SupportedVersions, version) { + return "", errorsmod.Wrapf(types.ErrInvalidVersion, "expected one of %s, got %s", types.SupportedVersions, version) + } + + return version, nil +} + +// OnChanOpenTry implements the IBCModule interface. +func (im IBCModule) OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + counterparty channeltypes.Counterparty, + counterpartyVersion string, +) (string, error) { + if err := ValidateTransferChannelParams(ctx, im.keeper, order, portID, channelID); err != nil { + return "", err + } + + if !slices.Contains(types.SupportedVersions, counterpartyVersion) { + im.keeper.Logger(ctx).Debug("invalid counterparty version, proposing latest app version", "counterpartyVersion", counterpartyVersion, "version", types.V1) + return types.V1, nil + } + + return counterpartyVersion, nil +} + +// OnChanOpenAck implements the IBCModule interface +func (IBCModule) OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + _ string, + counterpartyVersion string, +) error { + if !slices.Contains(types.SupportedVersions, counterpartyVersion) { + return errorsmod.Wrapf(types.ErrInvalidVersion, "invalid counterparty version: expected one of %s, got %s", types.SupportedVersions, counterpartyVersion) + } + + return nil +} + +// OnChanOpenConfirm implements the IBCModule interface +func (IBCModule) OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + return nil +} + +// OnChanCloseInit implements the IBCModule interface +func (IBCModule) OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + // Disallow user-initiated channel closing for transfer channels + return errorsmod.Wrap(ibcerrors.ErrInvalidRequest, "user cannot close channel") +} + +// OnChanCloseConfirm implements the IBCModule interface +func (IBCModule) OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, +) error { + return nil +} + +// OnRecvPacket implements the IBCModule interface. A successful acknowledgement +// is returned if the packet data is successfully decoded and the receive application +// logic returns without error. +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, + channelVersion string, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) ibcexported.Acknowledgement { + var ( + ack ibcexported.Acknowledgement + ackErr error + data types.InternalTransferRepresentation + ) + + // we are explicitly wrapping this emit event call in an anonymous function so that + // the packet data is evaluated after it has been assigned a value. + defer func() { + events.EmitOnRecvPacketEvent(ctx, data, ack, ackErr) + }() + + data, ackErr = types.UnmarshalPacketData(packet.GetData(), channelVersion, "") + if ackErr != nil { + ack = channeltypes.NewErrorAcknowledgement(ackErr) + im.keeper.Logger(ctx).Error(fmt.Sprintf("%s sequence %d", ackErr.Error(), packet.Sequence)) + return ack + } + + // NOTE: this needs to set the ackErr variable and not do if ackErr := ... because the ackErr variable is used in the defer function + ackErr = im.keeper.OnRecvPacket( + ctx, + data, + packet.SourcePort, + packet.SourceChannel, + packet.DestinationPort, + packet.DestinationChannel, + ) + if ackErr != nil { + ack = channeltypes.NewErrorAcknowledgement(ackErr) + im.keeper.Logger(ctx).Error(fmt.Sprintf("%s sequence %d", ackErr.Error(), packet.Sequence)) + return ack + } + + ack = channeltypes.NewResultAcknowledgement([]byte{byte(1)}) + + telemetry.ReportOnRecvPacket(packet.SourcePort, packet.SourceChannel, packet.DestinationPort, packet.DestinationChannel, data.Token) + + im.keeper.Logger(ctx).Info("successfully handled ICS-20 packet", "sequence", packet.Sequence) + + // NOTE: acknowledgement will be written synchronously during IBC handler execution. + return ack +} + +// OnAcknowledgementPacket implements the IBCModule interface +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + channelVersion string, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + var ack channeltypes.Acknowledgement + if err := types.ModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { + return errorsmod.Wrapf(ibcerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet acknowledgement: %v", err) + } + + data, err := types.UnmarshalPacketData(packet.GetData(), channelVersion, "") + if err != nil { + return err + } + + bz := types.ModuleCdc.MustMarshalJSON(&ack) + if !bytes.Equal(bz, acknowledgement) { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "acknowledgement did not marshal to expected bytes: %X ≠ %X", bz, acknowledgement) + } + + if err := im.keeper.OnAcknowledgementPacket(ctx, packet.SourcePort, packet.SourceChannel, data, ack); err != nil { + return err + } + + events.EmitOnAcknowledgementPacketEvent(ctx, data, ack) + + return nil +} + +// OnTimeoutPacket implements the IBCModule interface +func (im IBCModule) OnTimeoutPacket( + ctx sdk.Context, + channelVersion string, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) error { + data, err := types.UnmarshalPacketData(packet.GetData(), channelVersion, "") + if err != nil { + return err + } + + // refund tokens + if err := im.keeper.OnTimeoutPacket(ctx, packet.SourcePort, packet.SourceChannel, data); err != nil { + return err + } + + events.EmitOnTimeoutEvent(ctx, data) + + return nil +} + +// UnmarshalPacketData attempts to unmarshal the provided packet data bytes +// into a FungibleTokenPacketData. This function implements the optional +// PacketDataUnmarshaler interface required for ADR 008 support. +func (im IBCModule) UnmarshalPacketData(ctx sdk.Context, portID string, channelID string, bz []byte) (any, string, error) { + ics20Version, found := im.keeper.GetICS4Wrapper().GetAppVersion(ctx, portID, channelID) + if !found { + return types.InternalTransferRepresentation{}, "", errorsmod.Wrapf(ibcerrors.ErrNotFound, "app version not found for port %s and channel %s", portID, channelID) + } + + ftpd, err := types.UnmarshalPacketData(bz, ics20Version, "") + return ftpd, ics20Version, err +} diff --git a/modules/apps/transfer/ibc_module_test.go b/modules/apps/transfer/ibc_module_test.go new file mode 100644 index 0000000..6a88ade --- /dev/null +++ b/modules/apps/transfer/ibc_module_test.go @@ -0,0 +1,656 @@ +package transfer_test + +import ( + "errors" + "math" + + sdkmath "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *TransferTestSuite) TestOnChanOpenInit() { + var ( + channel *channeltypes.Channel + path *ibctesting.Path + counterparty channeltypes.Counterparty + ) + + testCases := []struct { + name string + malleate func() + expError error + expVersion string + }{ + { + "success", func() {}, nil, types.V1, + }, + { + // connection hops is not used in the transfer application callback, + "success: invalid connection hops", func() { + path.EndpointA.ConnectionID = ibctesting.InvalidID + }, nil, types.V1, + }, + { + "success: empty version string", func() { + channel.Version = "" + }, nil, types.V1, + }, + { + "success: ics20-1", func() { + channel.Version = types.V1 + }, nil, types.V1, + }, + { + "max channels reached", func() { + path.EndpointA.ChannelID = channeltypes.FormatChannelIdentifier(math.MaxUint32 + 1) + }, types.ErrMaxTransferChannels, "", + }, + { + "invalid order - ORDERED", func() { + channel.Ordering = channeltypes.ORDERED + }, channeltypes.ErrInvalidChannelOrdering, "", + }, + { + "invalid port ID", func() { + path.EndpointA.ChannelConfig.PortID = ibctesting.MockPort + }, porttypes.ErrInvalidPort, "", + }, + { + "invalid version", func() { + channel.Version = "version" //nolint:goconst + }, types.ErrInvalidVersion, "", + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + path = ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path.SetupConnections() + path.EndpointA.ChannelID = ibctesting.FirstChannelID + + counterparty = channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + channel = &channeltypes.Channel{ + State: channeltypes.INIT, + Ordering: channeltypes.UNORDERED, + Counterparty: counterparty, + ConnectionHops: []string{path.EndpointA.ConnectionID}, + Version: types.V1, + } + + tc.malleate() // explicitly change fields in channel and testChannel + + transferModule := transfer.NewIBCModule(suite.chainA.GetSimApp().TransferKeeper) + version, err := transferModule.OnChanOpenInit(suite.chainA.GetContext(), channel.Ordering, channel.ConnectionHops, + path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, counterparty, channel.Version, + ) + + if tc.expError == nil { + suite.Require().NoError(err) + suite.Require().Equal(tc.expVersion, version) + } else { + suite.Require().Error(err) + suite.Require().Contains(err.Error(), tc.expError.Error()) + } + }) + } +} + +func (suite *TransferTestSuite) TestOnChanOpenTry() { + var ( + channel *channeltypes.Channel + path *ibctesting.Path + counterparty channeltypes.Counterparty + counterpartyVersion string + ) + + testCases := []struct { + name string + malleate func() + expError error + expVersion string + }{ + { + "success", func() {}, nil, types.V1, + }, + { + "success: counterparty version is ics20-1", func() { + counterpartyVersion = types.V1 + }, nil, types.V1, + }, + { + "success: invalid counterparty version, we propose new version", func() { + // transfer module will propose the default version + counterpartyVersion = "version" + }, nil, types.V1, + }, + { + "failure: max channels reached", func() { + path.EndpointA.ChannelID = channeltypes.FormatChannelIdentifier(math.MaxUint32 + 1) + }, types.ErrMaxTransferChannels, "", + }, + { + "failure: invalid order - ORDERED", func() { + channel.Ordering = channeltypes.ORDERED + }, channeltypes.ErrInvalidChannelOrdering, "", + }, + { + "failure: invalid port ID", func() { + path.EndpointA.ChannelConfig.PortID = ibctesting.MockPort + }, porttypes.ErrInvalidPort, "", + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path.SetupConnections() + path.EndpointA.ChannelID = ibctesting.FirstChannelID + + counterparty = channeltypes.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + channel = &channeltypes.Channel{ + State: channeltypes.TRYOPEN, + Ordering: channeltypes.UNORDERED, + Counterparty: counterparty, + ConnectionHops: []string{path.EndpointA.ConnectionID}, + Version: types.V1, + } + counterpartyVersion = types.V1 + + cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(ibctesting.TransferPort) + suite.Require().True(ok) + + tc.malleate() // explicitly change fields in channel and testChannel + + version, err := cbs.OnChanOpenTry(suite.chainA.GetContext(), channel.Ordering, channel.ConnectionHops, + path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, channel.Counterparty, counterpartyVersion, + ) + if tc.expError == nil { + suite.Require().NoError(err) + suite.Require().Equal(tc.expVersion, version) + } else { + suite.Require().Error(err) + suite.Require().Contains(err.Error(), tc.expError.Error()) + } + }) + } +} + +func (suite *TransferTestSuite) TestOnChanOpenAck() { + var counterpartyVersion string + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", func() {}, nil, + }, + { + "invalid counterparty version", + func() { + counterpartyVersion = "version" + }, + types.ErrInvalidVersion, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path := ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path.SetupConnections() + path.EndpointA.ChannelID = ibctesting.FirstChannelID + counterpartyVersion = types.V1 + + cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(ibctesting.TransferPort) + suite.Require().True(ok) + + tc.malleate() // explicitly change fields in channel and testChannel + + err := cbs.OnChanOpenAck(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointA.Counterparty.ChannelID, counterpartyVersion) + + if tc.expError == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().Contains(err.Error(), tc.expError.Error()) + } + }) + } +} + +func (suite *TransferTestSuite) TestOnRecvPacket() { + // This test suite mostly covers the top-level logic of the ibc module OnRecvPacket function + // The core logic is covered in keeper OnRecvPacket + var ( + packet channeltypes.Packet + expectedAttributes []sdk.Attribute + path *ibctesting.Path + ) + testCases := []struct { + name string + malleate func() + expAck exported.Acknowledgement + expEventErrorMsg string + }{ + { + "success", func() {}, channeltypes.NewResultAcknowledgement([]byte{byte(1)}), "", + }, + { + "failure: invalid packet data bytes", + func() { + packet.Data = []byte("invalid data") + + // Override expected attributes because this fails on unmarshaling packet data (so can't get the attributes) + expectedAttributes = []sdk.Attribute{ + sdk.NewAttribute(types.AttributeKeySender, ""), + sdk.NewAttribute(types.AttributeKeyReceiver, ""), + sdk.NewAttribute(types.AttributeKeyDenom, ""), + sdk.NewAttribute(types.AttributeKeyAmount, ""), + sdk.NewAttribute(types.AttributeKeyMemo, ""), + sdk.NewAttribute(types.AttributeKeyAckSuccess, "false"), + sdk.NewAttribute(types.AttributeKeyAckError, "cannot unmarshal ICS20-V1 transfer packet data: invalid character 'i' looking for beginning of value: invalid type"), + } + }, + channeltypes.NewErrorAcknowledgement(ibcerrors.ErrInvalidType), + "cannot unmarshal ICS20-V1 transfer packet data: invalid character 'i' looking for beginning of value: invalid type", + }, + { + "failure: receive disabled", + func() { + suite.chainB.GetSimApp().TransferKeeper.SetParams(suite.chainB.GetContext(), types.Params{ReceiveEnabled: false}) + }, + channeltypes.NewErrorAcknowledgement(types.ErrReceiveDisabled), + "fungible token transfers to this chain are disabled", + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path.Setup() + + token := types.Token{ + Denom: types.NewDenom(sdk.DefaultBondDenom), + Amount: sdkmath.NewInt(100).String(), + } + packetData := types.NewFungibleTokenPacketData( + token.Denom.Path(), + token.Amount, + suite.chainA.SenderAccount.GetAddress().String(), + suite.chainB.SenderAccount.GetAddress().String(), + "", + ) + + expectedAttributes = []sdk.Attribute{ + sdk.NewAttribute(types.AttributeKeySender, packetData.Sender), + sdk.NewAttribute(types.AttributeKeyReceiver, packetData.Receiver), + sdk.NewAttribute(types.AttributeKeyDenom, packetData.Denom), + sdk.NewAttribute(types.AttributeKeyAmount, packetData.Amount), + sdk.NewAttribute(types.AttributeKeyMemo, packetData.Memo), + } + if tc.expAck == nil || tc.expAck.Success() { + expectedAttributes = append(expectedAttributes, sdk.NewAttribute(types.AttributeKeyAckSuccess, "true")) + } else { + expectedAttributes = append(expectedAttributes, + sdk.NewAttribute(types.AttributeKeyAckSuccess, "false"), + sdk.NewAttribute(types.AttributeKeyAckError, tc.expEventErrorMsg), + ) + } + + seq := uint64(1) + packet = channeltypes.NewPacket(packetData.GetBytes(), seq, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.ZeroHeight(), suite.chainA.GetTimeoutTimestamp()) + + ctx := suite.chainB.GetContext() + cbs, ok := suite.chainB.App.GetIBCKeeper().PortKeeper.Route(ibctesting.TransferPort) + suite.Require().True(ok) + + tc.malleate() // change fields in packet + + ack := cbs.OnRecvPacket(ctx, path.EndpointB.GetChannel().Version, packet, suite.chainB.SenderAccount.GetAddress()) + + suite.Require().Equal(tc.expAck, ack) + + expectedEvents := sdk.Events{ + sdk.NewEvent( + types.EventTypePacket, + expectedAttributes..., + ), + }.ToABCIEvents() + + expectedEvents = sdk.MarkEventsToIndex(expectedEvents, map[string]struct{}{}) + ibctesting.AssertEvents(&suite.Suite, expectedEvents, ctx.EventManager().Events().ToABCIEvents()) + }) + } +} + +func (suite *TransferTestSuite) TestOnAcknowledgePacket() { + var ( + path *ibctesting.Path + packet channeltypes.Packet + ack []byte + ) + + testCases := []struct { + name string + malleate func() + expError error + expRefund bool + }{ + { + "success", + func() {}, + nil, + false, + }, + { + "success: refund coins", + func() { + ack = channeltypes.NewErrorAcknowledgement(ibcerrors.ErrInsufficientFunds).Acknowledgement() + }, + nil, + true, + }, + { + "cannot refund ack on non-existent channel", + func() { + ack = channeltypes.NewErrorAcknowledgement(ibcerrors.ErrInsufficientFunds).Acknowledgement() + + packet.SourceChannel = "channel-100" + }, + errors.New("unable to unescrow tokens"), + false, + }, + { + "invalid packet data", + func() { + packet.Data = []byte("invalid data") + }, + ibcerrors.ErrInvalidType, + false, + }, + { + "invalid acknowledgement", + func() { + ack = []byte("invalid ack") + }, + ibcerrors.ErrUnknownRequest, + false, + }, + { + "cannot refund already acknowledged packet", + func() { + ack = channeltypes.NewErrorAcknowledgement(ibcerrors.ErrInsufficientFunds).Acknowledgement() + + cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(ibctesting.TransferPort) + suite.Require().True(ok) + + suite.Require().NoError(cbs.OnAcknowledgementPacket(suite.chainA.GetContext(), path.EndpointA.GetChannel().Version, packet, ack, suite.chainA.SenderAccount.GetAddress())) + }, + errors.New("unable to unescrow tokens"), + false, + }, + { + // See https://github.com/cosmos/ibc-go/security/advisories/GHSA-jg6f-48ff-5xrw + "non-deterministic JSON ack serialization should return an error", + func() { + // Create a valid acknowledgement using deterministic serialization. + ack = channeltypes.NewResultAcknowledgement([]byte{byte(1)}).Acknowledgement() + // Introduce non-determinism: insert an extra space after the first character '{' + // This will deserialize correctly but fail to re-serialize to the expected bytes. + if len(ack) > 0 && ack[0] == '{' { + ack = []byte("{ " + string(ack[1:])) + } + }, + errors.New("acknowledgement did not marshal to expected bytes"), + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path.Setup() + + timeoutHeight := suite.chainA.GetTimeoutHeight() + msg := types.NewMsgTransfer( + path.EndpointA.ChannelConfig.PortID, + path.EndpointA.ChannelID, + ibctesting.TestCoin, + suite.chainA.SenderAccount.GetAddress().String(), + suite.chainB.SenderAccount.GetAddress().String(), + timeoutHeight, + 0, + "", + ) + res, err := suite.chainA.SendMsgs(msg) + suite.Require().NoError(err) // message committed + + packet, err = ibctesting.ParseV1PacketFromEvents(res.Events) + suite.Require().NoError(err) + + cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(ibctesting.TransferPort) + suite.Require().True(ok) + + ack = channeltypes.NewResultAcknowledgement([]byte{byte(1)}).Acknowledgement() + + tc.malleate() // change fields in packet + + err = cbs.OnAcknowledgementPacket(suite.chainA.GetContext(), path.EndpointA.GetChannel().Version, packet, ack, suite.chainA.SenderAccount.GetAddress()) + + if tc.expError == nil { + suite.Require().NoError(err) + + if tc.expRefund { + escrowAddress := types.GetEscrowAddress(packet.GetSourcePort(), packet.GetSourceChannel()) + escrowBalanceAfter := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), escrowAddress, sdk.DefaultBondDenom) + suite.Require().Equal(sdkmath.NewInt(0), escrowBalanceAfter.Amount) + } + } else { + suite.Require().Error(err) + suite.Require().Contains(err.Error(), tc.expError.Error()) + } + }) + } +} + +func (suite *TransferTestSuite) TestOnTimeoutPacket() { + var path *ibctesting.Path + var packet channeltypes.Packet + + testCases := []struct { + name string + coinsToSendToB sdk.Coin + malleate func() + expError error + }{ + { + "success", + ibctesting.TestCoin, + func() {}, + nil, + }, + { + "non-existent channel", + ibctesting.TestCoin, + func() { + packet.SourceChannel = "channel-100" + }, + errors.New("unable to unescrow tokens"), + }, + { + "invalid packet data", + ibctesting.TestCoin, + func() { + packet.Data = []byte("invalid data") + }, + ibcerrors.ErrInvalidType, + }, + { + "already timed-out packet", + ibctesting.TestCoin, + func() { + cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(ibctesting.TransferPort) + suite.Require().True(ok) + + suite.Require().NoError(cbs.OnTimeoutPacket(suite.chainA.GetContext(), path.EndpointA.GetChannel().Version, packet, suite.chainA.SenderAccount.GetAddress())) + }, + errors.New("unable to unescrow tokens"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path.Setup() + + timeoutHeight := suite.chainA.GetTimeoutHeight() + msg := types.NewMsgTransfer( + path.EndpointA.ChannelConfig.PortID, + path.EndpointA.ChannelID, + tc.coinsToSendToB, + suite.chainA.SenderAccount.GetAddress().String(), + suite.chainB.SenderAccount.GetAddress().String(), + timeoutHeight, + 0, + "", + ) + res, err := suite.chainA.SendMsgs(msg) + suite.Require().NoError(err) // message committed + + packet, err = ibctesting.ParseV1PacketFromEvents(res.Events) + suite.Require().NoError(err) + + cbs, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(ibctesting.TransferPort) + suite.Require().True(ok) + + tc.malleate() // change fields in packet + + err = cbs.OnTimeoutPacket(suite.chainA.GetContext(), path.EndpointA.GetChannel().Version, packet, suite.chainA.SenderAccount.GetAddress()) + + if tc.expError == nil { + suite.Require().NoError(err) + + escrowAddress := types.GetEscrowAddress(packet.GetSourcePort(), packet.GetSourceChannel()) + escrowBalanceAfter := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), escrowAddress, sdk.DefaultBondDenom) + suite.Require().Equal(sdkmath.NewInt(0), escrowBalanceAfter.Amount) + } else { + suite.Require().Error(err) + suite.Require().Contains(err.Error(), tc.expError.Error()) + } + }) + } +} + +func (suite *TransferTestSuite) TestPacketDataUnmarshalerInterface() { + var ( + sender = sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()).String() + receiver = sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()).String() + + data []byte + initialPacketData any + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success: valid packet data with memo", + func() { + initialPacketData = types.FungibleTokenPacketData{ + Denom: ibctesting.TestCoin.Denom, + Amount: ibctesting.TestCoin.Amount.String(), + Sender: sender, + Receiver: receiver, + Memo: "some memo", + } + data = initialPacketData.(types.FungibleTokenPacketData).GetBytes() + }, + nil, + }, + { + "success: valid packet data denom with trace", + func() { + initialPacketData = types.FungibleTokenPacketData{ + Denom: "transfer/channel-0/atom", + Amount: ibctesting.TestCoin.Amount.String(), + Sender: sender, + Receiver: receiver, + Memo: "", + } + + data = initialPacketData.(types.FungibleTokenPacketData).GetBytes() + }, + nil, + }, + { + "failure: invalid packet data", + func() { + data = []byte("invalid packet data") + }, + errors.New("cannot unmarshal ICS20-V1 transfer packet data: invalid character 'i' looking for beginning of value: invalid type"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + tc.malleate() + + path := ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path.Setup() + + transferStack, ok := suite.chainA.App.GetIBCKeeper().PortKeeper.Route(types.ModuleName) + suite.Require().True(ok) + + unmarshalerStack, ok := transferStack.(porttypes.PacketDataUnmarshaler) + suite.Require().True(ok) + + packetData, version, err := unmarshalerStack.UnmarshalPacketData(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, data) + + if tc.expError == nil { + suite.Require().NoError(err) + + v2PacketData, ok := packetData.(types.InternalTransferRepresentation) + suite.Require().True(ok) + suite.Require().Equal(path.EndpointA.ChannelConfig.Version, version) + + if v1PacketData, ok := initialPacketData.(types.FungibleTokenPacketData); ok { + // Note: testing of the denom trace parsing/conversion should be done as part of testing internal conversion functions + suite.Require().Equal(v1PacketData.Amount, v2PacketData.Token.Amount) + suite.Require().Equal(v1PacketData.Sender, v2PacketData.Sender) + suite.Require().Equal(v1PacketData.Receiver, v2PacketData.Receiver) + suite.Require().Equal(v1PacketData.Memo, v2PacketData.Memo) + } else { + suite.Require().Equal(initialPacketData.(types.InternalTransferRepresentation), v2PacketData) + } + } else { + suite.Require().Error(err) + suite.Require().Contains(err.Error(), tc.expError.Error()) + } + }) + } +} diff --git a/modules/apps/transfer/internal/events/events.go b/modules/apps/transfer/internal/events/events.go new file mode 100644 index 0000000..0ff3508 --- /dev/null +++ b/modules/apps/transfer/internal/events/events.go @@ -0,0 +1,134 @@ +package events + +import ( + "encoding/json" + "strconv" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// EmitTransferEvent emits a ibc transfer event on successful transfers. +func EmitTransferEvent(ctx sdk.Context, sender, receiver string, token types.Token, memo string) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeTransfer, + sdk.NewAttribute(types.AttributeKeySender, sender), + sdk.NewAttribute(types.AttributeKeyReceiver, receiver), + sdk.NewAttribute(types.AttributeKeyDenom, token.Denom.Path()), + sdk.NewAttribute(types.AttributeKeyAmount, token.Amount), + sdk.NewAttribute(types.AttributeKeyMemo, memo), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + ), + }) +} + +// EmitOnRecvPacketEvent emits a fungible token packet event in the OnRecvPacket callback +func EmitOnRecvPacketEvent(ctx sdk.Context, packetData types.InternalTransferRepresentation, ack ibcexported.Acknowledgement, ackErr error) { + eventAttributes := []sdk.Attribute{ + sdk.NewAttribute(types.AttributeKeySender, packetData.Sender), + sdk.NewAttribute(types.AttributeKeyReceiver, packetData.Receiver), + sdk.NewAttribute(types.AttributeKeyDenom, packetData.Token.Denom.Path()), + sdk.NewAttribute(types.AttributeKeyAmount, packetData.Token.Amount), + sdk.NewAttribute(types.AttributeKeyMemo, packetData.Memo), + sdk.NewAttribute(types.AttributeKeyAckSuccess, strconv.FormatBool(ack.Success())), + } + + if ackErr != nil { + eventAttributes = append(eventAttributes, sdk.NewAttribute(types.AttributeKeyAckError, ackErr.Error())) + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypePacket, + eventAttributes..., + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + ), + }) +} + +// EmitOnAcknowledgementPacketEvent emits a fungible token packet event in the OnAcknowledgementPacket callback +func EmitOnAcknowledgementPacketEvent(ctx sdk.Context, packetData types.InternalTransferRepresentation, ack channeltypes.Acknowledgement) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypePacket, + sdk.NewAttribute(sdk.AttributeKeySender, packetData.Sender), + sdk.NewAttribute(types.AttributeKeyReceiver, packetData.Receiver), + sdk.NewAttribute(types.AttributeKeyDenom, packetData.Token.Denom.Path()), + sdk.NewAttribute(types.AttributeKeyAmount, packetData.Token.Amount), + sdk.NewAttribute(types.AttributeKeyMemo, packetData.Memo), + sdk.NewAttribute(types.AttributeKeyAck, ack.String()), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + ), + }) + + switch resp := ack.Response.(type) { + case *channeltypes.Acknowledgement_Result: + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypePacket, + sdk.NewAttribute(types.AttributeKeyAckSuccess, string(resp.Result)), + ), + ) + case *channeltypes.Acknowledgement_Error: + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypePacket, + sdk.NewAttribute(types.AttributeKeyAckError, resp.Error), + ), + ) + } +} + +// EmitOnTimeoutEvent emits a fungible token packet event in the OnTimeoutPacket callback +func EmitOnTimeoutEvent(ctx sdk.Context, packetData types.InternalTransferRepresentation) { + tokenStr := mustMarshalJSON(packetData.Token) + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeTimeout, + sdk.NewAttribute(types.AttributeKeyReceiver, packetData.Sender), + sdk.NewAttribute(types.AttributeKeyRefundTokens, tokenStr), + sdk.NewAttribute(types.AttributeKeyMemo, packetData.Memo), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + ), + }) +} + +// EmitDenomEvent emits a denomination event in the OnRecv callback. +func EmitDenomEvent(ctx sdk.Context, token types.Token) { + denomStr := mustMarshalJSON(token.Denom) + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeDenom, + sdk.NewAttribute(types.AttributeKeyDenomHash, token.Denom.Hash().String()), + sdk.NewAttribute(types.AttributeKeyDenom, denomStr), + ), + ) +} + +// mustMarshalJSON json marshals the given type and panics on failure. +func mustMarshalJSON(v any) string { + bz, err := json.Marshal(v) + if err != nil { + panic(err) + } + + return string(bz) +} diff --git a/modules/apps/transfer/internal/telemetry/telemetry.go b/modules/apps/transfer/internal/telemetry/telemetry.go new file mode 100644 index 0000000..c312bc2 --- /dev/null +++ b/modules/apps/transfer/internal/telemetry/telemetry.go @@ -0,0 +1,71 @@ +package telemetry + +import ( + "fmt" + + "github.com/hashicorp/go-metrics" + + sdkmath "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/telemetry" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + coremetrics "github.com/cosmos/ibc-go/v10/modules/core/metrics" +) + +func ReportTransfer(sourcePort, sourceChannel, destinationPort, destinationChannel string, token types.Token) { + labels := []metrics.Label{ + telemetry.NewLabel(coremetrics.LabelDestinationPort, destinationPort), + telemetry.NewLabel(coremetrics.LabelDestinationChannel, destinationChannel), + } + + amount, ok := sdkmath.NewIntFromString(token.Amount) + if ok && amount.IsInt64() { + telemetry.SetGaugeWithLabels( + []string{"tx", "msg", "ibc", "transfer"}, + float32(amount.Int64()), + []metrics.Label{telemetry.NewLabel(coremetrics.LabelDenom, token.Denom.Path())}, + ) + } + + labels = append(labels, telemetry.NewLabel(coremetrics.LabelSource, fmt.Sprintf("%t", !token.Denom.HasPrefix(sourcePort, sourceChannel)))) + + telemetry.IncrCounterWithLabels( + []string{"ibc", types.ModuleName, "send"}, + 1, + labels, + ) +} + +func ReportOnRecvPacket(sourcePort, sourceChannel, destinationPort, destinationChannel string, token types.Token) { + labels := []metrics.Label{ + telemetry.NewLabel(coremetrics.LabelSourcePort, sourcePort), + telemetry.NewLabel(coremetrics.LabelSourceChannel, sourceChannel), + } + + // Modify trace as Recv does. + if token.Denom.HasPrefix(sourcePort, sourceChannel) { + token.Denom.Trace = token.Denom.Trace[1:] + } else { + trace := []types.Hop{types.NewHop(destinationPort, destinationChannel)} + token.Denom.Trace = append(trace, token.Denom.Trace...) + } + + // Transfer amount has already been parsed in caller. + transferAmount, ok := sdkmath.NewIntFromString(token.Amount) + if ok && transferAmount.IsInt64() { + telemetry.SetGaugeWithLabels( + []string{"ibc", types.ModuleName, "packet", "receive"}, + float32(transferAmount.Int64()), + []metrics.Label{telemetry.NewLabel(coremetrics.LabelDenom, token.Denom.Path())}, + ) + } + + labels = append(labels, telemetry.NewLabel(coremetrics.LabelSource, fmt.Sprintf("%t", token.Denom.HasPrefix(sourcePort, sourceChannel)))) + + telemetry.IncrCounterWithLabels( + []string{"ibc", types.ModuleName, "receive"}, + 1, + labels, + ) +} diff --git a/modules/apps/transfer/internal/types/denomtrace.pb.go b/modules/apps/transfer/internal/types/denomtrace.pb.go new file mode 100644 index 0000000..9c1ae97 --- /dev/null +++ b/modules/apps/transfer/internal/types/denomtrace.pb.go @@ -0,0 +1,378 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/applications/transfer/v1/denomtrace.proto + +package types + +import ( + fmt "fmt" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// DenomTrace contains the base denomination for ICS20 fungible tokens and the +// source tracing information path. +// +// Deprecated: Do not use. +type DenomTrace struct { + // path defines the chain of port/channel identifiers used for tracing the + // source of the fungible token. + Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` + // base denomination of the relayed fungible token. + BaseDenom string `protobuf:"bytes,2,opt,name=base_denom,json=baseDenom,proto3" json:"base_denom,omitempty"` +} + +func (m *DenomTrace) Reset() { *m = DenomTrace{} } +func (m *DenomTrace) String() string { return proto.CompactTextString(m) } +func (*DenomTrace) ProtoMessage() {} +func (*DenomTrace) Descriptor() ([]byte, []int) { + return fileDescriptor_c400148599bf8bc8, []int{0} +} +func (m *DenomTrace) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DenomTrace) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_DenomTrace.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *DenomTrace) XXX_Merge(src proto.Message) { + xxx_messageInfo_DenomTrace.Merge(m, src) +} +func (m *DenomTrace) XXX_Size() int { + return m.Size() +} +func (m *DenomTrace) XXX_DiscardUnknown() { + xxx_messageInfo_DenomTrace.DiscardUnknown(m) +} + +var xxx_messageInfo_DenomTrace proto.InternalMessageInfo + +func (m *DenomTrace) GetPath() string { + if m != nil { + return m.Path + } + return "" +} + +func (m *DenomTrace) GetBaseDenom() string { + if m != nil { + return m.BaseDenom + } + return "" +} + +func init() { + proto.RegisterType((*DenomTrace)(nil), "ibc.applications.transfer.v1.DenomTrace") +} + +func init() { + proto.RegisterFile("ibc/applications/transfer/v1/denomtrace.proto", fileDescriptor_c400148599bf8bc8) +} + +var fileDescriptor_c400148599bf8bc8 = []byte{ + // 216 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0xcd, 0x4c, 0x4a, 0xd6, + 0x4f, 0x2c, 0x28, 0xc8, 0xc9, 0x4c, 0x4e, 0x2c, 0xc9, 0xcc, 0xcf, 0x2b, 0xd6, 0x2f, 0x29, 0x4a, + 0xcc, 0x2b, 0x4e, 0x4b, 0x2d, 0xd2, 0x2f, 0x33, 0xd4, 0x4f, 0x49, 0xcd, 0xcb, 0xcf, 0x2d, 0x29, + 0x4a, 0x4c, 0x4e, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x92, 0xc9, 0x4c, 0x4a, 0xd6, 0x43, + 0x56, 0xae, 0x07, 0x53, 0xae, 0x57, 0x66, 0xa8, 0xe4, 0xcc, 0xc5, 0xe5, 0x02, 0xd2, 0x11, 0x02, + 0xd2, 0x21, 0x24, 0xc4, 0xc5, 0x52, 0x90, 0x58, 0x92, 0x21, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x19, + 0x04, 0x66, 0x0b, 0xc9, 0x72, 0x71, 0x25, 0x25, 0x16, 0xa7, 0xc6, 0x83, 0x0d, 0x96, 0x60, 0x02, + 0xcb, 0x70, 0x82, 0x44, 0xc0, 0xfa, 0xac, 0x98, 0x24, 0x18, 0x9d, 0x82, 0x4e, 0x3c, 0x92, 0x63, + 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, + 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0xca, 0x22, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, + 0x3f, 0x57, 0x3f, 0x39, 0xbf, 0x38, 0x37, 0xbf, 0x58, 0x3f, 0x33, 0x29, 0x59, 0x37, 0x3d, 0x5f, + 0xbf, 0xcc, 0xd0, 0x40, 0x3f, 0x37, 0x3f, 0xa5, 0x34, 0x27, 0xb5, 0x18, 0xe4, 0x19, 0x24, 0x4f, + 0x94, 0x54, 0x16, 0xa4, 0x16, 0x27, 0xb1, 0x81, 0x5d, 0x6f, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, + 0xdc, 0x64, 0xf7, 0xcc, 0xee, 0x00, 0x00, 0x00, +} + +func (m *DenomTrace) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DenomTrace) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DenomTrace) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.BaseDenom) > 0 { + i -= len(m.BaseDenom) + copy(dAtA[i:], m.BaseDenom) + i = encodeVarintDenomtrace(dAtA, i, uint64(len(m.BaseDenom))) + i-- + dAtA[i] = 0x12 + } + if len(m.Path) > 0 { + i -= len(m.Path) + copy(dAtA[i:], m.Path) + i = encodeVarintDenomtrace(dAtA, i, uint64(len(m.Path))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintDenomtrace(dAtA []byte, offset int, v uint64) int { + offset -= sovDenomtrace(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *DenomTrace) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Path) + if l > 0 { + n += 1 + l + sovDenomtrace(uint64(l)) + } + l = len(m.BaseDenom) + if l > 0 { + n += 1 + l + sovDenomtrace(uint64(l)) + } + return n +} + +func sovDenomtrace(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozDenomtrace(x uint64) (n int) { + return sovDenomtrace(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *DenomTrace) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDenomtrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DenomTrace: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DenomTrace: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Path", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDenomtrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDenomtrace + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthDenomtrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Path = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BaseDenom", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDenomtrace + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDenomtrace + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthDenomtrace + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.BaseDenom = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDenomtrace(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthDenomtrace + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipDenomtrace(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDenomtrace + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDenomtrace + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDenomtrace + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthDenomtrace + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupDenomtrace + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthDenomtrace + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthDenomtrace = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowDenomtrace = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupDenomtrace = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/apps/transfer/internal/types/legacy_denomtrace.go b/modules/apps/transfer/internal/types/legacy_denomtrace.go new file mode 100644 index 0000000..ca83b91 --- /dev/null +++ b/modules/apps/transfer/internal/types/legacy_denomtrace.go @@ -0,0 +1,42 @@ +package types + +import ( + "crypto/sha256" + "fmt" + + cmtbytes "github.com/cometbft/cometbft/libs/bytes" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +) + +// Hash returns the hex bytes of the SHA256 hash of the DenomTrace fields using the following formula: +// +// hash = sha256(tracePath + "/" + baseDenom) +func (dt DenomTrace) Hash() cmtbytes.HexBytes { + hash := sha256.Sum256([]byte(dt.GetFullDenomPath())) + return hash[:] +} + +// GetPrefix returns the receiving denomination prefix composed by the trace info and a separator. +func (dt DenomTrace) GetPrefix() string { + return dt.Path + "/" +} + +// IBCDenom a coin denomination for an ICS20 fungible token in the format +// 'ibc/{hash(tracePath + baseDenom)}'. If the trace is empty, it will return the base denomination. +func (dt DenomTrace) IBCDenom() string { + if dt.Path != "" { + return fmt.Sprintf("%s/%s", types.DenomPrefix, dt.Hash()) + } + return dt.BaseDenom +} + +// GetFullDenomPath returns the full denomination according to the ICS20 specification: +// tracePath + "/" + baseDenom +// If there exists no trace then the base denomination is returned. +func (dt DenomTrace) GetFullDenomPath() string { + if dt.Path == "" { + return dt.BaseDenom + } + return dt.GetPrefix() + dt.BaseDenom +} diff --git a/modules/apps/transfer/internal/types/legacy_denomtrace_test.go b/modules/apps/transfer/internal/types/legacy_denomtrace_test.go new file mode 100644 index 0000000..efd63e3 --- /dev/null +++ b/modules/apps/transfer/internal/types/legacy_denomtrace_test.go @@ -0,0 +1,26 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + internaltypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/internal/types" +) + +func TestDenomTrace_IBCDenom(t *testing.T) { + testCases := []struct { + name string + trace internaltypes.DenomTrace + expDenom string + }{ + {"base denom", internaltypes.DenomTrace{BaseDenom: "uatom"}, "uatom"}, + {"trace info", internaltypes.DenomTrace{BaseDenom: "uatom", Path: "transfer/channel-1"}, "ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9"}, + } + + for _, tc := range testCases { + + denom := tc.trace.IBCDenom() + require.Equal(t, tc.expDenom, denom, tc.name) + } +} diff --git a/modules/apps/transfer/keeper/MBT_README.md b/modules/apps/transfer/keeper/MBT_README.md new file mode 100644 index 0000000..e18b042 --- /dev/null +++ b/modules/apps/transfer/keeper/MBT_README.md @@ -0,0 +1,49 @@ +# Token Transfer Model-based Testing Guide + +In the process of IBC Audit performed by Informal Systems, we have implemented +a preliminary set of model-based tests for the ICS-20 Token Transfer implementation. + +Model-based tests are based on the formal `TLA+` model of the Token transfer relay functions: see [relay.tla](relay_model/relay.tla). +The tests themselves are simple `TLA+` assertions, that describe the desired shape of execution that send or receive tokens; +see [relay_tests.tla](relay_model/relay_tests.tla) for some examples. +To be able to specify test assertions the TLA+ model contains the `history` variable, +which records the whole execution history. +So, by way of referring to `history` you simply specify declaratively what execution history you want to see. + +After you have specified your `TLA+` test, you can run it using [Apalache model checker](https://github.com/informalsystems/apalache). +E.g. for the test `TestUnescrowTokens` run + +```bash +apalache-mc check --inv=TestUnescrowTokensInv relay_tests.tla +``` + +In case there are no error in the TLA+ model or in the test assertions, this will produce a couple of so-called *counterexamples*. +This is a terminology from the model-checking community; for the testing purposes they can be considered simply as model executions. +See the files `counterexample.tla` for human-readable representation, and `counterexample.json` for machine-readable one. + +In order to execute the produced test, you need to translate it into another format. +For that translation you need the tool [Jsonatr (JSON Artifact Translator)](https://github.com/informalsystems/jsonatr). +It performs the translation using this [transformation spec](relay_model/apalache-to-relay-test2.json); + +To transform a counterexample into a test, run + +```bash +jsonatr --use apalache-to-relay-test2.json --in counterexample.json --out model_based_tests/YourTestName.json +``` + +Now, if you run `go test` in this directory, the file you have produced above should be picked up by the [model-based test driver](mbt_relay_test.go), +and executed automatically. + +The easiest way to run Apalache is by +[using a Docker image](https://apalache-mc.org/docs/apalache/installation/docker.html); +to run Jsonatr you need to locally clone the repository, and then, +after building it, add the `target/debug` directory into your `PATH`. + +To wrap Apalache docker image into an executable you might create the following executable bash script `apalache-mc`: + +```bash +#!/bin/bash +docker run --rm -v $(pwd):/var/apalache apalache/mc $@ +``` + +In case of any questions please don't hesitate to contact Andrey Kuprianov (). diff --git a/modules/apps/transfer/keeper/export_test.go b/modules/apps/transfer/keeper/export_test.go new file mode 100644 index 0000000..beb04a5 --- /dev/null +++ b/modules/apps/transfer/keeper/export_test.go @@ -0,0 +1,34 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + internaltypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/internal/types" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +) + +// SetDenomTrace is a wrapper around setDenomTrace for testing purposes. +func (k Keeper) SetDenomTrace(ctx sdk.Context, denomTrace internaltypes.DenomTrace) { + k.setDenomTrace(ctx, denomTrace) +} + +// IterateDenomTraces is a wrapper around iterateDenomTraces for testing purposes. +func (k Keeper) IterateDenomTraces(ctx sdk.Context, cb func(denomTrace internaltypes.DenomTrace) bool) { + k.iterateDenomTraces(ctx, cb) +} + +// GetAllDenomTraces returns the trace information for all the denominations. +func (k Keeper) GetAllDenomTraces(ctx sdk.Context) []internaltypes.DenomTrace { + var traces []internaltypes.DenomTrace + k.iterateDenomTraces(ctx, func(denomTrace internaltypes.DenomTrace) bool { + traces = append(traces, denomTrace) + return false + }) + + return traces +} + +// CreatePacketDataBytesFromVersion is a wrapper around createPacketDataBytesFromVersion for testing purposes +func CreatePacketDataBytesFromVersion(appVersion, sender, receiver, memo string, token types.Token) ([]byte, error) { + return createPacketDataBytesFromVersion(appVersion, sender, receiver, memo, token) +} diff --git a/modules/apps/transfer/keeper/genesis.go b/modules/apps/transfer/keeper/genesis.go new file mode 100644 index 0000000..361b6ad --- /dev/null +++ b/modules/apps/transfer/keeper/genesis.go @@ -0,0 +1,35 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +) + +// InitGenesis initializes the ibc-transfer state and binds to PortID. +func (k Keeper) InitGenesis(ctx sdk.Context, state types.GenesisState) { + k.SetPort(ctx, state.PortId) + + for _, denom := range state.Denoms { + k.SetDenom(ctx, denom) + k.SetDenomMetadata(ctx, denom) + } + + k.SetParams(ctx, state.Params) + + // Every denom will have only one total escrow amount, since any + // duplicate entry will fail validation in Validate of GenesisState + for _, denomEscrow := range state.TotalEscrowed { + k.SetTotalEscrowForDenom(ctx, denomEscrow) + } +} + +// ExportGenesis exports ibc-transfer module's portID and denom trace info into its genesis state. +func (k Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState { + return &types.GenesisState{ + PortId: k.GetPort(ctx), + Denoms: k.GetAllDenoms(ctx), + Params: k.GetParams(ctx), + TotalEscrowed: k.GetAllTotalEscrowed(ctx), + } +} diff --git a/modules/apps/transfer/keeper/genesis_test.go b/modules/apps/transfer/keeper/genesis_test.go new file mode 100644 index 0000000..6052ab2 --- /dev/null +++ b/modules/apps/transfer/keeper/genesis_test.go @@ -0,0 +1,59 @@ +package keeper_test + +import ( + "fmt" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +) + +func (suite *KeeperTestSuite) TestGenesis() { + getHop := func(index uint) types.Hop { + return types.NewHop("transfer", fmt.Sprintf("channelToChain%d", index)) + } + + var ( + denoms types.Denoms + escrows sdk.Coins + traceAndEscrowAmounts = []struct { + trace []types.Hop + escrow string + }{ + {[]types.Hop{getHop(0)}, "10"}, + {[]types.Hop{getHop(1), getHop(0)}, "100000"}, + {[]types.Hop{getHop(2), getHop(1), getHop(0)}, "10000000000"}, + {[]types.Hop{getHop(3), getHop(2), getHop(1), getHop(0)}, "1000000000000000"}, + {[]types.Hop{getHop(4), getHop(3), getHop(2), getHop(1), getHop(0)}, "100000000000000000000"}, + } + ) + + for _, traceAndEscrowAmount := range traceAndEscrowAmounts { + denom := types.NewDenom("uatom", traceAndEscrowAmount.trace...) + denoms = append(denoms, denom) + suite.chainA.GetSimApp().TransferKeeper.SetDenom(suite.chainA.GetContext(), denom) + + amount, ok := sdkmath.NewIntFromString(traceAndEscrowAmount.escrow) + suite.Require().True(ok) + escrow := sdk.NewCoin(denom.IBCDenom(), amount) + escrows = append(escrows, escrow) + suite.chainA.GetSimApp().TransferKeeper.SetTotalEscrowForDenom(suite.chainA.GetContext(), escrow) + } + + genesis := suite.chainA.GetSimApp().TransferKeeper.ExportGenesis(suite.chainA.GetContext()) + + suite.Require().Equal(types.PortID, genesis.PortId) + suite.Require().Equal(denoms.Sort(), genesis.Denoms) + suite.Require().Equal(escrows.Sort(), genesis.TotalEscrowed) + + suite.Require().NotPanics(func() { + suite.chainA.GetSimApp().TransferKeeper.InitGenesis(suite.chainA.GetContext(), *genesis) + }) + + for _, denom := range denoms { + _, found := suite.chainA.GetSimApp().BankKeeper.GetDenomMetaData(suite.chainA.GetContext(), denom.IBCDenom()) + suite.Require().True(found) + } +} diff --git a/modules/apps/transfer/keeper/grpc_query.go b/modules/apps/transfer/keeper/grpc_query.go new file mode 100644 index 0000000..499aea9 --- /dev/null +++ b/modules/apps/transfer/keeper/grpc_query.go @@ -0,0 +1,160 @@ +package keeper + +import ( + "context" + "fmt" + "strings" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/store/prefix" + + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + + "github.com/cosmos/ibc-go/v10/internal/validate" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" +) + +var _ types.QueryServer = (*Keeper)(nil) + +// Denom implements the Query/Denom gRPC method +func (k Keeper) Denom(goCtx context.Context, req *types.QueryDenomRequest) (*types.QueryDenomResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + hash, err := types.ParseHexHash(strings.TrimPrefix(req.Hash, "ibc/")) + if err != nil { + return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("invalid denom trace hash: %s, error: %s", hash.String(), err)) + } + + denom, found := k.GetDenom(ctx, hash) + if !found { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrap(types.ErrDenomNotFound, req.Hash).Error(), + ) + } + + return &types.QueryDenomResponse{ + Denom: &denom, + }, nil +} + +// Denoms implements the Query/Denoms gRPC method +func (k Keeper) Denoms(ctx context.Context, req *types.QueryDenomsRequest) (*types.QueryDenomsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + var denoms types.Denoms + store := prefix.NewStore(runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)), types.DenomKey) + + pageRes, err := query.Paginate(store, req.Pagination, func(_, value []byte) error { + var denom types.Denom + if err := k.cdc.Unmarshal(value, &denom); err != nil { + return err + } + + denoms = append(denoms, denom) + return nil + }) + if err != nil { + return nil, err + } + + return &types.QueryDenomsResponse{ + Denoms: denoms.Sort(), + Pagination: pageRes, + }, nil +} + +// Params implements the Query/Params gRPC method +func (k Keeper) Params(goCtx context.Context, _ *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + params := k.GetParams(ctx) + + return &types.QueryParamsResponse{ + Params: ¶ms, + }, nil +} + +// DenomHash implements the Query/DenomHash gRPC method +func (k Keeper) DenomHash(goCtx context.Context, req *types.QueryDenomHashRequest) (*types.QueryDenomHashResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + // Convert given request trace path to Denom struct to confirm the path in a valid denom trace format + denom := types.ExtractDenomFromPath(req.Trace) + if err := denom.Validate(); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + denomHash := denom.Hash() + found := k.HasDenom(ctx, denomHash) + if !found { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrap(types.ErrDenomNotFound, req.Trace).Error(), + ) + } + + return &types.QueryDenomHashResponse{ + Hash: denomHash.String(), + }, nil +} + +// EscrowAddress implements the EscrowAddress gRPC method +func (k Keeper) EscrowAddress(goCtx context.Context, req *types.QueryEscrowAddressRequest) (*types.QueryEscrowAddressResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + addr := types.GetEscrowAddress(req.PortId, req.ChannelId) + + if err := validate.GRPCRequest(req.PortId, req.ChannelId); err != nil { + return nil, err + } + + if !k.channelKeeper.HasChannel(ctx, req.PortId, req.ChannelId) { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrapf(channeltypes.ErrChannelNotFound, "port ID (%s) channel ID (%s)", req.PortId, req.ChannelId).Error(), + ) + } + + return &types.QueryEscrowAddressResponse{ + EscrowAddress: addr.String(), + }, nil +} + +// TotalEscrowForDenom implements the TotalEscrowForDenom gRPC method. +func (k Keeper) TotalEscrowForDenom(goCtx context.Context, req *types.QueryTotalEscrowForDenomRequest) (*types.QueryTotalEscrowForDenomResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := sdk.ValidateDenom(req.Denom); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + amount := k.GetTotalEscrowForDenom(ctx, req.Denom) + + return &types.QueryTotalEscrowForDenomResponse{ + Amount: amount, + }, nil +} diff --git a/modules/apps/transfer/keeper/grpc_query_test.go b/modules/apps/transfer/keeper/grpc_query_test.go new file mode 100644 index 0000000..4d30f07 --- /dev/null +++ b/modules/apps/transfer/keeper/grpc_query_test.go @@ -0,0 +1,398 @@ +package keeper_test + +import ( + "errors" + "fmt" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *KeeperTestSuite) TestQueryDenom() { + var ( + req *types.QueryDenomRequest + expDenom types.Denom + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "success: correct ibc denom", + func() { + expDenom = types.NewDenom( + "uatom", //nolint:goconst + types.NewHop("transfer", "channelToA"), //nolint:goconst + types.NewHop("transfer", "channelToB"), //nolint:goconst + ) + suite.chainA.GetSimApp().TransferKeeper.SetDenom(suite.chainA.GetContext(), expDenom) + + req = &types.QueryDenomRequest{ + Hash: expDenom.IBCDenom(), + } + }, + nil, + }, + { + "success: correct hex hash", + func() { + expDenom = types.NewDenom( + "uatom", //nolint:goconst + types.NewHop("transfer", "channelToA"), //nolint:goconst + types.NewHop("transfer", "channelToB"), //nolint:goconst + ) + suite.chainA.GetSimApp().TransferKeeper.SetDenom(suite.chainA.GetContext(), expDenom) + + req = &types.QueryDenomRequest{ + Hash: expDenom.Hash().String(), + } + }, + nil, + }, + { + "failure: invalid hash", + func() { + req = &types.QueryDenomRequest{ + Hash: "!@#!@#!", + } + }, + errors.New("invalid denom trace hash"), + }, + { + "failure: not found denom trace", + func() { + expDenom = types.NewDenom( + "uatom", //nolint:goconst + types.NewHop("transfer", "channelToA"), //nolint:goconst + types.NewHop("transfer", "channelToB"), //nolint:goconst + ) + + req = &types.QueryDenomRequest{ + Hash: expDenom.IBCDenom(), + } + }, + errors.New("denomination not found"), + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + tc := tc + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + res, err := suite.chainA.GetSimApp().TransferKeeper.Denom(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(&expDenom, res.Denom) + } else { + ibctesting.RequireErrorIsOrContains(suite.T(), err, tc.expErr, err.Error()) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryDenoms() { + var ( + req *types.QueryDenomsRequest + expDenoms = types.Denoms(nil) + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "empty pagination", + func() { + req = &types.QueryDenomsRequest{} + }, + nil, + }, + { + "success", + func() { + expDenoms = append(expDenoms, types.NewDenom("uatom")) + expDenoms = append(expDenoms, types.NewDenom("uatom", types.NewHop("transfer", "channelToB"))) + expDenoms = append(expDenoms, types.NewDenom("uatom", types.NewHop("transfer", "channelToA"), types.NewHop("transfer", "channelToB"))) + + for _, trace := range expDenoms { + suite.chainA.GetSimApp().TransferKeeper.SetDenom(suite.chainA.GetContext(), trace) + } + + req = &types.QueryDenomsRequest{ + Pagination: &query.PageRequest{ + Limit: 5, + CountTotal: false, + }, + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(tc.msg, func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + res, err := suite.chainA.GetSimApp().TransferKeeper.Denoms(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expDenoms.Sort(), res.Denoms) + } else { + ibctesting.RequireErrorIsOrContains(suite.T(), err, tc.expErr, err.Error()) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryParams() { + ctx := suite.chainA.GetContext() + expParams := types.DefaultParams() + res, _ := suite.chainA.GetSimApp().TransferKeeper.Params(ctx, &types.QueryParamsRequest{}) + suite.Require().Equal(&expParams, res.Params) +} + +func (suite *KeeperTestSuite) TestQueryDenomHash() { + reqDenom := types.NewDenom("uatom", types.NewHop("transfer", "channelToA"), types.NewHop("transfer", "channelToB")) + + var ( + req *types.QueryDenomHashRequest + expHash = reqDenom.Hash().String() + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "success", + func() {}, + nil, + }, + { + "invalid trace", + func() { + req = &types.QueryDenomHashRequest{ + Trace: "transfer%%/channel-1/transfer/channel-1/uatom", + } + }, + errors.New("invalid trace"), + }, + { + "not found denom trace", + func() { + req = &types.QueryDenomHashRequest{ + Trace: "transfer/channelToC/uatom", + } + }, + errors.New("denomination not found"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.msg, func() { + suite.SetupTest() // reset + + req = &types.QueryDenomHashRequest{ + Trace: reqDenom.Path(), + } + suite.chainA.GetSimApp().TransferKeeper.SetDenom(suite.chainA.GetContext(), reqDenom) + + tc.malleate() + ctx := suite.chainA.GetContext() + + res, err := suite.chainA.GetSimApp().TransferKeeper.DenomHash(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expHash, res.Hash) + } else { + ibctesting.RequireErrorIsOrContains(suite.T(), err, tc.expErr, err.Error()) + } + }) + } +} + +func (suite *KeeperTestSuite) TestEscrowAddress() { + var req *types.QueryEscrowAddressRequest + var path *ibctesting.Path + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "success", + func() { + req = &types.QueryEscrowAddressRequest{ + PortId: ibctesting.TransferPort, + ChannelId: path.EndpointA.ChannelID, + } + }, + nil, + }, + { + "failure - channel not found", + func() { + req = &types.QueryEscrowAddressRequest{ + PortId: ibctesting.InvalidID, + ChannelId: ibctesting.FirstChannelID, + } + }, + errors.New("channel not found"), + }, + { + "failure - empty channelID", + func() { + req = &types.QueryEscrowAddressRequest{ + PortId: ibctesting.TransferPort, + ChannelId: "", + } + }, + errors.New("identifier cannot be blank"), + }, + { + "failure - empty portID", + func() { + req = &types.QueryEscrowAddressRequest{ + PortId: "", + ChannelId: ibctesting.FirstChannelID, + } + }, + errors.New("identifier cannot be blank"), + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + path = ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path.Setup() + + tc.malleate() + ctx := suite.chainA.GetContext() + + res, err := suite.chainA.GetSimApp().TransferKeeper.EscrowAddress(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + expected := types.GetEscrowAddress(ibctesting.TransferPort, path.EndpointA.ChannelID).String() + suite.Require().Equal(expected, res.EscrowAddress) + } else { + ibctesting.RequireErrorIsOrContains(suite.T(), err, tc.expErr, err.Error()) + } + }) + } +} + +func (suite *KeeperTestSuite) TestTotalEscrowForDenom() { + var ( + req *types.QueryTotalEscrowForDenomRequest + expEscrowAmount sdkmath.Int + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "valid native denom with escrow amount < 2^63", + func() { + req = &types.QueryTotalEscrowForDenomRequest{ + Denom: sdk.DefaultBondDenom, + } + + expEscrowAmount = sdkmath.NewInt(100) + suite.chainA.GetSimApp().TransferKeeper.SetTotalEscrowForDenom(suite.chainA.GetContext(), sdk.NewCoin(sdk.DefaultBondDenom, expEscrowAmount)) + }, + nil, + }, + { + "valid ibc denom with escrow amount > 2^63", + func() { + denom := types.NewDenom(sdk.DefaultBondDenom, types.NewHop("transfer", "channel-0")) + + suite.chainA.GetSimApp().TransferKeeper.SetDenom(suite.chainA.GetContext(), denom) + expEscrowAmount, ok := sdkmath.NewIntFromString("100000000000000000000") + suite.Require().True(ok) + suite.chainA.GetSimApp().TransferKeeper.SetTotalEscrowForDenom(suite.chainA.GetContext(), sdk.NewCoin(sdk.DefaultBondDenom, expEscrowAmount)) + + req = &types.QueryTotalEscrowForDenomRequest{ + Denom: denom.IBCDenom(), + } + }, + nil, + }, + { + "valid ibc denom treated as native denom", + func() { + denom := types.NewDenom(sdk.DefaultBondDenom, types.NewHop("transfer", "channel-0")) + + req = &types.QueryTotalEscrowForDenomRequest{ + Denom: denom.IBCDenom(), + } + }, + nil, // denom trace is not found, thus the denom is considered a native token + }, + { + "invalid ibc denom treated as valid native denom", + func() { + req = &types.QueryTotalEscrowForDenomRequest{ + Denom: "ibc/123", + } + }, + nil, // the ibc denom does not contain a valid hash, thus the denom is considered a native token + }, + { + "invalid denom", + func() { + req = &types.QueryTotalEscrowForDenomRequest{ + Denom: "??𓃠🐾??", + } + }, + errors.New("invalid denom"), + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + expEscrowAmount = sdkmath.ZeroInt() + tc.malleate() + ctx := suite.chainA.GetContext() + + res, err := suite.chainA.GetSimApp().TransferKeeper.TotalEscrowForDenom(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().Equal(expEscrowAmount, res.Amount.Amount) + } else { + ibctesting.RequireErrorIsOrContains(suite.T(), err, tc.expErr, err.Error()) + suite.Require().Error(err) + } + }) + } +} diff --git a/modules/apps/transfer/keeper/keeper.go b/modules/apps/transfer/keeper/keeper.go new file mode 100644 index 0000000..93b049c --- /dev/null +++ b/modules/apps/transfer/keeper/keeper.go @@ -0,0 +1,326 @@ +package keeper + +import ( + "errors" + "fmt" + "strings" + + "cosmossdk.io/core/address" + corestore "cosmossdk.io/core/store" + "cosmossdk.io/log" + sdkmath "cosmossdk.io/math" + "cosmossdk.io/store/prefix" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + cmtbytes "github.com/cometbft/cometbft/libs/bytes" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// Keeper defines the IBC fungible transfer keeper +type Keeper struct { + storeService corestore.KVStoreService + cdc codec.BinaryCodec + legacySubspace types.ParamSubspace + addressCodec address.Codec + + ics4Wrapper porttypes.ICS4Wrapper + channelKeeper types.ChannelKeeper + msgRouter types.MessageRouter + AuthKeeper types.AccountKeeper + BankKeeper types.BankKeeper + + // the address capable of executing a MsgUpdateParams message. Typically, this + // should be the x/gov module account. + authority string +} + +// NewKeeper creates a new IBC transfer Keeper instance +func NewKeeper( + cdc codec.BinaryCodec, + storeService corestore.KVStoreService, + legacySubspace types.ParamSubspace, + ics4Wrapper porttypes.ICS4Wrapper, + channelKeeper types.ChannelKeeper, + msgRouter types.MessageRouter, + authKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, + authority string, +) Keeper { + // ensure ibc transfer module account is set + if addr := authKeeper.GetModuleAddress(types.ModuleName); addr == nil { + panic(errors.New("the IBC transfer module account has not been set")) + } + + if strings.TrimSpace(authority) == "" { + panic(errors.New("authority must be non-empty")) + } + + addressCodec := authcodec.NewBech32Codec(sdk.GetConfig().GetBech32AccountAddrPrefix()) + + return Keeper{ + cdc: cdc, + storeService: storeService, + legacySubspace: legacySubspace, + addressCodec: addressCodec, + ics4Wrapper: ics4Wrapper, + channelKeeper: channelKeeper, + msgRouter: msgRouter, + AuthKeeper: authKeeper, + BankKeeper: bankKeeper, + authority: authority, + } +} + +// WithICS4Wrapper sets the ICS4Wrapper. This function may be used after +// the keepers creation to set the middleware which is above this module +// in the IBC application stack. +func (k *Keeper) WithICS4Wrapper(wrapper porttypes.ICS4Wrapper) { + k.ics4Wrapper = wrapper +} + +// GetICS4Wrapper returns the ICS4Wrapper. +func (k Keeper) GetICS4Wrapper() porttypes.ICS4Wrapper { + return k.ics4Wrapper +} + +// GetAuthority returns the transfer module's authority. +func (k Keeper) GetAuthority() string { + return k.authority +} + +// SetAddressCodec sets the address codec used by the keeper. +func (k *Keeper) SetAddressCodec(addressCodec address.Codec) { + k.addressCodec = addressCodec +} + +// GetAddressCodec returns the address codec used by the keeper. +func (k *Keeper) GetAddressCodec() address.Codec { + return k.addressCodec +} + +// Logger returns a module-specific logger. +func (Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", "x/"+exported.ModuleName+"-"+types.ModuleName) +} + +// GetPort returns the portID for the transfer module. Used in ExportGenesis +func (k Keeper) GetPort(ctx sdk.Context) string { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(types.PortKey) + if err != nil { + panic(err) + } + return string(bz) +} + +// SetPort sets the portID for the transfer module. Used in InitGenesis +func (k Keeper) SetPort(ctx sdk.Context, portID string) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Set(types.PortKey, []byte(portID)); err != nil { + panic(err) + } +} + +// GetParams returns the current transfer module parameters. +func (k Keeper) GetParams(ctx sdk.Context) types.Params { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get([]byte(types.ParamsKey)) + if err != nil { + panic(err) + } + if bz == nil { // only panic on unset params and not on empty params + panic(errors.New("transfer params are not set in store")) + } + + var params types.Params + k.cdc.MustUnmarshal(bz, ¶ms) + return params +} + +// SetParams sets the transfer module parameters. +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { + store := k.storeService.OpenKVStore(ctx) + bz := k.cdc.MustMarshal(¶ms) + if err := store.Set([]byte(types.ParamsKey), bz); err != nil { + panic(err) + } +} + +// GetDenom retrieves the denom from store given the hash of the denom. +func (k Keeper) GetDenom(ctx sdk.Context, denomHash cmtbytes.HexBytes) (types.Denom, bool) { + store := prefix.NewStore(runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)), types.DenomKey) + bz := store.Get(denomHash) + if len(bz) == 0 { + return types.Denom{}, false + } + + var denom types.Denom + k.cdc.MustUnmarshal(bz, &denom) + + return denom, true +} + +// HasDenom checks if a the key with the given denomination hash exists on the store. +func (k Keeper) HasDenom(ctx sdk.Context, denomHash cmtbytes.HexBytes) bool { + store := prefix.NewStore(runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)), types.DenomKey) + return store.Has(denomHash) +} + +// SetDenom sets a new {denom hash -> denom } pair to the store. +// This allows for reverse lookup of the denom given the hash. +func (k Keeper) SetDenom(ctx sdk.Context, denom types.Denom) { + store := prefix.NewStore(runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)), types.DenomKey) + bz := k.cdc.MustMarshal(&denom) + store.Set(denom.Hash(), bz) +} + +// GetAllDenoms returns all the denominations. +func (k Keeper) GetAllDenoms(ctx sdk.Context) types.Denoms { + denoms := types.Denoms{} + k.IterateDenoms(ctx, func(denom types.Denom) bool { + denoms = append(denoms, denom) + return false + }) + + return denoms.Sort() +} + +// IterateDenoms iterates over the denominations in the store and performs a callback function. +func (k Keeper) IterateDenoms(ctx sdk.Context, cb func(denom types.Denom) bool) { + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + iterator := storetypes.KVStorePrefixIterator(store, types.DenomKey) + + defer sdk.LogDeferred(k.Logger(ctx), func() error { return iterator.Close() }) + for ; iterator.Valid(); iterator.Next() { + var denom types.Denom + k.cdc.MustUnmarshal(iterator.Value(), &denom) + + if cb(denom) { + break + } + } +} + +// SetDenomMetadata sets an IBC token's denomination metadata +func (k Keeper) SetDenomMetadata(ctx sdk.Context, denom types.Denom) { + metadata := banktypes.Metadata{ + Description: fmt.Sprintf("IBC token from %s", denom.Path()), + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: denom.Base, + Exponent: 0, + }, + }, + // Setting base as IBC hash denom since bank keepers's SetDenomMetadata uses + // Base as key path and the IBC hash is what gives this token uniqueness + // on the executing chain + Base: denom.IBCDenom(), + Display: denom.Path(), + Name: fmt.Sprintf("%s IBC token", denom.Path()), + Symbol: strings.ToUpper(denom.Base), + } + + k.BankKeeper.SetDenomMetaData(ctx, metadata) +} + +// GetTotalEscrowForDenom gets the total amount of source chain tokens that +// are in escrow, keyed by the denomination. +// +// NOTE: if there is no value stored in state for the provided denom then a new Coin is returned for the denom with an initial value of zero. +// This accommodates callers to simply call `Add()` on the returned Coin as an empty Coin literal (e.g. sdk.Coin{}) will trigger a panic due to the absence of a denom. +func (k Keeper) GetTotalEscrowForDenom(ctx sdk.Context, denom string) sdk.Coin { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(types.TotalEscrowForDenomKey(denom)) + if err != nil { + panic(err) + } + if len(bz) == 0 { + return sdk.NewCoin(denom, sdkmath.ZeroInt()) + } + + amount := sdk.IntProto{} + k.cdc.MustUnmarshal(bz, &amount) + + return sdk.NewCoin(denom, amount.Int) +} + +// SetTotalEscrowForDenom stores the total amount of source chain tokens that are in escrow. +// Amount is stored in state if and only if it is not equal to zero. The function will panic +// if the amount is negative. +func (k Keeper) SetTotalEscrowForDenom(ctx sdk.Context, coin sdk.Coin) { + if coin.Amount.IsNegative() { + panic(fmt.Errorf("amount cannot be negative: %s", coin.Amount)) + } + + store := k.storeService.OpenKVStore(ctx) + key := types.TotalEscrowForDenomKey(coin.Denom) + + if coin.Amount.IsZero() { + if err := store.Delete(key); err != nil { // delete the key since Cosmos SDK x/bank module will prune any non-zero balances + panic(err) + } + return + } + + bz := k.cdc.MustMarshal(&sdk.IntProto{Int: coin.Amount}) + if err := store.Set(key, bz); err != nil { + panic(err) + } +} + +// GetAllTotalEscrowed returns the escrow information for all the denominations. +func (k Keeper) GetAllTotalEscrowed(ctx sdk.Context) sdk.Coins { + var escrows sdk.Coins + k.IterateTokensInEscrow(ctx, []byte(types.KeyTotalEscrowPrefix), func(denomEscrow sdk.Coin) bool { + escrows = escrows.Add(denomEscrow) + return false + }) + + return escrows +} + +// IterateTokensInEscrow iterates over the denomination escrows in the store +// and performs a callback function. Denominations for which an invalid value +// (i.e. not integer) is stored, will be skipped. +func (k Keeper) IterateTokensInEscrow(ctx sdk.Context, storeprefix []byte, cb func(denomEscrow sdk.Coin) bool) { + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + iterator := storetypes.KVStorePrefixIterator(store, storeprefix) + + defer sdk.LogDeferred(k.Logger(ctx), func() error { return iterator.Close() }) + for ; iterator.Valid(); iterator.Next() { + denom := strings.TrimPrefix(string(iterator.Key()), fmt.Sprintf("%s/", types.KeyTotalEscrowPrefix)) + if strings.TrimSpace(denom) == "" { + continue // denom is empty + } + + amount := sdk.IntProto{} + if err := k.cdc.Unmarshal(iterator.Value(), &amount); err != nil { + continue // total escrow amount cannot be unmarshalled to integer + } + + denomEscrow := sdk.NewCoin(denom, amount.Int) + if cb(denomEscrow) { + break + } + } +} + +// IsBlockedAddr checks if the given address is allowed to send or receive tokens. +// The module account is always allowed to send and receive tokens. +func (k Keeper) IsBlockedAddr(addr sdk.AccAddress) bool { + moduleAddr := k.AuthKeeper.GetModuleAddress(types.ModuleName) + if addr.Equals(moduleAddr) { + return false + } + + return k.BankKeeper.BlockedAddr(addr) +} diff --git a/modules/apps/transfer/keeper/keeper_test.go b/modules/apps/transfer/keeper/keeper_test.go new file mode 100644 index 0000000..53142bb --- /dev/null +++ b/modules/apps/transfer/keeper/keeper_test.go @@ -0,0 +1,384 @@ +package keeper_test + +import ( + "errors" + "fmt" + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + sdkmath "cosmossdk.io/math" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/keeper" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + channelkeeper "github.com/cosmos/ibc-go/v10/modules/core/04-channel/keeper" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +type KeeperTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains used for convenience and readability + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain + chainC *ibctesting.TestChain +} + +func (suite *KeeperTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 3) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) + suite.chainC = suite.coordinator.GetChain(ibctesting.GetChainID(3)) + + queryHelper := baseapp.NewQueryServerTestHelper(suite.chainA.GetContext(), suite.chainA.GetSimApp().InterfaceRegistry()) + types.RegisterQueryServer(queryHelper, suite.chainA.GetSimApp().TransferKeeper) +} + +func TestKeeperTestSuite(t *testing.T) { + testifysuite.Run(t, new(KeeperTestSuite)) +} + +func (suite *KeeperTestSuite) TestNewKeeper() { + testCases := []struct { + name string + instantiateFn func() + panicMsg string + }{ + {"success", func() { + keeper.NewKeeper( + suite.chainA.GetSimApp().AppCodec(), + runtime.NewKVStoreService(suite.chainA.GetSimApp().GetKey(types.StoreKey)), + suite.chainA.GetSimApp().GetSubspace(types.ModuleName), + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, + suite.chainA.GetSimApp().MsgServiceRouter(), + suite.chainA.GetSimApp().AccountKeeper, + suite.chainA.GetSimApp().BankKeeper, + suite.chainA.GetSimApp().ICAControllerKeeper.GetAuthority(), + ) + }, ""}, + {"failure: transfer module account does not exist", func() { + keeper.NewKeeper( + suite.chainA.GetSimApp().AppCodec(), + runtime.NewKVStoreService(suite.chainA.GetSimApp().GetKey(types.StoreKey)), + suite.chainA.GetSimApp().GetSubspace(types.ModuleName), + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, + suite.chainA.GetSimApp().MsgServiceRouter(), + authkeeper.AccountKeeper{}, // empty account keeper + suite.chainA.GetSimApp().BankKeeper, + suite.chainA.GetSimApp().ICAControllerKeeper.GetAuthority(), + ) + }, "the IBC transfer module account has not been set"}, + {"failure: empty authority", func() { + keeper.NewKeeper( + suite.chainA.GetSimApp().AppCodec(), + runtime.NewKVStoreService(suite.chainA.GetSimApp().GetKey(types.StoreKey)), + suite.chainA.GetSimApp().GetSubspace(types.ModuleName), + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, + suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper, + suite.chainA.GetSimApp().MsgServiceRouter(), + suite.chainA.GetSimApp().AccountKeeper, + suite.chainA.GetSimApp().BankKeeper, + "", // authority + ) + }, "authority must be non-empty"}, + } + + for _, tc := range testCases { + + suite.SetupTest() + + suite.Run(tc.name, func() { + if tc.panicMsg == "" { + suite.Require().NotPanics( + tc.instantiateFn, + ) + } else { + suite.Require().PanicsWithError( + tc.panicMsg, + tc.instantiateFn, + ) + } + }) + } +} + +func (suite *KeeperTestSuite) TestSetGetTotalEscrowForDenom() { + const denom = "atom" + var expAmount sdkmath.Int + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success: with non-zero escrow amount", + func() {}, + nil, + }, + { + "success: with escrow amount > 2^63", + func() { + expAmount, _ = sdkmath.NewIntFromString("100000000000000000000") + }, + nil, + }, + { + "success: escrow amount 0 is not stored", + func() { + expAmount = sdkmath.ZeroInt() + }, + nil, + }, + { + "failure: setter panics with negative escrow amount", + func() { + expAmount = sdkmath.NewInt(-1) + }, + errors.New("amount cannot be negative: -1"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + expAmount = sdkmath.NewInt(100) + ctx := suite.chainA.GetContext() + + tc.malleate() + + coin := sdk.Coin{ + Denom: denom, + Amount: expAmount, + } + + if tc.expError == nil { + suite.chainA.GetSimApp().TransferKeeper.SetTotalEscrowForDenom(ctx, coin) + total := suite.chainA.GetSimApp().TransferKeeper.GetTotalEscrowForDenom(ctx, denom) + suite.Require().Equal(expAmount, total.Amount) + + storeKey := suite.chainA.GetSimApp().GetKey(types.ModuleName) + store := ctx.KVStore(storeKey) + key := types.TotalEscrowForDenomKey(denom) + if expAmount.IsZero() { + suite.Require().False(store.Has(key)) + } else { + suite.Require().True(store.Has(key)) + } + } else { + suite.Require().PanicsWithError(tc.expError.Error(), func() { + suite.chainA.GetSimApp().TransferKeeper.SetTotalEscrowForDenom(ctx, coin) + }) + total := suite.chainA.GetSimApp().TransferKeeper.GetTotalEscrowForDenom(ctx, denom) + suite.Require().Equal(sdkmath.ZeroInt(), total.Amount) + } + }) + } +} + +func (suite *KeeperTestSuite) TestGetAllDenomEscrows() { + var ( + store storetypes.KVStore + cdc codec.Codec + expDenomEscrows sdk.Coins + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "success", + func() { + denom := "uatom" //nolint:goconst + amount := sdkmath.NewInt(100) + expDenomEscrows = append(expDenomEscrows, sdk.NewCoin(denom, amount)) + + bz := cdc.MustMarshal(&sdk.IntProto{Int: amount}) + store.Set(types.TotalEscrowForDenomKey(denom), bz) + }, + true, + }, + { + "success: multiple denoms", + func() { + denom := "uatom" + amount := sdkmath.NewInt(100) + expDenomEscrows = append(expDenomEscrows, sdk.NewCoin(denom, amount)) + + bz := cdc.MustMarshal(&sdk.IntProto{Int: amount}) + store.Set(types.TotalEscrowForDenomKey(denom), bz) + + denom = "bar/foo" + amount = sdkmath.NewInt(50) + expDenomEscrows = append(expDenomEscrows, sdk.NewCoin(denom, amount)) + + bz = cdc.MustMarshal(&sdk.IntProto{Int: amount}) + store.Set(types.TotalEscrowForDenomKey(denom), bz) + }, + true, + }, + { + "success: denom with non-alphanumeric characters", + func() { + denom := "ibc/123-456" + amount := sdkmath.NewInt(100) + expDenomEscrows = append(expDenomEscrows, sdk.NewCoin(denom, amount)) + + bz := cdc.MustMarshal(&sdk.IntProto{Int: amount}) + store.Set(types.TotalEscrowForDenomKey(denom), bz) + }, + true, + }, + { + "failure: empty denom", + func() { + denom := "" + amount := sdkmath.ZeroInt() + + bz := cdc.MustMarshal(&sdk.IntProto{Int: amount}) + store.Set(types.TotalEscrowForDenomKey(denom), bz) + }, + false, + }, + { + "failure: wrong prefix key", + func() { + denom := "uatom" + amount := sdkmath.ZeroInt() + + bz := cdc.MustMarshal(&sdk.IntProto{Int: amount}) + store.Set(fmt.Appendf(nil, "wrong-prefix/%s", denom), bz) + }, + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + expDenomEscrows = sdk.Coins{} + ctx := suite.chainA.GetContext() + + storeKey := suite.chainA.GetSimApp().GetKey(types.ModuleName) + store = ctx.KVStore(storeKey) + cdc = suite.chainA.App.AppCodec() + + tc.malleate() + + denomEscrows := suite.chainA.GetSimApp().TransferKeeper.GetAllTotalEscrowed(ctx) + + if tc.expPass { + suite.Require().Len(expDenomEscrows, len(denomEscrows)) + suite.Require().ElementsMatch(expDenomEscrows, denomEscrows) + } else { + suite.Require().Empty(denomEscrows) + } + }) + } +} + +func (suite *KeeperTestSuite) TestParams() { + testCases := []struct { + name string + input types.Params + panicMsg string + }{ + // it is not possible to set invalid booleans + {"success: set params false-false", types.NewParams(false, false), ""}, + {"success: set params false-true", types.NewParams(false, true), ""}, + {"success: set params true-false", types.NewParams(true, false), ""}, + {"success: set params true-true", types.NewParams(true, true), ""}, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + ctx := suite.chainA.GetContext() + if tc.panicMsg == "" { + suite.chainA.GetSimApp().TransferKeeper.SetParams(ctx, tc.input) + expected := tc.input + p := suite.chainA.GetSimApp().TransferKeeper.GetParams(ctx) + suite.Require().Equal(expected, p) + } else { + suite.Require().PanicsWithError(tc.panicMsg, func() { + suite.chainA.GetSimApp().TransferKeeper.SetParams(ctx, tc.input) + }) + } + }) + } +} + +func (suite *KeeperTestSuite) TestUnsetParams() { + suite.SetupTest() + + ctx := suite.chainA.GetContext() + store := suite.chainA.GetContext().KVStore(suite.chainA.GetSimApp().GetKey(types.ModuleName)) + store.Delete([]byte(types.ParamsKey)) + + suite.Require().Panics(func() { + suite.chainA.GetSimApp().TransferKeeper.GetParams(ctx) + }) +} + +func (suite *KeeperTestSuite) TestWithICS4Wrapper() { + suite.SetupTest() + + // test if the ics4 wrapper is the channel keeper initially + ics4Wrapper := suite.chainA.GetSimApp().TransferKeeper.GetICS4Wrapper() + + _, isChannelKeeper := ics4Wrapper.(*channelkeeper.Keeper) + suite.Require().True(isChannelKeeper) + suite.Require().IsType((*channelkeeper.Keeper)(nil), ics4Wrapper) + + // set the ics4 wrapper to the channel keeper + suite.chainA.GetSimApp().TransferKeeper.WithICS4Wrapper(nil) + ics4Wrapper = suite.chainA.GetSimApp().TransferKeeper.GetICS4Wrapper() + suite.Require().Nil(ics4Wrapper) +} + +func (suite *KeeperTestSuite) TestIsBlockedAddr() { + suite.SetupTest() + + testCases := []struct { + name string + addr sdk.AccAddress + expBlock bool + }{ + { + "transfer module account address", + suite.chainA.GetSimApp().AccountKeeper.GetModuleAddress(types.ModuleName), + false, + }, + { + "regular address", + suite.chainA.SenderAccount.GetAddress(), + false, + }, + { + "blocked address", + suite.chainA.GetSimApp().AccountKeeper.GetModuleAddress(minttypes.ModuleName), + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.Require().Equal(tc.expBlock, suite.chainA.GetSimApp().TransferKeeper.IsBlockedAddr(tc.addr)) + }) + } +} diff --git a/modules/apps/transfer/keeper/mbt_relay_test.go b/modules/apps/transfer/keeper/mbt_relay_test.go new file mode 100644 index 0000000..f2e6e2b --- /dev/null +++ b/modules/apps/transfer/keeper/mbt_relay_test.go @@ -0,0 +1,403 @@ +package keeper_test + +// This file is a test driver for model-based tests generated from the TLA+ model of token transfer +// Written by Andrey Kuprianov within the scope of IBC Audit performed by Informal Systems. +// In case of any questions please don't hesitate to contact andrey@informal.systems. + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "strconv" + "strings" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cometbft/cometbft/crypto" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +type TlaBalance struct { + Address []string `json:"address"` + Denom []string `json:"denom"` + Amount int64 `json:"amount"` +} + +type TlaFungibleTokenPacketData struct { + Sender string `json:"sender"` + Receiver string `json:"receiver"` + Amount string `json:"amount"` + Denom []string `json:"denom"` +} + +type TlaFungibleTokenPacket struct { + SourceChannel string `json:"sourceChannel"` + SourcePort string `json:"sourcePort"` + DestChannel string `json:"destChannel"` + DestPort string `json:"destPort"` + Data TlaFungibleTokenPacketData `json:"data"` +} + +type TlaOnRecvPacketTestCase = struct { + // The required subset of bank balances + BankBefore []TlaBalance `json:"bankBefore"` + // The packet to process + Packet TlaFungibleTokenPacket `json:"packet"` + // The handler to call + Handler string `json:"handler"` + // The expected changes in the bank + BankAfter []TlaBalance `json:"bankAfter"` + // Whether OnRecvPacket should fail or not + Error bool `json:"error"` +} + +type FungibleTokenPacket struct { + SourceChannel string + SourcePort string + DestChannel string + DestPort string + Data types.InternalTransferRepresentation +} + +type OnRecvPacketTestCase = struct { + description string + // The required subset of bank balances + bankBefore []Balance + // The packet to process + packet FungibleTokenPacket + // The handler to call + handler string + // The expected bank state after processing (wrt. bankBefore) + bankAfter []Balance + // Whether OnRecvPacket should pass or fail + pass bool +} + +type OwnedCoin struct { + Address string + Denom string +} + +type Balance struct { + ID string + Address string + Denom string + Amount sdkmath.Int +} + +func AddressFromString(address string) string { + return sdk.AccAddress(crypto.AddressHash([]byte(address))).String() +} + +func AddressFromTla(addr []string) string { + if len(addr) != 3 { + panic(errors.New("failed to convert from TLA+ address: wrong number of address components")) + } + s := "" + if len(addr[0]) == 0 && len(addr[1]) == 0 { //nolint:gocritic + // simple address: id + s = addr[2] + } else if len(addr[2]) == 0 { + // escrow address: ics20-1\x00port/channel + s = fmt.Sprintf("%s\x00%s/%s", types.V1, addr[0], addr[1]) + } else { + panic(errors.New("failed to convert from TLA+ address: neither simple nor escrow address")) + } + return s +} + +func DenomFromTla(denom []string) string { + var i int + for i = 0; i+1 < len(denom); i += 2 { + if len(denom[i]) != 0 || len(denom[i+1]) != 0 { + break + } + } + return strings.Join(denom[i:], "/") +} + +func BalanceFromTla(balance TlaBalance) Balance { + return Balance{ + ID: AddressFromTla(balance.Address), + Address: AddressFromString(AddressFromTla(balance.Address)), + Denom: DenomFromTla(balance.Denom), + Amount: sdkmath.NewInt(balance.Amount), + } +} + +func BalancesFromTla(tla []TlaBalance) []Balance { + balances := make([]Balance, 0) + for _, b := range tla { + balances = append(balances, BalanceFromTla(b)) + } + return balances +} + +func FungibleTokenPacketFromTla(packet TlaFungibleTokenPacket) FungibleTokenPacket { + denom := types.ExtractDenomFromPath(DenomFromTla(packet.Data.Denom)) + return FungibleTokenPacket{ + SourceChannel: packet.SourceChannel, + SourcePort: packet.SourcePort, + DestChannel: packet.DestChannel, + DestPort: packet.DestPort, + Data: types.NewInternalTransferRepresentation( + types.Token{ + Denom: denom, + Amount: packet.Data.Amount, + }, + AddressFromString(packet.Data.Sender), + AddressFromString(packet.Data.Receiver), + "", + ), + } +} + +func OnRecvPacketTestCaseFromTla(tc TlaOnRecvPacketTestCase) OnRecvPacketTestCase { + return OnRecvPacketTestCase{ + description: "auto-generated", + bankBefore: BalancesFromTla(tc.BankBefore), + packet: FungibleTokenPacketFromTla(tc.Packet), + handler: tc.Handler, + bankAfter: BalancesFromTla(tc.BankAfter), // TODO different semantics + pass: !tc.Error, + } +} + +var addressMap = make(map[string]string) + +type Bank struct { + balances map[OwnedCoin]sdkmath.Int +} + +// Make an empty bank +func MakeBank() Bank { + return Bank{balances: make(map[OwnedCoin]sdkmath.Int)} +} + +// Subtract other bank from this bank +func (bank *Bank) Sub(other *Bank) Bank { + diff := MakeBank() + for coin, amount := range bank.balances { + otherAmount, exists := other.balances[coin] + if exists { + diff.balances[coin] = amount.Sub(otherAmount) + } else { + diff.balances[coin] = amount + } + } + for coin, amount := range other.balances { + if _, exists := bank.balances[coin]; !exists { + diff.balances[coin] = amount.Neg() + } + } + return diff +} + +// Set specific bank balance +func (bank *Bank) SetBalance(address string, denom string, amount sdkmath.Int) { + bank.balances[OwnedCoin{address, denom}] = amount +} + +// Set several balances at once +func (bank *Bank) SetBalances(balances []Balance) { + for _, balance := range balances { + bank.balances[OwnedCoin{balance.Address, balance.Denom}] = balance.Amount + addressMap[balance.Address] = balance.ID + } +} + +func NullCoin() OwnedCoin { + return OwnedCoin{ + Address: AddressFromString(""), + Denom: "", + } +} + +// Set several balances at once +func BankFromBalances(balances []Balance) Bank { + bank := MakeBank() + for _, balance := range balances { + coin := OwnedCoin{balance.Address, balance.Denom} + if coin != NullCoin() { // ignore null coin + bank.balances[coin] = balance.Amount + addressMap[balance.Address] = balance.ID + } + } + return bank +} + +// String representation of all bank balances +func (bank *Bank) String() string { + str := "" + for coin, amount := range bank.balances { + str += coin.Address + if addressMap[coin.Address] != "" { + str += "(" + addressMap[coin.Address] + ")" + } + str += " : " + coin.Denom + " = " + amount.String() + "\n" + } + return str +} + +// String representation of non-zero bank balances +func (bank *Bank) NonZeroString() string { + str := "" + for coin, amount := range bank.balances { + if !amount.IsZero() { + str += coin.Address + " : " + coin.Denom + " = " + amount.String() + "\n" + } + } + return str +} + +// Construct a bank out of the chain bank +func BankOfChain(chain *ibctesting.TestChain) Bank { + bank := MakeBank() + chain.GetSimApp().BankKeeper.IterateAllBalances(chain.GetContext(), func(address sdk.AccAddress, coin sdk.Coin) (stop bool) { + token, err := chain.GetSimApp().TransferKeeper.TokenFromCoin(chain.GetContext(), coin) + if err != nil { + panic(fmt.Errorf("Failed to construct token from coin: %w", err)) + } + bank.SetBalance(address.String(), token.Denom.Path(), coin.Amount) + return false + }) + return bank +} + +// Check that the state of the bank is the bankBefore + expectedBankChange +func (*KeeperTestSuite) CheckBankBalances(chain *ibctesting.TestChain, bankBefore *Bank, expectedBankChange *Bank) error { + bankAfter := BankOfChain(chain) + bankChange := bankAfter.Sub(bankBefore) + diff := bankChange.Sub(expectedBankChange) + nonZeroString := diff.NonZeroString() + if len(nonZeroString) != 0 { + return errorsmod.Wrap(ibcerrors.ErrInvalidCoins, "Unexpected changes in the bank: \n"+nonZeroString) + } + return nil +} + +func (suite *KeeperTestSuite) TestModelBasedRelay() { + dirname := "model_based_tests/" + files, err := os.ReadDir(dirname) + if err != nil { + panic(fmt.Errorf("Failed to read model-based test files: %w", err)) + } + for _, fileInfo := range files { + tlaTestCases := []TlaOnRecvPacketTestCase{} + if !strings.HasSuffix(fileInfo.Name(), ".json") { + continue + } + jsonBlob, err := os.ReadFile(dirname + fileInfo.Name()) + if err != nil { + panic(fmt.Errorf("Failed to read JSON test fixture: %w", err)) + } + err = json.Unmarshal(jsonBlob, &tlaTestCases) + if err != nil { + panic(fmt.Errorf("Failed to parse JSON test fixture: %w", err)) + } + + suite.SetupTest() + pathAtoB := ibctesting.NewTransferPath(suite.chainA, suite.chainB).DisableUniqueChannelIDs() + pathBtoC := ibctesting.NewTransferPath(suite.chainB, suite.chainC).DisableUniqueChannelIDs() + pathAtoB.Setup() + pathBtoC.Setup() + + for i, tlaTc := range tlaTestCases { + tc := OnRecvPacketTestCaseFromTla(tlaTc) + registerDenomFn := func() { + if !suite.chainB.GetSimApp().TransferKeeper.HasDenom(suite.chainB.GetContext(), tc.packet.Data.Token.Denom.Hash()) { + suite.chainB.GetSimApp().TransferKeeper.SetDenom(suite.chainB.GetContext(), tc.packet.Data.Token.Denom) + } + } + + description := fileInfo.Name() + " # " + strconv.Itoa(i+1) + suite.Run(fmt.Sprintf("Case %s", description), func() { + seq := uint64(1) + packet := channeltypes.NewPacket([]byte("mockdata"), seq, tc.packet.SourcePort, tc.packet.SourceChannel, tc.packet.DestPort, tc.packet.DestChannel, clienttypes.NewHeight(1, 100), 0) + bankBefore := BankFromBalances(tc.bankBefore) + realBankBefore := BankOfChain(suite.chainB) + // First validate the packet itself (mimics what happens when the packet is being sent and/or received) + err := packet.ValidateBasic() + if err != nil { + suite.Require().False(tc.pass, err.Error()) + return + } + switch tc.handler { + case "SendTransfer": + var sender sdk.AccAddress + sender, err = sdk.AccAddressFromBech32(tc.packet.Data.Sender) + if err != nil { + panic(errors.New("MBT failed to convert sender address")) + } + registerDenomFn() + denom := tc.packet.Data.Token.Denom.IBCDenom() + err = sdk.ValidateDenom(denom) + if err == nil { + amount, ok := sdkmath.NewIntFromString(tc.packet.Data.Token.Amount) + if !ok { + panic(errors.New("MBT failed to parse amount from string")) + } + + msg := types.NewMsgTransfer( + tc.packet.SourcePort, + tc.packet.SourceChannel, + sdk.NewCoin(denom, amount), + sender.String(), + tc.packet.Data.Receiver, + suite.chainA.GetTimeoutHeight(), 0, // only use timeout height + "", + ) + + _, err = suite.chainB.GetSimApp().TransferKeeper.Transfer(suite.chainB.GetContext(), msg) + + } + case "OnRecvPacket": + err = suite.chainB.GetSimApp().TransferKeeper.OnRecvPacket( + suite.chainB.GetContext(), + tc.packet.Data, + packet.SourcePort, + packet.SourceChannel, + packet.DestinationPort, + packet.DestinationChannel, + ) + + case "OnTimeoutPacket": + registerDenomFn() + err = suite.chainB.GetSimApp().TransferKeeper.OnTimeoutPacket(suite.chainB.GetContext(), packet.SourcePort, packet.SourceChannel, tc.packet.Data) + + case "OnRecvAcknowledgementResult": + err = suite.chainB.GetSimApp().TransferKeeper.OnAcknowledgementPacket( + suite.chainB.GetContext(), packet.SourcePort, packet.SourceChannel, tc.packet.Data, + channeltypes.NewResultAcknowledgement(nil)) + case "OnRecvAcknowledgementError": + registerDenomFn() + err = suite.chainB.GetSimApp().TransferKeeper.OnAcknowledgementPacket( + suite.chainB.GetContext(), packet.SourcePort, packet.SourceChannel, tc.packet.Data, + channeltypes.NewErrorAcknowledgement(errors.New("MBT Error Acknowledgement"))) + default: + err = fmt.Errorf("Unknown handler: %s", tc.handler) + } + if err != nil { + suite.Require().False(tc.pass, err.Error()) + return + } + bankAfter := BankFromBalances(tc.bankAfter) + expectedBankChange := bankAfter.Sub(&bankBefore) + if err := suite.CheckBankBalances(suite.chainB, &realBankBefore, &expectedBankChange); err != nil { + suite.Require().False(tc.pass, err.Error()) + return + } + suite.Require().True(tc.pass) + }) + } + } +} diff --git a/modules/apps/transfer/keeper/migrations.go b/modules/apps/transfer/keeper/migrations.go new file mode 100644 index 0000000..58da763 --- /dev/null +++ b/modules/apps/transfer/keeper/migrations.go @@ -0,0 +1,167 @@ +package keeper + +import ( + "fmt" + "strings" + + "cosmossdk.io/store/prefix" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + internaltypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/internal/types" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +) + +// Migrator is a struct for handling in-place store migrations. +type Migrator struct { + keeper Keeper +} + +// NewMigrator returns a new Migrator. +func NewMigrator(keeper Keeper) Migrator { + return Migrator{ + keeper: keeper, + } +} + +// MigrateParams migrates the transfer module's parameters from the x/params to self store. +func (m Migrator) MigrateParams(ctx sdk.Context) error { + var params types.Params + m.keeper.legacySubspace.GetParamSet(ctx, ¶ms) + + m.keeper.SetParams(ctx, params) + m.keeper.Logger(ctx).Info("successfully migrated transfer app self-manage params") + return nil +} + +// MigrateDenomMetadata sets token metadata for all the IBC denom traces +func (m Migrator) MigrateDenomMetadata(ctx sdk.Context) error { + m.keeper.iterateDenomTraces(ctx, + func(dt internaltypes.DenomTrace) (stop bool) { + // check if the metadata for the given denom trace does not already exist + if !m.keeper.BankKeeper.HasDenomMetaData(ctx, dt.IBCDenom()) { + m.keeper.setDenomMetadataWithDenomTrace(ctx, dt) + } + return false + }) + + m.keeper.Logger(ctx).Info("successfully added metadata to IBC voucher denominations") + return nil +} + +// MigrateTotalEscrowForDenom migrates the total amount of source chain tokens in escrow. +func (m Migrator) MigrateTotalEscrowForDenom(ctx sdk.Context) error { + var totalEscrowed sdk.Coins + portID := m.keeper.GetPort(ctx) + + transferChannels := m.keeper.channelKeeper.GetAllChannelsWithPortPrefix(ctx, portID) + for _, channel := range transferChannels { + escrowAddress := types.GetEscrowAddress(portID, channel.ChannelId) + escrowBalances := m.keeper.BankKeeper.GetAllBalances(ctx, escrowAddress) + + totalEscrowed = totalEscrowed.Add(escrowBalances...) + } + + for _, totalEscrow := range totalEscrowed { + m.keeper.SetTotalEscrowForDenom(ctx, totalEscrow) + } + + m.keeper.Logger(ctx).Info("successfully set total escrow", "number of denominations", totalEscrowed.Len()) + return nil +} + +// MigrateDenomTraceToDenom migrates storage from using DenomTrace to Denom. +func (m Migrator) MigrateDenomTraceToDenom(ctx sdk.Context) error { + var ( + denoms []types.Denom + denomTraces []internaltypes.DenomTrace + ) + m.keeper.iterateDenomTraces(ctx, + func(dt internaltypes.DenomTrace) (stop bool) { + // convert denomTrace to denom + denom := types.ExtractDenomFromPath(dt.GetFullDenomPath()) + err := denom.Validate() + if err != nil { + panic(err) + } + + // defense in depth + if dt.IBCDenom() != denom.IBCDenom() { + // This migration must not change the SDK coin denom. + // A panic should occur to prevent the chain from using corrupted state. + panic(fmt.Errorf("migration will result in corrupted state. expected: %s, got: %s", denom.IBCDenom(), dt.IBCDenom())) + } + + denoms = append(denoms, denom) + denomTraces = append(denomTraces, dt) + + return false + }) + + if len(denoms) != len(denomTraces) { + return fmt.Errorf("length of denoms does not match length of denom traces, %d != %d", len(denoms), len(denomTraces)) + } + + for i := range denoms { + m.keeper.SetDenom(ctx, denoms[i]) + m.keeper.deleteDenomTrace(ctx, denomTraces[i]) + } + + return nil +} + +// setDenomTrace sets a new {trace hash -> denom trace} pair to the store. +func (k Keeper) setDenomTrace(ctx sdk.Context, denomTrace internaltypes.DenomTrace) { + store := prefix.NewStore(runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)), types.DenomTraceKey) + bz := k.cdc.MustMarshal(&denomTrace) + + store.Set(denomTrace.Hash(), bz) +} + +// deleteDenomTrace deletes the denom trace +func (k Keeper) deleteDenomTrace(ctx sdk.Context, denomTrace internaltypes.DenomTrace) { + store := prefix.NewStore(runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)), types.DenomTraceKey) + store.Delete(denomTrace.Hash()) +} + +// iterateDenomTraces iterates over the denomination traces in the store +// and performs a callback function. +func (k Keeper) iterateDenomTraces(ctx sdk.Context, cb func(denomTrace internaltypes.DenomTrace) bool) { + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + iterator := storetypes.KVStorePrefixIterator(store, types.DenomTraceKey) + + defer sdk.LogDeferred(k.Logger(ctx), func() error { return iterator.Close() }) + for ; iterator.Valid(); iterator.Next() { + var denomTrace internaltypes.DenomTrace + k.cdc.MustUnmarshal(iterator.Value(), &denomTrace) + + if cb(denomTrace) { + break + } + } +} + +// setDenomMetadataWithDenomTrace sets an IBC token's denomination metadata +func (k Keeper) setDenomMetadataWithDenomTrace(ctx sdk.Context, denomTrace internaltypes.DenomTrace) { + metadata := banktypes.Metadata{ + Description: fmt.Sprintf("IBC token from %s", denomTrace.GetFullDenomPath()), + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: denomTrace.BaseDenom, + Exponent: 0, + }, + }, + // Setting base as IBC hash denom since bank keepers's SetDenomMetadata uses + // Base as key path and the IBC hash is what gives this token uniqueness + // on the executing chain + Base: denomTrace.IBCDenom(), + Display: denomTrace.GetFullDenomPath(), + Name: fmt.Sprintf("%s IBC token", denomTrace.GetFullDenomPath()), + Symbol: strings.ToUpper(denomTrace.BaseDenom), + } + + k.BankKeeper.SetDenomMetaData(ctx, metadata) +} diff --git a/modules/apps/transfer/keeper/migrations_test.go b/modules/apps/transfer/keeper/migrations_test.go new file mode 100644 index 0000000..dfedbf9 --- /dev/null +++ b/modules/apps/transfer/keeper/migrations_test.go @@ -0,0 +1,482 @@ +package keeper_test + +import ( + "fmt" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktestutil "github.com/cosmos/cosmos-sdk/x/bank/testutil" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + internaltransfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/internal/types" + transferkeeper "github.com/cosmos/ibc-go/v10/modules/apps/transfer/keeper" + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *KeeperTestSuite) TestMigratorMigrateParams() { + testCases := []struct { + msg string + malleate func() + expectedParams transfertypes.Params + }{ + { + "success: default params", + func() { + params := transfertypes.DefaultParams() + subspace := suite.chainA.GetSimApp().GetSubspace(transfertypes.ModuleName) + subspace.SetParamSet(suite.chainA.GetContext(), ¶ms) // set params + }, + transfertypes.DefaultParams(), + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() // explicitly set params + + migrator := transferkeeper.NewMigrator(suite.chainA.GetSimApp().TransferKeeper) + err := migrator.MigrateParams(suite.chainA.GetContext()) + suite.Require().NoError(err) + + params := suite.chainA.GetSimApp().TransferKeeper.GetParams(suite.chainA.GetContext()) + suite.Require().Equal(tc.expectedParams, params) + }) + } +} + +func (suite *KeeperTestSuite) TestMigratorMigrateDenomTraceToDenom() { + testCases := []struct { + msg string + malleate func() + expectedDenoms transfertypes.Denoms + }{ + { + "success: no trace", + func() { + suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace( + suite.chainA.GetContext(), + internaltransfertypes.DenomTrace{ + BaseDenom: "uatom", + }) + }, + transfertypes.Denoms{ + transfertypes.NewDenom("uatom"), + }, + }, + { + "success: single trace", + func() { + suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace( + suite.chainA.GetContext(), + internaltransfertypes.DenomTrace{ + BaseDenom: "uatom", Path: "transfer/channel-49", + }) + }, + transfertypes.Denoms{ + transfertypes.NewDenom("uatom", transfertypes.NewHop("transfer", "channel-49")), + }, + }, + { + "success: multiple trace", + func() { + suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace( + suite.chainA.GetContext(), + internaltransfertypes.DenomTrace{ + BaseDenom: "uatom", Path: "transfer/channel-49/transfer/channel-32/transfer/channel-2", + }) + }, + transfertypes.Denoms{ + transfertypes.NewDenom("uatom", transfertypes.NewHop("transfer", "channel-49"), transfertypes.NewHop("transfer", "channel-32"), transfertypes.NewHop("transfer", "channel-2")), + }, + }, + { + "success: many denoms", + func() { + suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace( + suite.chainA.GetContext(), + internaltransfertypes.DenomTrace{ + BaseDenom: "uatom", Path: "transfer/channel-49", + }) + suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace( + suite.chainA.GetContext(), + internaltransfertypes.DenomTrace{ + BaseDenom: "pineapple", Path: "transfer/channel-0", + }) + suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace( + suite.chainA.GetContext(), + internaltransfertypes.DenomTrace{ + BaseDenom: "apple", Path: "transfer/channel-0", + }) + suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace( + suite.chainA.GetContext(), + internaltransfertypes.DenomTrace{ + BaseDenom: "cucumber", Path: "transfer/channel-102/transfer/channel-0", + }) + }, + transfertypes.Denoms{ + transfertypes.NewDenom("apple", transfertypes.NewHop("transfer", "channel-0")), + transfertypes.NewDenom("cucumber", transfertypes.NewHop("transfer", "channel-102"), transfertypes.NewHop("transfer", "channel-0")), + transfertypes.NewDenom("pineapple", transfertypes.NewHop("transfer", "channel-0")), + transfertypes.NewDenom("uatom", transfertypes.NewHop("transfer", "channel-49")), + }, + }, + + { + "success: two slashes in base denom", + func() { + suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace( + suite.chainA.GetContext(), + internaltransfertypes.DenomTrace{ + BaseDenom: "gamm/pool/1", Path: "transfer/channel-0", + }) + }, + transfertypes.Denoms{ + transfertypes.NewDenom("gamm/pool/1", transfertypes.NewHop("transfer", "channel-0")), + }, + }, + { + "success: one slash in base denom", + func() { + suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace( + suite.chainA.GetContext(), + internaltransfertypes.DenomTrace{ + BaseDenom: "erc/0x85bcBCd7e79Ec36f4fBBDc54F90C643d921151AA", Path: "transfer/channel-149", + }) + }, + transfertypes.Denoms{ + transfertypes.NewDenom("erc/0x85bcBCd7e79Ec36f4fBBDc54F90C643d921151AA", transfertypes.NewHop("transfer", "channel-149")), + }, + }, + { + "success: non-standard port", + func() { + suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace( + suite.chainA.GetContext(), + internaltransfertypes.DenomTrace{ + BaseDenom: "uatom", Path: "transfer/channel-0/customport/channel-7", + }) + }, + transfertypes.Denoms{ + transfertypes.NewDenom("uatom", transfertypes.NewHop("transfer", "channel-0"), transfertypes.NewHop("customport", "channel-7")), + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.msg, func() { + suite.SetupTest() // reset + + tc.malleate() + + migrator := transferkeeper.NewMigrator(suite.chainA.GetSimApp().TransferKeeper) + err := migrator.MigrateDenomTraceToDenom(suite.chainA.GetContext()) + suite.Require().NoError(err) + + denoms := suite.chainA.GetSimApp().TransferKeeper.GetAllDenoms(suite.chainA.GetContext()) + suite.Require().Equal(tc.expectedDenoms, denoms) + + // assert no leftover denom traces + suite.chainA.GetSimApp().TransferKeeper.IterateDenomTraces(suite.chainA.GetContext(), + func(dt internaltransfertypes.DenomTrace) (stop bool) { + suite.FailNow("DenomTrace key still exists", dt) + return false + }, + ) + }) + } +} + +func (suite *KeeperTestSuite) TestMigratorMigrateDenomTraceToDenomCorruptionDetection() { + testCases := []struct { + name string + denomTrace internaltransfertypes.DenomTrace + }{ + { + "corrupted denom trace, denom.IBCHash() does not match", + internaltransfertypes.DenomTrace{ + BaseDenom: "customport/channel-0/uatom", + Path: "", + }, + }, + { + "invalid denom trace, base denom is empty", + internaltransfertypes.DenomTrace{ + BaseDenom: "", + Path: "transfer/channel-0", + }, + }, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace(suite.chainA.GetContext(), tc.denomTrace) + + migrator := transferkeeper.NewMigrator(suite.chainA.GetSimApp().TransferKeeper) + suite.Panics(func() { + migrator.MigrateDenomTraceToDenom(suite.chainA.GetContext()) //nolint:errcheck // we shouldn't check the error here because we want to ensure that a panic occurs. + }) + }) + } +} + +func (suite *KeeperTestSuite) TestMigrateTotalEscrowForDenom() { + var ( + path *ibctesting.Path + denom string + ) + + testCases := []struct { + msg string + malleate func() + expectedEscrowAmt sdkmath.Int + }{ + { + "success: one native denom escrowed in one channel", + func() { + denom = sdk.DefaultBondDenom + escrowAddress := transfertypes.GetEscrowAddress(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + coin := ibctesting.TestCoin + + // funds the escrow account to have balance + suite.Require().NoError(banktestutil.FundAccount(suite.chainA.GetContext(), suite.chainA.GetSimApp().BankKeeper, escrowAddress, sdk.NewCoins(coin))) + }, + sdkmath.NewInt(100), + }, + { + "success: one native denom escrowed in two channels", + func() { + denom = sdk.DefaultBondDenom + extraPath := ibctesting.NewTransferPath(suite.chainA, suite.chainB) + extraPath.Setup() + + escrowAddress1 := transfertypes.GetEscrowAddress(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + escrowAddress2 := transfertypes.GetEscrowAddress(extraPath.EndpointA.ChannelConfig.PortID, extraPath.EndpointA.ChannelID) + coin1 := ibctesting.TestCoin + coin2 := ibctesting.TestCoin + + // funds the escrow accounts to have balance + suite.Require().NoError(banktestutil.FundAccount(suite.chainA.GetContext(), suite.chainA.GetSimApp().BankKeeper, escrowAddress1, sdk.NewCoins(coin1))) + suite.Require().NoError(banktestutil.FundAccount(suite.chainA.GetContext(), suite.chainA.GetSimApp().BankKeeper, escrowAddress2, sdk.NewCoins(coin2))) + }, + sdkmath.NewInt(200), + }, + { + "success: valid ibc denom escrowed in one channel", + func() { + escrowAddress := transfertypes.GetEscrowAddress(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + voucherDenom := transfertypes.NewDenom(sdk.DefaultBondDenom, transfertypes.NewHop(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)) + coin := sdk.NewCoin(voucherDenom.IBCDenom(), sdkmath.NewInt(100)) + denom = voucherDenom.IBCDenom() + + // funds the escrow account to have balance + suite.Require().NoError(banktestutil.FundAccount(suite.chainA.GetContext(), suite.chainA.GetSimApp().BankKeeper, escrowAddress, sdk.NewCoins(coin))) + }, + sdkmath.NewInt(100), + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + path = ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path.Setup() + + tc.malleate() // explicitly fund escrow account + + migrator := transferkeeper.NewMigrator(suite.chainA.GetSimApp().TransferKeeper) + suite.Require().NoError(migrator.MigrateTotalEscrowForDenom(suite.chainA.GetContext())) + + // check that the migration set the expected amount for both native and IBC tokens + amount := suite.chainA.GetSimApp().TransferKeeper.GetTotalEscrowForDenom(suite.chainA.GetContext(), denom) + suite.Require().Equal(tc.expectedEscrowAmt, amount.Amount) + }) + } +} + +func (suite *KeeperTestSuite) TestMigratorMigrateMetadata() { + var ( + denomTraces []internaltransfertypes.DenomTrace + expectedMetadata []banktypes.Metadata + ) + + testCases := []struct { + msg string + malleate func() + }{ + { + "success with one denom trace with one hop", + func() { + denomTraces = []internaltransfertypes.DenomTrace{ + { + BaseDenom: "foo", + Path: "transfer/channel-0", + }, + } + + expectedMetadata = []banktypes.Metadata{ + { + Description: "IBC token from transfer/channel-0/foo", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "foo", + Exponent: 0, + }, + }, + Base: denomTraces[0].IBCDenom(), // ibc/EB7094899ACFB7A6F2A67DB084DEE2E9A83DEFAA5DEF92D9A9814FFD9FF673FA + Display: "transfer/channel-0/foo", + Name: "transfer/channel-0/foo IBC token", + Symbol: "FOO", + }, + } + }, + }, + { + "success with one denom trace with two hops", + func() { + denomTraces = []internaltransfertypes.DenomTrace{ + { + BaseDenom: "ubar", + Path: "transfer/channel-1/transfer/channel-2", + }, + } + + expectedMetadata = []banktypes.Metadata{ + { + Description: "IBC token from transfer/channel-1/transfer/channel-2/ubar", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "ubar", + Exponent: 0, + }, + }, + Base: denomTraces[0].IBCDenom(), // ibc/8243B3EAA19BAB1DB3B0020B81C0C5A953E7B22C042CEE44E639A11A238BA57C + Display: "transfer/channel-1/transfer/channel-2/ubar", + Name: "transfer/channel-1/transfer/channel-2/ubar IBC token", + Symbol: "UBAR", + }, + } + }, + }, + { + "success with two denom traces with one hop", + func() { + denomTraces = []internaltransfertypes.DenomTrace{ + { + BaseDenom: "foo", + Path: "transfer/channel-0", + }, + { + BaseDenom: "bar", + Path: "transfer/channel-0", + }, + } + + expectedMetadata = []banktypes.Metadata{ + { + Description: "IBC token from transfer/channel-0/foo", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "foo", + Exponent: 0, + }, + }, + Base: denomTraces[0].IBCDenom(), // ibc/EB7094899ACFB7A6F2A67DB084DEE2E9A83DEFAA5DEF92D9A9814FFD9FF673FA + Display: "transfer/channel-0/foo", + Name: "transfer/channel-0/foo IBC token", + Symbol: "FOO", + }, + { + Description: "IBC token from transfer/channel-0/bar", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "bar", + Exponent: 0, + }, + }, + Base: denomTraces[1].IBCDenom(), // ibc/E1530E21F1848B6C29C9E89256D43E294976897611A61741CACBA55BE21736F5 + Display: "transfer/channel-0/bar", + Name: "transfer/channel-0/bar IBC token", + Symbol: "BAR", + }, + } + }, + }, + { + "success with two denom traces, metadata for one of them already exists", + func() { + denomTraces = []internaltransfertypes.DenomTrace{ + { + BaseDenom: "foo", + Path: "transfer/channel-0", + }, + { + BaseDenom: "bar", + Path: "transfer/channel-0", + }, + } + + expectedMetadata = []banktypes.Metadata{ + { + Description: "IBC token from transfer/channel-0/foo", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "foo", + Exponent: 0, + }, + }, + Base: denomTraces[0].IBCDenom(), // ibc/EB7094899ACFB7A6F2A67DB084DEE2E9A83DEFAA5DEF92D9A9814FFD9FF673FA + Display: "transfer/channel-0/foo", + Name: "transfer/channel-0/foo IBC token", + Symbol: "FOO", + }, + { + Description: "IBC token from transfer/channel-0/bar", + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: "bar", + Exponent: 0, + }, + }, + Base: denomTraces[1].IBCDenom(), // ibc/E1530E21F1848B6C29C9E89256D43E294976897611A61741CACBA55BE21736F5 + Display: "transfer/channel-0/bar", + Name: "transfer/channel-0/bar IBC token", + Symbol: "BAR", + }, + } + + // set metadata for one of the tokens, so that it exists already in state before doing the migration + suite.chainA.GetSimApp().BankKeeper.SetDenomMetaData(suite.chainA.GetContext(), expectedMetadata[1]) + }, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + ctx := suite.chainA.GetContext() + + tc.malleate() + + for _, denomTrace := range denomTraces { + suite.chainA.GetSimApp().TransferKeeper.SetDenomTrace(ctx, denomTrace) + } + + // run migration + migrator := transferkeeper.NewMigrator(suite.chainA.GetSimApp().TransferKeeper) + err := migrator.MigrateDenomMetadata(ctx) + suite.Require().NoError(err) + + for _, expMetadata := range expectedMetadata { + denomMetadata, found := suite.chainA.GetSimApp().BankKeeper.GetDenomMetaData(ctx, expMetadata.Base) + suite.Require().True(found) + suite.Require().Equal(expMetadata, denomMetadata) + } + }) + } +} diff --git a/modules/apps/transfer/keeper/model_based_tests/Test5Packets.json b/modules/apps/transfer/keeper/model_based_tests/Test5Packets.json new file mode 100644 index 0000000..4459f34 --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/Test5Packets.json @@ -0,0 +1,492 @@ +[ + { + "packet": { + "sourceChannel": "channel-0", + "sourcePort": "transfer", + "destChannel": "channel-1", + "destPort": "transfer", + "data": { + "sender": "a3", + "receiver": "a3", + "amount": "2", + "denom": [ + "", + "", + "", + "", + "btc" + ] + } + }, + "handler": "OnRecvPacket", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a3" + ], + "denom": [ + "", + "", + "transfer", + "channel-1", + "btc" + ], + "amount": 2 + } + ], + "error": false + }, + { + "packet": { + "sourceChannel": "ethereum-hub", + "sourcePort": "channel-0", + "destChannel": "channel-1", + "destPort": "transfer", + "data": { + "sender": "a1", + "receiver": "a3", + "amount": "1", + "denom": [ + "cosmos-hub", + "", + "", + "", + "btc" + ] + } + }, + "handler": "SendTransfer", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a3" + ], + "denom": [ + "", + "", + "transfer", + "channel-1", + "btc" + ], + "amount": 2 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a3" + ], + "denom": [ + "", + "", + "transfer", + "channel-1", + "btc" + ], + "amount": 2 + } + ], + "error": true + }, + { + "packet": { + "sourceChannel": "channel-0", + "sourcePort": "transfer", + "destChannel": "channel-1", + "destPort": "transfer", + "data": { + "sender": "a2", + "receiver": "a2", + "amount": "4", + "denom": [ + "", + "", + "ethereum-hub", + "cosmos-hub", + "atom" + ] + } + }, + "handler": "OnRecvPacket", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a3" + ], + "denom": [ + "", + "", + "transfer", + "channel-1", + "btc" + ], + "amount": 2 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a2" + ], + "denom": [ + "transfer", + "channel-1", + "ethereum-hub", + "cosmos-hub", + "atom" + ], + "amount": 4 + }, + { + "address": [ + "", + "", + "a3" + ], + "denom": [ + "", + "", + "transfer", + "channel-1", + "btc" + ], + "amount": 2 + } + ], + "error": false + }, + { + "packet": { + "sourceChannel": "channel-0", + "sourcePort": "transfer", + "destChannel": "channel-1", + "destPort": "transfer", + "data": { + "sender": "", + "receiver": "a2", + "amount": "4", + "denom": [ + "", + "", + "ethereum-hub", + "cosmos-hub", + "atom" + ] + } + }, + "handler": "OnRecvPacket", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a2" + ], + "denom": [ + "transfer", + "channel-1", + "ethereum-hub", + "cosmos-hub", + "atom" + ], + "amount": 4 + }, + { + "address": [ + "", + "", + "a3" + ], + "denom": [ + "", + "", + "transfer", + "channel-1", + "btc" + ], + "amount": 2 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a2" + ], + "denom": [ + "transfer", + "channel-1", + "ethereum-hub", + "cosmos-hub", + "atom" + ], + "amount": 8 + }, + { + "address": [ + "", + "", + "a3" + ], + "denom": [ + "", + "", + "transfer", + "channel-1", + "btc" + ], + "amount": 2 + } + ], + "error": false + }, + { + "packet": { + "sourceChannel": "cosmos-hub", + "sourcePort": "bitcoin-hub", + "destChannel": "channel-0", + "destPort": "channel-1", + "data": { + "sender": "a1", + "receiver": "", + "amount": "1", + "denom": [ + "transfer", + "channel-0", + "transfer", + "channel-0", + "atom" + ] + } + }, + "handler": "SendTransfer", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a2" + ], + "denom": [ + "transfer", + "channel-1", + "ethereum-hub", + "cosmos-hub", + "atom" + ], + "amount": 8 + }, + { + "address": [ + "", + "", + "a3" + ], + "denom": [ + "", + "", + "transfer", + "channel-1", + "btc" + ], + "amount": 2 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a2" + ], + "denom": [ + "transfer", + "channel-1", + "ethereum-hub", + "cosmos-hub", + "atom" + ], + "amount": 8 + }, + { + "address": [ + "", + "", + "a3" + ], + "denom": [ + "", + "", + "transfer", + "channel-1", + "btc" + ], + "amount": 2 + } + ], + "error": true + } +] diff --git a/modules/apps/transfer/keeper/model_based_tests/Test5Packets.tla b/modules/apps/transfer/keeper/model_based_tests/Test5Packets.tla new file mode 100644 index 0000000..596c2a9 --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/Test5Packets.tla @@ -0,0 +1,1056 @@ +------------------------- MODULE counterexample ------------------------- + +EXTENDS relay_tests + +(* Initial state *) + +State1 == +TRUE +(* Transition 0 to State2 *) + +State2 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 +/\ count = 0 +/\ error = FALSE +/\ handler = "" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 2, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a3", + sender |-> "a3"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 2, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a3", + sender |-> "a3"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"] + +(* Transition 3 to State3 *) + +State3 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2 +/\ count = 1 +/\ error = FALSE +/\ handler = "OnRecvPacket" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 2, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a3", + sender |-> "a3"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 2, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a3", + sender |-> "a3"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> "cosmos-hub"]], + receiver |-> "a3", + sender |-> "a1"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "ethereum-hub", + sourcePort |-> "channel-0"] + +(* Transition 0 to State4 *) + +State4 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2 +/\ count = 2 +/\ error = TRUE +/\ handler = "SendTransfer" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 2, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a3", + sender |-> "a3"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 2, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a3", + sender |-> "a3"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 2 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + error |-> TRUE, + handler |-> "SendTransfer", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> "cosmos-hub"]], + receiver |-> "a3", + sender |-> "a1"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "ethereum-hub", + sourcePort |-> "channel-0"]] +/\ p = [data |-> + [amount |-> 4, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a2"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"] + +(* Transition 5 to State5 *) + +State5 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 4 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2 +/\ count = 3 +/\ error = FALSE +/\ handler = "OnRecvPacket" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 2, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a3", + sender |-> "a3"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 2, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a3", + sender |-> "a3"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 2 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + error |-> TRUE, + handler |-> "SendTransfer", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> "cosmos-hub"]], + receiver |-> "a3", + sender |-> "a1"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "ethereum-hub", + sourcePort |-> "channel-0"]] + @@ 3 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 4 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 4, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> + [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a2"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 4, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> ""], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"] + +(* Transition 5 to State6 *) + +State6 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 8 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2 +/\ count = 4 +/\ error = FALSE +/\ handler = "OnRecvPacket" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 2, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a3", + sender |-> "a3"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 2, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a3", + sender |-> "a3"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 2 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + error |-> TRUE, + handler |-> "SendTransfer", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> "cosmos-hub"]], + receiver |-> "a3", + sender |-> "a1"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "ethereum-hub", + sourcePort |-> "channel-0"]] + @@ 3 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 4 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 4, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> + [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a2"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 4 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 8 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 4 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 4, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> + [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> ""], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "channel-0", port |-> "transfer"]], + receiver |-> "", + sender |-> "a1"], + destChannel |-> "channel-0", + destPort |-> "channel-1", + sourceChannel |-> "cosmos-hub", + sourcePort |-> "bitcoin-hub"] + +(* Transition 0 to State7 *) + +State7 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 8 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2 +/\ count = 5 +/\ error = TRUE +/\ handler = "SendTransfer" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 2, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a3", + sender |-> "a3"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 2, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a3", + sender |-> "a3"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 2 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + error |-> TRUE, + handler |-> "SendTransfer", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> "cosmos-hub"]], + receiver |-> "a3", + sender |-> "a1"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "ethereum-hub", + sourcePort |-> "channel-0"]] + @@ 3 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 4 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 4, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> + [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a2"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 4 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 8 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 4 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 4, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> + [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> ""], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 5 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 8 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 8 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "channel-1", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + error |-> TRUE, + handler |-> "SendTransfer", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "channel-0", port |-> "transfer"]], + receiver |-> "", + sender |-> "a1"], + destChannel |-> "channel-0", + destPort |-> "channel-1", + sourceChannel |-> "cosmos-hub", + sourcePort |-> "bitcoin-hub"]] +/\ p = [data |-> + [amount |-> 0, + denomTrace |-> + [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "", + sender |-> ""], + destChannel |-> "", + destPort |-> "", + sourceChannel |-> "", + sourcePort |-> ""] + +(* The following formula holds true in the last state and violates the invariant *) + +InvariantViolation == + count >= 5 + /\ BMC!Skolem((\E s1$2 \in DOMAIN history: + BMC!Skolem((\E s2$2 \in DOMAIN history: + ~(history[s1$2]["handler"] = history[s2$2]["handler"]))))) + +================================================================================ +\* Created by Apalache on Thu Dec 10 11:52:41 CET 2020 +\* https://github.com/informalsystems/apalache diff --git a/modules/apps/transfer/keeper/model_based_tests/Test5PacketsAllDifferentPass.json b/modules/apps/transfer/keeper/model_based_tests/Test5PacketsAllDifferentPass.json new file mode 100644 index 0000000..39242c8 --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/Test5PacketsAllDifferentPass.json @@ -0,0 +1,612 @@ +[ + { + "packet": { + "sourceChannel": "channel-0", + "sourcePort": "transfer", + "destChannel": "channel-0", + "destPort": "transfer", + "data": { + "sender": "a3", + "receiver": "a2", + "amount": "3", + "denom": [ + "", + "", + "transfer", + "channel-0", + "eth" + ] + } + }, + "handler": "OnTimeoutPacket", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a3" + ], + "denom": [ + "", + "", + "transfer", + "channel-0", + "eth" + ], + "amount": 3 + } + ], + "error": false + }, + { + "packet": { + "sourceChannel": "channel-1", + "sourcePort": "transfer", + "destChannel": "channel-0", + "destPort": "transfer", + "data": { + "sender": "a2", + "receiver": "a1", + "amount": "3", + "denom": [ + "transfer", + "channel-1", + "cosmos-hub", + "cosmos-hub", + "btc" + ] + } + }, + "handler": "OnRecvAcknowledgementError", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a3" + ], + "denom": [ + "", + "", + "transfer", + "channel-0", + "eth" + ], + "amount": 3 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a2" + ], + "denom": [ + "transfer", + "channel-1", + "cosmos-hub", + "cosmos-hub", + "btc" + ], + "amount": 3 + }, + { + "address": [ + "", + "", + "a3" + ], + "denom": [ + "", + "", + "transfer", + "channel-0", + "eth" + ], + "amount": 3 + } + ], + "error": false + }, + { + "packet": { + "sourceChannel": "channel-0", + "sourcePort": "transfer", + "destChannel": "channel-1", + "destPort": "transfer", + "data": { + "sender": "a1", + "receiver": "a2", + "amount": "3", + "denom": [ + "", + "", + "cosmos-hub", + "cosmos-hub", + "atom" + ] + } + }, + "handler": "OnRecvPacket", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a2" + ], + "denom": [ + "transfer", + "channel-1", + "cosmos-hub", + "cosmos-hub", + "btc" + ], + "amount": 3 + }, + { + "address": [ + "", + "", + "a3" + ], + "denom": [ + "", + "", + "transfer", + "channel-0", + "eth" + ], + "amount": 3 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a2" + ], + "denom": [ + "transfer", + "channel-1", + "cosmos-hub", + "cosmos-hub", + "atom" + ], + "amount": 3 + }, + { + "address": [ + "", + "", + "a2" + ], + "denom": [ + "transfer", + "channel-1", + "cosmos-hub", + "cosmos-hub", + "btc" + ], + "amount": 3 + }, + { + "address": [ + "", + "", + "a3" + ], + "denom": [ + "", + "", + "transfer", + "channel-0", + "eth" + ], + "amount": 3 + } + ], + "error": false + }, + { + "packet": { + "sourceChannel": "cosmos-hub", + "sourcePort": "bitcoin-hub", + "destChannel": "transfer", + "destPort": "cosmos-hub", + "data": { + "sender": "a1", + "receiver": "", + "amount": "2", + "denom": [ + "", + "channel-0", + "channel-1", + "channel-1", + "" + ] + } + }, + "handler": "OnRecvAcknowledgementResult", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a2" + ], + "denom": [ + "transfer", + "channel-1", + "cosmos-hub", + "cosmos-hub", + "atom" + ], + "amount": 3 + }, + { + "address": [ + "", + "", + "a2" + ], + "denom": [ + "transfer", + "channel-1", + "cosmos-hub", + "cosmos-hub", + "btc" + ], + "amount": 3 + }, + { + "address": [ + "", + "", + "a3" + ], + "denom": [ + "", + "", + "transfer", + "channel-0", + "eth" + ], + "amount": 3 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a2" + ], + "denom": [ + "transfer", + "channel-1", + "cosmos-hub", + "cosmos-hub", + "atom" + ], + "amount": 3 + }, + { + "address": [ + "", + "", + "a2" + ], + "denom": [ + "transfer", + "channel-1", + "cosmos-hub", + "cosmos-hub", + "btc" + ], + "amount": 3 + }, + { + "address": [ + "", + "", + "a3" + ], + "denom": [ + "", + "", + "transfer", + "channel-0", + "eth" + ], + "amount": 3 + } + ], + "error": false + }, + { + "packet": { + "sourceChannel": "channel-1", + "sourcePort": "transfer", + "destChannel": "channel-0", + "destPort": "transfer", + "data": { + "sender": "a3", + "receiver": "a3", + "amount": "1", + "denom": [ + "", + "", + "transfer", + "channel-0", + "eth" + ] + } + }, + "handler": "SendTransfer", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a2" + ], + "denom": [ + "transfer", + "channel-1", + "cosmos-hub", + "cosmos-hub", + "atom" + ], + "amount": 3 + }, + { + "address": [ + "", + "", + "a2" + ], + "denom": [ + "transfer", + "channel-1", + "cosmos-hub", + "cosmos-hub", + "btc" + ], + "amount": 3 + }, + { + "address": [ + "", + "", + "a3" + ], + "denom": [ + "", + "", + "transfer", + "channel-0", + "eth" + ], + "amount": 3 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a2" + ], + "denom": [ + "transfer", + "channel-1", + "cosmos-hub", + "cosmos-hub", + "atom" + ], + "amount": 3 + }, + { + "address": [ + "", + "", + "a2" + ], + "denom": [ + "transfer", + "channel-1", + "cosmos-hub", + "cosmos-hub", + "btc" + ], + "amount": 3 + }, + { + "address": [ + "", + "", + "a3" + ], + "denom": [ + "", + "", + "transfer", + "channel-0", + "eth" + ], + "amount": 2 + }, + { + "address": [ + "transfer", + "channel-1", + "" + ], + "denom": [ + "", + "", + "transfer", + "channel-0", + "eth" + ], + "amount": 1 + } + ], + "error": false + } +] diff --git a/modules/apps/transfer/keeper/model_based_tests/Test5PacketsAllDifferentPass.tla b/modules/apps/transfer/keeper/model_based_tests/Test5PacketsAllDifferentPass.tla new file mode 100644 index 0000000..64d8123 --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/Test5PacketsAllDifferentPass.tla @@ -0,0 +1,1188 @@ +------------------------- MODULE counterexample ------------------------- + +EXTENDS relay_tests + +(* Initial state *) + +State1 == +TRUE +(* Transition 0 to State2 *) + +State2 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 +/\ count = 0 +/\ error = FALSE +/\ handler = "" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a3"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a3"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"] + +(* Transition 6 to State3 *) + +State3 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3 +/\ count = 1 +/\ error = FALSE +/\ handler = "OnTimeoutPacket" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a3"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "OnTimeoutPacket", + packet |-> + [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a3"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]], + receiver |-> "a1", + sender |-> "a2"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-1", + sourcePort |-> "transfer"] + +(* Transition 10 to State4 *) + +State4 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3 +/\ count = 2 +/\ error = FALSE +/\ handler = "OnRecvAcknowledgementError" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a3"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "OnTimeoutPacket", + packet |-> + [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a3"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 2 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + error |-> FALSE, + handler |-> "OnRecvAcknowledgementError", + packet |-> + [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]], + receiver |-> "a1", + sender |-> "a2"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-1", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a1"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"] + +(* Transition 5 to State5 *) + +State5 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3 +/\ count = 3 +/\ error = FALSE +/\ handler = "OnRecvPacket" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a3"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "OnTimeoutPacket", + packet |-> + [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a3"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 2 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + error |-> FALSE, + handler |-> "OnRecvAcknowledgementError", + packet |-> + [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]], + receiver |-> "a1", + sender |-> "a2"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-1", + sourcePort |-> "transfer"]] + @@ 3 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a1"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 2, + denomTrace |-> + [denom |-> "", + prefix0 |-> [channel |-> "channel-1", port |-> "channel-1"], + prefix1 |-> [channel |-> "channel-0", port |-> ""]], + receiver |-> "", + sender |-> "a1"], + destChannel |-> "transfer", + destPort |-> "cosmos-hub", + sourceChannel |-> "cosmos-hub", + sourcePort |-> "bitcoin-hub"] + +(* Transition 12 to State6 *) + +State6 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3 +/\ count = 4 +/\ error = FALSE +/\ handler = "OnRecvAcknowledgementResult" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a3"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "OnTimeoutPacket", + packet |-> + [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a3"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 2 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + error |-> FALSE, + handler |-> "OnRecvAcknowledgementError", + packet |-> + [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]], + receiver |-> "a1", + sender |-> "a2"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-1", + sourcePort |-> "transfer"]] + @@ 3 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a1"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 4 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + error |-> FALSE, + handler |-> "OnRecvAcknowledgementResult", + packet |-> + [data |-> + [amount |-> 2, + denomTrace |-> + [denom |-> "", + prefix0 |-> [channel |-> "channel-1", port |-> "channel-1"], + prefix1 |-> [channel |-> "channel-0", port |-> ""]], + receiver |-> "", + sender |-> "a1"], + destChannel |-> "transfer", + destPort |-> "cosmos-hub", + sourceChannel |-> "cosmos-hub", + sourcePort |-> "bitcoin-hub"]] +/\ p = [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a3", + sender |-> "a3"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-1", + sourcePort |-> "transfer"] + +(* Transition 1 to State7 *) + +State7 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2 + @@ << + [channel |-> "channel-1", id |-> "", port |-> "transfer"], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 1 +/\ count = 5 +/\ error = FALSE +/\ handler = "SendTransfer" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a3"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "OnTimeoutPacket", + packet |-> + [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a3"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 2 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + error |-> FALSE, + handler |-> "OnRecvAcknowledgementError", + packet |-> + [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]], + receiver |-> "a1", + sender |-> "a2"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-1", + sourcePort |-> "transfer"]] + @@ 3 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a1"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 4 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + error |-> FALSE, + handler |-> "OnRecvAcknowledgementResult", + packet |-> + [data |-> + [amount |-> 2, + denomTrace |-> + [denom |-> "", + prefix0 |-> [channel |-> "channel-1", port |-> "channel-1"], + prefix1 |-> [channel |-> "channel-0", port |-> ""]], + receiver |-> "", + sender |-> "a1"], + destChannel |-> "transfer", + destPort |-> "cosmos-hub", + sourceChannel |-> "cosmos-hub", + sourcePort |-> "bitcoin-hub"]] + @@ 5 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2 + @@ << + [channel |-> "channel-1", id |-> "", port |-> "transfer"], [denom |-> + "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 1, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 3 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + error |-> FALSE, + handler |-> "SendTransfer", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "eth", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a3", + sender |-> "a3"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-1", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 0, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "transfer", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "cosmos-hub", port |-> "transfer"]], + receiver |-> "", + sender |-> ""], + destChannel |-> "bitcoin-hub", + destPort |-> "ethereum-hub", + sourceChannel |-> "transfer", + sourcePort |-> "channel-1"] + +(* The following formula holds true in the last state and violates the invariant *) + +InvariantViolation == + (count >= 5 + /\ (\A s1$2 \in DOMAIN history: + \A s2$2 \in DOMAIN history: + s1$2 = s2$2 \/ ~(history[s1$2]["handler"] = history[s2$2]["handler"]))) + /\ (\A s$2 \in DOMAIN history: + s$2 <= 0 + \/ (history[s$2]["error"] = FALSE + /\ history[s$2]["packet"]["data"]["amount"] > 0)) + +================================================================================ +\* Created by Apalache on Thu Dec 10 12:49:42 CET 2020 +\* https://github.com/informalsystems/apalache diff --git a/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementErrorFail.json b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementErrorFail.json new file mode 100644 index 0000000..86cb2a8 --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementErrorFail.json @@ -0,0 +1,58 @@ +[ + { + "packet": { + "sourceChannel": "", + "sourcePort": "", + "destChannel": "", + "destPort": "", + "data": { + "sender": "a1", + "receiver": "a2", + "amount": "1", + "denom": [ + "cosmos-hub", + "transfer", + "channel-0", + "cosmos-hub", + "btc" + ] + } + }, + "handler": "OnRecvAcknowledgementError", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + } + ], + "error": true + } +] diff --git a/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementErrorFail.tla b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementErrorFail.tla new file mode 100644 index 0000000..fca5230 --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementErrorFail.tla @@ -0,0 +1,159 @@ +------------------------- MODULE counterexample ------------------------- + +EXTENDS relay_tests + +(* Initial state *) + +State1 == +TRUE +(* Transition 0 to State2 *) + +State2 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 +/\ count = 0 +/\ error = FALSE +/\ handler = "" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "transfer", port |-> "cosmos-hub"]], + receiver |-> "a2", + sender |-> "a1"], + destChannel |-> "", + destPort |-> "", + sourceChannel |-> "", + sourcePort |-> ""]] +/\ p = [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "transfer", port |-> "cosmos-hub"]], + receiver |-> "a2", + sender |-> "a1"], + destChannel |-> "", + destPort |-> "", + sourceChannel |-> "", + sourcePort |-> ""] + +(* Transition 7 to State3 *) + +State3 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 +/\ count = 1 +/\ error = TRUE +/\ handler = "OnRecvAcknowledgementError" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "transfer", port |-> "cosmos-hub"]], + receiver |-> "a2", + sender |-> "a1"], + destChannel |-> "", + destPort |-> "", + sourceChannel |-> "", + sourcePort |-> ""]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> TRUE, + handler |-> "OnRecvAcknowledgementError", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "transfer", port |-> "cosmos-hub"]], + receiver |-> "a2", + sender |-> "a1"], + destChannel |-> "", + destPort |-> "", + sourceChannel |-> "", + sourcePort |-> ""]] +/\ p = [data |-> + [amount |-> 0, + denomTrace |-> + [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "", + sender |-> ""], + destChannel |-> "", + destPort |-> "", + sourceChannel |-> "", + sourcePort |-> ""] + +(* The following formula holds true in the last state and violates the invariant *) + +InvariantViolation == + BMC!Skolem((\E s$2 \in DOMAIN history: + history[s$2]["handler"] = "OnRecvAcknowledgementError" + /\ history[s$2]["error"] = TRUE + /\ history[s$2]["packet"]["data"]["amount"] > 0)) + +================================================================================ +\* Created by Apalache on Thu Dec 10 11:15:18 CET 2020 +\* https://github.com/informalsystems/apalache diff --git a/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementErrorPass.json b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementErrorPass.json new file mode 100644 index 0000000..2ded4a2 --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementErrorPass.json @@ -0,0 +1,159 @@ +[ + { + "packet": { + "sourceChannel": "channel-0", + "sourcePort": "transfer", + "destChannel": "channel-1", + "destPort": "transfer", + "data": { + "sender": "", + "receiver": "a1", + "amount": "1", + "denom": [ + "", + "", + "channel-0", + "ethereum-hub", + "btc" + ] + } + }, + "handler": "OnRecvPacket", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a1" + ], + "denom": [ + "transfer", + "channel-1", + "channel-0", + "ethereum-hub", + "btc" + ], + "amount": 1 + } + ], + "error": false + }, + { + "packet": { + "sourceChannel": "channel-1", + "sourcePort": "transfer", + "destChannel": "channel-0", + "destPort": "transfer", + "data": { + "sender": "a1", + "receiver": "a2", + "amount": "1", + "denom": [ + "transfer", + "channel-1", + "channel-0", + "ethereum-hub", + "btc" + ] + } + }, + "handler": "OnRecvAcknowledgementError", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a1" + ], + "denom": [ + "transfer", + "channel-1", + "channel-0", + "ethereum-hub", + "btc" + ], + "amount": 1 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a1" + ], + "denom": [ + "transfer", + "channel-1", + "channel-0", + "ethereum-hub", + "btc" + ], + "amount": 2 + } + ], + "error": false + } +] diff --git a/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementErrorPass.tla b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementErrorPass.tla new file mode 100644 index 0000000..4469c11 --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementErrorPass.tla @@ -0,0 +1,310 @@ +------------------------- MODULE counterexample ------------------------- + +EXTENDS relay_tests + +(* Initial state *) + +State1 == +TRUE +(* Transition 0 to State2 *) + +State2 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 +/\ count = 0 +/\ error = FALSE +/\ handler = "" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "ethereum-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a1", + sender |-> ""], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "ethereum-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a1", + sender |-> ""], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"] + +(* Transition 2 to State3 *) + +State3 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a1", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "ethereum-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 1 +/\ count = 1 +/\ error = FALSE +/\ handler = "OnRecvPacket" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "ethereum-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a1", + sender |-> ""], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a1", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "ethereum-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 1, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "ethereum-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a1", + sender |-> ""], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "ethereum-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]], + receiver |-> "a2", + sender |-> "a1"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-1", + sourcePort |-> "transfer"] + +(* Transition 11 to State4 *) + +State4 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a1", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "ethereum-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 2 +/\ count = 2 +/\ error = FALSE +/\ handler = "OnRecvAcknowledgementError" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "ethereum-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a1", + sender |-> ""], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a1", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "ethereum-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 1, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "ethereum-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a1", + sender |-> ""], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 2 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a1", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "ethereum-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 2, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a1", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "ethereum-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 1, + error |-> FALSE, + handler |-> "OnRecvAcknowledgementError", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "ethereum-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]], + receiver |-> "a2", + sender |-> "a1"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-1", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 0, + denomTrace |-> + [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "", + sender |-> ""], + destChannel |-> "", + destPort |-> "", + sourceChannel |-> "", + sourcePort |-> ""] + +(* The following formula holds true in the last state and violates the invariant *) + +InvariantViolation == + BMC!Skolem((\E s$2 \in DOMAIN history: + history[s$2]["handler"] = "OnRecvAcknowledgementError" + /\ history[s$2]["error"] = FALSE + /\ history[s$2]["packet"]["data"]["amount"] > 0)) + +================================================================================ +\* Created by Apalache on Thu Dec 10 11:14:33 CET 2020 +\* https://github.com/informalsystems/apalache diff --git a/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementResultFail.json b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementResultFail.json new file mode 100644 index 0000000..cf59194 --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementResultFail.json @@ -0,0 +1,58 @@ +[ + { + "packet": { + "sourceChannel": "", + "sourcePort": "", + "destChannel": "", + "destPort": "", + "data": { + "sender": "a1", + "receiver": "a2", + "amount": "1", + "denom": [ + "cosmos-hub", + "transfer", + "channel-0", + "cosmos-hub", + "btc" + ] + } + }, + "handler": "OnRecvAcknowledgementResult", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + } + ], + "error": true + } +] diff --git a/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementResultFail.tla b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementResultFail.tla new file mode 100644 index 0000000..2a29ec7 --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementResultFail.tla @@ -0,0 +1,159 @@ +------------------------- MODULE counterexample ------------------------- + +EXTENDS relay_tests + +(* Initial state *) + +State1 == +TRUE +(* Transition 0 to State2 *) + +State2 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 +/\ count = 0 +/\ error = FALSE +/\ handler = "" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "transfer", port |-> "cosmos-hub"]], + receiver |-> "a2", + sender |-> "a1"], + destChannel |-> "", + destPort |-> "", + sourceChannel |-> "", + sourcePort |-> ""]] +/\ p = [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "transfer", port |-> "cosmos-hub"]], + receiver |-> "a2", + sender |-> "a1"], + destChannel |-> "", + destPort |-> "", + sourceChannel |-> "", + sourcePort |-> ""] + +(* Transition 13 to State3 *) + +State3 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 +/\ count = 1 +/\ error = TRUE +/\ handler = "OnRecvAcknowledgementResult" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "transfer", port |-> "cosmos-hub"]], + receiver |-> "a2", + sender |-> "a1"], + destChannel |-> "", + destPort |-> "", + sourceChannel |-> "", + sourcePort |-> ""]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> TRUE, + handler |-> "OnRecvAcknowledgementResult", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "transfer", port |-> "cosmos-hub"]], + receiver |-> "a2", + sender |-> "a1"], + destChannel |-> "", + destPort |-> "", + sourceChannel |-> "", + sourcePort |-> ""]] +/\ p = [data |-> + [amount |-> 0, + denomTrace |-> + [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "", + sender |-> ""], + destChannel |-> "", + destPort |-> "", + sourceChannel |-> "", + sourcePort |-> ""] + +(* The following formula holds true in the last state and violates the invariant *) + +InvariantViolation == + BMC!Skolem((\E s$2 \in DOMAIN history: + history[s$2]["handler"] = "OnRecvAcknowledgementResult" + /\ history[s$2]["error"] = TRUE + /\ history[s$2]["packet"]["data"]["amount"] > 0)) + +================================================================================ +\* Created by Apalache on Thu Dec 10 11:13:42 CET 2020 +\* https://github.com/informalsystems/apalache diff --git a/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementResultPass.json b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementResultPass.json new file mode 100644 index 0000000..3c544b6 --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementResultPass.json @@ -0,0 +1,58 @@ +[ + { + "packet": { + "sourceChannel": "ethereum-hub", + "sourcePort": "transfer", + "destChannel": "channel-0", + "destPort": "ethereum-hub", + "data": { + "sender": "a1", + "receiver": "a2", + "amount": "1", + "denom": [ + "cosmos-hub", + "transfer", + "channel-0", + "cosmos-hub", + "btc" + ] + } + }, + "handler": "OnRecvAcknowledgementResult", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + } + ], + "error": false + } +] diff --git a/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementResultPass.tla b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementResultPass.tla new file mode 100644 index 0000000..fc3c557 --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvAcknowledgementResultPass.tla @@ -0,0 +1,159 @@ +------------------------- MODULE counterexample ------------------------- + +EXTENDS relay_tests + +(* Initial state *) + +State1 == +TRUE +(* Transition 0 to State2 *) + +State2 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 +/\ count = 0 +/\ error = FALSE +/\ handler = "" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "transfer", port |-> "cosmos-hub"]], + receiver |-> "a2", + sender |-> "a1"], + destChannel |-> "channel-0", + destPort |-> "ethereum-hub", + sourceChannel |-> "ethereum-hub", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "transfer", port |-> "cosmos-hub"]], + receiver |-> "a2", + sender |-> "a1"], + destChannel |-> "channel-0", + destPort |-> "ethereum-hub", + sourceChannel |-> "ethereum-hub", + sourcePort |-> "transfer"] + +(* Transition 12 to State3 *) + +State3 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 +/\ count = 1 +/\ error = FALSE +/\ handler = "OnRecvAcknowledgementResult" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "transfer", port |-> "cosmos-hub"]], + receiver |-> "a2", + sender |-> "a1"], + destChannel |-> "channel-0", + destPort |-> "ethereum-hub", + sourceChannel |-> "ethereum-hub", + sourcePort |-> "transfer"]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "OnRecvAcknowledgementResult", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "transfer", port |-> "cosmos-hub"]], + receiver |-> "a2", + sender |-> "a1"], + destChannel |-> "channel-0", + destPort |-> "ethereum-hub", + sourceChannel |-> "ethereum-hub", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 0, + denomTrace |-> + [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "", + sender |-> ""], + destChannel |-> "", + destPort |-> "", + sourceChannel |-> "", + sourcePort |-> ""] + +(* The following formula holds true in the last state and violates the invariant *) + +InvariantViolation == + BMC!Skolem((\E s$2 \in DOMAIN history: + history[s$2]["handler"] = "OnRecvAcknowledgementResult" + /\ history[s$2]["error"] = FALSE + /\ history[s$2]["packet"]["data"]["amount"] > 0)) + +================================================================================ +\* Created by Apalache on Thu Dec 10 11:12:59 CET 2020 +\* https://github.com/informalsystems/apalache diff --git a/modules/apps/transfer/keeper/model_based_tests/TestOnRecvPacketFail.json b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvPacketFail.json new file mode 100644 index 0000000..19890be --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvPacketFail.json @@ -0,0 +1,58 @@ +[ + { + "packet": { + "sourceChannel": "channel-0", + "sourcePort": "transfer", + "destChannel": "channel-0", + "destPort": "transfer", + "data": { + "sender": "", + "receiver": "", + "amount": "1", + "denom": [ + "", + "", + "transfer", + "channel-0", + "" + ] + } + }, + "handler": "OnRecvPacket", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + } + ], + "error": true + } +] diff --git a/modules/apps/transfer/keeper/model_based_tests/TestOnRecvPacketFail.tla b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvPacketFail.tla new file mode 100644 index 0000000..9d33473 --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvPacketFail.tla @@ -0,0 +1,159 @@ +------------------------- MODULE counterexample ------------------------- + +EXTENDS relay_tests + +(* Initial state *) + +State1 == +TRUE +(* Transition 0 to State2 *) + +State2 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 +/\ count = 0 +/\ error = FALSE +/\ handler = "" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "", + sender |-> ""], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "", + sender |-> ""], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"] + +(* Transition 3 to State3 *) + +State3 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 +/\ count = 1 +/\ error = TRUE +/\ handler = "OnRecvPacket" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "", + sender |-> ""], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> TRUE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "", + sender |-> ""], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 0, + denomTrace |-> + [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "", + sender |-> ""], + destChannel |-> "", + destPort |-> "", + sourceChannel |-> "", + sourcePort |-> ""] + +(* The following formula holds true in the last state and violates the invariant *) + +InvariantViolation == + BMC!Skolem((\E s$2 \in DOMAIN history: + history[s$2]["handler"] = "OnRecvPacket" + /\ history[s$2]["error"] = TRUE + /\ history[s$2]["packet"]["data"]["amount"] > 0)) + +================================================================================ +\* Created by Apalache on Thu Dec 10 11:02:31 CET 2020 +\* https://github.com/informalsystems/apalache diff --git a/modules/apps/transfer/keeper/model_based_tests/TestOnRecvPacketPass.json b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvPacketPass.json new file mode 100644 index 0000000..a9ec7f5 --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvPacketPass.json @@ -0,0 +1,73 @@ +[ + { + "packet": { + "sourceChannel": "channel-0", + "sourcePort": "transfer", + "destChannel": "channel-0", + "destPort": "transfer", + "data": { + "sender": "", + "receiver": "a2", + "amount": "1", + "denom": [ + "", + "", + "ethereum-hub", + "cosmos-hub", + "btc" + ] + } + }, + "handler": "OnRecvPacket", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a2" + ], + "denom": [ + "transfer", + "channel-0", + "ethereum-hub", + "cosmos-hub", + "btc" + ], + "amount": 1 + } + ], + "error": false + } +] diff --git a/modules/apps/transfer/keeper/model_based_tests/TestOnRecvPacketPass.tla b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvPacketPass.tla new file mode 100644 index 0000000..09ea28e --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/TestOnRecvPacketPass.tla @@ -0,0 +1,174 @@ +------------------------- MODULE counterexample ------------------------- + +EXTENDS relay_tests + +(* Initial state *) + +State1 == +TRUE +(* Transition 0 to State2 *) + +State2 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 +/\ count = 0 +/\ error = FALSE +/\ handler = "" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> + [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> ""], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> ""], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"] + +(* Transition 5 to State3 *) + +State3 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "channel-0", port |-> "transfer"]] + >> + :> 1 +/\ count = 1 +/\ error = FALSE +/\ handler = "OnRecvPacket" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> + [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> ""], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "channel-0", port |-> "transfer"]] + >> + :> 1, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> + [channel |-> "cosmos-hub", port |-> "ethereum-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> ""], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 0, + denomTrace |-> + [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "", + sender |-> ""], + destChannel |-> "", + destPort |-> "", + sourceChannel |-> "", + sourcePort |-> ""] + +(* The following formula holds true in the last state and violates the invariant *) + +InvariantViolation == + BMC!Skolem((\E s$2 \in DOMAIN history: + history[s$2]["handler"] = "OnRecvPacket" + /\ history[s$2]["error"] = FALSE + /\ history[s$2]["packet"]["data"]["amount"] > 0)) + +================================================================================ +\* Created by Apalache on Thu Dec 10 11:01:28 CET 2020 +\* https://github.com/informalsystems/apalache diff --git a/modules/apps/transfer/keeper/model_based_tests/TestOnTimeoutFail.json b/modules/apps/transfer/keeper/model_based_tests/TestOnTimeoutFail.json new file mode 100644 index 0000000..ac1e93b --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/TestOnTimeoutFail.json @@ -0,0 +1,58 @@ +[ + { + "packet": { + "sourceChannel": "", + "sourcePort": "", + "destChannel": "", + "destPort": "", + "data": { + "sender": "a1", + "receiver": "a2", + "amount": "1", + "denom": [ + "cosmos-hub", + "transfer", + "channel-0", + "cosmos-hub", + "btc" + ] + } + }, + "handler": "OnTimeoutPacket", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + } + ], + "error": true + } +] diff --git a/modules/apps/transfer/keeper/model_based_tests/TestOnTimeoutFail.tla b/modules/apps/transfer/keeper/model_based_tests/TestOnTimeoutFail.tla new file mode 100644 index 0000000..ba49f5b --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/TestOnTimeoutFail.tla @@ -0,0 +1,159 @@ +------------------------- MODULE counterexample ------------------------- + +EXTENDS relay_tests + +(* Initial state *) + +State1 == +TRUE +(* Transition 0 to State2 *) + +State2 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 +/\ count = 0 +/\ error = FALSE +/\ handler = "" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "transfer", port |-> "cosmos-hub"]], + receiver |-> "a2", + sender |-> "a1"], + destChannel |-> "", + destPort |-> "", + sourceChannel |-> "", + sourcePort |-> ""]] +/\ p = [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "transfer", port |-> "cosmos-hub"]], + receiver |-> "a2", + sender |-> "a1"], + destChannel |-> "", + destPort |-> "", + sourceChannel |-> "", + sourcePort |-> ""] + +(* Transition 6 to State3 *) + +State3 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 +/\ count = 1 +/\ error = TRUE +/\ handler = "OnTimeoutPacket" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "transfer", port |-> "cosmos-hub"]], + receiver |-> "a2", + sender |-> "a1"], + destChannel |-> "", + destPort |-> "", + sourceChannel |-> "", + sourcePort |-> ""]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> TRUE, + handler |-> "OnTimeoutPacket", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "channel-0"], + prefix1 |-> [channel |-> "transfer", port |-> "cosmos-hub"]], + receiver |-> "a2", + sender |-> "a1"], + destChannel |-> "", + destPort |-> "", + sourceChannel |-> "", + sourcePort |-> ""]] +/\ p = [data |-> + [amount |-> 0, + denomTrace |-> + [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "", + sender |-> ""], + destChannel |-> "", + destPort |-> "", + sourceChannel |-> "", + sourcePort |-> ""] + +(* The following formula holds true in the last state and violates the invariant *) + +InvariantViolation == + BMC!Skolem((\E s$2 \in DOMAIN history: + history[s$2]["handler"] = "OnTimeoutPacket" + /\ history[s$2]["error"] = TRUE + /\ history[s$2]["packet"]["data"]["amount"] > 0)) + +================================================================================ +\* Created by Apalache on Thu Dec 10 11:09:25 CET 2020 +\* https://github.com/informalsystems/apalache diff --git a/modules/apps/transfer/keeper/model_based_tests/TestOnTimeoutPass.json b/modules/apps/transfer/keeper/model_based_tests/TestOnTimeoutPass.json new file mode 100644 index 0000000..e6b7f29 --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/TestOnTimeoutPass.json @@ -0,0 +1,159 @@ +[ + { + "packet": { + "sourceChannel": "channel-0", + "sourcePort": "transfer", + "destChannel": "channel-1", + "destPort": "transfer", + "data": { + "sender": "a3", + "receiver": "a1", + "amount": "1", + "denom": [ + "", + "", + "bitcoin-hub", + "transfer", + "btc" + ] + } + }, + "handler": "OnRecvPacket", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a1" + ], + "denom": [ + "transfer", + "channel-1", + "bitcoin-hub", + "transfer", + "btc" + ], + "amount": 1 + } + ], + "error": false + }, + { + "packet": { + "sourceChannel": "channel-1", + "sourcePort": "transfer", + "destChannel": "channel-0", + "destPort": "transfer", + "data": { + "sender": "a1", + "receiver": "", + "amount": "1", + "denom": [ + "transfer", + "channel-1", + "bitcoin-hub", + "transfer", + "btc" + ] + } + }, + "handler": "OnTimeoutPacket", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a1" + ], + "denom": [ + "transfer", + "channel-1", + "bitcoin-hub", + "transfer", + "btc" + ], + "amount": 1 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a1" + ], + "denom": [ + "transfer", + "channel-1", + "bitcoin-hub", + "transfer", + "btc" + ], + "amount": 2 + } + ], + "error": false + } +] diff --git a/modules/apps/transfer/keeper/model_based_tests/TestOnTimeoutPass.tla b/modules/apps/transfer/keeper/model_based_tests/TestOnTimeoutPass.tla new file mode 100644 index 0000000..62c01e3 --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/TestOnTimeoutPass.tla @@ -0,0 +1,310 @@ +------------------------- MODULE counterexample ------------------------- + +EXTENDS relay_tests + +(* Initial state *) + +State1 == +TRUE +(* Transition 0 to State2 *) + +State2 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 +/\ count = 0 +/\ error = FALSE +/\ handler = "" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "transfer", port |-> "bitcoin-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a1", + sender |-> "a3"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "transfer", port |-> "bitcoin-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a1", + sender |-> "a3"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"] + +(* Transition 2 to State3 *) + +State3 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a1", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "transfer", port |-> "bitcoin-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 1 +/\ count = 1 +/\ error = FALSE +/\ handler = "OnRecvPacket" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "transfer", port |-> "bitcoin-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a1", + sender |-> "a3"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a1", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "transfer", port |-> "bitcoin-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 1, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "transfer", port |-> "bitcoin-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a1", + sender |-> "a3"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "transfer", port |-> "bitcoin-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]], + receiver |-> "", + sender |-> "a1"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-1", + sourcePort |-> "transfer"] + +(* Transition 10 to State4 *) + +State4 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a1", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "transfer", port |-> "bitcoin-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 2 +/\ count = 2 +/\ error = FALSE +/\ handler = "OnTimeoutPacket" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "transfer", port |-> "bitcoin-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a1", + sender |-> "a3"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a1", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "transfer", port |-> "bitcoin-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 1, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "transfer", port |-> "bitcoin-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a1", + sender |-> "a3"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 2 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a1", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "transfer", port |-> "bitcoin-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 2, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a1", port |-> ""], [denom |-> "btc", + prefix0 |-> [channel |-> "transfer", port |-> "bitcoin-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]] + >> + :> 1, + error |-> FALSE, + handler |-> "OnTimeoutPacket", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "transfer", port |-> "bitcoin-hub"], + prefix1 |-> [channel |-> "channel-1", port |-> "transfer"]], + receiver |-> "", + sender |-> "a1"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-1", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 0, + denomTrace |-> + [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "", + sender |-> ""], + destChannel |-> "", + destPort |-> "", + sourceChannel |-> "", + sourcePort |-> ""] + +(* The following formula holds true in the last state and violates the invariant *) + +InvariantViolation == + BMC!Skolem((\E s$2 \in DOMAIN history: + history[s$2]["handler"] = "OnTimeoutPacket" + /\ history[s$2]["error"] = FALSE + /\ history[s$2]["packet"]["data"]["amount"] > 0)) + +================================================================================ +\* Created by Apalache on Thu Dec 10 11:07:37 CET 2020 +\* https://github.com/informalsystems/apalache diff --git a/modules/apps/transfer/keeper/model_based_tests/TestSendTransferFail.json b/modules/apps/transfer/keeper/model_based_tests/TestSendTransferFail.json new file mode 100644 index 0000000..4303bdd --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/TestSendTransferFail.json @@ -0,0 +1,58 @@ +[ + { + "packet": { + "sourceChannel": "channel-0", + "sourcePort": "transfer", + "destChannel": "channel-0", + "destPort": "transfer", + "data": { + "sender": "", + "receiver": "", + "amount": "1", + "denom": [ + "", + "", + "", + "", + "" + ] + } + }, + "handler": "SendTransfer", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + } + ], + "error": true + } +] diff --git a/modules/apps/transfer/keeper/model_based_tests/TestSendTransferFail.tla b/modules/apps/transfer/keeper/model_based_tests/TestSendTransferFail.tla new file mode 100644 index 0000000..34feefc --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/TestSendTransferFail.tla @@ -0,0 +1,159 @@ +------------------------- MODULE counterexample ------------------------- + +EXTENDS relay_tests + +(* Initial state *) + +State1 == +TRUE +(* Transition 0 to State2 *) + +State2 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 +/\ count = 0 +/\ error = FALSE +/\ handler = "" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "", + sender |-> ""], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "", + sender |-> ""], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"] + +(* Transition 0 to State3 *) + +State3 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 +/\ count = 1 +/\ error = TRUE +/\ handler = "SendTransfer" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "", + sender |-> ""], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> TRUE, + handler |-> "SendTransfer", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "", + sender |-> ""], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 0, + denomTrace |-> + [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "", + sender |-> ""], + destChannel |-> "", + destPort |-> "", + sourceChannel |-> "", + sourcePort |-> ""] + +(* The following formula holds true in the last state and violates the invariant *) + +InvariantViolation == + BMC!Skolem((\E s$2 \in DOMAIN history: + history[s$2]["handler"] = "SendTransfer" + /\ history[s$2]["error"] = TRUE + /\ history[s$2]["packet"]["data"]["amount"] > 0)) + +================================================================================ +\* Created by Apalache on Thu Dec 10 11:00:34 CET 2020 +\* https://github.com/informalsystems/apalache diff --git a/modules/apps/transfer/keeper/model_based_tests/TestSendTransferPass.json b/modules/apps/transfer/keeper/model_based_tests/TestSendTransferPass.json new file mode 100644 index 0000000..ec3183b --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/TestSendTransferPass.json @@ -0,0 +1,174 @@ +[ + { + "packet": { + "sourceChannel": "channel-0", + "sourcePort": "transfer", + "destChannel": "channel-0", + "destPort": "transfer", + "data": { + "sender": "a3", + "receiver": "a2", + "amount": "1", + "denom": [ + "", + "", + "cosmos-hub", + "cosmos-hub", + "eth" + ] + } + }, + "handler": "OnRecvPacket", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a2" + ], + "denom": [ + "transfer", + "channel-0", + "cosmos-hub", + "cosmos-hub", + "eth" + ], + "amount": 1 + } + ], + "error": false + }, + { + "packet": { + "sourceChannel": "channel-1", + "sourcePort": "transfer", + "destChannel": "channel-0", + "destPort": "transfer", + "data": { + "sender": "a2", + "receiver": "a1", + "amount": "1", + "denom": [ + "transfer", + "channel-0", + "cosmos-hub", + "cosmos-hub", + "eth" + ] + } + }, + "handler": "SendTransfer", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a2" + ], + "denom": [ + "transfer", + "channel-0", + "cosmos-hub", + "cosmos-hub", + "eth" + ], + "amount": 1 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a2" + ], + "denom": [ + "transfer", + "channel-0", + "cosmos-hub", + "cosmos-hub", + "eth" + ], + "amount": 0 + }, + { + "address": [ + "transfer", + "channel-1", + "" + ], + "denom": [ + "transfer", + "channel-0", + "cosmos-hub", + "cosmos-hub", + "eth" + ], + "amount": 1 + } + ], + "error": false + } +] diff --git a/modules/apps/transfer/keeper/model_based_tests/TestSendTransferPass.tla b/modules/apps/transfer/keeper/model_based_tests/TestSendTransferPass.tla new file mode 100644 index 0000000..a13b65c --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/TestSendTransferPass.tla @@ -0,0 +1,323 @@ +------------------------- MODULE counterexample ------------------------- + +EXTENDS relay_tests + +(* Initial state *) + +State1 == +TRUE +(* Transition 0 to State2 *) + +State2 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 +/\ count = 0 +/\ error = FALSE +/\ handler = "" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "eth", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a3"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "eth", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a3"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"] + +(* Transition 2 to State3 *) + +State3 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-0", port |-> "transfer"]] + >> + :> 1 +/\ count = 1 +/\ error = FALSE +/\ handler = "OnRecvPacket" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "eth", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a3"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-0", port |-> "transfer"]] + >> + :> 1, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "eth", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a3"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "eth", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-0", port |-> "transfer"]], + receiver |-> "a1", + sender |-> "a2"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-1", + sourcePort |-> "transfer"] + +(* Transition 1 to State4 *) + +State4 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-0", port |-> "transfer"]] + >> + :> 0 + @@ << + [channel |-> "channel-1", id |-> "", port |-> "transfer"], [denom |-> "eth", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-0", port |-> "transfer"]] + >> + :> 1 +/\ count = 2 +/\ error = FALSE +/\ handler = "SendTransfer" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "eth", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a3"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-0", port |-> "transfer"]] + >> + :> 1, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "eth", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a2", + sender |-> "a3"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 2 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-0", port |-> "transfer"]] + >> + :> 0 + @@ << + [channel |-> "channel-1", id |-> "", port |-> "transfer"], [denom |-> + "eth", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-0", port |-> "transfer"]] + >> + :> 1, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a2", port |-> ""], [denom |-> "eth", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-0", port |-> "transfer"]] + >> + :> 1, + error |-> FALSE, + handler |-> "SendTransfer", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "eth", + prefix0 |-> [channel |-> "cosmos-hub", port |-> "cosmos-hub"], + prefix1 |-> [channel |-> "channel-0", port |-> "transfer"]], + receiver |-> "a1", + sender |-> "a2"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-1", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 0, + denomTrace |-> + [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "", + sender |-> ""], + destChannel |-> "", + destPort |-> "", + sourceChannel |-> "", + sourcePort |-> ""] + +(* The following formula holds true in the last state and violates the invariant *) + +InvariantViolation == + BMC!Skolem((\E s$2 \in DOMAIN history: + history[s$2]["handler"] = "SendTransfer" + /\ history[s$2]["error"] = FALSE + /\ history[s$2]["packet"]["data"]["amount"] > 0)) + +================================================================================ +\* Created by Apalache on Thu Dec 10 10:58:54 CET 2020 +\* https://github.com/informalsystems/apalache diff --git a/modules/apps/transfer/keeper/model_based_tests/TestUnescrowTokens.json b/modules/apps/transfer/keeper/model_based_tests/TestUnescrowTokens.json new file mode 100644 index 0000000..6260290 --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/TestUnescrowTokens.json @@ -0,0 +1,305 @@ +[ + { + "packet": { + "sourceChannel": "channel-0", + "sourcePort": "transfer", + "destChannel": "channel-0", + "destPort": "transfer", + "data": { + "sender": "a1", + "receiver": "a3", + "amount": "5", + "denom": [ + "", + "", + "", + "", + "atom" + ] + } + }, + "handler": "OnRecvPacket", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a3" + ], + "denom": [ + "", + "", + "transfer", + "channel-0", + "atom" + ], + "amount": 5 + } + ], + "error": false + }, + { + "packet": { + "sourceChannel": "channel-1", + "sourcePort": "transfer", + "destChannel": "channel-0", + "destPort": "transfer", + "data": { + "sender": "a3", + "receiver": "a1", + "amount": "3", + "denom": [ + "", + "", + "transfer", + "channel-0", + "atom" + ] + } + }, + "handler": "SendTransfer", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a3" + ], + "denom": [ + "", + "", + "transfer", + "channel-0", + "atom" + ], + "amount": 5 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a3" + ], + "denom": [ + "", + "", + "transfer", + "channel-0", + "atom" + ], + "amount": 2 + }, + { + "address": [ + "transfer", + "channel-1", + "" + ], + "denom": [ + "", + "", + "transfer", + "channel-0", + "atom" + ], + "amount": 3 + } + ], + "error": false + }, + { + "packet": { + "sourceChannel": "channel-0", + "sourcePort": "transfer", + "destChannel": "channel-1", + "destPort": "transfer", + "data": { + "sender": "a1", + "receiver": "a1", + "amount": "1", + "denom": [ + "transfer", + "channel-0", + "transfer", + "channel-0", + "atom" + ] + } + }, + "handler": "OnRecvPacket", + "bankBefore": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a3" + ], + "denom": [ + "", + "", + "transfer", + "channel-0", + "atom" + ], + "amount": 2 + }, + { + "address": [ + "transfer", + "channel-1", + "" + ], + "denom": [ + "", + "", + "transfer", + "channel-0", + "atom" + ], + "amount": 3 + } + ], + "bankAfter": [ + { + "address": [ + "", + "", + "" + ], + "denom": [ + "", + "", + "", + "", + "" + ], + "amount": 0 + }, + { + "address": [ + "", + "", + "a1" + ], + "denom": [ + "", + "", + "transfer", + "channel-0", + "atom" + ], + "amount": 1 + }, + { + "address": [ + "", + "", + "a3" + ], + "denom": [ + "", + "", + "transfer", + "channel-0", + "atom" + ], + "amount": 2 + }, + { + "address": [ + "transfer", + "channel-1", + "" + ], + "denom": [ + "", + "", + "transfer", + "channel-0", + "atom" + ], + "amount": 2 + } + ], + "error": false + } +] diff --git a/modules/apps/transfer/keeper/model_based_tests/TestUnescrowTokens.tla b/modules/apps/transfer/keeper/model_based_tests/TestUnescrowTokens.tla new file mode 100644 index 0000000..af8b932 --- /dev/null +++ b/modules/apps/transfer/keeper/model_based_tests/TestUnescrowTokens.tla @@ -0,0 +1,563 @@ +------------------------- MODULE counterexample ------------------------- + +EXTENDS relay_tests + +(* Initial state *) + +State1 == +TRUE +(* Transition 0 to State2 *) + +State2 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 +/\ count = 0 +/\ error = FALSE +/\ handler = "" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 5, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a3", + sender |-> "a1"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 5, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a3", + sender |-> "a1"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"] + +(* Transition 3 to State3 *) + +State3 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 5 +/\ count = 1 +/\ error = FALSE +/\ handler = "OnRecvPacket" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 5, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a3", + sender |-> "a1"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 5, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 5, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a3", + sender |-> "a1"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a1", + sender |-> "a3"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-1", + sourcePort |-> "transfer"] + +(* Transition 1 to State4 *) + +State4 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2 + @@ << + [channel |-> "channel-1", id |-> "", port |-> "transfer"], [denom |-> "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3 +/\ count = 2 +/\ error = FALSE +/\ handler = "SendTransfer" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 5, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a3", + sender |-> "a1"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 5, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 5, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a3", + sender |-> "a1"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 2 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2 + @@ << + [channel |-> "channel-1", id |-> "", port |-> "transfer"], [denom |-> + "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 5, + error |-> FALSE, + handler |-> "SendTransfer", + packet |-> + [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a1", + sender |-> "a3"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-1", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "channel-0", port |-> "transfer"]], + receiver |-> "a1", + sender |-> "a1"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"] + +(* Transition 4 to State5 *) + +State5 == +/\ bank = << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a1", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 1 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2 + @@ << + [channel |-> "channel-1", id |-> "", port |-> "transfer"], [denom |-> "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2 +/\ count = 3 +/\ error = FALSE +/\ handler = "OnRecvPacket" +/\ history = 0 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "", + packet |-> + [data |-> + [amount |-> 5, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a3", + sender |-> "a1"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 1 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 5, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 5, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a3", + sender |-> "a1"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] + @@ 2 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2 + @@ << + [channel |-> "channel-1", id |-> "", port |-> "transfer"], [denom |-> + "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 5, + error |-> FALSE, + handler |-> "SendTransfer", + packet |-> + [data |-> + [amount |-> 3, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]], + receiver |-> "a1", + sender |-> "a3"], + destChannel |-> "channel-0", + destPort |-> "transfer", + sourceChannel |-> "channel-1", + sourcePort |-> "transfer"]] + @@ 3 + :> [bankAfter |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a1", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 1 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2 + @@ << + [channel |-> "channel-1", id |-> "", port |-> "transfer"], [denom |-> + "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2, + bankBefore |-> + << + [channel |-> "", id |-> "", port |-> ""], [denom |-> "", + prefix0 |-> [channel |-> "", port |-> ""], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 0 + @@ << + [channel |-> "", id |-> "a3", port |-> ""], [denom |-> "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 2 + @@ << + [channel |-> "channel-1", id |-> "", port |-> "transfer"], [denom |-> + "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "", port |-> ""]] + >> + :> 3, + error |-> FALSE, + handler |-> "OnRecvPacket", + packet |-> + [data |-> + [amount |-> 1, + denomTrace |-> + [denom |-> "atom", + prefix0 |-> [channel |-> "channel-0", port |-> "transfer"], + prefix1 |-> [channel |-> "channel-0", port |-> "transfer"]], + receiver |-> "a1", + sender |-> "a1"], + destChannel |-> "channel-1", + destPort |-> "transfer", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"]] +/\ p = [data |-> + [amount |-> 0, + denomTrace |-> + [denom |-> "btc", + prefix0 |-> [channel |-> "transfer", port |-> "bitcoin-hub"], + prefix1 |-> [channel |-> "channel-0", port |-> "channel-1"]], + receiver |-> "a1", + sender |-> ""], + destChannel |-> "ethereum-hub", + destPort |-> "cosmos-hub", + sourceChannel |-> "channel-0", + sourcePort |-> "transfer"] + +(* The following formula holds true in the last state and violates the invariant *) + +InvariantViolation == + history[1]["handler"] = "OnRecvPacket" + /\ BMC!Skolem((\E s$2 \in DOMAIN history: + ((IF history[s$2]["packet"]["data"]["denomTrace"]["prefix0"] + = [port |-> "", channel |-> ""] + THEN [port |-> "", channel |-> ""] + ELSE IF history[s$2]["packet"]["data"]["denomTrace"]["prefix1"] + = [port |-> "", channel |-> ""] + THEN history[s$2]["packet"]["data"]["denomTrace"]["prefix0"] + ELSE history[s$2]["packet"]["data"]["denomTrace"]["prefix1"])[ + "port" + ] + = history[s$2]["packet"]["sourcePort"] + /\ (IF history[s$2]["packet"]["data"]["denomTrace"]["prefix0"] + = [port |-> "", channel |-> ""] + THEN [port |-> "", channel |-> ""] + ELSE IF history[s$2]["packet"]["data"]["denomTrace"]["prefix1"] + = [port |-> "", channel |-> ""] + THEN history[s$2]["packet"]["data"]["denomTrace"]["prefix0"] + ELSE history[s$2]["packet"]["data"]["denomTrace"]["prefix1"])[ + "channel" + ] + = history[s$2]["packet"]["sourceChannel"]) + /\ history[s$2]["handler"] = "OnRecvPacket" + /\ history[s$2]["error"] = FALSE)) + +================================================================================ +\* Created by Apalache on Thu Dec 10 13:38:11 CET 2020 +\* https://github.com/informalsystems/apalache diff --git a/modules/apps/transfer/keeper/msg_server.go b/modules/apps/transfer/keeper/msg_server.go new file mode 100644 index 0000000..1f020bc --- /dev/null +++ b/modules/apps/transfer/keeper/msg_server.go @@ -0,0 +1,151 @@ +package keeper + +import ( + "context" + + "github.com/cosmos/gogoproto/proto" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/internal/events" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/internal/telemetry" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +var _ types.MsgServer = (*Keeper)(nil) + +// Transfer defines an rpc handler method for MsgTransfer. +func (k Keeper) Transfer(goCtx context.Context, msg *types.MsgTransfer) (*types.MsgTransferResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + if !k.GetParams(ctx).SendEnabled { + return nil, types.ErrSendDisabled + } + + sender, err := k.addressCodec.StringToBytes(msg.Sender) + if err != nil { + return nil, err + } + + coin := msg.Token + + // Using types.UnboundedSpendLimit allows us to send the entire balance of a given denom. + if coin.Amount.Equal(types.UnboundedSpendLimit()) { + coin.Amount = k.BankKeeper.SpendableCoin(ctx, sender, coin.Denom).Amount + if coin.Amount.IsZero() { + return nil, errorsmod.Wrapf(types.ErrInvalidAmount, "empty spendable balance for %s", coin.Denom) + } + } + + token, err := k.TokenFromCoin(ctx, coin) + if err != nil { + return nil, err + } + + packetData := types.NewFungibleTokenPacketData(token.Denom.Path(), token.Amount, msg.Sender, msg.Receiver, msg.Memo) + + if err := packetData.ValidateBasic(); err != nil { + return nil, errorsmod.Wrapf(err, "failed to validate %s packet data", types.V1) + } + + // if a channel exists with source channel, then use IBC V1 protocol + // otherwise use IBC V2 protocol + channel, isIBCV1 := k.channelKeeper.GetChannel(ctx, msg.SourcePort, msg.SourceChannel) + + var sequence uint64 + if isIBCV1 { + // if a V1 channel exists for the source channel, then use IBC V1 protocol + sequence, err = k.transferV1Packet(ctx, msg.SourceChannel, token, msg.TimeoutHeight, msg.TimeoutTimestamp, sender, packetData) + // telemetry for transfer occurs here, in IBC V2 this is done in the onSendPacket callback + telemetry.ReportTransfer(msg.SourcePort, msg.SourceChannel, channel.Counterparty.PortId, channel.Counterparty.ChannelId, token) + } else { + // otherwise try to send an IBC V2 packet, if the sourceChannel is not a IBC V2 client + // then core IBC will return a CounterpartyNotFound error + sequence, err = k.transferV2Packet(ctx, msg.Encoding, msg.SourceChannel, msg.TimeoutTimestamp, packetData) + } + if err != nil { + return nil, err + } + + k.Logger(ctx).Info("IBC fungible token transfer", "token", coin, "sender", msg.Sender, "receiver", msg.Receiver) + + return &types.MsgTransferResponse{Sequence: sequence}, nil +} + +func (k *Keeper) transferV1Packet(ctx sdk.Context, sourceChannel string, token types.Token, timeoutHeight clienttypes.Height, timeoutTimestamp uint64, sender sdk.AccAddress, packetData types.FungibleTokenPacketData) (uint64, error) { + if err := k.SendTransfer(ctx, types.PortID, sourceChannel, token, sender); err != nil { + return 0, err + } + + packetDataBytes := packetData.GetBytes() + sequence, err := k.ics4Wrapper.SendPacket(ctx, types.PortID, sourceChannel, timeoutHeight, timeoutTimestamp, packetDataBytes) + if err != nil { + return 0, err + } + + events.EmitTransferEvent(ctx, packetData.Sender, packetData.Receiver, token, packetData.Memo) + + return sequence, nil +} + +func (k Keeper) transferV2Packet(ctx sdk.Context, encoding, sourceChannel string, timeoutTimestamp uint64, packetData types.FungibleTokenPacketData) (uint64, error) { + if encoding == "" { + encoding = types.EncodingJSON + } + + data, err := types.MarshalPacketData(packetData, types.V1, encoding) + if err != nil { + return 0, err + } + + payload := channeltypesv2.NewPayload( + types.PortID, types.PortID, + types.V1, encoding, data, + ) + msg := channeltypesv2.NewMsgSendPacket( + sourceChannel, timeoutTimestamp, + packetData.Sender, payload, + ) + + handler := k.msgRouter.Handler(msg) + if handler == nil { + return 0, errorsmod.Wrapf(ibcerrors.ErrInvalidRequest, "unrecognized packet type: %T", msg) + } + res, err := handler(ctx, msg) + if err != nil { + return 0, err + } + + // NOTE: The sdk msg handler creates a new EventManager, so events must be correctly propagated back to the current context + ctx.EventManager().EmitEvents(res.GetEvents()) + + // Each individual sdk.Result has exactly one Msg response. We aggregate here. + msgResponse := res.MsgResponses[0] + if msgResponse == nil { + return 0, errorsmod.Wrapf(ibcerrors.ErrLogic, "got nil Msg response for msg %s", sdk.MsgTypeURL(msg)) + } + var sendResponse channeltypesv2.MsgSendPacketResponse + err = proto.Unmarshal(msgResponse.Value, &sendResponse) + if err != nil { + return 0, err + } + + return sendResponse.Sequence, nil +} + +// UpdateParams defines an rpc handler method for MsgUpdateParams. Updates the ibc-transfer module's parameters. +func (k Keeper) UpdateParams(goCtx context.Context, msg *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { + if k.GetAuthority() != msg.Signer { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "expected %s, got %s", k.GetAuthority(), msg.Signer) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + k.SetParams(ctx, msg.Params) + + return &types.MsgUpdateParamsResponse{}, nil +} diff --git a/modules/apps/transfer/keeper/msg_server_test.go b/modules/apps/transfer/keeper/msg_server_test.go new file mode 100644 index 0000000..9525993 --- /dev/null +++ b/modules/apps/transfer/keeper/msg_server_test.go @@ -0,0 +1,359 @@ +package keeper_test + +import ( + "errors" + "strings" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + + abci "github.com/cometbft/cometbft/abci/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + clienttypesv2 "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +// TestMsgTransfer tests Transfer rpc handler +func (suite *KeeperTestSuite) TestMsgTransfer() { + var msg *types.MsgTransfer + var path *ibctesting.Path + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + msg.Token = ibctesting.TestCoin + }, + nil, + }, + { + "bank send enabled for denoms", + func() { + err := suite.chainA.GetSimApp().BankKeeper.SetParams(suite.chainA.GetContext(), + banktypes.Params{ + SendEnabled: []*banktypes.SendEnabled{ + {Denom: sdk.DefaultBondDenom, Enabled: true}, + {Denom: ibctesting.SecondaryDenom, Enabled: true}, + }, + }, + ) + suite.Require().NoError(err) + }, + nil, + }, + { + "failure: send transfers disabled", + func() { + suite.chainA.GetSimApp().TransferKeeper.SetParams(suite.chainA.GetContext(), + types.Params{ + SendEnabled: false, + }, + ) + }, + types.ErrSendDisabled, + }, + { + "failure: zero amount", + func() { + msg.Token = sdk.NewInt64Coin(sdk.DefaultBondDenom, 0) + }, + types.ErrInvalidAmount, + }, + { + "failure: invalid sender", + func() { + msg.Sender = "address" + }, + errors.New("decoding bech32 failed"), + }, + { + "failure: sender is a blocked address", + func() { + msg.Sender = suite.chainA.GetSimApp().AccountKeeper.GetModuleAddress(minttypes.ModuleName).String() + }, + ibcerrors.ErrUnauthorized, + }, + { + "failure: bank send disabled", + func() { + err := suite.chainA.GetSimApp().BankKeeper.SetParams(suite.chainA.GetContext(), + banktypes.Params{ + SendEnabled: []*banktypes.SendEnabled{{Denom: sdk.DefaultBondDenom, Enabled: false}}, + }, + ) + suite.Require().NoError(err) + }, + types.ErrSendDisabled, + }, + { + "failure: channel does not exist", + func() { + msg.SourceChannel = "channel-100" + }, + clienttypesv2.ErrCounterpartyNotFound, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + path = ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path.Setup() + + msg = types.NewMsgTransfer( + path.EndpointA.ChannelConfig.PortID, + path.EndpointA.ChannelID, + ibctesting.TestCoin, + suite.chainA.SenderAccount.GetAddress().String(), + suite.chainB.SenderAccount.GetAddress().String(), + clienttypes.Height{}, suite.chainB.GetTimeoutTimestamp(), // only use timeout height + "memo", + ) + + // send some coins of the second denom from bank module to the sender account as well + err := suite.chainA.GetSimApp().BankKeeper.MintCoins(suite.chainA.GetContext(), types.ModuleName, sdk.NewCoins(ibctesting.SecondaryTestCoin)) + suite.Require().NoError(err) + err = suite.chainA.GetSimApp().BankKeeper.SendCoinsFromModuleToAccount(suite.chainA.GetContext(), types.ModuleName, suite.chainA.SenderAccount.GetAddress(), sdk.NewCoins(ibctesting.SecondaryTestCoin)) + suite.Require().NoError(err) + + tc.malleate() + + ctx := suite.chainA.GetContext() + + token, err := suite.chainA.GetSimApp().TransferKeeper.TokenFromCoin(ctx, msg.Token) + suite.Require().NoError(err) + + res, err := suite.chainA.GetSimApp().TransferKeeper.Transfer(ctx, msg) + + // Verify events + var expEvents []abci.Event + events := ctx.EventManager().Events().ToABCIEvents() + + expEvents = sdk.Events{ + sdk.NewEvent(types.EventTypeTransfer, + sdk.NewAttribute(types.AttributeKeySender, msg.Sender), + sdk.NewAttribute(types.AttributeKeyReceiver, msg.Receiver), + sdk.NewAttribute(types.AttributeKeyDenom, token.Denom.Path()), + sdk.NewAttribute(types.AttributeKeyAmount, msg.Token.Amount.String()), + sdk.NewAttribute(types.AttributeKeyMemo, msg.Memo), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + ), + }.ToABCIEvents() + + if tc.expError == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().NotEqual(res.Sequence, uint64(0)) + ibctesting.AssertEvents(&suite.Suite, expEvents, events) + } else { + suite.Require().Nil(res) + suite.Require().True(errors.Is(err, tc.expError) || strings.Contains(err.Error(), tc.expError.Error()), err.Error()) + suite.Require().Len(events, 0) + } + }) + } +} + +// TestMsgTransfer tests Transfer rpc handler with IBC V2 protocol +func (suite *KeeperTestSuite) TestMsgTransferIBCV2() { + var msg *types.MsgTransfer + var path *ibctesting.Path + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + msg.Token = ibctesting.TestCoin + }, + nil, + }, + { + "bank send enabled for denoms", + func() { + err := suite.chainA.GetSimApp().BankKeeper.SetParams(suite.chainA.GetContext(), + banktypes.Params{ + SendEnabled: []*banktypes.SendEnabled{ + {Denom: sdk.DefaultBondDenom, Enabled: true}, + {Denom: ibctesting.SecondaryDenom, Enabled: true}, + }, + }, + ) + suite.Require().NoError(err) + }, + nil, + }, + { + "failure: send transfers disabled", + func() { + suite.chainA.GetSimApp().TransferKeeper.SetParams(suite.chainA.GetContext(), + types.Params{ + SendEnabled: false, + }, + ) + }, + types.ErrSendDisabled, + }, + { + "failure: invalid sender", + func() { + msg.Sender = "address" + }, + errors.New("decoding bech32 failed"), + }, + { + "failure: sender is a blocked address", + func() { + msg.Sender = suite.chainA.GetSimApp().AccountKeeper.GetModuleAddress(minttypes.ModuleName).String() + }, + ibcerrors.ErrUnauthorized, + }, + { + "failure: bank send disabled", + func() { + err := suite.chainA.GetSimApp().BankKeeper.SetParams(suite.chainA.GetContext(), + banktypes.Params{ + SendEnabled: []*banktypes.SendEnabled{{Denom: sdk.DefaultBondDenom, Enabled: false}}, + }, + ) + suite.Require().NoError(err) + }, + types.ErrSendDisabled, + }, + { + "failure: client does not exist", + func() { + msg.SourceChannel = "07-tendermint-500" + }, + clienttypesv2.ErrCounterpartyNotFound, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + timeoutTimestamp := uint64(suite.chainA.GetContext().BlockTime().Add(time.Hour).Unix()) + + msg = types.NewMsgTransfer( + types.PortID, + path.EndpointA.ClientID, // use eureka client id + ibctesting.TestCoin, + suite.chainA.SenderAccount.GetAddress().String(), + suite.chainB.SenderAccount.GetAddress().String(), + clienttypes.Height{}, timeoutTimestamp, // only use timeout timestamp + "memo", + ) + + // send some coins of the second denom from bank module to the sender account as well + err := suite.chainA.GetSimApp().BankKeeper.MintCoins(suite.chainA.GetContext(), types.ModuleName, sdk.NewCoins(ibctesting.SecondaryTestCoin)) + suite.Require().NoError(err) + err = suite.chainA.GetSimApp().BankKeeper.SendCoinsFromModuleToAccount(suite.chainA.GetContext(), types.ModuleName, suite.chainA.SenderAccount.GetAddress(), sdk.NewCoins(ibctesting.SecondaryTestCoin)) + suite.Require().NoError(err) + + tc.malleate() + + ctx := suite.chainA.GetContext() + + token, err := suite.chainA.GetSimApp().TransferKeeper.TokenFromCoin(ctx, msg.Token) + suite.Require().NoError(err) + + res, err := suite.chainA.GetSimApp().TransferKeeper.Transfer(ctx, msg) + + // Verify events + var expEvents []abci.Event + events := ctx.EventManager().Events().ToABCIEvents() + + expEvents = sdk.Events{ + sdk.NewEvent(types.EventTypeTransfer, + sdk.NewAttribute(types.AttributeKeySender, msg.Sender), + sdk.NewAttribute(types.AttributeKeyReceiver, msg.Receiver), + sdk.NewAttribute(types.AttributeKeyDenom, token.Denom.Path()), + sdk.NewAttribute(types.AttributeKeyAmount, msg.Token.Amount.String()), + sdk.NewAttribute(types.AttributeKeyMemo, msg.Memo), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), + ), + }.ToABCIEvents() + + if tc.expError == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().NotEqual(res.Sequence, uint64(0)) + ibctesting.AssertEvents(&suite.Suite, expEvents, events) + } else { + suite.Require().Nil(res) + suite.Require().True(errors.Is(err, tc.expError) || strings.Contains(err.Error(), tc.expError.Error()), err.Error()) + } + }) + } +} + +// TestUpdateParams tests UpdateParams rpc handler +func (suite *KeeperTestSuite) TestUpdateParams() { + signer := suite.chainA.GetSimApp().TransferKeeper.GetAuthority() + testCases := []struct { + name string + msg *types.MsgUpdateParams + expErr error + }{ + { + "success: valid signer and default params", + types.NewMsgUpdateParams(signer, types.DefaultParams()), + nil, + }, + { + "failure: malformed signer address", + types.NewMsgUpdateParams(ibctesting.InvalidID, types.DefaultParams()), + ibcerrors.ErrUnauthorized, + }, + { + "failure: empty signer address", + types.NewMsgUpdateParams("", types.DefaultParams()), + ibcerrors.ErrUnauthorized, + }, + { + "failure: whitespace signer address", + types.NewMsgUpdateParams(" ", types.DefaultParams()), + ibcerrors.ErrUnauthorized, + }, + { + "failure: unauthorized signer address", + types.NewMsgUpdateParams(ibctesting.TestAccAddress, types.DefaultParams()), + ibcerrors.ErrUnauthorized, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + _, err := suite.chainA.GetSimApp().TransferKeeper.UpdateParams(suite.chainA.GetContext(), tc.msg) + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} diff --git a/modules/apps/transfer/keeper/relay.go b/modules/apps/transfer/keeper/relay.go new file mode 100644 index 0000000..3dd54c5 --- /dev/null +++ b/modules/apps/transfer/keeper/relay.go @@ -0,0 +1,397 @@ +package keeper + +import ( + "fmt" + "strings" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/internal/events" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +// SendTransfer handles transfer sending logic. There are 2 possible cases: +// +// 1. Sender chain is acting as the source zone. The coins are transferred +// to an escrow address (i.e locked) on the sender chain and then transferred +// to the receiving chain through IBC TAO logic. It is expected that the +// receiving chain will mint vouchers to the receiving address. +// +// 2. Sender chain is acting as the sink zone. The coins (vouchers) are burned +// on the sender chain and then transferred to the receiving chain though IBC +// TAO logic. It is expected that the receiving chain, which had previously +// sent the original denomination, will unescrow the fungible token and send +// it to the receiving address. +// +// Another way of thinking of source and sink zones is through the token's +// timeline. Each send to any chain other than the one it was previously +// received from is a movement forwards in the token's timeline. This causes +// trace to be added to the token's history and the destination port and +// destination channel to be prefixed to the denomination. In these instances +// the sender chain is acting as the source zone. When the token is sent back +// to the chain it previously received from, the prefix is removed. This is +// a backwards movement in the token's timeline and the sender chain +// is acting as the sink zone. +// +// Example: +// These steps of transfer occur: A -> B -> C -> A -> C -> B -> A +// +// 1. A -> B : sender chain is source zone. Denom upon receiving: 'B/denom' +// 2. B -> C : sender chain is source zone. Denom upon receiving: 'C/B/denom' +// 3. C -> A : sender chain is source zone. Denom upon receiving: 'A/C/B/denom' +// 4. A -> C : sender chain is sink zone. Denom upon receiving: 'C/B/denom' +// 5. C -> B : sender chain is sink zone. Denom upon receiving: 'B/denom' +// 6. B -> A : sender chain is sink zone. Denom upon receiving: 'denom' +func (k Keeper) SendTransfer( + ctx sdk.Context, + sourcePort, + sourceChannel string, + token types.Token, + sender sdk.AccAddress, +) error { + if !k.GetParams(ctx).SendEnabled { + return types.ErrSendDisabled + } + + if k.IsBlockedAddr(sender) { + return errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "%s is not allowed to send funds", sender) + } + + coin, err := token.ToCoin() + if err != nil { + return err + } + + if err := k.BankKeeper.IsSendEnabledCoins(ctx, coin); err != nil { + return errorsmod.Wrap(types.ErrSendDisabled, err.Error()) + } + + // NOTE: SendTransfer simply sends the denomination as it exists on its own + // chain inside the packet data. The receiving chain will perform denom + // prefixing as necessary. + + // if the denom is prefixed by the port and channel on which we are sending + // the token, then we must be returning the token back to the chain they originated from + if token.Denom.HasPrefix(sourcePort, sourceChannel) { + // transfer the coins to the module account and burn them + if err := k.BankKeeper.SendCoinsFromAccountToModule( + ctx, sender, types.ModuleName, sdk.NewCoins(coin), + ); err != nil { + return err + } + + if err := k.BankKeeper.BurnCoins( + ctx, types.ModuleName, sdk.NewCoins(coin), + ); err != nil { + // NOTE: should not happen as the module account was + // retrieved on the step above and it has enough balance + // to burn. + panic(fmt.Errorf("cannot burn coins after a successful send to a module account: %v", err)) + } + } else { + // obtain the escrow address for the source channel end + escrowAddress := types.GetEscrowAddress(sourcePort, sourceChannel) + if err := k.EscrowCoin(ctx, sender, escrowAddress, coin); err != nil { + return err + } + } + + return nil +} + +// OnRecvPacket processes a cross chain fungible token transfer. +// +// If the sender chain is the source of minted tokens then vouchers will be minted +// and sent to the receiving address. Otherwise if the sender chain is sending +// back tokens this chain originally transferred to it, the tokens are +// unescrowed and sent to the receiving address. +func (k Keeper) OnRecvPacket( + ctx sdk.Context, + data types.InternalTransferRepresentation, + sourcePort string, + sourceChannel string, + destPort string, + destChannel string, +) error { + // validate packet data upon receiving + if err := data.ValidateBasic(); err != nil { + return errorsmod.Wrapf(err, "error validating ICS-20 transfer packet data") + } + + if !k.GetParams(ctx).ReceiveEnabled { + return types.ErrReceiveDisabled + } + + receiver, err := k.addressCodec.StringToBytes(data.Receiver) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "failed to decode receiver address: %s", data.Receiver) + } + + if k.IsBlockedAddr(receiver) { + return errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "%s is not allowed to receive funds", receiver) + } + + token := data.Token + + // parse the transfer amount + transferAmount, ok := sdkmath.NewIntFromString(token.Amount) + if !ok { + return errorsmod.Wrapf(types.ErrInvalidAmount, "unable to parse transfer amount: %s", token.Amount) + } + + // This is the prefix that would have been prefixed to the denomination + // on sender chain IF and only if the token originally came from the + // receiving chain. + // + // NOTE: We use SourcePort and SourceChannel here, because the counterparty + // chain would have prefixed with DestPort and DestChannel when originally + // receiving this token. + if token.Denom.HasPrefix(sourcePort, sourceChannel) { + // sender chain is not the source, unescrow tokens + + // remove prefix added by sender chain + token.Denom.Trace = token.Denom.Trace[1:] + + coin := sdk.NewCoin(token.Denom.IBCDenom(), transferAmount) + + escrowAddress := types.GetEscrowAddress(destPort, destChannel) + if err := k.UnescrowCoin(ctx, escrowAddress, receiver, coin); err != nil { + return err + } + } else { + // sender chain is the source, mint vouchers + + // since SendPacket did not prefix the denomination, we must add the destination port and channel to the trace + trace := []types.Hop{types.NewHop(destPort, destChannel)} + token.Denom.Trace = append(trace, token.Denom.Trace...) + + if !k.HasDenom(ctx, token.Denom.Hash()) { + k.SetDenom(ctx, token.Denom) + } + + voucherDenom := token.Denom.IBCDenom() + if !k.BankKeeper.HasDenomMetaData(ctx, voucherDenom) { + k.SetDenomMetadata(ctx, token.Denom) + } + + events.EmitDenomEvent(ctx, token) + + voucher := sdk.NewCoin(voucherDenom, transferAmount) + + // mint new tokens if the source of the transfer is the same chain + if err := k.BankKeeper.MintCoins( + ctx, types.ModuleName, sdk.NewCoins(voucher), + ); err != nil { + return errorsmod.Wrap(err, "failed to mint IBC tokens") + } + + // send to receiver + moduleAddr := k.AuthKeeper.GetModuleAddress(types.ModuleName) + if err := k.BankKeeper.SendCoins( + ctx, moduleAddr, receiver, sdk.NewCoins(voucher), + ); err != nil { + return errorsmod.Wrapf(err, "failed to send coins to receiver %s", data.Receiver) + } + + } + + // The ibc_module.go module will return the proper ack. + return nil +} + +// OnAcknowledgementPacket responds to the success or failure of a packet acknowledgment +// written on the receiving chain. +// +// If the acknowledgement was a success then nothing occurs. Otherwise, +// if the acknowledgement failed, then the sender is refunded their tokens. +func (k Keeper) OnAcknowledgementPacket( + ctx sdk.Context, + sourcePort string, + sourceChannel string, + data types.InternalTransferRepresentation, + ack channeltypes.Acknowledgement, +) error { + switch ack.Response.(type) { + case *channeltypes.Acknowledgement_Result: + // the acknowledgement succeeded on the receiving chain so nothing + // needs to be executed and no error needs to be returned + return nil + case *channeltypes.Acknowledgement_Error: + if err := k.refundPacketTokens(ctx, sourcePort, sourceChannel, data); err != nil { + return err + } + return nil + default: + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected one of [%T, %T], got %T", channeltypes.Acknowledgement_Result{}, channeltypes.Acknowledgement_Error{}, ack.Response) + } +} + +// OnTimeoutPacket processes a transfer packet timeout by refunding the tokens to the sender +func (k Keeper) OnTimeoutPacket( + ctx sdk.Context, + sourcePort string, + sourceChannel string, + data types.InternalTransferRepresentation, +) error { + return k.refundPacketTokens(ctx, sourcePort, sourceChannel, data) +} + +// refundPacketTokens will unescrow and send back the token back to sender +// if the sending chain was the source chain. Otherwise, the sent token +// were burnt in the original send so new tokens are minted and sent to +// the sending address. +func (k Keeper) refundPacketTokens( + ctx sdk.Context, + sourcePort string, + sourceChannel string, + data types.InternalTransferRepresentation, +) error { + // NOTE: packet data type already checked in handler.go + + sender, err := k.addressCodec.StringToBytes(data.Sender) + if err != nil { + return err + } + if k.IsBlockedAddr(sender) { + return errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "%s is not allowed to receive funds", sender) + } + + // escrow address for unescrowing tokens back to sender + escrowAddress := types.GetEscrowAddress(sourcePort, sourceChannel) + + moduleAccountAddr := k.AuthKeeper.GetModuleAddress(types.ModuleName) + token := data.Token + coin, err := token.ToCoin() + if err != nil { + return err + } + + // if the token we must refund is prefixed by the source port and channel + // then the tokens were burnt when the packet was sent and we must mint new tokens + if token.Denom.HasPrefix(sourcePort, sourceChannel) { + // mint vouchers back to sender + if err := k.BankKeeper.MintCoins( + ctx, types.ModuleName, sdk.NewCoins(coin), + ); err != nil { + return err + } + + if err := k.BankKeeper.SendCoins(ctx, moduleAccountAddr, sender, sdk.NewCoins(coin)); err != nil { + panic(fmt.Errorf("unable to send coins from module to account despite previously minting coins to module account: %v", err)) + } + } else { + if err := k.UnescrowCoin(ctx, escrowAddress, sender, coin); err != nil { + return err + } + } + + return nil +} + +// EscrowCoin will send the given coin from the provided sender to the escrow address. It will also +// update the total escrowed amount by adding the escrowed coin's amount to the current total escrow. +func (k Keeper) EscrowCoin(ctx sdk.Context, sender, escrowAddress sdk.AccAddress, coin sdk.Coin) error { + if err := k.BankKeeper.SendCoins(ctx, sender, escrowAddress, sdk.NewCoins(coin)); err != nil { + // failure is expected for insufficient balances + return err + } + + // track the total amount in escrow keyed by denomination to allow for efficient iteration + currentTotalEscrow := k.GetTotalEscrowForDenom(ctx, coin.GetDenom()) + newTotalEscrow := currentTotalEscrow.Add(coin) + k.SetTotalEscrowForDenom(ctx, newTotalEscrow) + + return nil +} + +// UnescrowCoin will send the given coin from the escrow address to the provided receiver. It will also +// update the total escrow by deducting the unescrowed coin's amount from the current total escrow. +func (k Keeper) UnescrowCoin(ctx sdk.Context, escrowAddress, receiver sdk.AccAddress, coin sdk.Coin) error { + if err := k.BankKeeper.SendCoins(ctx, escrowAddress, receiver, sdk.NewCoins(coin)); err != nil { + // NOTE: this error is only expected to occur given an unexpected bug or a malicious + // counterparty module. The bug may occur in bank or any part of the code that allows + // the escrow address to be drained. A malicious counterparty module could drain the + // escrow address by allowing more tokens to be sent back then were escrowed. + return errorsmod.Wrap(err, "unable to unescrow tokens, this may be caused by a malicious counterparty module or a bug: please open an issue on counterparty module") + } + + // track the total amount in escrow keyed by denomination to allow for efficient iteration + currentTotalEscrow := k.GetTotalEscrowForDenom(ctx, coin.GetDenom()) + newTotalEscrow := currentTotalEscrow.Sub(coin) + k.SetTotalEscrowForDenom(ctx, newTotalEscrow) + + return nil +} + +// tokenFromCoin constructs an IBC token given an SDK coin. +func (k Keeper) TokenFromCoin(ctx sdk.Context, coin sdk.Coin) (types.Token, error) { + // if the coin does not have an IBC denom, return as is + if !strings.HasPrefix(coin.Denom, "ibc/") { + return types.Token{ + Denom: types.NewDenom(coin.Denom), + Amount: coin.Amount.String(), + }, nil + } + + // NOTE: denomination and hex hash correctness checked during msg.ValidateBasic + denom, err := k.GetDenomFromIBCDenom(ctx, coin.Denom) + if err != nil { + return types.Token{}, err + } + + return types.Token{ + Denom: denom, + Amount: coin.Amount.String(), + }, nil +} + +// GetDenomFromIBCDenom returns the `Denom` given the IBC Denom (ibc/{hex hash}) of the denomination. +// The ibcDenom is the hex hash of the denomination prefixed by "ibc/", often referred to as the IBC denom. +func (k Keeper) GetDenomFromIBCDenom(ctx sdk.Context, ibcDenom string) (types.Denom, error) { + hexHash := ibcDenom[len(types.DenomPrefix+"/"):] + + hash, err := types.ParseHexHash(hexHash) + if err != nil { + return types.Denom{}, errorsmod.Wrap(types.ErrInvalidDenomForTransfer, err.Error()) + } + + denom, found := k.GetDenom(ctx, hash) + if !found { + return types.Denom{}, errorsmod.Wrap(types.ErrDenomNotFound, hexHash) + } + + return denom, nil +} + +// Deprecated: usage of this function should be replaced by `Keeper.GetDenomFromIBCDenom` +// DenomPathFromHash returns the full denomination path prefix from an ibc denom with a hash +// component. +func (k Keeper) DenomPathFromHash(ctx sdk.Context, ibcDenom string) (string, error) { + denom, err := k.GetDenomFromIBCDenom(ctx, ibcDenom) + if err != nil { + return "", err + } + + return denom.Path(), nil +} + +// createPacketDataBytesFromVersion creates the packet data bytes to be sent based on the application version. +func createPacketDataBytesFromVersion(appVersion, sender, receiver, memo string, token types.Token) ([]byte, error) { + switch appVersion { + case types.V1: + packetData := types.NewFungibleTokenPacketData(token.Denom.Path(), token.Amount, sender, receiver, memo) + + if err := packetData.ValidateBasic(); err != nil { + return nil, errorsmod.Wrapf(err, "failed to validate %s packet data", types.V1) + } + + return packetData.GetBytes(), nil + default: + return nil, errorsmod.Wrapf(types.ErrInvalidVersion, "app version must be one of %s", types.SupportedVersions) + } +} diff --git a/modules/apps/transfer/keeper/relay_model/account.tla b/modules/apps/transfer/keeper/relay_model/account.tla new file mode 100644 index 0000000..5465007 --- /dev/null +++ b/modules/apps/transfer/keeper/relay_model/account.tla @@ -0,0 +1,36 @@ +-------------------------- MODULE account ---------------------------- + +(** + The accounts interface; please ignore the definition bodies. +*) + +EXTENDS identifiers + +CONSTANT + AccountIds + +\* a non-account +NullAccount == "NullAccount" + +\* All accounts +Accounts == { NullAccount } + +\* Make an escrow account for the given port and channel +MakeEscrowAccount(port, channel) == NullAccount + +\* Make an account from the account id +MakeAccount(accountId) == NullAccount + +\* Type constraints for accounts +AccountTypeOK == + /\ NullAccount \in Accounts + /\ \A p \in Identifiers, c \in Identifiers: + MakeEscrowAccount(p, c) \in Accounts + /\ \A a \in Identifiers: + MakeAccount(a) \in Accounts + +============================================================================= +\* Modification History +\* Last modified Thu Nov 19 18:21:10 CET 2020 by c +\* Last modified Thu Nov 05 14:44:18 CET 2020 by andrey +\* Created Thu Nov 05 13:22:40 CET 2020 by andrey diff --git a/modules/apps/transfer/keeper/relay_model/account_record.tla b/modules/apps/transfer/keeper/relay_model/account_record.tla new file mode 100644 index 0000000..d15d39a --- /dev/null +++ b/modules/apps/transfer/keeper/relay_model/account_record.tla @@ -0,0 +1,46 @@ +-------------------------- MODULE account_record ---------------------------- + +(** + The most basic implementation of accounts, which is a union of normal and escrow accounts + Represented via records. +*) + +EXTENDS identifiers + +CONSTANT + AccountIds + +NullAccount == [ + port |-> NullId, + channel |-> NullId, + id |-> NullId +] + +Accounts == [ + port: Identifiers, + channel: Identifiers, + id: AccountIds +] + +MakeEscrowAccount(port, channel) == [ + port |-> port, + channel |-> channel, + id |-> NullId +] + +MakeAccount(accountId) == [ + port |-> NullId, + channel |-> NullId, + id |-> accountId +] + + +ACCOUNT == INSTANCE account +AccountTypeOK == ACCOUNT!AccountTypeOK + + +============================================================================= +\* Modification History +\* Last modified Thu Nov 19 18:21:46 CET 2020 by c +\* Last modified Thu Nov 05 14:49:10 CET 2020 by andrey +\* Created Thu Nov 05 13:22:40 CET 2020 by andrey diff --git a/modules/apps/transfer/keeper/relay_model/apalache-to-relay-test.json b/modules/apps/transfer/keeper/relay_model/apalache-to-relay-test.json new file mode 100644 index 0000000..d1a8356 --- /dev/null +++ b/modules/apps/transfer/keeper/relay_model/apalache-to-relay-test.json @@ -0,0 +1,100 @@ +{ + "description": "Transforms an Apalache counterexample into the test for ICS20 Token Transfer OnRecvPacket", + "usage": "jsonatr --use apalache-to-recv-test.json --in counterexample.json --out recv-test.json", + "input": [ + { + "name": "history", + "description": "extract history from the last state of Apalache CE", + "kind": "INLINE", + "source": "$.declarations[-2].body.and..[?(@.eq == 'history')].arg.atat..arg.record" + }, + { + "name": "bankRecordToBalance", + "description": "", + "kind": "INLINE", + "source": { + "address": [ + "$.colonGreater.tuple[0]..[?(@.key.str == 'port')].value.str | unwrap", + "$.colonGreater.tuple[0]..[?(@.key.str == 'channel')].value.str | unwrap", + "$.colonGreater.tuple[0]..[?(@.key.str == 'id')].value.str | unwrap" + ], + "denom": [ + "$.colonGreater.tuple[1]..[?(@.key.str == 'port')].value.str | unwrap", + "$.colonGreater.tuple[1]..[?(@.key.str == 'channel')].value.str | unwrap", + "$.colonGreater.tuple[1]..[?(@.key.str == 'denom')].value.str | unwrap" + ], + "amount": "$.arg | unwrap" + } + }, + { + "name": "bankBefore", + "description": "extract bankBefore from the history state", + "kind": "INLINE", + "source": "$..[?(@.key.str == 'bankBefore')].value.atat | unwrap | map(bankRecordToBalance)" + }, + { + "name": "bankAfter", + "description": "extract bankAfter from the history state", + "kind": "INLINE", + "source": "$..[?(@.key.str == 'bankAfter')].value.atat | unwrap | map(bankRecordToBalance)" + }, + { + "name": "packet", + "description": "extract packet from the history state", + "kind": "INLINE", + "source": "$..[?(@.key.str == 'packet')].value.record" + }, + { + "name": "packetData", + "description": "extract bankAfter from the history state", + "kind": "INLINE", + "source": "$..[?(@.key.str == 'data')].value.record" + }, + { + "name": "packetDataDenom", + "description": "extract bankAfter from the history state", + "kind": "INLINE", + "source": "$..[?(@.key.str == 'data')].value.record.[?(@.key.str == 'denomTrace')].value.record" + }, + { + "name": "packetRecord", + "description": "decompose packet", + "kind": "INLINE", + "source": { + "sourceChannel" : "$.[?(@.key.str == 'sourceChannel')].value.str | unwrap", + "sourcePort" : "$.[?(@.key.str == 'sourcePort')].value.str | unwrap", + "destChannel" : "$.[?(@.key.str == 'destChannel')].value.str | unwrap", + "destPort" : "$.[?(@.key.str == 'destPort')].value.str | unwrap", + "data": { + "sender": "$packetData.[?(@.key.str == 'sender')].value.str | unwrap", + "receiver": "$packetData.[?(@.key.str == 'receiver')].value.str | unwrap", + "amount": "$packetData.[?(@.key.str == 'amount')].value | unwrap", + "denom": [ + "$packetDataDenom.[?(@.key.str == 'port')].value.str | unwrap", + "$packetDataDenom.[?(@.key.str == 'channel')].value.str | unwrap", + "$packetDataDenom.[?(@.key.str == 'denom')].value.str | unwrap" + ] + } + } + }, + { + "name": "handler", + "description": "extract handler from the history state", + "kind": "INLINE", + "source": "$..[?(@.key.str == 'handler')].value.str" + }, + { + "name": "historyState", + "description": "decompose single history state", + "kind": "INLINE", + "source": { + "packet": "$packet | unwrap | packetRecord", + "handler": "$handler | unwrap", + "bankBefore": "$bankBefore", + "bankAfter": "$bankAfter", + "error": "$..[?(@.key.str == 'error')].value | unwrap" + } + } + ], + "output": "$history[1:] | map(historyState)" +} \ No newline at end of file diff --git a/modules/apps/transfer/keeper/relay_model/apalache-to-relay-test2.json b/modules/apps/transfer/keeper/relay_model/apalache-to-relay-test2.json new file mode 100644 index 0000000..d3800cb --- /dev/null +++ b/modules/apps/transfer/keeper/relay_model/apalache-to-relay-test2.json @@ -0,0 +1,104 @@ +{ + "description": "Transforms an Apalache counterexample into the test for ICS20 Token Transfer OnRecvPacket", + "usage": "jsonatr --use apalache-to-recv-test.json --in counterexample.json --out recv-test.json", + "input": [ + { + "name": "history", + "description": "extract history from the last state of Apalache CE", + "kind": "INLINE", + "source": "$.declarations[-2].body.and..[?(@.eq == 'history')].arg.atat..arg.record" + }, + { + "name": "bankRecordToBalance", + "description": "", + "kind": "INLINE", + "source": { + "address": [ + "$.colonGreater.tuple[0]..[?(@.key.str == 'port')].value.str | unwrap", + "$.colonGreater.tuple[0]..[?(@.key.str == 'channel')].value.str | unwrap", + "$.colonGreater.tuple[0]..[?(@.key.str == 'id')].value.str | unwrap" + ], + "denom": [ + "$.colonGreater.tuple[1]..[?(@.key.str == 'prefix1')].value..[?(@.key.str == 'port')].value.str | unwrap", + "$.colonGreater.tuple[1]..[?(@.key.str == 'prefix1')].value..[?(@.key.str == 'channel')].value.str | unwrap", + "$.colonGreater.tuple[1]..[?(@.key.str == 'prefix0')].value..[?(@.key.str == 'port')].value.str | unwrap", + "$.colonGreater.tuple[1]..[?(@.key.str == 'prefix0')].value..[?(@.key.str == 'channel')].value.str | unwrap", + "$.colonGreater.tuple[1]..[?(@.key.str == 'denom')].value.str | unwrap" + ], + "amount": "$.arg | unwrap" + } + }, + { + "name": "bankBefore", + "description": "extract bankBefore from the history state", + "kind": "INLINE", + "source": "$..[?(@.key.str == 'bankBefore')].value.atat | unwrap | map(bankRecordToBalance)" + }, + { + "name": "bankAfter", + "description": "extract bankAfter from the history state", + "kind": "INLINE", + "source": "$..[?(@.key.str == 'bankAfter')].value.atat | unwrap | map(bankRecordToBalance)" + }, + { + "name": "packet", + "description": "extract packet from the history state", + "kind": "INLINE", + "source": "$..[?(@.key.str == 'packet')].value.record" + }, + { + "name": "packetData", + "description": "extract bankAfter from the history state", + "kind": "INLINE", + "source": "$..[?(@.key.str == 'data')].value.record" + }, + { + "name": "packetDataDenom", + "description": "extract bankAfter from the history state", + "kind": "INLINE", + "source": "$..[?(@.key.str == 'data')].value.record.[?(@.key.str == 'denomTrace')].value.record" + }, + { + "name": "packetRecord", + "description": "decompose packet", + "kind": "INLINE", + "source": { + "sourceChannel" : "$.[?(@.key.str == 'sourceChannel')].value.str | unwrap", + "sourcePort" : "$.[?(@.key.str == 'sourcePort')].value.str | unwrap", + "destChannel" : "$.[?(@.key.str == 'destChannel')].value.str | unwrap", + "destPort" : "$.[?(@.key.str == 'destPort')].value.str | unwrap", + "data": { + "sender": "$packetData.[?(@.key.str == 'sender')].value.str | unwrap", + "receiver": "$packetData.[?(@.key.str == 'receiver')].value.str | unwrap", + "amount": "$packetData.[?(@.key.str == 'amount')].value | unwrap", + "denom": [ + "$packetDataDenom.[?(@.key.str == 'prefix1')].value..[?(@.key.str == 'port')].value.str | unwrap", + "$packetDataDenom.[?(@.key.str == 'prefix1')].value..[?(@.key.str == 'channel')].value.str | unwrap", + "$packetDataDenom.[?(@.key.str == 'prefix0')].value..[?(@.key.str == 'port')].value.str | unwrap", + "$packetDataDenom.[?(@.key.str == 'prefix0')].value..[?(@.key.str == 'channel')].value.str | unwrap", + "$packetDataDenom.[?(@.key.str == 'denom')].value.str | unwrap" + ] + } + } + }, + { + "name": "handler", + "description": "extract handler from the history state", + "kind": "INLINE", + "source": "$..[?(@.key.str == 'handler')].value.str" + }, + { + "name": "historyState", + "description": "decompose single history state", + "kind": "INLINE", + "source": { + "packet": "$packet | unwrap | packetRecord", + "handler": "$handler | unwrap", + "bankBefore": "$bankBefore", + "bankAfter": "$bankAfter", + "error": "$..[?(@.key.str == 'error')].value | unwrap" + } + } + ], + "output": "$history[1:] | map(historyState)" +} \ No newline at end of file diff --git a/modules/apps/transfer/keeper/relay_model/denom.tla b/modules/apps/transfer/keeper/relay_model/denom.tla new file mode 100644 index 0000000..8cd2973 --- /dev/null +++ b/modules/apps/transfer/keeper/relay_model/denom.tla @@ -0,0 +1,50 @@ +-------------------------- MODULE denom ---------------------------- + +(** + The denomination traces interface; please ignore the definition bodies. +*) + +EXTENDS identifiers + +CONSTANT + Denoms + +\* A non-account +NullDenomTrace == "NullDenomTrace" + +\* All denomination traces +DenomTraces == {NullDenomTrace} + +\* Make a new denomination trace from the port/channel prefix and the basic denom +MakeDenomTrace(port, channel, denom) == NullDenomTrace + +\* Get the denomination trace port +GetPort(trace) == NullId + +\* Get the denomination trace port +GetChannel(trace) == NullId + +\* Get the denomination trace basic denomination +GetDenom(trace) == NullDenomTrace + +\* Is this denomination trace a native denomination, or is it a prefixed trace +\* Note that those cases are exclusive, but not exhaustive +IsNativeDenomTrace(trace) == GetPort(trace) = NullId /\ GetChannel(trace) = NullId +IsPrefixedDenomTrace(trace) == GetPort(trace) /= NullId /\ GetChannel(trace) /= NullId + +DenomTypeOK == + /\ NullDenomTrace \in DenomTraces + /\ \A p \in Identifiers, c \in Identifiers, d \in Denoms: + MakeDenomTrace(p, c, d) \in DenomTraces + /\ \A t \in DenomTraces: + /\ GetPort(t) \in Identifiers + /\ GetChannel(t) \in Identifiers + /\ GetDenom(t) \in DenomTraces + + + + +============================================================================= +\* Modification History +\* Last modified Thu Nov 05 15:49:23 CET 2020 by andrey +\* Created Thu Nov 05 13:22:40 CET 2020 by andrey diff --git a/modules/apps/transfer/keeper/relay_model/denom_record.tla b/modules/apps/transfer/keeper/relay_model/denom_record.tla new file mode 100644 index 0000000..dbea8f7 --- /dev/null +++ b/modules/apps/transfer/keeper/relay_model/denom_record.tla @@ -0,0 +1,53 @@ +-------------------------- MODULE denom_record ---------------------------- + +(** + The most basic implementation of denomination traces that allows only one-step sequences + Represented via records +*) + +EXTENDS identifiers + +CONSTANT + Denoms + +MaxDenomLength == 3 + +DenomTraces == [ + port: Identifiers, + channel: Identifiers, + denom: Denoms +] + +NullDenomTrace == [ + port |-> NullId, + channel |-> NullId, + denom |-> NullId +] + +GetPort(trace) == trace.port +GetChannel(trace) == trace.channel +GetDenom(trace) == trace.denom + +IsNativeDenomTrace(trace) == GetPort(trace) = NullId /\ GetChannel(trace) = NullId /\ GetDenom(trace) /= NullId +IsPrefixedDenomTrace(trace) == GetPort(trace) /= NullId /\ GetChannel(trace) /= NullId /\ GetDenom(trace) /= NullId + +ExtendDenomTrace(port, channel, trace) == + IF GetPort(trace) = NullId /\ GetChannel(trace) = NullId + THEN + [ + port |-> port, + channel |-> channel, + denom |-> trace.denom + ] + ELSE + NullDenomTrace + + +DENOM == INSTANCE denom +DenomTypeOK == DENOM!DenomTypeOK + + +============================================================================= +\* Modification History +\* Last modified Thu Nov 05 16:41:47 CET 2020 by andrey +\* Created Thu Nov 05 13:22:40 CET 2020 by andrey diff --git a/modules/apps/transfer/keeper/relay_model/denom_record2.tla b/modules/apps/transfer/keeper/relay_model/denom_record2.tla new file mode 100644 index 0000000..85139b2 --- /dev/null +++ b/modules/apps/transfer/keeper/relay_model/denom_record2.tla @@ -0,0 +1,114 @@ +-------------------------- MODULE denom_record2 ---------------------------- + +(** + The implementation of denomination traces that allows one- or two-step sequences + Represented via records +*) + +EXTENDS identifiers + +CONSTANT + Denoms + +MaxDenomLength == 5 + +DenomPrefixes == [ + port: Identifiers, + channel: Identifiers +] + +NullDenomPrefix == [ + port |-> NullId, + channel |-> NullId +] + +MakeDenomPrefix(port, channel) == [ + port |-> port, + channel |-> channel +] + +IsValidDenomPrefix(prefix) == + /\ prefix.port /= NullId + /\ prefix.channel /= NullId + +DenomTraces == [ + prefix1: DenomPrefixes, \* the most recent prefix + prefix0: DenomPrefixes, \* the deepest prefix + denom: Denoms +] + +NullDenomTrace == [ + prefix1 |-> NullDenomPrefix, + prefix0 |-> NullDenomPrefix, + denom |-> NullId +] + + +TraceLen(trace) == + IF trace.prefix0 = NullDenomPrefix + THEN 1 + ELSE IF trace.prefix1 = NullDenomPrefix + THEN 3 + ELSE 5 + +LatestPrefix(trace) == + IF trace.prefix0 = NullDenomPrefix + THEN NullDenomPrefix + ELSE IF trace.prefix1 = NullDenomPrefix + THEN trace.prefix0 + ELSE trace.prefix1 + + +ExtendDenomTrace(port, channel, trace) == + IF trace.prefix0 = NullDenomPrefix + THEN [ + prefix1 |-> NullDenomPrefix, + prefix0 |-> MakeDenomPrefix(port, channel), + denom |-> trace.denom + ] + ELSE IF trace.prefix1 = NullDenomPrefix + THEN [ + prefix1 |-> MakeDenomPrefix(port, channel), + prefix0 |-> trace.prefix0, + denom |-> trace.denom + ] + ELSE NullDenomTrace \* can extend only for two steps + +ReduceDenomTrace(trace) == + IF trace.prefix1 /= NullDenomPrefix + THEN [ + prefix1 |-> NullDenomPrefix, + prefix0 |-> trace.prefix0, + denom |-> trace.denom + ] + ELSE IF trace.prefix0 /= NullDenomPrefix + THEN [ + prefix1 |-> NullDenomPrefix, + prefix0 |-> NullDenomPrefix, + denom |-> trace.denom + ] + ELSE NullDenomTrace \* cannot reduce further + +GetPort(trace) == LatestPrefix(trace).port +GetChannel(trace) == LatestPrefix(trace).channel +GetDenom(trace) == trace.denom + +IsValidDenomTrace(trace) == + /\ GetDenom(trace) /= NullId + /\ IF IsValidDenomPrefix(trace.prefix1) + THEN IsValidDenomPrefix(trace.prefix0) + ELSE + /\ trace.prefix1 = NullDenomPrefix + /\ (IsValidDenomPrefix(trace.prefix0) \/ trace.prefix0 = NullDenomPrefix) + +IsNativeDenomTrace(trace) == LatestPrefix(trace) = NullDenomPrefix /\ GetDenom(trace) /= NullId +IsPrefixedDenomTrace(trace) == LatestPrefix(trace) /= NullDenomPrefix /\ GetDenom(trace) /= NullId + +DENOM == INSTANCE denom +DenomTypeOK == DENOM!DenomTypeOK + + +============================================================================= +\* Modification History +\* Last modified Fri Dec 04 10:38:10 CET 2020 by andrey +\* Created Fri Dec 04 10:22:10 CET 2020 by andrey diff --git a/modules/apps/transfer/keeper/relay_model/denom_sequence.tla b/modules/apps/transfer/keeper/relay_model/denom_sequence.tla new file mode 100644 index 0000000..43a49a8 --- /dev/null +++ b/modules/apps/transfer/keeper/relay_model/denom_sequence.tla @@ -0,0 +1,47 @@ +-------------------------- MODULE denom_sequence ---------------------------- + +(** + The implementation of denomination traces via sequences +*) + +EXTENDS Integers, Sequences, identifiers + +CONSTANT + Denoms, + MaxDenomLength + + +a <: b == a +AsAddress(seq) == seq <: Seq(STRING) + +UNROLL_DEFAULT_GenSeq == { AsAddress(<< >>) } +UNROLL_TIMES_GenSeq == 5 + +\* This produces denomination sequences up to the given bound +RECURSIVE GenSeq(_) +GenSeq(n) == + IF n = 0 THEN { AsAddress(<< >>) } + ELSE LET Shorter == GenSeq(n-1) IN + { Append(s,x): x \in Identifiers, s \in Shorter } \union Shorter + +DenomTraces == GenSeq(MaxDenomLength) + +ExtendDenomTrace(port, channel, denom) == AsAddress(<>) \o denom + +GetPort(trace) == trace[1] +GetChannel(trace) == trace[2] +GetDenom(trace) == SubSeq(trace, 3, Len(trace)) + +NullDenomTrace == AsAddress(<< >>) + +IsNativeDenomTrace(trace) == GetPort(trace) = NullId /\ GetChannel(trace) = NullId /\ GetDenom(trace) /= NullDenomTrace +IsPrefixedDenomTrace(trace) == GetPort(trace) /= NullId /\ GetChannel(trace) /= NullId /\ GetDenom(trace) /= NullDenomTrace + +DENOM == INSTANCE denom +DenomTypeOK == DENOM!DenomTypeOK + + +============================================================================= +\* Modification History +\* Last modified Thu Nov 05 15:29:21 CET 2020 by andrey +\* Created Thu Nov 05 13:22:40 CET 2020 by andrey diff --git a/modules/apps/transfer/keeper/relay_model/identifiers.tla b/modules/apps/transfer/keeper/relay_model/identifiers.tla new file mode 100644 index 0000000..18c6675 --- /dev/null +++ b/modules/apps/transfer/keeper/relay_model/identifiers.tla @@ -0,0 +1,10 @@ +-------------------------- MODULE identifiers ---------------------------- + +CONSTANT + Identifiers, + NullId + +============================================================================= +\* Modification History +\* Last modified Thu Nov 05 13:23:12 CET 2020 by andrey +\* Created Thu Nov 05 13:22:40 CET 2020 by andrey diff --git a/modules/apps/transfer/keeper/relay_model/relay.tla b/modules/apps/transfer/keeper/relay_model/relay.tla new file mode 100644 index 0000000..537f1f2 --- /dev/null +++ b/modules/apps/transfer/keeper/relay_model/relay.tla @@ -0,0 +1,278 @@ +-------------------------- MODULE relay ---------------------------- +(** + * A primitive model for account arithmetic and token movement + * of the Cosmos SDK ICS20 Token Transfer + * We completely abstract away many details, + * and want to focus on a minimal spec useful for testing + * + * We also try to make the model modular in that it uses + * denomination traces and accounts via abstract interfaces, + * outlined in denom.tla and account.tla + *) + +EXTENDS Integers, FiniteSets, Sequences, identifiers, denom_record2, account_record + +CONSTANT + MaxAmount + +VARIABLE + error, + bank, + p, \* we want to start with generating single packets, + handler, + history, + count + +Amounts == 0..MaxAmount + +GetSourceEscrowAccount(packet) == MakeEscrowAccount(packet.sourcePort, packet.sourceChannel) +GetDestEscrowAccount(packet) == MakeEscrowAccount(packet.destPort, packet.destChannel) + +FungibleTokenPacketData == [ + sender: AccountIds, + receiver: AccountIds, + denomTrace: DenomTraces, + amount: Amounts +] + +Packets == [ + \* We abstract those packet fields away + \* sequence: uint64 + \* timeoutHeight: Height + \* timeoutTimestamp: uint64 + sourcePort: Identifiers, + sourceChannel: Identifiers, + destPort: Identifiers, + destChannel: Identifiers, + data: FungibleTokenPacketData +] + + +IsSource(packet) == + /\ GetPort(packet.data.denomTrace) = packet.sourcePort + /\ GetChannel(packet.data.denomTrace) = packet.sourceChannel + +\* This function models the port and channel checks that happen when the packet is sent +IsValidSendChannel(packet) == + /\ packet.sourcePort = "transfer" + /\ (packet.sourceChannel = "channel-0" \/ packet.sourceChannel = "channel-1") + /\ packet.destPort = "transfer" + /\ packet.destChannel = "channel-0" + +\* This function models the port and channel checks that happen when relay gets the packet +IsValidRecvChannel(packet) == + /\ packet.sourcePort = "transfer" + /\ packet.sourceChannel = "channel-0" + /\ packet.destPort = "transfer" + /\ (packet.destChannel = "channel-0" \/ packet.destChannel = "channel-1") + + +WellFormedPacket(packet) == + /\ packet.sourcePort /= NullId + /\ packet.sourceChannel /= NullId + /\ packet.destPort /= NullId + /\ packet.destChannel /= NullId + +BankWithAccount(abank, account, denom) == + IF <> \in DOMAIN abank + THEN abank + ELSE [x \in DOMAIN bank \union { <> } + |-> IF x = <> + THEN 0 + ELSE bank[x] ] + +IsKnownDenomTrace(trace) == + \E account \in Accounts : + <> \in DOMAIN bank + + +SendTransferPre(packet, pbank) == + LET data == packet.data + trace == data.denomTrace + sender == data.sender + amount == data.amount + escrow == GetSourceEscrowAccount(packet) + IN + /\ WellFormedPacket(packet) + /\ IsValidSendChannel(packet) + /\ IsNativeDenomTrace(trace) \/ (IsValidDenomTrace(trace) /\ IsKnownDenomTrace(trace)) + /\ data.sender /= NullId + /\ <> \in DOMAIN pbank + /\ \/ amount = 0 \* SendTrasfer actually allows for 0 amount + \/ <> \in DOMAIN pbank /\ bank[MakeAccount(sender), trace] >= amount + +SendTransferNext(packet) == + LET data == packet.data IN + LET denom == GetDenom(data.denomTrace) IN + LET amount == data.amount IN + LET sender == data.sender IN + LET escrow == GetSourceEscrowAccount(packet) IN + LET bankwithescrow == BankWithAccount(bank, escrow, data.denomTrace) IN + IF SendTransferPre(packet,bankwithescrow) + THEN + /\ error' = FALSE + \*/\ IBCsend(chain, packet) + /\ IF ~IsSource(packet) + \* This is how the check is encoded in ICS20 and the implementation. + \* The meaning is "IF denom = AsAddress(NativeDenom)" because of the following argument: + \* observe that due to the disjunction in SendTransferPre(packet), we have + \* ~IsSource(packet) /\ SendTransferPre(packet) => denom = AsAddress(NativeDenom) + THEN + \* tokens are from this chain + \* transfer tokens from sender into escrow account + bank' = [bankwithescrow EXCEPT ![MakeAccount(sender), data.denomTrace] = @ - amount, + ![escrow, data.denomTrace] = @ + amount] + ELSE + \* tokens are from other chain. We forward them. + \* burn sender's money + bank' = [bankwithescrow EXCEPT ![MakeAccount(sender), data.denomTrace] = @ - amount] + ELSE + /\ error' = TRUE + /\ UNCHANGED bank + + +OnRecvPacketPre(packet) == + LET data == packet.data + trace == data.denomTrace + denom == GetDenom(trace) + amount == data.amount + IN + /\ WellFormedPacket(packet) + /\ IsValidRecvChannel(packet) + /\ IsValidDenomTrace(trace) + /\ amount > 0 + \* if there is no receiver account, it is created by the bank + /\ data.receiver /= NullId + /\ IsSource(packet) => + LET escrow == GetDestEscrowAccount(packet) IN + LET denomTrace == ReduceDenomTrace(trace) IN + /\ <> \in DOMAIN bank + /\ bank[escrow, denomTrace] >= amount + + +OnRecvPacketNext(packet) == + LET data == packet.data IN + LET trace == data.denomTrace IN + LET denom == GetDenom(trace) IN + LET amount == data.amount IN + LET receiver == data.receiver IN + /\ IF OnRecvPacketPre(packet) + THEN + \* This condition is necessary so that denomination traces do not exceed the maximum length + /\ (IsSource(packet) \/ TraceLen(trace) < MaxDenomLength) + /\ error' = FALSE + /\ IF IsSource(packet) + THEN + \* transfer from the escrow account to the receiver account + LET denomTrace == ReduceDenomTrace(trace) IN + LET escrow == GetDestEscrowAccount(packet) IN + LET bankwithreceiver == BankWithAccount(bank, MakeAccount(receiver), denomTrace) IN + bank' = [bankwithreceiver + EXCEPT ![MakeAccount(receiver), denomTrace] = @ + amount, + ![escrow, denomTrace] = @ - amount] + ELSE + \* create new tokens with new denomination and transfer it to the receiver account + LET denomTrace == ExtendDenomTrace(packet.destPort, packet.destChannel, trace) IN + LET bankwithreceiver == + BankWithAccount(bank, MakeAccount(receiver), denomTrace) IN + bank' = [bankwithreceiver + EXCEPT ![MakeAccount(receiver), denomTrace] = @ + amount] + ELSE + /\ error' = TRUE + /\ UNCHANGED bank + + +OnTimeoutPacketPre(packet) == + LET data == packet.data + trace == data.denomTrace + denom == GetDenom(trace) + amount == data.amount + IN + /\ WellFormedPacket(packet) + /\ IsValidSendChannel(packet) + /\ IsValidDenomTrace(trace) + /\ data.sender /= NullId + /\ ~IsSource(packet) => + LET escrow == GetSourceEscrowAccount(packet) + IN /\ <> \in DOMAIN bank + /\ bank[escrow, trace] >= amount + + +OnTimeoutPacketNext(packet) == + LET data == packet.data IN + LET trace == data.denomTrace IN + LET denom == GetDenom(data.denomTrace) IN + LET amount == data.amount IN + LET sender == data.sender IN + LET bankwithsender == BankWithAccount(bank, MakeAccount(sender), trace) IN + IF OnTimeoutPacketPre(packet) + THEN + /\ error' = FALSE + /\ IF ~IsSource(packet) + THEN + \* transfer from the escrow account to the sender account + \* LET denomsuffix == SubSeq(denom, 3, Len(denom)) IN + LET escrow == GetSourceEscrowAccount(packet) IN + bank' = [bankwithsender + EXCEPT ![MakeAccount(sender), trace] = @ + amount, + ![escrow, trace] = @ - amount] + ELSE + \* mint back the money + bank' = [bankwithsender EXCEPT ![MakeAccount(sender), trace] = @ + amount] + + ELSE + /\ error' = TRUE + /\ UNCHANGED bank + + +OnAcknowledgementPacketResultNext(packet) == + IF WellFormedPacket(packet) + THEN + /\ error' = FALSE + /\ UNCHANGED bank + ELSE + /\ error' = TRUE + /\ UNCHANGED bank + + +OnAcknowledgementPacketErrorNext(packet) == + OnTimeoutPacketNext(packet) + +Init == + /\ p \in Packets + /\ bank = [ x \in {<>} |-> 0 ] + /\ count = 0 + /\ history = [ + n \in {0} |-> [ + error |-> FALSE, + packet |-> p, + handler |-> "", + bankBefore |-> bank, + bankAfter |-> bank + ] + ] + /\ error = FALSE + /\ handler = "" + +Next == + /\ p' \in Packets + /\ count'= count + 1 + /\ + \/ (SendTransferNext(p) /\ handler' = "SendTransfer") + \/ (OnRecvPacketNext(p) /\ handler' = "OnRecvPacket") + \/ (OnTimeoutPacketNext(p) /\ handler' = "OnTimeoutPacket") + \/ (OnAcknowledgementPacketResultNext(p) /\ handler' = "OnRecvAcknowledgementResult") + \/ (OnAcknowledgementPacketErrorNext(p) /\ handler' = "OnRecvAcknowledgementError") + /\ history' = [ n \in DOMAIN history \union {count'} |-> + IF n = count' THEN + [ packet |-> p, handler |-> handler', error |-> error', bankBefore |-> bank, bankAfter |-> bank' ] + ELSE history[n] + ] + +============================================================================= +\* Modification History +\* Last modified Wed Dec 2 10:15:45 CET 2020 by andrey +\* Last modified Fri Nov 20 12:37:38 CET 2020 by c +\* Last modified Thu Nov 05 20:56:37 CET 2020 by andrey +\* Last modified Fri Oct 30 21:52:38 CET 2020 by widder +\* Created Thu Oct 29 20:45:55 CET 2020 by andrey diff --git a/modules/apps/transfer/keeper/relay_model/relay_tests.tla b/modules/apps/transfer/keeper/relay_model/relay_tests.tla new file mode 100644 index 0000000..ce4e4d3 --- /dev/null +++ b/modules/apps/transfer/keeper/relay_model/relay_tests.tla @@ -0,0 +1,96 @@ +-------------------------- MODULE relay_tests ---------------------------- + +EXTENDS Integers, FiniteSets + +Identifiers == {"", "transfer", "channel-0", "channel-1", "cosmos-hub", "ethereum-hub", "bitcoin-hub"} +NullId == "" +MaxAmount == 5 +Denoms == {"", "atom", "eth", "btc" } +AccountIds == {"", "a1", "a2", "a3" } + +VARIABLES error, bank, p, count, history, handler + +INSTANCE relay + +\************************** Tests ****************************** + +\* Generic test for handler pass +TestHandlerPass(handlerName) == + \E s \in DOMAIN history : + /\ history[s].handler = handlerName + /\ history[s].error = FALSE + /\ history[s].packet.data.amount > 0 + +\* Generic test for handler fail +TestHandlerFail(handlerName) == + \E s \in DOMAIN history : + /\ history[s].handler = handlerName + /\ history[s].error = TRUE + /\ history[s].packet.data.amount > 0 + +TestSendTransferPass == TestHandlerPass("SendTransfer") +TestSendTransferPassInv == ~TestSendTransferPass + +TestSendTransferFail == TestHandlerFail("SendTransfer") +TestSendTransferFailInv == ~TestSendTransferFail + +TestOnRecvPacketPass == TestHandlerPass("OnRecvPacket") +TestOnRecvPacketPassInv == ~TestOnRecvPacketPass + +TestOnRecvPacketFail == TestHandlerFail("OnRecvPacket") +TestOnRecvPacketFailInv == ~TestOnRecvPacketFail + +TestOnTimeoutPass == TestHandlerPass("OnTimeoutPacket") +TestOnTimeoutPassInv == ~TestOnTimeoutPass + +TestOnTimeoutFail == TestHandlerFail("OnTimeoutPacket") +TestOnTimeoutFailInv == ~TestOnTimeoutFail + +TestOnRecvAcknowledgementResultPass == TestHandlerPass("OnRecvAcknowledgementResult") +TestOnRecvAcknowledgementResultPassInv == ~TestOnRecvAcknowledgementResultPass + +TestOnRecvAcknowledgementResultFail == TestHandlerFail("OnRecvAcknowledgementResult") +TestOnRecvAcknowledgementResultFailInv == ~TestOnRecvAcknowledgementResultFail + +TestOnRecvAcknowledgementErrorPass == TestHandlerPass("OnRecvAcknowledgementError") +TestOnRecvAcknowledgementErrorPassInv == ~TestOnRecvAcknowledgementErrorPass + +TestOnRecvAcknowledgementErrorFail == TestHandlerFail("OnRecvAcknowledgementError") +TestOnRecvAcknowledgementErrorFailInv == ~TestOnRecvAcknowledgementErrorFail + +Test5Packets == + count >= 5 + +Test5PacketsInv == ~Test5Packets + +Test5Packets2Different == + /\ count >= 5 + /\ \E s1, s2 \in DOMAIN history : + history[s1].handler /= history[s2].handler + +Test5Packets2DifferentInv == ~Test5Packets2Different + +Test5PacketsAllDifferent == + /\ count >= 5 + /\ \A s1, s2 \in DOMAIN history : + s1 /= s2 => history[s1].handler /= history[s2].handler + +Test5PacketsAllDifferentInv == ~Test5PacketsAllDifferent + +Test5PacketsAllDifferentPass == + /\ Test5PacketsAllDifferent + /\ \A s \in DOMAIN history : + s > 0 => + /\ history[s].error = FALSE + /\ history[s].packet.data.amount > 0 + +Test5PacketsAllDifferentPassInv == ~Test5PacketsAllDifferentPass + +TestUnescrowTokens == + \E s \in DOMAIN history : + /\ IsSource(history[s].packet) + /\ history[s].handler = "OnRecvPacket" + /\ history[s].error = FALSE +TestUnescrowTokensInv == ~TestUnescrowTokens + +============================================================================= diff --git a/modules/apps/transfer/keeper/relay_test.go b/modules/apps/transfer/keeper/relay_test.go new file mode 100644 index 0000000..143b2e8 --- /dev/null +++ b/modules/apps/transfer/keeper/relay_test.go @@ -0,0 +1,1262 @@ +package keeper_test + +import ( + "encoding/hex" + "errors" + "fmt" + "strings" + "time" + + sdkmath "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + banktestutil "github.com/cosmos/cosmos-sdk/x/bank/testutil" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + + transferkeeper "github.com/cosmos/ibc-go/v10/modules/apps/transfer/keeper" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + ibcmock "github.com/cosmos/ibc-go/v10/testing/mock" +) + +var ( + zeroAmount = sdkmath.NewInt(0) + defaultAmount = ibctesting.DefaultCoinAmount +) + +// TestSendTransfer tests sending from chainA to chainB using both coin +// that originate on chainA and coin that originate on chainB. +func (suite *KeeperTestSuite) TestSendTransfer() { + var ( + coin sdk.Coin + path *ibctesting.Path + sender sdk.AccAddress + memo string + expEscrowAmount sdkmath.Int // total amounts in escrow for denom on receiving chain + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success: transfer of native token", + func() {}, + nil, + }, + { + "success: transfer of native token with memo", + func() { + memo = "memo" //nolint:goconst + }, + nil, + }, + { + "success: transfer of IBC token", + func() { + // send IBC token back to chainB + denom := types.NewDenom(ibctesting.TestCoin.Denom, types.NewHop(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)) + coin = sdk.NewCoin(denom.IBCDenom(), ibctesting.TestCoin.Amount) + + expEscrowAmount = zeroAmount + }, + nil, + }, + { + "success: transfer of IBC token with memo", + func() { + // send IBC token back to chainB + denom := types.NewDenom(ibctesting.TestCoin.Denom, types.NewHop(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)) + coin = sdk.NewCoin(denom.IBCDenom(), ibctesting.TestCoin.Amount) + memo = "memo" + + expEscrowAmount = zeroAmount + }, + nil, + }, + { + "success: transfer of entire balance", + func() { + coin = sdk.NewCoin(coin.Denom, types.UnboundedSpendLimit()) + var ok bool + expEscrowAmount, ok = sdkmath.NewIntFromString(ibctesting.DefaultGenesisAccBalance) + suite.Require().True(ok) + }, + nil, + }, + { + "success: transfer of entire spendable balance with vesting account", + func() { + // create vesting account + vestingAccPrivKey := secp256k1.GenPrivKey() + vestingAccAddress := sdk.AccAddress(vestingAccPrivKey.PubKey().Address()) + + vestingCoins := sdk.NewCoins(sdk.NewCoin(coin.Denom, ibctesting.DefaultCoinAmount)) + _, err := suite.chainA.SendMsgs(vestingtypes.NewMsgCreateVestingAccount( + suite.chainA.SenderAccount.GetAddress(), + vestingAccAddress, + vestingCoins, + suite.chainA.GetContext().BlockTime().Add(time.Hour).Unix(), + false, + )) + suite.Require().NoError(err) + sender = vestingAccAddress + + // transfer some spendable coins to vesting account + transferCoin := sdk.NewCoin(coin.Denom, sdkmath.NewInt(42)) + _, err = suite.chainA.SendMsgs(banktypes.NewMsgSend(suite.chainA.SenderAccount.GetAddress(), vestingAccAddress, sdk.NewCoins(transferCoin))) + suite.Require().NoError(err) + + coin = sdk.NewCoin(coin.Denom, types.UnboundedSpendLimit()) + expEscrowAmount = transferCoin.Amount + }, + nil, + }, + { + "failure: no spendable coins for vesting account", + func() { + // create vesting account + vestingAccPrivKey := secp256k1.GenPrivKey() + vestingAccAddress := sdk.AccAddress(vestingAccPrivKey.PubKey().Address()) + + vestingCoin := sdk.NewCoin(coin.Denom, ibctesting.DefaultCoinAmount) + _, err := suite.chainA.SendMsgs(vestingtypes.NewMsgCreateVestingAccount( + suite.chainA.SenderAccount.GetAddress(), + vestingAccAddress, + sdk.NewCoins(vestingCoin), + suite.chainA.GetContext().BlockTime().Add(time.Hour).Unix(), + false, + )) + suite.Require().NoError(err) + sender = vestingAccAddress + + // just to prove that the vesting account has a balance (but not spendable) + vestingAccBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), vestingAccAddress, coin.Denom) + suite.Require().Equal(vestingCoin.Amount.Int64(), vestingAccBalance.Amount.Int64()) + vestinSpendableBalance := suite.chainA.GetSimApp().BankKeeper.SpendableCoins(suite.chainA.GetContext(), vestingAccAddress) + suite.Require().Zero(vestinSpendableBalance.AmountOf(coin.Denom).Int64()) + + coin = sdk.NewCoin(coin.Denom, types.UnboundedSpendLimit()) + }, + types.ErrInvalidAmount, + }, + { + "failure: sender account is blocked", + func() { + sender = suite.chainA.GetSimApp().AccountKeeper.GetModuleAddress(minttypes.ModuleName) + }, + ibcerrors.ErrUnauthorized, + }, + { + "failure: bank send from sender account failed, insufficient balance", + func() { + coin = sdk.NewCoin("randomdenom", defaultAmount) + }, + sdkerrors.ErrInsufficientFunds, + }, + { + "failure: denom trace not found", + func() { + denom := types.NewDenom("randomdenom", types.NewHop(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)) + coin = sdk.NewCoin(denom.IBCDenom(), ibctesting.TestCoin.Amount) + }, + types.ErrDenomNotFound, + }, + { + "failure: bank send from module account failed, insufficient balance", + func() { + denom := types.NewDenom(ibctesting.TestCoin.Denom, types.NewHop(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)) + coin = sdk.NewCoin(denom.IBCDenom(), ibctesting.TestCoin.Amount.Add(sdkmath.NewInt(1))) + }, + sdkerrors.ErrInsufficientFunds, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path.Setup() + + // create IBC token on chainA + transferMsg := types.NewMsgTransfer(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, ibctesting.TestCoin, suite.chainB.SenderAccount.GetAddress().String(), suite.chainA.SenderAccount.GetAddress().String(), suite.chainA.GetTimeoutHeight(), 0, "") + + result, err := suite.chainB.SendMsgs(transferMsg) + suite.Require().NoError(err) // message committed + + packet, err := ibctesting.ParseV1PacketFromEvents(result.Events) + suite.Require().NoError(err) + + err = path.RelayPacket(packet) + suite.Require().NoError(err) + + // Value that can malleated for Transfer we are testing. + coin = ibctesting.TestCoin + sender = suite.chainA.SenderAccount.GetAddress() + memo = "" + expEscrowAmount = defaultAmount + + tc.malleate() + + msg := types.NewMsgTransfer( + path.EndpointA.ChannelConfig.PortID, + path.EndpointA.ChannelID, + coin, + sender.String(), + suite.chainB.SenderAccount.GetAddress().String(), + suite.chainB.GetTimeoutHeight(), 0, // only use timeout height + memo, + ) + + res, err := suite.chainA.GetSimApp().TransferKeeper.Transfer(suite.chainA.GetContext(), msg) + + if tc.expError == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + } else { + suite.Require().Nil(res) + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expError) + + // We do not expect escrowed amounts in error cases. + expEscrowAmount = zeroAmount + } + // Assert amounts escrowed are as expected. + suite.assertEscrowEqual(suite.chainA, coin, expEscrowAmount) + }) + } +} + +func (suite *KeeperTestSuite) TestSendTransferSetsTotalEscrowAmountForSourceIBCToken() { + /* + Given the following flow of tokens: + + chain A (channel 0) -> (channel-0) chain B (channel-1) -> (channel-1) chain A + stake transfer/channel-0/stake transfer/channel-1/transfer/channel-0/stake + ^ + | + SendTransfer + + This test will transfer vouchers of denom "transfer/channel-0/stake" from chain B + to chain A over channel-1 to assert that total escrow amount is stored on chain B + for vouchers of denom "transfer/channel-0/stake" because chain B acts as source + + Set up: + - Two transfer channels between chain A and chain B (channel-0 and channel-1). + - Tokens of native denom "stake" on chain A transferred to chain B over channel-0 + and vouchers minted with denom trace "transfer/channel-0/stake". + + Execute: + - Transfer vouchers of denom trace "transfer/channel-0/stake" from chain B to chain A + over channel-1. + + Assert: + - The vouchers are not of a native denom (because they are of an IBC denom), but chain B + is the source, then the value for total escrow amount should still be stored for the IBC + denom that corresponds to the trace "transfer/channel-0/stake". + */ + + // set up + // 2 transfer channels between chain A and chain B + path1 := ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path1.Setup() + + path2 := ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path2.Setup() + + // create IBC token on chain B with denom trace "transfer/channel-0/stake" + coin := ibctesting.TestCoin + transferMsg := types.NewMsgTransfer( + path1.EndpointA.ChannelConfig.PortID, + path1.EndpointA.ChannelID, + coin, + suite.chainA.SenderAccount.GetAddress().String(), + suite.chainB.SenderAccount.GetAddress().String(), + suite.chainB.GetTimeoutHeight(), 0, "", + ) + result, err := suite.chainA.SendMsgs(transferMsg) + suite.Require().NoError(err) // message committed + + packet, err := ibctesting.ParseV1PacketFromEvents(result.Events) + suite.Require().NoError(err) + + err = path1.RelayPacket(packet) + suite.Require().NoError(err) + + // execute + denom := types.NewDenom(sdk.DefaultBondDenom, types.NewHop(path1.EndpointB.ChannelConfig.PortID, path1.EndpointB.ChannelID)) + coin = sdk.NewCoin(denom.IBCDenom(), defaultAmount) + msg := types.NewMsgTransfer( + path2.EndpointB.ChannelConfig.PortID, + path2.EndpointB.ChannelID, + coin, + suite.chainB.SenderAccount.GetAddress().String(), + suite.chainA.SenderAccount.GetAddress().String(), + suite.chainA.GetTimeoutHeight(), 0, "", + ) + + res, err := suite.chainB.GetSimApp().TransferKeeper.Transfer(suite.chainB.GetContext(), msg) + suite.Require().NoError(err) + suite.Require().NotNil(res) + + // check total amount in escrow of sent token on sending chain + totalEscrow := suite.chainB.GetSimApp().TransferKeeper.GetTotalEscrowForDenom(suite.chainB.GetContext(), coin.GetDenom()) + suite.Require().Equal(defaultAmount, totalEscrow.Amount) +} + +// TestOnRecvPacket_ReceiverIsNotSource tests receiving on chainB a coin that +// originates on chainA. The bulk of the testing occurs in the test case for +// loop since setup is intensive for all cases. The malleate function allows +// for testing invalid cases. +func (suite *KeeperTestSuite) TestOnRecvPacket_ReceiverIsNotSource() { + var packetData types.InternalTransferRepresentation + + testCases := []struct { + msg string + malleate func() + expError error + }{ + { + "success: receive", + func() {}, + nil, + }, + { + "success: receive with memo", + func() { + packetData.Memo = "memo" + }, + nil, + }, + { + "success: receive with hex receiver address", + func() { + suite.chainB.GetSimApp().TransferKeeper.SetAddressCodec(ibcmock.TestAddressCodec{}) + + receiver := sdk.MustAccAddressFromBech32(packetData.Receiver) + packetData.Receiver = hex.EncodeToString(receiver.Bytes()) + }, + nil, + }, + { + "failure: mint zero coin", + func() { + packetData.Token.Amount = zeroAmount.String() + }, + types.ErrInvalidAmount, + }, + { + "failure: receiver is module account", + func() { + packetData.Receiver = suite.chainB.GetSimApp().AccountKeeper.GetModuleAddress(minttypes.ModuleName).String() + }, + ibcerrors.ErrUnauthorized, + }, + { + "failure: receiver is invalid", + func() { + packetData.Receiver = "invalid-address" + }, + ibcerrors.ErrInvalidAddress, + }, + { + "failure: receive is disabled", + func() { + suite.chainB.GetSimApp().TransferKeeper.SetParams(suite.chainB.GetContext(), + types.Params{ + ReceiveEnabled: false, + }) + }, + types.ErrReceiveDisabled, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + path := ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path.Setup() + + receiver := suite.chainB.SenderAccount.GetAddress().String() // must be explicitly changed in malleate + + // send coins from chainA to chainB + transferMsg := types.NewMsgTransfer(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, ibctesting.TestCoin, suite.chainA.SenderAccount.GetAddress().String(), receiver, clienttypes.NewHeight(1, 110), 0, "") + _, err := suite.chainA.SendMsgs(transferMsg) + suite.Require().NoError(err) // message committed + + token := types.Token{Denom: types.NewDenom(transferMsg.Token.Denom), Amount: transferMsg.Token.Amount.String()} + packetData = types.NewInternalTransferRepresentation(token, suite.chainA.SenderAccount.GetAddress().String(), receiver, "") + sourcePort := path.EndpointA.ChannelConfig.PortID + sourceChannel := path.EndpointA.ChannelID + destinationPort := path.EndpointB.ChannelConfig.PortID + destinationChannel := path.EndpointB.ChannelID + + tc.malleate() + + denom := types.NewDenom(token.Denom.Base, types.NewHop(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)) + + err = suite.chainB.GetSimApp().TransferKeeper.OnRecvPacket( + suite.chainB.GetContext(), + packetData, + sourcePort, + sourceChannel, + destinationPort, + destinationChannel, + ) + + if tc.expError == nil { + suite.Require().NoError(err) + + // Check denom metadata for of tokens received on chain B. + actualMetadata, found := suite.chainB.GetSimApp().BankKeeper.GetDenomMetaData(suite.chainB.GetContext(), denom.IBCDenom()) + + suite.Require().True(found) + suite.Require().Equal(metadataFromDenom(denom), actualMetadata) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expError) + + // Check denom metadata absence for cases where recv fails. + _, found := suite.chainB.GetSimApp().BankKeeper.GetDenomMetaData(suite.chainB.GetContext(), denom.IBCDenom()) + + suite.Require().False(found) + } + }) + } +} + +// TestOnRecvPacket_ReceiverIsSource tests receiving on chainB a coin that +// originated on chainB, but was previously transferred to chainA. The bulk +// of the testing occurs in the test case for loop since setup is intensive +// for all cases. The malleate function allows for testing invalid cases. +func (suite *KeeperTestSuite) TestOnRecvPacket_ReceiverIsSource() { + var ( + packetData types.InternalTransferRepresentation + expEscrowAmount sdkmath.Int // total amount in escrow for denom on receiving chain + ) + + testCases := []struct { + msg string + malleate func() + expError error + }{ + { + "successful receive", + func() {}, + nil, + }, + { + "successful receive with memo", + func() { + packetData.Memo = "memo" + }, + nil, + }, + { + "successful receive of half the amount", + func() { + packetData.Token.Amount = sdkmath.NewInt(50).String() + // expect 50 remaining + expEscrowAmount = sdkmath.NewInt(50) + }, + nil, + }, + { + "failure: empty coin", + func() { + packetData.Token.Amount = zeroAmount.String() + }, + types.ErrInvalidAmount, + }, + { + "failure: tries to unescrow more tokens than allowed", + func() { + packetData.Token.Amount = sdkmath.NewInt(1000000).String() + }, + sdkerrors.ErrInsufficientFunds, + }, + { + "failure: empty denom", + func() { + packetData.Token.Denom = types.Denom{} + }, + types.ErrInvalidDenomForTransfer, + }, + { + "failure: invalid receiver address", + func() { + packetData.Receiver = "gaia1scqhwpgsmr6vmztaa7suurfl52my6nd2kmrudl" + }, + errors.New("failed to decode receiver address"), + }, + { + "failure: receiver is module account", + func() { + packetData.Receiver = suite.chainB.GetSimApp().AccountKeeper.GetModuleAddress(minttypes.ModuleName).String() + }, + ibcerrors.ErrUnauthorized, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + path := ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path.Setup() + + receiver := suite.chainB.SenderAccount.GetAddress().String() // must be explicitly changed in malleate + expEscrowAmount = zeroAmount // total amount in escrow of voucher denom on receiving chain + + // send coins from chainA to chainB, receive them, acknowledge them + transferMsg := types.NewMsgTransfer(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, ibctesting.TestCoin, suite.chainA.SenderAccount.GetAddress().String(), receiver, clienttypes.NewHeight(1, 110), 0, "") + _, err := suite.chainA.SendMsgs(transferMsg) + suite.Require().NoError(err) // message committed + + token := types.Token{Denom: types.NewDenom(transferMsg.Token.Denom, types.NewHop(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)), Amount: transferMsg.Token.Amount.String()} + packetData = types.NewInternalTransferRepresentation(token, suite.chainA.SenderAccount.GetAddress().String(), receiver, "") + sourcePort := path.EndpointB.ChannelConfig.PortID + sourceChannel := path.EndpointB.ChannelID + destinationPort := path.EndpointA.ChannelConfig.PortID + destinationChannel := path.EndpointA.ChannelID + + tc.malleate() + + err = suite.chainA.GetSimApp().TransferKeeper.OnRecvPacket( + suite.chainA.GetContext(), + packetData, + sourcePort, + sourceChannel, + destinationPort, + destinationChannel, + ) + + if tc.expError == nil { + suite.Require().NoError(err) + + _, found := suite.chainA.GetSimApp().BankKeeper.GetDenomMetaData(suite.chainA.GetContext(), sdk.DefaultBondDenom) + suite.Require().False(found) + } else { + suite.Require().Error(err) + suite.Require().ErrorContains(err, tc.expError.Error()) + + // Expect escrowed amount to stay same on failure. + expEscrowAmount = defaultAmount + } + + // Assert amounts escrowed are as expected, we do not malleate amount escrowed in initial transfer. + suite.assertEscrowEqual(suite.chainA, ibctesting.TestCoin, expEscrowAmount) + }) + } +} + +func (suite *KeeperTestSuite) TestOnRecvPacketSetsTotalEscrowAmountForSourceIBCToken() { + /* + Given the following flow of tokens: + + chain A (channel 0) -> (channel-0) chain B (channel-1) -> (channel-1) chain A (channel-1) -> (channel-1) chain B + stake transfer/channel-0/stake transfer/channel-1/transfer/channel-0/stake transfer/channel-0/stake + ^ + | + OnRecvPacket + + This test will assert that on receiving vouchers of denom "transfer/channel-0/stake" + on chain B the total escrow amount is updated on because chain B acted as source + when vouchers were transferred to chain A over channel-1. + + Setup: + - Two transfer channels between chain A and chain B. + - Vouchers of denom trace "transfer/channel-0/stake" on chain B are in escrow + account for port ID transfer and channel ID channel-1. + + Execute: + - Receive vouchers of denom trace "transfer/channel-0/stake" from chain A to chain B + over channel-1. + + Assert: + - The vouchers are not of a native denom (because they are of an IBC denom), but chain B + is the source, then the value for total escrow amount should still be updated for the IBC + denom that corresponds to the trace "transfer/channel-0/stake" when the vouchers are + received back on chain B. + */ + + amount := defaultAmount + + // setup + // 2 transfer channels between chain A and chain B + path1 := ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path1.Setup() + + path2 := ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path2.Setup() + + // denom path: {transfer/channel-1/transfer/channel-0} + denom := types.NewDenom( + sdk.DefaultBondDenom, + types.NewHop(path2.EndpointA.ChannelConfig.PortID, path2.EndpointA.ChannelID), + types.NewHop(path1.EndpointB.ChannelConfig.PortID, path1.EndpointB.ChannelID), + ) + + data := types.NewInternalTransferRepresentation( + types.Token{ + Denom: denom, + Amount: amount.String(), + }, suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), "") + sourcePort := path2.EndpointA.ChannelConfig.PortID + sourceChannel := path2.EndpointA.ChannelID + destinationPort := path2.EndpointB.ChannelConfig.PortID + destinationChannel := path2.EndpointB.ChannelID + + // fund escrow account for transfer and channel-1 on chain B + // denom path: transfer/channel-0 + denom = types.NewDenom( + sdk.DefaultBondDenom, + types.NewHop(path1.EndpointB.ChannelConfig.PortID, path1.EndpointB.ChannelID), + ) + + escrowAddress := types.GetEscrowAddress(path2.EndpointB.ChannelConfig.PortID, path2.EndpointB.ChannelID) + coin := sdk.NewCoin(denom.IBCDenom(), amount) + suite.Require().NoError( + banktestutil.FundAccount( + suite.chainB.GetContext(), + suite.chainB.GetSimApp().BankKeeper, + escrowAddress, + sdk.NewCoins(coin), + ), + ) + + suite.chainB.GetSimApp().TransferKeeper.SetTotalEscrowForDenom(suite.chainB.GetContext(), coin) + totalEscrowChainB := suite.chainB.GetSimApp().TransferKeeper.GetTotalEscrowForDenom(suite.chainB.GetContext(), coin.GetDenom()) + suite.Require().Equal(defaultAmount, totalEscrowChainB.Amount) + + // execute onRecvPacket, when chaninB receives the source token the escrow amount should decrease + err := suite.chainB.GetSimApp().TransferKeeper.OnRecvPacket( + suite.chainB.GetContext(), + data, + sourcePort, + sourceChannel, + destinationPort, + destinationChannel, + ) + suite.Require().NoError(err) + + // check total amount in escrow of sent token on receiving chain + totalEscrowChainB = suite.chainB.GetSimApp().TransferKeeper.GetTotalEscrowForDenom(suite.chainB.GetContext(), coin.GetDenom()) + suite.Require().Equal(zeroAmount, totalEscrowChainB.Amount) +} + +// TestOnAcknowledgementPacket tests that successful acknowledgement is a no-op +// and failure acknowledment leads to refund when attempting to send from chainA +// to chainB. If sender is source then the denomination being refunded has no +// trace. +func (suite *KeeperTestSuite) TestOnAcknowledgementPacket() { + var ( + successAck = channeltypes.NewResultAcknowledgement([]byte{byte(1)}) + failedAck = channeltypes.NewErrorAcknowledgement(errors.New("failed packet transfer")) + denom types.Denom + amount sdkmath.Int + path *ibctesting.Path + expEscrowAmount sdkmath.Int + ) + + testCases := []struct { + msg string + ack channeltypes.Acknowledgement + malleate func() + expError error + }{ + { + "success ack: no-op", + successAck, + func() { + denom = types.NewDenom(sdk.DefaultBondDenom, types.NewHop(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)) + }, + nil, + }, + { + "failed ack: successful refund of native coin", + failedAck, + func() { + escrow := types.GetEscrowAddress(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + denom = types.NewDenom(sdk.DefaultBondDenom) + coin := sdk.NewCoin(sdk.DefaultBondDenom, amount) + + suite.Require().NoError(banktestutil.FundAccount(suite.chainA.GetContext(), suite.chainA.GetSimApp().BankKeeper, escrow, sdk.NewCoins(coin))) + + // set escrow amount that would have been stored after successful execution of MsgTransfer + suite.chainA.GetSimApp().TransferKeeper.SetTotalEscrowForDenom(suite.chainA.GetContext(), sdk.NewCoin(sdk.DefaultBondDenom, amount)) + }, + nil, + }, + { + "failed ack: successful refund of IBC voucher", + failedAck, + func() { + escrow := types.GetEscrowAddress(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + denom = types.NewDenom(sdk.DefaultBondDenom, types.NewHop(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)) + coin := sdk.NewCoin(denom.IBCDenom(), amount) + + suite.Require().NoError(banktestutil.FundAccount(suite.chainA.GetContext(), suite.chainA.GetSimApp().BankKeeper, escrow, sdk.NewCoins(coin))) + }, + nil, + }, + { + "failed ack: funds cannot be refunded because escrow account has zero balance", + failedAck, + func() { + denom = types.NewDenom(sdk.DefaultBondDenom) + + // set escrow amount that would have been stored after successful execution of MsgTransfer + suite.chainA.GetSimApp().TransferKeeper.SetTotalEscrowForDenom(suite.chainA.GetContext(), sdk.NewCoin(sdk.DefaultBondDenom, amount)) + expEscrowAmount = defaultAmount + }, + sdkerrors.ErrInsufficientFunds, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + path = ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path.Setup() + + amount = defaultAmount // must be explicitly changed + expEscrowAmount = zeroAmount + + tc.malleate() + + data := types.NewInternalTransferRepresentation( + types.Token{ + Denom: denom, + Amount: amount.String(), + }, suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), "") + sourcePort := path.EndpointA.ChannelConfig.PortID + sourceChannel := path.EndpointA.ChannelID + preAcknowledgementBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), denom.IBCDenom()) + + err := suite.chainA.GetSimApp().TransferKeeper.OnAcknowledgementPacket(suite.chainA.GetContext(), sourcePort, sourceChannel, data, tc.ack) + + // check total amount in escrow of sent token denom on sending chain + totalEscrow := suite.chainA.GetSimApp().TransferKeeper.GetTotalEscrowForDenom(suite.chainA.GetContext(), denom.IBCDenom()) + suite.Require().Equal(expEscrowAmount, totalEscrow.Amount) + + if tc.expError == nil { + suite.Require().NoError(err) + postAcknowledgementBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), denom.IBCDenom()) + deltaAmount := postAcknowledgementBalance.Amount.Sub(preAcknowledgementBalance.Amount) + + if tc.ack.Success() { + suite.Require().Equal(int64(0), deltaAmount.Int64(), "successful ack changed balance") + } else { + suite.Require().Equal(amount, deltaAmount, "failed ack did not trigger refund") + } + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +func (suite *KeeperTestSuite) TestOnAcknowledgementPacketSetsTotalEscrowAmountForSourceIBCToken() { + /* + This test is testing the following scenario. Given tokens travelling like this: + + chain A (channel 0) -> (channel-0) chain B (channel-1) -> (channel-1) chain A (channel-1) + stake transfer/channel-0/stake transfer/channel-1/transfer/channel-0/stake + ^ + | + OnAcknowledgePacket + + We want to assert that on failed acknowledgment of vouchers sent with denom trace + "transfer/channel-0/stake" on chain B the total escrow amount is updated. + + Set up: + - Two transfer channels between chain A and chain B. + - Vouckers of denom "transfer/channel-0/stake" on chain B are in escrow + account for port ID transfer and channel ID channel-1. + + Execute: + - Acknowledge vouchers of denom trace "transfer/channel-0/stake" sent from chain B + to chain B over channel-1. + + Assert: + - The vouchers are not of a native denom (because they are of an IBC denom), but chain B + is the source, then the value for total escrow amount should still be updated for the IBC + denom that corresponds to the trace "transfer/channel-0/stake" when processing the failed + acknowledgement. + */ + + amount := defaultAmount + ack := channeltypes.NewErrorAcknowledgement(errors.New("failed packet transfer")) + + // set up + // 2 transfer channels between chain A and chain B + path1 := ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path1.Setup() + + path2 := ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path2.Setup() + + // fund escrow account for transfer and channel-1 on chain B + // denom path: transfer/channel-0 + denom := types.NewDenom(sdk.DefaultBondDenom, types.NewHop(path1.EndpointB.ChannelConfig.PortID, path1.EndpointB.ChannelID)) + + escrowAddress := types.GetEscrowAddress(path2.EndpointB.ChannelConfig.PortID, path2.EndpointB.ChannelID) + coin := sdk.NewCoin(denom.IBCDenom(), amount) + suite.Require().NoError( + banktestutil.FundAccount( + suite.chainB.GetContext(), + suite.chainB.GetSimApp().BankKeeper, + escrowAddress, + sdk.NewCoins(coin), + ), + ) + + data := types.NewInternalTransferRepresentation( + types.Token{ + Denom: denom, + Amount: amount.String(), + }, + suite.chainB.SenderAccount.GetAddress().String(), + suite.chainA.SenderAccount.GetAddress().String(), + "", + ) + sourcePort := path2.EndpointB.ChannelConfig.PortID + sourceChannel := path2.EndpointB.ChannelID + + suite.chainB.GetSimApp().TransferKeeper.SetTotalEscrowForDenom(suite.chainB.GetContext(), coin) + totalEscrowChainB := suite.chainB.GetSimApp().TransferKeeper.GetTotalEscrowForDenom(suite.chainB.GetContext(), coin.GetDenom()) + suite.Require().Equal(defaultAmount, totalEscrowChainB.Amount) + + err := suite.chainB.GetSimApp().TransferKeeper.OnAcknowledgementPacket(suite.chainB.GetContext(), sourcePort, sourceChannel, data, ack) + suite.Require().NoError(err) + + // check total amount in escrow of sent token on sending chain + totalEscrowChainB = suite.chainB.GetSimApp().TransferKeeper.GetTotalEscrowForDenom(suite.chainB.GetContext(), coin.GetDenom()) + suite.Require().Equal(zeroAmount, totalEscrowChainB.Amount) +} + +// TestOnTimeoutPacket tests private refundPacket function since it is a simple +// wrapper over it. The actual timeout does not matter since IBC core logic +// is not being tested. The test is timing out a send from chainA to chainB +// so the refunds are occurring on chainA. +func (suite *KeeperTestSuite) TestOnTimeoutPacket() { + var ( + path *ibctesting.Path + amount string + sender string + denom types.Denom + expEscrowAmount sdkmath.Int + ) + + testCases := []struct { + msg string + malleate func() + expError error + }{ + { + "successful timeout: sender is source of coin", + func() { + escrow := types.GetEscrowAddress(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + denom = types.NewDenom(sdk.DefaultBondDenom) + coinAmount, ok := sdkmath.NewIntFromString(amount) + suite.Require().True(ok) + coin := sdk.NewCoin(denom.IBCDenom(), coinAmount) + expEscrowAmount = zeroAmount + + // funds the escrow account to have balance + suite.Require().NoError(banktestutil.FundAccount(suite.chainA.GetContext(), suite.chainA.GetSimApp().BankKeeper, escrow, sdk.NewCoins(coin))) + // set escrow amount that would have been stored after successful execution of MsgTransfer + suite.chainA.GetSimApp().TransferKeeper.SetTotalEscrowForDenom(suite.chainA.GetContext(), coin) + }, + nil, + }, + { + "successful timeout: sender is not source of coin", + func() { + escrow := types.GetEscrowAddress(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + denom = types.NewDenom(sdk.DefaultBondDenom, types.NewHop(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)) + coinAmount, ok := sdkmath.NewIntFromString(amount) + suite.Require().True(ok) + coin := sdk.NewCoin(denom.IBCDenom(), coinAmount) + expEscrowAmount = zeroAmount + + // funds the escrow account to have balance + suite.Require().NoError(banktestutil.FundAccount(suite.chainA.GetContext(), suite.chainA.GetSimApp().BankKeeper, escrow, sdk.NewCoins(coin))) + }, + nil, + }, + { + "failure: funds cannot be refunded because escrow account has no balance for non-native coin", + func() { + denom = types.NewDenom("bitcoin") + var ok bool + expEscrowAmount, ok = sdkmath.NewIntFromString(amount) + suite.Require().True(ok) + + // set escrow amount that would have been stored after successful execution of MsgTransfer + suite.chainA.GetSimApp().TransferKeeper.SetTotalEscrowForDenom(suite.chainA.GetContext(), sdk.NewCoin(denom.IBCDenom(), expEscrowAmount)) + }, + sdkerrors.ErrInsufficientFunds, + }, + { + "failure: funds cannot be refunded because escrow account has no balance for native coin", + func() { + denom = types.NewDenom(sdk.DefaultBondDenom) + var ok bool + expEscrowAmount, ok = sdkmath.NewIntFromString(amount) + suite.Require().True(ok) + + // set escrow amount that would have been stored after successful execution of MsgTransfer + suite.chainA.GetSimApp().TransferKeeper.SetTotalEscrowForDenom(suite.chainA.GetContext(), sdk.NewCoin(denom.IBCDenom(), expEscrowAmount)) + }, + sdkerrors.ErrInsufficientFunds, + }, + { + "failure: cannot mint because sender address is invalid", + func() { + denom = types.NewDenom(sdk.DefaultBondDenom) + amount = sdkmath.OneInt().String() + sender = "invalid address" + }, + errors.New("decoding bech32 failed"), + }, + { + "failure: invalid amount", + func() { + denom = types.NewDenom(sdk.DefaultBondDenom) + amount = "invalid" + }, + types.ErrInvalidAmount, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + path = ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path.Setup() + + amount = defaultAmount.String() // must be explicitly changed + sender = suite.chainA.SenderAccount.GetAddress().String() + expEscrowAmount = zeroAmount + + tc.malleate() + + data := types.NewInternalTransferRepresentation( + types.Token{ + Denom: denom, + Amount: amount, + }, sender, suite.chainB.SenderAccount.GetAddress().String(), "") + sourcePort := path.EndpointA.ChannelConfig.PortID + sourceChannel := path.EndpointA.ChannelID + preTimeoutBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), denom.IBCDenom()) + + err := suite.chainA.GetSimApp().TransferKeeper.OnTimeoutPacket(suite.chainA.GetContext(), sourcePort, sourceChannel, data) + + postTimeoutBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), denom.IBCDenom()) + deltaAmount := postTimeoutBalance.Amount.Sub(preTimeoutBalance.Amount) + + // check total amount in escrow of sent token denom on sending chain + totalEscrow := suite.chainA.GetSimApp().TransferKeeper.GetTotalEscrowForDenom(suite.chainA.GetContext(), denom.IBCDenom()) + suite.Require().Equal(expEscrowAmount, totalEscrow.Amount) + + if tc.expError == nil { + suite.Require().NoError(err) + amountParsed, ok := sdkmath.NewIntFromString(amount) + suite.Require().True(ok) + suite.Require().Equal(amountParsed, deltaAmount, "successful timeout did not trigger refund") + } else { + suite.Require().Error(err) + suite.Require().ErrorContains(err, tc.expError.Error()) + } + }) + } +} + +func (suite *KeeperTestSuite) TestOnTimeoutPacketSetsTotalEscrowAmountForSourceIBCToken() { + /* + Given the following flow of tokens: + + chain A (channel 0) -> (channel-0) chain B (channel-1) -> (channel-1) chain A (channel-1) + stake transfer/channel-0/stake transfer/channel-1/transfer/channel-0/stake + ^ + | + OnTimeoutPacket + + We want to assert that on timeout of vouchers sent with denom trace + "transfer/channel-0/stake" on chain B the total escrow amount is updated. + + Set up: + - Two transfer channels between chain A and chain B. + - Vouckers of denom "transfer/channel-0/stake" on chain B are in escrow + account for port ID transfer and channel ID channel-1. + + Execute: + - Timeout vouchers of denom trace "transfer/channel-0/stake" sent from chain B + to chain B over channel-1. + + Assert: + - The vouchers are not of a native denom (because they are of an IBC denom), but chain B + is the source, then the value for total escrow amount should still be updated for the IBC + denom that corresponds to the trace "transfer/channel-0/stake" when processing the timeout. + */ + + amount := defaultAmount + + // set up + // 2 transfer channels between chain A and chain B + path1 := ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path1.Setup() + + path2 := ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path2.Setup() + + // fund escrow account for transfer and channel-1 on chain B + denom := types.NewDenom(sdk.DefaultBondDenom, types.NewHop(path1.EndpointB.ChannelConfig.PortID, path1.EndpointB.ChannelID)) + + escrowAddress := types.GetEscrowAddress(path2.EndpointB.ChannelConfig.PortID, path2.EndpointB.ChannelID) + coin := sdk.NewCoin(denom.IBCDenom(), amount) + suite.Require().NoError( + banktestutil.FundAccount( + suite.chainB.GetContext(), + suite.chainB.GetSimApp().BankKeeper, + escrowAddress, + sdk.NewCoins(coin), + ), + ) + + data := types.NewInternalTransferRepresentation( + types.Token{ + Denom: denom, + Amount: amount.String(), + }, suite.chainB.SenderAccount.GetAddress().String(), suite.chainA.SenderAccount.GetAddress().String(), "") + sourcePort := path2.EndpointB.ChannelConfig.PortID + sourceChannel := path2.EndpointB.ChannelID + + suite.chainB.GetSimApp().TransferKeeper.SetTotalEscrowForDenom(suite.chainB.GetContext(), coin) + totalEscrowChainB := suite.chainB.GetSimApp().TransferKeeper.GetTotalEscrowForDenom(suite.chainB.GetContext(), coin.GetDenom()) + suite.Require().Equal(defaultAmount, totalEscrowChainB.Amount) + + err := suite.chainB.GetSimApp().TransferKeeper.OnTimeoutPacket(suite.chainB.GetContext(), sourcePort, sourceChannel, data) + suite.Require().NoError(err) + + // check total amount in escrow of sent token on sending chain + totalEscrowChainB = suite.chainB.GetSimApp().TransferKeeper.GetTotalEscrowForDenom(suite.chainB.GetContext(), coin.GetDenom()) + suite.Require().Equal(zeroAmount, totalEscrowChainB.Amount) +} + +func (suite *KeeperTestSuite) TestPacketForwardsCompatibility() { + // We are testing a scenario where a packet in the future has a new populated + // field called "new_field". And this packet is being sent to this module which + // doesn't have this field in the packet data. The module should be able to handle + // this packet without any issues. + + // the test also ensures that an ack is written for any malformed or bad packet data. + + var packetData []byte + var path *ibctesting.Path + + testCases := []struct { + msg string + malleate func() + expError error + expAckError error + }{ + { + "success: no new field with memo", + func() { + jsonString := fmt.Sprintf(`{"denom":"denom","amount":"100","sender":"%s","receiver":"%s","memo":"memo"}`, suite.chainB.SenderAccount.GetAddress().String(), suite.chainA.SenderAccount.GetAddress().String()) + packetData = []byte(jsonString) + }, + nil, + nil, + }, + { + "success: no new field without memo", + func() { + jsonString := fmt.Sprintf(`{"denom":"denom","amount":"100","sender":"%s","receiver":"%s"}`, suite.chainB.SenderAccount.GetAddress().String(), suite.chainA.SenderAccount.GetAddress().String()) + packetData = []byte(jsonString) + }, + nil, + nil, + }, + { + "failure: invalid packet data", + func() { + packetData = []byte("invalid packet data") + }, + ibcerrors.ErrInvalidType, + ibcerrors.ErrInvalidType, + }, + { + "failure: new field", + func() { + jsonString := fmt.Sprintf(`{"denom":"denom","amount":"100","sender":"%s","receiver":"%s","memo":"memo","new_field":"value"}`, suite.chainB.SenderAccount.GetAddress().String(), suite.chainA.SenderAccount.GetAddress().String()) + packetData = []byte(jsonString) + }, + ibcerrors.ErrInvalidType, + ibcerrors.ErrInvalidType, + }, + { + "failure: missing field", + func() { + jsonString := fmt.Sprintf(`{"amount":"100","sender":%s","receiver":"%s"}`, suite.chainB.SenderAccount.GetAddress().String(), suite.chainA.SenderAccount.GetAddress().String()) + packetData = []byte(jsonString) + }, + ibcerrors.ErrInvalidType, + ibcerrors.ErrInvalidType, + }, + } + + for _, tc := range testCases { + suite.Run(tc.msg, func() { + suite.SetupTest() // reset + packetData = nil + + path = ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path.EndpointA.ChannelConfig.Version = types.V1 + path.EndpointB.ChannelConfig.Version = types.V1 + + tc.malleate() + + path.Setup() + + timeoutHeight := suite.chainB.GetTimeoutHeight() + + seq, err := path.EndpointB.SendPacket(timeoutHeight, 0, packetData) + suite.Require().NoError(err) + + packet := channeltypes.NewPacket(packetData, seq, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, timeoutHeight, 0) + + // receive packet on chainA + err = path.RelayPacket(packet) + + if tc.expError == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorContains(err, tc.expError.Error()) + ackBz, ok := path.EndpointA.Chain.GetSimApp().IBCKeeper.ChannelKeeper.GetPacketAcknowledgement(path.EndpointA.Chain.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, seq) + suite.Require().True(ok) + + // an ack should be written for the malformed / bad packet data. + expectedAck := channeltypes.NewErrorAcknowledgement(tc.expAckError) + expBz := channeltypes.CommitAcknowledgement(expectedAck.Acknowledgement()) + suite.Require().Equal(expBz, ackBz) + } + }) + } +} + +func (suite *KeeperTestSuite) TestCreatePacketDataBytesFromVersion() { + var ( + token types.Token + sender, receiver string + ) + + testCases := []struct { + name string + appVersion string + malleate func() + expResult func(bz []byte, err error) + }{ + { + "success", + types.V1, + func() {}, + func(bz []byte, err error) { + expPacketData := types.NewFungibleTokenPacketData(ibctesting.TestCoin.Denom, ibctesting.TestCoin.Amount.String(), sender, receiver, "") + suite.Require().Equal(bz, expPacketData.GetBytes()) + suite.Require().NoError(err) + }, + }, + { + "failure: version 2", + "ics20-2", + func() {}, + func(bz []byte, err error) { + suite.Require().Nil(bz) + suite.Require().Error(err, ibcerrors.ErrInvalidVersion) + }, + }, + { + "failure: fails v1 validation", + types.V1, + func() { + sender = "" + }, + func(bz []byte, err error) { + suite.Require().Nil(bz) + suite.Require().ErrorIs(err, ibcerrors.ErrInvalidAddress) + }, + }, + { + "failure: invalid version", + ibcmock.Version, + func() {}, + func(bz []byte, err error) { + suite.Require().Nil(bz) + suite.Require().ErrorIs(err, types.ErrInvalidVersion) + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + path := ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path.Setup() + + token = types.Token{ + Amount: ibctesting.TestCoin.Amount.String(), + Denom: types.NewDenom(ibctesting.TestCoin.Denom), + } + + sender = suite.chainA.SenderAccount.GetAddress().String() + receiver = suite.chainB.SenderAccount.GetAddress().String() + + tc.malleate() + + bz, err := transferkeeper.CreatePacketDataBytesFromVersion(tc.appVersion, sender, receiver, "", token) + + tc.expResult(bz, err) + }) + } +} + +// metadataFromDenom creates a banktypes.Metadata from a given types.Denom +func metadataFromDenom(denom types.Denom) banktypes.Metadata { + return banktypes.Metadata{ + Description: fmt.Sprintf("IBC token from %s", denom.Path()), + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: denom.Base, + Exponent: 0, + }, + }, + Base: denom.IBCDenom(), + Display: denom.Path(), + Name: fmt.Sprintf("%s IBC token", denom.Path()), + Symbol: strings.ToUpper(denom.Base), + } +} + +// assertEscrowEqual asserts that the amounts escrowed for each of the coins on chain matches the expectedAmounts +func (suite *KeeperTestSuite) assertEscrowEqual(chain *ibctesting.TestChain, coin sdk.Coin, expectedAmount sdkmath.Int) { + amount := chain.GetSimApp().TransferKeeper.GetTotalEscrowForDenom(chain.GetContext(), coin.GetDenom()) + suite.Require().Equal(expectedAmount, amount.Amount) +} diff --git a/modules/apps/transfer/module.go b/modules/apps/transfer/module.go new file mode 100644 index 0000000..0b3ac28 --- /dev/null +++ b/modules/apps/transfer/module.go @@ -0,0 +1,172 @@ +package transfer + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + + "cosmossdk.io/core/appmodule" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/client/cli" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/keeper" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/simulation" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" +) + +var ( + _ module.AppModule = (*AppModule)(nil) + _ module.AppModuleBasic = (*AppModuleBasic)(nil) + _ module.AppModuleSimulation = (*AppModule)(nil) + _ module.HasGenesis = (*AppModule)(nil) + _ module.HasName = (*AppModule)(nil) + _ module.HasConsensusVersion = (*AppModule)(nil) + _ module.HasServices = (*AppModule)(nil) + _ module.HasProposalMsgs = (*AppModule)(nil) + _ appmodule.AppModule = (*AppModule)(nil) + + _ porttypes.IBCModule = (*IBCModule)(nil) +) + +// AppModuleBasic is the IBC Transfer AppModuleBasic +type AppModuleBasic struct{} + +// Name implements AppModuleBasic interface +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (AppModule) IsOnePerModuleType() {} + +// IsAppModule implements the appmodule.AppModule interface. +func (AppModule) IsAppModule() {} + +// RegisterLegacyAminoCodec implements AppModuleBasic interface +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + types.RegisterLegacyAminoCodec(cdc) +} + +// RegisterInterfaces registers module concrete types into protobuf Any. +func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) { + types.RegisterInterfaces(registry) +} + +// DefaultGenesis returns default genesis state as raw bytes for the ibc +// transfer module. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(types.DefaultGenesisState()) +} + +// ValidateGenesis performs genesis state validation for the ibc transfer module. +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { + var gs types.GenesisState + if err := cdc.UnmarshalJSON(bz, &gs); err != nil { + return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) + } + + return gs.Validate() +} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the ibc-transfer module. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { + if err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)); err != nil { + panic(err) + } +} + +// GetTxCmd implements AppModuleBasic interface +func (AppModuleBasic) GetTxCmd() *cobra.Command { + return cli.NewTxCmd() +} + +// GetQueryCmd implements AppModuleBasic interface +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd() +} + +// AppModule represents the AppModule for this module +type AppModule struct { + AppModuleBasic + keeper keeper.Keeper +} + +// NewAppModule creates a new 20-transfer module +func NewAppModule(k keeper.Keeper) AppModule { + return AppModule{ + keeper: k, + } +} + +// RegisterServices registers module services. +func (am AppModule) RegisterServices(cfg module.Configurator) { + types.RegisterMsgServer(cfg.MsgServer(), am.keeper) + types.RegisterQueryServer(cfg.QueryServer(), am.keeper) + + m := keeper.NewMigrator(am.keeper) + if err := cfg.RegisterMigration(types.ModuleName, 2, m.MigrateTotalEscrowForDenom); err != nil { + panic(fmt.Errorf("failed to migrate transfer app from version 2 to 3 (total escrow entry migration): %v", err)) + } + + if err := cfg.RegisterMigration(types.ModuleName, 3, m.MigrateParams); err != nil { + panic(fmt.Errorf("failed to migrate transfer app version 3 to 4 (self-managed params migration): %v", err)) + } + + if err := cfg.RegisterMigration(types.ModuleName, 4, m.MigrateDenomMetadata); err != nil { + panic(fmt.Errorf("failed to migrate transfer app from version 4 to 5 (set denom metadata migration): %v", err)) + } + + if err := cfg.RegisterMigration(types.ModuleName, 5, m.MigrateDenomTraceToDenom); err != nil { + panic(fmt.Errorf("failed to migrate transfer app from version 5 to 6 (migrate DenomTrace to Denom): %v", err)) + } +} + +// InitGenesis performs genesis initialization for the ibc-transfer module. It returns +// no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) { + var genesisState types.GenesisState + cdc.MustUnmarshalJSON(data, &genesisState) + am.keeper.InitGenesis(ctx, genesisState) +} + +// ExportGenesis returns the exported genesis state as raw bytes for the ibc-transfer +// module. +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + gs := am.keeper.ExportGenesis(ctx) + return cdc.MustMarshalJSON(gs) +} + +// ConsensusVersion implements AppModule/ConsensusVersion defining the current version of transfer. +func (AppModule) ConsensusVersion() uint64 { return 6 } + +// AppModuleSimulation functions + +// GenerateGenesisState creates a randomized GenState of the transfer module. +func (AppModule) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) +} + +// ProposalMsgs returns msgs used for governance proposals for simulations. +func (AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { + return simulation.ProposalMsgs() +} + +// RegisterStoreDecoder registers a decoder for transfer module's types +func (AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { + sdr[types.StoreKey] = simulation.NewDecodeStore() +} + +// WeightedOperations returns the all the transfer module operations with their respective weights. +func (AppModule) WeightedOperations(_ module.SimulationState) []simtypes.WeightedOperation { + return nil +} diff --git a/modules/apps/transfer/simulation/decoder.go b/modules/apps/transfer/simulation/decoder.go new file mode 100644 index 0000000..e06cfcc --- /dev/null +++ b/modules/apps/transfer/simulation/decoder.go @@ -0,0 +1,30 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +) + +// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's +// Value to the corresponding Denom type. +func NewDecodeStore() func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + switch { + case bytes.Equal(kvA.Key[:1], types.PortKey): + return fmt.Sprintf("Port A: %s\nPort B: %s", string(kvA.Value), string(kvB.Value)) + + case bytes.Equal(kvA.Key[:1], types.DenomKey): + var denomA, denomB types.Denom + types.ModuleCdc.MustUnmarshal(kvA.Value, &denomA) + types.ModuleCdc.MustUnmarshal(kvB.Value, &denomB) + return fmt.Sprintf("Denom A: %s\nDenom B: %s", denomA.IBCDenom(), denomB.IBCDenom()) + + default: + panic(fmt.Errorf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1])) + } + } +} diff --git a/modules/apps/transfer/simulation/decoder_test.go b/modules/apps/transfer/simulation/decoder_test.go new file mode 100644 index 0000000..d669a5d --- /dev/null +++ b/modules/apps/transfer/simulation/decoder_test.go @@ -0,0 +1,53 @@ +package simulation_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/simulation" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +) + +func TestDecodeStore(t *testing.T) { + dec := simulation.NewDecodeStore() + denom := types.NewDenom("uatom", types.NewHop("transfer", "channelToA")) + + kvPairs := kv.Pairs{ + Pairs: []kv.Pair{ + { + Key: types.PortKey, + Value: []byte(types.PortID), + }, + { + Key: types.DenomKey, + Value: types.ModuleCdc.MustMarshal(&denom), + }, + { + Key: []byte{0x99}, + Value: []byte{0x99}, + }, + }, + } + tests := []struct { + name string + expectedLog string + }{ + {"PortID", fmt.Sprintf("Port A: %s\nPort B: %s", types.PortID, types.PortID)}, + {"Denom", fmt.Sprintf("Denom A: %s\nDenom B: %s", denom.IBCDenom(), denom.IBCDenom())}, + {"other", ""}, + } + + for i, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if i == len(tests)-1 { + require.Panics(t, func() { dec(kvPairs.Pairs[i], kvPairs.Pairs[i]) }, tt.name) + } else { + require.Equal(t, tt.expectedLog, dec(kvPairs.Pairs[i], kvPairs.Pairs[i]), tt.name) + } + }) + } +} diff --git a/modules/apps/transfer/simulation/genesis.go b/modules/apps/transfer/simulation/genesis.go new file mode 100644 index 0000000..34c68af --- /dev/null +++ b/modules/apps/transfer/simulation/genesis.go @@ -0,0 +1,55 @@ +package simulation + +import ( + "encoding/json" + "fmt" + "math/rand" + "strings" + + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +) + +// Simulation parameter constants +const port = "port_id" + +// RandomEnabled randomized send or receive enabled param with 75% prob of being true. +func RandomEnabled(r *rand.Rand) bool { + return r.Int63n(101) <= 75 +} + +// RandomizedGenState generates a random GenesisState for transfer. +func RandomizedGenState(simState *module.SimulationState) { + var portID string + simState.AppParams.GetOrGenerate( + port, &portID, simState.Rand, + func(r *rand.Rand) { portID = strings.ToLower(simtypes.RandStringOfLength(r, 20)) }, + ) + + var sendEnabled bool + simState.AppParams.GetOrGenerate( + string(types.KeySendEnabled), &sendEnabled, simState.Rand, + func(r *rand.Rand) { sendEnabled = RandomEnabled(r) }, + ) + + var receiveEnabled bool + simState.AppParams.GetOrGenerate( + string(types.KeyReceiveEnabled), &receiveEnabled, simState.Rand, + func(r *rand.Rand) { receiveEnabled = RandomEnabled(r) }, + ) + + transferGenesis := types.GenesisState{ + PortId: portID, + Denoms: types.Denoms{}, + Params: types.NewParams(sendEnabled, receiveEnabled), + } + + bz, err := json.MarshalIndent(&transferGenesis, "", " ") + if err != nil { + panic(err) + } + fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, bz) + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(&transferGenesis) +} diff --git a/modules/apps/transfer/simulation/genesis_test.go b/modules/apps/transfer/simulation/genesis_test.go new file mode 100644 index 0000000..434f7c5 --- /dev/null +++ b/modules/apps/transfer/simulation/genesis_test.go @@ -0,0 +1,87 @@ +package simulation_test + +import ( + "encoding/json" + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + + sdkmath "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/simulation" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +) + +// TestRandomizedGenState tests the normal scenario of applying RandomizedGenState. +// Abonormal scenarios are not tested here. +func TestRandomizedGenState(t *testing.T) { + interfaceRegistry := codectypes.NewInterfaceRegistry() + cryptocodec.RegisterInterfaces(interfaceRegistry) + cdc := codec.NewProtoCodec(interfaceRegistry) + + s := rand.NewSource(1) + r := rand.New(s) + + simState := module.SimulationState{ + AppParams: make(simtypes.AppParams), + Cdc: cdc, + Rand: r, + NumBonded: 3, + Accounts: simtypes.RandomAccounts(r, 3), + InitialStake: sdkmath.NewInt(1000), + GenState: make(map[string]json.RawMessage), + } + + simulation.RandomizedGenState(&simState) + + var ibcTransferGenesis types.GenesisState + simState.Cdc.MustUnmarshalJSON(simState.GenState[types.ModuleName], &ibcTransferGenesis) + + require.Equal(t, "euzxpfgkqegqiqwixnku", ibcTransferGenesis.PortId) + require.True(t, ibcTransferGenesis.Params.SendEnabled) + require.True(t, ibcTransferGenesis.Params.ReceiveEnabled) + require.Len(t, ibcTransferGenesis.Denoms, 0) +} + +// TestRandomizedGenState tests abnormal scenarios of applying RandomizedGenState. +func TestRandomizedGenState1(t *testing.T) { + interfaceRegistry := codectypes.NewInterfaceRegistry() + cdc := codec.NewProtoCodec(interfaceRegistry) + + s := rand.NewSource(1) + r := rand.New(s) + // all these tests will panic + tests := []struct { + name string + simState module.SimulationState + panicMsg string + }{ + { // panic => reason: incomplete initialization of the simState + "nil pointer dereference", + module.SimulationState{}, + "invalid memory address or nil pointer dereference", + }, + { // panic => reason: incomplete initialization of the simState + "assignment to entry in nil map", + module.SimulationState{ + AppParams: make(simtypes.AppParams), + Cdc: cdc, + Rand: r, + }, + "assignment to entry in nil map", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + require.Panicsf(t, func() { simulation.RandomizedGenState(&tc.simState) }, tc.panicMsg) + }) + } +} diff --git a/modules/apps/transfer/simulation/proposals.go b/modules/apps/transfer/simulation/proposals.go new file mode 100644 index 0000000..c75f7d0 --- /dev/null +++ b/modules/apps/transfer/simulation/proposals.go @@ -0,0 +1,42 @@ +package simulation + +import ( + "math/rand" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +) + +// Simulation operation weights constants +const ( + DefaultWeightMsgUpdateParams int = 100 + + OpWeightMsgUpdateParams = "op_weight_msg_update_params" // #nosec +) + +// ProposalMsgs defines the module weighted proposals' contents +func ProposalMsgs() []simtypes.WeightedProposalMsg { + return []simtypes.WeightedProposalMsg{ + simulation.NewWeightedProposalMsg( + OpWeightMsgUpdateParams, + DefaultWeightMsgUpdateParams, + SimulateMsgUpdateParams, + ), + } +} + +// SimulateMsgUpdateParams returns a MsgUpdateParams +func SimulateMsgUpdateParams(_ *rand.Rand, _ sdk.Context, _ []simtypes.Account) sdk.Msg { + var gov sdk.AccAddress = address.Module("gov") + params := types.DefaultParams() + params.SendEnabled = false + + return &types.MsgUpdateParams{ + Signer: gov.String(), + Params: params, + } +} diff --git a/modules/apps/transfer/simulation/proposals_test.go b/modules/apps/transfer/simulation/proposals_test.go new file mode 100644 index 0000000..9f334dc --- /dev/null +++ b/modules/apps/transfer/simulation/proposals_test.go @@ -0,0 +1,43 @@ +package simulation_test + +import ( + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/simulation" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +) + +func TestProposalMsgs(t *testing.T) { + // initialize parameters + s := rand.NewSource(1) + r := rand.New(s) + + ctx := sdk.NewContext(nil, cmtproto.Header{}, true, nil) + accounts := simtypes.RandomAccounts(r, 3) + + // execute ProposalMsgs function + weightedProposalMsgs := simulation.ProposalMsgs() + require.Equal(t, len(weightedProposalMsgs), 1) + + w0 := weightedProposalMsgs[0] + + // tests w0 interface: + require.Equal(t, simulation.OpWeightMsgUpdateParams, w0.AppParamsKey()) + require.Equal(t, simulation.DefaultWeightMsgUpdateParams, w0.DefaultWeight()) + + msg := w0.MsgSimulatorFn()(r, ctx, accounts) + msgUpdateParams, ok := msg.(*types.MsgUpdateParams) + require.True(t, ok) + + require.Equal(t, sdk.AccAddress(address.Module("gov")).String(), msgUpdateParams.Signer) + require.EqualValues(t, msgUpdateParams.Params.SendEnabled, false) +} diff --git a/modules/apps/transfer/transfer_test.go b/modules/apps/transfer/transfer_test.go new file mode 100644 index 0000000..7432eed --- /dev/null +++ b/modules/apps/transfer/transfer_test.go @@ -0,0 +1,201 @@ +package transfer_test + +import ( + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +type TransferTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains used for convenience and readability + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain + chainC *ibctesting.TestChain +} + +func (suite *TransferTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 3) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) + suite.chainC = suite.coordinator.GetChain(ibctesting.GetChainID(3)) +} + +// Constructs the following sends based on the established channels/connections +// 1 - from chainA to chainB +// 2 - from chainB to chainC +// 3 - from chainC to chainB +func (suite *TransferTestSuite) TestHandleMsgTransfer() { + var ( + sourceDenomToTransfer string + msgAmount sdkmath.Int + ) + + testCases := []struct { + name string + malleate func() + }{ + { + "transfer single denom", + func() {}, + }, + { + "transfer amount larger than int64", + func() { + var ok bool + msgAmount, ok = sdkmath.NewIntFromString("9223372036854775808") // 2^63 (one above int64) + suite.Require().True(ok) + }, + }, + { + "transfer entire balance", + func() { + msgAmount = types.UnboundedSpendLimit() + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + // setup between chainA and chainB + // NOTE: + // pathAToB.EndpointA = endpoint on chainA + // pathAToB.EndpointB = endpoint on chainB + pathAToB := ibctesting.NewTransferPath(suite.chainA, suite.chainB) + pathAToB.Setup() + traceAToB := types.NewHop(pathAToB.EndpointB.ChannelConfig.PortID, pathAToB.EndpointB.ChannelID) + + sourceDenomToTransfer = sdk.DefaultBondDenom + msgAmount = ibctesting.DefaultCoinAmount + + tc.malleate() + + originalBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), sourceDenomToTransfer) + + timeoutHeight := clienttypes.NewHeight(1, 110) + + originalCoin := sdk.NewCoin(sourceDenomToTransfer, msgAmount) + + // send from chainA to chainB + msg := types.NewMsgTransfer(pathAToB.EndpointA.ChannelConfig.PortID, pathAToB.EndpointA.ChannelID, originalCoin, suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), timeoutHeight, 0, "") + res, err := suite.chainA.SendMsgs(msg) + suite.Require().NoError(err) // message committed + + packet, err := ibctesting.ParseV1PacketFromEvents(res.Events) + suite.Require().NoError(err) + + // Get the packet data to determine the amount of tokens being transferred (needed for sending entire balance) + packetData, err := types.UnmarshalPacketData(packet.GetData(), pathAToB.EndpointA.GetChannel().Version, "") + suite.Require().NoError(err) + transferAmount, ok := sdkmath.NewIntFromString(packetData.Token.Amount) + suite.Require().True(ok) + + // relay send + err = pathAToB.RelayPacket(packet) + suite.Require().NoError(err) // relay committed + + escrowAddress := types.GetEscrowAddress(packet.GetSourcePort(), packet.GetSourceChannel()) + // check that the balance for chainA is updated + chainABalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), originalCoin.Denom) + suite.Require().True(originalBalance.Amount.Sub(transferAmount).Equal(chainABalance.Amount)) + + // check that module account escrow address has locked the tokens + chainAEscrowBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), escrowAddress, originalCoin.Denom) + suite.Require().True(transferAmount.Equal(chainAEscrowBalance.Amount)) + + // check that voucher exists on chain B + chainBDenom := types.NewDenom(originalCoin.Denom, traceAToB) + chainBBalance := suite.chainB.GetSimApp().BankKeeper.GetBalance(suite.chainB.GetContext(), suite.chainB.SenderAccount.GetAddress(), chainBDenom.IBCDenom()) + coinSentFromAToB := sdk.NewCoin(chainBDenom.IBCDenom(), transferAmount) + suite.Require().Equal(coinSentFromAToB, chainBBalance) + + // setup between chainB to chainC + // NOTE: + // pathBToC.EndpointA = endpoint on chainB + // pathBToC.EndpointB = endpoint on chainC + pathBToC := ibctesting.NewTransferPath(suite.chainB, suite.chainC) + pathBToC.Setup() + traceBToC := types.NewHop(pathBToC.EndpointB.ChannelConfig.PortID, pathBToC.EndpointB.ChannelID) + + // send from chainB to chainC + msg = types.NewMsgTransfer(pathBToC.EndpointA.ChannelConfig.PortID, pathBToC.EndpointA.ChannelID, coinSentFromAToB, suite.chainB.SenderAccount.GetAddress().String(), suite.chainC.SenderAccount.GetAddress().String(), timeoutHeight, 0, "") + res, err = suite.chainB.SendMsgs(msg) + suite.Require().NoError(err) // message committed + + packet, err = ibctesting.ParseV1PacketFromEvents(res.Events) + suite.Require().NoError(err) + + err = pathBToC.RelayPacket(packet) + suite.Require().NoError(err) // relay committed + + coinsSentFromBToC := sdk.NewCoins() + // check balances for chainB and chainC after transfer from chainB to chainC + // NOTE: fungible token is prefixed with the full trace in order to verify the packet commitment + chainCDenom := types.NewDenom(originalCoin.Denom, traceBToC, traceAToB) + + // check that the balance is updated on chainC + coinSentFromBToC := sdk.NewCoin(chainCDenom.IBCDenom(), transferAmount) + chainCBalance := suite.chainC.GetSimApp().BankKeeper.GetBalance(suite.chainC.GetContext(), suite.chainC.SenderAccount.GetAddress(), coinSentFromBToC.Denom) + suite.Require().Equal(coinSentFromBToC, chainCBalance) + + // check that balance on chain B is empty + chainBBalance = suite.chainB.GetSimApp().BankKeeper.GetBalance(suite.chainB.GetContext(), suite.chainB.SenderAccount.GetAddress(), coinSentFromBToC.Denom) + suite.Require().Zero(chainBBalance.Amount.Int64()) + + // send from chainC back to chainB + msg = types.NewMsgTransfer(pathBToC.EndpointB.ChannelConfig.PortID, pathBToC.EndpointB.ChannelID, coinSentFromBToC, suite.chainC.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), timeoutHeight, 0, "") + res, err = suite.chainC.SendMsgs(msg) + suite.Require().NoError(err) // message committed + + packet, err = ibctesting.ParseV1PacketFromEvents(res.Events) + suite.Require().NoError(err) + + err = pathBToC.RelayPacket(packet) + suite.Require().NoError(err) // relay committed + + // check balances for chainC are empty after transfer from chainC to chainB + for _, coin := range coinsSentFromBToC { + // check that balance on chain C is empty + chainCBalance := suite.chainC.GetSimApp().BankKeeper.GetBalance(suite.chainC.GetContext(), suite.chainC.SenderAccount.GetAddress(), coin.Denom) + suite.Require().Zero(chainCBalance.Amount.Int64()) + } + + // check balances for chainB after transfer from chainC to chainB + // check that balance on chain B has the transferred amount + chainBBalance = suite.chainB.GetSimApp().BankKeeper.GetBalance(suite.chainB.GetContext(), suite.chainB.SenderAccount.GetAddress(), coinSentFromAToB.Denom) + suite.Require().Equal(coinSentFromAToB, chainBBalance) + + // check that module account escrow address is empty + escrowAddress = types.GetEscrowAddress(traceBToC.PortId, traceBToC.ChannelId) + chainBEscrowBalance := suite.chainB.GetSimApp().BankKeeper.GetBalance(suite.chainB.GetContext(), escrowAddress, coinSentFromAToB.Denom) + suite.Require().Zero(chainBEscrowBalance.Amount.Int64()) + + // check balances for chainA after transfer from chainC to chainB + // check that the balance is unchanged + chainABalance = suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), originalCoin.Denom) + suite.Require().True(originalBalance.Amount.Sub(transferAmount).Equal(chainABalance.Amount)) + + // check that module account escrow address is unchanged + escrowAddress = types.GetEscrowAddress(pathAToB.EndpointA.ChannelConfig.PortID, pathAToB.EndpointA.ChannelID) + chainAEscrowBalance = suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), escrowAddress, originalCoin.Denom) + suite.Require().True(transferAmount.Equal(chainAEscrowBalance.Amount)) + }) + } +} + +func TestTransferTestSuite(t *testing.T) { + testifysuite.Run(t, new(TransferTestSuite)) +} diff --git a/modules/apps/transfer/types/authz.pb.go b/modules/apps/transfer/types/authz.pb.go new file mode 100644 index 0000000..ed3025b --- /dev/null +++ b/modules/apps/transfer/types/authz.pb.go @@ -0,0 +1,752 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/applications/transfer/v1/authz.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" + types "github.com/cosmos/cosmos-sdk/types" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Allocation defines the spend limit for a particular port and channel +type Allocation struct { + // the port on which the packet will be sent + SourcePort string `protobuf:"bytes,1,opt,name=source_port,json=sourcePort,proto3" json:"source_port,omitempty"` + // the channel by which the packet will be sent + SourceChannel string `protobuf:"bytes,2,opt,name=source_channel,json=sourceChannel,proto3" json:"source_channel,omitempty"` + // spend limitation on the channel + SpendLimit github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,3,rep,name=spend_limit,json=spendLimit,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"spend_limit"` + // allow list of receivers, an empty allow list permits any receiver address + AllowList []string `protobuf:"bytes,4,rep,name=allow_list,json=allowList,proto3" json:"allow_list,omitempty"` + // allow list of memo strings, an empty list prohibits all memo strings; + // a list only with "*" permits any memo string + AllowedPacketData []string `protobuf:"bytes,5,rep,name=allowed_packet_data,json=allowedPacketData,proto3" json:"allowed_packet_data,omitempty"` +} + +func (m *Allocation) Reset() { *m = Allocation{} } +func (m *Allocation) String() string { return proto.CompactTextString(m) } +func (*Allocation) ProtoMessage() {} +func (*Allocation) Descriptor() ([]byte, []int) { + return fileDescriptor_b1a28b55d17325aa, []int{0} +} +func (m *Allocation) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Allocation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Allocation.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Allocation) XXX_Merge(src proto.Message) { + xxx_messageInfo_Allocation.Merge(m, src) +} +func (m *Allocation) XXX_Size() int { + return m.Size() +} +func (m *Allocation) XXX_DiscardUnknown() { + xxx_messageInfo_Allocation.DiscardUnknown(m) +} + +var xxx_messageInfo_Allocation proto.InternalMessageInfo + +func (m *Allocation) GetSourcePort() string { + if m != nil { + return m.SourcePort + } + return "" +} + +func (m *Allocation) GetSourceChannel() string { + if m != nil { + return m.SourceChannel + } + return "" +} + +func (m *Allocation) GetSpendLimit() github_com_cosmos_cosmos_sdk_types.Coins { + if m != nil { + return m.SpendLimit + } + return nil +} + +func (m *Allocation) GetAllowList() []string { + if m != nil { + return m.AllowList + } + return nil +} + +func (m *Allocation) GetAllowedPacketData() []string { + if m != nil { + return m.AllowedPacketData + } + return nil +} + +// TransferAuthorization allows the grantee to spend up to spend_limit coins from +// the granter's account for ibc transfer on a specific channel +type TransferAuthorization struct { + // port and channel amounts + Allocations []Allocation `protobuf:"bytes,1,rep,name=allocations,proto3" json:"allocations"` +} + +func (m *TransferAuthorization) Reset() { *m = TransferAuthorization{} } +func (m *TransferAuthorization) String() string { return proto.CompactTextString(m) } +func (*TransferAuthorization) ProtoMessage() {} +func (*TransferAuthorization) Descriptor() ([]byte, []int) { + return fileDescriptor_b1a28b55d17325aa, []int{1} +} +func (m *TransferAuthorization) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TransferAuthorization) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TransferAuthorization.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TransferAuthorization) XXX_Merge(src proto.Message) { + xxx_messageInfo_TransferAuthorization.Merge(m, src) +} +func (m *TransferAuthorization) XXX_Size() int { + return m.Size() +} +func (m *TransferAuthorization) XXX_DiscardUnknown() { + xxx_messageInfo_TransferAuthorization.DiscardUnknown(m) +} + +var xxx_messageInfo_TransferAuthorization proto.InternalMessageInfo + +func (m *TransferAuthorization) GetAllocations() []Allocation { + if m != nil { + return m.Allocations + } + return nil +} + +func init() { + proto.RegisterType((*Allocation)(nil), "ibc.applications.transfer.v1.Allocation") + proto.RegisterType((*TransferAuthorization)(nil), "ibc.applications.transfer.v1.TransferAuthorization") +} + +func init() { + proto.RegisterFile("ibc/applications/transfer/v1/authz.proto", fileDescriptor_b1a28b55d17325aa) +} + +var fileDescriptor_b1a28b55d17325aa = []byte{ + // 436 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x92, 0x4f, 0x6f, 0xd3, 0x30, + 0x18, 0xc6, 0x9b, 0x75, 0x20, 0xd5, 0x15, 0x48, 0x04, 0x90, 0xb2, 0x09, 0xd2, 0xaa, 0x12, 0x28, + 0x97, 0xda, 0x0b, 0x5c, 0x10, 0xb7, 0x75, 0x1c, 0x77, 0xa8, 0x22, 0x4e, 0x5c, 0x22, 0xc7, 0x31, + 0xad, 0x35, 0x37, 0x6f, 0x14, 0xbf, 0x09, 0x62, 0x9f, 0x82, 0x7d, 0x0d, 0xce, 0x7c, 0x88, 0x89, + 0xd3, 0x8e, 0x9c, 0x00, 0xb5, 0x5f, 0x04, 0xc5, 0xf6, 0xa0, 0x08, 0x89, 0x53, 0xe2, 0xe7, 0x7d, + 0xfc, 0xfe, 0xf9, 0xf9, 0x25, 0x89, 0x2a, 0x04, 0xe3, 0x75, 0xad, 0x95, 0xe0, 0xa8, 0xa0, 0x32, + 0x0c, 0x1b, 0x5e, 0x99, 0xf7, 0xb2, 0x61, 0x5d, 0xca, 0x78, 0x8b, 0xeb, 0x4b, 0x5a, 0x37, 0x80, + 0x10, 0x3e, 0x51, 0x85, 0xa0, 0xfb, 0x4e, 0x7a, 0xeb, 0xa4, 0x5d, 0x7a, 0x7c, 0x24, 0xc0, 0x6c, + 0xc0, 0xe4, 0xd6, 0xcb, 0xdc, 0xc1, 0x5d, 0x3c, 0x7e, 0xb4, 0x82, 0x15, 0x38, 0xbd, 0xff, 0xf3, + 0x6a, 0xec, 0x3c, 0xac, 0xe0, 0x46, 0xb2, 0x2e, 0x2d, 0x24, 0xf2, 0x94, 0x09, 0x50, 0x95, 0x8b, + 0xcf, 0xae, 0x0e, 0x08, 0x39, 0xd5, 0x1a, 0x5c, 0xb1, 0x70, 0x42, 0xc6, 0x06, 0xda, 0x46, 0xc8, + 0xbc, 0x86, 0x06, 0xa3, 0x60, 0x1a, 0x24, 0xa3, 0x8c, 0x38, 0x69, 0x09, 0x0d, 0x86, 0xcf, 0xc8, + 0x7d, 0x6f, 0x10, 0x6b, 0x5e, 0x55, 0x52, 0x47, 0x07, 0xd6, 0x73, 0xcf, 0xa9, 0x67, 0x4e, 0x0c, + 0x35, 0x19, 0x9b, 0x5a, 0x56, 0x65, 0xae, 0xd5, 0x46, 0x61, 0x34, 0x9c, 0x0e, 0x93, 0xf1, 0x8b, + 0x23, 0xea, 0x1b, 0xee, 0x9b, 0xa1, 0xbe, 0x19, 0x7a, 0x06, 0xaa, 0x5a, 0x9c, 0x5c, 0x7f, 0x9f, + 0x0c, 0x3e, 0xff, 0x98, 0x24, 0x2b, 0x85, 0xeb, 0xb6, 0xa0, 0x02, 0x36, 0x7e, 0x3a, 0xff, 0x99, + 0x9b, 0xf2, 0x82, 0xe1, 0xc7, 0x5a, 0x1a, 0x7b, 0xc1, 0x64, 0xc4, 0xe6, 0x3f, 0xef, 0xd3, 0x87, + 0x4f, 0x09, 0xe1, 0x5a, 0xc3, 0x87, 0x5c, 0x2b, 0x83, 0xd1, 0xe1, 0x74, 0x98, 0x8c, 0xb2, 0x91, + 0x55, 0xce, 0x95, 0xc1, 0x90, 0x92, 0x87, 0xf6, 0x20, 0xcb, 0xbc, 0xe6, 0xe2, 0x42, 0x62, 0x5e, + 0x72, 0xe4, 0xd1, 0x1d, 0xeb, 0x7b, 0xe0, 0x43, 0x4b, 0x1b, 0x79, 0xc3, 0x91, 0xcf, 0xae, 0x02, + 0xf2, 0xf8, 0xad, 0x87, 0x7e, 0xda, 0xe2, 0x1a, 0x1a, 0x75, 0xe9, 0xf0, 0x2c, 0xc9, 0x98, 0xff, + 0x86, 0x65, 0xa2, 0xc0, 0x8e, 0x95, 0xd0, 0xff, 0x3d, 0x19, 0xfd, 0x43, 0x77, 0x71, 0xd8, 0x4f, + 0x99, 0xed, 0xa7, 0x78, 0xfd, 0xfc, 0xeb, 0x97, 0xf9, 0xcc, 0x63, 0x71, 0x6b, 0x70, 0xcb, 0xe5, + 0xaf, 0xca, 0x8b, 0xec, 0x7a, 0x1b, 0x07, 0x37, 0xdb, 0x38, 0xf8, 0xb9, 0x8d, 0x83, 0x4f, 0xbb, + 0x78, 0x70, 0xb3, 0x8b, 0x07, 0xdf, 0x76, 0xf1, 0xe0, 0xdd, 0xab, 0x7f, 0x91, 0xa9, 0x42, 0xcc, + 0x57, 0xc0, 0xba, 0xf4, 0x84, 0x6d, 0xa0, 0x6c, 0xb5, 0x34, 0xfd, 0xee, 0xed, 0xed, 0x9c, 0x05, + 0x59, 0xdc, 0xb5, 0x2b, 0xf0, 0xf2, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe7, 0x77, 0x0f, 0x28, + 0x9d, 0x02, 0x00, 0x00, +} + +func (m *Allocation) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Allocation) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Allocation) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.AllowedPacketData) > 0 { + for iNdEx := len(m.AllowedPacketData) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.AllowedPacketData[iNdEx]) + copy(dAtA[i:], m.AllowedPacketData[iNdEx]) + i = encodeVarintAuthz(dAtA, i, uint64(len(m.AllowedPacketData[iNdEx]))) + i-- + dAtA[i] = 0x2a + } + } + if len(m.AllowList) > 0 { + for iNdEx := len(m.AllowList) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.AllowList[iNdEx]) + copy(dAtA[i:], m.AllowList[iNdEx]) + i = encodeVarintAuthz(dAtA, i, uint64(len(m.AllowList[iNdEx]))) + i-- + dAtA[i] = 0x22 + } + } + if len(m.SpendLimit) > 0 { + for iNdEx := len(m.SpendLimit) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.SpendLimit[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintAuthz(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + if len(m.SourceChannel) > 0 { + i -= len(m.SourceChannel) + copy(dAtA[i:], m.SourceChannel) + i = encodeVarintAuthz(dAtA, i, uint64(len(m.SourceChannel))) + i-- + dAtA[i] = 0x12 + } + if len(m.SourcePort) > 0 { + i -= len(m.SourcePort) + copy(dAtA[i:], m.SourcePort) + i = encodeVarintAuthz(dAtA, i, uint64(len(m.SourcePort))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *TransferAuthorization) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TransferAuthorization) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TransferAuthorization) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Allocations) > 0 { + for iNdEx := len(m.Allocations) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Allocations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintAuthz(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintAuthz(dAtA []byte, offset int, v uint64) int { + offset -= sovAuthz(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Allocation) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.SourcePort) + if l > 0 { + n += 1 + l + sovAuthz(uint64(l)) + } + l = len(m.SourceChannel) + if l > 0 { + n += 1 + l + sovAuthz(uint64(l)) + } + if len(m.SpendLimit) > 0 { + for _, e := range m.SpendLimit { + l = e.Size() + n += 1 + l + sovAuthz(uint64(l)) + } + } + if len(m.AllowList) > 0 { + for _, s := range m.AllowList { + l = len(s) + n += 1 + l + sovAuthz(uint64(l)) + } + } + if len(m.AllowedPacketData) > 0 { + for _, s := range m.AllowedPacketData { + l = len(s) + n += 1 + l + sovAuthz(uint64(l)) + } + } + return n +} + +func (m *TransferAuthorization) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Allocations) > 0 { + for _, e := range m.Allocations { + l = e.Size() + n += 1 + l + sovAuthz(uint64(l)) + } + } + return n +} + +func sovAuthz(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozAuthz(x uint64) (n int) { + return sovAuthz(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Allocation) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthz + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Allocation: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Allocation: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SourcePort", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthz + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAuthz + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAuthz + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SourcePort = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SourceChannel", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthz + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAuthz + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAuthz + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SourceChannel = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SpendLimit", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthz + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAuthz + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthAuthz + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SpendLimit = append(m.SpendLimit, types.Coin{}) + if err := m.SpendLimit[len(m.SpendLimit)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowList", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthz + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAuthz + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAuthz + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AllowList = append(m.AllowList, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowedPacketData", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthz + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAuthz + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAuthz + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AllowedPacketData = append(m.AllowedPacketData, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipAuthz(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAuthz + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TransferAuthorization) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthz + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TransferAuthorization: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TransferAuthorization: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Allocations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthz + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAuthz + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthAuthz + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Allocations = append(m.Allocations, Allocation{}) + if err := m.Allocations[len(m.Allocations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipAuthz(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAuthz + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipAuthz(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAuthz + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAuthz + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAuthz + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthAuthz + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupAuthz + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthAuthz + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthAuthz = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowAuthz = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupAuthz = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/apps/transfer/types/codec.go b/modules/apps/transfer/types/codec.go new file mode 100644 index 0000000..b4f67a0 --- /dev/null +++ b/modules/apps/transfer/types/codec.go @@ -0,0 +1,36 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/legacy" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/msgservice" + "github.com/cosmos/cosmos-sdk/x/authz" +) + +// RegisterLegacyAminoCodec registers the necessary x/ibc transfer interfaces and concrete types +// on the provided LegacyAmino codec. These types are used for Amino JSON serialization. +func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + legacy.RegisterAminoMsg(cdc, &MsgTransfer{}, "cosmos-sdk/MsgTransfer") +} + +// RegisterInterfaces register the ibc transfer module interfaces to protobuf +// Any. +func RegisterInterfaces(registry codectypes.InterfaceRegistry) { + registry.RegisterImplementations((*sdk.Msg)(nil), &MsgTransfer{}, &MsgUpdateParams{}) + + registry.RegisterImplementations( + (*authz.Authorization)(nil), + &TransferAuthorization{}, + ) + + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} + +// ModuleCdc references the global x/ibc-transfer module codec. Note, the codec +// should ONLY be used in certain instances of tests and for JSON encoding. +// +// The actual codec used for serialization should be provided to x/ibc transfer and +// defined at the application level. +var ModuleCdc = codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) diff --git a/modules/apps/transfer/types/codec_test.go b/modules/apps/transfer/types/codec_test.go new file mode 100644 index 0000000..581f628 --- /dev/null +++ b/modules/apps/transfer/types/codec_test.go @@ -0,0 +1,73 @@ +package types_test + +import ( + "errors" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +// TestMustMarshalProtoJSON tests that the memo field is only emitted (marshalled) if it is populated +func (suite *TypesTestSuite) TestMustMarshalProtoJSON() { + memo := "memo" + packetData := types.NewFungibleTokenPacketData(sdk.DefaultBondDenom, "1", suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), memo) + + bz := packetData.GetBytes() + exists := strings.Contains(string(bz), memo) + suite.Require().True(exists) + + packetData.Memo = "" + + bz = packetData.GetBytes() + exists = strings.Contains(string(bz), memo) + suite.Require().False(exists) +} + +func (suite *TypesTestSuite) TestCodecTypeRegistration() { + testCases := []struct { + name string + typeURL string + expErr error + }{ + { + "success: MsgTransfer", + sdk.MsgTypeURL(&types.MsgTransfer{}), + nil, + }, + { + "success: MsgUpdateParams", + sdk.MsgTypeURL(&types.MsgUpdateParams{}), + nil, + }, + { + "success: TransferAuthorization", + sdk.MsgTypeURL(&types.TransferAuthorization{}), + nil, + }, + { + "type not registered on codec", + "ibc.invalid.MsgTypeURL", + errors.New("unable to resolve type URL"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + encodingCfg := moduletestutil.MakeTestEncodingConfig(transfer.AppModuleBasic{}) + msg, err := encodingCfg.Codec.InterfaceRegistry().Resolve(tc.typeURL) + + if tc.expErr == nil { + suite.Require().NotNil(msg) + suite.Require().NoError(err) + } else { + suite.Require().Nil(msg) + ibctesting.RequireErrorIsOrContains(suite.T(), err, tc.expErr) + } + }) + } +} diff --git a/modules/apps/transfer/types/denom.go b/modules/apps/transfer/types/denom.go new file mode 100644 index 0000000..eaf5d96 --- /dev/null +++ b/modules/apps/transfer/types/denom.go @@ -0,0 +1,226 @@ +package types + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "sort" + "strings" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + cmtbytes "github.com/cometbft/cometbft/libs/bytes" + cmttypes "github.com/cometbft/cometbft/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" +) + +// NewDenom creates a new Denom instance given the base denomination and a variable number of hops. +func NewDenom(base string, trace ...Hop) Denom { + return Denom{ + Base: base, + Trace: trace, + } +} + +// Validate performs a basic validation of the Denom fields. +func (d Denom) Validate() error { + // NOTE: base denom validation cannot be performed as each chain may define + // its own base denom validation + if strings.TrimSpace(d.Base) == "" { + return errorsmod.Wrap(ErrInvalidDenomForTransfer, "base denomination cannot be blank") + } + + for _, hop := range d.Trace { + if err := hop.Validate(); err != nil { + return errorsmod.Wrap(err, "invalid trace") + } + } + + return nil +} + +// Hash returns the hex bytes of the SHA256 hash of the Denom fields using the following formula: +// +// hash = sha256(trace + "/" + baseDenom) +func (d Denom) Hash() cmtbytes.HexBytes { + hash := sha256.Sum256([]byte(d.Path())) + return hash[:] +} + +// IBCDenom a coin denomination for an ICS20 fungible token in the format +// 'ibc/{hash(trace + baseDenom)}'. If the trace is empty, it will return the base denomination. +func (d Denom) IBCDenom() string { + if d.IsNative() { + return d.Base + } + + return fmt.Sprintf("%s/%s", DenomPrefix, d.Hash()) +} + +// Path returns the full denomination according to the ICS20 specification: +// trace + "/" + baseDenom +// If there exists no trace then the base denomination is returned. +func (d Denom) Path() string { + if d.IsNative() { + return d.Base + } + + var sb strings.Builder + for _, t := range d.Trace { + sb.WriteString(t.String()) // nolint:revive // no error returned by WriteString + sb.WriteByte('/') //nolint:revive // no error returned by WriteByte + } + sb.WriteString(d.Base) //nolint:revive + return sb.String() +} + +// IsNative returns true if the denomination is native, thus containing no trace history. +func (d Denom) IsNative() bool { + return len(d.Trace) == 0 +} + +// HasPrefix returns true if the first element of the trace of the denom +// matches the provided portId and channelId. +func (d Denom) HasPrefix(portID, channelID string) bool { + // if the denom is native, then it is not prefixed by any port/channel pair + if d.IsNative() { + return false + } + + return d.Trace[0].PortId == portID && d.Trace[0].ChannelId == channelID +} + +// Denoms defines a wrapper type for a slice of Denom. +type Denoms []Denom + +// Validate performs a basic validation of each denomination trace info. +func (d Denoms) Validate() error { + seenDenoms := make(map[string]bool) + for i, denom := range d { + hash := denom.Hash().String() + if seenDenoms[hash] { + return fmt.Errorf("duplicated denomination with hash %s", denom.Hash()) + } + + if err := denom.Validate(); err != nil { + return errorsmod.Wrapf(err, "failed denom %d validation", i) + } + seenDenoms[hash] = true + } + return nil +} + +var _ sort.Interface = (*Denoms)(nil) + +// Len implements sort.Interface for Denoms +func (d Denoms) Len() int { return len(d) } + +// Less implements sort.Interface for Denoms +func (d Denoms) Less(i, j int) bool { + if d[i].Base != d[j].Base { + return d[i].Base < d[j].Base + } + + if len(d[i].Trace) != len(d[j].Trace) { + return len(d[i].Trace) < len(d[j].Trace) + } + + return d[i].Path() < d[j].Path() +} + +// Swap implements sort.Interface for Denoms +func (d Denoms) Swap(i, j int) { d[i], d[j] = d[j], d[i] } + +// Sort is a helper function to sort the set of denomination in-place +func (d Denoms) Sort() Denoms { + sort.Sort(d) + return d +} + +// ExtractDenomFromPath returns the denom from the full path. +func ExtractDenomFromPath(fullPath string) Denom { + denomSplit := strings.Split(fullPath, "/") + + if denomSplit[0] == fullPath { + return Denom{ + Base: fullPath, + } + } + + var ( + trace []Hop + baseDenomSlice []string + ) + + length := len(denomSplit) + for i := 0; i < length; i += 2 { + // The IBC specification does not guarantee the expected format of the + // destination port or destination channel identifier. A short term solution + // to determine base denomination is to expect the channel identifier to be the + // one ibc-go specifies. A longer term solution is to separate the path and base + // denomination in the ICS20 packet. If an intermediate hop prefixes the full denom + // with a channel identifier format different from our own, the base denomination + // will be incorrectly parsed, but the token will continue to be treated correctly + // as an IBC denomination. The hash used to store the token internally on our chain + // will be the same value as the base denomination being correctly parsed. + if i < length-1 && length > 2 && (channeltypes.IsValidChannelID(denomSplit[i+1]) || clienttypes.IsValidClientID(denomSplit[i+1])) { + trace = append(trace, NewHop(denomSplit[i], denomSplit[i+1])) + } else { + baseDenomSlice = denomSplit[i:] + break + } + } + + base := strings.Join(baseDenomSlice, "/") + + return Denom{ + Base: base, + Trace: trace, + } +} + +// validateIBCDenom validates that the given denomination is either: +// +// - A valid base denomination (eg: 'uatom' or 'gamm/pool/1' as in https://github.com/cosmos/ibc-go/issues/894) +// - A valid fungible token representation (i.e 'ibc/{hash}') per ADR 001 https://github.com/cosmos/ibc-go/blob/main/docs/architecture/adr-001-coin-source-tracing.md +func validateIBCDenom(denom string) error { + if err := sdk.ValidateDenom(denom); err != nil { + return err + } + + denomSplit := strings.SplitN(denom, "/", 2) + + switch { + case denom == DenomPrefix: + return errorsmod.Wrapf(ErrInvalidDenomForTransfer, "denomination should be prefixed with the format 'ibc/{hash(trace + \"/\" + %s)}'", denom) + + case len(denomSplit) == 2 && denomSplit[0] == DenomPrefix: + if strings.TrimSpace(denomSplit[1]) == "" { + return errorsmod.Wrapf(ErrInvalidDenomForTransfer, "denomination should be prefixed with the format 'ibc/{hash(trace + \"/\" + %s)}'", denom) + } + + if _, err := ParseHexHash(denomSplit[1]); err != nil { + return errorsmod.Wrapf(err, "invalid denom trace hash %s", denomSplit[1]) + } + } + + return nil +} + +// ParseHexHash parses a hex hash in string format to bytes and validates its correctness. +func ParseHexHash(hexHash string) (cmtbytes.HexBytes, error) { + hash, err := hex.DecodeString(hexHash) + if err != nil { + return nil, err + } + + if err := cmttypes.ValidateHash(hash); err != nil { + return nil, err + } + + return hash, nil +} diff --git a/modules/apps/transfer/types/denom_test.go b/modules/apps/transfer/types/denom_test.go new file mode 100644 index 0000000..15393f9 --- /dev/null +++ b/modules/apps/transfer/types/denom_test.go @@ -0,0 +1,333 @@ +package types_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +) + +func (suite *TypesTestSuite) TestDenomsValidate() { + testCases := []struct { + name string + denoms types.Denoms + expError error + }{ + { + "empty Denoms", + types.Denoms{}, + nil, + }, + { + "valid trace with client id", + types.Denoms{types.NewDenom("uatom", types.NewHop("transfer", "07-tendermint-0"))}, + nil, + }, + { + "valid multiple trace info", + types.Denoms{types.NewDenom("uatom", types.NewHop("transfer", "channel-1"), types.NewHop("transfer", "channel-2"))}, + nil, + }, + { + "valid multiple trace info", + types.Denoms{ + types.NewDenom("uatom", types.NewHop("transfer", "channel-1"), types.NewHop("transfer", "channel-2")), + types.NewDenom("uatom", types.NewHop("transfer", "channel-1"), types.NewHop("transfer", "channel-2")), + }, + errors.New("duplicated denomination with hash"), + }, + { + "empty base denom with trace", + types.Denoms{types.NewDenom("", types.NewHop("transfer", "channel-1"))}, + errors.New("base denomination cannot be blank"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := tc.denoms.Validate() + if tc.expError == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().ErrorContains(err, tc.expError.Error()) + } + }) + } +} + +func (suite *TypesTestSuite) TestPath() { + testCases := []struct { + name string + denom types.Denom + expPath string + }{ + { + "empty Denom", + types.Denom{}, + "", + }, + { + "only base denom", + types.NewDenom("uatom"), + "uatom", + }, + { + "base with slashes", + types.NewDenom("gamm/pool/osmo"), + "gamm/pool/osmo", + }, + { + "1 hop denom", + types.NewDenom("uatom", types.NewHop("transfer", "channel-0")), + "transfer/channel-0/uatom", + }, + { + "1 hop denom with client id", + types.NewDenom("uatom", types.NewHop("transfer", "07-tendermint-0")), + "transfer/07-tendermint-0/uatom", + }, + { + "1 hop denom with client id and slashes", + types.NewDenom("gamm/pool/osmo", types.NewHop("transfer", "07-tendermint-0")), + "transfer/07-tendermint-0/gamm/pool/osmo", + }, + { + "2 hop denom", + types.NewDenom("uatom", types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-52")), + "transfer/channel-0/transfer/channel-52/uatom", + }, + { + "3 hop denom", + types.NewDenom("uatom", types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-52"), types.NewHop("transfer", "channel-52")), + "transfer/channel-0/transfer/channel-52/transfer/channel-52/uatom", + }, + { + "4 hop denom with base denom slashes", + types.NewDenom("other-denom/", types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-52"), types.NewHop("transfer", "channel-52"), types.NewHop("transfer", "channel-49")), + "transfer/channel-0/transfer/channel-52/transfer/channel-52/transfer/channel-49/other-denom/", + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.Require().Equal(tc.expPath, tc.denom.Path()) + }) + } +} + +func (suite *TypesTestSuite) TestSort() { + testCases := []struct { + name string + denoms types.Denoms + expDenoms types.Denoms + }{ + { + "only base denom", + types.Denoms{types.NewDenom("uosmo"), types.NewDenom("gamm"), types.NewDenom("uatom")}, + types.Denoms{types.NewDenom("gamm"), types.NewDenom("uatom"), types.NewDenom("uosmo")}, + }, + { + "different base denom and same traces", + types.Denoms{ + types.NewDenom("uosmo", types.NewHop("transfer", "channel-0")), + types.NewDenom("gamm", types.NewHop("transfer", "channel-0")), + types.NewDenom("uatom", types.NewHop("transfer", "channel-0")), + }, + types.Denoms{ + types.NewDenom("gamm", types.NewHop("transfer", "channel-0")), + types.NewDenom("uatom", types.NewHop("transfer", "channel-0")), + types.NewDenom("uosmo", types.NewHop("transfer", "channel-0")), + }, + }, + { + "same base denom and different traces", + types.Denoms{ + types.NewDenom("uatom", types.NewHop("transfer", "channel-0")), + types.NewDenom("uatom", types.NewHop("mountain", "channel-0")), + types.NewDenom("uatom", types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-52"), types.NewHop("transfer", "channel-52")), + types.NewDenom("uatom", types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-52")), + types.NewDenom("uatom"), + }, + types.Denoms{ + types.NewDenom("uatom"), + types.NewDenom("uatom", types.NewHop("mountain", "channel-0")), + types.NewDenom("uatom", types.NewHop("transfer", "channel-0")), + types.NewDenom("uatom", types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-52")), + types.NewDenom("uatom", types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-52"), types.NewHop("transfer", "channel-52")), + }, + }, + { + "different base denoms and different traces", + types.Denoms{ + types.NewDenom("uatom", types.NewHop("transfer", "channel-0")), + types.NewDenom("gamm", types.NewHop("pool", "channel-0")), + types.NewDenom("gamm", types.NewHop("pool", "channel-0"), types.NewHop("transfer", "channel-52")), + types.NewDenom("uatom", types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-52"), types.NewHop("transfer", "channel-52")), + types.NewDenom("utia"), + types.NewDenom("gamm", types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-52")), + }, + types.Denoms{ + types.NewDenom("gamm", types.NewHop("pool", "channel-0")), + types.NewDenom("gamm", types.NewHop("pool", "channel-0"), types.NewHop("transfer", "channel-52")), + types.NewDenom("gamm", types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-52")), + types.NewDenom("uatom", types.NewHop("transfer", "channel-0")), + types.NewDenom("uatom", types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-52"), types.NewHop("transfer", "channel-52")), + types.NewDenom("utia"), + }, + }, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.Require().Equal(tc.expDenoms, tc.denoms.Sort()) + }) + } +} + +func (suite *TypesTestSuite) TestDenomChainSource() { + testCases := []struct { + name string + denom types.Denom + sourcePort string + sourceChannel string + expHasPrefix bool + }{ + { + "sender chain is source: empty trace", + types.NewDenom("uatom", []types.Hop{}...), + "transfer", + "channel-0", + false, + }, + { + "sender chain is source: nil trace", + types.NewDenom("uatom"), + "transfer", + "channel-0", + false, + }, + { + "sender chain is source: single trace", + types.NewDenom("ubtc", types.NewHop("transfer", "channel-1")), + "transfer", + "channel-0", + false, + }, + { + "sender chain is source: single trace with client id", + types.NewDenom("ubtc", types.NewHop("transfer", "07-tendermint-0")), + "transfer", + "channel-0", + false, + }, + { + "sender chain is source: swapped portID and channelID", + types.NewDenom("uatom", types.NewHop("transfer", "channel-0")), + "channel-0", + "transfer", + false, + }, + { + "sender chain is source: multi-trace", + types.NewDenom("uatom", types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-52")), + "transfer", + "channel-1", + false, + }, + { + "receiver chain is source: single trace", + types.NewDenom( + "factory/stars16da2uus9zrsy83h23ur42v3lglg5rmyrpqnju4/dust", + types.NewHop("transfer", "channel-0"), + ), + "transfer", + "channel-0", + true, + }, + { + "receiver chain is source: single trace with client id", + types.NewDenom("ubtc", types.NewHop("transfer", "07-tendermint-0")), + "transfer", + "07-tendermint-0", + true, + }, + { + "receiver chain is source: multi-trace", + types.NewDenom("uatom", types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-52")), + "transfer", + "channel-0", + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.Require().Equal(tc.expHasPrefix, tc.denom.HasPrefix(tc.sourcePort, tc.sourceChannel)) + }) + } +} + +func TestValidateIBCDenom(t *testing.T) { + testCases := []struct { + name string + denom string + expError bool + }{ + {"denom with trace hash", "ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2", false}, + {"base denom", "uatom", false}, + {"base denom ending with '/'", "uatom/", false}, + {"base denom with single '/'s", "gamm/pool/1", false}, + {"base denom with double '/'s", "gamm//pool//1", false}, + {"non-ibc prefix with hash", "notibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2", false}, + {"empty denom", "", true}, + {"denom 'ibc'", "ibc", true}, + {"denom 'ibc/'", "ibc/", true}, + {"invalid hash", "ibc/!@#$!@#", true}, + } + + for _, tc := range testCases { + + err := types.ValidateIBCDenom(tc.denom) + if tc.expError { + require.Error(t, err, tc.name) + continue + } + require.NoError(t, err, tc.name) + } +} + +func TestExtractDenomFromPath(t *testing.T) { + testCases := []struct { + name string + fullPath string + expDenom types.Denom + }{ + {"empty denom", "", types.Denom{}}, + {"base denom no slashes", "atom", types.NewDenom("atom")}, + {"base denom with trailing slash", "atom/", types.NewDenom("atom/")}, + {"base denom multiple trailing slash", "foo///bar//baz/atom/", types.NewDenom("foo///bar//baz/atom/")}, + {"ibc denom one hop", "transfer/channel-0/atom", types.NewDenom("atom", types.NewHop("transfer", "channel-0"))}, + {"ibc denom one hop with client id", "transfer/07-tendermint-0/atom", types.NewDenom("atom", types.NewHop("transfer", "07-tendermint-0"))}, + {"ibc denom one hop trailing slash", "transfer/channel-0/atom/", types.NewDenom("atom/", types.NewHop("transfer", "channel-0"))}, + {"ibc denom one hop multiple slashes", "transfer/channel-0//at/om/", types.NewDenom("/at/om/", types.NewHop("transfer", "channel-0"))}, + {"ibc denom two hops", "transfer/channel-0/transfer/channel-60/atom", types.NewDenom("atom", types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-60"))}, + {"ibc denom two hops trailing slash", "transfer/channel-0/transfer/channel-60/atom/", types.NewDenom("atom/", types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-60"))}, + {"empty prefix", "/uatom", types.NewDenom("/uatom")}, + {"empty identifiers", "//uatom", types.NewDenom("//uatom")}, + {"base denom with single '/'", "erc20/0x85bcBCd7e79Ec36f4fBBDc54F90C643d921151AA", types.NewDenom("erc20/0x85bcBCd7e79Ec36f4fBBDc54F90C643d921151AA")}, + {"trace info and base denom with single '/'", "transfer/channel-1/erc20/0x85bcBCd7e79Ec36f4fBBDc54F90C643d921151AA", types.NewDenom("erc20/0x85bcBCd7e79Ec36f4fBBDc54F90C643d921151AA", types.NewHop("transfer", "channel-1"))}, + {"single trace identifier", "transfer/", types.NewDenom("transfer/")}, + {"trace info with custom port", "customtransfer/channel-1/uatom", types.NewDenom("uatom", types.NewHop("customtransfer", "channel-1"))}, + {"invalid path (1)", "channel-1/transfer/uatom", types.NewDenom("channel-1/transfer/uatom")}, + {"invalid path (2)", "transfer/channel-1", types.NewDenom("transfer/channel-1")}, + {"invalid path (3)", "transfer/channel-1/transfer/channel-2", types.NewDenom("", types.NewHop("transfer", "channel-1"), types.NewHop("transfer", "channel-2"))}, + {"invalid path (4)", "transfer/channelToA/uatom", types.NewDenom("transfer/channelToA/uatom")}, + } + + for _, tc := range testCases { + + denom := types.ExtractDenomFromPath(tc.fullPath) + require.Equal(t, tc.expDenom, denom, tc.name) + } +} diff --git a/modules/apps/transfer/types/deprecated.go b/modules/apps/transfer/types/deprecated.go new file mode 100644 index 0000000..cd6e7e4 --- /dev/null +++ b/modules/apps/transfer/types/deprecated.go @@ -0,0 +1,60 @@ +package types + +import ( + fmt "fmt" + "strings" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Deprecated: usage of this function should be replaced by `Denom.hasPrefix` +// SenderChainIsSource returns false if the denomination originally came +// from the receiving chain and true otherwise. +func SenderChainIsSource(sourcePort, sourceChannel, denom string) bool { + // This is the prefix that would have been prefixed to the denomination + // on sender chain IF and only if the token originally came from the + // receiving chain. + + return !ReceiverChainIsSource(sourcePort, sourceChannel, denom) +} + +// Deprecated: usage of this function should be replaced by `Denom.hasPrefix` +// ReceiverChainIsSource returns true if the denomination originally came +// from the receiving chain and false otherwise. +func ReceiverChainIsSource(sourcePort, sourceChannel, denom string) bool { + // The prefix passed in should contain the SourcePort and SourceChannel. + // If the receiver chain originally sent the token to the sender chain + // the denom will have the sender's SourcePort and SourceChannel as the + // prefix. + + voucherPrefix := GetDenomPrefix(sourcePort, sourceChannel) + return strings.HasPrefix(denom, voucherPrefix) +} + +// Deprecated: usage of this function should be replaced by `NewHop` +// GetDenomPrefix returns the receiving denomination prefix +func GetDenomPrefix(portID, channelID string) string { + return fmt.Sprintf("%s/%s/", portID, channelID) +} + +// Deprecated: usage of this function should be replaced by `NewDenom` +// GetPrefixedDenom returns the denomination with the portID and channelID prefixed +func GetPrefixedDenom(portID, channelID, baseDenom string) string { + return fmt.Sprintf("%s/%s/%s", portID, channelID, baseDenom) +} + +// Deprecated: usage of this function should be replaced by `Token.ToCoin` +// GetTransferCoin creates a transfer coin with the port ID and channel ID +// prefixed to the base denom. +func GetTransferCoin(portID, channelID, baseDenom string, amount sdkmath.Int) sdk.Coin { + denomTrace := ExtractDenomFromPath(GetPrefixedDenom(portID, channelID, baseDenom)) + return sdk.NewCoin(denomTrace.IBCDenom(), amount) +} + +// Deprecated: usage of this function should be replaced by `ExtractDenomFromPath` +// ExtractDenomFromPath returns the denom from the full path. +func ParseDenomTrace(rawDenom string) Denom { + return ExtractDenomFromPath(rawDenom) +} diff --git a/modules/apps/transfer/types/encoding.go b/modules/apps/transfer/types/encoding.go new file mode 100644 index 0000000..a08d68f --- /dev/null +++ b/modules/apps/transfer/types/encoding.go @@ -0,0 +1,26 @@ +package types + +import ( + "bytes" + "encoding/json" +) + +// UnmarshalJSON implements the Unmarshaller interface for FungibleTokenPacketData. +func (ftpd *FungibleTokenPacketData) UnmarshalJSON(bz []byte) error { + // Recursion protection. We cannot unmarshal into FungibleTokenPacketData directly + // else UnmarshalJSON is going to get invoked again, ad infinum. Create an alias + // and unmarshal into that, instead. + type ftpdAlias FungibleTokenPacketData + + d := json.NewDecoder(bytes.NewReader(bz)) + // Raise errors during decoding if unknown fields are encountered. + d.DisallowUnknownFields() + + var alias ftpdAlias + if err := d.Decode(&alias); err != nil { + return err + } + + *ftpd = FungibleTokenPacketData(alias) + return nil +} diff --git a/modules/apps/transfer/types/errors.go b/modules/apps/transfer/types/errors.go new file mode 100644 index 0000000..74c6adf --- /dev/null +++ b/modules/apps/transfer/types/errors.go @@ -0,0 +1,24 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" +) + +// IBC transfer sentinel errors +var ( + ErrInvalidPacketTimeout = errorsmod.Register(ModuleName, 2, "invalid packet timeout") + ErrInvalidDenomForTransfer = errorsmod.Register(ModuleName, 3, "invalid denomination for cross-chain transfer") + ErrInvalidVersion = errorsmod.Register(ModuleName, 4, "invalid ICS20 version") + ErrInvalidAmount = errorsmod.Register(ModuleName, 5, "invalid token amount") + ErrDenomNotFound = errorsmod.Register(ModuleName, 6, "denomination not found") + ErrSendDisabled = errorsmod.Register(ModuleName, 7, "fungible token transfers from this chain are disabled") + ErrReceiveDisabled = errorsmod.Register(ModuleName, 8, "fungible token transfers to this chain are disabled") + ErrMaxTransferChannels = errorsmod.Register(ModuleName, 9, "max transfer channels") + ErrInvalidAuthorization = errorsmod.Register(ModuleName, 10, "invalid transfer authorization") + ErrInvalidMemo = errorsmod.Register(ModuleName, 11, "invalid memo") + ErrForwardedPacketTimedOut = errorsmod.Register(ModuleName, 12, "forwarded packet timed out") + ErrForwardedPacketFailed = errorsmod.Register(ModuleName, 13, "forwarded packet failed") + ErrAbiEncoding = errorsmod.Register(ModuleName, 14, "encoding abi failed") + ErrAbiDecoding = errorsmod.Register(ModuleName, 15, "decoding abi failed") + ErrReceiveFailed = errorsmod.Register(ModuleName, 16, "receive packet failed") +) diff --git a/modules/apps/transfer/types/events.go b/modules/apps/transfer/types/events.go new file mode 100644 index 0000000..1924d38 --- /dev/null +++ b/modules/apps/transfer/types/events.go @@ -0,0 +1,22 @@ +package types + +// IBC transfer events +const ( + EventTypeTimeout = "timeout" + EventTypePacket = "fungible_token_packet" + EventTypeTransfer = "ibc_transfer" + EventTypeChannelClose = "channel_closed" + EventTypeDenom = "denomination" + + AttributeKeySender = "sender" + AttributeKeyReceiver = "receiver" + AttributeKeyDenom = "denom" + AttributeKeyDenomHash = "denom_hash" + AttributeKeyAmount = "amount" + AttributeKeyRefundReceiver = "refund_receiver" + AttributeKeyRefundTokens = "refund_tokens" + AttributeKeyAckSuccess = "success" + AttributeKeyAck = "acknowledgement" + AttributeKeyAckError = "error" + AttributeKeyMemo = "memo" +) diff --git a/modules/apps/transfer/types/expected_keepers.go b/modules/apps/transfer/types/expected_keepers.go new file mode 100644 index 0000000..0251a88 --- /dev/null +++ b/modules/apps/transfer/types/expected_keepers.go @@ -0,0 +1,70 @@ +package types + +import ( + "context" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + + clienttypesv2 "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" +) + +// AccountKeeper defines the contract required for account APIs. +type AccountKeeper interface { + GetModuleAddress(name string) sdk.AccAddress + GetModuleAccount(ctx context.Context, name string) sdk.ModuleAccountI +} + +// BankKeeper defines the expected bank keeper +type BankKeeper interface { + SendCoins(ctx context.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error + MintCoins(ctx context.Context, moduleName string, amt sdk.Coins) error + BurnCoins(ctx context.Context, moduleName string, amt sdk.Coins) error + SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error + SendCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + BlockedAddr(addr sdk.AccAddress) bool + IsSendEnabledCoins(ctx context.Context, coins ...sdk.Coin) error + HasDenomMetaData(ctx context.Context, denom string) bool + SetDenomMetaData(ctx context.Context, denomMetaData banktypes.Metadata) + SpendableCoin(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin + GetAllBalances(ctx context.Context, addr sdk.AccAddress) sdk.Coins +} + +// ChannelKeeper defines the expected IBC channel keeper +type ChannelKeeper interface { + GetChannel(ctx sdk.Context, srcPort, srcChan string) (channel channeltypes.Channel, found bool) + GetNextSequenceSend(ctx sdk.Context, portID, channelID string) (uint64, bool) + GetAllChannelsWithPortPrefix(ctx sdk.Context, portPrefix string) []channeltypes.IdentifiedChannel + HasChannel(ctx sdk.Context, portID, channelID string) bool +} + +// MessageRouter ADR 031 request type routing +// https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-031-msg-service.md +type MessageRouter interface { + Handler(msg sdk.Msg) baseapp.MsgServiceHandler +} + +// ChannelKeeperV2 defines the expected IBC channelV2 keeper +type ChannelKeeperV2 interface { + SendPacket(ctx context.Context, msg *channeltypesv2.MsgSendPacket) (*channeltypesv2.MsgSendPacketResponse, error) +} + +// ClientKeeper defines the expected IBC client keeper +type ClientKeeperV2 interface { + GetClientCounterparty(ctx sdk.Context, clientID string) (counterparty clienttypesv2.CounterpartyInfo, found bool) +} + +// ConnectionKeeper defines the expected IBC connection keeper +type ConnectionKeeper interface { + GetConnection(ctx sdk.Context, connectionID string) (connection connectiontypes.ConnectionEnd, found bool) +} + +// ParamSubspace defines the expected Subspace interface for module parameters. +type ParamSubspace interface { + GetParamSet(ctx sdk.Context, ps paramtypes.ParamSet) +} diff --git a/modules/apps/transfer/types/export_test.go b/modules/apps/transfer/types/export_test.go new file mode 100644 index 0000000..31b81a4 --- /dev/null +++ b/modules/apps/transfer/types/export_test.go @@ -0,0 +1,6 @@ +package types + +// ValidateIBCDenom is a wrapper around validateIBCDenom for testing purposes. +func ValidateIBCDenom(denom string) error { + return validateIBCDenom(denom) +} diff --git a/modules/apps/transfer/types/genesis.go b/modules/apps/transfer/types/genesis.go new file mode 100644 index 0000000..054a264 --- /dev/null +++ b/modules/apps/transfer/types/genesis.go @@ -0,0 +1,39 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +// NewGenesisState creates a new ibc-transfer GenesisState instance. +func NewGenesisState(portID string, denoms Denoms, params Params, totalEscrowed sdk.Coins) *GenesisState { + return &GenesisState{ + PortId: portID, + Denoms: denoms, + Params: params, + TotalEscrowed: totalEscrowed, + } +} + +// DefaultGenesisState returns a GenesisState with "transfer" as the default PortID. +func DefaultGenesisState() *GenesisState { + return &GenesisState{ + PortId: PortID, + Denoms: Denoms{}, + Params: DefaultParams(), + TotalEscrowed: sdk.Coins{}, + } +} + +// Validate performs basic genesis state validation returning an error upon any +// failure. +func (gs GenesisState) Validate() error { + if err := host.PortIdentifierValidator(gs.PortId); err != nil { + return err + } + if err := gs.Denoms.Validate(); err != nil { + return err + } + return gs.TotalEscrowed.Validate() // will fail if there are duplicates for any denom +} diff --git a/modules/apps/transfer/types/genesis.pb.go b/modules/apps/transfer/types/genesis.pb.go new file mode 100644 index 0000000..dc496b6 --- /dev/null +++ b/modules/apps/transfer/types/genesis.pb.go @@ -0,0 +1,513 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/applications/transfer/v1/genesis.proto + +package types + +import ( + fmt "fmt" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" + types "github.com/cosmos/cosmos-sdk/types" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// GenesisState defines the ibc-transfer genesis state +type GenesisState struct { + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + Denoms Denoms `protobuf:"bytes,2,rep,name=denoms,proto3,castrepeated=Denoms" json:"denoms"` + Params Params `protobuf:"bytes,3,opt,name=params,proto3" json:"params"` + // total_escrowed contains the total amount of tokens escrowed + // by the transfer module + TotalEscrowed github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,4,rep,name=total_escrowed,json=totalEscrowed,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"total_escrowed"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_a4f788affd5bea89, []int{0} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetPortId() string { + if m != nil { + return m.PortId + } + return "" +} + +func (m *GenesisState) GetDenoms() Denoms { + if m != nil { + return m.Denoms + } + return nil +} + +func (m *GenesisState) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +func (m *GenesisState) GetTotalEscrowed() github_com_cosmos_cosmos_sdk_types.Coins { + if m != nil { + return m.TotalEscrowed + } + return nil +} + +func init() { + proto.RegisterType((*GenesisState)(nil), "ibc.applications.transfer.v1.GenesisState") +} + +func init() { + proto.RegisterFile("ibc/applications/transfer/v1/genesis.proto", fileDescriptor_a4f788affd5bea89) +} + +var fileDescriptor_a4f788affd5bea89 = []byte{ + // 374 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x91, 0xc1, 0x4e, 0xe3, 0x30, + 0x10, 0x86, 0x93, 0xb6, 0xca, 0x6a, 0xd3, 0xdd, 0x1e, 0xa2, 0x95, 0x36, 0x5b, 0xad, 0xd2, 0x0a, + 0x38, 0x44, 0xa0, 0xda, 0x4d, 0xb9, 0x70, 0x0e, 0x20, 0x84, 0xb8, 0xa0, 0x70, 0xe3, 0x52, 0x39, + 0x8e, 0x09, 0x56, 0x9b, 0x4c, 0x14, 0xbb, 0x41, 0xbc, 0x05, 0xcf, 0x81, 0x78, 0x90, 0x1e, 0x7b, + 0xe4, 0x04, 0xa8, 0x7d, 0x11, 0x14, 0x27, 0x45, 0x95, 0x90, 0x72, 0xf2, 0x8c, 0xfd, 0xcd, 0x3f, + 0xe3, 0x7f, 0xcc, 0x43, 0x1e, 0x52, 0x4c, 0xb2, 0x6c, 0xce, 0x29, 0x91, 0x1c, 0x52, 0x81, 0x65, + 0x4e, 0x52, 0x71, 0xc7, 0x72, 0x5c, 0x78, 0x38, 0x66, 0x29, 0x13, 0x5c, 0xa0, 0x2c, 0x07, 0x09, + 0xd6, 0x7f, 0x1e, 0x52, 0xb4, 0xcb, 0xa2, 0x2d, 0x8b, 0x0a, 0xaf, 0x7f, 0xd4, 0xa8, 0xf4, 0x45, + 0x2a, 0xa9, 0xbe, 0xdb, 0x0c, 0xc3, 0x8c, 0xa5, 0x35, 0xe9, 0x50, 0x10, 0x09, 0x08, 0x1c, 0x12, + 0xc1, 0x70, 0xe1, 0x85, 0x4c, 0x12, 0x0f, 0x53, 0xe0, 0xdb, 0xf7, 0x3f, 0x31, 0xc4, 0xa0, 0x42, + 0x5c, 0x46, 0xd5, 0xed, 0xde, 0x4b, 0xcb, 0xfc, 0x75, 0x51, 0x0d, 0x7f, 0x23, 0x89, 0x64, 0xd6, + 0x5f, 0xf3, 0x47, 0x06, 0xb9, 0x9c, 0xf2, 0xc8, 0xd6, 0x87, 0xba, 0xfb, 0x33, 0x30, 0xca, 0xf4, + 0x32, 0xb2, 0xae, 0x4c, 0x23, 0x62, 0x29, 0x24, 0xc2, 0x6e, 0x0d, 0xdb, 0x6e, 0x77, 0xb2, 0x8f, + 0x9a, 0x7e, 0x89, 0xce, 0x4a, 0xd6, 0xef, 0x2d, 0xdf, 0x06, 0xda, 0xf3, 0xfb, 0xc0, 0x50, 0xa9, + 0x08, 0x6a, 0x09, 0xcb, 0x37, 0x8d, 0x8c, 0xe4, 0x24, 0x11, 0x76, 0x7b, 0xa8, 0xbb, 0xdd, 0xc9, + 0x41, 0xb3, 0xd8, 0xb5, 0x62, 0xfd, 0x4e, 0xa9, 0x16, 0xd4, 0x95, 0x56, 0x6e, 0xf6, 0x24, 0x48, + 0x32, 0x9f, 0x32, 0x41, 0x73, 0x78, 0x60, 0x91, 0xdd, 0x51, 0x83, 0xfd, 0x43, 0x95, 0x13, 0xa8, + 0x74, 0x02, 0xd5, 0x4e, 0xa0, 0x53, 0xe0, 0xa9, 0x3f, 0xae, 0xc7, 0x71, 0x63, 0x2e, 0xef, 0x17, + 0x21, 0xa2, 0x90, 0xe0, 0xda, 0xb6, 0xea, 0x18, 0x89, 0x68, 0x86, 0xe5, 0x63, 0xc6, 0x84, 0x2a, + 0x10, 0xc1, 0x6f, 0xd5, 0xe2, 0xbc, 0xee, 0xe0, 0x07, 0xcb, 0xb5, 0xa3, 0xaf, 0xd6, 0x8e, 0xfe, + 0xb1, 0x76, 0xf4, 0xa7, 0x8d, 0xa3, 0xad, 0x36, 0x8e, 0xf6, 0xba, 0x71, 0xb4, 0xdb, 0x93, 0xef, + 0x92, 0x3c, 0xa4, 0xa3, 0x18, 0x70, 0xe1, 0x8d, 0x71, 0x02, 0xd1, 0x62, 0xce, 0x44, 0xb9, 0xc9, + 0x9d, 0x0d, 0xaa, 0x46, 0xa1, 0xa1, 0x36, 0x71, 0xfc, 0x19, 0x00, 0x00, 0xff, 0xff, 0x83, 0x55, + 0x07, 0xb2, 0x62, 0x02, 0x00, 0x00, +} + +func (m *GenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.TotalEscrowed) > 0 { + for iNdEx := len(m.TotalEscrowed) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.TotalEscrowed[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + } + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.Denoms) > 0 { + for iNdEx := len(m.Denoms) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Denoms[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + if len(m.Denoms) > 0 { + for _, e := range m.Denoms { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + l = m.Params.Size() + n += 1 + l + sovGenesis(uint64(l)) + if len(m.TotalEscrowed) > 0 { + for _, e := range m.TotalEscrowed { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Denoms", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Denoms = append(m.Denoms, Denom{}) + if err := m.Denoms[len(m.Denoms)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TotalEscrowed", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.TotalEscrowed = append(m.TotalEscrowed, types.Coin{}) + if err := m.TotalEscrowed[len(m.TotalEscrowed)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/apps/transfer/types/genesis_test.go b/modules/apps/transfer/types/genesis_test.go new file mode 100644 index 0000000..14388c3 --- /dev/null +++ b/modules/apps/transfer/types/genesis_test.go @@ -0,0 +1,48 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +func TestValidateGenesis(t *testing.T) { + testCases := []struct { + name string + genState *types.GenesisState + expErr error + }{ + { + name: "default", + genState: types.DefaultGenesisState(), + expErr: nil, + }, + { + "valid genesis", + &types.GenesisState{ + PortId: "portidone", + }, + nil, + }, + { + "invalid client", + &types.GenesisState{ + PortId: "(INVALIDPORT)", + }, + host.ErrInvalidID, + }, + } + + for _, tc := range testCases { + + err := tc.genState.Validate() + if tc.expErr == nil { + require.NoError(t, err, tc.name) + } else { + require.ErrorIs(t, err, tc.expErr, tc.name) + } + } +} diff --git a/modules/apps/transfer/types/hop.go b/modules/apps/transfer/types/hop.go new file mode 100644 index 0000000..d1fdeff --- /dev/null +++ b/modules/apps/transfer/types/hop.go @@ -0,0 +1,32 @@ +package types + +import ( + fmt "fmt" + + errorsmod "cosmossdk.io/errors" + + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +// NewHop creates a Hop with the given port ID and channel ID. +func NewHop(portID, channelID string) Hop { + return Hop{portID, channelID} +} + +// Validate performs a basic validation of the Hop fields. +func (h Hop) Validate() error { + if err := host.PortIdentifierValidator(h.PortId); err != nil { + return errorsmod.Wrapf(err, "invalid hop source port ID %s", h.PortId) + } + if err := host.ChannelIdentifierValidator(h.ChannelId); err != nil { + return errorsmod.Wrapf(err, "invalid hop source channel ID %s", h.ChannelId) + } + + return nil +} + +// String returns the Hop in the format: +// / +func (h Hop) String() string { + return fmt.Sprintf("%s/%s", h.PortId, h.ChannelId) +} diff --git a/modules/apps/transfer/types/hop_test.go b/modules/apps/transfer/types/hop_test.go new file mode 100644 index 0000000..7521133 --- /dev/null +++ b/modules/apps/transfer/types/hop_test.go @@ -0,0 +1,68 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func TestValidateHop(t *testing.T) { + tests := []struct { + name string + hop types.Hop + expError error + }{ + { + "valid hop", + types.NewHop(types.PortID, ibctesting.FirstChannelID), + nil, + }, + { + "invalid hop with too short port ID", + types.NewHop(invalidShortPort, ibctesting.FirstChannelID), + host.ErrInvalidID, + }, + { + "invalid hop with too long port ID", + types.NewHop(invalidLongPort, ibctesting.FirstChannelID), + host.ErrInvalidID, + }, + { + "invalid hop with non-alpha port ID", + types.NewHop(invalidPort, ibctesting.FirstChannelID), + host.ErrInvalidID, + }, + { + "invalid hop with too long channel ID", + types.NewHop(types.PortID, invalidLongChannel), + host.ErrInvalidID, + }, + { + "invalid hop with too short channel ID", + types.NewHop(types.PortID, invalidShortChannel), + host.ErrInvalidID, + }, + { + "invalid hop with non-alpha channel ID", + types.NewHop(types.PortID, invalidChannel), + host.ErrInvalidID, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + tc := tc + + err := tc.hop.Validate() + + if tc.expError == nil { + require.NoError(t, err) + } else { + require.ErrorIs(t, err, tc.expError) + } + }) + } +} diff --git a/modules/apps/transfer/types/keys.go b/modules/apps/transfer/types/keys.go new file mode 100644 index 0000000..03a6f68 --- /dev/null +++ b/modules/apps/transfer/types/keys.go @@ -0,0 +1,77 @@ +package types + +import ( + "crypto/sha256" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + // ModuleName defines the IBC transfer name + ModuleName = "transfer" + + // PortID is the default port id that transfer module binds to + PortID = "transfer" + + // StoreKey is the store key string for IBC transfer + StoreKey = ModuleName + + // RouterKey is the message route for IBC transfer + RouterKey = ModuleName + + // QuerierRoute is the querier route for IBC transfer + QuerierRoute = ModuleName + + // DenomPrefix is the prefix used for internal SDK coin representation. + DenomPrefix = "ibc" + + // AllowAllPacketDataKeys holds the string key that allows all memo strings in authz transfer messages + AllowAllPacketDataKeys = "*" + + KeyTotalEscrowPrefix = "totalEscrowForDenom" + + ParamsKey = "params" + + // V1 defines first version of the IBC transfer module + V1 = "ics20-1" + + // escrowAddressVersion should remain as ics20-1 to avoid the address changing. + // this address has been reasoned about to avoid collisions with other addresses + // https://github.com/cosmos/cosmos-sdk/issues/7737#issuecomment-735671951 + escrowAddressVersion = V1 +) + +var ( + // PortKey defines the key to store the port ID in store + PortKey = []byte{0x01} + // DenomTraceKey defines the key to store the denomination trace info in store + DenomTraceKey = []byte{0x02} + // DenomKey defines the key to store the token denomination in store + DenomKey = []byte{0x03} + + // SupportedVersions defines all versions that are supported by the module + SupportedVersions = []string{V1} +) + +// GetEscrowAddress returns the escrow address for the specified channel. +// The escrow address follows the format as outlined in ADR 028: +// https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-028-public-key-addresses.md +func GetEscrowAddress(portID, channelID string) sdk.AccAddress { + // a slash is used to create domain separation between port and channel identifiers to + // prevent address collisions between escrow addresses created for different channels + contents := fmt.Sprintf("%s/%s", portID, channelID) + + // ADR 028 AddressHash construction + preImage := []byte(escrowAddressVersion) + preImage = append(preImage, 0) + preImage = append(preImage, contents...) + hash := sha256.Sum256(preImage) + return hash[:20] +} + +// TotalEscrowForDenomKey returns the store key of under which the total amount of +// source chain tokens in escrow is stored. +func TotalEscrowForDenomKey(denom string) []byte { + return fmt.Appendf(nil, "%s/%s", KeyTotalEscrowPrefix, denom) +} diff --git a/modules/apps/transfer/types/keys_test.go b/modules/apps/transfer/types/keys_test.go new file mode 100644 index 0000000..bc54db6 --- /dev/null +++ b/modules/apps/transfer/types/keys_test.go @@ -0,0 +1,24 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +) + +// Test that there is domain separation between the port id and the channel id otherwise an +// escrow address may overlap with another channel end +func TestGetEscrowAddress(t *testing.T) { + var ( + port1 = "transfer" + channel1 = "channel" + port2 = "transfercha" + channel2 = "nnel" + ) + + escrow1 := types.GetEscrowAddress(port1, channel1) + escrow2 := types.GetEscrowAddress(port2, channel2) + require.NotEqual(t, escrow1, escrow2) +} diff --git a/modules/apps/transfer/types/msgs.go b/modules/apps/transfer/types/msgs.go new file mode 100644 index 0000000..7bea854 --- /dev/null +++ b/modules/apps/transfer/types/msgs.go @@ -0,0 +1,147 @@ +package types + +import ( + "strings" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +const ( + MaximumReceiverLength = 2048 // maximum length of the receiver address in bytes (value chosen arbitrarily) + MaximumMemoLength = 32768 // maximum length of the memo in bytes (value chosen arbitrarily) +) + +var ( + _ sdk.Msg = (*MsgUpdateParams)(nil) + _ sdk.Msg = (*MsgTransfer)(nil) + _ sdk.HasValidateBasic = (*MsgUpdateParams)(nil) + _ sdk.HasValidateBasic = (*MsgTransfer)(nil) +) + +// NewMsgUpdateParams creates a new MsgUpdateParams instance +func NewMsgUpdateParams(signer string, params Params) *MsgUpdateParams { + return &MsgUpdateParams{ + Signer: signer, + Params: params, + } +} + +// ValidateBasic implements sdk.Msg +func (msg MsgUpdateParams) ValidateBasic() error { + if strings.TrimSpace(msg.Signer) == "" { + return errorsmod.Wrap(ibcerrors.ErrInvalidAddress, "missing sender address") + } + + return nil +} + +// NewMsgTransfer creates a new MsgTransfer instance +func NewMsgTransfer( + sourcePort, sourceChannel string, + token sdk.Coin, sender, receiver string, + timeoutHeight clienttypes.Height, timeoutTimestamp uint64, + memo string, +) *MsgTransfer { + return &MsgTransfer{ + SourcePort: sourcePort, + SourceChannel: sourceChannel, + Token: token, + Sender: sender, + Receiver: receiver, + TimeoutHeight: timeoutHeight, + TimeoutTimestamp: timeoutTimestamp, + Memo: memo, + } +} + +// NewMsgTransferWithEncoding creates a new MsgTransfer instance +// with the provided encoding +func NewMsgTransferWithEncoding( + sourcePort, sourceChannel string, + token sdk.Coin, sender, receiver string, + timeoutHeight clienttypes.Height, timeoutTimestamp uint64, + memo string, encoding string, +) *MsgTransfer { + return &MsgTransfer{ + SourcePort: sourcePort, + SourceChannel: sourceChannel, + Token: token, + Sender: sender, + Receiver: receiver, + TimeoutHeight: timeoutHeight, + TimeoutTimestamp: timeoutTimestamp, + Memo: memo, + Encoding: encoding, + } +} + +// ValidateBasic performs a basic check of the MsgTransfer fields. +// NOTE: If you are sending with V1 protocol, timeoutHeight or timeoutTimestamp must be non-zero, +// if you are sending with V2 protocol, timeoutTimestamp must be non-zero and timeoutHeight must be zero +// NOTE: The recipient addresses format is not validated as the format defined by +// the chain is not known to IBC. +func (msg MsgTransfer) ValidateBasic() error { + if err := msg.validateIdentifiers(); err != nil { + return err + } + + if !isValidIBCCoin(msg.Token) { + return errorsmod.Wrap(ibcerrors.ErrInvalidCoins, msg.Token.String()) + } + + if strings.TrimSpace(msg.Sender) == "" { + return errorsmod.Wrap(ibcerrors.ErrInvalidAddress, "missing sender address") + } + if strings.TrimSpace(msg.Receiver) == "" { + return errorsmod.Wrap(ibcerrors.ErrInvalidAddress, "missing recipient address") + } + if len(msg.Receiver) > MaximumReceiverLength { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "recipient address must not exceed %d bytes", MaximumReceiverLength) + } + if len(msg.Memo) > MaximumMemoLength { + return errorsmod.Wrapf(ErrInvalidMemo, "memo must not exceed %d bytes", MaximumMemoLength) + } + + return nil +} + +// validateIdentifiers checks if the source port and channel identifiers are valid +func (msg MsgTransfer) validateIdentifiers() error { + if err := host.PortIdentifierValidator(msg.SourcePort); err != nil { + return errorsmod.Wrapf(err, "invalid source port ID %s", msg.SourcePort) + } + if err := host.ChannelIdentifierValidator(msg.SourceChannel); err != nil { + return errorsmod.Wrapf(err, "invalid source channel ID %s", msg.SourceChannel) + } + + return nil +} + +// isValidIBCCoin returns true if the token provided is valid, +// and should be used to transfer tokens. +func isValidIBCCoin(coin sdk.Coin) bool { + return validateIBCCoin(coin) == nil +} + +// validateIBCCoin returns true if the token provided is valid, +// and should be used to transfer tokens. The token must +// have a positive amount. +func validateIBCCoin(coin sdk.Coin) error { + if err := coin.Validate(); err != nil { + return err + } + if !coin.IsPositive() { + return errorsmod.Wrap(ErrInvalidAmount, "amount must be positive") + } + if err := validateIBCDenom(coin.GetDenom()); err != nil { + return errorsmod.Wrap(ErrInvalidDenomForTransfer, err.Error()) + } + + return nil +} diff --git a/modules/apps/transfer/types/msgs_test.go b/modules/apps/transfer/types/msgs_test.go new file mode 100644 index 0000000..07a8b8c --- /dev/null +++ b/modules/apps/transfer/types/msgs_test.go @@ -0,0 +1,157 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdkmath "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +// define constants used for testing +const ( + validPort = "testportid" + invalidPort = "(invalidport1)" + invalidShortPort = "p" + // 195 characters + invalidLongPort = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis eros neque, ultricies vel ligula ac, convallis porttitor elit. Maecenas tincidunt turpis elit, vel faucibus nisl pellentesque sodales" + + validChannel = "testchannel" + eurekaClient = "07-tendermint-0" + invalidChannel = "(invalidchannel1)" + invalidShortChannel = "invalid" + invalidLongChannel = "invalidlongchannelinvalidlongchannelinvalidlongchannelinvalidlongchannel" +) + +var ( + sender = sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()).String() + receiver = sdk.AccAddress("testaddr2").String() + emptyAddr string + + coin = ibctesting.TestCoin + ibcCoin = sdk.NewCoin("ibc/7F1D3FCF4AE79E1554D670D1AD949A9BA4E4A3C76C63093E17E446A46061A7A2", sdkmath.NewInt(100)) + invalidIBCCoin = sdk.NewCoin("ibc/7F1D3FCF4AE79E1554", sdkmath.NewInt(100)) + invalidDenomCoin = sdk.Coin{Denom: "0atom", Amount: sdkmath.NewInt(100)} + zeroCoin = sdk.Coin{Denom: "atoms", Amount: sdkmath.NewInt(0)} + + timeoutHeight = clienttypes.NewHeight(0, 10) +) + +// TestMsgTransferValidation tests ValidateBasic for MsgTransfer +func TestMsgTransferValidation(t *testing.T) { + testCases := []struct { + name string + msg *types.MsgTransfer + expError error + }{ + {"valid msg with base denom", types.NewMsgTransfer(validPort, validChannel, coin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), nil}, + {"valid eureka msg with base denom", types.NewMsgTransfer(validPort, eurekaClient, coin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), nil}, + {"valid eureka msg with base denom and encoding", types.NewMsgTransferWithEncoding(validPort, eurekaClient, coin, sender, receiver, clienttypes.ZeroHeight(), 100, "", "application/json"), nil}, + {"valid msg with trace hash", types.NewMsgTransfer(validPort, validChannel, ibcCoin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), nil}, + {"valid eureka msg with trace hash", types.NewMsgTransfer(validPort, eurekaClient, ibcCoin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), nil}, + {"valid eureka msg with trace hash with encoding", types.NewMsgTransferWithEncoding(validPort, eurekaClient, ibcCoin, sender, receiver, clienttypes.ZeroHeight(), 100, "", "application/json"), nil}, + {"invalid ibc denom", types.NewMsgTransfer(validPort, validChannel, invalidIBCCoin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), ibcerrors.ErrInvalidCoins}, + {"too short port id", types.NewMsgTransfer(invalidShortPort, validChannel, coin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), host.ErrInvalidID}, + {"too long port id", types.NewMsgTransfer(invalidLongPort, validChannel, coin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), host.ErrInvalidID}, + {"port id contains non-alpha", types.NewMsgTransfer(invalidPort, validChannel, coin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), host.ErrInvalidID}, + {"too short channel id", types.NewMsgTransfer(validPort, invalidShortChannel, coin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), host.ErrInvalidID}, + {"too long channel id", types.NewMsgTransfer(validPort, invalidLongChannel, coin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), host.ErrInvalidID}, + {"too long memo", types.NewMsgTransfer(validPort, validChannel, coin, sender, receiver, clienttypes.ZeroHeight(), 100, ibctesting.GenerateString(types.MaximumMemoLength+1)), types.ErrInvalidMemo}, + {"channel id contains non-alpha", types.NewMsgTransfer(validPort, invalidChannel, coin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), host.ErrInvalidID}, + {"invalid denom", types.NewMsgTransfer(validPort, validChannel, invalidDenomCoin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), ibcerrors.ErrInvalidCoins}, + {"zero coin", types.NewMsgTransfer(validPort, validChannel, zeroCoin, sender, receiver, clienttypes.ZeroHeight(), 100, ""), ibcerrors.ErrInvalidCoins}, + {"missing sender address", types.NewMsgTransfer(validPort, validChannel, coin, emptyAddr, receiver, clienttypes.ZeroHeight(), 100, ""), ibcerrors.ErrInvalidAddress}, + {"missing recipient address", types.NewMsgTransfer(validPort, validChannel, coin, sender, "", clienttypes.ZeroHeight(), 100, ""), ibcerrors.ErrInvalidAddress}, + {"too long recipient address", types.NewMsgTransfer(validPort, validChannel, coin, sender, ibctesting.GenerateString(types.MaximumReceiverLength+1), clienttypes.ZeroHeight(), 100, ""), ibcerrors.ErrInvalidAddress}, + {"empty coin", types.NewMsgTransfer(validPort, validChannel, sdk.Coin{}, sender, receiver, clienttypes.ZeroHeight(), 100, ""), ibcerrors.ErrInvalidCoins}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + + if tc.expError == nil { + require.NoError(t, err) + } else { + require.ErrorIs(t, err, tc.expError) + } + }) + } +} + +// TestMsgTransferGetSigners tests GetSigners for MsgTransfer +func TestMsgTransferGetSigners(t *testing.T) { + addr := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()) + msg := types.NewMsgTransfer(validPort, validChannel, coin, addr.String(), receiver, timeoutHeight, 0, "") + + encodingCfg := moduletestutil.MakeTestEncodingConfig(transfer.AppModuleBasic{}) + signers, _, err := encodingCfg.Codec.GetMsgV1Signers(msg) + require.NoError(t, err) + require.Equal(t, addr.Bytes(), signers[0]) +} + +// TestMsgUpdateParamsValidateBasic tests ValidateBasic for MsgUpdateParams +func TestMsgUpdateParamsValidateBasic(t *testing.T) { + testCases := []struct { + name string + msg *types.MsgUpdateParams + expError error + }{ + {"success: valid signer and valid params", types.NewMsgUpdateParams(ibctesting.TestAccAddress, types.DefaultParams()), nil}, + {"failure: empty signer with valid params", types.NewMsgUpdateParams(emptyAddr, types.DefaultParams()), ibcerrors.ErrInvalidAddress}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + + if tc.expError == nil { + require.NoError(t, err) + } else { + require.ErrorIs(t, err, tc.expError) + } + }) + } +} + +// TestMsgUpdateParamsGetSigners tests GetSigners for MsgUpdateParams +func TestMsgUpdateParamsGetSigners(t *testing.T) { + testCases := []struct { + name string + address sdk.AccAddress + errMsg string + }{ + {"success: valid address", sdk.AccAddress(ibctesting.TestAccAddress), ""}, + {"failure: nil address", nil, "empty address string is not allowed"}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + msg := types.MsgUpdateParams{ + Signer: tc.address.String(), + Params: types.DefaultParams(), + } + + encodingCfg := moduletestutil.MakeTestEncodingConfig(transfer.AppModuleBasic{}) + signers, _, err := encodingCfg.Codec.GetMsgV1Signers(&msg) + + if tc.errMsg == "" { + require.NoError(t, err) + require.Equal(t, tc.address.Bytes(), signers[0]) + } else { + require.ErrorContains(t, err, tc.errMsg) + } + }) + } +} diff --git a/modules/apps/transfer/types/packet.go b/modules/apps/transfer/types/packet.go new file mode 100644 index 0000000..3ca476f --- /dev/null +++ b/modules/apps/transfer/types/packet.go @@ -0,0 +1,287 @@ +package types + +import ( + "encoding/json" + "errors" + "strings" + + "github.com/cosmos/gogoproto/proto" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/codec/unknownproto" + + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// InternalTransferRepresentation defines a struct used internally by the transfer application to represent a fungible token transfer +type InternalTransferRepresentation struct { + // the tokens to be transferred + Token Token + // the sender address + Sender string + // the recipient address on the destination chain + Receiver string + // optional memo + Memo string +} + +var ( + _ ibcexported.PacketData = (*FungibleTokenPacketData)(nil) + _ ibcexported.PacketDataProvider = (*FungibleTokenPacketData)(nil) +) + +const ( + EncodingJSON = "application/json" + EncodingProtobuf = "application/x-protobuf" + EncodingABI = "application/x-solidity-abi" +) + +// NewFungibleTokenPacketData constructs a new FungibleTokenPacketData instance +func NewFungibleTokenPacketData( + denom string, amount string, + sender, receiver string, + memo string, +) FungibleTokenPacketData { + return FungibleTokenPacketData{ + Denom: denom, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: memo, + } +} + +// ValidateBasic is used for validating the token transfer. +// NOTE: The addresses formats are not validated as the sender and recipient can have different +// formats defined by their corresponding chains that are not known to IBC. +func (ftpd FungibleTokenPacketData) ValidateBasic() error { + amount, ok := sdkmath.NewIntFromString(ftpd.Amount) + if !ok { + return errorsmod.Wrapf(ErrInvalidAmount, "unable to parse transfer amount (%s) into math.Int", ftpd.Amount) + } + if !amount.IsPositive() { + return errorsmod.Wrapf(ErrInvalidAmount, "amount must be strictly positive: got %d", amount) + } + if strings.TrimSpace(ftpd.Sender) == "" { + return errorsmod.Wrap(ibcerrors.ErrInvalidAddress, "sender address cannot be blank") + } + if strings.TrimSpace(ftpd.Receiver) == "" { + return errorsmod.Wrap(ibcerrors.ErrInvalidAddress, "receiver address cannot be blank") + } + denom := ExtractDenomFromPath(ftpd.Denom) + return denom.Validate() +} + +// GetBytes is a helper for serialising the packet to bytes. +// The memo field of FungibleTokenPacketData is marked with the JSON omitempty tag +// ensuring that the memo field is not included in the marshalled bytes if one is not specified. +func (ftpd FungibleTokenPacketData) GetBytes() []byte { + bz, err := json.Marshal(ftpd) + if err != nil { + panic(errors.New("cannot marshal FungibleTokenPacketData into bytes")) + } + + return bz +} + +// GetPacketSender returns the sender address embedded in the packet data. +// +// NOTE: +// - The sender address is set by the module which requested the packet to be sent, +// and this module may not have validated the sender address by a signature check. +// - The sender address must only be used by modules on the sending chain. +// - sourcePortID is not used in this implementation. +func (ftpd FungibleTokenPacketData) GetPacketSender(sourcePortID string) string { + return ftpd.Sender +} + +// GetCustomPacketData interprets the memo field of the packet data as a JSON object +// and returns the value associated with the given key. +// If the key is missing or the memo is not properly formatted, then nil is returned. +func (ftpd FungibleTokenPacketData) GetCustomPacketData(key string) any { + if len(ftpd.Memo) == 0 { + return nil + } + + jsonObject := make(map[string]any) + err := json.Unmarshal([]byte(ftpd.Memo), &jsonObject) + if err != nil { + return nil + } + + memoData, found := jsonObject[key] + if !found { + return nil + } + + return memoData +} + +// NewInternalTransferRepresentation constructs a new InternalTransferRepresentation instance +func NewInternalTransferRepresentation( + token Token, + sender, receiver string, + memo string, +) InternalTransferRepresentation { + return InternalTransferRepresentation{ + Token: token, + Sender: sender, + Receiver: receiver, + Memo: memo, + } +} + +// ValidateBasic is used for validating the token transfer. +// NOTE: The addresses formats are not validated as the sender and recipient can have different +// formats defined by their corresponding chains that are not known to IBC. +func (ftpd InternalTransferRepresentation) ValidateBasic() error { + if strings.TrimSpace(ftpd.Sender) == "" { + return errorsmod.Wrap(ibcerrors.ErrInvalidAddress, "sender address cannot be blank") + } + + if strings.TrimSpace(ftpd.Receiver) == "" { + return errorsmod.Wrap(ibcerrors.ErrInvalidAddress, "receiver address cannot be blank") + } + + if err := ftpd.Token.Validate(); err != nil { + return err + } + + if len(ftpd.Memo) > MaximumMemoLength { + return errorsmod.Wrapf(ErrInvalidMemo, "memo must not exceed %d bytes", MaximumMemoLength) + } + + return nil +} + +// GetCustomPacketData interprets the memo field of the packet data as a JSON object +// and returns the value associated with the given key. +// If the key is missing or the memo is not properly formatted, then nil is returned. +func (ftpd InternalTransferRepresentation) GetCustomPacketData(key string) any { + if len(ftpd.Memo) == 0 { + return nil + } + + jsonObject := make(map[string]any) + err := json.Unmarshal([]byte(ftpd.Memo), &jsonObject) + if err != nil { + return nil + } + + memoData, found := jsonObject[key] + if !found { + return nil + } + + return memoData +} + +// GetPacketSender returns the sender address embedded in the packet data. +// +// NOTE: +// - The sender address is set by the module which requested the packet to be sent, +// and this module may not have validated the sender address by a signature check. +// - The sender address must only be used by modules on the sending chain. +// - sourcePortID is not used in this implementation. +func (ftpd InternalTransferRepresentation) GetPacketSender(sourcePortID string) string { + return ftpd.Sender +} + +// MarshalPacketData attempts to marshal the provided FungibleTokenPacketData into bytes with the provided encoding. +func MarshalPacketData(data FungibleTokenPacketData, ics20Version string, encoding string) ([]byte, error) { + if ics20Version != V1 { + panic("unsupported ics20 version") + } + + switch encoding { + case EncodingJSON: + return json.Marshal(data) + case EncodingProtobuf: + return proto.Marshal(&data) + case EncodingABI: + return EncodeABIFungibleTokenPacketData(&data) + default: + return nil, errorsmod.Wrapf(ibcerrors.ErrInvalidType, "invalid encoding provided, must be either empty or one of [%q, %q], got %s", EncodingJSON, EncodingProtobuf, encoding) + } +} + +// UnmarshalPacketData attempts to unmarshal the provided packet data bytes into a InternalTransferRepresentation. +func UnmarshalPacketData(bz []byte, ics20Version string, encoding string) (InternalTransferRepresentation, error) { + const failedUnmarshalingErrorMsg = "cannot unmarshal %s transfer packet data: %s" + + // Depending on the ics20 version, we use a different default encoding (json for V1, proto for V2) + // and we have a different type to unmarshal the data into. + var data proto.Message + switch ics20Version { + case V1: + if encoding == "" { + encoding = EncodingJSON + } + data = &FungibleTokenPacketData{} + default: + return InternalTransferRepresentation{}, errorsmod.Wrap(ErrInvalidVersion, ics20Version) + } + + errorMsgVersion := "ICS20-V1" + + // Here we perform the unmarshaling based on the specified encoding. + // The functions act on the generic "data" variable which is of type proto.Message (an interface). + switch encoding { + case EncodingJSON: + if err := json.Unmarshal(bz, &data); err != nil { + return InternalTransferRepresentation{}, errorsmod.Wrapf(ibcerrors.ErrInvalidType, failedUnmarshalingErrorMsg, errorMsgVersion, err.Error()) + } + case EncodingProtobuf: + if err := unknownproto.RejectUnknownFieldsStrict(bz, data, unknownproto.DefaultAnyResolver{}); err != nil { + return InternalTransferRepresentation{}, errorsmod.Wrapf(ibcerrors.ErrInvalidType, failedUnmarshalingErrorMsg, errorMsgVersion, err.Error()) + } + + if err := proto.Unmarshal(bz, data); err != nil { + return InternalTransferRepresentation{}, errorsmod.Wrapf(ibcerrors.ErrInvalidType, failedUnmarshalingErrorMsg, errorMsgVersion, err.Error()) + } + case EncodingABI: + if ics20Version != V1 { + return InternalTransferRepresentation{}, errorsmod.Wrapf(ibcerrors.ErrInvalidType, "encoding %s is only supported for ICS20-V1", EncodingABI) + } + var err error + data, err = DecodeABIFungibleTokenPacketData(bz) + if err != nil { + return InternalTransferRepresentation{}, errorsmod.Wrapf(ibcerrors.ErrInvalidType, failedUnmarshalingErrorMsg, errorMsgVersion, err.Error()) + } + default: + return InternalTransferRepresentation{}, errorsmod.Wrapf(ibcerrors.ErrInvalidType, "invalid encoding provided, must be either empty or one of [%q, %q, %q], got %s", EncodingJSON, EncodingProtobuf, EncodingABI, encoding) + } + + // When the unmarshaling is done, we want to retrieve the underlying data type based on the value of ics20Version + // Since it has to be v1, we convert the data to FungibleTokenPacketData and then call the conversion function to construct + // the v2 type. + datav1, ok := data.(*FungibleTokenPacketData) + if !ok { + // We should never get here, as we manually constructed the type at the beginning of the file + return InternalTransferRepresentation{}, errorsmod.Wrapf(ibcerrors.ErrInvalidType, "cannot convert proto message into FungibleTokenPacketData") + } + // The call to ValidateBasic for V1 is done inside PacketDataV1toV2. + return PacketDataV1ToV2(*datav1) +} + +// PacketDataV1ToV2 converts a v1 packet data to a v2 packet data. The packet data is validated +// before conversion. +func PacketDataV1ToV2(packetData FungibleTokenPacketData) (InternalTransferRepresentation, error) { + if err := packetData.ValidateBasic(); err != nil { + return InternalTransferRepresentation{}, errorsmod.Wrapf(err, "invalid packet data") + } + + denom := ExtractDenomFromPath(packetData.Denom) + return InternalTransferRepresentation{ + Token: Token{ + Denom: denom, + Amount: packetData.Amount, + }, + Sender: packetData.Sender, + Receiver: packetData.Receiver, + Memo: packetData.Memo, + }, nil +} diff --git a/modules/apps/transfer/types/packet.pb.go b/modules/apps/transfer/types/packet.pb.go new file mode 100644 index 0000000..439c273 --- /dev/null +++ b/modules/apps/transfer/types/packet.pb.go @@ -0,0 +1,534 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/applications/transfer/v1/packet.proto + +package types + +import ( + fmt "fmt" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// FungibleTokenPacketData defines a struct for the packet payload +// See FungibleTokenPacketData spec: +// https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#data-structures +type FungibleTokenPacketData struct { + // the token denomination to be transferred + Denom string `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom,omitempty"` + // the token amount to be transferred + Amount string `protobuf:"bytes,2,opt,name=amount,proto3" json:"amount,omitempty"` + // the sender address + Sender string `protobuf:"bytes,3,opt,name=sender,proto3" json:"sender,omitempty"` + // the recipient address on the destination chain + Receiver string `protobuf:"bytes,4,opt,name=receiver,proto3" json:"receiver,omitempty"` + // optional memo + Memo string `protobuf:"bytes,5,opt,name=memo,proto3" json:"memo,omitempty"` +} + +func (m *FungibleTokenPacketData) Reset() { *m = FungibleTokenPacketData{} } +func (m *FungibleTokenPacketData) String() string { return proto.CompactTextString(m) } +func (*FungibleTokenPacketData) ProtoMessage() {} +func (*FungibleTokenPacketData) Descriptor() ([]byte, []int) { + return fileDescriptor_8499af348a22cb56, []int{0} +} +func (m *FungibleTokenPacketData) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *FungibleTokenPacketData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_FungibleTokenPacketData.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *FungibleTokenPacketData) XXX_Merge(src proto.Message) { + xxx_messageInfo_FungibleTokenPacketData.Merge(m, src) +} +func (m *FungibleTokenPacketData) XXX_Size() int { + return m.Size() +} +func (m *FungibleTokenPacketData) XXX_DiscardUnknown() { + xxx_messageInfo_FungibleTokenPacketData.DiscardUnknown(m) +} + +var xxx_messageInfo_FungibleTokenPacketData proto.InternalMessageInfo + +func (m *FungibleTokenPacketData) GetDenom() string { + if m != nil { + return m.Denom + } + return "" +} + +func (m *FungibleTokenPacketData) GetAmount() string { + if m != nil { + return m.Amount + } + return "" +} + +func (m *FungibleTokenPacketData) GetSender() string { + if m != nil { + return m.Sender + } + return "" +} + +func (m *FungibleTokenPacketData) GetReceiver() string { + if m != nil { + return m.Receiver + } + return "" +} + +func (m *FungibleTokenPacketData) GetMemo() string { + if m != nil { + return m.Memo + } + return "" +} + +func init() { + proto.RegisterType((*FungibleTokenPacketData)(nil), "ibc.applications.transfer.v1.FungibleTokenPacketData") +} + +func init() { + proto.RegisterFile("ibc/applications/transfer/v1/packet.proto", fileDescriptor_8499af348a22cb56) +} + +var fileDescriptor_8499af348a22cb56 = []byte{ + // 253 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x90, 0x31, 0x4b, 0xc4, 0x30, + 0x18, 0x86, 0x1b, 0xbd, 0x3b, 0x34, 0x63, 0x10, 0x2d, 0x22, 0x41, 0x9c, 0x74, 0xb0, 0xb1, 0xb8, + 0x38, 0x8b, 0x38, 0xcb, 0xe1, 0xe4, 0x96, 0xa4, 0x9f, 0x35, 0x5c, 0x93, 0xaf, 0x24, 0x69, 0xc1, + 0x5f, 0xa1, 0x3f, 0xcb, 0xf1, 0x46, 0x47, 0x69, 0xff, 0x88, 0x5c, 0xaa, 0xc7, 0x6d, 0x79, 0x9e, + 0xbc, 0xdf, 0xf2, 0xd0, 0x2b, 0xa3, 0xb4, 0x90, 0x6d, 0xdb, 0x18, 0x2d, 0xa3, 0x41, 0x17, 0x44, + 0xf4, 0xd2, 0x85, 0x57, 0xf0, 0xa2, 0x2f, 0x45, 0x2b, 0xf5, 0x0a, 0x62, 0xd1, 0x7a, 0x8c, 0xc8, + 0xce, 0x8c, 0xd2, 0xc5, 0xee, 0xb4, 0xf8, 0x9f, 0x16, 0x7d, 0x79, 0xf1, 0x41, 0xe8, 0xc9, 0x63, + 0xe7, 0x6a, 0xa3, 0x1a, 0x78, 0xc6, 0x15, 0xb8, 0xa7, 0x74, 0xfb, 0x20, 0xa3, 0x64, 0x47, 0x74, + 0x5e, 0x81, 0x43, 0x9b, 0x93, 0x73, 0x72, 0x79, 0xb8, 0x9c, 0x80, 0x1d, 0xd3, 0x85, 0xb4, 0xd8, + 0xb9, 0x98, 0xef, 0x25, 0xfd, 0x47, 0x1b, 0x1f, 0xc0, 0x55, 0xe0, 0xf3, 0xfd, 0xc9, 0x4f, 0xc4, + 0x4e, 0xe9, 0x81, 0x07, 0x0d, 0xa6, 0x07, 0x9f, 0xcf, 0xd2, 0xcf, 0x96, 0x19, 0xa3, 0x33, 0x0b, + 0x16, 0xf3, 0x79, 0xf2, 0xe9, 0x7d, 0xbf, 0xfc, 0x1a, 0x38, 0x59, 0x0f, 0x9c, 0xfc, 0x0c, 0x9c, + 0x7c, 0x8e, 0x3c, 0x5b, 0x8f, 0x3c, 0xfb, 0x1e, 0x79, 0xf6, 0x72, 0x57, 0x9b, 0xf8, 0xd6, 0xa9, + 0x42, 0xa3, 0x15, 0x1a, 0x83, 0xc5, 0x20, 0x8c, 0xd2, 0xd7, 0x35, 0x8a, 0xbe, 0xbc, 0x11, 0x16, + 0xab, 0xae, 0x81, 0xb0, 0xa9, 0xb2, 0x53, 0x23, 0xbe, 0xb7, 0x10, 0xd4, 0x22, 0xa5, 0xb8, 0xfd, + 0x0d, 0x00, 0x00, 0xff, 0xff, 0x83, 0xa2, 0x8b, 0x97, 0x37, 0x01, 0x00, 0x00, +} + +func (m *FungibleTokenPacketData) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *FungibleTokenPacketData) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *FungibleTokenPacketData) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Memo) > 0 { + i -= len(m.Memo) + copy(dAtA[i:], m.Memo) + i = encodeVarintPacket(dAtA, i, uint64(len(m.Memo))) + i-- + dAtA[i] = 0x2a + } + if len(m.Receiver) > 0 { + i -= len(m.Receiver) + copy(dAtA[i:], m.Receiver) + i = encodeVarintPacket(dAtA, i, uint64(len(m.Receiver))) + i-- + dAtA[i] = 0x22 + } + if len(m.Sender) > 0 { + i -= len(m.Sender) + copy(dAtA[i:], m.Sender) + i = encodeVarintPacket(dAtA, i, uint64(len(m.Sender))) + i-- + dAtA[i] = 0x1a + } + if len(m.Amount) > 0 { + i -= len(m.Amount) + copy(dAtA[i:], m.Amount) + i = encodeVarintPacket(dAtA, i, uint64(len(m.Amount))) + i-- + dAtA[i] = 0x12 + } + if len(m.Denom) > 0 { + i -= len(m.Denom) + copy(dAtA[i:], m.Denom) + i = encodeVarintPacket(dAtA, i, uint64(len(m.Denom))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintPacket(dAtA []byte, offset int, v uint64) int { + offset -= sovPacket(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *FungibleTokenPacketData) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Denom) + if l > 0 { + n += 1 + l + sovPacket(uint64(l)) + } + l = len(m.Amount) + if l > 0 { + n += 1 + l + sovPacket(uint64(l)) + } + l = len(m.Sender) + if l > 0 { + n += 1 + l + sovPacket(uint64(l)) + } + l = len(m.Receiver) + if l > 0 { + n += 1 + l + sovPacket(uint64(l)) + } + l = len(m.Memo) + if l > 0 { + n += 1 + l + sovPacket(uint64(l)) + } + return n +} + +func sovPacket(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozPacket(x uint64) (n int) { + return sovPacket(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *FungibleTokenPacketData) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: FungibleTokenPacketData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: FungibleTokenPacketData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Denom", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthPacket + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthPacket + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Denom = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthPacket + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthPacket + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Amount = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthPacket + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthPacket + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Sender = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Receiver", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthPacket + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthPacket + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Receiver = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Memo", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthPacket + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthPacket + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Memo = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipPacket(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthPacket + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipPacket(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowPacket + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowPacket + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowPacket + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthPacket + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupPacket + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthPacket + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthPacket = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowPacket = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupPacket = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/apps/transfer/types/packet_test.go b/modules/apps/transfer/types/packet_test.go new file mode 100644 index 0000000..344ed4c --- /dev/null +++ b/modules/apps/transfer/types/packet_test.go @@ -0,0 +1,657 @@ +package types_test + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +const ( + denom = "transfer/gaiachannel/atom" + amount = "100" + largeAmount = "18446744073709551616" // one greater than largest uint64 (^uint64(0)) + invalidLargeAmount = "115792089237316195423570985008687907853269984665640564039457584007913129639936" // 2^256 +) + +// TestFungibleTokenPacketDataValidateBasic tests ValidateBasic for FungibleTokenPacketData +func TestFungibleTokenPacketDataValidateBasic(t *testing.T) { + testCases := []struct { + name string + packetData types.FungibleTokenPacketData + expErr error + }{ + {"valid packet", types.NewFungibleTokenPacketData(denom, amount, sender, receiver, ""), nil}, + {"valid packet with memo", types.NewFungibleTokenPacketData(denom, amount, sender, receiver, "memo"), nil}, + {"valid packet with large amount", types.NewFungibleTokenPacketData(denom, largeAmount, sender, receiver, ""), nil}, + {"invalid denom", types.NewFungibleTokenPacketData("", amount, sender, receiver, ""), types.ErrInvalidDenomForTransfer}, + {"invalid denom, invalid portID", types.NewFungibleTokenPacketData("(transfer)/channel-1/uatom", amount, sender, receiver, ""), host.ErrInvalidID}, + {"invalid empty amount", types.NewFungibleTokenPacketData(denom, "", sender, receiver, ""), types.ErrInvalidAmount}, + {"invalid zero amount", types.NewFungibleTokenPacketData(denom, "0", sender, receiver, ""), types.ErrInvalidAmount}, + {"invalid negative amount", types.NewFungibleTokenPacketData(denom, "-1", sender, receiver, ""), types.ErrInvalidAmount}, + {"invalid large amount", types.NewFungibleTokenPacketData(denom, invalidLargeAmount, sender, receiver, ""), types.ErrInvalidAmount}, + {"missing sender address", types.NewFungibleTokenPacketData(denom, amount, emptyAddr, receiver, ""), ibcerrors.ErrInvalidAddress}, + {"missing recipient address", types.NewFungibleTokenPacketData(denom, amount, sender, emptyAddr, ""), ibcerrors.ErrInvalidAddress}, + } + + for i, tc := range testCases { + + err := tc.packetData.ValidateBasic() + if tc.expErr == nil { + require.NoError(t, err, "valid test case %d failed: %v", i, err) + } else { + require.ErrorIs(t, err, tc.expErr, "invalid test case %d passed: %s", i, tc.name) + } + } +} + +func (suite *TypesTestSuite) TestGetPacketSender() { + packetData := types.FungibleTokenPacketData{ + Denom: denom, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: "", + } + + suite.Require().Equal(sender, packetData.GetPacketSender(types.PortID)) +} + +func (suite *TypesTestSuite) TestPacketDataProvider() { + testCases := []struct { + name string + packetData types.FungibleTokenPacketData + expCustomData any + }{ + { + "success: src_callback key in memo", + types.FungibleTokenPacketData{ + Denom: denom, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, receiver), + }, + map[string]any{ + "address": receiver, + }, + }, + { + "success: src_callback key in memo with additional fields", + types.FungibleTokenPacketData{ + Denom: denom, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: fmt.Sprintf(`{"src_callback": {"address": "%s", "gas_limit": "200000"}}`, receiver), + }, + map[string]any{ + "address": receiver, + "gas_limit": "200000", + }, + }, + { + "success: src_callback has string value", + types.FungibleTokenPacketData{ + Denom: denom, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: `{"src_callback": "string"}`, + }, + "string", + }, + { + "failure: empty memo", + types.FungibleTokenPacketData{ + Denom: denom, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: "", + }, + nil, + }, + { + "failure: non-json memo", + types.FungibleTokenPacketData{ + Denom: denom, + Amount: amount, + Sender: sender, + Receiver: receiver, + Memo: "invalid", + }, + nil, + }, + } + + for _, tc := range testCases { + + customData := tc.packetData.GetCustomPacketData("src_callback") + suite.Require().Equal(tc.expCustomData, customData) + } +} + +func (suite *TypesTestSuite) TestFungibleTokenPacketDataOmitEmpty() { + // check that omitempty is present for the memo field + packetData := types.FungibleTokenPacketData{ + Denom: denom, + Amount: amount, + Sender: sender, + Receiver: receiver, + // Default value for non-specified memo field is empty string + } + + bz, err := json.Marshal(packetData) + suite.Require().NoError(err) + + // check that the memo field is not present in the marshalled bytes + suite.Require().NotContains(string(bz), "memo") + + packetData.Memo = "abc" + bz, err = json.Marshal(packetData) + suite.Require().NoError(err) + + // check that the memo field is present in the marshalled bytes + suite.Require().Contains(string(bz), "memo") +} + +// TestInternalTransferRepresentationValidateBasic tests ValidateBasic for FungibleTokenPacketData +func TestInternalTransferRepresentationValidateBasic(t *testing.T) { + testCases := []struct { + name string + packetData types.InternalTransferRepresentation + expErr error + }{ + { + "success: valid packet", + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom(denom, types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-1")), + Amount: amount, + }, + sender, + receiver, + "", + ), + nil, + }, + { + "success: valid packet with memo", + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom(denom, types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-1")), + Amount: amount, + }, + sender, + receiver, + "memo", + ), + nil, + }, + { + "success: valid packet with large amount", + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom(denom, types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-1")), + Amount: largeAmount, + }, + sender, + receiver, + "memo", + ), + nil, + }, + { + "failure: invalid denom", + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom("", types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-1")), + Amount: amount, + }, + sender, + receiver, + "", + ), + types.ErrInvalidDenomForTransfer, + }, + { + "failure: invalid empty amount", + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom(denom, types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-1")), + Amount: "", + }, + sender, + receiver, + "", + ), + types.ErrInvalidAmount, + }, + { + "failure: invalid zero amount", + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom(denom, types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-1")), + Amount: "0", + }, + sender, + receiver, + "", + ), + types.ErrInvalidAmount, + }, + { + "failure: invalid negative amount", + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom(denom, types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-1")), + Amount: "-100", + }, + sender, + receiver, + "", + ), + types.ErrInvalidAmount, + }, + { + "failure: invalid large amount", + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom(denom, types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-1")), + Amount: invalidLargeAmount, + }, + sender, + receiver, + "memo", + ), + types.ErrInvalidAmount, + }, + { + "failure: missing sender address", + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom(denom, types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-1")), + Amount: amount, + }, + "", + receiver, + "memo", + ), + ibcerrors.ErrInvalidAddress, + }, + { + "failure: missing recipient address", + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom(denom, types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-1")), + Amount: amount, + }, + sender, + "", + "", + ), + ibcerrors.ErrInvalidAddress, + }, + { + "failure: memo field too large", + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom(denom, types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-1")), + Amount: largeAmount, + }, + sender, + receiver, + ibctesting.GenerateString(types.MaximumMemoLength+1), + ), + types.ErrInvalidMemo, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.packetData.ValidateBasic() + + if tc.expErr == nil { + require.NoError(t, err, tc.name) + } else { + require.ErrorContains(t, err, tc.expErr.Error(), tc.name) + } + }) + } +} + +func TestGetPacketSender(t *testing.T) { + testCases := []struct { + name string + packetData types.InternalTransferRepresentation + expSender string + }{ + { + "non-empty sender field", + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom(denom, types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-1")), + Amount: amount, + }, + sender, + receiver, + "", + ), + sender, + }, + { + "empty sender field", + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom(denom, types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-1")), + Amount: amount, + }, + "", + receiver, + "abc", + ), + "", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + require.Equal(t, tc.expSender, tc.packetData.GetPacketSender(types.PortID)) + }) + } +} + +func TestPacketDataProvider(t *testing.T) { + testCases := []struct { + name string + packetData types.InternalTransferRepresentation + expCustomData any + }{ + { + "success: src_callback key in memo", + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom(denom, types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-1")), + Amount: amount, + }, + sender, + receiver, + fmt.Sprintf(`{"src_callback": {"address": "%s"}}`, receiver), + ), + + map[string]any{ + "address": receiver, + }, + }, + { + "success: src_callback key in memo with additional fields", + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom(denom, types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-1")), + Amount: amount, + }, + sender, + receiver, + fmt.Sprintf(`{"src_callback": {"address": "%s", "gas_limit": "200000"}}`, receiver), + ), + map[string]any{ + "address": receiver, + "gas_limit": "200000", + }, + }, + { + "success: src_callback has string value", + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom(denom, types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-1")), + Amount: amount, + }, + sender, + receiver, + `{"src_callback": "string"}`, + ), + "string", + }, + { + "failure: src_callback key not found memo", + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom(denom, types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-1")), + Amount: amount, + }, + sender, + receiver, + fmt.Sprintf(`{"dest_callback": {"address": "%s", "min_gas": "200000"}}`, receiver), + ), + nil, + }, + { + "failure: empty memo", + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom(denom, types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-1")), + Amount: amount, + }, + sender, + receiver, + "", + ), + nil, + }, + { + "failure: non-json memo", + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom(denom, types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-1")), + Amount: amount, + }, + sender, + receiver, + "invalid", + ), + nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + customData := tc.packetData.GetCustomPacketData("src_callback") + require.Equal(t, tc.expCustomData, customData) + }) + } +} + +func TestUnmarshalPacketData(t *testing.T) { + var ( + packetDataBz []byte + version string + encoding string + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success: v1 -> v2 with empty encoding (JSON)", + func() {}, + nil, + }, + { + "success: v1 -> v2 with JSON encoding", + func() { + packetDataV1 := types.NewFungibleTokenPacketData("transfer/channel-0/atom", "1000", sender, receiver, "") + encoding = types.EncodingJSON + bz, err := types.MarshalPacketData(packetDataV1, types.V1, encoding) + require.NoError(t, err) + packetDataBz = bz + }, + nil, + }, + { + "success: v1 -> v2 with protobuf encoding", + func() { + packetData := types.NewFungibleTokenPacketData("transfer/channel-0/atom", "1000", sender, receiver, "") + bz, err := types.MarshalPacketData(packetData, types.V1, types.EncodingProtobuf) + require.NoError(t, err) + + packetDataBz = bz + encoding = types.EncodingProtobuf + }, + nil, + }, + { + "success: v1 -> v2 with abi encoding", + func() { + packetData := types.NewFungibleTokenPacketData("transfer/channel-0/atom", "1000", sender, receiver, "") + bz, err := types.MarshalPacketData(packetData, types.V1, types.EncodingABI) + require.NoError(t, err) + + packetDataBz = bz + encoding = types.EncodingABI + }, + nil, + }, + { + "invalid version", + func() { + version = "ics20-100" + }, + types.ErrInvalidVersion, + }, + } + + for _, tc := range testCases { + packetDataV1 := types.NewFungibleTokenPacketData("transfer/channel-0/atom", "1000", sender, receiver, "") + + packetDataBz = packetDataV1.GetBytes() + version = types.V1 + encoding = "" + + tc.malleate() + + packetData, err := types.UnmarshalPacketData(packetDataBz, version, encoding) + + if tc.expError == nil { + require.NoError(t, err) + require.NotEmpty(t, packetData.Token) + require.NotEmpty(t, packetData.Sender) + require.NotEmpty(t, packetData.Receiver) + require.IsType(t, types.InternalTransferRepresentation{}, packetData) + } else { + ibctesting.RequireErrorIsOrContains(t, err, tc.expError) + } + } +} + +func TestPacketV1ToPacketV2(t *testing.T) { + const ( + sender = "sender" + receiver = "receiver" + ) + + testCases := []struct { + name string + v1Data types.FungibleTokenPacketData + v2Data types.InternalTransferRepresentation + expError error + }{ + { + "success", + types.NewFungibleTokenPacketData("transfer/channel-0/atom", "1000", sender, receiver, ""), + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom("atom", types.NewHop("transfer", "channel-0")), + Amount: "1000", + }, sender, receiver, ""), + nil, + }, + { + "success with empty trace", + types.NewFungibleTokenPacketData("atom", "1000", sender, receiver, ""), + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom("atom"), + Amount: "1000", + }, sender, receiver, ""), + nil, + }, + { + "success: base denom with '/'", + types.NewFungibleTokenPacketData("transfer/channel-0/atom/withslash", "1000", sender, receiver, ""), + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom("atom/withslash", types.NewHop("transfer", "channel-0")), + Amount: "1000", + }, sender, receiver, ""), + nil, + }, + { + "success: base denom with '/' at the end", + types.NewFungibleTokenPacketData("transfer/channel-0/atom/", "1000", sender, receiver, ""), + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom("atom/", types.NewHop("transfer", "channel-0")), + Amount: "1000", + }, sender, receiver, ""), + nil, + }, + { + "success: longer trace base denom with '/'", + types.NewFungibleTokenPacketData("transfer/channel-0/transfer/channel-1/atom/pool", "1000", sender, receiver, ""), + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom("atom/pool", types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-1")), + Amount: "1000", + }, sender, receiver, ""), + nil, + }, + { + "success: longer trace with non transfer port", + types.NewFungibleTokenPacketData("transfer/channel-0/transfer/channel-1/transfer-custom/channel-2/atom", "1000", sender, receiver, ""), + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom("atom", types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-1"), types.NewHop("transfer-custom", "channel-2")), + Amount: "1000", + }, sender, receiver, ""), + nil, + }, + { + "success: base denom with slash, trace with non transfer port", + types.NewFungibleTokenPacketData("transfer/channel-0/transfer/channel-1/transfer-custom/channel-2/atom/pool", "1000", sender, receiver, ""), + types.NewInternalTransferRepresentation( + types.Token{ + Denom: types.NewDenom("atom/pool", types.NewHop("transfer", "channel-0"), types.NewHop("transfer", "channel-1"), types.NewHop("transfer-custom", "channel-2")), + Amount: "1000", + }, sender, receiver, ""), + nil, + }, + { + "failure: packet data fails validation with empty denom", + types.NewFungibleTokenPacketData("", "1000", sender, receiver, ""), + types.InternalTransferRepresentation{}, + errorsmod.Wrap(types.ErrInvalidDenomForTransfer, "base denomination cannot be blank"), + }, + } + + for _, tc := range testCases { + actualV2Data, err := types.PacketDataV1ToV2(tc.v1Data) + + if tc.expError == nil { + require.NoError(t, err, "test case: %s", tc.name) + require.Equal(t, tc.v2Data, actualV2Data, "test case: %s", tc.name) + } else { + require.Error(t, err, "test case: %s", tc.name) + } + } +} diff --git a/modules/apps/transfer/types/params.go b/modules/apps/transfer/types/params.go new file mode 100644 index 0000000..7e90bec --- /dev/null +++ b/modules/apps/transfer/types/params.go @@ -0,0 +1,21 @@ +package types + +const ( + // DefaultSendEnabled enabled + DefaultSendEnabled = true + // DefaultReceiveEnabled enabled + DefaultReceiveEnabled = true +) + +// NewParams creates a new parameter configuration for the ibc transfer module +func NewParams(enableSend, enableReceive bool) Params { + return Params{ + SendEnabled: enableSend, + ReceiveEnabled: enableReceive, + } +} + +// DefaultParams is the default parameter configuration for the ibc-transfer module +func DefaultParams() Params { + return NewParams(DefaultSendEnabled, DefaultReceiveEnabled) +} diff --git a/modules/apps/transfer/types/params_legacy.go b/modules/apps/transfer/types/params_legacy.go new file mode 100644 index 0000000..222de93 --- /dev/null +++ b/modules/apps/transfer/types/params_legacy.go @@ -0,0 +1,42 @@ +/* +NOTE: Usage of x/params to manage parameters is deprecated in favor of x/gov +controlled execution of MsgUpdateParams messages. These types remains solely +for migration purposes and will be removed in a future release. +[#3621](https://github.com/cosmos/ibc-go/issues/3621) +*/ +package types + +import ( + "fmt" + + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" +) + +var ( + // KeySendEnabled is store's key for SendEnabled Params + KeySendEnabled = []byte("SendEnabled") + // KeyReceiveEnabled is store's key for ReceiveEnabled Params + KeyReceiveEnabled = []byte("ReceiveEnabled") +) + +// ParamKeyTable type declaration for parameters +func ParamKeyTable() paramtypes.KeyTable { + return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) +} + +// ParamSetPairs implements params.ParamSet +func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { + return paramtypes.ParamSetPairs{ + paramtypes.NewParamSetPair(KeySendEnabled, &p.SendEnabled, validateEnabledTypeLegacy), + paramtypes.NewParamSetPair(KeyReceiveEnabled, &p.ReceiveEnabled, validateEnabledTypeLegacy), + } +} + +func validateEnabledTypeLegacy(i any) error { + _, ok := i.(bool) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + return nil +} diff --git a/modules/apps/transfer/types/query.pb.go b/modules/apps/transfer/types/query.pb.go new file mode 100644 index 0000000..c44acb4 --- /dev/null +++ b/modules/apps/transfer/types/query.pb.go @@ -0,0 +1,2637 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/applications/transfer/v1/query.proto + +package types + +import ( + context "context" + fmt "fmt" + types "github.com/cosmos/cosmos-sdk/types" + query "github.com/cosmos/cosmos-sdk/types/query" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// QueryParamsRequest is the request type for the Query/Params RPC method. +type QueryParamsRequest struct { +} + +func (m *QueryParamsRequest) Reset() { *m = QueryParamsRequest{} } +func (m *QueryParamsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryParamsRequest) ProtoMessage() {} +func (*QueryParamsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_a638e2800a01538c, []int{0} +} +func (m *QueryParamsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsRequest.Merge(m, src) +} +func (m *QueryParamsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsRequest proto.InternalMessageInfo + +// QueryParamsResponse is the response type for the Query/Params RPC method. +type QueryParamsResponse struct { + // params defines the parameters of the module. + Params *Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params,omitempty"` +} + +func (m *QueryParamsResponse) Reset() { *m = QueryParamsResponse{} } +func (m *QueryParamsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryParamsResponse) ProtoMessage() {} +func (*QueryParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_a638e2800a01538c, []int{1} +} +func (m *QueryParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsResponse.Merge(m, src) +} +func (m *QueryParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsResponse proto.InternalMessageInfo + +func (m *QueryParamsResponse) GetParams() *Params { + if m != nil { + return m.Params + } + return nil +} + +// QueryDenomRequest is the request type for the Query/Denom RPC +// method +type QueryDenomRequest struct { + // hash (in hex format) or denom (full denom with ibc prefix) of the on chain denomination. + Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` +} + +func (m *QueryDenomRequest) Reset() { *m = QueryDenomRequest{} } +func (m *QueryDenomRequest) String() string { return proto.CompactTextString(m) } +func (*QueryDenomRequest) ProtoMessage() {} +func (*QueryDenomRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_a638e2800a01538c, []int{2} +} +func (m *QueryDenomRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryDenomRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryDenomRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryDenomRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryDenomRequest.Merge(m, src) +} +func (m *QueryDenomRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryDenomRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryDenomRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryDenomRequest proto.InternalMessageInfo + +func (m *QueryDenomRequest) GetHash() string { + if m != nil { + return m.Hash + } + return "" +} + +// QueryDenomResponse is the response type for the Query/Denom RPC +// method. +type QueryDenomResponse struct { + // denom returns the requested denomination. + Denom *Denom `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom,omitempty"` +} + +func (m *QueryDenomResponse) Reset() { *m = QueryDenomResponse{} } +func (m *QueryDenomResponse) String() string { return proto.CompactTextString(m) } +func (*QueryDenomResponse) ProtoMessage() {} +func (*QueryDenomResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_a638e2800a01538c, []int{3} +} +func (m *QueryDenomResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryDenomResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryDenomResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryDenomResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryDenomResponse.Merge(m, src) +} +func (m *QueryDenomResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryDenomResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryDenomResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryDenomResponse proto.InternalMessageInfo + +func (m *QueryDenomResponse) GetDenom() *Denom { + if m != nil { + return m.Denom + } + return nil +} + +// QueryDenomsRequest is the request type for the Query/Denoms RPC +// method +type QueryDenomsRequest struct { + // pagination defines an optional pagination for the request. + Pagination *query.PageRequest `protobuf:"bytes,1,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryDenomsRequest) Reset() { *m = QueryDenomsRequest{} } +func (m *QueryDenomsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryDenomsRequest) ProtoMessage() {} +func (*QueryDenomsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_a638e2800a01538c, []int{4} +} +func (m *QueryDenomsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryDenomsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryDenomsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryDenomsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryDenomsRequest.Merge(m, src) +} +func (m *QueryDenomsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryDenomsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryDenomsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryDenomsRequest proto.InternalMessageInfo + +func (m *QueryDenomsRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryDenomsResponse is the response type for the Query/Denoms RPC +// method. +type QueryDenomsResponse struct { + // denoms returns all denominations. + Denoms Denoms `protobuf:"bytes,1,rep,name=denoms,proto3,castrepeated=Denoms" json:"denoms"` + // pagination defines the pagination in the response. + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryDenomsResponse) Reset() { *m = QueryDenomsResponse{} } +func (m *QueryDenomsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryDenomsResponse) ProtoMessage() {} +func (*QueryDenomsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_a638e2800a01538c, []int{5} +} +func (m *QueryDenomsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryDenomsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryDenomsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryDenomsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryDenomsResponse.Merge(m, src) +} +func (m *QueryDenomsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryDenomsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryDenomsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryDenomsResponse proto.InternalMessageInfo + +func (m *QueryDenomsResponse) GetDenoms() Denoms { + if m != nil { + return m.Denoms + } + return nil +} + +func (m *QueryDenomsResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryDenomHashRequest is the request type for the Query/DenomHash RPC +// method +type QueryDenomHashRequest struct { + // The denomination trace ([port_id]/[channel_id])+/[denom] + Trace string `protobuf:"bytes,1,opt,name=trace,proto3" json:"trace,omitempty"` +} + +func (m *QueryDenomHashRequest) Reset() { *m = QueryDenomHashRequest{} } +func (m *QueryDenomHashRequest) String() string { return proto.CompactTextString(m) } +func (*QueryDenomHashRequest) ProtoMessage() {} +func (*QueryDenomHashRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_a638e2800a01538c, []int{6} +} +func (m *QueryDenomHashRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryDenomHashRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryDenomHashRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryDenomHashRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryDenomHashRequest.Merge(m, src) +} +func (m *QueryDenomHashRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryDenomHashRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryDenomHashRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryDenomHashRequest proto.InternalMessageInfo + +func (m *QueryDenomHashRequest) GetTrace() string { + if m != nil { + return m.Trace + } + return "" +} + +// QueryDenomHashResponse is the response type for the Query/DenomHash RPC +// method. +type QueryDenomHashResponse struct { + // hash (in hex format) of the denomination trace information. + Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` +} + +func (m *QueryDenomHashResponse) Reset() { *m = QueryDenomHashResponse{} } +func (m *QueryDenomHashResponse) String() string { return proto.CompactTextString(m) } +func (*QueryDenomHashResponse) ProtoMessage() {} +func (*QueryDenomHashResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_a638e2800a01538c, []int{7} +} +func (m *QueryDenomHashResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryDenomHashResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryDenomHashResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryDenomHashResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryDenomHashResponse.Merge(m, src) +} +func (m *QueryDenomHashResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryDenomHashResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryDenomHashResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryDenomHashResponse proto.InternalMessageInfo + +func (m *QueryDenomHashResponse) GetHash() string { + if m != nil { + return m.Hash + } + return "" +} + +// QueryEscrowAddressRequest is the request type for the EscrowAddress RPC method. +type QueryEscrowAddressRequest struct { + // unique port identifier + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + // unique channel identifier + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` +} + +func (m *QueryEscrowAddressRequest) Reset() { *m = QueryEscrowAddressRequest{} } +func (m *QueryEscrowAddressRequest) String() string { return proto.CompactTextString(m) } +func (*QueryEscrowAddressRequest) ProtoMessage() {} +func (*QueryEscrowAddressRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_a638e2800a01538c, []int{8} +} +func (m *QueryEscrowAddressRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryEscrowAddressRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryEscrowAddressRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryEscrowAddressRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryEscrowAddressRequest.Merge(m, src) +} +func (m *QueryEscrowAddressRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryEscrowAddressRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryEscrowAddressRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryEscrowAddressRequest proto.InternalMessageInfo + +func (m *QueryEscrowAddressRequest) GetPortId() string { + if m != nil { + return m.PortId + } + return "" +} + +func (m *QueryEscrowAddressRequest) GetChannelId() string { + if m != nil { + return m.ChannelId + } + return "" +} + +// QueryEscrowAddressResponse is the response type of the EscrowAddress RPC method. +type QueryEscrowAddressResponse struct { + // the escrow account address + EscrowAddress string `protobuf:"bytes,1,opt,name=escrow_address,json=escrowAddress,proto3" json:"escrow_address,omitempty"` +} + +func (m *QueryEscrowAddressResponse) Reset() { *m = QueryEscrowAddressResponse{} } +func (m *QueryEscrowAddressResponse) String() string { return proto.CompactTextString(m) } +func (*QueryEscrowAddressResponse) ProtoMessage() {} +func (*QueryEscrowAddressResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_a638e2800a01538c, []int{9} +} +func (m *QueryEscrowAddressResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryEscrowAddressResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryEscrowAddressResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryEscrowAddressResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryEscrowAddressResponse.Merge(m, src) +} +func (m *QueryEscrowAddressResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryEscrowAddressResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryEscrowAddressResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryEscrowAddressResponse proto.InternalMessageInfo + +func (m *QueryEscrowAddressResponse) GetEscrowAddress() string { + if m != nil { + return m.EscrowAddress + } + return "" +} + +// QueryTotalEscrowForDenomRequest is the request type for TotalEscrowForDenom RPC method. +type QueryTotalEscrowForDenomRequest struct { + Denom string `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom,omitempty"` +} + +func (m *QueryTotalEscrowForDenomRequest) Reset() { *m = QueryTotalEscrowForDenomRequest{} } +func (m *QueryTotalEscrowForDenomRequest) String() string { return proto.CompactTextString(m) } +func (*QueryTotalEscrowForDenomRequest) ProtoMessage() {} +func (*QueryTotalEscrowForDenomRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_a638e2800a01538c, []int{10} +} +func (m *QueryTotalEscrowForDenomRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryTotalEscrowForDenomRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryTotalEscrowForDenomRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryTotalEscrowForDenomRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryTotalEscrowForDenomRequest.Merge(m, src) +} +func (m *QueryTotalEscrowForDenomRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryTotalEscrowForDenomRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryTotalEscrowForDenomRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryTotalEscrowForDenomRequest proto.InternalMessageInfo + +func (m *QueryTotalEscrowForDenomRequest) GetDenom() string { + if m != nil { + return m.Denom + } + return "" +} + +// QueryTotalEscrowForDenomResponse is the response type for TotalEscrowForDenom RPC method. +type QueryTotalEscrowForDenomResponse struct { + Amount types.Coin `protobuf:"bytes,1,opt,name=amount,proto3" json:"amount"` +} + +func (m *QueryTotalEscrowForDenomResponse) Reset() { *m = QueryTotalEscrowForDenomResponse{} } +func (m *QueryTotalEscrowForDenomResponse) String() string { return proto.CompactTextString(m) } +func (*QueryTotalEscrowForDenomResponse) ProtoMessage() {} +func (*QueryTotalEscrowForDenomResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_a638e2800a01538c, []int{11} +} +func (m *QueryTotalEscrowForDenomResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryTotalEscrowForDenomResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryTotalEscrowForDenomResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryTotalEscrowForDenomResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryTotalEscrowForDenomResponse.Merge(m, src) +} +func (m *QueryTotalEscrowForDenomResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryTotalEscrowForDenomResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryTotalEscrowForDenomResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryTotalEscrowForDenomResponse proto.InternalMessageInfo + +func (m *QueryTotalEscrowForDenomResponse) GetAmount() types.Coin { + if m != nil { + return m.Amount + } + return types.Coin{} +} + +func init() { + proto.RegisterType((*QueryParamsRequest)(nil), "ibc.applications.transfer.v1.QueryParamsRequest") + proto.RegisterType((*QueryParamsResponse)(nil), "ibc.applications.transfer.v1.QueryParamsResponse") + proto.RegisterType((*QueryDenomRequest)(nil), "ibc.applications.transfer.v1.QueryDenomRequest") + proto.RegisterType((*QueryDenomResponse)(nil), "ibc.applications.transfer.v1.QueryDenomResponse") + proto.RegisterType((*QueryDenomsRequest)(nil), "ibc.applications.transfer.v1.QueryDenomsRequest") + proto.RegisterType((*QueryDenomsResponse)(nil), "ibc.applications.transfer.v1.QueryDenomsResponse") + proto.RegisterType((*QueryDenomHashRequest)(nil), "ibc.applications.transfer.v1.QueryDenomHashRequest") + proto.RegisterType((*QueryDenomHashResponse)(nil), "ibc.applications.transfer.v1.QueryDenomHashResponse") + proto.RegisterType((*QueryEscrowAddressRequest)(nil), "ibc.applications.transfer.v1.QueryEscrowAddressRequest") + proto.RegisterType((*QueryEscrowAddressResponse)(nil), "ibc.applications.transfer.v1.QueryEscrowAddressResponse") + proto.RegisterType((*QueryTotalEscrowForDenomRequest)(nil), "ibc.applications.transfer.v1.QueryTotalEscrowForDenomRequest") + proto.RegisterType((*QueryTotalEscrowForDenomResponse)(nil), "ibc.applications.transfer.v1.QueryTotalEscrowForDenomResponse") +} + +func init() { + proto.RegisterFile("ibc/applications/transfer/v1/query.proto", fileDescriptor_a638e2800a01538c) +} + +var fileDescriptor_a638e2800a01538c = []byte{ + // 804 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x96, 0x5d, 0x4f, 0x13, 0x4d, + 0x14, 0xc7, 0xbb, 0x3c, 0xb4, 0xcf, 0xd3, 0x79, 0x02, 0x89, 0x43, 0x7d, 0xa1, 0xc1, 0x85, 0xac, + 0x08, 0x04, 0x65, 0x87, 0x82, 0x06, 0x4c, 0xc4, 0x44, 0x50, 0x14, 0x35, 0x11, 0x8b, 0x57, 0x6a, + 0xd2, 0x4c, 0xb7, 0xe3, 0x76, 0x63, 0xbb, 0xb3, 0xec, 0x6c, 0x6b, 0x48, 0xc3, 0x8d, 0x9f, 0xc0, + 0x84, 0x3b, 0xbf, 0x80, 0x89, 0xc6, 0xef, 0x41, 0xe2, 0x0d, 0x89, 0x89, 0xf1, 0x4a, 0x0d, 0xf8, + 0x41, 0xcc, 0xce, 0x9c, 0x2d, 0x5d, 0x2c, 0x65, 0xf1, 0xae, 0x9d, 0x39, 0xff, 0x73, 0x7e, 0x73, + 0xde, 0xb2, 0x68, 0xca, 0x29, 0x5b, 0x84, 0x7a, 0x5e, 0xcd, 0xb1, 0x68, 0xe0, 0x70, 0x57, 0x90, + 0xc0, 0xa7, 0xae, 0x78, 0xc9, 0x7c, 0xd2, 0x2c, 0x90, 0xcd, 0x06, 0xf3, 0xb7, 0x4c, 0xcf, 0xe7, + 0x01, 0xc7, 0x23, 0x4e, 0xd9, 0x32, 0x3b, 0x2d, 0xcd, 0xc8, 0xd2, 0x6c, 0x16, 0xf2, 0x39, 0x9b, + 0xdb, 0x5c, 0x1a, 0x92, 0xf0, 0x97, 0xd2, 0xe4, 0x75, 0x8b, 0x8b, 0x3a, 0x17, 0xa4, 0x4c, 0x05, + 0x23, 0xcd, 0x42, 0x99, 0x05, 0xb4, 0x40, 0x2c, 0xee, 0xb8, 0x70, 0x7f, 0xa5, 0x67, 0xf4, 0xb6, + 0x7f, 0x65, 0xdc, 0x1b, 0x35, 0xe0, 0xaf, 0x58, 0xe4, 0x76, 0xba, 0x33, 0xac, 0x7c, 0x43, 0x3b, + 0xb8, 0x47, 0x6d, 0xc7, 0x95, 0x72, 0xb0, 0x1d, 0xb1, 0x39, 0xb7, 0x6b, 0x8c, 0x50, 0xcf, 0x21, + 0xd4, 0x75, 0x79, 0x00, 0x8f, 0x93, 0xb7, 0x46, 0x0e, 0xe1, 0x27, 0xa1, 0x7e, 0x9d, 0xfa, 0xb4, + 0x2e, 0x8a, 0x6c, 0xb3, 0xc1, 0x44, 0x60, 0x6c, 0xa0, 0xa1, 0xd8, 0xa9, 0xf0, 0xb8, 0x2b, 0x18, + 0xbe, 0x89, 0x32, 0x9e, 0x3c, 0xb9, 0xa0, 0x8d, 0x69, 0x53, 0xff, 0xcf, 0x8d, 0x9b, 0xbd, 0x52, + 0x66, 0x82, 0x1a, 0x34, 0xc6, 0x24, 0x3a, 0x23, 0x9d, 0xde, 0x61, 0x2e, 0xaf, 0x43, 0x24, 0x8c, + 0x51, 0x7f, 0x95, 0x8a, 0xaa, 0x74, 0x98, 0x2d, 0xca, 0xdf, 0xc6, 0x63, 0x60, 0x02, 0x43, 0x08, + 0x7e, 0x03, 0xa5, 0x2b, 0xe1, 0x01, 0xc4, 0xbe, 0xd4, 0x3b, 0xb6, 0xd2, 0x2a, 0x85, 0xf1, 0xa2, + 0xd3, 0x61, 0xf4, 0x48, 0xbc, 0x8a, 0xd0, 0x61, 0xb2, 0xc0, 0xeb, 0x84, 0xa9, 0x32, 0x6b, 0x86, + 0x99, 0x35, 0x55, 0x77, 0x40, 0x66, 0xcd, 0x75, 0x6a, 0x33, 0xd0, 0x16, 0x3b, 0x94, 0xc6, 0x47, + 0x0d, 0xb2, 0x15, 0xb9, 0x07, 0xe0, 0x87, 0x28, 0x23, 0xc3, 0x87, 0xd9, 0xfa, 0x27, 0x21, 0xf1, + 0xf2, 0xe0, 0xee, 0xf7, 0xd1, 0xd4, 0x87, 0x1f, 0xa3, 0x19, 0x70, 0x06, 0x2e, 0xf0, 0xbd, 0x18, + 0x6c, 0x9f, 0x84, 0x9d, 0x3c, 0x11, 0x56, 0x91, 0xc4, 0x68, 0x67, 0xd0, 0xd9, 0x43, 0xd8, 0xfb, + 0x54, 0x54, 0xa3, 0x74, 0xe4, 0x50, 0x3a, 0xf0, 0xa9, 0xc5, 0xa0, 0x14, 0xea, 0x8f, 0x71, 0x15, + 0x9d, 0x3b, 0x6a, 0x0e, 0xcf, 0xeb, 0x56, 0xb9, 0x0d, 0x34, 0x2c, 0xad, 0xef, 0x0a, 0xcb, 0xe7, + 0xaf, 0x6f, 0x57, 0x2a, 0x3e, 0x13, 0xed, 0x7c, 0x9f, 0x47, 0xff, 0x7a, 0xdc, 0x0f, 0x4a, 0x4e, + 0x05, 0x34, 0x99, 0xf0, 0xef, 0x5a, 0x05, 0x5f, 0x44, 0xc8, 0xaa, 0x52, 0xd7, 0x65, 0xb5, 0xf0, + 0xae, 0x4f, 0xde, 0x65, 0xe1, 0x64, 0xad, 0x62, 0xac, 0xa0, 0x7c, 0x37, 0xa7, 0x80, 0x71, 0x19, + 0x0d, 0x32, 0x79, 0x51, 0xa2, 0xea, 0x06, 0x9c, 0x0f, 0xb0, 0x4e, 0x73, 0x63, 0x01, 0x8d, 0x4a, + 0x27, 0x4f, 0x79, 0x40, 0x6b, 0xca, 0xd3, 0x2a, 0xf7, 0x63, 0xad, 0x98, 0xeb, 0x6c, 0xb0, 0x6c, + 0xd4, 0x3b, 0xcf, 0xd1, 0xd8, 0xf1, 0x42, 0x60, 0x58, 0x40, 0x19, 0x5a, 0xe7, 0x0d, 0x37, 0x80, + 0x2e, 0x1a, 0x8e, 0x15, 0x26, 0x2a, 0xc9, 0x0a, 0x77, 0xdc, 0xe5, 0xfe, 0xb0, 0xbe, 0x45, 0x30, + 0x9f, 0x7b, 0xff, 0x1f, 0x4a, 0x4b, 0xef, 0x78, 0x47, 0x43, 0x19, 0x35, 0x2f, 0x78, 0xb6, 0x77, + 0x9f, 0xfc, 0x39, 0xae, 0xf9, 0xc2, 0x29, 0x14, 0x0a, 0xd9, 0x18, 0x7f, 0xf3, 0xe5, 0xd7, 0x4e, + 0x9f, 0x8e, 0x47, 0x08, 0x2c, 0x9d, 0xf8, 0xb2, 0x51, 0x23, 0x2b, 0xa9, 0x54, 0x23, 0x26, 0xa2, + 0x8a, 0xcd, 0x57, 0x22, 0xaa, 0xf8, 0xc8, 0x9c, 0x44, 0x05, 0xb3, 0xf0, 0x4e, 0x43, 0x69, 0x29, + 0xc4, 0x24, 0x69, 0x88, 0x88, 0x69, 0x36, 0xb9, 0x00, 0x90, 0x4c, 0x89, 0x34, 0x85, 0x27, 0x7a, + 0x21, 0x91, 0x56, 0xd8, 0xfe, 0x4b, 0xd3, 0xd3, 0xdb, 0xf8, 0x93, 0x86, 0xb2, 0xed, 0x61, 0xc1, + 0xf3, 0x49, 0xe3, 0x75, 0x4c, 0x62, 0xfe, 0xda, 0xe9, 0x44, 0x00, 0x7a, 0x5d, 0x82, 0x12, 0x3c, + 0xd3, 0x03, 0xb4, 0x14, 0x62, 0x32, 0x41, 0x5a, 0x72, 0xb8, 0x25, 0xef, 0x57, 0x0d, 0x0d, 0xc4, + 0x26, 0x0b, 0x2f, 0x24, 0x08, 0xdf, 0x6d, 0xc0, 0xf3, 0x8b, 0xa7, 0x17, 0x02, 0x7b, 0x51, 0xb2, + 0x3f, 0xc2, 0x0f, 0xba, 0xb3, 0xc3, 0x2e, 0x10, 0xa4, 0x75, 0xb8, 0x27, 0xb6, 0x49, 0xb8, 0x3d, + 0x04, 0x69, 0xc1, 0x4e, 0xd9, 0x26, 0xf1, 0x35, 0x80, 0x3f, 0x6b, 0x68, 0xa8, 0xcb, 0xd0, 0xe2, + 0xa5, 0x04, 0x94, 0xc7, 0x6f, 0x89, 0xfc, 0xad, 0xbf, 0x95, 0x27, 0x2b, 0x53, 0x10, 0x4a, 0x4b, + 0xea, 0x29, 0xa4, 0x25, 0x8b, 0x16, 0x96, 0x69, 0xb9, 0xb8, 0xbb, 0xaf, 0x6b, 0x7b, 0xfb, 0xba, + 0xf6, 0x73, 0x5f, 0xd7, 0xde, 0x1e, 0xe8, 0xa9, 0xbd, 0x03, 0x3d, 0xf5, 0xed, 0x40, 0x4f, 0x3d, + 0x5b, 0xb4, 0x9d, 0xa0, 0xda, 0x28, 0x9b, 0x16, 0xaf, 0x13, 0xf8, 0x2c, 0x70, 0xca, 0xd6, 0x8c, + 0xcd, 0x49, 0xb3, 0x30, 0x4b, 0xea, 0xbc, 0xd2, 0xa8, 0x31, 0x71, 0x24, 0x50, 0xb0, 0xe5, 0x31, + 0x51, 0xce, 0xc8, 0x4f, 0x80, 0xf9, 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0xa4, 0x29, 0xac, 0x94, + 0x23, 0x09, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // Params queries all parameters of the ibc-transfer module. + Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) + // Denoms queries all denominations + Denoms(ctx context.Context, in *QueryDenomsRequest, opts ...grpc.CallOption) (*QueryDenomsResponse, error) + // Denom queries a denomination + Denom(ctx context.Context, in *QueryDenomRequest, opts ...grpc.CallOption) (*QueryDenomResponse, error) + // DenomHash queries a denomination hash information. + DenomHash(ctx context.Context, in *QueryDenomHashRequest, opts ...grpc.CallOption) (*QueryDenomHashResponse, error) + // EscrowAddress returns the escrow address for a particular port and channel id. + EscrowAddress(ctx context.Context, in *QueryEscrowAddressRequest, opts ...grpc.CallOption) (*QueryEscrowAddressResponse, error) + // TotalEscrowForDenom returns the total amount of tokens in escrow based on the denom. + TotalEscrowForDenom(ctx context.Context, in *QueryTotalEscrowForDenomRequest, opts ...grpc.CallOption) (*QueryTotalEscrowForDenomResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) { + out := new(QueryParamsResponse) + err := c.cc.Invoke(ctx, "/ibc.applications.transfer.v1.Query/Params", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) Denoms(ctx context.Context, in *QueryDenomsRequest, opts ...grpc.CallOption) (*QueryDenomsResponse, error) { + out := new(QueryDenomsResponse) + err := c.cc.Invoke(ctx, "/ibc.applications.transfer.v1.Query/Denoms", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) Denom(ctx context.Context, in *QueryDenomRequest, opts ...grpc.CallOption) (*QueryDenomResponse, error) { + out := new(QueryDenomResponse) + err := c.cc.Invoke(ctx, "/ibc.applications.transfer.v1.Query/Denom", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) DenomHash(ctx context.Context, in *QueryDenomHashRequest, opts ...grpc.CallOption) (*QueryDenomHashResponse, error) { + out := new(QueryDenomHashResponse) + err := c.cc.Invoke(ctx, "/ibc.applications.transfer.v1.Query/DenomHash", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) EscrowAddress(ctx context.Context, in *QueryEscrowAddressRequest, opts ...grpc.CallOption) (*QueryEscrowAddressResponse, error) { + out := new(QueryEscrowAddressResponse) + err := c.cc.Invoke(ctx, "/ibc.applications.transfer.v1.Query/EscrowAddress", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) TotalEscrowForDenom(ctx context.Context, in *QueryTotalEscrowForDenomRequest, opts ...grpc.CallOption) (*QueryTotalEscrowForDenomResponse, error) { + out := new(QueryTotalEscrowForDenomResponse) + err := c.cc.Invoke(ctx, "/ibc.applications.transfer.v1.Query/TotalEscrowForDenom", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // Params queries all parameters of the ibc-transfer module. + Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) + // Denoms queries all denominations + Denoms(context.Context, *QueryDenomsRequest) (*QueryDenomsResponse, error) + // Denom queries a denomination + Denom(context.Context, *QueryDenomRequest) (*QueryDenomResponse, error) + // DenomHash queries a denomination hash information. + DenomHash(context.Context, *QueryDenomHashRequest) (*QueryDenomHashResponse, error) + // EscrowAddress returns the escrow address for a particular port and channel id. + EscrowAddress(context.Context, *QueryEscrowAddressRequest) (*QueryEscrowAddressResponse, error) + // TotalEscrowForDenom returns the total amount of tokens in escrow based on the denom. + TotalEscrowForDenom(context.Context, *QueryTotalEscrowForDenomRequest) (*QueryTotalEscrowForDenomResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") +} +func (*UnimplementedQueryServer) Denoms(ctx context.Context, req *QueryDenomsRequest) (*QueryDenomsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Denoms not implemented") +} +func (*UnimplementedQueryServer) Denom(ctx context.Context, req *QueryDenomRequest) (*QueryDenomResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Denom not implemented") +} +func (*UnimplementedQueryServer) DenomHash(ctx context.Context, req *QueryDenomHashRequest) (*QueryDenomHashResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DenomHash not implemented") +} +func (*UnimplementedQueryServer) EscrowAddress(ctx context.Context, req *QueryEscrowAddressRequest) (*QueryEscrowAddressResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method EscrowAddress not implemented") +} +func (*UnimplementedQueryServer) TotalEscrowForDenom(ctx context.Context, req *QueryTotalEscrowForDenomRequest) (*QueryTotalEscrowForDenomResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method TotalEscrowForDenom not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryParamsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Params(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.applications.transfer.v1.Query/Params", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Params(ctx, req.(*QueryParamsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_Denoms_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryDenomsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Denoms(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.applications.transfer.v1.Query/Denoms", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Denoms(ctx, req.(*QueryDenomsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_Denom_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryDenomRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Denom(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.applications.transfer.v1.Query/Denom", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Denom(ctx, req.(*QueryDenomRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_DenomHash_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryDenomHashRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).DenomHash(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.applications.transfer.v1.Query/DenomHash", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).DenomHash(ctx, req.(*QueryDenomHashRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_EscrowAddress_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryEscrowAddressRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).EscrowAddress(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.applications.transfer.v1.Query/EscrowAddress", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).EscrowAddress(ctx, req.(*QueryEscrowAddressRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_TotalEscrowForDenom_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryTotalEscrowForDenomRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).TotalEscrowForDenom(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.applications.transfer.v1.Query/TotalEscrowForDenom", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).TotalEscrowForDenom(ctx, req.(*QueryTotalEscrowForDenomRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ibc.applications.transfer.v1.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Params", + Handler: _Query_Params_Handler, + }, + { + MethodName: "Denoms", + Handler: _Query_Denoms_Handler, + }, + { + MethodName: "Denom", + Handler: _Query_Denom_Handler, + }, + { + MethodName: "DenomHash", + Handler: _Query_DenomHash_Handler, + }, + { + MethodName: "EscrowAddress", + Handler: _Query_EscrowAddress_Handler, + }, + { + MethodName: "TotalEscrowForDenom", + Handler: _Query_TotalEscrowForDenom_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "ibc/applications/transfer/v1/query.proto", +} + +func (m *QueryParamsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Params != nil { + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryDenomRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryDenomRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryDenomRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Hash) > 0 { + i -= len(m.Hash) + copy(dAtA[i:], m.Hash) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Hash))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryDenomResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryDenomResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryDenomResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Denom != nil { + { + size, err := m.Denom.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryDenomsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryDenomsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryDenomsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryDenomsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryDenomsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryDenomsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Denoms) > 0 { + for iNdEx := len(m.Denoms) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Denoms[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *QueryDenomHashRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryDenomHashRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryDenomHashRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Trace) > 0 { + i -= len(m.Trace) + copy(dAtA[i:], m.Trace) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Trace))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryDenomHashResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryDenomHashResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryDenomHashResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Hash) > 0 { + i -= len(m.Hash) + copy(dAtA[i:], m.Hash) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Hash))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryEscrowAddressRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryEscrowAddressRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryEscrowAddressRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryEscrowAddressResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryEscrowAddressResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryEscrowAddressResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.EscrowAddress) > 0 { + i -= len(m.EscrowAddress) + copy(dAtA[i:], m.EscrowAddress) + i = encodeVarintQuery(dAtA, i, uint64(len(m.EscrowAddress))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryTotalEscrowForDenomRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryTotalEscrowForDenomRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryTotalEscrowForDenomRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Denom) > 0 { + i -= len(m.Denom) + copy(dAtA[i:], m.Denom) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Denom))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryTotalEscrowForDenomResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryTotalEscrowForDenomResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryTotalEscrowForDenomResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Amount.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryParamsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Params != nil { + l = m.Params.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryDenomRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Hash) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryDenomResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Denom != nil { + l = m.Denom.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryDenomsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryDenomsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Denoms) > 0 { + for _, e := range m.Denoms { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryDenomHashRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Trace) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryDenomHashResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Hash) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryEscrowAddressRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryEscrowAddressResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.EscrowAddress) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryTotalEscrowForDenomRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Denom) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryTotalEscrowForDenomResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Amount.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryParamsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Params == nil { + m.Params = &Params{} + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryDenomRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryDenomRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryDenomRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Hash = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryDenomResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryDenomResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryDenomResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Denom", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Denom == nil { + m.Denom = &Denom{} + } + if err := m.Denom.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryDenomsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryDenomsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryDenomsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryDenomsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryDenomsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryDenomsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Denoms", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Denoms = append(m.Denoms, Denom{}) + if err := m.Denoms[len(m.Denoms)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryDenomHashRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryDenomHashRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryDenomHashRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Trace", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Trace = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryDenomHashResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryDenomHashResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryDenomHashResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Hash = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryEscrowAddressRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryEscrowAddressRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryEscrowAddressRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryEscrowAddressResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryEscrowAddressResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryEscrowAddressResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field EscrowAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.EscrowAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryTotalEscrowForDenomRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryTotalEscrowForDenomRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryTotalEscrowForDenomRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Denom", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Denom = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryTotalEscrowForDenomResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryTotalEscrowForDenomResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryTotalEscrowForDenomResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Amount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/apps/transfer/types/query.pb.gw.go b/modules/apps/transfer/types/query.pb.gw.go new file mode 100644 index 0000000..4519c06 --- /dev/null +++ b/modules/apps/transfer/types/query.pb.gw.go @@ -0,0 +1,662 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: ibc/applications/transfer/v1/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +func request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := client.Params(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := server.Params(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Query_Denoms_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Query_Denoms_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryDenomsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Denoms_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Denoms(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Denoms_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryDenomsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Denoms_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Denoms(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_Denom_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryDenomRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["hash"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "hash") + } + + protoReq.Hash, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "hash", err) + } + + msg, err := client.Denom(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Denom_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryDenomRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["hash"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "hash") + } + + protoReq.Hash, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "hash", err) + } + + msg, err := server.Denom(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_DenomHash_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryDenomHashRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["trace"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "trace") + } + + protoReq.Trace, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "trace", err) + } + + msg, err := client.DenomHash(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_DenomHash_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryDenomHashRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["trace"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "trace") + } + + protoReq.Trace, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "trace", err) + } + + msg, err := server.DenomHash(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_EscrowAddress_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryEscrowAddressRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + msg, err := client.EscrowAddress(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_EscrowAddress_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryEscrowAddressRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + msg, err := server.EscrowAddress(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_TotalEscrowForDenom_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryTotalEscrowForDenomRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["denom"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "denom") + } + + protoReq.Denom, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "denom", err) + } + + msg, err := client.TotalEscrowForDenom(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_TotalEscrowForDenom_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryTotalEscrowForDenomRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["denom"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "denom") + } + + protoReq.Denom, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "denom", err) + } + + msg, err := server.TotalEscrowForDenom(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Params_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Denoms_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Denoms_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Denoms_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Denom_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Denom_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Denom_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_DenomHash_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_DenomHash_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_DenomHash_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_EscrowAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_EscrowAddress_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_EscrowAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_TotalEscrowForDenom_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_TotalEscrowForDenom_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_TotalEscrowForDenom_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Params_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Denoms_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Denoms_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Denoms_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Denom_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Denom_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Denom_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_DenomHash_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_DenomHash_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_DenomHash_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_EscrowAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_EscrowAddress_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_EscrowAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_TotalEscrowForDenom_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_TotalEscrowForDenom_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_TotalEscrowForDenom_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"ibc", "apps", "transfer", "v1", "params"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_Denoms_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"ibc", "apps", "transfer", "v1", "denoms"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_Denom_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 3, 0, 4, 1, 5, 5}, []string{"ibc", "apps", "transfer", "v1", "denoms", "hash"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_DenomHash_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 3, 0, 4, 1, 5, 5}, []string{"ibc", "apps", "transfer", "v1", "denom_hashes", "trace"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_EscrowAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 1, 0, 4, 1, 5, 7, 2, 8}, []string{"ibc", "apps", "transfer", "v1", "channels", "channel_id", "ports", "port_id", "escrow_address"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_TotalEscrowForDenom_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 3, 0, 4, 1, 5, 5}, []string{"ibc", "apps", "transfer", "v1", "total_escrow", "denom"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Query_Params_0 = runtime.ForwardResponseMessage + + forward_Query_Denoms_0 = runtime.ForwardResponseMessage + + forward_Query_Denom_0 = runtime.ForwardResponseMessage + + forward_Query_DenomHash_0 = runtime.ForwardResponseMessage + + forward_Query_EscrowAddress_0 = runtime.ForwardResponseMessage + + forward_Query_TotalEscrowForDenom_0 = runtime.ForwardResponseMessage +) diff --git a/modules/apps/transfer/types/solidity_abi.go b/modules/apps/transfer/types/solidity_abi.go new file mode 100644 index 0000000..7dffe4d --- /dev/null +++ b/modules/apps/transfer/types/solidity_abi.go @@ -0,0 +1,111 @@ +package types + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi" + + errorsmod "cosmossdk.io/errors" +) + +// getICS20ABI returns an abi.Arguments slice describing the Solidity types of the struct. +func getICS20ABI() abi.Arguments { + // Create the ABI types for each field. + // The Solidity types used are: + // - string for Denom, Sender, Receiver and Memo. + // - uint256 for Amount. + tupleType, err := abi.NewType("tuple", "", []abi.ArgumentMarshaling{ + { + Name: "denom", + Type: "string", + }, + { + Name: "sender", + Type: "string", + }, + { + Name: "receiver", + Type: "string", + }, + { + Name: "amount", + Type: "uint256", + }, + { + Name: "memo", + Type: "string", + }, + }) + if err != nil { + panic(err) + } + + // Create an ABI argument representing our struct as a single tuple argument. + arguments := abi.Arguments{ + { + Type: tupleType, + }, + } + + return arguments +} + +// DecodeABIFungibleTokenPacketData decodes a solidity ABI encoded ics20lib.ICS20LibFungibleTokenPacketData +// and converts it into an ibc-go FungibleTokenPacketData. +func DecodeABIFungibleTokenPacketData(data []byte) (*FungibleTokenPacketData, error) { + arguments := getICS20ABI() + + packetDataI, err := arguments.Unpack(data) + if err != nil { + return nil, errorsmod.Wrapf(ErrAbiDecoding, "failed to unpack data: %s", err) + } + + packetData, ok := packetDataI[0].(struct { + Denom string `json:"denom"` + Sender string `json:"sender"` + Receiver string `json:"receiver"` + Amount *big.Int `json:"amount"` + Memo string `json:"memo"` + }) + if !ok { + return nil, errorsmod.Wrapf(ErrAbiDecoding, "failed to parse packet data") + } + + return &FungibleTokenPacketData{ + Denom: packetData.Denom, + Sender: packetData.Sender, + Receiver: packetData.Receiver, + Amount: packetData.Amount.String(), + Memo: packetData.Memo, + }, nil +} + +func EncodeABIFungibleTokenPacketData(data *FungibleTokenPacketData) ([]byte, error) { + amount, ok := new(big.Int).SetString(data.Amount, 10) + if !ok { + return nil, errorsmod.Wrapf(ErrAbiEncoding, "failed to parse amount: %s", data.Amount) + } + + packetData := struct { + Denom string `json:"denom"` + Sender string `json:"sender"` + Receiver string `json:"receiver"` + Amount *big.Int `json:"amount"` + Memo string `json:"memo"` + }{ + data.Denom, + data.Sender, + data.Receiver, + amount, + data.Memo, + } + + arguments := getICS20ABI() + // Pack the values in the order defined in the ABI. + encodedData, err := arguments.Pack(packetData) + if err != nil { + return nil, errorsmod.Wrapf(ErrAbiEncoding, "failed to pack data: %s", err) + } + + return encodedData, nil +} diff --git a/modules/apps/transfer/types/solidity_abi_test.go b/modules/apps/transfer/types/solidity_abi_test.go new file mode 100644 index 0000000..3ebda68 --- /dev/null +++ b/modules/apps/transfer/types/solidity_abi_test.go @@ -0,0 +1,23 @@ +package types_test + +import ( + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" +) + +func (suite *TypesTestSuite) TestFTPD() { + packetData := types.FungibleTokenPacketData{ + Denom: "uatom", + Amount: "1000000", + Sender: "sender", + Receiver: "receiver", + Memo: "memo", + } + + bz, err := types.EncodeABIFungibleTokenPacketData(&packetData) + suite.Require().NoError(err) + + decodedPacketData, err := types.DecodeABIFungibleTokenPacketData(bz) + suite.Require().NoError(err) + + suite.Require().Equal(packetData, *decodedPacketData) +} diff --git a/modules/apps/transfer/types/token.go b/modules/apps/transfer/types/token.go new file mode 100644 index 0000000..1de308f --- /dev/null +++ b/modules/apps/transfer/types/token.go @@ -0,0 +1,55 @@ +package types + +import ( + "math/big" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// maxUint256 is the maximum value for a 256 bit unsigned integer. +var maxUint256 = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(1)) + +// Validate validates a token denomination and amount. +func (t Token) Validate() error { + if err := t.Denom.Validate(); err != nil { + return errorsmod.Wrap(err, "invalid token denom") + } + + amount, ok := sdkmath.NewIntFromString(t.Amount) + if !ok { + return errorsmod.Wrapf(ErrInvalidAmount, "unable to parse transfer amount (%s) into math.Int", t.Amount) + } + + if !amount.IsPositive() { + return errorsmod.Wrapf(ErrInvalidAmount, "amount must be strictly positive: got %d", amount) + } + + return nil +} + +// ToCoin converts a Token to an sdk.Coin. +// +// The function parses the Amount field of the Token into an sdkmath.Int and returns a new sdk.Coin with +// the IBCDenom of the Token's Denom field and the parsed Amount. +// If the Amount cannot be parsed, an error is returned with a wrapped error message. +func (t Token) ToCoin() (sdk.Coin, error) { + transferAmount, ok := sdkmath.NewIntFromString(t.Amount) + if !ok { + return sdk.Coin{}, errorsmod.Wrapf(ErrInvalidAmount, "unable to parse transfer amount (%s) into math.Int", transferAmount) + } + + coin := sdk.NewCoin(t.Denom.IBCDenom(), transferAmount) + return coin, nil +} + +// UnboundedSpendLimit returns the sentinel value that can be used +// as the amount for a denomination's spend limit for which spend limit updating +// should be disabled. Please note that using this sentinel value means that a grantee +// will be granted the privilege to do ICS20 token transfers for the total amount +// of the denomination available at the granter's account. +func UnboundedSpendLimit() sdkmath.Int { + return sdkmath.NewIntFromBigInt(maxUint256) +} diff --git a/modules/apps/transfer/types/token.pb.go b/modules/apps/transfer/types/token.pb.go new file mode 100644 index 0000000..f534488 --- /dev/null +++ b/modules/apps/transfer/types/token.pb.go @@ -0,0 +1,840 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/applications/transfer/v1/token.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Token defines a struct which represents a token to be transferred. +type Token struct { + // the token denomination + Denom Denom `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom"` + // the token amount to be transferred + Amount string `protobuf:"bytes,2,opt,name=amount,proto3" json:"amount,omitempty"` +} + +func (m *Token) Reset() { *m = Token{} } +func (m *Token) String() string { return proto.CompactTextString(m) } +func (*Token) ProtoMessage() {} +func (*Token) Descriptor() ([]byte, []int) { + return fileDescriptor_aa36f5082d5cd501, []int{0} +} +func (m *Token) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Token) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Token.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Token) XXX_Merge(src proto.Message) { + xxx_messageInfo_Token.Merge(m, src) +} +func (m *Token) XXX_Size() int { + return m.Size() +} +func (m *Token) XXX_DiscardUnknown() { + xxx_messageInfo_Token.DiscardUnknown(m) +} + +var xxx_messageInfo_Token proto.InternalMessageInfo + +func (m *Token) GetDenom() Denom { + if m != nil { + return m.Denom + } + return Denom{} +} + +func (m *Token) GetAmount() string { + if m != nil { + return m.Amount + } + return "" +} + +// Denom holds the base denom of a Token and a trace of the chains it was sent through. +type Denom struct { + // the base token denomination + Base string `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + // the trace of the token + Trace []Hop `protobuf:"bytes,3,rep,name=trace,proto3" json:"trace"` +} + +func (m *Denom) Reset() { *m = Denom{} } +func (m *Denom) String() string { return proto.CompactTextString(m) } +func (*Denom) ProtoMessage() {} +func (*Denom) Descriptor() ([]byte, []int) { + return fileDescriptor_aa36f5082d5cd501, []int{1} +} +func (m *Denom) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Denom) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Denom.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Denom) XXX_Merge(src proto.Message) { + xxx_messageInfo_Denom.Merge(m, src) +} +func (m *Denom) XXX_Size() int { + return m.Size() +} +func (m *Denom) XXX_DiscardUnknown() { + xxx_messageInfo_Denom.DiscardUnknown(m) +} + +var xxx_messageInfo_Denom proto.InternalMessageInfo + +func (m *Denom) GetBase() string { + if m != nil { + return m.Base + } + return "" +} + +func (m *Denom) GetTrace() []Hop { + if m != nil { + return m.Trace + } + return nil +} + +// Hop defines a port ID, channel ID pair specifying a unique "hop" in a trace +type Hop struct { + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` +} + +func (m *Hop) Reset() { *m = Hop{} } +func (*Hop) ProtoMessage() {} +func (*Hop) Descriptor() ([]byte, []int) { + return fileDescriptor_aa36f5082d5cd501, []int{2} +} +func (m *Hop) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Hop) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Hop.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Hop) XXX_Merge(src proto.Message) { + xxx_messageInfo_Hop.Merge(m, src) +} +func (m *Hop) XXX_Size() int { + return m.Size() +} +func (m *Hop) XXX_DiscardUnknown() { + xxx_messageInfo_Hop.DiscardUnknown(m) +} + +var xxx_messageInfo_Hop proto.InternalMessageInfo + +func (m *Hop) GetPortId() string { + if m != nil { + return m.PortId + } + return "" +} + +func (m *Hop) GetChannelId() string { + if m != nil { + return m.ChannelId + } + return "" +} + +func init() { + proto.RegisterType((*Token)(nil), "ibc.applications.transfer.v1.Token") + proto.RegisterType((*Denom)(nil), "ibc.applications.transfer.v1.Denom") + proto.RegisterType((*Hop)(nil), "ibc.applications.transfer.v1.Hop") +} + +func init() { + proto.RegisterFile("ibc/applications/transfer/v1/token.proto", fileDescriptor_aa36f5082d5cd501) +} + +var fileDescriptor_aa36f5082d5cd501 = []byte{ + // 322 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x91, 0x31, 0x4f, 0x02, 0x31, + 0x1c, 0xc5, 0xef, 0x84, 0xc3, 0x50, 0xb6, 0xc6, 0x28, 0x31, 0x7a, 0x20, 0x2e, 0xb7, 0xd8, 0x0a, + 0x2e, 0xc6, 0xc4, 0x98, 0xa0, 0x03, 0xac, 0x17, 0x27, 0x16, 0xed, 0xf5, 0xea, 0xd1, 0xc8, 0xf5, + 0xdf, 0x5c, 0x0b, 0x89, 0xdf, 0xc2, 0xd1, 0xd1, 0x8f, 0xc3, 0xc8, 0xe8, 0x64, 0x0c, 0x7c, 0x11, + 0x73, 0x05, 0x12, 0x26, 0xb6, 0xff, 0xbf, 0xfd, 0xbd, 0xf7, 0x9a, 0x3e, 0x14, 0xc9, 0x84, 0x53, + 0xa6, 0xf5, 0x44, 0x72, 0x66, 0x25, 0x28, 0x43, 0x6d, 0xc1, 0x94, 0x79, 0x13, 0x05, 0x9d, 0x75, + 0xa9, 0x85, 0x77, 0xa1, 0x88, 0x2e, 0xc0, 0x02, 0x3e, 0x93, 0x09, 0x27, 0xbb, 0x24, 0xd9, 0x92, + 0x64, 0xd6, 0x3d, 0x3d, 0xca, 0x20, 0x03, 0x07, 0xd2, 0x72, 0x5a, 0x6b, 0x3a, 0xaf, 0x28, 0x78, + 0x2e, 0x2d, 0xf0, 0x03, 0x0a, 0x52, 0xa1, 0x20, 0x6f, 0xfa, 0x6d, 0x3f, 0x6a, 0xf4, 0x2e, 0xc9, + 0x3e, 0x33, 0xf2, 0x54, 0xa2, 0xfd, 0xea, 0xfc, 0xb7, 0xe5, 0xc5, 0x6b, 0x1d, 0x3e, 0x46, 0x35, + 0x96, 0xc3, 0x54, 0xd9, 0xe6, 0x41, 0xdb, 0x8f, 0xea, 0xf1, 0x66, 0xeb, 0x8c, 0x50, 0xe0, 0x68, + 0x8c, 0x51, 0x35, 0x61, 0x46, 0xb8, 0x80, 0x7a, 0xec, 0x66, 0x7c, 0x8f, 0x02, 0x5b, 0x30, 0x2e, + 0x9a, 0x95, 0x76, 0x25, 0x6a, 0xf4, 0x2e, 0xf6, 0xa7, 0x0e, 0x40, 0x6f, 0x33, 0x9d, 0xaa, 0xf3, + 0x88, 0x2a, 0x03, 0xd0, 0xf8, 0x04, 0x1d, 0x6a, 0x28, 0xec, 0x8b, 0x4c, 0x37, 0xe6, 0xb5, 0x72, + 0x1d, 0xa6, 0xf8, 0x1c, 0x21, 0x3e, 0x66, 0x4a, 0x89, 0x49, 0x79, 0xb7, 0x7e, 0x57, 0x7d, 0x73, + 0x32, 0x4c, 0xef, 0xaa, 0x5f, 0xdf, 0x2d, 0xaf, 0x1f, 0xcf, 0x97, 0xa1, 0xbf, 0x58, 0x86, 0xfe, + 0xdf, 0x32, 0xf4, 0x3f, 0x57, 0xa1, 0xb7, 0x58, 0x85, 0xde, 0xcf, 0x2a, 0xf4, 0x46, 0xb7, 0x99, + 0xb4, 0xe3, 0x69, 0x42, 0x38, 0xe4, 0x94, 0x83, 0xc9, 0xc1, 0x50, 0x99, 0xf0, 0xab, 0x0c, 0xe8, + 0xac, 0x7b, 0x4d, 0x73, 0x48, 0xa7, 0x13, 0x61, 0xca, 0x6e, 0x76, 0x3a, 0xb1, 0x1f, 0x5a, 0x98, + 0xa4, 0xe6, 0x7e, 0xf7, 0xe6, 0x3f, 0x00, 0x00, 0xff, 0xff, 0x2d, 0x38, 0x59, 0x27, 0xbd, 0x01, + 0x00, 0x00, +} + +func (m *Token) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Token) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Token) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Amount) > 0 { + i -= len(m.Amount) + copy(dAtA[i:], m.Amount) + i = encodeVarintToken(dAtA, i, uint64(len(m.Amount))) + i-- + dAtA[i] = 0x12 + } + { + size, err := m.Denom.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintToken(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *Denom) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Denom) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Denom) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Trace) > 0 { + for iNdEx := len(m.Trace) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Trace[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintToken(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + if len(m.Base) > 0 { + i -= len(m.Base) + copy(dAtA[i:], m.Base) + i = encodeVarintToken(dAtA, i, uint64(len(m.Base))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Hop) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Hop) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Hop) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintToken(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintToken(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintToken(dAtA []byte, offset int, v uint64) int { + offset -= sovToken(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Token) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Denom.Size() + n += 1 + l + sovToken(uint64(l)) + l = len(m.Amount) + if l > 0 { + n += 1 + l + sovToken(uint64(l)) + } + return n +} + +func (m *Denom) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Base) + if l > 0 { + n += 1 + l + sovToken(uint64(l)) + } + if len(m.Trace) > 0 { + for _, e := range m.Trace { + l = e.Size() + n += 1 + l + sovToken(uint64(l)) + } + } + return n +} + +func (m *Hop) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovToken(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovToken(uint64(l)) + } + return n +} + +func sovToken(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozToken(x uint64) (n int) { + return sovToken(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Token) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowToken + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Token: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Token: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Denom", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowToken + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthToken + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthToken + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Denom.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowToken + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthToken + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthToken + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Amount = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipToken(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthToken + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Denom) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowToken + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Denom: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Denom: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Base", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowToken + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthToken + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthToken + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Base = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Trace", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowToken + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthToken + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthToken + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Trace = append(m.Trace, Hop{}) + if err := m.Trace[len(m.Trace)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipToken(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthToken + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Hop) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowToken + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Hop: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Hop: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowToken + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthToken + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthToken + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowToken + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthToken + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthToken + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipToken(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthToken + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipToken(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowToken + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowToken + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowToken + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthToken + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupToken + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthToken + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthToken = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowToken = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupToken = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/apps/transfer/types/token_test.go b/modules/apps/transfer/types/token_test.go new file mode 100644 index 0000000..21fa5cf --- /dev/null +++ b/modules/apps/transfer/types/token_test.go @@ -0,0 +1,202 @@ +package types + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + denom = "atom/pool" + amount = "100" +) + +func TestValidate(t *testing.T) { + testCases := []struct { + name string + token Token + expError error + }{ + { + "success: multiple port channel pair denom", + Token{ + Denom: Denom{ + Base: "atom", + Trace: []Hop{ + NewHop("transfer", "channel-0"), + NewHop("transfer", "channel-1"), + }, + }, + Amount: amount, + }, + nil, + }, + { + "success: one port channel pair denom", + Token{ + Denom: Denom{ + Base: "uatom", + Trace: []Hop{ + NewHop("transfer", "channel-1"), + }, + }, + Amount: amount, + }, + nil, + }, + { + "success: non transfer port trace", + Token{ + Denom: Denom{ + Base: "uatom", + Trace: []Hop{ + NewHop("transfer", "channel-0"), + NewHop("transfer", "channel-1"), + NewHop("transfer-custom", "channel-2"), + }, + }, + Amount: amount, + }, + nil, + }, + { + "failure: empty denom", + Token{ + Denom: Denom{}, + Amount: amount, + }, + ErrInvalidDenomForTransfer, + }, + { + "failure: invalid amount string", + Token{ + Denom: Denom{ + Base: "atom", + Trace: []Hop{ + NewHop("transfer", "channel-0"), + NewHop("transfer", "channel-1"), + }, + }, + Amount: "value", + }, + ErrInvalidAmount, + }, + { + "failure: amount is zero", + Token{ + Denom: Denom{ + Base: "atom", + Trace: []Hop{ + NewHop("transfer", "channel-0"), + NewHop("transfer", "channel-1"), + }, + }, + Amount: "0", + }, + ErrInvalidAmount, + }, + { + "failure: amount is negative", + Token{ + Denom: Denom{ + Base: "atom", + Trace: []Hop{ + NewHop("transfer", "channel-0"), + NewHop("transfer", "channel-1"), + }, + }, + Amount: "-1", + }, + ErrInvalidAmount, + }, + { + "failure: invalid identifier in trace", + Token{ + Denom: Denom{ + Base: "uatom", + Trace: []Hop{ + NewHop("transfer", "channel-1"), + NewHop("randomport", ""), + }, + }, + Amount: amount, + }, + errors.New("invalid token denom: invalid trace: invalid hop source channel ID : identifier cannot be blank: invalid identifier"), + }, + { + "failure: empty identifier in trace", + Token{ + Denom: Denom{ + Base: "uatom", + Trace: []Hop{{}}, + }, + Amount: amount, + }, + errors.New("invalid token denom: invalid trace: invalid hop source port ID : identifier cannot be blank: invalid identifier"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.token.Validate() + if tc.expError == nil { + require.NoError(t, err, tc.name) + } else { + require.ErrorContains(t, err, tc.expError.Error(), tc.name) + } + }) + } +} + +func TestToCoin(t *testing.T) { + testCases := []struct { + name string + token Token + expCoin sdk.Coin + expError error + }{ + { + "success: convert token to coin", + Token{ + Denom: Denom{ + Base: denom, + Trace: []Hop{}, + }, + Amount: amount, + }, + sdk.NewCoin(denom, sdkmath.NewInt(100)), + nil, + }, + { + "failure: invalid amount string", + Token{ + Denom: Denom{ + Base: denom, + Trace: []Hop{}, + }, + Amount: "value", + }, + sdk.Coin{}, + ErrInvalidAmount, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + coin, err := tc.token.ToCoin() + + require.Equal(t, tc.expCoin, coin, tc.name) + + if tc.expError == nil { + require.NoError(t, err, tc.name) + } else { + require.ErrorContains(t, err, tc.expError.Error(), tc.name) + } + }) + } +} diff --git a/modules/apps/transfer/types/transfer.pb.go b/modules/apps/transfer/types/transfer.pb.go new file mode 100644 index 0000000..febc9fd --- /dev/null +++ b/modules/apps/transfer/types/transfer.pb.go @@ -0,0 +1,359 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/applications/transfer/v1/transfer.proto + +package types + +import ( + fmt "fmt" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Params defines the set of IBC transfer parameters. +// NOTE: To prevent a single token from being transferred, set the +// TransfersEnabled parameter to true and then set the bank module's SendEnabled +// parameter for the denomination to false. +type Params struct { + // send_enabled enables or disables all cross-chain token transfers from this + // chain. + SendEnabled bool `protobuf:"varint,1,opt,name=send_enabled,json=sendEnabled,proto3" json:"send_enabled,omitempty"` + // receive_enabled enables or disables all cross-chain token transfers to this + // chain. + ReceiveEnabled bool `protobuf:"varint,2,opt,name=receive_enabled,json=receiveEnabled,proto3" json:"receive_enabled,omitempty"` +} + +func (m *Params) Reset() { *m = Params{} } +func (m *Params) String() string { return proto.CompactTextString(m) } +func (*Params) ProtoMessage() {} +func (*Params) Descriptor() ([]byte, []int) { + return fileDescriptor_5041673e96e97901, []int{0} +} +func (m *Params) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Params) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params.Merge(m, src) +} +func (m *Params) XXX_Size() int { + return m.Size() +} +func (m *Params) XXX_DiscardUnknown() { + xxx_messageInfo_Params.DiscardUnknown(m) +} + +var xxx_messageInfo_Params proto.InternalMessageInfo + +func (m *Params) GetSendEnabled() bool { + if m != nil { + return m.SendEnabled + } + return false +} + +func (m *Params) GetReceiveEnabled() bool { + if m != nil { + return m.ReceiveEnabled + } + return false +} + +func init() { + proto.RegisterType((*Params)(nil), "ibc.applications.transfer.v1.Params") +} + +func init() { + proto.RegisterFile("ibc/applications/transfer/v1/transfer.proto", fileDescriptor_5041673e96e97901) +} + +var fileDescriptor_5041673e96e97901 = []byte{ + // 215 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0xce, 0x4c, 0x4a, 0xd6, + 0x4f, 0x2c, 0x28, 0xc8, 0xc9, 0x4c, 0x4e, 0x2c, 0xc9, 0xcc, 0xcf, 0x2b, 0xd6, 0x2f, 0x29, 0x4a, + 0xcc, 0x2b, 0x4e, 0x4b, 0x2d, 0xd2, 0x2f, 0x33, 0x84, 0xb3, 0xf5, 0x0a, 0x8a, 0xf2, 0x4b, 0xf2, + 0x85, 0x64, 0x32, 0x93, 0x92, 0xf5, 0x90, 0x15, 0xeb, 0xc1, 0x15, 0x94, 0x19, 0x2a, 0x85, 0x70, + 0xb1, 0x05, 0x24, 0x16, 0x25, 0xe6, 0x16, 0x0b, 0x29, 0x72, 0xf1, 0x14, 0xa7, 0xe6, 0xa5, 0xc4, + 0xa7, 0xe6, 0x25, 0x26, 0xe5, 0xa4, 0xa6, 0x48, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x04, 0x71, 0x83, + 0xc4, 0x5c, 0x21, 0x42, 0x42, 0xea, 0x5c, 0xfc, 0x45, 0xa9, 0xc9, 0xa9, 0x99, 0x65, 0xa9, 0x70, + 0x55, 0x4c, 0x60, 0x55, 0x7c, 0x50, 0x61, 0xa8, 0x42, 0xa7, 0xa0, 0x13, 0x8f, 0xe4, 0x18, 0x2f, + 0x3c, 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, + 0x6e, 0x3c, 0x96, 0x63, 0x88, 0xb2, 0x48, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, + 0xd5, 0x4f, 0xce, 0x2f, 0xce, 0xcd, 0x2f, 0xd6, 0xcf, 0x4c, 0x4a, 0xd6, 0x4d, 0xcf, 0xd7, 0x2f, + 0x33, 0x34, 0xd0, 0xcf, 0xcd, 0x4f, 0x29, 0xcd, 0x49, 0x2d, 0x06, 0xf9, 0x0d, 0xc9, 0x4f, 0x25, + 0x95, 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0x60, 0xef, 0x18, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x4b, + 0xe2, 0xb2, 0xbf, 0xfd, 0x00, 0x00, 0x00, +} + +func (m *Params) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Params) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.ReceiveEnabled { + i-- + if m.ReceiveEnabled { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x10 + } + if m.SendEnabled { + i-- + if m.SendEnabled { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintTransfer(dAtA []byte, offset int, v uint64) int { + offset -= sovTransfer(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SendEnabled { + n += 2 + } + if m.ReceiveEnabled { + n += 2 + } + return n +} + +func sovTransfer(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTransfer(x uint64) (n int) { + return sovTransfer(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Params) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTransfer + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Params: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SendEnabled", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTransfer + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.SendEnabled = bool(v != 0) + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ReceiveEnabled", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTransfer + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.ReceiveEnabled = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipTransfer(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTransfer + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTransfer(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTransfer + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTransfer + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTransfer + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTransfer + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTransfer + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTransfer + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTransfer = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTransfer = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTransfer = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/apps/transfer/types/transfer_authorization.go b/modules/apps/transfer/types/transfer_authorization.go new file mode 100644 index 0000000..7f556d7 --- /dev/null +++ b/modules/apps/transfer/types/transfer_authorization.go @@ -0,0 +1,198 @@ +package types + +import ( + "context" + "slices" + "strings" + + "github.com/cosmos/gogoproto/proto" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/authz" + + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +var _ authz.Authorization = (*TransferAuthorization)(nil) + +const ( + allocationNotFound = -1 +) + +// NewTransferAuthorization creates a new TransferAuthorization object. +func NewTransferAuthorization(allocations ...Allocation) *TransferAuthorization { + return &TransferAuthorization{ + Allocations: allocations, + } +} + +// MsgTypeURL implements Authorization.MsgTypeURL. +func (TransferAuthorization) MsgTypeURL() string { + return sdk.MsgTypeURL(&MsgTransfer{}) +} + +// Accept implements Authorization.Accept. +func (a TransferAuthorization) Accept(goCtx context.Context, msg proto.Message) (authz.AcceptResponse, error) { + msgTransfer, ok := msg.(*MsgTransfer) + if !ok { + return authz.AcceptResponse{}, errorsmod.Wrap(ibcerrors.ErrInvalidType, "type mismatch") + } + + index := getAllocationIndex(*msgTransfer, a.Allocations) + if index == allocationNotFound { + return authz.AcceptResponse{}, errorsmod.Wrap(ibcerrors.ErrNotFound, "requested port and channel allocation does not exist") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + if !isAllowedAddress(ctx, msgTransfer.Receiver, a.Allocations[index].AllowList) { + return authz.AcceptResponse{}, errorsmod.Wrap(ibcerrors.ErrInvalidAddress, "not allowed receiver address for transfer") + } + + if err := validateMemo(ctx, msgTransfer.Memo, a.Allocations[index].AllowedPacketData); err != nil { + return authz.AcceptResponse{}, err + } + + // bool flag to see if we have updated any of the allocations + allocationModified := false + + // update spend limit the token token in the MsgTransfer + // If the spend limit is set to the MaxUint256 sentinel value, do not subtract the amount from the spend limit. + // if there is no unlimited spend, then we need to subtract the amount from the spend limit to get the limit left + if !a.Allocations[index].SpendLimit.AmountOf(msgTransfer.Token.Denom).Equal(UnboundedSpendLimit()) { + limitLeft, isNegative := a.Allocations[index].SpendLimit.SafeSub(msgTransfer.Token) + if isNegative { + return authz.AcceptResponse{}, errorsmod.Wrapf(ibcerrors.ErrInsufficientFunds, "requested amount of token %s is more than spend limit", msgTransfer.Token.Denom) + } + + allocationModified = true + + // modify the spend limit with the reduced amount. + a.Allocations[index].SpendLimit = limitLeft + } + + // if the spend limit is zero of the associated allocation then we delete it. + // NOTE: SpendLimit is an array of coins, with each one representing the remaining spend limit for an + // individual denomination. + if a.Allocations[index].SpendLimit.IsZero() { + a.Allocations = slices.Delete(a.Allocations, index, index+1) + } + + if len(a.Allocations) == 0 { + return authz.AcceptResponse{Accept: true, Delete: true}, nil + } + + if !allocationModified { + return authz.AcceptResponse{Accept: true, Delete: false, Updated: nil}, nil + } + + return authz.AcceptResponse{Accept: true, Delete: false, Updated: &TransferAuthorization{ + Allocations: a.Allocations, + }}, nil +} + +// ValidateBasic implements Authorization.ValidateBasic. +func (a TransferAuthorization) ValidateBasic() error { + if len(a.Allocations) == 0 { + return errorsmod.Wrap(ErrInvalidAuthorization, "allocations cannot be empty") + } + + foundChannels := make(map[string]bool, 0) + + for _, allocation := range a.Allocations { + if _, found := foundChannels[allocation.SourceChannel]; found { + return errorsmod.Wrapf(channeltypes.ErrInvalidChannel, "duplicate source channel ID: %s", allocation.SourceChannel) + } + + foundChannels[allocation.SourceChannel] = true + + if allocation.SpendLimit == nil { + return errorsmod.Wrap(ibcerrors.ErrInvalidCoins, "spend limit cannot be nil") + } + + if err := allocation.SpendLimit.Validate(); err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidCoins, "invalid spend limit: %s", err.Error()) + } + + if err := host.PortIdentifierValidator(allocation.SourcePort); err != nil { + return errorsmod.Wrap(err, "invalid source port ID") + } + + if err := host.ChannelIdentifierValidator(allocation.SourceChannel); err != nil { + return errorsmod.Wrap(err, "invalid source channel ID") + } + + found := make(map[string]bool, 0) + for i := range allocation.AllowList { + if found[allocation.AllowList[i]] { + return errorsmod.Wrapf(ErrInvalidAuthorization, "duplicate entry in allow list %s", allocation.AllowList[i]) + } + found[allocation.AllowList[i]] = true + } + } + + return nil +} + +// isAllowedAddress returns a boolean indicating if the receiver address is valid for transfer. +// gasCostPerIteration gas is consumed for each iteration. +func isAllowedAddress(ctx sdk.Context, receiver string, allowedAddrs []string) bool { + if len(allowedAddrs) == 0 { + return true + } + + gasCostPerIteration := ctx.KVGasConfig().IterNextCostFlat + + for _, addr := range allowedAddrs { + ctx.GasMeter().ConsumeGas(gasCostPerIteration, "transfer authorization") + if addr == receiver { + return true + } + } + return false +} + +// validateMemo returns a nil error indicating if the memo is valid for transfer. +func validateMemo(ctx sdk.Context, memo string, allowedMemos []string) error { + // if the allow list is empty, then the memo must be an empty string + if len(allowedMemos) == 0 { + if len(strings.TrimSpace(memo)) != 0 { + return errorsmod.Wrapf(ErrInvalidAuthorization, "memo must be empty because allowed packet data in allocation is empty") + } + + return nil + } + + // if allowedPacketDataList has only 1 element and it equals AllowAllPacketDataKeys + // then accept all the memo strings + if len(allowedMemos) == 1 && allowedMemos[0] == AllowAllPacketDataKeys { + return nil + } + + gasCostPerIteration := ctx.KVGasConfig().IterNextCostFlat + isMemoAllowed := slices.ContainsFunc(allowedMemos, func(allowedMemo string) bool { + ctx.GasMeter().ConsumeGas(gasCostPerIteration, "transfer authorization") + + return strings.TrimSpace(memo) == strings.TrimSpace(allowedMemo) + }) + + if !isMemoAllowed { + return errorsmod.Wrapf(ErrInvalidAuthorization, "not allowed memo: %s", memo) + } + + return nil +} + +// getAllocationIndex ranges through a set of allocations, and returns the index of the allocation if found. If not, returns -1. +func getAllocationIndex(msg MsgTransfer, allocations []Allocation) int { + for index, allocation := range allocations { + if allocation.SourceChannel == msg.SourceChannel && allocation.SourcePort == msg.SourcePort { + return index + } + } + return allocationNotFound +} diff --git a/modules/apps/transfer/types/transfer_authorization_test.go b/modules/apps/transfer/types/transfer_authorization_test.go new file mode 100644 index 0000000..b759877 --- /dev/null +++ b/modules/apps/transfer/types/transfer_authorization_test.go @@ -0,0 +1,393 @@ +package types_test + +import ( + "fmt" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/authz" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + "github.com/cosmos/ibc-go/v10/testing/mock" +) + +const ( + testMemo1 = `{"wasm":{"contract":"osmo1c3ljch9dfw5kf52nfwpxd2zmj2ese7agnx0p9tenkrryasrle5sqf3ftpg","msg":{"osmosis_swap":{"output_denom":"uosmo","slippage":{"twap":{"slippage_percentage":"20","window_seconds":10}},"receiver":"feeabs/feeabs1efd63aw40lxf3n4mhf7dzhjkr453axurwrhrrw","on_failed_delivery":"do_nothing"}}}}` + testMemo2 = `{"forward":{"channel":"channel-11","port":"transfer","receiver":"stars1twfv52yxcyykx2lcvgl42svw46hsm5dd4ww6xy","retries":2,"timeout":1712146014542131200}}` +) + +func (suite *TypesTestSuite) TestTransferAuthorizationAccept() { + var ( + msgTransfer *types.MsgTransfer + transferAuthz types.TransferAuthorization + ) + + testCases := []struct { + name string + malleate func() + assertResult func(res authz.AcceptResponse, err error) + }{ + { + "success", + func() {}, + func(res authz.AcceptResponse, err error) { + suite.Require().NoError(err) + + suite.Require().True(res.Accept) + suite.Require().True(res.Delete) + suite.Require().Nil(res.Updated) + }, + }, + { + "success: with spend limit updated", + func() { + msgTransfer.Token = sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(50)) + }, + func(res authz.AcceptResponse, err error) { + suite.Require().NoError(err) + + suite.Require().True(res.Accept) + suite.Require().False(res.Delete) + + updatedAuthz, ok := res.Updated.(*types.TransferAuthorization) + suite.Require().True(ok) + + isEqual := updatedAuthz.Allocations[0].SpendLimit.Equal(sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(50)))) + suite.Require().True(isEqual) + }, + }, + { + "success: with empty allow list", + func() { + transferAuthz.Allocations[0].AllowList = []string{} + }, + func(res authz.AcceptResponse, err error) { + suite.Require().NoError(err) + + suite.Require().True(res.Accept) + suite.Require().True(res.Delete) + suite.Require().Nil(res.Updated) + }, + }, + { + "success: with unlimited spend limit of max uint256", + func() { + transferAuthz.Allocations[0].SpendLimit = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, types.UnboundedSpendLimit())) + }, + func(res authz.AcceptResponse, err error) { + suite.Require().NoError(err) + + suite.Require().True(res.Accept) + suite.Require().False(res.Delete) + suite.Require().Nil(res.Updated) + }, + }, + { + "success: empty AllowedPacketData and empty memo", + func() { + allowedList := []string{} + transferAuthz.Allocations[0].AllowedPacketData = allowedList + }, + func(res authz.AcceptResponse, err error) { + suite.Require().NoError(err) + + suite.Require().True(res.Accept) + suite.Require().True(res.Delete) + suite.Require().Nil(res.Updated) + }, + }, + { + "success: AllowedPacketData allows any packet", + func() { + allowedList := []string{"*"} + transferAuthz.Allocations[0].AllowedPacketData = allowedList + msgTransfer.Memo = testMemo1 + }, + func(res authz.AcceptResponse, err error) { + suite.Require().NoError(err) + + suite.Require().True(res.Accept) + suite.Require().True(res.Delete) + suite.Require().Nil(res.Updated) + }, + }, + { + "success: transfer memo allowed", + func() { + allowedList := []string{testMemo1, testMemo2} + transferAuthz.Allocations[0].AllowedPacketData = allowedList + msgTransfer.Memo = testMemo1 + }, + func(res authz.AcceptResponse, err error) { + suite.Require().NoError(err) + + suite.Require().True(res.Accept) + suite.Require().True(res.Delete) + suite.Require().Nil(res.Updated) + }, + }, + { + "empty AllowedPacketData but not empty memo", + func() { + allowedList := []string{} + transferAuthz.Allocations[0].AllowedPacketData = allowedList + msgTransfer.Memo = testMemo1 + }, + func(res authz.AcceptResponse, err error) { + suite.Require().Error(err) + }, + }, + { + "memo not allowed", + func() { + allowedList := []string{testMemo1} + transferAuthz.Allocations[0].AllowedPacketData = allowedList + msgTransfer.Memo = testMemo2 + }, + func(res authz.AcceptResponse, err error) { + suite.Require().Error(err) + suite.Require().ErrorContains(err, fmt.Sprintf("not allowed memo: %s", testMemo2)) + }, + }, + { + "test multiple coins does not overspend", + func() { + transferAuthz.Allocations[0].SpendLimit = transferAuthz.Allocations[0].SpendLimit.Add( + sdk.NewCoins( + sdk.NewCoin("test-denom", sdkmath.NewInt(100)), + sdk.NewCoin("test-denom2", sdkmath.NewInt(100)), + )..., + ) + msgTransfer.Token = sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(50)) + }, + func(res authz.AcceptResponse, err error) { + suite.Require().NoError(err) + + updatedTransferAuthz, ok := res.Updated.(*types.TransferAuthorization) + suite.Require().True(ok) + + remainder := updatedTransferAuthz.Allocations[0].SpendLimit.AmountOf(sdk.DefaultBondDenom) + suite.Require().True(sdkmath.NewInt(50).Equal(remainder)) + + remainder = updatedTransferAuthz.Allocations[0].SpendLimit.AmountOf("test-denom") + suite.Require().True(sdkmath.NewInt(100).Equal(remainder)) + + remainder = updatedTransferAuthz.Allocations[0].SpendLimit.AmountOf("test-denom2") + suite.Require().True(sdkmath.NewInt(100).Equal(remainder)) + }, + }, + { + "no spend limit set for MsgTransfer port/channel", + func() { + msgTransfer.SourcePort = ibctesting.MockPort + msgTransfer.SourceChannel = "channel-9" + }, + func(res authz.AcceptResponse, err error) { + suite.Require().Error(err) + }, + }, + { + "requested transfer amount is more than the spend limit", + func() { + msgTransfer.Token = sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(1000)) + }, + func(res authz.AcceptResponse, err error) { + suite.Require().Error(err) + }, + }, + { + "receiver address not permitted via allow list", + func() { + msgTransfer.Receiver = suite.chainB.SenderAccount.GetAddress().String() + }, + func(res authz.AcceptResponse, err error) { + suite.Require().Error(err) + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + path := ibctesting.NewTransferPath(suite.chainA, suite.chainB) + path.Setup() + + transferAuthz = types.TransferAuthorization{ + Allocations: []types.Allocation{ + { + SourcePort: path.EndpointA.ChannelConfig.PortID, + SourceChannel: path.EndpointA.ChannelID, + SpendLimit: sdk.NewCoins(ibctesting.TestCoin), + AllowList: []string{ibctesting.TestAccAddress}, + }, + }, + } + + msgTransfer = types.NewMsgTransfer( + path.EndpointA.ChannelConfig.PortID, + path.EndpointA.ChannelID, + ibctesting.TestCoin, + suite.chainA.SenderAccount.GetAddress().String(), + ibctesting.TestAccAddress, + suite.chainB.GetTimeoutHeight(), + 0, + "", + ) + + tc.malleate() + + res, err := transferAuthz.Accept(suite.chainA.GetContext(), msgTransfer) + tc.assertResult(res, err) + }) + } +} + +func (suite *TypesTestSuite) TestTransferAuthorizationMsgTypeURL() { + var transferAuthz types.TransferAuthorization + suite.Require().Equal(sdk.MsgTypeURL(&types.MsgTransfer{}), transferAuthz.MsgTypeURL(), "invalid type url for transfer authorization") +} + +func (suite *TypesTestSuite) TestTransferAuthorizationValidateBasic() { + var transferAuthz types.TransferAuthorization + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() {}, + nil, + }, + { + "success: empty allow list", + func() { + transferAuthz.Allocations[0].AllowList = []string{} + }, + nil, + }, + { + "success: with multiple allocations", + func() { + allocation := types.Allocation{ + SourcePort: types.PortID, + SourceChannel: "channel-1", + SpendLimit: sdk.NewCoins(ibctesting.TestCoin), + AllowList: []string{}, + } + + transferAuthz.Allocations = append(transferAuthz.Allocations, allocation) + }, + nil, + }, + { + "success: with unlimited spend limit of max uint256", + func() { + transferAuthz.Allocations[0].SpendLimit = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, types.UnboundedSpendLimit())) + }, + nil, + }, + { + "success: wildcard allowed packet data", + func() { + transferAuthz.Allocations[0].AllowedPacketData = []string{"*"} + }, + nil, + }, + { + "empty allocations", + func() { + transferAuthz = types.TransferAuthorization{Allocations: []types.Allocation{}} + }, + types.ErrInvalidAuthorization, + }, + { + "nil allocations", + func() { + transferAuthz = types.TransferAuthorization{} + }, + types.ErrInvalidAuthorization, + }, + { + "nil spend limit coins", + func() { + transferAuthz.Allocations[0].SpendLimit = nil + }, + ibcerrors.ErrInvalidCoins, + }, + { + "invalid spend limit coins", + func() { + transferAuthz.Allocations[0].SpendLimit = sdk.Coins{sdk.Coin{Denom: ""}} + }, + ibcerrors.ErrInvalidCoins, + }, + { + "duplicate entry in allow list", + func() { + transferAuthz.Allocations[0].AllowList = []string{ibctesting.TestAccAddress, ibctesting.TestAccAddress} + }, + types.ErrInvalidAuthorization, + }, + { + "invalid port identifier", + func() { + transferAuthz.Allocations[0].SourcePort = "" + }, + host.ErrInvalidID, + }, + { + "invalid channel identifier", + func() { + transferAuthz.Allocations[0].SourceChannel = "" + }, + host.ErrInvalidID, + }, + { + "duplicate channel ID", + func() { + allocation := types.Allocation{ + SourcePort: mock.PortID, + SourceChannel: transferAuthz.Allocations[0].SourceChannel, + SpendLimit: sdk.NewCoins(ibctesting.TestCoin), + AllowList: []string{ibctesting.TestAccAddress}, + } + + transferAuthz.Allocations = append(transferAuthz.Allocations, allocation) + }, + channeltypes.ErrInvalidChannel, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + transferAuthz = types.TransferAuthorization{ + Allocations: []types.Allocation{ + { + SourcePort: mock.PortID, + SourceChannel: ibctesting.FirstChannelID, + SpendLimit: sdk.NewCoins(ibctesting.TestCoin), + AllowList: []string{ibctesting.TestAccAddress}, + }, + }, + } + + tc.malleate() + + err := transferAuthz.ValidateBasic() + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} diff --git a/modules/apps/transfer/types/tx.pb.go b/modules/apps/transfer/types/tx.pb.go new file mode 100644 index 0000000..43a2ac3 --- /dev/null +++ b/modules/apps/transfer/types/tx.pb.go @@ -0,0 +1,1309 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/applications/transfer/v1/tx.proto + +package types + +import ( + context "context" + fmt "fmt" + types "github.com/cosmos/cosmos-sdk/types" + _ "github.com/cosmos/cosmos-sdk/types/msgservice" + _ "github.com/cosmos/cosmos-sdk/types/tx/amino" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + types1 "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// MsgTransfer defines a msg to transfer fungible tokens (i.e Coins) between +// ICS20 enabled chains. See ICS Spec here: +// https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#data-structures +type MsgTransfer struct { + // the port on which the packet will be sent + SourcePort string `protobuf:"bytes,1,opt,name=source_port,json=sourcePort,proto3" json:"source_port,omitempty"` + // the channel by which the packet will be sent + SourceChannel string `protobuf:"bytes,2,opt,name=source_channel,json=sourceChannel,proto3" json:"source_channel,omitempty"` + // token to be transferred + Token types.Coin `protobuf:"bytes,3,opt,name=token,proto3" json:"token"` + // the sender address + Sender string `protobuf:"bytes,4,opt,name=sender,proto3" json:"sender,omitempty"` + // the recipient address on the destination chain + Receiver string `protobuf:"bytes,5,opt,name=receiver,proto3" json:"receiver,omitempty"` + // Timeout height relative to the current block height. + // If you are sending with IBC v1 protocol, either timeout_height or timeout_timestamp must be set. + // If you are sending with IBC v2 protocol, timeout_timestamp must be set, and timeout_height must be omitted. + TimeoutHeight types1.Height `protobuf:"bytes,6,opt,name=timeout_height,json=timeoutHeight,proto3" json:"timeout_height"` + // Timeout timestamp in absolute nanoseconds since unix epoch. + // If you are sending with IBC v1 protocol, either timeout_height or timeout_timestamp must be set. + // If you are sending with IBC v2 protocol, timeout_timestamp must be set. + TimeoutTimestamp uint64 `protobuf:"varint,7,opt,name=timeout_timestamp,json=timeoutTimestamp,proto3" json:"timeout_timestamp,omitempty"` + // optional memo + Memo string `protobuf:"bytes,8,opt,name=memo,proto3" json:"memo,omitempty"` + // optional encoding + Encoding string `protobuf:"bytes,9,opt,name=encoding,proto3" json:"encoding,omitempty"` +} + +func (m *MsgTransfer) Reset() { *m = MsgTransfer{} } +func (m *MsgTransfer) String() string { return proto.CompactTextString(m) } +func (*MsgTransfer) ProtoMessage() {} +func (*MsgTransfer) Descriptor() ([]byte, []int) { + return fileDescriptor_7401ed9bed2f8e09, []int{0} +} +func (m *MsgTransfer) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgTransfer) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgTransfer.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgTransfer) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgTransfer.Merge(m, src) +} +func (m *MsgTransfer) XXX_Size() int { + return m.Size() +} +func (m *MsgTransfer) XXX_DiscardUnknown() { + xxx_messageInfo_MsgTransfer.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgTransfer proto.InternalMessageInfo + +// MsgTransferResponse defines the Msg/Transfer response type. +type MsgTransferResponse struct { + // sequence number of the transfer packet sent + Sequence uint64 `protobuf:"varint,1,opt,name=sequence,proto3" json:"sequence,omitempty"` +} + +func (m *MsgTransferResponse) Reset() { *m = MsgTransferResponse{} } +func (m *MsgTransferResponse) String() string { return proto.CompactTextString(m) } +func (*MsgTransferResponse) ProtoMessage() {} +func (*MsgTransferResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_7401ed9bed2f8e09, []int{1} +} +func (m *MsgTransferResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgTransferResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgTransferResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgTransferResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgTransferResponse.Merge(m, src) +} +func (m *MsgTransferResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgTransferResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgTransferResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgTransferResponse proto.InternalMessageInfo + +// MsgUpdateParams is the Msg/UpdateParams request type. +type MsgUpdateParams struct { + // signer address + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // params defines the transfer parameters to update. + // + // NOTE: All parameters must be supplied. + Params Params `protobuf:"bytes,2,opt,name=params,proto3" json:"params"` +} + +func (m *MsgUpdateParams) Reset() { *m = MsgUpdateParams{} } +func (m *MsgUpdateParams) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParams) ProtoMessage() {} +func (*MsgUpdateParams) Descriptor() ([]byte, []int) { + return fileDescriptor_7401ed9bed2f8e09, []int{2} +} +func (m *MsgUpdateParams) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParams.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParams.Merge(m, src) +} +func (m *MsgUpdateParams) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParams) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParams.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParams proto.InternalMessageInfo + +// MsgUpdateParamsResponse defines the response structure for executing a +// MsgUpdateParams message. +type MsgUpdateParamsResponse struct { +} + +func (m *MsgUpdateParamsResponse) Reset() { *m = MsgUpdateParamsResponse{} } +func (m *MsgUpdateParamsResponse) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParamsResponse) ProtoMessage() {} +func (*MsgUpdateParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_7401ed9bed2f8e09, []int{3} +} +func (m *MsgUpdateParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParamsResponse.Merge(m, src) +} +func (m *MsgUpdateParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParamsResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*MsgTransfer)(nil), "ibc.applications.transfer.v1.MsgTransfer") + proto.RegisterType((*MsgTransferResponse)(nil), "ibc.applications.transfer.v1.MsgTransferResponse") + proto.RegisterType((*MsgUpdateParams)(nil), "ibc.applications.transfer.v1.MsgUpdateParams") + proto.RegisterType((*MsgUpdateParamsResponse)(nil), "ibc.applications.transfer.v1.MsgUpdateParamsResponse") +} + +func init() { + proto.RegisterFile("ibc/applications/transfer/v1/tx.proto", fileDescriptor_7401ed9bed2f8e09) +} + +var fileDescriptor_7401ed9bed2f8e09 = []byte{ + // 629 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x53, 0x31, 0x6f, 0xd4, 0x4a, + 0x10, 0x3e, 0xbf, 0x5c, 0xee, 0x25, 0x7b, 0x2f, 0xc9, 0x8b, 0x41, 0x89, 0x63, 0x21, 0x5f, 0x74, + 0x22, 0x52, 0xb8, 0x28, 0xbb, 0x38, 0x08, 0x81, 0xae, 0xbc, 0x34, 0x14, 0x44, 0x8a, 0xac, 0xd0, + 0xd0, 0x44, 0xf6, 0xde, 0xe0, 0x5b, 0xe5, 0xbc, 0x6b, 0xbc, 0x7b, 0x27, 0x68, 0x10, 0xa2, 0x42, + 0x54, 0xfc, 0x00, 0x0a, 0x4a, 0xca, 0xfc, 0x8c, 0x94, 0x29, 0xa9, 0x10, 0x4a, 0x8a, 0x34, 0xfc, + 0x08, 0xb4, 0xeb, 0xf5, 0x61, 0x28, 0x02, 0x34, 0xf6, 0xce, 0xcc, 0x37, 0xdf, 0xcc, 0x7c, 0xb3, + 0x8b, 0xb6, 0x58, 0x42, 0x49, 0x9c, 0xe7, 0x63, 0x46, 0x63, 0xc5, 0x04, 0x97, 0x44, 0x15, 0x31, + 0x97, 0xcf, 0xa0, 0x20, 0xd3, 0x90, 0xa8, 0x17, 0x38, 0x2f, 0x84, 0x12, 0xee, 0x2d, 0x96, 0x50, + 0x5c, 0x87, 0xe1, 0x0a, 0x86, 0xa7, 0xa1, 0xbf, 0x1a, 0x67, 0x8c, 0x0b, 0x62, 0xbe, 0x65, 0x82, + 0x7f, 0x33, 0x15, 0xa9, 0x30, 0x47, 0xa2, 0x4f, 0xd6, 0xbb, 0x4e, 0x85, 0xcc, 0x84, 0x24, 0x99, + 0x4c, 0x35, 0x7d, 0x26, 0x53, 0x1b, 0x08, 0x6c, 0x20, 0x89, 0x25, 0x90, 0x69, 0x98, 0x80, 0x8a, + 0x43, 0x42, 0x05, 0xe3, 0x36, 0xde, 0xd1, 0x6d, 0x52, 0x51, 0x00, 0xa1, 0x63, 0x06, 0x5c, 0xe9, + 0xec, 0xf2, 0x64, 0x01, 0x3b, 0xd7, 0xcf, 0x51, 0x35, 0x6b, 0xc0, 0xdd, 0x0f, 0x73, 0xa8, 0x7d, + 0x20, 0xd3, 0x23, 0xeb, 0x75, 0x3b, 0xa8, 0x2d, 0xc5, 0xa4, 0xa0, 0x70, 0x9c, 0x8b, 0x42, 0x79, + 0xce, 0xa6, 0xb3, 0xbd, 0x18, 0xa1, 0xd2, 0x75, 0x28, 0x0a, 0xe5, 0x6e, 0xa1, 0x65, 0x0b, 0xa0, + 0xa3, 0x98, 0x73, 0x18, 0x7b, 0xff, 0x18, 0xcc, 0x52, 0xe9, 0xdd, 0x2f, 0x9d, 0x6e, 0x1f, 0xcd, + 0x2b, 0x71, 0x02, 0xdc, 0x9b, 0xdb, 0x74, 0xb6, 0xdb, 0x7b, 0x1b, 0xb8, 0x9c, 0x0a, 0xeb, 0xa9, + 0xb0, 0x9d, 0x0a, 0xef, 0x0b, 0xc6, 0x07, 0x8b, 0x67, 0x5f, 0x3a, 0x8d, 0x4f, 0x57, 0xa7, 0x3d, + 0x27, 0x2a, 0x53, 0xdc, 0x35, 0xd4, 0x92, 0xc0, 0x87, 0x50, 0x78, 0x4d, 0x43, 0x6d, 0x2d, 0xd7, + 0x47, 0x0b, 0x05, 0x50, 0x60, 0x53, 0x28, 0xbc, 0x79, 0x13, 0x99, 0xd9, 0xee, 0x63, 0xb4, 0xac, + 0x58, 0x06, 0x62, 0xa2, 0x8e, 0x47, 0xc0, 0xd2, 0x91, 0xf2, 0x5a, 0xa6, 0xb0, 0x8f, 0xf5, 0xba, + 0xb4, 0x5c, 0xd8, 0x8a, 0x34, 0x0d, 0xf1, 0x23, 0x83, 0xa8, 0x57, 0x5e, 0xb2, 0xc9, 0x65, 0xc4, + 0xdd, 0x41, 0xab, 0x15, 0x9b, 0xfe, 0x4b, 0x15, 0x67, 0xb9, 0xf7, 0xef, 0xa6, 0xb3, 0xdd, 0x8c, + 0xfe, 0xb7, 0x81, 0xa3, 0xca, 0xef, 0xba, 0xa8, 0x99, 0x41, 0x26, 0xbc, 0x05, 0xd3, 0x92, 0x39, + 0xeb, 0x56, 0x81, 0x53, 0x31, 0x64, 0x3c, 0xf5, 0x16, 0xcb, 0x56, 0x2b, 0xbb, 0xdf, 0x7b, 0xfb, + 0xb1, 0xd3, 0x78, 0x73, 0x75, 0xda, 0xb3, 0x73, 0xbd, 0xbb, 0x3a, 0xed, 0xad, 0x95, 0xf2, 0xec, + 0xca, 0xe1, 0x09, 0xa9, 0xad, 0xa3, 0xfb, 0x00, 0xdd, 0xa8, 0x99, 0x11, 0xc8, 0x5c, 0x70, 0x09, + 0x9a, 0x5e, 0xc2, 0xf3, 0x09, 0x70, 0x0a, 0x66, 0x45, 0xcd, 0x68, 0x66, 0xf7, 0x9b, 0x9a, 0xbe, + 0xfb, 0x0a, 0xad, 0x1c, 0xc8, 0xf4, 0x49, 0x3e, 0x8c, 0x15, 0x1c, 0xc6, 0x45, 0x9c, 0x49, 0x23, + 0x2b, 0x4b, 0x39, 0x14, 0x76, 0xab, 0xd6, 0x72, 0x07, 0xa8, 0x95, 0x1b, 0x84, 0xd9, 0x64, 0x7b, + 0xef, 0x36, 0xbe, 0xee, 0x86, 0xe3, 0x92, 0x6d, 0xd0, 0xd4, 0xe2, 0x45, 0x36, 0xb3, 0xbf, 0xf2, + 0x63, 0x26, 0x43, 0xda, 0xdd, 0x40, 0xeb, 0xbf, 0xd4, 0xaf, 0x9a, 0xdf, 0xfb, 0xe6, 0xa0, 0xb9, + 0x03, 0x99, 0xba, 0x23, 0xb4, 0x30, 0xbb, 0x76, 0x77, 0xae, 0xaf, 0x59, 0xd3, 0xc0, 0x0f, 0xff, + 0x18, 0x3a, 0x93, 0x4b, 0xa1, 0xff, 0x7e, 0x52, 0x62, 0xf7, 0xb7, 0x14, 0x75, 0xb8, 0x7f, 0xff, + 0xaf, 0xe0, 0x55, 0x55, 0x7f, 0xfe, 0xb5, 0xbe, 0x5a, 0x83, 0xe8, 0xec, 0x22, 0x70, 0xce, 0x2f, + 0x02, 0xe7, 0xeb, 0x45, 0xe0, 0xbc, 0xbf, 0x0c, 0x1a, 0xe7, 0x97, 0x41, 0xe3, 0xf3, 0x65, 0xd0, + 0x78, 0xfa, 0x30, 0x65, 0x6a, 0x34, 0x49, 0x30, 0x15, 0x19, 0xb1, 0x8f, 0x9e, 0x25, 0x74, 0x37, + 0x15, 0x64, 0x1a, 0xde, 0x25, 0x99, 0x18, 0x4e, 0xc6, 0x20, 0xf5, 0x4b, 0xae, 0xbd, 0x60, 0xf5, + 0x32, 0x07, 0x99, 0xb4, 0xcc, 0xe3, 0xbd, 0xf7, 0x3d, 0x00, 0x00, 0xff, 0xff, 0xc9, 0x87, 0xc2, + 0x5c, 0xb3, 0x04, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MsgClient is the client API for Msg service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MsgClient interface { + // Transfer defines a rpc handler method for MsgTransfer. + Transfer(ctx context.Context, in *MsgTransfer, opts ...grpc.CallOption) (*MsgTransferResponse, error) + // UpdateParams defines a rpc handler for MsgUpdateParams. + UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) +} + +type msgClient struct { + cc grpc1.ClientConn +} + +func NewMsgClient(cc grpc1.ClientConn) MsgClient { + return &msgClient{cc} +} + +func (c *msgClient) Transfer(ctx context.Context, in *MsgTransfer, opts ...grpc.CallOption) (*MsgTransferResponse, error) { + out := new(MsgTransferResponse) + err := c.cc.Invoke(ctx, "/ibc.applications.transfer.v1.Msg/Transfer", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { + out := new(MsgUpdateParamsResponse) + err := c.cc.Invoke(ctx, "/ibc.applications.transfer.v1.Msg/UpdateParams", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MsgServer is the server API for Msg service. +type MsgServer interface { + // Transfer defines a rpc handler method for MsgTransfer. + Transfer(context.Context, *MsgTransfer) (*MsgTransferResponse, error) + // UpdateParams defines a rpc handler for MsgUpdateParams. + UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) +} + +// UnimplementedMsgServer can be embedded to have forward compatible implementations. +type UnimplementedMsgServer struct { +} + +func (*UnimplementedMsgServer) Transfer(ctx context.Context, req *MsgTransfer) (*MsgTransferResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Transfer not implemented") +} +func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") +} + +func RegisterMsgServer(s grpc1.Server, srv MsgServer) { + s.RegisterService(&_Msg_serviceDesc, srv) +} + +func _Msg_Transfer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgTransfer) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).Transfer(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.applications.transfer.v1.Msg/Transfer", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).Transfer(ctx, req.(*MsgTransfer)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUpdateParams) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).UpdateParams(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.applications.transfer.v1.Msg/UpdateParams", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).UpdateParams(ctx, req.(*MsgUpdateParams)) + } + return interceptor(ctx, in, info, handler) +} + +var _Msg_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ibc.applications.transfer.v1.Msg", + HandlerType: (*MsgServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Transfer", + Handler: _Msg_Transfer_Handler, + }, + { + MethodName: "UpdateParams", + Handler: _Msg_UpdateParams_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "ibc/applications/transfer/v1/tx.proto", +} + +func (m *MsgTransfer) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgTransfer) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgTransfer) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Encoding) > 0 { + i -= len(m.Encoding) + copy(dAtA[i:], m.Encoding) + i = encodeVarintTx(dAtA, i, uint64(len(m.Encoding))) + i-- + dAtA[i] = 0x4a + } + if len(m.Memo) > 0 { + i -= len(m.Memo) + copy(dAtA[i:], m.Memo) + i = encodeVarintTx(dAtA, i, uint64(len(m.Memo))) + i-- + dAtA[i] = 0x42 + } + if m.TimeoutTimestamp != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.TimeoutTimestamp)) + i-- + dAtA[i] = 0x38 + } + { + size, err := m.TimeoutHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + if len(m.Receiver) > 0 { + i -= len(m.Receiver) + copy(dAtA[i:], m.Receiver) + i = encodeVarintTx(dAtA, i, uint64(len(m.Receiver))) + i-- + dAtA[i] = 0x2a + } + if len(m.Sender) > 0 { + i -= len(m.Sender) + copy(dAtA[i:], m.Sender) + i = encodeVarintTx(dAtA, i, uint64(len(m.Sender))) + i-- + dAtA[i] = 0x22 + } + { + size, err := m.Token.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.SourceChannel) > 0 { + i -= len(m.SourceChannel) + copy(dAtA[i:], m.SourceChannel) + i = encodeVarintTx(dAtA, i, uint64(len(m.SourceChannel))) + i-- + dAtA[i] = 0x12 + } + if len(m.SourcePort) > 0 { + i -= len(m.SourcePort) + copy(dAtA[i:], m.SourcePort) + i = encodeVarintTx(dAtA, i, uint64(len(m.SourcePort))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgTransferResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgTransferResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgTransferResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Sequence != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *MsgUpdateParams) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParams) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgUpdateParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgTransfer) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.SourcePort) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.SourceChannel) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Token.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.Sender) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Receiver) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.TimeoutHeight.Size() + n += 1 + l + sovTx(uint64(l)) + if m.TimeoutTimestamp != 0 { + n += 1 + sovTx(uint64(m.TimeoutTimestamp)) + } + l = len(m.Memo) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Encoding) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgTransferResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Sequence != 0 { + n += 1 + sovTx(uint64(m.Sequence)) + } + return n +} + +func (m *MsgUpdateParams) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Params.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgUpdateParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgTransfer) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgTransfer: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgTransfer: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SourcePort", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SourcePort = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SourceChannel", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SourceChannel = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Token.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Sender = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Receiver", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Receiver = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeoutHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.TimeoutHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeoutTimestamp", wireType) + } + m.TimeoutTimestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TimeoutTimestamp |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Memo", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Memo = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Encoding", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Encoding = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgTransferResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgTransferResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgTransferResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateParams: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParams: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTx(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/apps/transfer/types/types_test.go b/modules/apps/transfer/types/types_test.go new file mode 100644 index 0000000..3eac07a --- /dev/null +++ b/modules/apps/transfer/types/types_test.go @@ -0,0 +1,29 @@ +package types_test + +import ( + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +type TypesTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain +} + +func (suite *TypesTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) +} + +func TestTypesTestSuite(t *testing.T) { + testifysuite.Run(t, new(TypesTestSuite)) +} diff --git a/modules/apps/transfer/v2/ibc_module.go b/modules/apps/transfer/v2/ibc_module.go new file mode 100644 index 0000000..9aec2e9 --- /dev/null +++ b/modules/apps/transfer/v2/ibc_module.go @@ -0,0 +1,200 @@ +package v2 + +import ( + "bytes" + "fmt" + "strings" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/internal/events" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/internal/telemetry" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/keeper" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + "github.com/cosmos/ibc-go/v10/modules/core/api" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +var _ api.IBCModule = (*IBCModule)(nil) + +// NewIBCModule creates a new IBCModule given the keeper +func NewIBCModule(k keeper.Keeper) *IBCModule { + return &IBCModule{ + keeper: k, + } +} + +type IBCModule struct { + keeper keeper.Keeper +} + +func (im *IBCModule) OnSendPacket(ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, payload channeltypesv2.Payload, signer sdk.AccAddress) error { + // Enforce that the source and destination portIDs are the same and equal to the transfer portID + // Enforce that the source and destination clientIDs are also in the clientID format that transfer expects: {clientid}-{sequence} + // This is necessary for IBC v2 since the portIDs (and thus the application-application connection) is not prenegotiated + // by the channel handshake + // This restriction can be removed in a future where the trace hop on receive commits to **both** the source and destination portIDs + // rather than just the destination port + if payload.SourcePort != types.PortID || payload.DestinationPort != types.PortID { + return errorsmod.Wrapf(channeltypesv2.ErrInvalidPacket, "payload port ID is invalid: expected %s, got sourcePort: %s destPort: %s", types.PortID, payload.SourcePort, payload.DestinationPort) + } + if !clienttypes.IsValidClientID(sourceChannel) || !clienttypes.IsValidClientID(destinationChannel) { + return errorsmod.Wrapf(channeltypesv2.ErrInvalidPacket, "client IDs must be in valid format: {string}-{number}") + } + + data, err := types.UnmarshalPacketData(payload.Value, payload.Version, payload.Encoding) + if err != nil { + return err + } + + addressCodec := im.keeper.GetAddressCodec() + sender, err := addressCodec.StringToBytes(data.Sender) + if err != nil { + return err + } + + if !bytes.Equal(sender, signer) { + return errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "sender %s is different from signer %s", sender, signer) + } + + // Enforce that the base denom does not contain any slashes + // Since IBC v2 packets will no longer have channel identifiers, we cannot rely + // on the channel format to easily divide the trace from the base denomination in ICS20 v1 packets + // The simplest way to prevent any potential issues from arising is to simply disallow any slashes in the base denomination + // This prevents such denominations from being sent with IBCV v2 packets, however we can still support them in IBC v1 packets + // If we enforce that IBC v2 packets are sent with ICS20 v2 and above versions that separate the trace from the base denomination + // in the packet data, then we can remove this restriction. + if strings.Contains(data.Token.Denom.Base, "/") { + return errorsmod.Wrapf(types.ErrInvalidDenomForTransfer, "base denomination %s cannot contain slashes for IBC v2 packet", data.Token.Denom.Base) + } + + if err := im.keeper.SendTransfer(ctx, payload.SourcePort, sourceChannel, data.Token, signer); err != nil { + return err + } + + events.EmitTransferEvent(ctx, data.Sender, data.Receiver, data.Token, data.Memo) + + telemetry.ReportTransfer(payload.SourcePort, sourceChannel, payload.DestinationPort, destinationChannel, data.Token) + + return nil +} + +func (im *IBCModule) OnRecvPacket(ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, payload channeltypesv2.Payload, relayer sdk.AccAddress) channeltypesv2.RecvPacketResult { + // Enforce that the source and destination portIDs are the same and equal to the transfer portID + // Enforce that the source and destination clientIDs are also in the clientID format that transfer expects: {clientid}-{sequence} + // This is necessary for IBC v2 since the portIDs (and thus the application-application connection) is not prenegotiated + // by the channel handshake + // This restriction can be removed in a future where the trace hop on receive commits to **both** the source and destination portIDs + // rather than just the destination port + if payload.SourcePort != types.PortID || payload.DestinationPort != types.PortID { + return channeltypesv2.RecvPacketResult{ + Status: channeltypesv2.PacketStatus_Failure, + } + } + if !clienttypes.IsValidClientID(sourceChannel) || !clienttypes.IsValidClientID(destinationChannel) { + return channeltypesv2.RecvPacketResult{ + Status: channeltypesv2.PacketStatus_Failure, + } + } + + var ( + ackErr error + data types.InternalTransferRepresentation + ) + + ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)}) + recvResult := channeltypesv2.RecvPacketResult{ + Status: channeltypesv2.PacketStatus_Success, + Acknowledgement: ack.Acknowledgement(), + } + // we are explicitly wrapping this emit event call in an anonymous function so that + // the packet data is evaluated after it has been assigned a value. + defer func() { + events.EmitOnRecvPacketEvent(ctx, data, ack, ackErr) + }() + + data, ackErr = types.UnmarshalPacketData(payload.Value, payload.Version, payload.Encoding) + if ackErr != nil { + im.keeper.Logger(ctx).Error(fmt.Sprintf("%s sequence %d", ackErr.Error(), sequence)) + return channeltypesv2.RecvPacketResult{ + Status: channeltypesv2.PacketStatus_Failure, + } + } + + if ackErr = im.keeper.OnRecvPacket( + ctx, + data, + payload.SourcePort, + sourceChannel, + payload.DestinationPort, + destinationChannel, + ); ackErr != nil { + im.keeper.Logger(ctx).Error(fmt.Sprintf("%s sequence %d", ackErr.Error(), sequence)) + return channeltypesv2.RecvPacketResult{ + Status: channeltypesv2.PacketStatus_Failure, + } + } + + im.keeper.Logger(ctx).Info("successfully handled ICS-20 packet", "sequence", sequence) + + telemetry.ReportOnRecvPacket(payload.SourcePort, sourceChannel, payload.DestinationPort, destinationChannel, data.Token) + + // NOTE: acknowledgement will be written synchronously during IBC handler execution. + return recvResult +} + +func (im *IBCModule) OnTimeoutPacket(ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, payload channeltypesv2.Payload, relayer sdk.AccAddress) error { + data, err := types.UnmarshalPacketData(payload.Value, payload.Version, payload.Encoding) + if err != nil { + return err + } + + // refund tokens + if err := im.keeper.OnTimeoutPacket(ctx, payload.SourcePort, sourceChannel, data); err != nil { + return err + } + + events.EmitOnTimeoutEvent(ctx, data) + + return nil +} + +func (im *IBCModule) OnAcknowledgementPacket(ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, acknowledgement []byte, payload channeltypesv2.Payload, relayer sdk.AccAddress) error { + var ack channeltypes.Acknowledgement + // construct an error acknowledgement if the acknowledgement bytes are the sentinel error acknowledgement so we can use the shared transfer logic + if bytes.Equal(acknowledgement, channeltypesv2.ErrorAcknowledgement[:]) { + // the specific error does not matter + ack = channeltypes.NewErrorAcknowledgement(types.ErrReceiveFailed) + } else { + if err := types.ModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { + return errorsmod.Wrapf(ibcerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet acknowledgement: %v", err) + } + if !ack.Success() { + return errorsmod.Wrapf(ibcerrors.ErrInvalidRequest, "cannot pass in a custom error acknowledgement with IBC v2") + } + } + + data, err := types.UnmarshalPacketData(payload.Value, payload.Version, payload.Encoding) + if err != nil { + return err + } + + if err := im.keeper.OnAcknowledgementPacket(ctx, payload.SourcePort, sourceChannel, data, ack); err != nil { + return err + } + + events.EmitOnAcknowledgementPacketEvent(ctx, data, ack) + + return nil +} + +// UnmarshalPacketData unmarshals the ICS20 packet data based on the version and encoding +// it implements the PacketDataUnmarshaler interface +func (*IBCModule) UnmarshalPacketData(payload channeltypesv2.Payload) (any, error) { + return types.UnmarshalPacketData(payload.Value, payload.Version, payload.Encoding) +} diff --git a/modules/apps/transfer/v2/ibc_module_test.go b/modules/apps/transfer/v2/ibc_module_test.go new file mode 100644 index 0000000..6f74336 --- /dev/null +++ b/modules/apps/transfer/v2/ibc_module_test.go @@ -0,0 +1,421 @@ +package v2_test + +import ( + "crypto/sha256" + "fmt" + "testing" + "time" + + testifysuite "github.com/stretchr/testify/suite" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +const testclientid = "testclientid" + +type TransferTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains used for convenience and readability + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain + chainC *ibctesting.TestChain + + pathAToB *ibctesting.Path + pathBToC *ibctesting.Path +} + +const invalidPortID = "invalidportid" + +func (suite *TransferTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 3) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) + suite.chainC = suite.coordinator.GetChain(ibctesting.GetChainID(3)) + + // setup between chainA and chainB + // NOTE: + // pathAToB.EndpointA = endpoint on chainA + // pathAToB.EndpointB = endpoint on chainB + suite.pathAToB = ibctesting.NewPath(suite.chainA, suite.chainB) + + // setup between chainB and chainC + // pathBToC.EndpointA = endpoint on chainB + // pathBToC.EndpointB = endpoint on chainC + suite.pathBToC = ibctesting.NewPath(suite.chainB, suite.chainC) + + // setup IBC v2 paths between the chains + suite.pathAToB.SetupV2() + suite.pathBToC.SetupV2() +} + +func TestTransferTestSuite(t *testing.T) { + testifysuite.Run(t, new(TransferTestSuite)) +} + +func (suite *TransferTestSuite) TestOnSendPacket() { + var payload channeltypesv2.Payload + testCases := []struct { + name string + sourceDenomToTransfer string + malleate func() + expError error + }{ + { + "transfer single denom", + sdk.DefaultBondDenom, + func() {}, + nil, + }, + { + "transfer with invalid source port", + sdk.DefaultBondDenom, + func() { + payload.SourcePort = invalidPortID + }, + channeltypesv2.ErrInvalidPacket, + }, + { + "transfer with invalid destination port", + sdk.DefaultBondDenom, + func() { + payload.DestinationPort = invalidPortID + }, + channeltypesv2.ErrInvalidPacket, + }, + { + "transfer with invalid source client", + sdk.DefaultBondDenom, + func() { + suite.pathAToB.EndpointA.ClientID = testclientid + }, + channeltypesv2.ErrInvalidPacket, + }, + { + "transfer with invalid destination client", + sdk.DefaultBondDenom, + func() { + suite.pathAToB.EndpointB.ClientID = testclientid + }, + channeltypesv2.ErrInvalidPacket, + }, + { + "transfer with slashes in base denom", + "base/coin", + func() {}, + types.ErrInvalidDenomForTransfer, + }, + { + "transfer with slashes in ibc denom", + fmt.Sprintf("ibc/%x", sha256.Sum256([]byte("coin"))), + func() {}, + types.ErrInvalidDenomForTransfer, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + originalBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), tc.sourceDenomToTransfer) + + amount, ok := sdkmath.NewIntFromString("9223372036854775808") // 2^63 (one above int64) + suite.Require().True(ok) + originalCoin := sdk.NewCoin(tc.sourceDenomToTransfer, amount) + + token := types.Token{ + Denom: types.Denom{Base: originalCoin.Denom}, + Amount: originalCoin.Amount.String(), + } + + transferData := types.NewFungibleTokenPacketData(token.Denom.Path(), token.Amount, suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), "") + bz := suite.chainA.Codec.MustMarshal(&transferData) + payload = channeltypesv2.NewPayload(types.PortID, types.PortID, types.V1, types.EncodingProtobuf, bz) + + // malleate payload + tc.malleate() + + ctx := suite.chainA.GetContext() + cbs := suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.Router.Route(ibctesting.TransferPort) + + err := cbs.OnSendPacket(ctx, suite.pathAToB.EndpointA.ClientID, suite.pathAToB.EndpointB.ClientID, 1, payload, suite.chainA.SenderAccount.GetAddress()) + + if tc.expError != nil { + suite.Require().Contains(err.Error(), tc.expError.Error()) + return + } + + suite.Require().NoError(err) + + escrowAddress := types.GetEscrowAddress(types.PortID, suite.pathAToB.EndpointA.ClientID) + // check that the balance for chainA is updated + chainABalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), originalCoin.Denom) + suite.Require().Equal(originalBalance.Amount.Sub(amount).Int64(), chainABalance.Amount.Int64()) + + // check that module account escrow address has locked the tokens + chainAEscrowBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), escrowAddress, originalCoin.Denom) + suite.Require().Equal(originalCoin, chainAEscrowBalance) + }) + } +} + +func (suite *TransferTestSuite) TestOnRecvPacket() { + var payload channeltypesv2.Payload + testCases := []struct { + name string + sourceDenomToTransfer string + malleate func() + expErr bool + }{ + { + "transfer single denom", + sdk.DefaultBondDenom, + func() {}, + false, + }, + { + "transfer with invalid source port", + sdk.DefaultBondDenom, + func() { + payload.SourcePort = invalidPortID + }, + true, + }, + { + "transfer with invalid dest port", + sdk.DefaultBondDenom, + func() { + payload.DestinationPort = invalidPortID + }, + true, + }, + { + "transfer with invalid source client", + sdk.DefaultBondDenom, + func() { + suite.pathAToB.EndpointA.ClientID = testclientid + }, + true, + }, + { + "transfer with invalid destination client", + sdk.DefaultBondDenom, + func() { + suite.pathAToB.EndpointB.ClientID = testclientid + }, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + originalBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), tc.sourceDenomToTransfer) + + timeoutTimestamp := uint64(suite.chainB.GetContext().BlockTime().Add(time.Hour).Unix()) + + amount, ok := sdkmath.NewIntFromString("9223372036854775808") // 2^63 (one above int64) + suite.Require().True(ok) + originalCoin := sdk.NewCoin(tc.sourceDenomToTransfer, amount) + + msg := types.NewMsgTransferWithEncoding(types.PortID, suite.pathAToB.EndpointA.ClientID, originalCoin, suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), clienttypes.Height{}, timeoutTimestamp, "", types.EncodingProtobuf) + resp, err := suite.chainA.SendMsgs(msg) + suite.Require().NoError(err) // message committed + + packets, err := ibctesting.ParseIBCV2Packets(channeltypes.EventTypeSendPacket, resp.Events) + suite.Require().NoError(err) + + suite.Require().Len(packets, 1) + suite.Require().Len(packets[0].Payloads, 1) + payload = packets[0].Payloads[0] + + ctx := suite.chainB.GetContext() + cbs := suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.Router.Route(ibctesting.TransferPort) + + // malleate payload after it has been sent but before OnRecvPacket callback is called + tc.malleate() + + recvResult := cbs.OnRecvPacket(ctx, suite.pathAToB.EndpointA.ClientID, suite.pathAToB.EndpointB.ClientID, packets[0].Sequence, payload, suite.chainB.SenderAccount.GetAddress()) + + if tc.expErr { + suite.Require().Equal(channeltypesv2.PacketStatus_Failure, recvResult.Status) + return + } + + suite.Require().Equal(channeltypesv2.PacketStatus_Success, recvResult.Status) + suite.Require().Equal(channeltypes.NewResultAcknowledgement([]byte{byte(1)}).Acknowledgement(), recvResult.Acknowledgement) + + escrowAddress := types.GetEscrowAddress(types.PortID, suite.pathAToB.EndpointA.ClientID) + // check that the balance for chainA is updated + chainABalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), originalCoin.Denom) + suite.Require().Equal(originalBalance.Amount.Sub(amount).Int64(), chainABalance.Amount.Int64()) + + // check that module account escrow address has locked the tokens + chainAEscrowBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), escrowAddress, originalCoin.Denom) + suite.Require().Equal(originalCoin, chainAEscrowBalance) + + traceAToB := types.NewHop(types.PortID, suite.pathAToB.EndpointB.ClientID) + + // check that voucher exists on chain B + chainBDenom := types.NewDenom(originalCoin.Denom, traceAToB) + chainBBalance := suite.chainB.GetSimApp().BankKeeper.GetBalance(suite.chainB.GetContext(), suite.chainB.SenderAccount.GetAddress(), chainBDenom.IBCDenom()) + coinSentFromAToB := sdk.NewCoin(chainBDenom.IBCDenom(), amount) + suite.Require().Equal(coinSentFromAToB, chainBBalance) + }) + } +} + +func (suite *TransferTestSuite) TestOnAckPacket() { + testCases := []struct { + name string + sourceDenomToTransfer string + }{ + { + "transfer single denom", + sdk.DefaultBondDenom, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + originalBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), tc.sourceDenomToTransfer) + + timeoutTimestamp := uint64(suite.chainB.GetContext().BlockTime().Add(time.Hour).Unix()) + + amount, ok := sdkmath.NewIntFromString("9223372036854775808") // 2^63 (one above int64) + suite.Require().True(ok) + originalCoin := sdk.NewCoin(tc.sourceDenomToTransfer, amount) + + msg := types.NewMsgTransferWithEncoding(types.PortID, suite.pathAToB.EndpointA.ClientID, originalCoin, suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), clienttypes.Height{}, timeoutTimestamp, "", types.EncodingProtobuf) + + resp, err := suite.chainA.SendMsgs(msg) + suite.Require().NoError(err) // message committed + packets, err := ibctesting.ParseIBCV2Packets(channeltypes.EventTypeSendPacket, resp.Events) + suite.Require().NoError(err) + + suite.Require().Len(packets, 1) + suite.Require().Len(packets[0].Payloads, 1) + payload := packets[0].Payloads[0] + + ctx := suite.chainA.GetContext() + cbs := suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.Router.Route(ibctesting.TransferPort) + + ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)}) + + err = cbs.OnAcknowledgementPacket( + ctx, suite.pathAToB.EndpointA.ClientID, suite.pathAToB.EndpointB.ClientID, + packets[0].Sequence, ack.Acknowledgement(), payload, suite.chainA.SenderAccount.GetAddress(), + ) + suite.Require().NoError(err) + + // on successful ack, the tokens sent in packets should still be in escrow + escrowAddress := types.GetEscrowAddress(types.PortID, suite.pathAToB.EndpointA.ClientID) + // check that the balance for chainA is updated + chainABalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), originalCoin.Denom) + suite.Require().Equal(originalBalance.Amount.Sub(amount).Int64(), chainABalance.Amount.Int64()) + + // check that module account escrow address has locked the tokens + chainAEscrowBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), escrowAddress, originalCoin.Denom) + suite.Require().Equal(originalCoin, chainAEscrowBalance) + + // create a custom error ack and replay the callback to ensure it fails with IBC v2 callbacks + errAck := channeltypes.NewErrorAcknowledgement(types.ErrInvalidAmount) + err = cbs.OnAcknowledgementPacket( + ctx, suite.pathAToB.EndpointA.ClientID, suite.pathAToB.EndpointB.ClientID, + 1, errAck.Acknowledgement(), payload, suite.chainA.SenderAccount.GetAddress(), + ) + suite.Require().Error(err) + + // create the sentinel error ack and replay the callback to ensure the tokens are correctly refunded + // we can replay the callback here because the replay protection is handled in the IBC handler + err = cbs.OnAcknowledgementPacket( + ctx, suite.pathAToB.EndpointA.ClientID, suite.pathAToB.EndpointB.ClientID, + 1, channeltypesv2.ErrorAcknowledgement[:], payload, suite.chainA.SenderAccount.GetAddress(), + ) + suite.Require().NoError(err) + + // on error ack, the tokens sent in packets should be returned to sender + // check that the balance for chainA is refunded + chainABalance = suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), originalCoin.Denom) + suite.Require().Equal(originalBalance.Amount, chainABalance.Amount) + + // check that module account escrow address has no tokens + chainAEscrowBalance = suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), escrowAddress, originalCoin.Denom) + suite.Require().Equal(sdk.NewCoin(originalCoin.Denom, sdkmath.ZeroInt()), chainAEscrowBalance) + }) + } +} + +func (suite *TransferTestSuite) TestOnTimeoutPacket() { + testCases := []struct { + name string + sourceDenomToTransfer string + }{ + { + "transfer single denom", + sdk.DefaultBondDenom, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + originalBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), tc.sourceDenomToTransfer) + + timeoutTimestamp := uint64(suite.chainB.GetContext().BlockTime().Add(time.Hour).Unix()) + + amount, ok := sdkmath.NewIntFromString("9223372036854775808") // 2^63 (one above int64) + suite.Require().True(ok) + originalCoin := sdk.NewCoin(tc.sourceDenomToTransfer, amount) + + msg := types.NewMsgTransferWithEncoding(types.PortID, suite.pathAToB.EndpointA.ClientID, originalCoin, suite.chainA.SenderAccount.GetAddress().String(), suite.chainB.SenderAccount.GetAddress().String(), clienttypes.Height{}, timeoutTimestamp, "", types.EncodingProtobuf) + resp, err := suite.chainA.SendMsgs(msg) + suite.Require().NoError(err) // message committed + packets, err := ibctesting.ParseIBCV2Packets(channeltypes.EventTypeSendPacket, resp.Events) + suite.Require().NoError(err) + + suite.Require().Len(packets, 1) + suite.Require().Len(packets[0].Payloads, 1) + payload := packets[0].Payloads[0] + + // on successful send, the tokens sent in packets should be in escrow + escrowAddress := types.GetEscrowAddress(types.PortID, suite.pathAToB.EndpointA.ClientID) + // check that the balance for chainA is updated + chainABalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), originalCoin.Denom) + suite.Require().Equal(originalBalance.Amount.Sub(amount).Int64(), chainABalance.Amount.Int64()) + + // check that module account escrow address has locked the tokens + chainAEscrowBalance := suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), escrowAddress, originalCoin.Denom) + suite.Require().Equal(originalCoin, chainAEscrowBalance) + + ctx := suite.chainA.GetContext() + cbs := suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.Router.Route(ibctesting.TransferPort) + + err = cbs.OnTimeoutPacket(ctx, suite.pathAToB.EndpointA.ClientID, suite.pathAToB.EndpointB.ClientID, packets[0].Sequence, payload, suite.chainA.SenderAccount.GetAddress()) + suite.Require().NoError(err) + + // on timeout, the tokens sent in packets should be returned to sender + // check that the balance for chainA is refunded + chainABalance = suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), suite.chainA.SenderAccount.GetAddress(), originalCoin.Denom) + suite.Require().Equal(originalBalance.Amount, chainABalance.Amount) + + // check that module account escrow address has no tokens + chainAEscrowBalance = suite.chainA.GetSimApp().BankKeeper.GetBalance(suite.chainA.GetContext(), escrowAddress, originalCoin.Denom) + suite.Require().Equal(sdk.NewCoin(originalCoin.Denom, sdkmath.ZeroInt()), chainAEscrowBalance) + }) + } +} diff --git a/modules/core/02-client/abci.go b/modules/core/02-client/abci.go new file mode 100644 index 0000000..1f64947 --- /dev/null +++ b/modules/core/02-client/abci.go @@ -0,0 +1,35 @@ +package client + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/keeper" + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" +) + +// BeginBlocker is used to perform IBC client upgrades +func BeginBlocker(ctx sdk.Context, k *keeper.Keeper) { + plan, err := k.GetUpgradePlan(ctx) + if err == nil { + // Once we are at the last block this chain will commit, set the upgraded consensus state + // so that IBC clients can use the last NextValidatorsHash as a trusted kernel for verifying + // headers on the next version of the chain. + // Set the time to the last block time of the current chain. + // In order for a client to upgrade successfully, the first block of the new chain must be committed + // within the trusting period of the last block time on this chain. + _, err := k.GetUpgradedClient(ctx, plan.Height) + if err == nil && ctx.BlockHeight() == plan.Height-1 { + upgradedConsState := &ibctm.ConsensusState{ + Timestamp: ctx.BlockTime(), + NextValidatorsHash: ctx.BlockHeader().NextValidatorsHash, + } + bz := types.MustMarshalConsensusState(k.Codec(), upgradedConsState) + + // SetUpgradedConsensusState always returns nil, hence the blank here. + _ = k.SetUpgradedConsensusState(ctx, plan.Height, bz) + + keeper.EmitUpgradeChainEvent(ctx, plan.Height) + } + } +} diff --git a/modules/core/02-client/abci_test.go b/modules/core/02-client/abci_test.go new file mode 100644 index 0000000..6f77058 --- /dev/null +++ b/modules/core/02-client/abci_test.go @@ -0,0 +1,133 @@ +package client_test + +import ( + "strings" + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + upgradetypes "cosmossdk.io/x/upgrade/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + + client "github.com/cosmos/ibc-go/v10/modules/core/02-client" + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +type ClientTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain +} + +func (suite *ClientTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) +} + +func TestClientTestSuite(t *testing.T) { + testifysuite.Run(t, new(ClientTestSuite)) +} + +func (suite *ClientTestSuite) TestBeginBlocker() { + for range 10 { + // increment height + suite.coordinator.CommitBlock(suite.chainA, suite.chainB) + + suite.Require().NotPanics(func() { + client.BeginBlocker(suite.chainA.GetContext(), suite.chainA.App.GetIBCKeeper().ClientKeeper) + }, "BeginBlocker shouldn't panic") + } +} + +func (suite *ClientTestSuite) TestBeginBlockerConsensusState() { + plan := &upgradetypes.Plan{ + Name: "test", + Height: suite.chainA.GetContext().BlockHeight() + 1, + } + // set upgrade plan in the upgrade store + store := suite.chainA.GetContext().KVStore(suite.chainA.GetSimApp().GetKey(upgradetypes.StoreKey)) + bz := suite.chainA.App.AppCodec().MustMarshal(plan) + store.Set(upgradetypes.PlanKey(), bz) + + nextValsHash := []byte("nextValsHash") + newCtx := suite.chainA.GetContext().WithBlockHeader(cmtproto.Header{ + ChainID: suite.chainA.ChainID, + Height: suite.chainA.GetContext().BlockHeight(), + NextValidatorsHash: nextValsHash, + }) + + err := suite.chainA.GetSimApp().UpgradeKeeper.SetUpgradedClient(newCtx, plan.Height, []byte("client state")) + suite.Require().NoError(err) + + client.BeginBlocker(newCtx, suite.chainA.App.GetIBCKeeper().ClientKeeper) + + // plan Height is at ctx.BlockHeight+1 + consState, err := suite.chainA.GetSimApp().UpgradeKeeper.GetUpgradedConsensusState(newCtx, plan.Height) + suite.Require().NoError(err) + + bz, err = types.MarshalConsensusState(suite.chainA.App.AppCodec(), &ibctm.ConsensusState{Timestamp: newCtx.BlockTime(), NextValidatorsHash: nextValsHash}) + suite.Require().NoError(err) + suite.Require().Equal(bz, consState) +} + +func (suite *ClientTestSuite) TestBeginBlockerUpgradeEvents() { + plan := &upgradetypes.Plan{ + Name: "test", + Height: suite.chainA.GetContext().BlockHeight() + 1, + } + // set upgrade plan in the upgrade store + store := suite.chainA.GetContext().KVStore(suite.chainA.GetSimApp().GetKey(upgradetypes.StoreKey)) + bz := suite.chainA.App.AppCodec().MustMarshal(plan) + store.Set(upgradetypes.PlanKey(), bz) + + nextValsHash := []byte("nextValsHash") + newCtx := suite.chainA.GetContext().WithBlockHeader(cmtproto.Header{ + Height: suite.chainA.GetContext().BlockHeight(), + NextValidatorsHash: nextValsHash, + }) + + err := suite.chainA.GetSimApp().UpgradeKeeper.SetUpgradedClient(newCtx, plan.Height, []byte("client state")) + suite.Require().NoError(err) + + cacheCtx, writeCache := suite.chainA.GetContext().CacheContext() + + client.BeginBlocker(cacheCtx, suite.chainA.App.GetIBCKeeper().ClientKeeper) + writeCache() + + suite.requireContainsEvent(cacheCtx.EventManager().Events(), types.EventTypeUpgradeChain, true) +} + +func (suite *ClientTestSuite) TestBeginBlockerUpgradeEventsAbsence() { + cacheCtx, writeCache := suite.chainA.GetContext().CacheContext() + client.BeginBlocker(suite.chainA.GetContext(), suite.chainA.App.GetIBCKeeper().ClientKeeper) + writeCache() + suite.requireContainsEvent(cacheCtx.EventManager().Events(), types.EventTypeUpgradeChain, false) +} + +// requireContainsEvent verifies if an event of a specific type was emitted. +func (suite *ClientTestSuite) requireContainsEvent(events sdk.Events, eventType string, shouldContain bool) { + found := false + var eventTypes []string + for _, e := range events { + eventTypes = append(eventTypes, e.Type) + if e.Type == eventType { + found = true + break + } + } + if shouldContain { + suite.Require().True(found, "event type %s was not found in %s", eventType, strings.Join(eventTypes, ",")) + } else { + suite.Require().False(found, "event type %s was found in %s", eventType, strings.Join(eventTypes, ",")) + } +} diff --git a/modules/core/02-client/client/cli/cli.go b/modules/core/02-client/client/cli/cli.go new file mode 100644 index 0000000..66d52d4 --- /dev/null +++ b/modules/core/02-client/client/cli/cli.go @@ -0,0 +1,63 @@ +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" +) + +// GetQueryCmd returns the query commands for IBC clients +func GetQueryCmd() *cobra.Command { + queryCmd := &cobra.Command{ + Use: types.SubModuleName, + Short: "IBC client query subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + queryCmd.AddCommand( + GetCmdQueryClientStates(), + GetCmdQueryCounterpartyInfo(), + GetCmdQueryClientState(), + GetCmdQueryClientStatus(), + GetCmdQueryConsensusStates(), + GetCmdQueryConsensusStateHeights(), + GetCmdQueryConsensusState(), + GetCmdQueryHeader(), + GetCmdSelfConsensusState(), + GetCmdClientParams(), + GetCmdClientParams(), + GetCmdQueryClientCreator(), + GetCmdQueryClientConfig(), + ) + + return queryCmd +} + +// NewTxCmd returns the command to create and handle IBC clients +func NewTxCmd() *cobra.Command { + txCmd := &cobra.Command{ + Use: types.SubModuleName, + Short: "IBC client transaction subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + txCmd.AddCommand( + newCreateClientCmd(), + newAddCounterpartyCmd(), + newUpdateClientCmd(), + newSubmitMisbehaviourCmd(), // Deprecated + newUpgradeClientCmd(), + newSubmitRecoverClientProposalCmd(), + newScheduleIBCUpgradeProposalCmd(), + newUpdateClientConfigCmd(), + newDeleteClientCreatorCmd(), + ) + + return txCmd +} diff --git a/modules/core/02-client/client/cli/query.go b/modules/core/02-client/client/cli/query.go new file mode 100644 index 0000000..52e6c93 --- /dev/null +++ b/modules/core/02-client/client/cli/query.go @@ -0,0 +1,438 @@ +package cli + +import ( + "errors" + "fmt" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/version" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/client/utils" + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + clienttypesv2 "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +const ( + flagLatestHeight = "latest-height" +) + +// GetCmdQueryClientStates defines the command to query all the light clients +// that this chain maintains. +func GetCmdQueryClientStates() *cobra.Command { + cmd := &cobra.Command{ + Use: "states", + Short: "Query all available light clients", + Long: "Query all available light clients", + Example: fmt.Sprintf("%s query %s %s states", version.AppName, ibcexported.ModuleName, types.SubModuleName), + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + req := &types.QueryClientStatesRequest{ + Pagination: pageReq, + } + + res, err := queryClient.ClientStates(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "client states") + + return cmd +} + +// GetCmdQueryCounterpartyInfo defines the command to query the counterparty chain +func GetCmdQueryCounterpartyInfo() *cobra.Command { + cmd := &cobra.Command{ + Use: "counterparty-info [client-id]", + Short: "Query a client's counterparty info", + Long: "Query a client's counterparty info", + Example: fmt.Sprintf("%s query %s %s counterparty-info [client-id]", version.AppName, ibcexported.ModuleName, types.SubModuleName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + clientID := args[0] + + queryClient := clienttypesv2.NewQueryClient(clientCtx) + req := &clienttypesv2.QueryCounterpartyInfoRequest{ + ClientId: clientID, + } + counterpartyRes, err := queryClient.CounterpartyInfo(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(counterpartyRes) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdQueryClientCreator defines the command to query the creator of a client +func GetCmdQueryClientCreator() *cobra.Command { + cmd := &cobra.Command{ + Use: "creator [client-id]", + Short: "Query a client's creator", + Long: "Query a client's creator", + Example: fmt.Sprintf("%s query %s %s creator 08-wasm-0", version.AppName, ibcexported.ModuleName, types.SubModuleName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + clientID := args[0] + + queryClient := types.NewQueryClient(clientCtx) + req := &types.QueryClientCreatorRequest{ + ClientId: clientID, + } + paramsResp, err := queryClient.ClientCreator(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(paramsResp) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdQueryClientConfig defines the command to query a client's configuration +func GetCmdQueryClientConfig() *cobra.Command { + cmd := &cobra.Command{ + Use: "config [client-id]", + Short: "Query a client's config", + Long: "Query a client's config", + Example: fmt.Sprintf("%s query %s %s params 08-wasm-0", version.AppName, ibcexported.ModuleName, types.SubModuleName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + clientID := args[0] + + queryClient := clienttypesv2.NewQueryClient(clientCtx) + req := &clienttypesv2.QueryConfigRequest{ + ClientId: clientID, + } + paramsResp, err := queryClient.Config(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(paramsResp) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdQueryClientState defines the command to query the state of a client with +// a given id as defined in https://github.com/cosmos/ibc/tree/master/spec/core/ics-002-client-semantics#query +func GetCmdQueryClientState() *cobra.Command { + cmd := &cobra.Command{ + Use: "state [client-id]", + Short: "Query a client state", + Long: "Query stored client state", + Example: fmt.Sprintf("%s query %s %s state [client-id]", version.AppName, ibcexported.ModuleName, types.SubModuleName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + clientID := args[0] + prove, _ := cmd.Flags().GetBool(flags.FlagProve) + + clientStateRes, err := utils.QueryClientState(clientCtx, clientID, prove) + if err != nil { + return err + } + + return clientCtx.PrintProto(clientStateRes) + }, + } + + cmd.Flags().Bool(flags.FlagProve, true, "show proofs for the query results") + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdQueryClientStatus defines the command to query the status of a client with a given id +func GetCmdQueryClientStatus() *cobra.Command { + cmd := &cobra.Command{ + Use: "status [client-id]", + Short: "Query client status", + Long: "Query client activity status. Any client without an 'Active' status is considered inactive", + Example: fmt.Sprintf("%s query %s %s status [client-id]", version.AppName, ibcexported.ModuleName, types.SubModuleName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + clientID := args[0] + queryClient := types.NewQueryClient(clientCtx) + + req := &types.QueryClientStatusRequest{ + ClientId: clientID, + } + + clientStatusRes, err := queryClient.ClientStatus(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(clientStatusRes) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdQueryConsensusStates defines the command to query all the consensus states from a given +// client state. +func GetCmdQueryConsensusStates() *cobra.Command { + cmd := &cobra.Command{ + Use: "consensus-states [client-id]", + Short: "Query all the consensus states of a client.", + Long: "Query all the consensus states from a given client state.", + Example: fmt.Sprintf("%s query %s %s consensus-states [client-id]", version.AppName, ibcexported.ModuleName, types.SubModuleName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + clientID := args[0] + + queryClient := types.NewQueryClient(clientCtx) + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + req := &types.QueryConsensusStatesRequest{ + ClientId: clientID, + Pagination: pageReq, + } + + res, err := queryClient.ConsensusStates(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "consensus states") + + return cmd +} + +// GetCmdQueryConsensusStateHeights defines the command to query the heights of all client consensus states associated with the +// provided client ID. +func GetCmdQueryConsensusStateHeights() *cobra.Command { + cmd := &cobra.Command{ + Use: "consensus-state-heights [client-id]", + Short: "Query the heights of all consensus states of a client.", + Long: "Query the heights of all consensus states associated with the provided client ID.", + Example: fmt.Sprintf("%s query %s %s consensus-state-heights [client-id]", version.AppName, ibcexported.ModuleName, types.SubModuleName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + clientID := args[0] + + queryClient := types.NewQueryClient(clientCtx) + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + req := &types.QueryConsensusStateHeightsRequest{ + ClientId: clientID, + Pagination: pageReq, + } + + res, err := queryClient.ConsensusStateHeights(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "consensus state heights") + + return cmd +} + +// GetCmdQueryConsensusState defines the command to query the consensus state of +// the chain as defined in https://github.com/cosmos/ibc/tree/master/spec/core/ics-002-client-semantics#query +func GetCmdQueryConsensusState() *cobra.Command { + cmd := &cobra.Command{ + Use: "consensus-state [client-id] [height]", + Short: "Query the consensus state of a client at a given height", + Long: `Query the consensus state for a particular light client at a given height. +If the '--latest' flag is included, the query returns the latest consensus state, overriding the height argument.`, + Example: fmt.Sprintf("%s query %s %s consensus-state [client-id] [height]", version.AppName, ibcexported.ModuleName, types.SubModuleName), + Args: cobra.RangeArgs(1, 2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + clientID := args[0] + queryLatestHeight, _ := cmd.Flags().GetBool(flagLatestHeight) + var height types.Height + + if !queryLatestHeight { + if len(args) != 2 { + return errors.New("must include a second 'height' argument when '--latest-height' flag is not provided") + } + + height, err = types.ParseHeight(args[1]) + if err != nil { + return err + } + } + + prove, _ := cmd.Flags().GetBool(flags.FlagProve) + + csRes, err := utils.QueryConsensusState(clientCtx, clientID, height, prove, queryLatestHeight) + if err != nil { + return err + } + + return clientCtx.PrintProto(csRes) + }, + } + + cmd.Flags().Bool(flags.FlagProve, true, "show proofs for the query results") + cmd.Flags().Bool(flagLatestHeight, false, "return latest stored consensus state") + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdQueryHeader defines the command to query the latest header on the chain +func GetCmdQueryHeader() *cobra.Command { + cmd := &cobra.Command{ + Use: "header", + Short: "Query the latest header of the running chain", + Long: "Query the latest Tendermint header of the running chain", + Example: fmt.Sprintf("%s query %s %s header", version.AppName, ibcexported.ModuleName, types.SubModuleName), + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + header, _, err := utils.QueryTendermintHeader(clientCtx) + if err != nil { + return err + } + + return clientCtx.PrintProto(&header) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdSelfConsensusState defines the command to query the self consensus state of a chain +func GetCmdSelfConsensusState() *cobra.Command { + cmd := &cobra.Command{ + Use: "self-consensus-state", + Short: "Query the self consensus state for this chain", + Long: "Query the self consensus state for this chain. This result may be used for verifying IBC clients representing this chain which are hosted on counterparty chains.", + Example: fmt.Sprintf("%s query %s %s self-consensus-state", version.AppName, ibcexported.ModuleName, types.SubModuleName), + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + state, _, err := utils.QuerySelfConsensusState(clientCtx) + if err != nil { + return err + } + + return clientCtx.PrintProto(state) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdClientParams returns the command handler for ibc client parameter querying. +func GetCmdClientParams() *cobra.Command { + cmd := &cobra.Command{ + Use: "params", + Short: "Query the current ibc client parameters", + Long: "Query the current ibc client parameters", + Args: cobra.NoArgs, + Example: fmt.Sprintf("%s query %s %s params", version.AppName, ibcexported.ModuleName, types.SubModuleName), + RunE: func(cmd *cobra.Command, _ []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + res, _ := queryClient.ClientParams(cmd.Context(), &types.QueryClientParamsRequest{}) + return clientCtx.PrintProto(res.Params) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/modules/core/02-client/client/cli/tx.go b/modules/core/02-client/client/cli/tx.go new file mode 100644 index 0000000..784d061 --- /dev/null +++ b/modules/core/02-client/client/cli/tx.go @@ -0,0 +1,546 @@ +package cli + +import ( + "encoding/base64" + "fmt" + "os" + "strconv" + + "github.com/spf13/cobra" + + upgradetypes "cosmossdk.io/x/upgrade/types" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + "github.com/cosmos/cosmos-sdk/version" + govcli "github.com/cosmos/cosmos-sdk/x/gov/client/cli" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + clienttypesv2 "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +const FlagAuthority = "authority" + +// newCreateClientCmd defines the command to create a new IBC light client. +func newCreateClientCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "create [path/to/client_state.json] [path/to/consensus_state.json]", + Short: "create new IBC client", + Long: `create a new IBC client with the specified client state and consensus state + - ClientState JSON example: {"@type":"/ibc.lightclients.solomachine.v1.ClientState","sequence":"1","frozen_sequence":"0","consensus_state":{"public_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AtK50+5pJOoaa04qqAqrnyAqsYrwrR/INnA6UPIaYZlp"},"diversifier":"testing","timestamp":"10"},"allow_update_after_proposal":false} + - ConsensusState JSON example: {"@type":"/ibc.lightclients.solomachine.v1.ConsensusState","public_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AtK50+5pJOoaa04qqAqrnyAqsYrwrR/INnA6UPIaYZlp"},"diversifier":"testing","timestamp":"10"}`, + Example: fmt.Sprintf("%s tx ibc %s create [path/to/client_state.json] [path/to/consensus_state.json] --from node0 --home ../node0/cli --chain-id $CID", version.AppName, types.SubModuleName), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry) + + // attempt to unmarshal client state argument + var clientState exported.ClientState + clientContentOrFileName := args[0] + if err := cdc.UnmarshalInterfaceJSON([]byte(clientContentOrFileName), &clientState); err != nil { + + // check for file path if JSON input is not provided + contents, err := os.ReadFile(clientContentOrFileName) + if err != nil { + return fmt.Errorf("neither JSON input nor path to .json file for client state were provided: %w", err) + } + + if err := cdc.UnmarshalInterfaceJSON(contents, &clientState); err != nil { + return fmt.Errorf("error unmarshalling client state file: %w", err) + } + } + + // attempt to unmarshal consensus state argument + var consensusState exported.ConsensusState + consensusContentOrFileName := args[1] + if err := cdc.UnmarshalInterfaceJSON([]byte(consensusContentOrFileName), &consensusState); err != nil { + + // check for file path if JSON input is not provided + contents, err := os.ReadFile(consensusContentOrFileName) + if err != nil { + return fmt.Errorf("neither JSON input nor path to .json file for consensus state were provided: %w", err) + } + + if err := cdc.UnmarshalInterfaceJSON(contents, &consensusState); err != nil { + return fmt.Errorf("error unmarshalling consensus state file: %w", err) + } + } + + msg, err := types.NewMsgCreateClient(clientState, consensusState, clientCtx.GetFromAddress().String()) + if err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} + +func newAddCounterpartyCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "add-counterparty [client-id] [counterparty-client-id] [merkle-prefix...]", + Short: "add counterparty to client", + Example: fmt.Sprintf("%s tx ibc %s add-counterparty 07-tendermint-0 client-0 \"aWJj\" \"\"", version.AppName, types.SubModuleName), + Args: cobra.MinimumNArgs(3), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + clientID := args[0] + counterpartyClientID := args[1] + + var merklePrefix [][]byte + for _, base64EncodedPathPart := range args[2:] { + pathPart, err := base64.StdEncoding.DecodeString(base64EncodedPathPart) + if err != nil { + return err + } + merklePrefix = append(merklePrefix, pathPart) + } + + msg := clienttypesv2.NewMsgRegisterCounterparty(clientID, merklePrefix, counterpartyClientID, clientCtx.GetFromAddress().String()) + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} + +// newDeleteClientCreatorCmd defines the command to delete the client creator for a given client. +func newDeleteClientCreatorCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "delete-client-creator [client-id]", + Short: "delete the client creator", + Example: fmt.Sprintf("%s tx ibc %s delete-client-creator 07-tendermint-0", version.AppName, types.SubModuleName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + clientID := args[0] + + msg := types.NewMsgDeleteClientCreator(clientID, clientCtx.GetFromAddress().String()) + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} + +// newUpdateClientConfigCmd defines the command to update the client config (allowed relayers) for a given client. +func newUpdateClientConfigCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "update-client-config client-id [allowed-relayer-addresses...]", + Short: "update allowed relayers for a client (replaces existing list, and no addresses means empty list and permissionless relaying)", + Example: fmt.Sprintf("%s tx ibc %s update-client-params 08-wasm-0 cosmos123... cosmos456...", version.AppName, types.SubModuleName), + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + clientID := args[0] + + // NOTE: Make sure all fields are gathered from the user for the config objects, as it replaces the entire existing config. + // In other words, if we add a new field to the config object, we need to make sure it is gathered here + var allowedRelayers []string + for _, relayerAddress := range args[1:] { + _ = sdk.MustAccAddressFromBech32(relayerAddress) + allowedRelayers = append(allowedRelayers, relayerAddress) + } + + msg := clienttypesv2.NewMsgUpdateClientConfig(clientID, clientCtx.GetFromAddress().String(), clienttypesv2.NewConfig(allowedRelayers...)) + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} + +// newUpdateClientCmd defines the command to update an IBC client. +func newUpdateClientCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "update [client-id] [path/to/client_msg.json]", + Short: "update existing client with a client message", + Long: "update existing client with a client message, for example a header, misbehaviour or batch update", + Example: fmt.Sprintf("%s tx ibc %s update [client-id] [path/to/client_msg.json] --from node0 --home ../node0/cli --chain-id $CID", version.AppName, types.SubModuleName), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + clientID := args[0] + + cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry) + + var clientMsg exported.ClientMessage + clientMsgContentOrFileName := args[1] + if err := cdc.UnmarshalInterfaceJSON([]byte(clientMsgContentOrFileName), &clientMsg); err != nil { + + // check for file path if JSON input is not provided + contents, err := os.ReadFile(clientMsgContentOrFileName) + if err != nil { + return fmt.Errorf("neither JSON input nor path to .json file for header were provided: %w", err) + } + + if err := cdc.UnmarshalInterfaceJSON(contents, &clientMsg); err != nil { + return fmt.Errorf("error unmarshalling header file: %w", err) + } + } + + msg, err := types.NewMsgUpdateClient(clientID, clientMsg, clientCtx.GetFromAddress().String()) + if err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} + +// newSubmitMisbehaviourCmd defines the command to submit a misbehaviour to prevent +// future updates. +// Deprecated: NewSubmitMisbehaviourCmd is deprecated and will be removed in a future release. +// Please use NewUpdateClientCmd instead. +func newSubmitMisbehaviourCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "misbehaviour [clientID] [path/to/misbehaviour.json]", + Short: "submit a client misbehaviour", + Long: "submit a client misbehaviour to prevent future updates", + Example: fmt.Sprintf("%s tx ibc %s misbehaviour [clientID] [path/to/misbehaviour.json] --from node0 --home ../node0/cli --chain-id $CID", version.AppName, types.SubModuleName), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry) + + var misbehaviour exported.ClientMessage + clientID := args[0] + misbehaviourContentOrFileName := args[1] + if err := cdc.UnmarshalInterfaceJSON([]byte(misbehaviourContentOrFileName), &misbehaviour); err != nil { + + // check for file path if JSON input is not provided + contents, err := os.ReadFile(misbehaviourContentOrFileName) + if err != nil { + return fmt.Errorf("neither JSON input nor path to .json file for misbehaviour were provided: %w", err) + } + + if err := cdc.UnmarshalInterfaceJSON(contents, &misbehaviour); err != nil { + return fmt.Errorf("error unmarshalling misbehaviour file: %w", err) + } + } + + msg, err := types.NewMsgSubmitMisbehaviour(clientID, misbehaviour, clientCtx.GetFromAddress().String()) + if err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} + +// newUpgradeClientCmd defines the command to upgrade an IBC light client. +func newUpgradeClientCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "upgrade [client-identifier] [path/to/client_state.json] [path/to/consensus_state.json] [upgrade-client-proof] [upgrade-consensus-state-proof]", + Short: "upgrade an IBC client", + Long: `upgrade the IBC client associated with the provided client identifier while providing proof committed by the counterparty chain to the new client and consensus states + - ClientState JSON example: {"@type":"/ibc.lightclients.solomachine.v1.ClientState","sequence":"1","frozen_sequence":"0","consensus_state":{"public_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AtK50+5pJOoaa04qqAqrnyAqsYrwrR/INnA6UPIaYZlp"},"diversifier":"testing","timestamp":"10"},"allow_update_after_proposal":false} + - ConsensusState JSON example: {"@type":"/ibc.lightclients.solomachine.v1.ConsensusState","public_key":{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AtK50+5pJOoaa04qqAqrnyAqsYrwrR/INnA6UPIaYZlp"},"diversifier":"testing","timestamp":"10"}`, + Example: fmt.Sprintf("%s tx ibc %s upgrade [client-identifier] [path/to/client_state.json] [path/to/consensus_state.json] [client-state-proof] [consensus-state-proof] --from node0 --home ../node0/cli --chain-id $CID", version.AppName, types.SubModuleName), + Args: cobra.ExactArgs(5), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry) + clientID := args[0] + + // attempt to unmarshal client state argument + var clientState exported.ClientState + clientContentOrFileName := args[1] + if err := cdc.UnmarshalInterfaceJSON([]byte(clientContentOrFileName), &clientState); err != nil { + + // check for file path if JSON input is not provided + contents, err := os.ReadFile(clientContentOrFileName) + if err != nil { + return fmt.Errorf("neither JSON input nor path to .json file for client state were provided: %w", err) + } + + if err := cdc.UnmarshalInterfaceJSON(contents, &clientState); err != nil { + return fmt.Errorf("error unmarshalling client state file: %w", err) + } + } + + // attempt to unmarshal consensus state argument + var consensusState exported.ConsensusState + consensusContentOrFileName := args[2] + if err := cdc.UnmarshalInterfaceJSON([]byte(consensusContentOrFileName), &consensusState); err != nil { + + // check for file path if JSON input is not provided + contents, err := os.ReadFile(consensusContentOrFileName) + if err != nil { + return fmt.Errorf("neither JSON input nor path to .json file for consensus state were provided: %w", err) + } + + if err := cdc.UnmarshalInterfaceJSON(contents, &consensusState); err != nil { + return fmt.Errorf("error unmarshalling consensus state file: %w", err) + } + } + + upgradeClientProof := []byte(args[3]) + upgradeConsensusProof := []byte(args[4]) + + msg, err := types.NewMsgUpgradeClient(clientID, clientState, consensusState, upgradeClientProof, upgradeConsensusProof, clientCtx.GetFromAddress().String()) + if err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} + +// newSubmitRecoverClientProposalCmd defines the command to recover an IBC light client. +func newSubmitRecoverClientProposalCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "recover-client [subject-client-id] [substitute-client-id] [flags]", + Args: cobra.ExactArgs(2), + Short: "recover an IBC client", + Long: `Submit a recover IBC client proposal along with an initial deposit + Please specify a subject client identifier you want to recover + Please specify the substitute client the subject client will be recovered to.`, + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + proposal, err := govcli.ReadGovPropFlags(clientCtx, cmd.Flags()) + if err != nil { + return err + } + + subjectClientID, substituteClientID := args[0], args[1] + + authority, _ := cmd.Flags().GetString(FlagAuthority) + if authority != "" { + if _, err = sdk.AccAddressFromBech32(authority); err != nil { + return fmt.Errorf("invalid authority address: %w", err) + } + } else { + authority = sdk.AccAddress(address.Module(govtypes.ModuleName)).String() + } + + msg := types.NewMsgRecoverClient(authority, subjectClientID, substituteClientID) + + if err = msg.ValidateBasic(); err != nil { + return fmt.Errorf("error validating %T: %w", types.MsgRecoverClient{}, err) + } + + if err := proposal.SetMsgs([]sdk.Msg{msg}); err != nil { + return fmt.Errorf("failed to create recover client proposal message: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), proposal) + }, + } + + cmd.Flags().String(FlagAuthority, "", "The address of the client module authority (defaults to gov)") + + flags.AddTxFlagsToCmd(cmd) + govcli.AddGovPropFlagsToCmd(cmd) + err := cmd.MarkFlagRequired(govcli.FlagTitle) + if err != nil { + panic(err) + } + + return cmd +} + +// newScheduleIBCUpgradeProposalCmd defines the command for submitting an IBC software upgrade proposal. +func newScheduleIBCUpgradeProposalCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "schedule-ibc-upgrade [name] [height] [path/to/upgraded_client_state.json] [flags]", + Args: cobra.ExactArgs(3), + Short: "Submit an IBC software upgrade proposal", + Long: `Please specify a unique name and height for the upgrade to take effect. + The client state specified is the upgraded client state representing the upgraded chain + + Example Upgraded Client State JSON: + { + "@type":"/ibc.lightclients.tendermint.v1.ClientState", + "chain_id":"testchain1", + "unbonding_period":"1814400s", + "latest_height":{ + "revision_number":"0", + "revision_height":"2" + }, + "proof_specs":[ + { + "leaf_spec":{ + "hash":"SHA256", + "prehash_key":"NO_HASH", + "prehash_value":"SHA256", + "length":"VAR_PROTO", + "prefix":"AA==" + }, + "inner_spec":{ + "child_order":[ + 0, + 1 + ], + "child_size":33, + "min_prefix_length":4, + "max_prefix_length":12, + "empty_child":null, + "hash":"SHA256" + }, + "max_depth":0, + "min_depth":0 + }, + { + "leaf_spec":{ + "hash":"SHA256", + "prehash_key":"NO_HASH", + "prehash_value":"SHA256", + "length":"VAR_PROTO", + "prefix":"AA==" + }, + "inner_spec":{ + "child_order":[ + 0, + 1 + ], + "child_size":32, + "min_prefix_length":1, + "max_prefix_length":1, + "empty_child":null, + "hash":"SHA256" + }, + "max_depth":0, + "min_depth":0 + } + ], + "upgrade_path":[ + "upgrade", + "upgradedIBCState" + ] + } + `, + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + proposal, err := govcli.ReadGovPropFlags(clientCtx, cmd.Flags()) + if err != nil { + return err + } + + cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry) + + name := args[0] + + height, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return err + } + + plan := upgradetypes.Plan{ + Name: name, + Height: height, + } + + // attempt to unmarshal client state argument + var clientState exported.ClientState + clientContentOrFileName := args[2] + if err := cdc.UnmarshalInterfaceJSON([]byte(clientContentOrFileName), &clientState); err != nil { + + // check for file path if JSON input is not provided + contents, err := os.ReadFile(clientContentOrFileName) + if err != nil { + return fmt.Errorf("neither JSON input nor path to .json file for client state were provided: %w", err) + } + + if err := cdc.UnmarshalInterfaceJSON(contents, &clientState); err != nil { + return fmt.Errorf("error unmarshalling client state file: %w", err) + } + } + + authority, _ := cmd.Flags().GetString(FlagAuthority) + if authority != "" { + if _, err = sdk.AccAddressFromBech32(authority); err != nil { + return fmt.Errorf("invalid authority address: %w", err) + } + } else { + authority = sdk.AccAddress(address.Module(govtypes.ModuleName)).String() + } + + msg, err := types.NewMsgIBCSoftwareUpgrade(authority, plan, clientState) + if err != nil { + return fmt.Errorf("error in %T: %w", types.MsgIBCSoftwareUpgrade{}, err) + } + + if err = msg.ValidateBasic(); err != nil { + return fmt.Errorf("error validating %T: %w", types.MsgIBCSoftwareUpgrade{}, err) + } + + if err := proposal.SetMsgs([]sdk.Msg{msg}); err != nil { + return fmt.Errorf("failed to create proposal message for scheduling an IBC software upgrade: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), proposal) + }, + } + + cmd.Flags().String(FlagAuthority, "", "The address of the client module authority (defaults to gov)") + + flags.AddTxFlagsToCmd(cmd) + govcli.AddGovPropFlagsToCmd(cmd) + err := cmd.MarkFlagRequired(govcli.FlagTitle) + if err != nil { + panic(err) + } + + return cmd +} diff --git a/modules/core/02-client/client/utils/utils.go b/modules/core/02-client/client/utils/utils.go new file mode 100644 index 0000000..366c8aa --- /dev/null +++ b/modules/core/02-client/client/utils/utils.go @@ -0,0 +1,210 @@ +package utils + +import ( + "context" + + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + + cmttypes "github.com/cometbft/cometbft/types" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcclient "github.com/cosmos/ibc-go/v10/modules/core/client" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" +) + +// QueryClientState returns a client state. If prove is true, it performs an ABCI store query +// in order to retrieve the merkle proof. Otherwise, it uses the gRPC query client. +func QueryClientState( + clientCtx client.Context, clientID string, prove bool, +) (*types.QueryClientStateResponse, error) { + if prove { + return QueryClientStateABCI(clientCtx, clientID) + } + + queryClient := types.NewQueryClient(clientCtx) + req := &types.QueryClientStateRequest{ + ClientId: clientID, + } + + return queryClient.ClientState(context.Background(), req) +} + +// QueryClientStateABCI queries the store to get the light client state and a merkle proof. +func QueryClientStateABCI( + clientCtx client.Context, clientID string, +) (*types.QueryClientStateResponse, error) { + key := host.FullClientStateKey(clientID) + + value, proofBz, proofHeight, err := ibcclient.QueryTendermintProof(clientCtx, key) + if err != nil { + return nil, err + } + + // check if client exists + if len(value) == 0 { + return nil, errorsmod.Wrap(types.ErrClientNotFound, clientID) + } + + cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry) + + clientState, err := types.UnmarshalClientState(cdc, value) + if err != nil { + return nil, err + } + + anyClientState, err := types.PackClientState(clientState) + if err != nil { + return nil, err + } + + clientStateRes := types.NewQueryClientStateResponse(anyClientState, proofBz, proofHeight) + return clientStateRes, nil +} + +// QueryConsensusState returns a consensus state. If prove is true, it performs an ABCI store +// query in order to retrieve the merkle proof. Otherwise, it uses the gRPC query client. +func QueryConsensusState( + clientCtx client.Context, clientID string, height exported.Height, prove, latestHeight bool, +) (*types.QueryConsensusStateResponse, error) { + if prove { + return QueryConsensusStateABCI(clientCtx, clientID, height) + } + + queryClient := types.NewQueryClient(clientCtx) + req := &types.QueryConsensusStateRequest{ + ClientId: clientID, + RevisionNumber: height.GetRevisionNumber(), + RevisionHeight: height.GetRevisionHeight(), + LatestHeight: latestHeight, + } + + return queryClient.ConsensusState(context.Background(), req) +} + +// QueryConsensusStateABCI queries the store to get the consensus state of a light client and a +// merkle proof of its existence or non-existence. +func QueryConsensusStateABCI( + clientCtx client.Context, clientID string, height exported.Height, +) (*types.QueryConsensusStateResponse, error) { + key := host.FullConsensusStateKey(clientID, height) + + value, proofBz, proofHeight, err := ibcclient.QueryTendermintProof(clientCtx, key) + if err != nil { + return nil, err + } + + // check if consensus state exists + if len(value) == 0 { + return nil, errorsmod.Wrap(types.ErrConsensusStateNotFound, clientID) + } + + cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry) + + cs, err := types.UnmarshalConsensusState(cdc, value) + if err != nil { + return nil, err + } + + anyConsensusState, err := types.PackConsensusState(cs) + if err != nil { + return nil, err + } + + return types.NewQueryConsensusStateResponse(anyConsensusState, proofBz, proofHeight), nil +} + +// QueryTendermintHeader takes a client context and returns the appropriate +// tendermint header +func QueryTendermintHeader(clientCtx client.Context) (ibctm.Header, int64, error) { + node, err := clientCtx.GetNode() + if err != nil { + return ibctm.Header{}, 0, err + } + + info, err := node.ABCIInfo(context.Background()) + if err != nil { + return ibctm.Header{}, 0, err + } + + var height int64 + if clientCtx.Height != 0 { + height = clientCtx.Height + } else { + height = info.Response.LastBlockHeight + } + + commit, err := node.Commit(context.Background(), &height) + if err != nil { + return ibctm.Header{}, 0, err + } + + page := 1 + count := 10_000 + + validators, err := node.Validators(context.Background(), &height, &page, &count) + if err != nil { + return ibctm.Header{}, 0, err + } + + protoCommit := commit.ToProto() + protoValset, err := cmttypes.NewValidatorSet(validators.Validators).ToProto() + if err != nil { + return ibctm.Header{}, 0, err + } + + header := ibctm.Header{ + SignedHeader: protoCommit, + ValidatorSet: protoValset, + } + + return header, height, nil +} + +// QuerySelfConsensusState takes a client context and returns the appropriate +// tendermint consensus state +func QuerySelfConsensusState(clientCtx client.Context) (*ibctm.ConsensusState, int64, error) { + node, err := clientCtx.GetNode() + if err != nil { + return &ibctm.ConsensusState{}, 0, err + } + + info, err := node.ABCIInfo(context.Background()) + if err != nil { + return &ibctm.ConsensusState{}, 0, err + } + + var height int64 + if clientCtx.Height != 0 { + height = clientCtx.Height + } else { + height = info.Response.LastBlockHeight + } + + commit, err := node.Commit(context.Background(), &height) + if err != nil { + return &ibctm.ConsensusState{}, 0, err + } + + page := 1 + count := 10_000 + + nextHeight := height + 1 + nextVals, err := node.Validators(context.Background(), &nextHeight, &page, &count) + if err != nil { + return &ibctm.ConsensusState{}, 0, err + } + + state := &ibctm.ConsensusState{ + Timestamp: commit.Time, + Root: commitmenttypes.NewMerkleRoot(commit.AppHash), + NextValidatorsHash: cmttypes.NewValidatorSet(nextVals.Validators).Hash(), + } + + return state, height, nil +} diff --git a/modules/core/02-client/doc.go b/modules/core/02-client/doc.go new file mode 100644 index 0000000..81ac892 --- /dev/null +++ b/modules/core/02-client/doc.go @@ -0,0 +1,7 @@ +/* +Package client implements the ICS 02 - Client Semantics specification +(https://github.com/cosmos/ibc/tree/master/spec/core/ics-002-client-semantics). This +concrete implementation defines types and methods to store and update light +clients which tracks on other chain's state. +*/ +package client diff --git a/modules/core/02-client/genesis.go b/modules/core/02-client/genesis.go new file mode 100644 index 0000000..8531fa2 --- /dev/null +++ b/modules/core/02-client/genesis.go @@ -0,0 +1,73 @@ +package client + +import ( + "errors" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/keeper" + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// InitGenesis initializes the ibc client submodule's state from a provided genesis +// state. +func InitGenesis(ctx sdk.Context, k *keeper.Keeper, gs types.GenesisState) { + if err := gs.Params.Validate(); err != nil { + panic(fmt.Errorf("invalid ibc client genesis state parameters: %v", err)) + } + k.SetParams(ctx, gs.Params) + + // Set all client metadata first. This will allow client keeper to overwrite client and consensus state keys + // if clients accidentally write to ClientKeeper reserved keys. + if len(gs.ClientsMetadata) != 0 { + k.SetAllClientMetadata(ctx, gs.ClientsMetadata) + } + + for _, client := range gs.Clients { + cs, ok := client.ClientState.GetCachedValue().(exported.ClientState) + if !ok { + panic(errors.New("invalid client state")) + } + + if !gs.Params.IsAllowedClient(cs.ClientType()) { + panic(fmt.Errorf("client state type %s is not registered on the allowlist", cs.ClientType())) + } + + k.SetClientState(ctx, client.ClientId, cs) + } + + for _, cs := range gs.ClientsConsensus { + for _, consState := range cs.ConsensusStates { + consensusState, ok := consState.ConsensusState.GetCachedValue().(exported.ConsensusState) + if !ok { + panic(fmt.Errorf("invalid consensus state with client ID %s at height %s", cs.ClientId, consState.Height)) + } + + k.SetClientConsensusState(ctx, cs.ClientId, consState.Height, consensusState) + } + } + + k.SetNextClientSequence(ctx, gs.NextClientSequence) +} + +// ExportGenesis returns the ibc client submodule's exported genesis. +// NOTE: the export process is not optimized, it will iterate three +// times over the 02-client sub-store. +func ExportGenesis(ctx sdk.Context, k *keeper.Keeper) types.GenesisState { + genClients := k.GetAllGenesisClients(ctx) + clientsMetadata, err := k.GetAllClientMetadata(ctx, genClients) + if err != nil { + panic(err) + } + return types.GenesisState{ + Clients: genClients, + ClientsMetadata: clientsMetadata, + ClientsConsensus: k.GetAllConsensusStates(ctx), + Params: k.GetParams(ctx), + // Warning: CreateLocalhost is deprecated + CreateLocalhost: false, + NextClientSequence: k.GetNextClientSequence(ctx), + } +} diff --git a/modules/core/02-client/keeper/client.go b/modules/core/02-client/keeper/client.go new file mode 100644 index 0000000..dfce435 --- /dev/null +++ b/modules/core/02-client/keeper/client.go @@ -0,0 +1,152 @@ +package keeper + +import ( + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + "github.com/cosmos/ibc-go/v10/modules/core/internal/telemetry" +) + +// CreateClient generates a new client identifier and invokes the associated light client module in order to +// initialize a new client. An isolated prefixed store will be reserved for this client using the generated +// client identifier. The light client module is responsible for setting any client-specific data in the store +// via the Initialize method. This includes the client state, initial consensus state and any associated +// metadata. The generated client identifier will be returned if a client was successfully initialized. +func (k *Keeper) CreateClient(ctx sdk.Context, clientType string, clientState, consensusState []byte) (string, error) { + if clientType == exported.Localhost { + return "", errorsmod.Wrapf(types.ErrInvalidClientType, "cannot create client of type: %s", clientType) + } + + clientID := k.GenerateClientIdentifier(ctx, clientType) + + clientModule, err := k.Route(ctx, clientID) + if err != nil { + return "", err + } + + if err := clientModule.Initialize(ctx, clientID, clientState, consensusState); err != nil { + return "", err + } + + if status := clientModule.Status(ctx, clientID); status != exported.Active { + return "", errorsmod.Wrapf(types.ErrClientNotActive, "cannot create client (%s) with status %s", clientID, status) + } + + initialHeight := clientModule.LatestHeight(ctx, clientID) + k.Logger(ctx).Info("client created at height", "client-id", clientID, "height", initialHeight.String()) + + defer telemetry.ReportCreateClient(clientType) + emitCreateClientEvent(ctx, clientID, clientType, initialHeight) + + return clientID, nil +} + +// UpdateClient updates the consensus state and the state root from a provided header. +func (k *Keeper) UpdateClient(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) error { + clientModule, err := k.Route(ctx, clientID) + if err != nil { + return err + } + + if status := clientModule.Status(ctx, clientID); status != exported.Active { + return errorsmod.Wrapf(types.ErrClientNotActive, "cannot update client (%s) with status %s", clientID, status) + } + + if err := clientModule.VerifyClientMessage(ctx, clientID, clientMsg); err != nil { + return err + } + + foundMisbehaviour := clientModule.CheckForMisbehaviour(ctx, clientID, clientMsg) + if foundMisbehaviour { + clientModule.UpdateStateOnMisbehaviour(ctx, clientID, clientMsg) + + k.Logger(ctx).Info("client frozen due to misbehaviour", "client-id", clientID) + + clientType := types.MustParseClientIdentifier(clientID) + defer telemetry.ReportUpdateClient(foundMisbehaviour, clientType, clientID) + emitSubmitMisbehaviourEvent(ctx, clientID, clientType) + + return nil + } + + consensusHeights := clientModule.UpdateState(ctx, clientID, clientMsg) + + k.Logger(ctx).Info("client state updated", "client-id", clientID, "heights", consensusHeights) + + clientType := types.MustParseClientIdentifier(clientID) + defer telemetry.ReportUpdateClient(foundMisbehaviour, clientType, clientID) + emitUpdateClientEvent(ctx, clientID, clientType, consensusHeights, k.cdc, clientMsg) + + return nil +} + +// UpgradeClient upgrades the client to a new client state if this new client was committed to +// by the old client at the specified upgrade height +func (k *Keeper) UpgradeClient( + ctx sdk.Context, + clientID string, + upgradedClient, upgradedConsState, upgradeClientProof, upgradeConsensusStateProof []byte, +) error { + clientModule, err := k.Route(ctx, clientID) + if err != nil { + return err + } + + if status := clientModule.Status(ctx, clientID); status != exported.Active { + return errorsmod.Wrapf(types.ErrClientNotActive, "cannot upgrade client (%s) with status %s", clientID, status) + } + + if err := clientModule.VerifyUpgradeAndUpdateState(ctx, clientID, upgradedClient, upgradedConsState, upgradeClientProof, upgradeConsensusStateProof); err != nil { + return errorsmod.Wrapf(err, "cannot upgrade client with ID %s", clientID) + } + + latestHeight := clientModule.LatestHeight(ctx, clientID) + k.Logger(ctx).Info("client state upgraded", "client-id", clientID, "height", latestHeight.String()) + + clientType := types.MustParseClientIdentifier(clientID) + defer telemetry.ReportUpgradeClient(clientType, clientID) + emitUpgradeClientEvent(ctx, clientID, clientType, latestHeight) + + return nil +} + +// RecoverClient will invoke the light client module associated with the subject clientID requesting it to +// recover the subject client given a substitute client identifier. The light client implementation +// is responsible for validating the parameters of the substitute (ensuring they match the subject's parameters) +// as well as copying the necessary consensus states from the substitute to the subject client store. +// The substitute must be Active and the subject must not be Active. +func (k *Keeper) RecoverClient(ctx sdk.Context, subjectClientID, substituteClientID string) error { + clientModule, err := k.Route(ctx, subjectClientID) + if err != nil { + return errorsmod.Wrap(types.ErrRouteNotFound, subjectClientID) + } + + if status := clientModule.Status(ctx, subjectClientID); status == exported.Active { + return errorsmod.Wrapf(types.ErrInvalidRecoveryClient, "cannot recover subject client (%s) with status %s", subjectClientID, status) + } + + if status := clientModule.Status(ctx, substituteClientID); status != exported.Active { + return errorsmod.Wrapf(types.ErrClientNotActive, "cannot recover client using substitute client (%s) with status %s", substituteClientID, status) + } + + subjectLatestHeight := clientModule.LatestHeight(ctx, subjectClientID) + substituteLatestHeight := clientModule.LatestHeight(ctx, substituteClientID) + if subjectLatestHeight.GTE(substituteLatestHeight) { + return errorsmod.Wrapf(types.ErrInvalidHeight, "subject client state latest height is greater or equal to substitute client state latest height (%s >= %s)", subjectLatestHeight, substituteLatestHeight) + } + + if err := clientModule.RecoverClient(ctx, subjectClientID, substituteClientID); err != nil { + return err + } + + k.Logger(ctx).Info("client recovered", "client-id", subjectClientID) + + clientType := types.MustParseClientIdentifier(subjectClientID) + defer telemetry.ReportRecoverClient(clientType, subjectClientID) + emitRecoverClientEvent(ctx, subjectClientID, clientType) + + return nil +} diff --git a/modules/core/02-client/keeper/client_test.go b/modules/core/02-client/keeper/client_test.go new file mode 100644 index 0000000..ac0788f --- /dev/null +++ b/modules/core/02-client/keeper/client_test.go @@ -0,0 +1,693 @@ +package keeper_test + +import ( + "fmt" + "time" + + errorsmod "cosmossdk.io/errors" + upgradetypes "cosmossdk.io/x/upgrade/types" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + + abci "github.com/cometbft/cometbft/abci/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *KeeperTestSuite) TestCreateClient() { + var ( + clientState []byte + consensusState []byte + ) + + testCases := []struct { + msg string + malleate func() + clientType string + expErr error + }{ + { + "success: 07-tendermint client type supported", + func() { + tmClientState := ibctm.NewClientState(testChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath) + clientState = suite.chainA.App.AppCodec().MustMarshal(tmClientState) + consensusState = suite.chainA.App.AppCodec().MustMarshal(suite.consensusState) + }, + exported.Tendermint, + nil, + }, + { + "failure: 07-tendermint client status is not active", + func() { + tmClientState := ibctm.NewClientState(testChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, testClientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath) + tmClientState.FrozenHeight = ibctm.FrozenHeight + clientState = suite.chainA.App.AppCodec().MustMarshal(tmClientState) + consensusState = suite.chainA.App.AppCodec().MustMarshal(suite.consensusState) + }, + exported.Tendermint, + errorsmod.Wrapf(clienttypes.ErrClientNotActive, "cannot create client (07-tendermint-0) with status Frozen"), + }, + { + "success: 06-solomachine client type supported", + func() { + smClientState := solomachine.NewClientState(1, &solomachine.ConsensusState{PublicKey: suite.solomachine.ConsensusState().PublicKey, Diversifier: suite.solomachine.Diversifier, Timestamp: suite.solomachine.Time}) + smConsensusState := &solomachine.ConsensusState{PublicKey: suite.solomachine.ConsensusState().PublicKey, Diversifier: suite.solomachine.Diversifier, Timestamp: suite.solomachine.Time} + clientState = suite.chainA.App.AppCodec().MustMarshal(smClientState) + consensusState = suite.chainA.App.AppCodec().MustMarshal(smConsensusState) + }, + exported.Solomachine, + nil, + }, + { + "failure: 09-localhost client type not supported", + func() {}, + exported.Localhost, + errorsmod.Wrapf(clienttypes.ErrInvalidClientType, "cannot create client of type: 09-localhost"), + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + clientState, consensusState = []byte{}, []byte{} + + tc.malleate() + + clientID, err := suite.chainA.GetSimApp().IBCKeeper.ClientKeeper.CreateClient(suite.chainA.GetContext(), tc.clientType, clientState, consensusState) + + // assert correct behaviour based on expected error + clientState, found := suite.chainA.GetSimApp().IBCKeeper.ClientKeeper.GetClientState(suite.chainA.GetContext(), clientID) + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotEmpty(clientID) + suite.Require().True(found) + suite.Require().NotEmpty(clientState) + } else { + suite.Require().Error(err) + suite.Require().Empty(clientID) + suite.Require().False(found) + suite.Require().Empty(clientState) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestUpdateClientTendermint() { + var ( + path *ibctesting.Path + updateHeader *ibctm.Header + ) + + // Must create header creation functions since suite.header gets recreated on each test case + createFutureUpdateFn := func(trustedHeight clienttypes.Height) *ibctm.Header { + header, err := path.EndpointB.Chain.IBCClientHeader(path.EndpointB.Chain.LatestCommittedHeader, trustedHeight) + suite.Require().NoError(err) + return header + } + createPastUpdateFn := func(fillHeight, trustedHeight clienttypes.Height) *ibctm.Header { + consState, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientConsensusState(suite.chainA.GetContext(), path.EndpointA.ClientID, trustedHeight) + suite.Require().True(found) + + return suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(fillHeight.RevisionHeight), trustedHeight, consState.(*ibctm.ConsensusState).Timestamp.Add(time.Second*5), + suite.chainB.Vals, suite.chainB.Vals, suite.chainB.Vals, suite.chainB.Signers) + } + + cases := []struct { + name string + malleate func() + expErr error + expFreeze bool + }{ + {"valid update", func() { + trustedHeight := path.EndpointA.GetClientLatestHeight() + + // store intermediate consensus state to check that trustedHeight does not need to be highest consensus state before header height + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + updateHeader = createFutureUpdateFn(trustedHeight.(clienttypes.Height)) + }, nil, false}, + {"valid past update", func() { + trustedHeight := path.EndpointA.GetClientLatestHeight() + + currHeight := suite.chainB.ProposedHeader.Height + fillHeight := clienttypes.NewHeight(trustedHeight.GetRevisionNumber(), uint64(currHeight)) + + // commit a couple blocks to allow client to fill in gaps + suite.coordinator.CommitBlock(suite.chainB) // this height is not filled in yet + suite.coordinator.CommitBlock(suite.chainB) // this height is filled in by the update below + + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + // ensure fill height not set + _, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientConsensusState(suite.chainA.GetContext(), path.EndpointA.ClientID, fillHeight) + suite.Require().False(found) + + // updateHeader will fill in consensus state between prevConsState and suite.consState + // clientState should not be updated + updateHeader = createPastUpdateFn(fillHeight, trustedHeight.(clienttypes.Height)) + }, nil, false}, + {"valid duplicate update", func() { + height1 := clienttypes.NewHeight(1, 1) + + // store previous consensus state + prevConsState := &ibctm.ConsensusState{ + Timestamp: suite.past, + NextValidatorsHash: suite.chainB.Vals.Hash(), + } + path.EndpointA.SetConsensusState(prevConsState, height1) + + height5 := clienttypes.NewHeight(1, 5) + // store next consensus state to check that trustedHeight does not need to be highest consensus state before header height + nextConsState := &ibctm.ConsensusState{ + Timestamp: suite.past.Add(time.Minute), + NextValidatorsHash: suite.chainB.Vals.Hash(), + } + path.EndpointA.SetConsensusState(nextConsState, height5) + + // update client state latest height + clientState := path.EndpointA.GetClientState() + clientState.(*ibctm.ClientState).LatestHeight = height5 + path.EndpointA.SetClientState(clientState) + + height3 := clienttypes.NewHeight(1, 3) + // updateHeader will fill in consensus state between prevConsState and suite.consState + // clientState should not be updated + updateHeader = createPastUpdateFn(height3, height1) + // set updateHeader's consensus state in store to create duplicate UpdateClient scenario + path.EndpointA.SetConsensusState(updateHeader.ConsensusState(), updateHeader.GetHeight()) + }, nil, false}, + {"misbehaviour detection: conflicting header", func() { + clientID := path.EndpointA.ClientID + + height1 := clienttypes.NewHeight(1, 1) + // store previous consensus state + prevConsState := &ibctm.ConsensusState{ + Timestamp: suite.past, + NextValidatorsHash: suite.chainB.Vals.Hash(), + } + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), clientID, height1, prevConsState) + + height5 := clienttypes.NewHeight(1, 5) + // store next consensus state to check that trustedHeight does not need to be highest consensus state before header height + nextConsState := &ibctm.ConsensusState{ + Timestamp: suite.past.Add(time.Minute), + NextValidatorsHash: suite.chainB.Vals.Hash(), + } + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), clientID, height5, nextConsState) + + height3 := clienttypes.NewHeight(1, 3) + // updateHeader will fill in consensus state between prevConsState and suite.consState + // clientState should not be updated + updateHeader = createPastUpdateFn(height3, height1) + // set conflicting consensus state in store to create misbehaviour scenario + conflictConsState := updateHeader.ConsensusState() + conflictConsState.Root = commitmenttypes.NewMerkleRoot([]byte("conflicting apphash")) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), clientID, updateHeader.GetHeight(), conflictConsState) + }, nil, true}, + {"misbehaviour detection: monotonic time violation", func() { + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + clientID := path.EndpointA.ClientID + trustedHeight := clientState.LatestHeight + + // store intermediate consensus state at a time greater than updateHeader time + // this will break time monotonicity + incrementedClientHeight, ok := clientState.LatestHeight.Increment().(clienttypes.Height) + suite.Require().True(ok) + intermediateConsState := &ibctm.ConsensusState{ + Timestamp: suite.coordinator.CurrentTime.Add(2 * time.Hour), + NextValidatorsHash: suite.chainB.Vals.Hash(), + } + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), clientID, incrementedClientHeight, intermediateConsState) + // set iteration key + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), clientID) + ibctm.SetIterationKey(clientStore, incrementedClientHeight) + + clientState.LatestHeight = incrementedClientHeight + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + + updateHeader = createFutureUpdateFn(trustedHeight) + }, nil, true}, + {"client state not found", func() { + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + updateHeader = createFutureUpdateFn(clientState.LatestHeight) + + path.EndpointA.ClientID = ibctesting.InvalidID + }, errorsmod.Wrapf(host.ErrInvalidID, "invalid client identifier IDisInvalid is not in format: `{client-type}-{N}`"), false}, + {"consensus state not found", func() { + clientState := path.EndpointA.GetClientState() + tmClient, ok := clientState.(*ibctm.ClientState) + suite.Require().True(ok) + tmClient.LatestHeight, ok = tmClient.LatestHeight.Increment().(clienttypes.Height) + suite.Require().True(ok) + + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID, clientState) + updateHeader = createFutureUpdateFn(tmClient.LatestHeight) + }, errorsmod.Wrapf(clienttypes.ErrClientNotActive, "cannot update client (07-tendermint-0) with status Expired"), false}, + {"client is not active", func() { + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + clientState.FrozenHeight = clienttypes.NewHeight(1, 1) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID, clientState) + updateHeader = createFutureUpdateFn(clientState.LatestHeight) + }, errorsmod.Wrapf(clienttypes.ErrClientNotActive, "cannot update client (07-tendermint-0) with status Frozen"), false}, + {"invalid header", func() { + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + updateHeader = createFutureUpdateFn(clientState.LatestHeight) + updateHeader.TrustedHeight, ok = updateHeader.TrustedHeight.Increment().(clienttypes.Height) + suite.Require().True(ok) + }, errorsmod.Wrapf(clienttypes.ErrConsensusStateNotFound, "could not get trusted consensus state from clientStore for Header at TrustedHeight: 1-3"), false}, + } + + for _, tc := range cases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + tc.malleate() + + var clientState *ibctm.ClientState + var ok bool + if tc.expErr == nil { + clientState, ok = path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + } + + err := suite.chainA.App.GetIBCKeeper().ClientKeeper.UpdateClient(suite.chainA.GetContext(), path.EndpointA.ClientID, updateHeader) + + if tc.expErr == nil { + suite.Require().NoError(err, err) + + newClientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + + if tc.expFreeze { + suite.Require().True(!newClientState.FrozenHeight.IsZero(), "client did not freeze after conflicting header was submitted to UpdateClient") + } else { + expConsensusState := &ibctm.ConsensusState{ + Timestamp: updateHeader.GetTime(), + Root: commitmenttypes.NewMerkleRoot(updateHeader.Header.GetAppHash()), + NextValidatorsHash: updateHeader.Header.NextValidatorsHash, + } + + consensusState, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientConsensusState(suite.chainA.GetContext(), path.EndpointA.ClientID, updateHeader.GetHeight()) + suite.Require().True(found) + + // Determine if clientState should be updated or not + if updateHeader.GetHeight().GT(clientState.LatestHeight) { + // Header Height is greater than clientState latest Height, clientState should be updated with header.GetHeight() + suite.Require().Equal(updateHeader.GetHeight(), newClientState.LatestHeight, "clientstate height did not update") + } else { + // Update will add past consensus state, clientState should not be updated at all + suite.Require().Equal(clientState.LatestHeight, newClientState.LatestHeight, "client state height updated for past header") + } + + suite.Require().NoError(err) + suite.Require().Equal(expConsensusState, consensusState, "consensus state should have been updated on case %s", tc.name) + } + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestUpgradeClient() { + var ( + path *ibctesting.Path + upgradedClient *ibctm.ClientState + upgradedConsState exported.ConsensusState + upgradeHeight exported.Height + upgradedClientAny, upgradedConsStateAny *codectypes.Any + upgradedClientProof, upgradedConsensusStateProof []byte + ) + + testCases := []struct { + name string + setup func() + expErr error + }{ + { + name: "successful upgrade", + setup: func() { + // upgrade Height is at next block + upgradeHeight = clienttypes.NewHeight(1, uint64(suite.chainB.GetContext().BlockHeight()+1)) + + // zero custom fields and store in upgrade store + err := suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(upgradeHeight.GetRevisionHeight()), suite.chainB.Codec.MustMarshal(upgradedClientAny)) + suite.Require().NoError(err) + err = suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(upgradeHeight.GetRevisionHeight()), suite.chainB.Codec.MustMarshal(upgradedConsStateAny)) + suite.Require().NoError(err) + + // commit upgrade store changes and update clients + suite.coordinator.CommitBlock(suite.chainB) + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(upgradeHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(upgradeHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + }, + expErr: nil, + }, + { + name: "client state not found", + setup: func() { + // upgrade height is at next block + upgradeHeight = clienttypes.NewHeight(1, uint64(suite.chainB.GetContext().BlockHeight()+1)) + + // zero custom fields and store in upgrade store + err := suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(upgradeHeight.GetRevisionHeight()), suite.chainB.Codec.MustMarshal(upgradedClientAny)) + suite.Require().NoError(err) + err = suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(upgradeHeight.GetRevisionHeight()), suite.chainB.Codec.MustMarshal(upgradedConsStateAny)) + suite.Require().NoError(err) + + // commit upgrade store changes and update clients + + suite.coordinator.CommitBlock(suite.chainB) + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(upgradeHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(upgradeHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + + path.EndpointA.ClientID = "wrongclientid" + }, + expErr: errorsmod.Wrap(host.ErrInvalidID, "unable to parse client identifier wrongclientid: invalid client identifier wrongclientid is not in format: `{client-type}-{N}"), + }, + { + name: "client state is not active", + setup: func() { + // client is frozen + + // upgrade height is at next block + upgradeHeight = clienttypes.NewHeight(1, uint64(suite.chainB.GetContext().BlockHeight()+1)) + + // zero custom fields and store in upgrade store + err := suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(upgradeHeight.GetRevisionHeight()), suite.chainB.Codec.MustMarshal(upgradedClientAny)) + suite.Require().NoError(err) + err = suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(upgradeHeight.GetRevisionHeight()), suite.chainB.Codec.MustMarshal(upgradedConsStateAny)) + suite.Require().NoError(err) + + // commit upgrade store changes and update clients + suite.coordinator.CommitBlock(suite.chainB) + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(upgradeHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(upgradeHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + + // set frozen client in store + tmClient, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + tmClient.FrozenHeight = clienttypes.NewHeight(1, 1) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID, tmClient) + }, + expErr: errorsmod.Wrap(clienttypes.ErrClientNotActive, "cannot upgrade client (07-tendermint-2) with status Frozen"), + }, + { + name: "light client module VerifyUpgradeAndUpdateState fails", + setup: func() { + // upgrade height is at next block + upgradeHeight = clienttypes.NewHeight(1, uint64(suite.chainB.GetContext().BlockHeight()+1)) + + // zero custom fields and store in upgrade store + err := suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(upgradeHeight.GetRevisionHeight()), suite.chainB.Codec.MustMarshal(upgradedClientAny)) + suite.Require().NoError(err) + err = suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(upgradeHeight.GetRevisionHeight()), suite.chainB.Codec.MustMarshal(upgradedConsStateAny)) + suite.Require().NoError(err) + + // change upgradedClient client-specified parameters + upgradedClient.ChainId = "wrongchainID" + upgradedClientAny, err = codectypes.NewAnyWithValue(upgradedClient) + suite.Require().NoError(err) + + suite.coordinator.CommitBlock(suite.chainB) + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(upgradeHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(upgradeHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + }, + expErr: errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "failed to verify membership proof at index 0: provided value doesn't match proof"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + revisionNumber := clienttypes.ParseChainID(clientState.ChainId) + + newChainID, err := clienttypes.SetRevisionNumber(clientState.ChainId, revisionNumber+1) + suite.Require().NoError(err) + + upgradedClient = ibctm.NewClientState(newChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod+trustingPeriod, maxClockDrift, clienttypes.NewHeight(revisionNumber+1, clientState.LatestHeight.GetRevisionHeight()+1), commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath) + upgradedClient = upgradedClient.ZeroCustomFields() + + upgradedClientAny, err = codectypes.NewAnyWithValue(upgradedClient) + suite.Require().NoError(err) + + upgradedConsState = &ibctm.ConsensusState{NextValidatorsHash: []byte("nextValsHash")} + + upgradedConsStateAny, err = codectypes.NewAnyWithValue(upgradedConsState) + suite.Require().NoError(err) + + tc.setup() + + err = suite.chainA.App.GetIBCKeeper().ClientKeeper.UpgradeClient(suite.chainA.GetContext(), path.EndpointA.ClientID, upgradedClientAny.Value, upgradedConsStateAny.Value, upgradedClientProof, upgradedConsensusStateProof) + + if tc.expErr == nil { + suite.Require().NoError(err, "verify upgrade failed on valid case: %s", tc.name) + } else { + suite.Require().Error(err, "verify upgrade passed on invalid case: %s", tc.name) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestUpdateClientEventEmission() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + tmClientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + trustedHeight := tmClientState.LatestHeight + header, err := path.EndpointA.Counterparty.Chain.IBCClientHeader(path.EndpointA.Counterparty.Chain.LatestCommittedHeader, trustedHeight) + suite.Require().NoError(err) + + msg, err := clienttypes.NewMsgUpdateClient( + path.EndpointA.ClientID, header, + suite.chainA.SenderAccount.GetAddress().String(), + ) + suite.Require().NoError(err) + + result, err := suite.chainA.SendMsgs(msg) + + // check that update client event was emitted + suite.Require().NoError(err) + var event abci.Event + for _, e := range result.Events { + if e.Type == clienttypes.EventTypeUpdateClient { + event = e + } + } + suite.Require().NotNil(event) +} + +func (suite *KeeperTestSuite) TestRecoverClient() { + var ( + subject, substitute string + subjectClientState, substituteClientState exported.ClientState + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "success", + func() {}, + nil, + }, + { + "success, subject and substitute use different revision number", + func() { + tmClientState, ok := substituteClientState.(*ibctm.ClientState) + suite.Require().True(ok) + consState, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientConsensusState(suite.chainA.GetContext(), substitute, tmClientState.LatestHeight) + suite.Require().True(found) + newRevisionNumber := tmClientState.LatestHeight.GetRevisionNumber() + 1 + + tmClientState.LatestHeight = clienttypes.NewHeight(newRevisionNumber, tmClientState.LatestHeight.GetRevisionHeight()) + + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), substitute, tmClientState.LatestHeight, consState) + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), substitute) + ibctm.SetProcessedTime(clientStore, tmClientState.LatestHeight, 100) + ibctm.SetProcessedHeight(clientStore, tmClientState.LatestHeight, clienttypes.NewHeight(0, 1)) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), substitute, tmClientState) + }, + nil, + }, + { + "subject client does not exist", + func() { + subject = ibctesting.InvalidID + }, + clienttypes.ErrRouteNotFound, + }, + { + "subject is Active", + func() { + tmClientState, ok := subjectClientState.(*ibctm.ClientState) + suite.Require().True(ok) + // Set FrozenHeight to zero to ensure client is reported as Active + tmClientState.FrozenHeight = clienttypes.ZeroHeight() + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), subject, tmClientState) + }, + clienttypes.ErrInvalidRecoveryClient, + }, + { + "substitute client does not exist", + func() { + substitute = ibctesting.InvalidID + }, + clienttypes.ErrClientNotActive, + }, + { + "subject and substitute have equal latest height", + func() { + tmClientState, ok := subjectClientState.(*ibctm.ClientState) + suite.Require().True(ok) + tmClientState.LatestHeight = substituteClientState.(*ibctm.ClientState).LatestHeight + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), subject, tmClientState) + }, + clienttypes.ErrInvalidHeight, + }, + { + "subject height is greater than substitute height", + func() { + tmClientState, ok := subjectClientState.(*ibctm.ClientState) + suite.Require().True(ok) + tmClientState.LatestHeight, ok = substituteClientState.(*ibctm.ClientState).LatestHeight.Increment().(clienttypes.Height) + suite.Require().True(ok) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), subject, tmClientState) + }, + clienttypes.ErrInvalidHeight, + }, + { + "substitute is frozen", + func() { + tmClientState, ok := substituteClientState.(*ibctm.ClientState) + suite.Require().True(ok) + tmClientState.FrozenHeight = clienttypes.NewHeight(0, 1) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), substitute, tmClientState) + }, + clienttypes.ErrClientNotActive, + }, + { + "light client module RecoverClient fails, substitute client trust level doesn't match subject client trust level", + func() { + tmClientState, ok := substituteClientState.(*ibctm.ClientState) + suite.Require().True(ok) + tmClientState.UnbondingPeriod += time.Minute + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), substitute, tmClientState) + }, + clienttypes.ErrInvalidSubstitute, + }, + } + + for _, tc := range testCases { + suite.Run(tc.msg, func() { + suite.SetupTest() // reset + + subjectPath := ibctesting.NewPath(suite.chainA, suite.chainB) + subjectPath.SetupClients() + subject = subjectPath.EndpointA.ClientID + subjectClientState = suite.chainA.GetClientState(subject) + + substitutePath := ibctesting.NewPath(suite.chainA, suite.chainB) + substitutePath.SetupClients() + substitute = substitutePath.EndpointA.ClientID + + // update substitute twice + err := substitutePath.EndpointA.UpdateClient() + suite.Require().NoError(err) + err = substitutePath.EndpointA.UpdateClient() + suite.Require().NoError(err) + substituteClientState = suite.chainA.GetClientState(substitute) + + tmClientState, ok := subjectClientState.(*ibctm.ClientState) + suite.Require().True(ok) + tmClientState.FrozenHeight = tmClientState.LatestHeight + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), subject, tmClientState) + + tc.malleate() + + ctx := suite.chainA.GetContext() + err = suite.chainA.App.GetIBCKeeper().ClientKeeper.RecoverClient(ctx, subject, substitute) + + if tc.expErr == nil { + suite.Require().NoError(err) + + expectedEvents := sdk.Events{ + sdk.NewEvent( + clienttypes.EventTypeRecoverClient, + sdk.NewAttribute(clienttypes.AttributeKeySubjectClientID, subjectPath.EndpointA.ClientID), + sdk.NewAttribute(clienttypes.AttributeKeyClientType, subjectPath.EndpointA.GetClientState().ClientType()), + ), + }.ToABCIEvents() + + expectedEvents = sdk.MarkEventsToIndex(expectedEvents, map[string]struct{}{}) + ibctesting.AssertEvents(&suite.Suite, expectedEvents, ctx.EventManager().Events().ToABCIEvents()) + + // Assert that client status is now Active + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), subjectPath.EndpointA.ClientID) + suite.Require().NoError(err) + suite.Require().Equal(lightClientModule.Status(suite.chainA.GetContext(), subjectPath.EndpointA.ClientID), exported.Active) + + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} diff --git a/modules/core/02-client/keeper/events.go b/modules/core/02-client/keeper/events.go new file mode 100644 index 0000000..01da887 --- /dev/null +++ b/modules/core/02-client/keeper/events.go @@ -0,0 +1,136 @@ +package keeper + +import ( + "fmt" + "strconv" + "strings" + + upgradetypes "cosmossdk.io/x/upgrade/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// emitCreateClientEvent emits a create client event +func emitCreateClientEvent(ctx sdk.Context, clientID, clientType string, initialHeight exported.Height) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeCreateClient, + sdk.NewAttribute(types.AttributeKeyClientID, clientID), + sdk.NewAttribute(types.AttributeKeyClientType, clientType), + sdk.NewAttribute(types.AttributeKeyConsensusHeight, initialHeight.String()), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitUpdateClientEvent emits an update client event +func emitUpdateClientEvent(ctx sdk.Context, clientID string, clientType string, consensusHeights []exported.Height, _ codec.BinaryCodec, _ exported.ClientMessage) { + var consensusHeightAttr string + if len(consensusHeights) != 0 { + consensusHeightAttr = consensusHeights[0].String() + } + + consensusHeightsAttr := make([]string, len(consensusHeights)) + for i, height := range consensusHeights { + consensusHeightsAttr[i] = height.String() + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeUpdateClient, + sdk.NewAttribute(types.AttributeKeyClientID, clientID), + sdk.NewAttribute(types.AttributeKeyClientType, clientType), + // Deprecated: AttributeKeyConsensusHeight is deprecated and will be removed in a future release. + // Please use AttributeKeyConsensusHeights instead. + sdk.NewAttribute(types.AttributeKeyConsensusHeight, consensusHeightAttr), + sdk.NewAttribute(types.AttributeKeyConsensusHeights, strings.Join(consensusHeightsAttr, ",")), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitUpgradeClientEvent emits an upgrade client event +func emitUpgradeClientEvent(ctx sdk.Context, clientID, clientType string, latestHeight exported.Height) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeUpgradeClient, + sdk.NewAttribute(types.AttributeKeyClientID, clientID), + sdk.NewAttribute(types.AttributeKeyClientType, clientType), + sdk.NewAttribute(types.AttributeKeyConsensusHeight, latestHeight.String()), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitSubmitMisbehaviourEvent emits a client misbehaviour event +func emitSubmitMisbehaviourEvent(ctx sdk.Context, clientID string, clientType string) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeSubmitMisbehaviour, + sdk.NewAttribute(types.AttributeKeyClientID, clientID), + sdk.NewAttribute(types.AttributeKeyClientType, clientType), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitRecoverClientEvent emits a recover client event +func emitRecoverClientEvent(ctx sdk.Context, clientID, clientType string) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeRecoverClient, + sdk.NewAttribute(types.AttributeKeySubjectClientID, clientID), + sdk.NewAttribute(types.AttributeKeyClientType, clientType), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitScheduleIBCSoftwareUpgradeEvent emits a schedule IBC software upgrade event +func emitScheduleIBCSoftwareUpgradeEvent(ctx sdk.Context, title string, height int64) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeScheduleIBCSoftwareUpgrade, + sdk.NewAttribute(types.AttributeKeyUpgradePlanTitle, title), + sdk.NewAttribute(types.AttributeKeyUpgradePlanHeight, fmt.Sprintf("%d", height)), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// EmitUpgradeChainEvent emits an upgrade chain event. +func EmitUpgradeChainEvent(ctx sdk.Context, height int64) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeUpgradeChain, + sdk.NewAttribute(types.AttributeKeyUpgradePlanHeight, strconv.FormatInt(height, 10)), + sdk.NewAttribute(types.AttributeKeyUpgradeStore, upgradetypes.StoreKey), // which store to query proof of consensus state from + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} diff --git a/modules/core/02-client/keeper/events_test.go b/modules/core/02-client/keeper/events_test.go new file mode 100644 index 0000000..f9e6f1a --- /dev/null +++ b/modules/core/02-client/keeper/events_test.go @@ -0,0 +1,94 @@ +package keeper_test + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *KeeperTestSuite) TestMsgCreateClientEvents() { + suite.SetupTest() + path := ibctesting.NewPath(suite.chainA, suite.chainB) + + path.EndpointA.Counterparty.Chain.NextBlock() + + tmConfig, ok := path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig) + suite.Require().True(ok) + + height, ok := path.EndpointA.Counterparty.Chain.LatestCommittedHeader.GetHeight().(clienttypes.Height) + suite.Require().True(ok) + + clientState := ibctm.NewClientState( + path.EndpointA.Counterparty.Chain.ChainID, tmConfig.TrustLevel, tmConfig.TrustingPeriod, tmConfig.UnbondingPeriod, tmConfig.MaxClockDrift, + height, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath) + consensusState := path.EndpointA.Counterparty.Chain.LatestCommittedHeader.ConsensusState() + + msg, err := clienttypes.NewMsgCreateClient( + clientState, consensusState, path.EndpointA.Chain.SenderAccount.GetAddress().String(), + ) + suite.Require().NoError(err) + + res, err := suite.chainA.SendMsgs(msg) + suite.Require().NoError(err) + suite.Require().NotNil(res) + + events := res.Events + expectedEvents := sdk.Events{ + sdk.NewEvent( + clienttypes.EventTypeCreateClient, + sdk.NewAttribute(clienttypes.AttributeKeyClientID, ibctesting.FirstClientID), + sdk.NewAttribute(clienttypes.AttributeKeyClientType, clientState.ClientType()), + sdk.NewAttribute(clienttypes.AttributeKeyConsensusHeight, clientState.LatestHeight.String()), + ), + }.ToABCIEvents() + + var indexSet map[string]struct{} + expectedEvents = sdk.MarkEventsToIndex(expectedEvents, indexSet) + ibctesting.AssertEvents(&suite.Suite, expectedEvents, events) +} + +func (suite *KeeperTestSuite) TestMsgUpdateClientEvents() { + suite.SetupTest() + path := ibctesting.NewPath(suite.chainA, suite.chainB) + + suite.Require().NoError(path.EndpointA.CreateClient()) + + suite.chainB.Coordinator.CommitBlock(suite.chainB) + + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + + trustedHeight := clientState.LatestHeight + header, err := suite.chainB.IBCClientHeader(suite.chainB.LatestCommittedHeader, trustedHeight) + suite.Require().NoError(err) + suite.Require().NotNil(header) + + msg, err := clienttypes.NewMsgUpdateClient( + ibctesting.FirstClientID, header, + path.EndpointA.Chain.SenderAccount.GetAddress().String(), + ) + + suite.Require().NoError(err) + + res, err := suite.chainA.SendMsgs(msg) + suite.Require().NoError(err) + suite.Require().NotNil(res) + + events := res.Events + expectedEvents := sdk.Events{ + sdk.NewEvent( + clienttypes.EventTypeUpdateClient, + sdk.NewAttribute(clienttypes.AttributeKeyClientID, ibctesting.FirstClientID), + sdk.NewAttribute(clienttypes.AttributeKeyClientType, path.EndpointA.GetClientState().ClientType()), + sdk.NewAttribute(clienttypes.AttributeKeyConsensusHeight, path.EndpointA.GetClientLatestHeight().String()), + sdk.NewAttribute(clienttypes.AttributeKeyConsensusHeights, path.EndpointA.GetClientLatestHeight().String()), + ), + }.ToABCIEvents() + + var indexSet map[string]struct{} + expectedEvents = sdk.MarkEventsToIndex(expectedEvents, indexSet) + ibctesting.AssertEvents(&suite.Suite, expectedEvents, events) +} diff --git a/modules/core/02-client/keeper/grpc_query.go b/modules/core/02-client/keeper/grpc_query.go new file mode 100644 index 0000000..094e5d0 --- /dev/null +++ b/modules/core/02-client/keeper/grpc_query.go @@ -0,0 +1,431 @@ +package keeper + +import ( + "bytes" + "context" + "fmt" + "slices" + "sort" + "strings" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/store/prefix" + + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var _ types.QueryServer = (*queryServer)(nil) + +// queryServer implements the 02-client types.QueryServer interface. +// It embeds the client keeper to leverage store access while limiting the api of the client keeper. +type queryServer struct { + *Keeper +} + +// NewQueryServer returns a new 02-client types.QueryServer implementation. +func NewQueryServer(k *Keeper) types.QueryServer { + return &queryServer{ + Keeper: k, + } +} + +// ClientState implements the Query/ClientState gRPC method +func (q *queryServer) ClientState(goCtx context.Context, req *types.QueryClientStateRequest) (*types.QueryClientStateResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + if err := host.ClientIdentifierValidator(req.ClientId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + clientState, found := q.GetClientState(ctx, req.ClientId) + if !found { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrap(types.ErrClientNotFound, req.ClientId).Error(), + ) + } + + protoAny, err := types.PackClientState(clientState) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + proofHeight := types.GetSelfHeight(ctx) + return &types.QueryClientStateResponse{ + ClientState: protoAny, + ProofHeight: proofHeight, + }, nil +} + +// ClientStates implements the Query/ClientStates gRPC method +func (q *queryServer) ClientStates(goCtx context.Context, req *types.QueryClientStatesRequest) (*types.QueryClientStatesResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + var clientStates types.IdentifiedClientStates + store := prefix.NewStore(runtime.KVStoreAdapter(q.storeService.OpenKVStore(ctx)), host.KeyClientStorePrefix) + + pageRes, err := query.FilteredPaginate(store, req.Pagination, func(key, value []byte, accumulate bool) (bool, error) { + // filter any metadata stored under client state key + keySplit := strings.Split(string(key), "/") + if keySplit[len(keySplit)-1] != "clientState" { + return false, nil + } + + clientState, err := types.UnmarshalClientState(q.cdc, value) + if err != nil { + return false, err + } + + clientID := keySplit[1] + if err := host.ClientIdentifierValidator(clientID); err != nil { + return false, err + } + + identifiedClient := types.NewIdentifiedClientState(clientID, clientState) + clientStates = append(clientStates, identifiedClient) + return true, nil + }) + if err != nil { + return nil, err + } + + sort.Sort(clientStates) + + return &types.QueryClientStatesResponse{ + ClientStates: clientStates, + Pagination: pageRes, + }, nil +} + +// ConsensusState implements the Query/ConsensusState gRPC method +func (q *queryServer) ConsensusState(goCtx context.Context, req *types.QueryConsensusStateRequest) (*types.QueryConsensusStateResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + if err := host.ClientIdentifierValidator(req.ClientId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + var ( + consensusState exported.ConsensusState + found bool + ) + + height := types.NewHeight(req.RevisionNumber, req.RevisionHeight) + if req.LatestHeight { + consensusState, found = q.GetLatestClientConsensusState(ctx, req.ClientId) + } else { + if req.RevisionHeight == 0 { + return nil, status.Error(codes.InvalidArgument, "consensus state height cannot be 0") + } + + consensusState, found = q.GetClientConsensusState(ctx, req.ClientId, height) + } + + if !found { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrConsensusStateNotFound, "client-id: %s, height: %s", req.ClientId, height).Error(), + ) + } + + protoAny, err := types.PackConsensusState(consensusState) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + proofHeight := types.GetSelfHeight(ctx) + return &types.QueryConsensusStateResponse{ + ConsensusState: protoAny, + ProofHeight: proofHeight, + }, nil +} + +// ConsensusStates implements the Query/ConsensusStates gRPC method +func (q *queryServer) ConsensusStates(goCtx context.Context, req *types.QueryConsensusStatesRequest) (*types.QueryConsensusStatesResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + if err := host.ClientIdentifierValidator(req.ClientId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + var consensusStates []types.ConsensusStateWithHeight + store := prefix.NewStore(runtime.KVStoreAdapter(q.storeService.OpenKVStore(ctx)), host.FullClientKey(req.ClientId, fmt.Appendf(nil, "%s/", host.KeyConsensusStatePrefix))) + + pageRes, err := query.FilteredPaginate(store, req.Pagination, func(key, value []byte, accumulate bool) (bool, error) { + // filter any metadata stored under consensus state key + if bytes.Contains(key, []byte("/")) { + return false, nil + } + + height, err := types.ParseHeight(string(key)) + if err != nil { + return false, err + } + + consensusState, err := types.UnmarshalConsensusState(q.cdc, value) + if err != nil { + return false, err + } + + consensusStates = append(consensusStates, types.NewConsensusStateWithHeight(height, consensusState)) + return true, nil + }) + if err != nil { + return nil, err + } + + return &types.QueryConsensusStatesResponse{ + ConsensusStates: consensusStates, + Pagination: pageRes, + }, nil +} + +// ConsensusStateHeights implements the Query/ConsensusStateHeights gRPC method +func (q *queryServer) ConsensusStateHeights(goCtx context.Context, req *types.QueryConsensusStateHeightsRequest) (*types.QueryConsensusStateHeightsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + if err := host.ClientIdentifierValidator(req.ClientId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + var consensusStateHeights []types.Height + store := prefix.NewStore(runtime.KVStoreAdapter(q.storeService.OpenKVStore(ctx)), host.FullClientKey(req.ClientId, fmt.Appendf(nil, "%s/", host.KeyConsensusStatePrefix))) + + pageRes, err := query.FilteredPaginate(store, req.Pagination, func(key, _ []byte, accumulate bool) (bool, error) { + // filter any metadata stored under consensus state key + if bytes.Contains(key, []byte("/")) { + return false, nil + } + + height, err := types.ParseHeight(string(key)) + if err != nil { + return false, err + } + + consensusStateHeights = append(consensusStateHeights, height) + return true, nil + }) + if err != nil { + return nil, err + } + + return &types.QueryConsensusStateHeightsResponse{ + ConsensusStateHeights: consensusStateHeights, + Pagination: pageRes, + }, nil +} + +// ClientStatus implements the Query/ClientStatus gRPC method +func (q *queryServer) ClientStatus(goCtx context.Context, req *types.QueryClientStatusRequest) (*types.QueryClientStatusResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + if err := host.ClientIdentifierValidator(req.ClientId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + clientStatus := q.GetClientStatus(ctx, req.ClientId) + + return &types.QueryClientStatusResponse{ + Status: clientStatus.String(), + }, nil +} + +// ClientCreator implements the Query/ClientCreator gRPC method +func (q *queryServer) ClientCreator(goCtx context.Context, req *types.QueryClientCreatorRequest) (*types.QueryClientCreatorResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + if err := host.ClientIdentifierValidator(req.ClientId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + creator := q.GetClientCreator(ctx, req.ClientId) + + return &types.QueryClientCreatorResponse{ + Creator: creator.String(), + }, nil +} + +// ClientParams implements the Query/ClientParams gRPC method +func (q *queryServer) ClientParams(goCtx context.Context, _ *types.QueryClientParamsRequest) (*types.QueryClientParamsResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + params := q.GetParams(ctx) + + return &types.QueryClientParamsResponse{ + Params: ¶ms, + }, nil +} + +// UpgradedClientState implements the Query/UpgradedClientState gRPC method +func (q *queryServer) UpgradedClientState(goCtx context.Context, req *types.QueryUpgradedClientStateRequest) (*types.QueryUpgradedClientStateResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + plan, err := q.GetUpgradePlan(ctx) + if err != nil { + return nil, status.Error(codes.NotFound, err.Error()) + } + + bz, err := q.GetUpgradedClient(ctx, plan.Height) + if err != nil { + return nil, status.Error(codes.NotFound, err.Error()) + } + + clientState, err := types.UnmarshalClientState(q.cdc, bz) + if err != nil { + return nil, status.Error( + codes.Internal, err.Error(), + ) + } + + protoAny, err := types.PackClientState(clientState) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &types.QueryUpgradedClientStateResponse{ + UpgradedClientState: protoAny, + }, nil +} + +// UpgradedConsensusState implements the Query/UpgradedConsensusState gRPC method +func (q *queryServer) UpgradedConsensusState(goCtx context.Context, req *types.QueryUpgradedConsensusStateRequest) (*types.QueryUpgradedConsensusStateResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + bz, err := q.GetUpgradedConsensusState(ctx, ctx.BlockHeight()) + if err != nil { + return nil, status.Errorf(codes.NotFound, "%s, height %d", err.Error(), ctx.BlockHeight()) + } + + consensusState, err := types.UnmarshalConsensusState(q.cdc, bz) + if err != nil { + return nil, status.Error( + codes.Internal, err.Error(), + ) + } + + protoAny, err := types.PackConsensusState(consensusState) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &types.QueryUpgradedConsensusStateResponse{ + UpgradedConsensusState: protoAny, + }, nil +} + +// VerifyMembership implements the Query/VerifyMembership gRPC method +// NOTE: Any state changes made within this handler are discarded by leveraging a cached context. Gas is consumed for underlying state access. +// This gRPC method is intended to be used within the context of the state machine and delegates to light clients to verify proofs. +func (q *queryServer) VerifyMembership(goCtx context.Context, req *types.QueryVerifyMembershipRequest) (*types.QueryVerifyMembershipResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + if err := host.ClientIdentifierValidator(req.ClientId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + clientType, _, err := types.ParseClientIdentifier(req.ClientId) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + denyClients := []string{exported.Localhost, exported.Solomachine} + if slices.Contains(denyClients, clientType) { + return nil, status.Error(codes.InvalidArgument, errorsmod.Wrapf(types.ErrInvalidClientType, "verify membership is disabled for client types %s", denyClients).Error()) + } + + if len(req.Proof) == 0 { + return nil, status.Error(codes.InvalidArgument, "empty proof") + } + + if req.ProofHeight.IsZero() { + return nil, status.Error(codes.InvalidArgument, "proof height must be non-zero") + } + + if req.MerklePath.Empty() { + return nil, status.Error(codes.InvalidArgument, "empty merkle path") + } + + if len(req.Value) == 0 { + return nil, status.Error(codes.InvalidArgument, "empty value") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + // cache the context to ensure clientState.VerifyMembership does not change state + cachedCtx, _ := ctx.CacheContext() + + // make sure we charge the higher level context even on panic + defer func() { + ctx.GasMeter().ConsumeGas(cachedCtx.GasMeter().GasConsumed(), "verify membership query") + }() + + clientModule, err := q.Route(ctx, req.ClientId) + if err != nil { + return nil, status.Error(codes.NotFound, err.Error()) + } + + if clientStatus := q.GetClientStatus(ctx, req.ClientId); clientStatus != exported.Active { + return nil, status.Error(codes.FailedPrecondition, errorsmod.Wrapf(types.ErrClientNotActive, "cannot verify membership using client (%s) with status %s", req.ClientId, clientStatus).Error()) + } + + // consume flat gas fee for proof verification queries. + // NOTE: consuming gas prior to method invocation also provides protection against recursive calls reaching stack overflow + ctx.GasMeter().ConsumeGas( + 3*ctx.KVGasConfig().ReadCostPerByte*uint64(len(req.Proof)), + "verify membership query", + ) + + if err := clientModule.VerifyMembership(cachedCtx, req.ClientId, req.ProofHeight, req.TimeDelay, req.BlockDelay, req.Proof, req.MerklePath, req.Value); err != nil { + q.Logger(ctx).Debug("proof verification failed", "key", req.MerklePath, "error", err) + return &types.QueryVerifyMembershipResponse{ + Success: false, + }, nil + } + + return &types.QueryVerifyMembershipResponse{ + Success: true, + }, nil +} diff --git a/modules/core/02-client/keeper/grpc_query_test.go b/modules/core/02-client/keeper/grpc_query_test.go new file mode 100644 index 0000000..4794b19 --- /dev/null +++ b/modules/core/02-client/keeper/grpc_query_test.go @@ -0,0 +1,1025 @@ +package keeper_test + +import ( + "errors" + "fmt" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + errorsmod "cosmossdk.io/errors" + upgradetypes "cosmossdk.io/x/upgrade/types" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/types/query" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/keeper" + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + "github.com/cosmos/ibc-go/v10/testing/mock" +) + +func (suite *KeeperTestSuite) TestQueryClientState() { + var ( + req *types.QueryClientStateRequest + expClientState *codectypes.Any + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "req is nil", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid clientID", + func() { + req = &types.QueryClientStateRequest{} + }, + status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"), + }, + { + "client not found", + func() { + req = &types.QueryClientStateRequest{ + ClientId: testClientID, + } + }, + status.Error( + codes.NotFound, + errorsmod.Wrap(types.ErrClientNotFound, "tendermint-0").Error(), + ), + }, + { + "success", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + var err error + expClientState, err = types.PackClientState(path.EndpointA.GetClientState()) + suite.Require().NoError(err) + + req = &types.QueryClientStateRequest{ + ClientId: path.EndpointA.ClientID, + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.GetSimApp().IBCKeeper.ClientKeeper) + res, err := queryServer.ClientState(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expClientState, res.ClientState) + + // ensure UnpackInterfaces is defined + cachedValue := res.ClientState.GetCachedValue() + suite.Require().NotNil(cachedValue) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryClientStates() { + var ( + req *types.QueryClientStatesRequest + expClientStates = types.IdentifiedClientStates{} + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "req is nil", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "empty pagination", + func() { + expClientStates = nil + req = &types.QueryClientStatesRequest{} + }, + nil, + }, + { + "success", + func() { + path1 := ibctesting.NewPath(suite.chainA, suite.chainB) + path1.SetupClients() + + path2 := ibctesting.NewPath(suite.chainA, suite.chainB) + path2.SetupClients() + + clientStateA1 := path1.EndpointA.GetClientState() + clientStateA2 := path2.EndpointA.GetClientState() + + idcs := types.NewIdentifiedClientState(path1.EndpointA.ClientID, clientStateA1) + idcs2 := types.NewIdentifiedClientState(path2.EndpointA.ClientID, clientStateA2) + + // order is sorted by client id + expClientStates = types.IdentifiedClientStates{idcs, idcs2}.Sort() + req = &types.QueryClientStatesRequest{ + Pagination: &query.PageRequest{ + Limit: 20, + CountTotal: true, + }, + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + tc.malleate() + + ctx := suite.chainA.GetContext() + queryServer := keeper.NewQueryServer(suite.chainA.GetSimApp().IBCKeeper.ClientKeeper) + res, err := queryServer.ClientStates(ctx, req) + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expClientStates.Sort(), res.ClientStates) + suite.Require().Equal(len(expClientStates), int(res.Pagination.Total)) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryConsensusState() { + var ( + req *types.QueryConsensusStateRequest + expConsensusState *codectypes.Any + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "req is nil", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid clientID", + func() { + req = &types.QueryConsensusStateRequest{} + }, + status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"), + }, + { + "invalid height", + func() { + req = &types.QueryConsensusStateRequest{ + ClientId: testClientID, + RevisionNumber: 0, + RevisionHeight: 0, + LatestHeight: false, + } + }, + status.Error(codes.InvalidArgument, "consensus state height cannot be 0"), + }, + { + "consensus state not found", + func() { + req = &types.QueryConsensusStateRequest{ + ClientId: ibctesting.FirstClientID, + LatestHeight: true, + } + }, + status.Error(codes.NotFound, "client-id: 07-tendermint-0, height: 0-0: consensus state not found"), + }, + { + "success latest height", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + cs := path.EndpointA.GetConsensusState(path.EndpointA.GetClientLatestHeight()) + + var err error + expConsensusState, err = types.PackConsensusState(cs) + suite.Require().NoError(err) + + req = &types.QueryConsensusStateRequest{ + ClientId: path.EndpointA.ClientID, + LatestHeight: true, + } + }, + nil, + }, + { + "success with height", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + height := path.EndpointA.GetClientLatestHeight() + cs := path.EndpointA.GetConsensusState(height) + + var err error + expConsensusState, err = types.PackConsensusState(cs) + suite.Require().NoError(err) + + // update client to new height + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + req = &types.QueryConsensusStateRequest{ + ClientId: path.EndpointA.ClientID, + RevisionNumber: height.GetRevisionNumber(), + RevisionHeight: height.GetRevisionHeight(), + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + queryServer := keeper.NewQueryServer(suite.chainA.GetSimApp().IBCKeeper.ClientKeeper) + res, err := queryServer.ConsensusState(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expConsensusState, res.ConsensusState) + + // ensure UnpackInterfaces is defined + cachedValue := res.ConsensusState.GetCachedValue() + suite.Require().NotNil(cachedValue) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryConsensusStates() { + var ( + req *types.QueryConsensusStatesRequest + expConsensusStates []types.ConsensusStateWithHeight + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "success: without pagination", + func() { + req = &types.QueryConsensusStatesRequest{ + ClientId: testClientID, + } + }, + nil, + }, + { + "success, no results", + func() { + req = &types.QueryConsensusStatesRequest{ + ClientId: testClientID, + Pagination: &query.PageRequest{ + Limit: 3, + CountTotal: true, + }, + } + }, + nil, + }, + { + "success", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + height1, ok := path.EndpointA.GetClientLatestHeight().(types.Height) + suite.Require().True(ok) + expConsensusStates = append( + expConsensusStates, + types.NewConsensusStateWithHeight( + height1, + path.EndpointA.GetConsensusState(height1), + )) + + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + height2, ok := path.EndpointA.GetClientLatestHeight().(types.Height) + suite.Require().True(ok) + expConsensusStates = append( + expConsensusStates, + types.NewConsensusStateWithHeight( + height2, + path.EndpointA.GetConsensusState(height2), + )) + + req = &types.QueryConsensusStatesRequest{ + ClientId: path.EndpointA.ClientID, + Pagination: &query.PageRequest{ + Limit: 3, + CountTotal: true, + }, + } + }, + nil, + }, + { + "invalid client identifier", + func() { + req = &types.QueryConsensusStatesRequest{} + }, + status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"), + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + queryServer := keeper.NewQueryServer(suite.chainA.GetSimApp().IBCKeeper.ClientKeeper) + res, err := queryServer.ConsensusStates(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(len(expConsensusStates), len(res.ConsensusStates)) + for i := range expConsensusStates { + suite.Require().NotNil(res.ConsensusStates[i]) + suite.Require().Equal(expConsensusStates[i], res.ConsensusStates[i]) + // ensure UnpackInterfaces is defined + cachedValue := res.ConsensusStates[i].ConsensusState.GetCachedValue() + suite.Require().NotNil(cachedValue) + } + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryConsensusStateHeights() { + var ( + req *types.QueryConsensusStateHeightsRequest + expConsensusStateHeights []types.Height + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "success: without pagination", + func() { + req = &types.QueryConsensusStateHeightsRequest{ + ClientId: testClientID, + } + }, + nil, + }, + { + "success: response contains no results", + func() { + req = &types.QueryConsensusStateHeightsRequest{ + ClientId: testClientID, + Pagination: &query.PageRequest{ + Limit: 3, + CountTotal: true, + }, + } + }, + nil, + }, + { + "success: returns consensus heights", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + expConsensusStateHeights = append(expConsensusStateHeights, path.EndpointA.GetClientLatestHeight().(types.Height)) + + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + expConsensusStateHeights = append(expConsensusStateHeights, path.EndpointA.GetClientLatestHeight().(types.Height)) + + req = &types.QueryConsensusStateHeightsRequest{ + ClientId: path.EndpointA.ClientID, + Pagination: &query.PageRequest{ + Limit: 3, + CountTotal: true, + }, + } + }, + nil, + }, + { + "invalid client identifier", + func() { + req = &types.QueryConsensusStateHeightsRequest{} + }, + status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"), + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + queryServer := keeper.NewQueryServer(suite.chainA.GetSimApp().IBCKeeper.ClientKeeper) + res, err := queryServer.ConsensusStateHeights(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(len(expConsensusStateHeights), len(res.ConsensusStateHeights)) + for i := range expConsensusStateHeights { + suite.Require().NotNil(res.ConsensusStateHeights[i]) + suite.Require().Equal(expConsensusStateHeights[i], res.ConsensusStateHeights[i]) + } + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryClientStatus() { + var req *types.QueryClientStatusRequest + + testCases := []struct { + msg string + malleate func() + expErr error + expStatus string + }{ + { + "req is nil", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), "", + }, + { + "invalid clientID", + func() { + req = &types.QueryClientStatusRequest{} + }, + status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"), "", + }, + { + "client not found", + func() { + req = &types.QueryClientStatusRequest{ + ClientId: ibctesting.InvalidID, + } + }, + nil, exported.Unauthorized.String(), + }, + { + "Active client status", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + req = &types.QueryClientStatusRequest{ + ClientId: path.EndpointA.ClientID, + } + }, + nil, exported.Active.String(), + }, + { + "Unknown client status", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + + // increment latest height so no consensus state is stored + clientState.LatestHeight, ok = clientState.LatestHeight.Increment().(types.Height) + suite.Require().True(ok) + path.EndpointA.SetClientState(clientState) + + req = &types.QueryClientStatusRequest{ + ClientId: path.EndpointA.ClientID, + } + }, + nil, exported.Expired.String(), + }, + { + "Frozen client status", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + + clientState.FrozenHeight = types.NewHeight(0, 1) + path.EndpointA.SetClientState(clientState) + + req = &types.QueryClientStatusRequest{ + ClientId: path.EndpointA.ClientID, + } + }, + nil, exported.Frozen.String(), + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + queryServer := keeper.NewQueryServer(suite.chainA.GetSimApp().IBCKeeper.ClientKeeper) + res, err := queryServer.ClientStatus(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(tc.expStatus, res.Status) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryUpgradedClientState() { + var ( + req *types.QueryUpgradedClientStateRequest + path *ibctesting.Path + expClientState *ibctm.ClientState + ) + + upgradePlan := upgradetypes.Plan{ + Name: "upgrade IBC clients", + Height: 1000, + } + + testCases := []struct { + msg string + malleate func() + expError error + }{ + { + "success", + func() { + validAuthority := suite.chainA.App.GetIBCKeeper().GetAuthority() + + // update trusting period + clientState := path.EndpointA.GetClientState() + clientState.(*ibctm.ClientState).TrustingPeriod += 100 + + msg, err := types.NewMsgIBCSoftwareUpgrade( + validAuthority, + upgradePlan, + clientState, + ) + suite.Require().NoError(err) + + resp, err := suite.chainA.App.GetIBCKeeper().IBCSoftwareUpgrade(suite.chainA.GetContext(), msg) + suite.Require().NoError(err) + suite.Require().NotNil(resp) + + var ok bool + expClientState, ok = clientState.(*ibctm.ClientState) + suite.Require().True(ok) + }, + nil, + }, + { + "req is nil", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "no plan", + func() { + req = &types.QueryUpgradedClientStateRequest{} + }, + status.Error(codes.NotFound, "upgrade plan not found"), + }, + { + "no upgraded client set in store", + func() { + err := suite.chainA.GetSimApp().UpgradeKeeper.ScheduleUpgrade(suite.chainA.GetContext(), upgradePlan) + suite.Require().NoError(err) + }, + status.Error(codes.NotFound, "upgraded client not found"), + }, + { + "invalid upgraded client state", + func() { + err := suite.chainA.GetSimApp().UpgradeKeeper.ScheduleUpgrade(suite.chainA.GetContext(), upgradePlan) + suite.Require().NoError(err) + + bz := []byte{1, 2, 3} + err = suite.chainA.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainA.GetContext(), upgradePlan.Height, bz) + suite.Require().NoError(err) + }, + status.Error(codes.Internal, "proto: Any: illegal tag 0 (wire type 1)"), + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + req = &types.QueryUpgradedClientStateRequest{} + + tc.malleate() + + queryServer := keeper.NewQueryServer(suite.chainA.GetSimApp().IBCKeeper.ClientKeeper) + res, err := queryServer.UpgradedClientState(suite.chainA.GetContext(), req) + + if tc.expError == nil { + suite.Require().NoError(err) + + upgradedClientState, err := types.UnpackClientState(res.UpgradedClientState) + suite.Require().NoError(err) + upgradedClientStateCmt, ok := upgradedClientState.(*ibctm.ClientState) + suite.Require().True(ok) + + suite.Require().Equal(expClientState.ZeroCustomFields(), upgradedClientStateCmt) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryUpgradedConsensusStates() { + var ( + req *types.QueryUpgradedConsensusStateRequest + expConsensusState *codectypes.Any + height int64 + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "req is nil", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "no plan", + func() { + req = &types.QueryUpgradedConsensusStateRequest{} + }, + status.Error(codes.NotFound, "upgraded consensus state not found, height 2"), + }, + { + "valid consensus state", + func() { + req = &types.QueryUpgradedConsensusStateRequest{} + + ctx := suite.chainA.GetContext() + lastHeight := types.NewHeight(0, uint64(ctx.BlockHeight())) + height = int64(lastHeight.GetRevisionHeight()) + ctx = ctx.WithBlockHeight(height) + + expConsensusState = types.MustPackConsensusState(suite.consensusState) + bz := types.MustMarshalConsensusState(suite.cdc, suite.consensusState) + err := suite.chainA.GetSimApp().GetIBCKeeper().ClientKeeper.SetUpgradedConsensusState(ctx, height, bz) + suite.Require().NoError(err) + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + + queryServer := keeper.NewQueryServer(suite.chainA.GetSimApp().IBCKeeper.ClientKeeper) + res, err := queryServer.UpgradedConsensusState(suite.chainA.GetContext(), req) + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().True(expConsensusState.Equal(res.UpgradedConsensusState)) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryCreator() { + var ( + req *types.QueryClientCreatorRequest + expRes *types.QueryClientCreatorResponse + path *ibctesting.Path + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "req is nil", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid clientID", + func() { + req = &types.QueryClientCreatorRequest{} + }, + status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"), + }, + { + "client not found", + func() { + req = &types.QueryClientCreatorRequest{ + ClientId: ibctesting.FirstClientID, + } + expRes = &types.QueryClientCreatorResponse{ + Creator: "", + } + }, + nil, + }, + { + "success", + func() { + path.SetupClients() + req = &types.QueryClientCreatorRequest{ + ClientId: path.EndpointA.ClientID, + } + expRes = &types.QueryClientCreatorResponse{ + Creator: suite.chainA.SenderAccount.GetAddress().String(), + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + tc.malleate() + ctx := suite.chainA.GetContext() + queryServer := keeper.NewQueryServer(suite.chainA.GetSimApp().IBCKeeper.ClientKeeper) + res, err := queryServer.ClientCreator(ctx, req) + + if tc.expError == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expRes, res) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryClientParams() { + ctx := suite.chainA.GetContext() + expParams := types.DefaultParams() + queryServer := keeper.NewQueryServer(suite.chainA.GetSimApp().IBCKeeper.ClientKeeper) + res, _ := queryServer.ClientParams(ctx, &types.QueryClientParamsRequest{}) + suite.Require().Equal(&expParams, res.Params) +} + +func (suite *KeeperTestSuite) TestQueryVerifyMembershipProof() { + const wasmClientID = "08-wasm-0" + + var ( + path *ibctesting.Path + req *types.QueryVerifyMembershipRequest + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + channel := path.EndpointB.GetChannel() + bz, err := suite.chainB.Codec.Marshal(&channel) + suite.Require().NoError(err) + + channelProof, proofHeight := path.EndpointB.QueryProof(host.ChannelKey(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)) + + merklePath := commitmenttypes.NewMerklePath(host.ChannelKey(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)) + merklePath, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + req = &types.QueryVerifyMembershipRequest{ + ClientId: path.EndpointA.ClientID, + Proof: channelProof, + ProofHeight: proofHeight, + MerklePath: merklePath, + Value: bz, + } + }, + nil, + }, + { + "req is nil", + func() { + req = nil + }, + errors.New("empty request"), + }, + { + "invalid client ID", + func() { + req = &types.QueryVerifyMembershipRequest{ + ClientId: "//invalid_id", + } + }, + host.ErrInvalidID, + }, + { + "localhost client ID is denied", + func() { + req = &types.QueryVerifyMembershipRequest{ + ClientId: exported.LocalhostClientID, + } + }, + types.ErrInvalidClientType, + }, + { + "solomachine client ID is denied", + func() { + req = &types.QueryVerifyMembershipRequest{ + ClientId: types.FormatClientIdentifier(exported.Solomachine, 1), + } + }, + types.ErrInvalidClientType, + }, + { + "empty proof", + func() { + req = &types.QueryVerifyMembershipRequest{ + ClientId: ibctesting.FirstClientID, + Proof: []byte{}, + } + }, + errors.New("empty proof"), + }, + { + "invalid proof height", + func() { + req = &types.QueryVerifyMembershipRequest{ + ClientId: ibctesting.FirstClientID, + Proof: []byte{0x01}, + ProofHeight: types.ZeroHeight(), + } + }, + errors.New("proof height must be non-zero"), + }, + { + "empty merkle path", + func() { + req = &types.QueryVerifyMembershipRequest{ + ClientId: ibctesting.FirstClientID, + Proof: []byte{0x01}, + ProofHeight: types.NewHeight(1, 100), + } + }, + errors.New("empty merkle path"), + }, + { + "empty value", + func() { + req = &types.QueryVerifyMembershipRequest{ + ClientId: ibctesting.FirstClientID, + Proof: []byte{0x01}, + ProofHeight: types.NewHeight(1, 100), + MerklePath: commitmenttypes.NewMerklePath([]byte("/ibc"), host.ChannelKey(mock.PortID, ibctesting.FirstChannelID)), + } + }, + errors.New("empty value"), + }, + { + "light client module not found", + func() { + req = &types.QueryVerifyMembershipRequest{ + ClientId: wasmClientID, // use a client type that is not registered + Proof: []byte{0x01}, + ProofHeight: types.NewHeight(1, 100), + MerklePath: commitmenttypes.NewMerklePath([]byte("/ibc"), host.ChannelKey(mock.PortID, ibctesting.FirstChannelID)), + Value: []byte{0x01}, + } + }, + errors.New(wasmClientID), + }, + { + "client type not allowed", + func() { + params := types.NewParams("") // disable all clients + suite.chainA.GetSimApp().GetIBCKeeper().ClientKeeper.SetParams(suite.chainA.GetContext(), params) + + req = &types.QueryVerifyMembershipRequest{ + ClientId: path.EndpointA.ClientID, + Proof: []byte{0x01}, + ProofHeight: types.NewHeight(1, 100), + MerklePath: commitmenttypes.NewMerklePath([]byte("/ibc"), host.ChannelKey(mock.PortID, ibctesting.FirstChannelID)), + Value: []byte{0x01}, + } + }, + types.ErrInvalidClientType, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + tc.malleate() + + ctx := suite.chainA.GetContext() + initialGas := ctx.GasMeter().GasConsumed() + queryServer := keeper.NewQueryServer(suite.chainA.GetSimApp().IBCKeeper.ClientKeeper) + res, err := queryServer.VerifyMembership(ctx, req) + + if tc.expError == nil { + suite.Require().NoError(err) + suite.Require().True(res.Success, "failed to verify membership proof") + + gasConsumed := ctx.GasMeter().GasConsumed() + suite.Require().Greater(gasConsumed, initialGas, "gas consumed should be greater than initial gas") + } else { + suite.Require().ErrorContains(err, tc.expError.Error()) + + gasConsumed := ctx.GasMeter().GasConsumed() + suite.Require().GreaterOrEqual(gasConsumed, initialGas, "gas consumed should be greater than or equal to initial gas") + } + }) + } +} diff --git a/modules/core/02-client/keeper/keeper.go b/modules/core/02-client/keeper/keeper.go new file mode 100644 index 0000000..633d7e5 --- /dev/null +++ b/modules/core/02-client/keeper/keeper.go @@ -0,0 +1,517 @@ +package keeper + +import ( + "errors" + "fmt" + "strings" + + corestore "cosmossdk.io/core/store" + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/log" + "cosmossdk.io/store/prefix" + storetypes "cosmossdk.io/store/types" + upgradetypes "cosmossdk.io/x/upgrade/types" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + localhost "github.com/cosmos/ibc-go/v10/modules/light-clients/09-localhost" +) + +// Keeper represents a type that grants read and write permissions to any client +// state information +type Keeper struct { + storeService corestore.KVStoreService + cdc codec.BinaryCodec + router *types.Router + legacySubspace types.ParamSubspace + upgradeKeeper types.UpgradeKeeper +} + +// NewKeeper creates a new NewKeeper instance +func NewKeeper(cdc codec.BinaryCodec, storeService corestore.KVStoreService, legacySubspace types.ParamSubspace, uk types.UpgradeKeeper) *Keeper { + router := types.NewRouter() + localhostModule := localhost.NewLightClientModule(cdc, storeService) + router.AddRoute(exported.Localhost, localhostModule) + + return &Keeper{ + storeService: storeService, + cdc: cdc, + router: router, + legacySubspace: legacySubspace, + upgradeKeeper: uk, + } +} + +// Codec returns the IBC Client module codec. +func (k *Keeper) Codec() codec.BinaryCodec { + return k.cdc +} + +// Logger returns a module-specific logger. +func (Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", "x/"+exported.ModuleName+"/"+types.SubModuleName) +} + +// AddRoute adds a new route to the underlying router. +func (k *Keeper) AddRoute(clientType string, module exported.LightClientModule) { + k.router.AddRoute(clientType, module) +} + +// GetStoreProvider returns the light client store provider. +func (k *Keeper) GetStoreProvider() types.StoreProvider { + return types.NewStoreProvider(k.storeService) +} + +// Route returns the light client module for the given client identifier. +func (k *Keeper) Route(ctx sdk.Context, clientID string) (exported.LightClientModule, error) { + clientType, _, err := types.ParseClientIdentifier(clientID) + if err != nil { + return nil, errorsmod.Wrapf(err, "unable to parse client identifier %s", clientID) + } + + if !k.GetParams(ctx).IsAllowedClient(clientType) { + return nil, errorsmod.Wrapf( + types.ErrInvalidClientType, + "client (%s) type %s is not in the allowed client list", clientID, clientType, + ) + } + + clientModule, found := k.router.GetRoute(clientType) + if !found { + return nil, errorsmod.Wrap(types.ErrRouteNotFound, clientID) + } + + return clientModule, nil +} + +// GenerateClientIdentifier returns the next client identifier. +func (k *Keeper) GenerateClientIdentifier(ctx sdk.Context, clientType string) string { + nextClientSeq := k.GetNextClientSequence(ctx) + clientID := types.FormatClientIdentifier(clientType, nextClientSeq) + + nextClientSeq++ + k.SetNextClientSequence(ctx, nextClientSeq) + return clientID +} + +// GetClientState gets a particular client from the store +func (k *Keeper) GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool) { + store := k.ClientStore(ctx, clientID) + bz := store.Get(host.ClientStateKey()) + if len(bz) == 0 { + return nil, false + } + + clientState := types.MustUnmarshalClientState(k.cdc, bz) + return clientState, true +} + +// SetClientState sets a particular Client to the store +func (k *Keeper) SetClientState(ctx sdk.Context, clientID string, clientState exported.ClientState) { + store := k.ClientStore(ctx, clientID) + store.Set(host.ClientStateKey(), types.MustMarshalClientState(k.cdc, clientState)) +} + +// GetClientCreator returns the creator of a client +func (k *Keeper) GetClientCreator(ctx sdk.Context, clientID string) sdk.AccAddress { + store := k.ClientStore(ctx, clientID) + bz := store.Get(types.CreatorKey()) + if len(bz) == 0 { + return nil + } + return sdk.AccAddress(bz) +} + +// SetClientCreator sets the creator of a client +func (k *Keeper) SetClientCreator(ctx sdk.Context, clientID string, creator sdk.AccAddress) { + store := k.ClientStore(ctx, clientID) + store.Set(types.CreatorKey(), creator.Bytes()) +} + +// DeleteClientCreator deletes the creator of a client +func (k *Keeper) DeleteClientCreator(ctx sdk.Context, clientID string) { + store := k.ClientStore(ctx, clientID) + store.Delete(types.CreatorKey()) +} + +// GetClientConsensusState gets the stored consensus state from a client at a given height. +func (k *Keeper) GetClientConsensusState(ctx sdk.Context, clientID string, height exported.Height) (exported.ConsensusState, bool) { + store := k.ClientStore(ctx, clientID) + bz := store.Get(host.ConsensusStateKey(height)) + if len(bz) == 0 { + return nil, false + } + + consensusState := types.MustUnmarshalConsensusState(k.cdc, bz) + return consensusState, true +} + +// SetClientConsensusState sets a ConsensusState to a particular client at the given +// height +func (k *Keeper) SetClientConsensusState(ctx sdk.Context, clientID string, height exported.Height, consensusState exported.ConsensusState) { + store := k.ClientStore(ctx, clientID) + store.Set(host.ConsensusStateKey(height), types.MustMarshalConsensusState(k.cdc, consensusState)) +} + +// GetNextClientSequence gets the next client sequence from the store. +func (k *Keeper) GetNextClientSequence(ctx sdk.Context) uint64 { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get([]byte(types.KeyNextClientSequence)) + if err != nil { + panic(err) + } + if len(bz) == 0 { + panic(errors.New("next client sequence is nil")) + } + + return sdk.BigEndianToUint64(bz) +} + +// SetNextClientSequence sets the next client sequence to the store. +func (k *Keeper) SetNextClientSequence(ctx sdk.Context, sequence uint64) { + store := k.storeService.OpenKVStore(ctx) + bz := sdk.Uint64ToBigEndian(sequence) + if err := store.Set([]byte(types.KeyNextClientSequence), bz); err != nil { + panic(err) + } +} + +// IterateConsensusStates provides an iterator over all stored consensus states. +// objects. For each State object, cb will be called. If the cb returns true, +// the iterator will close and stop. +func (k *Keeper) IterateConsensusStates(ctx sdk.Context, cb func(clientID string, cs types.ConsensusStateWithHeight) bool) { + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + iterator := storetypes.KVStorePrefixIterator(store, host.KeyClientStorePrefix) + + defer sdk.LogDeferred(k.Logger(ctx), func() error { return iterator.Close() }) + for ; iterator.Valid(); iterator.Next() { + keySplit := strings.Split(string(iterator.Key()), "/") + // consensus key is in the format "clients//consensusStates/" + if len(keySplit) != 4 || keySplit[2] != string(host.KeyConsensusStatePrefix) { + continue + } + clientID := keySplit[1] + height := types.MustParseHeight(keySplit[3]) + consensusState := types.MustUnmarshalConsensusState(k.cdc, iterator.Value()) + + consensusStateWithHeight := types.NewConsensusStateWithHeight(height, consensusState) + + if cb(clientID, consensusStateWithHeight) { + break + } + } +} + +// iterateMetadata provides an iterator over all stored metadata keys in the client store. +// For each metadata object, it will perform a callback. +func (k *Keeper) iterateMetadata(ctx sdk.Context, cb func(clientID string, key, value []byte) bool) { + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + iterator := storetypes.KVStorePrefixIterator(store, host.KeyClientStorePrefix) + + defer sdk.LogDeferred(k.Logger(ctx), func() error { return iterator.Close() }) + for ; iterator.Valid(); iterator.Next() { + split := strings.Split(string(iterator.Key()), "/") + if len(split) == 3 && split[2] == string(host.KeyClientState) { + // skip client state keys + continue + } + + if len(split) == 4 && split[2] == string(host.KeyConsensusStatePrefix) { + // skip consensus state keys + continue + } + + if split[0] != string(host.KeyClientStorePrefix) { + panic(errorsmod.Wrapf(host.ErrInvalidPath, "path does not begin with client store prefix: expected %s, got %s", host.KeyClientStorePrefix, split[0])) + } + if strings.TrimSpace(split[1]) == "" { + panic(errorsmod.Wrap(host.ErrInvalidPath, "clientID is empty")) + } + + clientID := split[1] + + key := []byte(strings.Join(split[2:], "/")) + + if cb(clientID, key, iterator.Value()) { + break + } + } +} + +// GetAllGenesisClients returns all the clients in state with their client ids returned as IdentifiedClientState +func (k *Keeper) GetAllGenesisClients(ctx sdk.Context) types.IdentifiedClientStates { + var genClients types.IdentifiedClientStates + k.IterateClientStates(ctx, nil, func(clientID string, cs exported.ClientState) bool { + genClients = append(genClients, types.NewIdentifiedClientState(clientID, cs)) + return false + }) + + return genClients.Sort() +} + +// GetAllClientMetadata will take a list of IdentifiedClientState and return a list +// of IdentifiedGenesisMetadata necessary for exporting and importing client metadata +// into the client store. +func (k *Keeper) GetAllClientMetadata(ctx sdk.Context, genClients []types.IdentifiedClientState) ([]types.IdentifiedGenesisMetadata, error) { + metadataMap := make(map[string][]types.GenesisMetadata) + k.iterateMetadata(ctx, func(clientID string, key, value []byte) bool { + metadataMap[clientID] = append(metadataMap[clientID], types.NewGenesisMetadata(key, value)) + return false + }) + + genMetadata := make([]types.IdentifiedGenesisMetadata, 0) + for _, ic := range genClients { + metadata := metadataMap[ic.ClientId] + if len(metadata) != 0 { + genMetadata = append(genMetadata, types.NewIdentifiedGenesisMetadata( + ic.ClientId, + metadata, + )) + } + } + + return genMetadata, nil +} + +// SetAllClientMetadata takes a list of IdentifiedGenesisMetadata and stores all of the metadata in the client store at the appropriate paths. +func (k *Keeper) SetAllClientMetadata(ctx sdk.Context, genMetadata []types.IdentifiedGenesisMetadata) { + for _, igm := range genMetadata { + // create client store + store := k.ClientStore(ctx, igm.ClientId) + // set all metadata kv pairs in client store + for _, md := range igm.ClientMetadata { + store.Set(md.GetKey(), md.GetValue()) + } + } +} + +// GetAllConsensusStates returns all stored client consensus states. +func (k *Keeper) GetAllConsensusStates(ctx sdk.Context) types.ClientsConsensusStates { + clientConsStates := make(types.ClientsConsensusStates, 0) + mapClientIDToConsStateIdx := make(map[string]int) + + k.IterateConsensusStates(ctx, func(clientID string, cs types.ConsensusStateWithHeight) bool { + idx, ok := mapClientIDToConsStateIdx[clientID] + if ok { + clientConsStates[idx].ConsensusStates = append(clientConsStates[idx].ConsensusStates, cs) + return false + } + + clientConsState := types.ClientConsensusStates{ + ClientId: clientID, + ConsensusStates: []types.ConsensusStateWithHeight{cs}, + } + + clientConsStates = append(clientConsStates, clientConsState) + mapClientIDToConsStateIdx[clientID] = len(clientConsStates) - 1 + return false + }) + + return clientConsStates.Sort() +} + +// HasClientConsensusState returns if keeper has a ConsensusState for a particular +// client at the given height +func (k *Keeper) HasClientConsensusState(ctx sdk.Context, clientID string, height exported.Height) bool { + store := k.ClientStore(ctx, clientID) + return store.Has(host.ConsensusStateKey(height)) +} + +// GetLatestClientConsensusState gets the latest ConsensusState stored for a given client +func (k *Keeper) GetLatestClientConsensusState(ctx sdk.Context, clientID string) (exported.ConsensusState, bool) { + clientModule, err := k.Route(ctx, clientID) + if err != nil { + return nil, false + } + + return k.GetClientConsensusState(ctx, clientID, clientModule.LatestHeight(ctx, clientID)) +} + +// VerifyMembership retrieves the light client module for the clientID and verifies the proof of the existence of a key-value pair at a specified height. +func (k *Keeper) VerifyMembership(ctx sdk.Context, clientID string, height exported.Height, delayTimePeriod uint64, delayBlockPeriod uint64, proof []byte, path exported.Path, value []byte) error { + clientModule, err := k.Route(ctx, clientID) + if err != nil { + return err + } + + if status := clientModule.Status(ctx, clientID); status != exported.Active { + return errorsmod.Wrapf(types.ErrClientNotActive, "cannot call verify membership on client (%s) with status %s", clientID, status) + } + + return clientModule.VerifyMembership(ctx, clientID, height, delayTimePeriod, delayBlockPeriod, proof, path, value) +} + +// VerifyNonMembership retrieves the light client module for the clientID and verifies the absence of a given key at a specified height. +func (k *Keeper) VerifyNonMembership(ctx sdk.Context, clientID string, height exported.Height, delayTimePeriod uint64, delayBlockPeriod uint64, proof []byte, path exported.Path) error { + clientModule, err := k.Route(ctx, clientID) + if err != nil { + return err + } + + if status := clientModule.Status(ctx, clientID); status != exported.Active { + return errorsmod.Wrapf(types.ErrClientNotActive, "cannot call verify non membership on client (%s) with status %s", clientID, status) + } + + return clientModule.VerifyNonMembership(ctx, clientID, height, delayTimePeriod, delayBlockPeriod, proof, path) +} + +// GetUpgradePlan executes the upgrade keeper GetUpgradePlan function. +func (k *Keeper) GetUpgradePlan(ctx sdk.Context) (upgradetypes.Plan, error) { + return k.upgradeKeeper.GetUpgradePlan(ctx) +} + +// GetUpgradedClient executes the upgrade keeper GetUpgradeClient function. +func (k *Keeper) GetUpgradedClient(ctx sdk.Context, planHeight int64) ([]byte, error) { + return k.upgradeKeeper.GetUpgradedClient(ctx, planHeight) +} + +// GetUpgradedConsensusState returns the upgraded consensus state +func (k *Keeper) GetUpgradedConsensusState(ctx sdk.Context, planHeight int64) ([]byte, error) { + return k.upgradeKeeper.GetUpgradedConsensusState(ctx, planHeight) +} + +// SetUpgradedConsensusState executes the upgrade keeper SetUpgradedConsensusState function. +func (k *Keeper) SetUpgradedConsensusState(ctx sdk.Context, planHeight int64, bz []byte) error { + return k.upgradeKeeper.SetUpgradedConsensusState(ctx, planHeight, bz) +} + +// IterateClientStates provides an iterator over all stored ibc ClientState +// objects using the provided store prefix. For each ClientState object, cb will be called. If the cb returns true, +// the iterator will close and stop. +func (k *Keeper) IterateClientStates(ctx sdk.Context, storePrefix []byte, cb func(clientID string, cs exported.ClientState) bool) { + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + iterator := storetypes.KVStorePrefixIterator(store, host.PrefixedClientStoreKey(storePrefix)) + + defer sdk.LogDeferred(k.Logger(ctx), func() error { return iterator.Close() }) + for ; iterator.Valid(); iterator.Next() { + path := string(iterator.Key()) + if !strings.Contains(path, host.KeyClientState) { + // skip non client state keys + continue + } + + clientID := host.MustParseClientStatePath(path) + clientState := types.MustUnmarshalClientState(k.cdc, iterator.Value()) + + if cb(clientID, clientState) { + break + } + } +} + +// GetAllClients returns all stored light client State objects. +func (k *Keeper) GetAllClients(ctx sdk.Context) []exported.ClientState { + var states []exported.ClientState + k.IterateClientStates(ctx, nil, func(_ string, state exported.ClientState) bool { + states = append(states, state) + return false + }) + + return states +} + +// ClientStore returns isolated prefix store for each client so they can read/write in separate +// namespace without being able to read/write other client's data +func (k *Keeper) ClientStore(ctx sdk.Context, clientID string) storetypes.KVStore { + clientPrefix := fmt.Appendf(nil, "%s/%s/", host.KeyClientStorePrefix, clientID) + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + return prefix.NewStore(store, clientPrefix) +} + +// GetClientStatus returns the status for a client state given a client identifier. If the client type is not in the allowed +// clients param field, Unauthorized is returned, otherwise the client state status is returned. +func (k *Keeper) GetClientStatus(ctx sdk.Context, clientID string) exported.Status { + clientModule, err := k.Route(ctx, clientID) + if err != nil { + return exported.Unauthorized + } + + return clientModule.Status(ctx, clientID) +} + +// GetClientLatestHeight returns the latest height of a client state for a given client identifier. If the client type is not in the allowed +// clients param field, a zero value height is returned, otherwise the client state latest height is returned. +func (k *Keeper) GetClientLatestHeight(ctx sdk.Context, clientID string) types.Height { + clientModule, err := k.Route(ctx, clientID) + if err != nil { + return types.ZeroHeight() + } + + var latestHeight types.Height + latestHeight, ok := clientModule.LatestHeight(ctx, clientID).(types.Height) + if !ok { + panic(fmt.Errorf("cannot convert %T to %T", clientModule.LatestHeight, latestHeight)) + } + return latestHeight +} + +// GetClientTimestampAtHeight returns the timestamp in nanoseconds of the consensus state at the given height. +func (k *Keeper) GetClientTimestampAtHeight(ctx sdk.Context, clientID string, height exported.Height) (uint64, error) { + clientModule, err := k.Route(ctx, clientID) + if err != nil { + return 0, err + } + + return clientModule.TimestampAtHeight(ctx, clientID, height) +} + +// GetParams returns the total set of ibc-client parameters. +func (k *Keeper) GetParams(ctx sdk.Context) types.Params { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get([]byte(types.ParamsKey)) + if err != nil { + panic(err) + } + if bz == nil { // only panic on unset params and not on empty params + panic(errors.New("client params are not set in store")) + } + + var params types.Params + k.cdc.MustUnmarshal(bz, ¶ms) + return params +} + +// SetParams sets the total set of ibc-client parameters. +func (k *Keeper) SetParams(ctx sdk.Context, params types.Params) { + store := k.storeService.OpenKVStore(ctx) + bz := k.cdc.MustMarshal(¶ms) + if err := store.Set([]byte(types.ParamsKey), bz); err != nil { + panic(err) + } +} + +// ScheduleIBCSoftwareUpgrade schedules an upgrade for the IBC client. +func (k *Keeper) ScheduleIBCSoftwareUpgrade(ctx sdk.Context, plan upgradetypes.Plan, upgradedClientState exported.ClientState) error { + // zero out any custom fields before setting + cs, ok := upgradedClientState.(*ibctm.ClientState) + if !ok { + return errorsmod.Wrapf(types.ErrInvalidClientType, "expected: %T, got: %T", &ibctm.ClientState{}, upgradedClientState) + } + + cs = cs.ZeroCustomFields() + bz, err := types.MarshalClientState(k.cdc, cs) + if err != nil { + return errorsmod.Wrap(err, "could not marshal UpgradedClientState") + } + + if err := k.upgradeKeeper.ScheduleUpgrade(ctx, plan); err != nil { + return err + } + + // sets the new upgraded client last height committed on this chain at plan.Height, + // since the chain will panic at plan.Height and new chain will resume at plan.Height + if err = k.upgradeKeeper.SetUpgradedClient(ctx, plan.Height, bz); err != nil { + return err + } + + // emitting an event for scheduling an upgrade plan + emitScheduleIBCSoftwareUpgradeEvent(ctx, plan.Name, plan.Height) + + return nil +} diff --git a/modules/core/02-client/keeper/keeper_test.go b/modules/core/02-client/keeper/keeper_test.go new file mode 100644 index 0000000..1b45fc8 --- /dev/null +++ b/modules/core/02-client/keeper/keeper_test.go @@ -0,0 +1,781 @@ +package keeper_test + +import ( + "errors" + "fmt" + "math/rand" + "testing" + "time" + + testifysuite "github.com/stretchr/testify/suite" + + sdkmath "cosmossdk.io/math" + upgradetypes "cosmossdk.io/x/upgrade/types" + + "github.com/cosmos/cosmos-sdk/codec" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + cmtbytes "github.com/cometbft/cometbft/libs/bytes" + cmttypes "github.com/cometbft/cometbft/types" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/keeper" + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + "github.com/cosmos/ibc-go/v10/testing/simapp" +) + +const ( + testChainID = "gaiahub-0" + testChainIDRevision1 = "gaiahub-1" + + testClientID = "tendermint-0" + testClientID2 = "tendermint-1" + testClientID3 = "tendermint-2" + + trustingPeriod time.Duration = time.Hour * 24 * 7 * 2 + ubdPeriod time.Duration = time.Hour * 24 * 7 * 3 + maxClockDrift time.Duration = time.Second * 10 +) + +var testClientHeight = types.NewHeight(0, 5) + +type KeeperTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain + + cdc codec.Codec + ctx sdk.Context + keeper *keeper.Keeper + consensusState *ibctm.ConsensusState + valSet *cmttypes.ValidatorSet + valSetHash cmtbytes.HexBytes + privVal cmttypes.PrivValidator + now time.Time + past time.Time + solomachine *ibctesting.Solomachine + + signers map[string]cmttypes.PrivValidator +} + +func (suite *KeeperTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) + + isCheckTx := false + suite.now = time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC) + suite.past = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) + app := simapp.Setup(suite.T(), isCheckTx) + + suite.cdc = app.AppCodec() + suite.ctx = app.NewContext(isCheckTx) + suite.keeper = app.IBCKeeper.ClientKeeper + suite.privVal = cmttypes.NewMockPV() + pubKey, err := suite.privVal.GetPubKey() + suite.Require().NoError(err) + + validator := cmttypes.NewValidator(pubKey, 1) + suite.valSet = cmttypes.NewValidatorSet([]*cmttypes.Validator{validator}) + suite.valSetHash = suite.valSet.Hash() + + suite.signers = make(map[string]cmttypes.PrivValidator, 1) + suite.signers[validator.Address.String()] = suite.privVal + + suite.consensusState = ibctm.NewConsensusState(suite.now, commitmenttypes.NewMerkleRoot([]byte("hash")), suite.valSetHash) + + var validators stakingtypes.Validators + for i := 1; i < 11; i++ { + privVal := cmttypes.NewMockPV() + tmPk, err := privVal.GetPubKey() + suite.Require().NoError(err) + pk, err := cryptocodec.FromCmtPubKeyInterface(tmPk) + suite.Require().NoError(err) + val, err := stakingtypes.NewValidator(pk.Address().String(), pk, stakingtypes.Description{}) + suite.Require().NoError(err) + + val.Status = stakingtypes.Bonded + val.Tokens = sdkmath.NewInt(rand.Int63()) + validators.Validators = append(validators.Validators, val) + + hi := stakingtypes.NewHistoricalInfo(suite.ctx.BlockHeader(), validators, sdk.DefaultPowerReduction) + err = app.StakingKeeper.SetHistoricalInfo(suite.ctx, int64(i), &hi) + suite.Require().NoError(err) + } + + suite.solomachine = ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachinesingle", "testing", 1) +} + +func TestKeeperTestSuite(t *testing.T) { + testifysuite.Run(t, new(KeeperTestSuite)) +} + +func (suite *KeeperTestSuite) TestSetClientState() { + clientState := ibctm.NewClientState(testChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.ZeroHeight(), commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath) + suite.keeper.SetClientState(suite.ctx, testClientID, clientState) + + retrievedState, found := suite.keeper.GetClientState(suite.ctx, testClientID) + suite.Require().True(found, "GetClientState failed") + suite.Require().Equal(clientState, retrievedState, "Client states are not equal") +} + +func (suite *KeeperTestSuite) TestSetClientCreator() { + creator := suite.chainA.SenderAccount.GetAddress() + suite.keeper.SetClientCreator(suite.ctx, testClientID, creator) + getCreator := suite.keeper.GetClientCreator(suite.ctx, testClientID) + suite.Require().Equal(creator, getCreator) + suite.keeper.DeleteClientCreator(suite.ctx, testClientID) + getCreator = suite.keeper.GetClientCreator(suite.ctx, testClientID) + suite.Require().Equal(sdk.AccAddress(nil), getCreator) +} + +func (suite *KeeperTestSuite) TestSetClientConsensusState() { + suite.keeper.SetClientConsensusState(suite.ctx, testClientID, testClientHeight, suite.consensusState) + + retrievedConsState, found := suite.keeper.GetClientConsensusState(suite.ctx, testClientID, testClientHeight) + suite.Require().True(found, "GetConsensusState failed") + + tmConsState, ok := retrievedConsState.(*ibctm.ConsensusState) + suite.Require().True(ok) + suite.Require().Equal(suite.consensusState, tmConsState, "ConsensusState not stored correctly") +} + +func (suite *KeeperTestSuite) TestGetAllGenesisClients() { + clientIDs := []string{ + testClientID2, testClientID3, testClientID, + } + expClients := []exported.ClientState{ + ibctm.NewClientState(testChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.ZeroHeight(), commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), + ibctm.NewClientState(testChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.ZeroHeight(), commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), + ibctm.NewClientState(testChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.ZeroHeight(), commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), + } + + expGenClients := make(types.IdentifiedClientStates, len(expClients)) + + for i := range expClients { + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientIDs[i], expClients[i]) + expGenClients[i] = types.NewIdentifiedClientState(clientIDs[i], expClients[i]) + } + + genClients := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetAllGenesisClients(suite.chainA.GetContext()) + + suite.Require().Equal(expGenClients.Sort(), genClients) +} + +func (suite *KeeperTestSuite) TestGetAllGenesisMetadata() { + clientA, clientB := "07-tendermint-1", "clientB" + + // create some starting state + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientA, &ibctm.ClientState{}) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), clientA, types.NewHeight(0, 1), &ibctm.ConsensusState{}) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), clientA, types.NewHeight(0, 2), &ibctm.ConsensusState{}) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), clientA, types.NewHeight(0, 3), &ibctm.ConsensusState{}) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), clientA, types.NewHeight(2, 300), &ibctm.ConsensusState{}) + + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientB, &ibctm.ClientState{}) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), clientB, types.NewHeight(1, 100), &ibctm.ConsensusState{}) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), clientB, types.NewHeight(2, 300), &ibctm.ConsensusState{}) + + // NOTE: correct ordering of expected value is required + // Ordering is typically determined by the lexographic ordering of the height passed into each key. + expectedGenMetadata := []types.IdentifiedGenesisMetadata{ + types.NewIdentifiedGenesisMetadata( + clientA, + []types.GenesisMetadata{ + types.NewGenesisMetadata(fmt.Appendf(nil, "%s/%s", host.KeyClientState, "clientMetadata"), []byte("value")), + types.NewGenesisMetadata(ibctm.ProcessedTimeKey(types.NewHeight(0, 1)), []byte("foo")), + types.NewGenesisMetadata(ibctm.ProcessedTimeKey(types.NewHeight(0, 2)), []byte("bar")), + types.NewGenesisMetadata(ibctm.ProcessedTimeKey(types.NewHeight(0, 3)), []byte("baz")), + types.NewGenesisMetadata(ibctm.ProcessedHeightKey(types.NewHeight(2, 300)), []byte(types.NewHeight(1, 100).String())), + }, + ), + types.NewIdentifiedGenesisMetadata( + clientB, + []types.GenesisMetadata{ + types.NewGenesisMetadata(ibctm.ProcessedTimeKey(types.NewHeight(1, 100)), []byte("val1")), + types.NewGenesisMetadata(ibctm.ProcessedHeightKey(types.NewHeight(2, 300)), []byte(types.NewHeight(1, 100).String())), + types.NewGenesisMetadata(ibctm.ProcessedTimeKey(types.NewHeight(2, 300)), []byte("val2")), + types.NewGenesisMetadata([]byte("key"), []byte("value")), + }, + ), + } + + genClients := []types.IdentifiedClientState{ + types.NewIdentifiedClientState(clientA, &ibctm.ClientState{}), types.NewIdentifiedClientState(clientB, &ibctm.ClientState{}), + } + + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetAllClientMetadata(suite.chainA.GetContext(), expectedGenMetadata) + + actualGenMetadata, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetAllClientMetadata(suite.chainA.GetContext(), genClients) + suite.Require().NoError(err, "get client metadata returned error unexpectedly") + suite.Require().Equal(expectedGenMetadata, actualGenMetadata, "retrieved metadata is unexpected") + + // set invalid key in client store which will cause panic during iteration + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), "") + clientStore.Set([]byte("key"), []byte("val")) + suite.Require().Panics(func() { + suite.chainA.App.GetIBCKeeper().ClientKeeper.GetAllClientMetadata(suite.chainA.GetContext(), genClients) //nolint:errcheck // we expect a panic + }) +} + +// 2 clients in total are created on chainA. The first client is updated so it contains an initial consensus state +// and a consensus state at the update height. +func (suite *KeeperTestSuite) TestGetAllConsensusStates() { + path1 := ibctesting.NewPath(suite.chainA, suite.chainB) + path1.SetupClients() + + expConsensusHeight0 := path1.EndpointA.GetClientLatestHeight() + consensusState0, ok := suite.chainA.GetConsensusState(path1.EndpointA.ClientID, expConsensusHeight0) + suite.Require().True(ok) + + // update client to create a second consensus state + err := path1.EndpointA.UpdateClient() + suite.Require().NoError(err) + + expConsensusHeight1 := path1.EndpointA.GetClientLatestHeight() + suite.Require().True(expConsensusHeight1.GT(expConsensusHeight0)) + consensusState1, ok := suite.chainA.GetConsensusState(path1.EndpointA.ClientID, expConsensusHeight1) + suite.Require().True(ok) + + expConsensus := []exported.ConsensusState{ + consensusState0, + consensusState1, + } + + // create second client on chainA + path2 := ibctesting.NewPath(suite.chainA, suite.chainB) + path2.SetupClients() + + expConsensusHeight2 := path2.EndpointA.GetClientLatestHeight() + consensusState2, ok := suite.chainA.GetConsensusState(path2.EndpointA.ClientID, expConsensusHeight2) + suite.Require().True(ok) + + expConsensus2 := []exported.ConsensusState{consensusState2} + + expConsensusStates := types.ClientsConsensusStates{ + types.NewClientConsensusStates(path1.EndpointA.ClientID, []types.ConsensusStateWithHeight{ + types.NewConsensusStateWithHeight(expConsensusHeight0.(types.Height), expConsensus[0]), + types.NewConsensusStateWithHeight(expConsensusHeight1.(types.Height), expConsensus[1]), + }), + types.NewClientConsensusStates(path2.EndpointA.ClientID, []types.ConsensusStateWithHeight{ + types.NewConsensusStateWithHeight(expConsensusHeight2.(types.Height), expConsensus2[0]), + }), + }.Sort() + + consStates := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetAllConsensusStates(suite.chainA.GetContext()) + suite.Require().Equal(expConsensusStates, consStates, "%s \n\n%s", expConsensusStates, consStates) +} + +func (suite *KeeperTestSuite) TestIterateClientStates() { + paths := []*ibctesting.Path{ + ibctesting.NewPath(suite.chainA, suite.chainB), + ibctesting.NewPath(suite.chainA, suite.chainB), + ibctesting.NewPath(suite.chainA, suite.chainB), + } + + solomachines := []*ibctesting.Solomachine{ + ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, ibctesting.DefaultSolomachineClientID, "testing", 1), + ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "06-solomachine-1", "testing", 4), + } + + var ( + expTMClientIDs = make([]string, len(paths)) + expSMClientIDs = make([]string, len(solomachines)) + ) + + // create tendermint clients + for i, path := range paths { + path.SetupClients() + expTMClientIDs[i] = path.EndpointA.ClientID + } + + // create solomachine clients + for i, sm := range solomachines { + expSMClientIDs[i] = sm.CreateClient(suite.chainA) + } + + testCases := []struct { + name string + prefix []byte + expClientIDs func() []string + }{ + { + "all clientIDs", + nil, + func() []string { + return append(expSMClientIDs, expTMClientIDs...) + }, + }, + { + "tendermint clientIDs", + []byte(exported.Tendermint), + func() []string { + return expTMClientIDs + }, + }, + { + "solo machine clientIDs", + []byte(exported.Solomachine), + func() []string { + return expSMClientIDs + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + var clientIDs []string + suite.chainA.GetSimApp().IBCKeeper.ClientKeeper.IterateClientStates(suite.chainA.GetContext(), tc.prefix, func(clientID string, _ exported.ClientState) bool { + clientIDs = append(clientIDs, clientID) + return false + }) + + suite.Require().ElementsMatch(tc.expClientIDs(), clientIDs) + }) + } +} + +func (suite *KeeperTestSuite) TestGetClientLatestHeight() { + var path *ibctesting.Path + + cases := []struct { + name string + malleate func() + expPass bool + }{ + { + "success", + func() {}, + true, + }, + { + "invalid client type", + func() { + path.EndpointA.ClientID = ibctesting.InvalidID + }, + false, + }, + { + "client type is not allowed", func() { + params := types.NewParams(exported.Localhost) + suite.chainA.GetSimApp().GetIBCKeeper().ClientKeeper.SetParams(suite.chainA.GetContext(), params) + }, + false, + }, + { + "client type is not registered on router", func() { + path.EndpointA.ClientID = types.FormatClientIdentifier("08-wasm", 0) + }, + false, + }, + } + + for _, tc := range cases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupConnections() + + tc.malleate() + + height := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientLatestHeight(suite.chainA.GetContext(), path.EndpointA.ClientID) + + if tc.expPass { + suite.Require().Equal(suite.chainB.LatestCommittedHeader.GetHeight().(types.Height), height) + } else { + suite.Require().Equal(types.ZeroHeight(), height) + } + }) + } +} + +func (suite *KeeperTestSuite) TestGetTimestampAtHeight() { + var ( + height exported.Height + path *ibctesting.Path + ) + + cases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() {}, + nil, + }, + { + "invalid client type", + func() { + path.EndpointA.ClientID = ibctesting.InvalidID + }, + host.ErrInvalidID, + }, + { + "client type is not allowed", func() { + params := types.NewParams(exported.Localhost) + suite.chainA.GetSimApp().GetIBCKeeper().ClientKeeper.SetParams(suite.chainA.GetContext(), params) + }, + types.ErrInvalidClientType, + }, + { + "client type is not registered on router", func() { + path.EndpointA.ClientID = types.FormatClientIdentifier("08-wasm", 0) + }, + types.ErrRouteNotFound, + }, + { + "client state not found", func() { + path.EndpointA.ClientID = types.FormatClientIdentifier(exported.Tendermint, 100) + }, + types.ErrClientNotFound, + }, + { + "consensus state not found", func() { + height = suite.chainB.LatestCommittedHeader.GetHeight().Increment() + }, + types.ErrConsensusStateNotFound, + }, + } + + for _, tc := range cases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupConnections() + + height = suite.chainB.LatestCommittedHeader.GetHeight() + + tc.malleate() + + actualTimestamp, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientTimestampAtHeight(suite.chainA.GetContext(), path.EndpointA.ClientID, height) + + if tc.expError == nil { + suite.Require().NoError(err) + suite.Require().Equal(uint64(suite.chainB.LatestCommittedHeader.GetTime().UnixNano()), actualTimestamp) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +func (suite *KeeperTestSuite) TestVerifyMembership() { + var path *ibctesting.Path + + cases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() {}, + nil, + }, + { + "invalid client id", + func() { + path.EndpointA.ClientID = "" + }, + host.ErrInvalidID, + }, + { + "failure: client is frozen", + func() { + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + clientState.FrozenHeight = types.NewHeight(0, 1) + path.EndpointA.SetClientState(clientState) + }, + types.ErrClientNotActive, + }, + } + + for _, tc := range cases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + // create default proof, merklePath, and value which passes + key := host.FullClientStateKey(path.EndpointB.ClientID) + merklePath := commitmenttypes.NewMerklePath(key) + merklePrefixPath, err := commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight := suite.chainB.QueryProof(key) + + clientState, ok := path.EndpointB.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + value, err := suite.chainB.Codec.MarshalInterface(clientState) + suite.Require().NoError(err) + + tc.malleate() + + err = suite.chainA.App.GetIBCKeeper().ClientKeeper.VerifyMembership(suite.chainA.GetContext(), path.EndpointA.ClientID, proofHeight, 0, 0, proof, merklePrefixPath, value) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +func (suite *KeeperTestSuite) TestVerifyNonMembership() { + var path *ibctesting.Path + + cases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() {}, + nil, + }, + { + "invalid client id", + func() { + path.EndpointA.ClientID = "" + }, + host.ErrInvalidID, + }, + { + "failure: client is frozen", + func() { + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + clientState.FrozenHeight = types.NewHeight(0, 1) + path.EndpointA.SetClientState(clientState) + }, + types.ErrClientNotActive, + }, + } + + for _, tc := range cases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + // create default proof, merklePath, and value which passes + key := host.FullClientStateKey("invalid-client-id") + + merklePath := commitmenttypes.NewMerklePath(key) + merklePrefixPath, err := commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight := suite.chainB.QueryProof(key) + + tc.malleate() + + err = suite.chainA.App.GetIBCKeeper().ClientKeeper.VerifyNonMembership(suite.chainA.GetContext(), path.EndpointA.ClientID, proofHeight, 0, 0, proof, merklePrefixPath) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +// TestDefaultSetParams tests the default params set are what is expected +func (suite *KeeperTestSuite) TestDefaultSetParams() { + expParams := types.DefaultParams() + + clientKeeper := suite.chainA.App.GetIBCKeeper().ClientKeeper + params := clientKeeper.GetParams(suite.chainA.GetContext()) + + suite.Require().Equal(expParams, params) + suite.Require().Equal(expParams.AllowedClients, clientKeeper.GetParams(suite.chainA.GetContext()).AllowedClients) +} + +// TestParams tests that Param setting and retrieval works properly +func (suite *KeeperTestSuite) TestParams() { + testCases := []struct { + name string + input types.Params + expErr error + }{ + {"success: set default params", types.DefaultParams(), nil}, + {"success: empty allowedClients", types.NewParams(), nil}, + {"success: subset of allowedClients", types.NewParams(exported.Tendermint, exported.Localhost), nil}, + {"failure: contains a single empty string value as allowedClient", types.NewParams(exported.Localhost, ""), errors.New("client type 1 cannot be blank")}, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + ctx := suite.chainA.GetContext() + err := tc.input.Validate() + suite.chainA.GetSimApp().IBCKeeper.ClientKeeper.SetParams(ctx, tc.input) + if tc.expErr == nil { + suite.Require().NoError(err) + expected := tc.input + p := suite.chainA.GetSimApp().IBCKeeper.ClientKeeper.GetParams(ctx) + suite.Require().Equal(expected, p) + } else { + suite.Require().Error(err) + suite.Require().Equal(err.Error(), tc.expErr.Error()) + } + }) + } +} + +// TestUnsetParams tests that trying to get params that are not set panics. +func (suite *KeeperTestSuite) TestUnsetParams() { + suite.SetupTest() + ctx := suite.chainA.GetContext() + store := ctx.KVStore(suite.chainA.GetSimApp().GetKey(exported.StoreKey)) + store.Delete([]byte(types.ParamsKey)) + + suite.Require().Panics(func() { + suite.chainA.GetSimApp().IBCKeeper.ClientKeeper.GetParams(ctx) + }) +} + +// TestIBCSoftwareUpgrade tests that an IBC client upgrade has been properly scheduled +func (suite *KeeperTestSuite) TestIBCSoftwareUpgrade() { + var ( + upgradedClientState *ibctm.ClientState + oldPlan, plan upgradetypes.Plan + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "valid upgrade proposal", + func() {}, + nil, + }, + { + "valid upgrade proposal with previous IBC state", func() { + oldPlan = upgradetypes.Plan{ + Name: "upgrade IBC clients", + Height: 100, + } + }, + nil, + }, + { + "fail: scheduling upgrade with plan height 0", + func() { + plan.Height = 0 + }, + sdkerrors.ErrInvalidRequest, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + oldPlan.Height = 0 // reset + + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + tmClientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + upgradedClientState = tmClientState.ZeroCustomFields() + + // use height 1000 to distinguish from old plan + plan = upgradetypes.Plan{ + Name: "upgrade IBC clients", + Height: 1000, + } + + tc.malleate() + + // set the old plan if it is not empty + if oldPlan.Height != 0 { + // set upgrade plan in the upgrade store + store := suite.chainA.GetContext().KVStore(suite.chainA.GetSimApp().GetKey(upgradetypes.StoreKey)) + bz := suite.chainA.App.AppCodec().MustMarshal(&oldPlan) + store.Set(upgradetypes.PlanKey(), bz) + + bz, err := types.MarshalClientState(suite.chainA.App.AppCodec(), upgradedClientState) + suite.Require().NoError(err) + + suite.Require().NoError(suite.chainA.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainA.GetContext(), oldPlan.Height, bz)) + } + + ctx := suite.chainA.GetContext() + err := suite.chainA.App.GetIBCKeeper().ClientKeeper.ScheduleIBCSoftwareUpgrade(ctx, plan, upgradedClientState) + + if tc.expError == nil { + suite.Require().NoError(err) + + // check that the correct plan is returned + storedPlan, err := suite.chainA.GetSimApp().UpgradeKeeper.GetUpgradePlan(suite.chainA.GetContext()) + suite.Require().NoError(err) + suite.Require().Equal(plan, storedPlan) + + // check that old upgraded client state is cleared + cs, err := suite.chainA.GetSimApp().UpgradeKeeper.GetUpgradedClient(suite.chainA.GetContext(), oldPlan.Height) + suite.Require().ErrorIs(err, upgradetypes.ErrNoUpgradedClientFound) + suite.Require().Empty(cs) + + // check that client state was set + storedClientState, err := suite.chainA.GetSimApp().UpgradeKeeper.GetUpgradedClient(suite.chainA.GetContext(), plan.Height) + suite.Require().NoError(err) + clientState, err := types.UnmarshalClientState(suite.chainA.App.AppCodec(), storedClientState) + suite.Require().NoError(err) + suite.Require().Equal(upgradedClientState, clientState) + + expectedEvents := sdk.Events{ + sdk.NewEvent( + types.EventTypeScheduleIBCSoftwareUpgrade, + sdk.NewAttribute(types.AttributeKeyUpgradePlanTitle, plan.Name), + sdk.NewAttribute(types.AttributeKeyUpgradePlanHeight, fmt.Sprintf("%d", plan.Height)), + ), + }.ToABCIEvents() + + expectedEvents = sdk.MarkEventsToIndex(expectedEvents, map[string]struct{}{}) + ibctesting.AssertEvents(&suite.Suite, expectedEvents, ctx.EventManager().Events().ToABCIEvents()) + + } else { + // check that the new plan wasn't stored + storedPlan, err := suite.chainA.GetSimApp().UpgradeKeeper.GetUpgradePlan(suite.chainA.GetContext()) + if oldPlan.Height != 0 { + // NOTE: this is only true if the ScheduleUpgrade function + // returns an error before clearing the old plan + suite.Require().NoError(err) + suite.Require().Equal(oldPlan, storedPlan) + } else { + suite.Require().ErrorIs(err, upgradetypes.ErrNoUpgradePlanFound) + suite.Require().Empty(storedPlan) + } + + // check that client state was not set + cs, err := suite.chainA.GetSimApp().UpgradeKeeper.GetUpgradedClient(suite.chainA.GetContext(), plan.Height) + suite.Require().Empty(cs) + suite.Require().ErrorIs(err, upgradetypes.ErrNoUpgradedClientFound) + } + }) + } +} diff --git a/modules/core/02-client/keeper/migrations.go b/modules/core/02-client/keeper/migrations.go new file mode 100644 index 0000000..3f34936 --- /dev/null +++ b/modules/core/02-client/keeper/migrations.go @@ -0,0 +1,55 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/migrations/v7" + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// Migrator is a struct for handling in-place store migrations. +type Migrator struct { + keeper *Keeper +} + +// NewMigrator returns a new Migrator. +func NewMigrator(keeper *Keeper) Migrator { + return Migrator{keeper: keeper} +} + +// Migrate2to3 migrates from consensus version 2 to 3. +// This migration +// - migrates solo machine client states from v2 to v3 protobuf definition +// - prunes solo machine consensus states +// - removes the localhost client +// - asserts that existing tendermint clients are properly registered on the chain codec +func (m Migrator) Migrate2to3(ctx sdk.Context) error { + return v7.MigrateStore(ctx, m.keeper.storeService, m.keeper.cdc, m.keeper) +} + +// MigrateParams migrates from consensus version 4 to 5. +// This migration takes the parameters that are currently stored and managed by x/params +// and stores them directly in the ibc module's state. +func (m Migrator) MigrateParams(ctx sdk.Context) error { + var params types.Params + m.keeper.legacySubspace.GetParamSet(ctx, ¶ms) + if err := params.Validate(); err != nil { + return err + } + + m.keeper.SetParams(ctx, params) + m.keeper.Logger(ctx).Info("successfully migrated client to self-manage params") + return nil +} + +// MigrateToStatelessLocalhost deletes the localhost client state. The localhost +// implementation is now stateless. +func (m Migrator) MigrateToStatelessLocalhost(ctx sdk.Context) error { + clientStore := m.keeper.ClientStore(ctx, exported.LocalhostClientID) + + // delete the client state + clientStore.Delete(host.ClientStateKey()) + return nil +} diff --git a/modules/core/02-client/keeper/migrations_test.go b/modules/core/02-client/keeper/migrations_test.go new file mode 100644 index 0000000..7bd1d84 --- /dev/null +++ b/modules/core/02-client/keeper/migrations_test.go @@ -0,0 +1,59 @@ +package keeper_test + +import ( + "github.com/cosmos/ibc-go/v10/modules/core/02-client/keeper" + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// TestMigrateParams tests the migration for the client params +func (suite *KeeperTestSuite) TestMigrateParams() { + testCases := []struct { + name string + malleate func() + expectedParams types.Params + }{ + { + "success: default params", + func() { + params := types.DefaultParams() + subspace := suite.chainA.GetSimApp().GetSubspace(ibcexported.ModuleName) + subspace.SetParamSet(suite.chainA.GetContext(), ¶ms) + }, + types.DefaultParams(), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + tc.malleate() + + ctx := suite.chainA.GetContext() + migrator := keeper.NewMigrator(suite.chainA.GetSimApp().IBCKeeper.ClientKeeper) + err := migrator.MigrateParams(ctx) + suite.Require().NoError(err) + + params := suite.chainA.GetSimApp().IBCKeeper.ClientKeeper.GetParams(ctx) + suite.Require().Equal(tc.expectedParams, params) + }) + } +} + +func (suite *KeeperTestSuite) TestMigrateToStatelessLocalhost() { + // set localhost in state + clientStore := suite.chainA.GetSimApp().IBCKeeper.ClientKeeper.ClientStore(suite.chainA.GetContext(), ibcexported.LocalhostClientID) + clientStore.Set(host.ClientStateKey(), []byte("clientState")) + + m := keeper.NewMigrator(suite.chainA.GetSimApp().IBCKeeper.ClientKeeper) + err := m.MigrateToStatelessLocalhost(suite.chainA.GetContext()) + suite.Require().NoError(err) + suite.Require().False(clientStore.Has(host.ClientStateKey())) + + // rerun migration on no localhost set + err = m.MigrateToStatelessLocalhost(suite.chainA.GetContext()) + suite.Require().NoError(err) + suite.Require().False(clientStore.Has(host.ClientStateKey())) +} diff --git a/modules/core/02-client/migrations/v7/expected_keepers.go b/modules/core/02-client/migrations/v7/expected_keepers.go new file mode 100644 index 0000000..20e45cf --- /dev/null +++ b/modules/core/02-client/migrations/v7/expected_keepers.go @@ -0,0 +1,16 @@ +package v7 + +import ( + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// ClientKeeper expected IBC client keeper +type ClientKeeper interface { + GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool) + SetClientState(ctx sdk.Context, clientID string, clientState exported.ClientState) + ClientStore(ctx sdk.Context, clientID string) storetypes.KVStore +} diff --git a/modules/core/02-client/migrations/v7/genesis.go b/modules/core/02-client/migrations/v7/genesis.go new file mode 100644 index 0000000..20d9934 --- /dev/null +++ b/modules/core/02-client/migrations/v7/genesis.go @@ -0,0 +1,78 @@ +package v7 + +import ( + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/cosmos-sdk/codec" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// MigrateGenesis accepts an exported IBC client genesis file and migrates it to: +// +// - Update solo machine client state protobuf definition (v2 to v3) +// - Remove all solo machine consensus states +// - Remove localhost client +func MigrateGenesis(clientGenState *clienttypes.GenesisState, cdc codec.ProtoCodecMarshaler) (*clienttypes.GenesisState, error) { + // To prune the client and consensus states, we will create new slices to fill up + // with information we want to keep. + var ( + clientsConsensus []clienttypes.ClientConsensusStates + clients []clienttypes.IdentifiedClientState + ) + + for _, client := range clientGenState.Clients { + clientType, _, err := clienttypes.ParseClientIdentifier(client.ClientId) + if err != nil { + return nil, err + } + + switch clientType { + case exported.Solomachine: + var clientState ClientState + if err := cdc.Unmarshal(client.ClientState.Value, &clientState); err != nil { + return nil, errorsmod.Wrap(err, "failed to unmarshal client state bytes into solo machine client state") + } + + updatedClientState := migrateSolomachine(clientState) + + protoAny, err := clienttypes.PackClientState(&updatedClientState) + if err != nil { + return nil, err + } + + clients = append(clients, clienttypes.IdentifiedClientState{ + ClientId: client.ClientId, + ClientState: protoAny, + }) + + case Localhost: + // remove localhost client state by not adding client state + + default: + // add all other client states + clients = append(clients, client) + } + + // iterate consensus states by client + for _, clientConsensusStates := range clientGenState.ClientsConsensus { + // look for consensus states for the current client + if clientConsensusStates.ClientId == client.ClientId { + switch clientType { + case exported.Solomachine, Localhost: + // remove all consensus states for the solo machine and localhost + // do not add to new clientsConsensus + + default: + // ensure all consensus states added for other client types + clientsConsensus = append(clientsConsensus, clientConsensusStates) + } + } + } + } + + clientGenState.Clients = clients + clientGenState.ClientsConsensus = clientsConsensus + return clientGenState, nil +} diff --git a/modules/core/02-client/migrations/v7/genesis_test.go b/modules/core/02-client/migrations/v7/genesis_test.go new file mode 100644 index 0000000..3b03d57 --- /dev/null +++ b/modules/core/02-client/migrations/v7/genesis_test.go @@ -0,0 +1,141 @@ +package v7_test + +import ( + "encoding/json" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/runtime" + + ibcclient "github.com/cosmos/ibc-go/v10/modules/core/02-client" + "github.com/cosmos/ibc-go/v10/modules/core/02-client/migrations/v7" + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *MigrationsV7TestSuite) TestMigrateGenesisSolomachine() { + // create tendermint clients + for range 3 { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + + path.SetupClients() + + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + // update a second time to add more state + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + } + + // create multiple legacy solo machine clients + solomachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, ibctesting.DefaultSolomachineClientID, "testing", 1) + solomachineMulti := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "06-solomachine-1", "testing", 4) + + clientGenState := ibcclient.ExportGenesis(suite.chainA.GetContext(), suite.chainA.App.GetIBCKeeper().ClientKeeper) + + // manually generate old proto buf definitions and set in genesis + // NOTE: we cannot use 'ExportGenesis' for the solo machines since we are + // using client states and consensus states which do not implement the exported.ClientState + // and exported.ConsensusState interface + var clients []types.IdentifiedClientState + for _, sm := range []*ibctesting.Solomachine{solomachine, solomachineMulti} { + clientState := sm.ClientState() + + // generate old client state proto definition + legacyClientState := &v7.ClientState{ + Sequence: clientState.Sequence, + ConsensusState: &v7.ConsensusState{ + PublicKey: clientState.ConsensusState.PublicKey, + Diversifier: clientState.ConsensusState.Diversifier, + Timestamp: clientState.ConsensusState.Timestamp, + }, + AllowUpdateAfterProposal: true, + } + + // set client state + protoAny, err := codectypes.NewAnyWithValue(legacyClientState) + suite.Require().NoError(err) + suite.Require().NotNil(protoAny) + + clients = append(clients, types.IdentifiedClientState{ + ClientId: sm.ClientID, + ClientState: protoAny, + }) + + // set in store for ease of determining expected genesis + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), sm.ClientID) + + cdc, ok := suite.chainA.App.AppCodec().(*codec.ProtoCodec) + suite.Require().True(ok) + v7.RegisterInterfaces(cdc.InterfaceRegistry()) + + bz, err := cdc.MarshalInterface(legacyClientState) + suite.Require().NoError(err) + clientStore.Set(host.ClientStateKey(), bz) + + protoAny, err = codectypes.NewAnyWithValue(legacyClientState.ConsensusState) + suite.Require().NoError(err) + suite.Require().NotNil(protoAny) + + // obtain marshalled bytes to set in client store + bz, err = cdc.MarshalInterface(legacyClientState.ConsensusState) + suite.Require().NoError(err) + + var consensusStates []types.ConsensusStateWithHeight + + // set consensus states in store and genesis + for i := uint64(0); i < numCreations; i++ { + height := types.NewHeight(1, i) + clientStore.Set(host.ConsensusStateKey(height), bz) + consensusStates = append(consensusStates, types.ConsensusStateWithHeight{ + Height: height, + ConsensusState: protoAny, + }) + } + + clientGenState.ClientsConsensus = append(clientGenState.ClientsConsensus, types.ClientConsensusStates{ + ClientId: sm.ClientID, + ConsensusStates: consensusStates, + }) + } + + // solo machine clients must come before tendermint in expected + clientGenState.Clients = append(clients, clientGenState.Clients...) + + // migrate store get expected genesis + // store migration and genesis migration should produce identical results + // NOTE: tendermint clients are not pruned in genesis so the test should not have expired tendermint clients + err := v7.MigrateStore(suite.chainA.GetContext(), runtime.NewKVStoreService(suite.chainA.GetSimApp().GetKey(ibcexported.StoreKey)), suite.chainA.App.AppCodec(), suite.chainA.GetSimApp().IBCKeeper.ClientKeeper) + suite.Require().NoError(err) + expectedClientGenState := ibcclient.ExportGenesis(suite.chainA.GetContext(), suite.chainA.App.GetIBCKeeper().ClientKeeper) + + cdc, ok := suite.chainA.App.AppCodec().(codec.ProtoCodecMarshaler) + suite.Require().True(ok) + + migrated, err := v7.MigrateGenesis(&clientGenState, cdc) + suite.Require().NoError(err) + + bz, err := cdc.MarshalJSON(&expectedClientGenState) + suite.Require().NoError(err) + + // Indent the JSON bz correctly. + var jsonObj map[string]any + err = json.Unmarshal(bz, &jsonObj) + suite.Require().NoError(err) + expectedIndentedBz, err := json.MarshalIndent(jsonObj, "", "\t") + suite.Require().NoError(err) + + bz, err = cdc.MarshalJSON(migrated) + suite.Require().NoError(err) + + // Indent the JSON bz correctly. + err = json.Unmarshal(bz, &jsonObj) + suite.Require().NoError(err) + indentedBz, err := json.MarshalIndent(jsonObj, "", "\t") + suite.Require().NoError(err) + + suite.Require().Equal(string(expectedIndentedBz), string(indentedBz)) +} diff --git a/modules/core/02-client/migrations/v7/solomachine.go b/modules/core/02-client/migrations/v7/solomachine.go new file mode 100644 index 0000000..45a36d3 --- /dev/null +++ b/modules/core/02-client/migrations/v7/solomachine.go @@ -0,0 +1,236 @@ +package v7 + +import ( + "errors" + + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// NOTE: this is a mock implementation for exported.ClientState. This implementation +// should only be registered on the InterfaceRegistry during cli command genesis migration. +// This implementation is only used to successfully unmarshal the previous solo machine +// client state and consensus state and migrate them to the new implementations. When the proto +// codec unmarshals, it calls UnpackInterfaces() to create a cached value of the any. The +// UnpackInterfaces function for IdenitifiedClientState will attempt to unpack the any to +// exported.ClientState. If the solomachine v2 type is not registered against the exported.ClientState +// the unmarshal will fail. This implementation will panic on every interface function. +// The same is done for the ConsensusState. + +// Interface implementation checks. +var ( + _, _ codectypes.UnpackInterfacesMessage = (*ClientState)(nil), (*ConsensusState)(nil) + _ exported.ClientState = (*ClientState)(nil) + _ exported.ConsensusState = (*ConsensusState)(nil) +) + +// RegisterInterfaces registers the solomachine v2 ClientState and ConsensusState types in the interface registry. +func RegisterInterfaces(registry codectypes.InterfaceRegistry) { + registry.RegisterImplementations( + (*exported.ClientState)(nil), + &ClientState{}, + ) + registry.RegisterImplementations( + (*exported.ConsensusState)(nil), + &ConsensusState{}, + ) +} + +// UnpackInterfaces implements the UnpackInterfaceMessages.UnpackInterfaces method +func (cs ClientState) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + return cs.ConsensusState.UnpackInterfaces(unpacker) +} + +// UnpackInterfaces implements the UnpackInterfaceMessages.UnpackInterfaces method +func (cs ConsensusState) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + return unpacker.UnpackAny(cs.PublicKey, new(cryptotypes.PubKey)) +} + +// ClientType panics! +func (ClientState) ClientType() string { + panic(errors.New("legacy solo machine is deprecated")) +} + +// GetLatestHeight panics! +func (ClientState) GetLatestHeight() exported.Height { + panic(errors.New("legacy solo machine is deprecated")) +} + +// Status panics! +func (ClientState) Status(_ sdk.Context, _ storetypes.KVStore, _ codec.BinaryCodec) exported.Status { + panic(errors.New("legacy solo machine is deprecated")) +} + +// Validate panics! +func (ClientState) Validate() error { + panic(errors.New("legacy solo machine is deprecated")) +} + +// Initialize panics! +func (ClientState) Initialize(_ sdk.Context, _ codec.BinaryCodec, _ storetypes.KVStore, _ exported.ConsensusState) error { + panic(errors.New("legacy solo machine is deprecated")) +} + +// CheckForMisbehaviour panics! +func (ClientState) CheckForMisbehaviour(_ sdk.Context, _ codec.BinaryCodec, _ storetypes.KVStore, _ exported.ClientMessage) bool { + panic(errors.New("legacy solo machine is deprecated")) +} + +// UpdateStateOnMisbehaviour panics! +func (*ClientState) UpdateStateOnMisbehaviour( + _ sdk.Context, _ codec.BinaryCodec, _ storetypes.KVStore, _ exported.ClientMessage, +) { + panic(errors.New("legacy solo machine is deprecated")) +} + +// VerifyClientMessage panics! +func (*ClientState) VerifyClientMessage( + _ sdk.Context, _ codec.BinaryCodec, _ storetypes.KVStore, _ exported.ClientMessage, +) error { + panic(errors.New("legacy solo machine is deprecated")) +} + +// UpdateState panis! +func (*ClientState) UpdateState(_ sdk.Context, _ codec.BinaryCodec, _ storetypes.KVStore, _ exported.ClientMessage) []exported.Height { + panic(errors.New("legacy solo machine is deprecated")) +} + +// CheckHeaderAndUpdateState panics! +func (*ClientState) CheckHeaderAndUpdateState( + _ sdk.Context, _ codec.BinaryCodec, _ storetypes.KVStore, _ exported.ClientMessage, +) (exported.ClientState, exported.ConsensusState, error) { + panic(errors.New("legacy solo machine is deprecated")) +} + +// CheckMisbehaviourAndUpdateState panics! +func (ClientState) CheckMisbehaviourAndUpdateState( + _ sdk.Context, _ codec.BinaryCodec, _ storetypes.KVStore, _ exported.ClientMessage, +) (exported.ClientState, error) { + panic(errors.New("legacy solo machine is deprecated")) +} + +// CheckSubstituteAndUpdateState panics! +func (ClientState) CheckSubstituteAndUpdateState( + ctx sdk.Context, _ codec.BinaryCodec, _, _ storetypes.KVStore, + _ exported.ClientState, +) error { + panic(errors.New("legacy solo machine is deprecated")) +} + +// VerifyUpgradeAndUpdateState panics! +func (ClientState) VerifyUpgradeAndUpdateState( + _ sdk.Context, _ codec.BinaryCodec, _ storetypes.KVStore, + _ exported.ClientState, _ exported.ConsensusState, _, _ []byte, +) error { + panic(errors.New("legacy solo machine is deprecated")) +} + +// VerifyClientState panics! +func (ClientState) VerifyClientState( + store storetypes.KVStore, cdc codec.BinaryCodec, + _ exported.Height, _ exported.Prefix, _ string, _ []byte, clientState exported.ClientState, +) error { + panic(errors.New("legacy solo machine is deprecated")) +} + +// VerifyClientConsensusState panics! +func (ClientState) VerifyClientConsensusState( + storetypes.KVStore, codec.BinaryCodec, + exported.Height, string, exported.Height, exported.Prefix, + []byte, exported.ConsensusState, +) error { + panic(errors.New("legacy solo machine is deprecated")) +} + +// VerifyPacketCommitment panics! +func (ClientState) VerifyPacketCommitment( + sdk.Context, storetypes.KVStore, codec.BinaryCodec, exported.Height, + uint64, uint64, exported.Prefix, []byte, + string, string, uint64, []byte, +) error { + panic(errors.New("legacy solo machine is deprecated")) +} + +// VerifyPacketAcknowledgement panics! +func (ClientState) VerifyPacketAcknowledgement( + sdk.Context, storetypes.KVStore, codec.BinaryCodec, exported.Height, + uint64, uint64, exported.Prefix, []byte, + string, string, uint64, []byte, +) error { + panic(errors.New("legacy solo machine is deprecated")) +} + +// VerifyPacketReceiptAbsence panics! +func (ClientState) VerifyPacketReceiptAbsence( + sdk.Context, storetypes.KVStore, codec.BinaryCodec, exported.Height, + uint64, uint64, exported.Prefix, []byte, + string, string, uint64, +) error { + panic(errors.New("legacy solo machine is deprecated")) +} + +// VerifyNextSequenceRecv panics! +func (ClientState) VerifyNextSequenceRecv( + sdk.Context, storetypes.KVStore, codec.BinaryCodec, exported.Height, + uint64, uint64, exported.Prefix, []byte, + string, string, uint64, +) error { + panic(errors.New("legacy solo machine is deprecated")) +} + +// GetTimestampAtHeight panics! +func (ClientState) GetTimestampAtHeight( + sdk.Context, storetypes.KVStore, codec.BinaryCodec, exported.Height, +) (uint64, error) { + panic(errors.New("legacy solo machine is deprecated")) +} + +// VerifyMembership panics! +func (*ClientState) VerifyMembership( + ctx sdk.Context, + clientStore storetypes.KVStore, + cdc codec.BinaryCodec, + height exported.Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path exported.Path, + value []byte, +) error { + panic(errors.New("legacy solo machine is deprecated")) +} + +// VerifyNonMembership panics! +func (*ClientState) VerifyNonMembership( + ctx sdk.Context, + clientStore storetypes.KVStore, + cdc codec.BinaryCodec, + height exported.Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path exported.Path, +) error { + panic(errors.New("legacy solo machine is deprecated")) +} + +// ClientType panics! +func (ConsensusState) ClientType() string { + panic(errors.New("legacy solo machine is deprecated")) +} + +// GetTimestamp panics! +func (ConsensusState) GetTimestamp() uint64 { + panic(errors.New("legacy solo machine is deprecated")) +} + +// ValidateBasic panics! +func (ConsensusState) ValidateBasic() error { + panic(errors.New("legacy solo machine is deprecated")) +} diff --git a/modules/core/02-client/migrations/v7/solomachine.pb.go b/modules/core/02-client/migrations/v7/solomachine.pb.go new file mode 100644 index 0000000..bb4d811 --- /dev/null +++ b/modules/core/02-client/migrations/v7/solomachine.pb.go @@ -0,0 +1,4119 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/lightclients/solomachine/v2/solomachine.proto + +package v7 + +import ( + fmt "fmt" + types "github.com/cosmos/cosmos-sdk/codec/types" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + types1 "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + types2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// DataType defines the type of solo machine proof being created. This is done +// to preserve uniqueness of different data sign byte encodings. +type DataType int32 + +const ( + // Default State + UNSPECIFIED DataType = 0 + // Data type for client state verification + CLIENT DataType = 1 + // Data type for consensus state verification + CONSENSUS DataType = 2 + // Data type for connection state verification + CONNECTION DataType = 3 + // Data type for channel state verification + CHANNEL DataType = 4 + // Data type for packet commitment verification + PACKETCOMMITMENT DataType = 5 + // Data type for packet acknowledgement verification + PACKETACKNOWLEDGEMENT DataType = 6 + // Data type for packet receipt absence verification + PACKETRECEIPTABSENCE DataType = 7 + // Data type for next sequence recv verification + NEXTSEQUENCERECV DataType = 8 + // Data type for header verification + HEADER DataType = 9 +) + +var DataType_name = map[int32]string{ + 0: "DATA_TYPE_UNINITIALIZED_UNSPECIFIED", + 1: "DATA_TYPE_CLIENT_STATE", + 2: "DATA_TYPE_CONSENSUS_STATE", + 3: "DATA_TYPE_CONNECTION_STATE", + 4: "DATA_TYPE_CHANNEL_STATE", + 5: "DATA_TYPE_PACKET_COMMITMENT", + 6: "DATA_TYPE_PACKET_ACKNOWLEDGEMENT", + 7: "DATA_TYPE_PACKET_RECEIPT_ABSENCE", + 8: "DATA_TYPE_NEXT_SEQUENCE_RECV", + 9: "DATA_TYPE_HEADER", +} + +var DataType_value = map[string]int32{ + "DATA_TYPE_UNINITIALIZED_UNSPECIFIED": 0, + "DATA_TYPE_CLIENT_STATE": 1, + "DATA_TYPE_CONSENSUS_STATE": 2, + "DATA_TYPE_CONNECTION_STATE": 3, + "DATA_TYPE_CHANNEL_STATE": 4, + "DATA_TYPE_PACKET_COMMITMENT": 5, + "DATA_TYPE_PACKET_ACKNOWLEDGEMENT": 6, + "DATA_TYPE_PACKET_RECEIPT_ABSENCE": 7, + "DATA_TYPE_NEXT_SEQUENCE_RECV": 8, + "DATA_TYPE_HEADER": 9, +} + +func (x DataType) String() string { + return proto.EnumName(DataType_name, int32(x)) +} + +func (DataType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_141333b361aae010, []int{0} +} + +// ClientState defines a solo machine client that tracks the current consensus +// state and if the client is frozen. +type ClientState struct { + // latest sequence of the client state + Sequence uint64 `protobuf:"varint,1,opt,name=sequence,proto3" json:"sequence,omitempty"` + // frozen sequence of the solo machine + IsFrozen bool `protobuf:"varint,2,opt,name=is_frozen,json=isFrozen,proto3" json:"is_frozen,omitempty"` + ConsensusState *ConsensusState `protobuf:"bytes,3,opt,name=consensus_state,json=consensusState,proto3" json:"consensus_state,omitempty"` + // when set to true, will allow governance to update a solo machine client. + // The client will be unfrozen if it is frozen. + AllowUpdateAfterProposal bool `protobuf:"varint,4,opt,name=allow_update_after_proposal,json=allowUpdateAfterProposal,proto3" json:"allow_update_after_proposal,omitempty"` +} + +func (m *ClientState) Reset() { *m = ClientState{} } +func (m *ClientState) String() string { return proto.CompactTextString(m) } +func (*ClientState) ProtoMessage() {} +func (*ClientState) Descriptor() ([]byte, []int) { + return fileDescriptor_141333b361aae010, []int{0} +} +func (m *ClientState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ClientState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ClientState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ClientState) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClientState.Merge(m, src) +} +func (m *ClientState) XXX_Size() int { + return m.Size() +} +func (m *ClientState) XXX_DiscardUnknown() { + xxx_messageInfo_ClientState.DiscardUnknown(m) +} + +var xxx_messageInfo_ClientState proto.InternalMessageInfo + +// ConsensusState defines a solo machine consensus state. The sequence of a +// consensus state is contained in the "height" key used in storing the +// consensus state. +type ConsensusState struct { + // public key of the solo machine + PublicKey *types.Any `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` + // diversifier allows the same public key to be reused across different solo + // machine clients (potentially on different chains) without being considered + // misbehaviour. + Diversifier string `protobuf:"bytes,2,opt,name=diversifier,proto3" json:"diversifier,omitempty"` + Timestamp uint64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` +} + +func (m *ConsensusState) Reset() { *m = ConsensusState{} } +func (m *ConsensusState) String() string { return proto.CompactTextString(m) } +func (*ConsensusState) ProtoMessage() {} +func (*ConsensusState) Descriptor() ([]byte, []int) { + return fileDescriptor_141333b361aae010, []int{1} +} +func (m *ConsensusState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ConsensusState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ConsensusState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ConsensusState) XXX_Merge(src proto.Message) { + xxx_messageInfo_ConsensusState.Merge(m, src) +} +func (m *ConsensusState) XXX_Size() int { + return m.Size() +} +func (m *ConsensusState) XXX_DiscardUnknown() { + xxx_messageInfo_ConsensusState.DiscardUnknown(m) +} + +var xxx_messageInfo_ConsensusState proto.InternalMessageInfo + +// Header defines a solo machine consensus header +type Header struct { + // sequence to update solo machine public key at + Sequence uint64 `protobuf:"varint,1,opt,name=sequence,proto3" json:"sequence,omitempty"` + Timestamp uint64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Signature []byte `protobuf:"bytes,3,opt,name=signature,proto3" json:"signature,omitempty"` + NewPublicKey *types.Any `protobuf:"bytes,4,opt,name=new_public_key,json=newPublicKey,proto3" json:"new_public_key,omitempty"` + NewDiversifier string `protobuf:"bytes,5,opt,name=new_diversifier,json=newDiversifier,proto3" json:"new_diversifier,omitempty"` +} + +func (m *Header) Reset() { *m = Header{} } +func (m *Header) String() string { return proto.CompactTextString(m) } +func (*Header) ProtoMessage() {} +func (*Header) Descriptor() ([]byte, []int) { + return fileDescriptor_141333b361aae010, []int{2} +} +func (m *Header) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Header) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Header.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Header) XXX_Merge(src proto.Message) { + xxx_messageInfo_Header.Merge(m, src) +} +func (m *Header) XXX_Size() int { + return m.Size() +} +func (m *Header) XXX_DiscardUnknown() { + xxx_messageInfo_Header.DiscardUnknown(m) +} + +var xxx_messageInfo_Header proto.InternalMessageInfo + +// Misbehaviour defines misbehaviour for a solo machine which consists +// of a sequence and two signatures over different messages at that sequence. +type Misbehaviour struct { + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + Sequence uint64 `protobuf:"varint,2,opt,name=sequence,proto3" json:"sequence,omitempty"` + SignatureOne *SignatureAndData `protobuf:"bytes,3,opt,name=signature_one,json=signatureOne,proto3" json:"signature_one,omitempty"` + SignatureTwo *SignatureAndData `protobuf:"bytes,4,opt,name=signature_two,json=signatureTwo,proto3" json:"signature_two,omitempty"` +} + +func (m *Misbehaviour) Reset() { *m = Misbehaviour{} } +func (m *Misbehaviour) String() string { return proto.CompactTextString(m) } +func (*Misbehaviour) ProtoMessage() {} +func (*Misbehaviour) Descriptor() ([]byte, []int) { + return fileDescriptor_141333b361aae010, []int{3} +} +func (m *Misbehaviour) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Misbehaviour) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Misbehaviour.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Misbehaviour) XXX_Merge(src proto.Message) { + xxx_messageInfo_Misbehaviour.Merge(m, src) +} +func (m *Misbehaviour) XXX_Size() int { + return m.Size() +} +func (m *Misbehaviour) XXX_DiscardUnknown() { + xxx_messageInfo_Misbehaviour.DiscardUnknown(m) +} + +var xxx_messageInfo_Misbehaviour proto.InternalMessageInfo + +// SignatureAndData contains a signature and the data signed over to create that +// signature. +type SignatureAndData struct { + Signature []byte `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"` + DataType DataType `protobuf:"varint,2,opt,name=data_type,json=dataType,proto3,enum=ibc.lightclients.solomachine.v2.DataType" json:"data_type,omitempty"` + Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` + Timestamp uint64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` +} + +func (m *SignatureAndData) Reset() { *m = SignatureAndData{} } +func (m *SignatureAndData) String() string { return proto.CompactTextString(m) } +func (*SignatureAndData) ProtoMessage() {} +func (*SignatureAndData) Descriptor() ([]byte, []int) { + return fileDescriptor_141333b361aae010, []int{4} +} +func (m *SignatureAndData) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *SignatureAndData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_SignatureAndData.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *SignatureAndData) XXX_Merge(src proto.Message) { + xxx_messageInfo_SignatureAndData.Merge(m, src) +} +func (m *SignatureAndData) XXX_Size() int { + return m.Size() +} +func (m *SignatureAndData) XXX_DiscardUnknown() { + xxx_messageInfo_SignatureAndData.DiscardUnknown(m) +} + +var xxx_messageInfo_SignatureAndData proto.InternalMessageInfo + +// TimestampedSignatureData contains the signature data and the timestamp of the +// signature. +type TimestampedSignatureData struct { + SignatureData []byte `protobuf:"bytes,1,opt,name=signature_data,json=signatureData,proto3" json:"signature_data,omitempty"` + Timestamp uint64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` +} + +func (m *TimestampedSignatureData) Reset() { *m = TimestampedSignatureData{} } +func (m *TimestampedSignatureData) String() string { return proto.CompactTextString(m) } +func (*TimestampedSignatureData) ProtoMessage() {} +func (*TimestampedSignatureData) Descriptor() ([]byte, []int) { + return fileDescriptor_141333b361aae010, []int{5} +} +func (m *TimestampedSignatureData) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TimestampedSignatureData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TimestampedSignatureData.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TimestampedSignatureData) XXX_Merge(src proto.Message) { + xxx_messageInfo_TimestampedSignatureData.Merge(m, src) +} +func (m *TimestampedSignatureData) XXX_Size() int { + return m.Size() +} +func (m *TimestampedSignatureData) XXX_DiscardUnknown() { + xxx_messageInfo_TimestampedSignatureData.DiscardUnknown(m) +} + +var xxx_messageInfo_TimestampedSignatureData proto.InternalMessageInfo + +// SignBytes defines the signed bytes used for signature verification. +type SignBytes struct { + Sequence uint64 `protobuf:"varint,1,opt,name=sequence,proto3" json:"sequence,omitempty"` + Timestamp uint64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Diversifier string `protobuf:"bytes,3,opt,name=diversifier,proto3" json:"diversifier,omitempty"` + // type of the data used + DataType DataType `protobuf:"varint,4,opt,name=data_type,json=dataType,proto3,enum=ibc.lightclients.solomachine.v2.DataType" json:"data_type,omitempty"` + // marshaled data + Data []byte `protobuf:"bytes,5,opt,name=data,proto3" json:"data,omitempty"` +} + +func (m *SignBytes) Reset() { *m = SignBytes{} } +func (m *SignBytes) String() string { return proto.CompactTextString(m) } +func (*SignBytes) ProtoMessage() {} +func (*SignBytes) Descriptor() ([]byte, []int) { + return fileDescriptor_141333b361aae010, []int{6} +} +func (m *SignBytes) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *SignBytes) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_SignBytes.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *SignBytes) XXX_Merge(src proto.Message) { + xxx_messageInfo_SignBytes.Merge(m, src) +} +func (m *SignBytes) XXX_Size() int { + return m.Size() +} +func (m *SignBytes) XXX_DiscardUnknown() { + xxx_messageInfo_SignBytes.DiscardUnknown(m) +} + +var xxx_messageInfo_SignBytes proto.InternalMessageInfo + +// HeaderData returns the SignBytes data for update verification. +type HeaderData struct { + // header public key + NewPubKey *types.Any `protobuf:"bytes,1,opt,name=new_pub_key,json=newPubKey,proto3" json:"new_pub_key,omitempty"` + // header diversifier + NewDiversifier string `protobuf:"bytes,2,opt,name=new_diversifier,json=newDiversifier,proto3" json:"new_diversifier,omitempty"` +} + +func (m *HeaderData) Reset() { *m = HeaderData{} } +func (m *HeaderData) String() string { return proto.CompactTextString(m) } +func (*HeaderData) ProtoMessage() {} +func (*HeaderData) Descriptor() ([]byte, []int) { + return fileDescriptor_141333b361aae010, []int{7} +} +func (m *HeaderData) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *HeaderData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_HeaderData.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *HeaderData) XXX_Merge(src proto.Message) { + xxx_messageInfo_HeaderData.Merge(m, src) +} +func (m *HeaderData) XXX_Size() int { + return m.Size() +} +func (m *HeaderData) XXX_DiscardUnknown() { + xxx_messageInfo_HeaderData.DiscardUnknown(m) +} + +var xxx_messageInfo_HeaderData proto.InternalMessageInfo + +// ClientStateData returns the SignBytes data for client state verification. +type ClientStateData struct { + Path []byte `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` + ClientState *types.Any `protobuf:"bytes,2,opt,name=client_state,json=clientState,proto3" json:"client_state,omitempty"` +} + +func (m *ClientStateData) Reset() { *m = ClientStateData{} } +func (m *ClientStateData) String() string { return proto.CompactTextString(m) } +func (*ClientStateData) ProtoMessage() {} +func (*ClientStateData) Descriptor() ([]byte, []int) { + return fileDescriptor_141333b361aae010, []int{8} +} +func (m *ClientStateData) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ClientStateData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ClientStateData.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ClientStateData) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClientStateData.Merge(m, src) +} +func (m *ClientStateData) XXX_Size() int { + return m.Size() +} +func (m *ClientStateData) XXX_DiscardUnknown() { + xxx_messageInfo_ClientStateData.DiscardUnknown(m) +} + +var xxx_messageInfo_ClientStateData proto.InternalMessageInfo + +// ConsensusStateData returns the SignBytes data for consensus state +// verification. +type ConsensusStateData struct { + Path []byte `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` + ConsensusState *types.Any `protobuf:"bytes,2,opt,name=consensus_state,json=consensusState,proto3" json:"consensus_state,omitempty"` +} + +func (m *ConsensusStateData) Reset() { *m = ConsensusStateData{} } +func (m *ConsensusStateData) String() string { return proto.CompactTextString(m) } +func (*ConsensusStateData) ProtoMessage() {} +func (*ConsensusStateData) Descriptor() ([]byte, []int) { + return fileDescriptor_141333b361aae010, []int{9} +} +func (m *ConsensusStateData) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ConsensusStateData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ConsensusStateData.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ConsensusStateData) XXX_Merge(src proto.Message) { + xxx_messageInfo_ConsensusStateData.Merge(m, src) +} +func (m *ConsensusStateData) XXX_Size() int { + return m.Size() +} +func (m *ConsensusStateData) XXX_DiscardUnknown() { + xxx_messageInfo_ConsensusStateData.DiscardUnknown(m) +} + +var xxx_messageInfo_ConsensusStateData proto.InternalMessageInfo + +// ConnectionStateData returns the SignBytes data for connection state +// verification. +type ConnectionStateData struct { + Path []byte `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` + Connection *types1.ConnectionEnd `protobuf:"bytes,2,opt,name=connection,proto3" json:"connection,omitempty"` +} + +func (m *ConnectionStateData) Reset() { *m = ConnectionStateData{} } +func (m *ConnectionStateData) String() string { return proto.CompactTextString(m) } +func (*ConnectionStateData) ProtoMessage() {} +func (*ConnectionStateData) Descriptor() ([]byte, []int) { + return fileDescriptor_141333b361aae010, []int{10} +} +func (m *ConnectionStateData) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ConnectionStateData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ConnectionStateData.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ConnectionStateData) XXX_Merge(src proto.Message) { + xxx_messageInfo_ConnectionStateData.Merge(m, src) +} +func (m *ConnectionStateData) XXX_Size() int { + return m.Size() +} +func (m *ConnectionStateData) XXX_DiscardUnknown() { + xxx_messageInfo_ConnectionStateData.DiscardUnknown(m) +} + +var xxx_messageInfo_ConnectionStateData proto.InternalMessageInfo + +// ChannelStateData returns the SignBytes data for channel state +// verification. +type ChannelStateData struct { + Path []byte `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` + Channel *types2.Channel `protobuf:"bytes,2,opt,name=channel,proto3" json:"channel,omitempty"` +} + +func (m *ChannelStateData) Reset() { *m = ChannelStateData{} } +func (m *ChannelStateData) String() string { return proto.CompactTextString(m) } +func (*ChannelStateData) ProtoMessage() {} +func (*ChannelStateData) Descriptor() ([]byte, []int) { + return fileDescriptor_141333b361aae010, []int{11} +} +func (m *ChannelStateData) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ChannelStateData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ChannelStateData.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ChannelStateData) XXX_Merge(src proto.Message) { + xxx_messageInfo_ChannelStateData.Merge(m, src) +} +func (m *ChannelStateData) XXX_Size() int { + return m.Size() +} +func (m *ChannelStateData) XXX_DiscardUnknown() { + xxx_messageInfo_ChannelStateData.DiscardUnknown(m) +} + +var xxx_messageInfo_ChannelStateData proto.InternalMessageInfo + +// PacketCommitmentData returns the SignBytes data for packet commitment +// verification. +type PacketCommitmentData struct { + Path []byte `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` + Commitment []byte `protobuf:"bytes,2,opt,name=commitment,proto3" json:"commitment,omitempty"` +} + +func (m *PacketCommitmentData) Reset() { *m = PacketCommitmentData{} } +func (m *PacketCommitmentData) String() string { return proto.CompactTextString(m) } +func (*PacketCommitmentData) ProtoMessage() {} +func (*PacketCommitmentData) Descriptor() ([]byte, []int) { + return fileDescriptor_141333b361aae010, []int{12} +} +func (m *PacketCommitmentData) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PacketCommitmentData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PacketCommitmentData.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *PacketCommitmentData) XXX_Merge(src proto.Message) { + xxx_messageInfo_PacketCommitmentData.Merge(m, src) +} +func (m *PacketCommitmentData) XXX_Size() int { + return m.Size() +} +func (m *PacketCommitmentData) XXX_DiscardUnknown() { + xxx_messageInfo_PacketCommitmentData.DiscardUnknown(m) +} + +var xxx_messageInfo_PacketCommitmentData proto.InternalMessageInfo + +func (m *PacketCommitmentData) GetPath() []byte { + if m != nil { + return m.Path + } + return nil +} + +func (m *PacketCommitmentData) GetCommitment() []byte { + if m != nil { + return m.Commitment + } + return nil +} + +// PacketAcknowledgementData returns the SignBytes data for acknowledgement +// verification. +type PacketAcknowledgementData struct { + Path []byte `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` + Acknowledgement []byte `protobuf:"bytes,2,opt,name=acknowledgement,proto3" json:"acknowledgement,omitempty"` +} + +func (m *PacketAcknowledgementData) Reset() { *m = PacketAcknowledgementData{} } +func (m *PacketAcknowledgementData) String() string { return proto.CompactTextString(m) } +func (*PacketAcknowledgementData) ProtoMessage() {} +func (*PacketAcknowledgementData) Descriptor() ([]byte, []int) { + return fileDescriptor_141333b361aae010, []int{13} +} +func (m *PacketAcknowledgementData) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PacketAcknowledgementData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PacketAcknowledgementData.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *PacketAcknowledgementData) XXX_Merge(src proto.Message) { + xxx_messageInfo_PacketAcknowledgementData.Merge(m, src) +} +func (m *PacketAcknowledgementData) XXX_Size() int { + return m.Size() +} +func (m *PacketAcknowledgementData) XXX_DiscardUnknown() { + xxx_messageInfo_PacketAcknowledgementData.DiscardUnknown(m) +} + +var xxx_messageInfo_PacketAcknowledgementData proto.InternalMessageInfo + +func (m *PacketAcknowledgementData) GetPath() []byte { + if m != nil { + return m.Path + } + return nil +} + +func (m *PacketAcknowledgementData) GetAcknowledgement() []byte { + if m != nil { + return m.Acknowledgement + } + return nil +} + +// PacketReceiptAbsenceData returns the SignBytes data for +// packet receipt absence verification. +type PacketReceiptAbsenceData struct { + Path []byte `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` +} + +func (m *PacketReceiptAbsenceData) Reset() { *m = PacketReceiptAbsenceData{} } +func (m *PacketReceiptAbsenceData) String() string { return proto.CompactTextString(m) } +func (*PacketReceiptAbsenceData) ProtoMessage() {} +func (*PacketReceiptAbsenceData) Descriptor() ([]byte, []int) { + return fileDescriptor_141333b361aae010, []int{14} +} +func (m *PacketReceiptAbsenceData) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PacketReceiptAbsenceData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PacketReceiptAbsenceData.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *PacketReceiptAbsenceData) XXX_Merge(src proto.Message) { + xxx_messageInfo_PacketReceiptAbsenceData.Merge(m, src) +} +func (m *PacketReceiptAbsenceData) XXX_Size() int { + return m.Size() +} +func (m *PacketReceiptAbsenceData) XXX_DiscardUnknown() { + xxx_messageInfo_PacketReceiptAbsenceData.DiscardUnknown(m) +} + +var xxx_messageInfo_PacketReceiptAbsenceData proto.InternalMessageInfo + +func (m *PacketReceiptAbsenceData) GetPath() []byte { + if m != nil { + return m.Path + } + return nil +} + +// NextSequenceRecvData returns the SignBytes data for verification of the next +// sequence to be received. +type NextSequenceRecvData struct { + Path []byte `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` + NextSeqRecv uint64 `protobuf:"varint,2,opt,name=next_seq_recv,json=nextSeqRecv,proto3" json:"next_seq_recv,omitempty"` +} + +func (m *NextSequenceRecvData) Reset() { *m = NextSequenceRecvData{} } +func (m *NextSequenceRecvData) String() string { return proto.CompactTextString(m) } +func (*NextSequenceRecvData) ProtoMessage() {} +func (*NextSequenceRecvData) Descriptor() ([]byte, []int) { + return fileDescriptor_141333b361aae010, []int{15} +} +func (m *NextSequenceRecvData) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *NextSequenceRecvData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_NextSequenceRecvData.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *NextSequenceRecvData) XXX_Merge(src proto.Message) { + xxx_messageInfo_NextSequenceRecvData.Merge(m, src) +} +func (m *NextSequenceRecvData) XXX_Size() int { + return m.Size() +} +func (m *NextSequenceRecvData) XXX_DiscardUnknown() { + xxx_messageInfo_NextSequenceRecvData.DiscardUnknown(m) +} + +var xxx_messageInfo_NextSequenceRecvData proto.InternalMessageInfo + +func (m *NextSequenceRecvData) GetPath() []byte { + if m != nil { + return m.Path + } + return nil +} + +func (m *NextSequenceRecvData) GetNextSeqRecv() uint64 { + if m != nil { + return m.NextSeqRecv + } + return 0 +} + +func init() { + proto.RegisterEnum("ibc.lightclients.solomachine.v2.DataType", DataType_name, DataType_value) + proto.RegisterType((*ClientState)(nil), "ibc.lightclients.solomachine.v2.ClientState") + proto.RegisterType((*ConsensusState)(nil), "ibc.lightclients.solomachine.v2.ConsensusState") + proto.RegisterType((*Header)(nil), "ibc.lightclients.solomachine.v2.Header") + proto.RegisterType((*Misbehaviour)(nil), "ibc.lightclients.solomachine.v2.Misbehaviour") + proto.RegisterType((*SignatureAndData)(nil), "ibc.lightclients.solomachine.v2.SignatureAndData") + proto.RegisterType((*TimestampedSignatureData)(nil), "ibc.lightclients.solomachine.v2.TimestampedSignatureData") + proto.RegisterType((*SignBytes)(nil), "ibc.lightclients.solomachine.v2.SignBytes") + proto.RegisterType((*HeaderData)(nil), "ibc.lightclients.solomachine.v2.HeaderData") + proto.RegisterType((*ClientStateData)(nil), "ibc.lightclients.solomachine.v2.ClientStateData") + proto.RegisterType((*ConsensusStateData)(nil), "ibc.lightclients.solomachine.v2.ConsensusStateData") + proto.RegisterType((*ConnectionStateData)(nil), "ibc.lightclients.solomachine.v2.ConnectionStateData") + proto.RegisterType((*ChannelStateData)(nil), "ibc.lightclients.solomachine.v2.ChannelStateData") + proto.RegisterType((*PacketCommitmentData)(nil), "ibc.lightclients.solomachine.v2.PacketCommitmentData") + proto.RegisterType((*PacketAcknowledgementData)(nil), "ibc.lightclients.solomachine.v2.PacketAcknowledgementData") + proto.RegisterType((*PacketReceiptAbsenceData)(nil), "ibc.lightclients.solomachine.v2.PacketReceiptAbsenceData") + proto.RegisterType((*NextSequenceRecvData)(nil), "ibc.lightclients.solomachine.v2.NextSequenceRecvData") +} + +func init() { + proto.RegisterFile("ibc/lightclients/solomachine/v2/solomachine.proto", fileDescriptor_141333b361aae010) +} + +var fileDescriptor_141333b361aae010 = []byte{ + // 1239 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0xc1, 0x6e, 0xdb, 0x46, + 0x13, 0x36, 0x15, 0xc5, 0x91, 0x46, 0x8e, 0x2d, 0xf0, 0xf7, 0xdf, 0xca, 0x4c, 0xa0, 0xb0, 0x2e, + 0xd2, 0xb8, 0x45, 0x43, 0xc6, 0x4e, 0x9b, 0x14, 0x01, 0xd2, 0x82, 0xa1, 0x98, 0x46, 0x8d, 0x4d, + 0xab, 0x14, 0x9d, 0x26, 0xe9, 0x81, 0xa0, 0xc8, 0xb5, 0x4c, 0x44, 0xe2, 0x2a, 0xe4, 0x4a, 0x8e, + 0xfa, 0x04, 0x81, 0xda, 0x43, 0x5f, 0x40, 0x40, 0x81, 0xbe, 0x40, 0x1f, 0xa3, 0x45, 0x2f, 0x39, + 0xf6, 0x58, 0x24, 0xe8, 0x63, 0x14, 0x28, 0xb8, 0xbb, 0x12, 0x29, 0x39, 0x96, 0x0f, 0xb9, 0xed, + 0xce, 0x7c, 0xf3, 0x7d, 0x33, 0xb3, 0x9c, 0x5d, 0xc2, 0x76, 0xd0, 0xf2, 0xd4, 0x4e, 0xd0, 0x3e, + 0x22, 0x5e, 0x27, 0x40, 0x21, 0x89, 0xd5, 0x18, 0x77, 0x70, 0xd7, 0xf5, 0x8e, 0x82, 0x10, 0xa9, + 0x83, 0x9d, 0xec, 0x56, 0xe9, 0x45, 0x98, 0x60, 0xf1, 0x4a, 0xd0, 0xf2, 0x94, 0x6c, 0x88, 0x92, + 0xc5, 0x0c, 0x76, 0xa4, 0x6b, 0x09, 0xa7, 0x87, 0x23, 0xa4, 0x7a, 0x38, 0x0c, 0x91, 0x47, 0x02, + 0x1c, 0xaa, 0x83, 0xed, 0xcc, 0x8e, 0x31, 0x49, 0x1f, 0xa4, 0xc0, 0x23, 0x37, 0x0c, 0x51, 0x87, + 0xa2, 0xd8, 0x92, 0x43, 0xd6, 0xdb, 0xb8, 0x8d, 0xe9, 0x52, 0x4d, 0x56, 0xdc, 0xba, 0xd1, 0xc6, + 0xb8, 0xdd, 0x41, 0x2a, 0xdd, 0xb5, 0xfa, 0x87, 0xaa, 0x1b, 0x0e, 0x99, 0x6b, 0xf3, 0x1f, 0x01, + 0x4a, 0x3a, 0xcd, 0xab, 0x49, 0x5c, 0x82, 0x44, 0x09, 0x0a, 0x31, 0x7a, 0xde, 0x47, 0xa1, 0x87, + 0x2a, 0x82, 0x2c, 0x6c, 0xe5, 0xad, 0xe9, 0x5e, 0xbc, 0x04, 0xc5, 0x20, 0x76, 0x0e, 0x23, 0xfc, + 0x03, 0x0a, 0x2b, 0x39, 0x59, 0xd8, 0x2a, 0x58, 0x85, 0x20, 0xbe, 0x4f, 0xf7, 0xe2, 0x63, 0x58, + 0xf3, 0x70, 0x18, 0xa3, 0x30, 0xee, 0xc7, 0x4e, 0x9c, 0x70, 0x55, 0xce, 0xc9, 0xc2, 0x56, 0x69, + 0x47, 0x55, 0xce, 0x68, 0x80, 0xa2, 0x4f, 0xe2, 0x68, 0x0a, 0xd6, 0xaa, 0x37, 0xb3, 0x17, 0xef, + 0xc2, 0x25, 0xb7, 0xd3, 0xc1, 0xc7, 0x4e, 0xbf, 0xe7, 0xbb, 0x04, 0x39, 0xee, 0x21, 0x41, 0x91, + 0xd3, 0x8b, 0x70, 0x0f, 0xc7, 0x6e, 0xa7, 0x92, 0xa7, 0x89, 0x54, 0x28, 0xe4, 0x80, 0x22, 0xb4, + 0x04, 0xd0, 0xe0, 0xfe, 0x3b, 0xf9, 0x97, 0xbf, 0x5c, 0x59, 0xda, 0xfc, 0x51, 0x80, 0xd5, 0x59, + 0x1d, 0xf1, 0x26, 0x40, 0xaf, 0xdf, 0xea, 0x04, 0x9e, 0xf3, 0x0c, 0x0d, 0x69, 0xb1, 0xa5, 0x9d, + 0x75, 0x85, 0xb5, 0x4a, 0x99, 0xb4, 0x4a, 0xd1, 0xc2, 0xa1, 0x55, 0x64, 0xb8, 0x87, 0x68, 0x28, + 0xca, 0x50, 0xf2, 0x83, 0x01, 0x8a, 0xe2, 0xe0, 0x30, 0x40, 0x11, 0xed, 0x42, 0xd1, 0xca, 0x9a, + 0xc4, 0xcb, 0x50, 0x24, 0x41, 0x17, 0xc5, 0xc4, 0xed, 0xf6, 0x68, 0x0b, 0xf2, 0x56, 0x6a, 0xe0, + 0xd9, 0xfc, 0x29, 0xc0, 0xf2, 0x03, 0xe4, 0xfa, 0x28, 0x5a, 0xd8, 0xf0, 0x19, 0xaa, 0xdc, 0x1c, + 0x55, 0xe2, 0x8d, 0x83, 0x76, 0xe8, 0x92, 0x7e, 0xc4, 0x7a, 0xbd, 0x62, 0xa5, 0x06, 0xf1, 0x0e, + 0xac, 0x86, 0xe8, 0xd8, 0xc9, 0x54, 0x98, 0x5f, 0x50, 0xe1, 0x4a, 0x88, 0x8e, 0x1b, 0xd3, 0x22, + 0xaf, 0xc1, 0x5a, 0x12, 0x9b, 0x2d, 0xf4, 0x3c, 0x2d, 0x34, 0xa1, 0xac, 0xa5, 0x56, 0x5e, 0xcd, + 0xbf, 0x02, 0xac, 0xec, 0x05, 0x71, 0x0b, 0x1d, 0xb9, 0x83, 0x00, 0xf7, 0xa3, 0xe4, 0x43, 0x61, + 0x47, 0xed, 0x04, 0x3e, 0x2d, 0xaa, 0x68, 0x15, 0x98, 0xa1, 0xee, 0xcf, 0x14, 0x9c, 0x9b, 0x2b, + 0xf8, 0x11, 0x5c, 0x9c, 0x56, 0xe0, 0xe0, 0x70, 0xf2, 0x09, 0x6d, 0x9f, 0xf9, 0x09, 0x35, 0x27, + 0x51, 0x5a, 0xe8, 0xd7, 0x5c, 0xe2, 0x5a, 0x2b, 0x53, 0x9e, 0xfd, 0x70, 0x8e, 0x97, 0x1c, 0x63, + 0xde, 0x8b, 0x77, 0xe2, 0xb5, 0x8f, 0x31, 0xaf, 0xff, 0x37, 0x01, 0xca, 0xf3, 0xc0, 0xd9, 0xd3, + 0x11, 0xe6, 0x4f, 0xe7, 0x3e, 0x14, 0x7d, 0x97, 0xb8, 0x0e, 0x19, 0xf6, 0x58, 0x17, 0x56, 0x77, + 0x3e, 0x3e, 0x33, 0x99, 0x84, 0xd7, 0x1e, 0xf6, 0x90, 0x55, 0xf0, 0xf9, 0x4a, 0x14, 0x21, 0x9f, + 0xac, 0xf9, 0xf1, 0xd3, 0xf5, 0xec, 0x57, 0x93, 0x7f, 0xfb, 0x07, 0x88, 0xa0, 0x62, 0x4f, 0x4c, + 0xc8, 0x9f, 0x26, 0x4f, 0x33, 0xbf, 0x0a, 0xab, 0x69, 0xb3, 0x28, 0x3b, 0x4b, 0x3f, 0x6d, 0x61, + 0xed, 0x84, 0x4c, 0xee, 0xed, 0x32, 0x7f, 0x08, 0x50, 0x4c, 0xc8, 0xef, 0x0d, 0x09, 0x8a, 0xdf, + 0xe1, 0x53, 0x9f, 0x9b, 0xba, 0x73, 0x27, 0xa7, 0x6e, 0xa6, 0xa1, 0xf9, 0x77, 0x6f, 0xe8, 0xf9, + 0xb4, 0xa1, 0xbc, 0x96, 0xe7, 0x00, 0x6c, 0x64, 0x69, 0xf5, 0x9f, 0x41, 0x89, 0x8f, 0xd7, 0xd9, + 0xb7, 0x07, 0x9b, 0xad, 0x53, 0x06, 0x2b, 0xb7, 0x60, 0xb0, 0x7c, 0x58, 0xcb, 0xdc, 0xcd, 0x54, + 0x57, 0x84, 0x7c, 0xcf, 0x25, 0x47, 0xfc, 0x48, 0xe8, 0x5a, 0xbc, 0x0d, 0x2b, 0x7c, 0xdc, 0xd8, + 0xbd, 0x9b, 0x5b, 0x90, 0x4c, 0xc9, 0x4b, 0x09, 0xb9, 0x4a, 0x17, 0xc4, 0xd9, 0x9b, 0xf1, 0x54, + 0xa1, 0xbb, 0x27, 0xef, 0xf8, 0x45, 0x5a, 0x73, 0x17, 0x39, 0x97, 0x1b, 0xc0, 0xff, 0xf4, 0xe9, + 0xcb, 0xb6, 0x58, 0xcf, 0x00, 0x48, 0x1f, 0x41, 0x2e, 0x75, 0x95, 0x9e, 0x6a, 0xf2, 0x0a, 0x2a, + 0x99, 0x07, 0x72, 0xb0, 0xad, 0xa4, 0xa4, 0x46, 0xe8, 0x5b, 0x99, 0xc0, 0x69, 0x33, 0xcb, 0x3a, + 0x7b, 0x2b, 0x17, 0x8b, 0xde, 0x82, 0x0b, 0xfc, 0x4d, 0xe5, 0x8a, 0x97, 0x33, 0x8a, 0xfc, 0xb1, + 0x4d, 0xe4, 0xd8, 0xd2, 0x9a, 0x80, 0xb9, 0xca, 0x37, 0xb0, 0xde, 0x70, 0xbd, 0x67, 0x88, 0xe8, + 0xb8, 0xdb, 0x0d, 0x48, 0x17, 0x85, 0xe4, 0x54, 0xa5, 0x6a, 0x52, 0xde, 0x04, 0x45, 0xc5, 0x56, + 0xac, 0x8c, 0x65, 0xf3, 0x09, 0x6c, 0x30, 0x2e, 0xcd, 0x7b, 0x16, 0xe2, 0xe3, 0x0e, 0xf2, 0xdb, + 0x68, 0x21, 0xe1, 0x16, 0xac, 0xb9, 0xb3, 0x50, 0xce, 0x3a, 0x6f, 0xde, 0x54, 0xa0, 0xc2, 0xa8, + 0x2d, 0xe4, 0xa1, 0xa0, 0x47, 0xb4, 0x56, 0x9c, 0x8c, 0xe1, 0x69, 0xcc, 0x9b, 0x26, 0xac, 0x9b, + 0xe8, 0x05, 0x69, 0xf2, 0x71, 0xb5, 0x90, 0x37, 0x38, 0x35, 0x8b, 0x4d, 0xb8, 0x18, 0xa2, 0x17, + 0xc4, 0x89, 0xd1, 0x73, 0x27, 0x42, 0xde, 0x80, 0x8f, 0x73, 0x29, 0x64, 0x04, 0x49, 0xec, 0x27, + 0x3f, 0xe5, 0xa1, 0x30, 0x99, 0x3e, 0xf1, 0x0b, 0xf8, 0xb0, 0xa6, 0xd9, 0x9a, 0x63, 0x3f, 0x69, + 0x18, 0xce, 0x81, 0x59, 0x37, 0xeb, 0x76, 0x5d, 0xdb, 0xad, 0x3f, 0x35, 0x6a, 0xce, 0x81, 0xd9, + 0x6c, 0x18, 0x7a, 0xfd, 0x7e, 0xdd, 0xa8, 0x95, 0x97, 0xa4, 0xb5, 0xd1, 0x58, 0x2e, 0x65, 0x4c, + 0xe2, 0x47, 0xf0, 0x5e, 0x1a, 0xa9, 0xef, 0xd6, 0x0d, 0xd3, 0x76, 0x9a, 0xb6, 0x66, 0x1b, 0x65, + 0x41, 0x82, 0xd1, 0x58, 0x5e, 0x66, 0x36, 0xf1, 0x53, 0xd8, 0xc8, 0xe0, 0xf6, 0xcd, 0xa6, 0x61, + 0x36, 0x0f, 0x9a, 0x1c, 0x9a, 0x93, 0x2e, 0x8e, 0xc6, 0x72, 0x71, 0x6a, 0x16, 0x15, 0x90, 0x66, + 0xd0, 0xa6, 0xa1, 0xdb, 0xf5, 0x7d, 0x93, 0xc3, 0xcf, 0x49, 0xab, 0xa3, 0xb1, 0x0c, 0xa9, 0x5d, + 0xdc, 0x82, 0xf7, 0x33, 0xf8, 0x07, 0x9a, 0x69, 0x1a, 0xbb, 0x1c, 0x9c, 0x97, 0x4a, 0xa3, 0xb1, + 0x7c, 0x81, 0x1b, 0xc5, 0xcf, 0xe1, 0x52, 0x8a, 0x6c, 0x68, 0xfa, 0x43, 0xc3, 0x76, 0xf4, 0xfd, + 0xbd, 0xbd, 0xba, 0xbd, 0x67, 0x98, 0x76, 0xf9, 0xbc, 0xb4, 0x3e, 0x1a, 0xcb, 0x65, 0xe6, 0x48, + 0xed, 0xe2, 0x57, 0x20, 0x9f, 0x08, 0xd3, 0xf4, 0x87, 0xe6, 0xfe, 0x77, 0xbb, 0x46, 0xed, 0x6b, + 0x83, 0xc6, 0x2e, 0x4b, 0x1b, 0xa3, 0xb1, 0xfc, 0x7f, 0xe6, 0x9d, 0x73, 0x8a, 0x5f, 0xbe, 0x85, + 0xc0, 0x32, 0x74, 0xa3, 0xde, 0xb0, 0x1d, 0xed, 0x5e, 0xd3, 0x30, 0x75, 0xa3, 0x7c, 0x41, 0xaa, + 0x8c, 0xc6, 0xf2, 0x3a, 0xf3, 0x72, 0x27, 0xf7, 0x89, 0xb7, 0xe0, 0x72, 0x1a, 0x6f, 0x1a, 0x8f, + 0x6d, 0xa7, 0x69, 0x7c, 0x7b, 0x90, 0xb8, 0x12, 0x9a, 0x47, 0xe5, 0x02, 0x4b, 0x3c, 0xf1, 0x4c, + 0x1c, 0x89, 0x5d, 0x94, 0xa1, 0x9c, 0xc6, 0x3d, 0x30, 0xb4, 0x9a, 0x61, 0x95, 0x8b, 0xec, 0x64, + 0xd8, 0x4e, 0xca, 0xbf, 0xfc, 0xb5, 0xba, 0x74, 0xef, 0xfb, 0xdf, 0x5f, 0x57, 0x85, 0x57, 0xaf, + 0xab, 0xc2, 0xdf, 0xaf, 0xab, 0xc2, 0xcf, 0x6f, 0xaa, 0x4b, 0xaf, 0xde, 0x54, 0x97, 0xfe, 0x7a, + 0x53, 0x5d, 0x7a, 0xaa, 0xb5, 0x03, 0x72, 0xd4, 0x6f, 0x29, 0x1e, 0xee, 0xaa, 0x1e, 0x8e, 0xbb, + 0x38, 0x56, 0x83, 0x96, 0x77, 0xbd, 0x8d, 0xd5, 0xc1, 0xf6, 0x0d, 0xb5, 0x8b, 0xfd, 0x7e, 0x07, + 0xc5, 0xec, 0xa7, 0xf8, 0xc6, 0xce, 0x75, 0x76, 0xb9, 0xa9, 0xdd, 0xa0, 0x1d, 0xb9, 0xc9, 0xe8, + 0xc7, 0xea, 0xe0, 0x76, 0x6b, 0x99, 0x5e, 0x4a, 0x37, 0xff, 0x0b, 0x00, 0x00, 0xff, 0xff, 0xe7, + 0x7a, 0x6d, 0x61, 0xbc, 0x0b, 0x00, 0x00, +} + +func (m *ClientState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ClientState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ClientState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.AllowUpdateAfterProposal { + i-- + if m.AllowUpdateAfterProposal { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x20 + } + if m.ConsensusState != nil { + { + size, err := m.ConsensusState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintSolomachine(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.IsFrozen { + i-- + if m.IsFrozen { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x10 + } + if m.Sequence != 0 { + i = encodeVarintSolomachine(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *ConsensusState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ConsensusState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ConsensusState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Timestamp != 0 { + i = encodeVarintSolomachine(dAtA, i, uint64(m.Timestamp)) + i-- + dAtA[i] = 0x18 + } + if len(m.Diversifier) > 0 { + i -= len(m.Diversifier) + copy(dAtA[i:], m.Diversifier) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Diversifier))) + i-- + dAtA[i] = 0x12 + } + if m.PublicKey != nil { + { + size, err := m.PublicKey.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintSolomachine(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Header) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Header) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Header) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.NewDiversifier) > 0 { + i -= len(m.NewDiversifier) + copy(dAtA[i:], m.NewDiversifier) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.NewDiversifier))) + i-- + dAtA[i] = 0x2a + } + if m.NewPublicKey != nil { + { + size, err := m.NewPublicKey.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintSolomachine(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + if len(m.Signature) > 0 { + i -= len(m.Signature) + copy(dAtA[i:], m.Signature) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Signature))) + i-- + dAtA[i] = 0x1a + } + if m.Timestamp != 0 { + i = encodeVarintSolomachine(dAtA, i, uint64(m.Timestamp)) + i-- + dAtA[i] = 0x10 + } + if m.Sequence != 0 { + i = encodeVarintSolomachine(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *Misbehaviour) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Misbehaviour) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Misbehaviour) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.SignatureTwo != nil { + { + size, err := m.SignatureTwo.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintSolomachine(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + if m.SignatureOne != nil { + { + size, err := m.SignatureOne.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintSolomachine(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.Sequence != 0 { + i = encodeVarintSolomachine(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x10 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *SignatureAndData) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SignatureAndData) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *SignatureAndData) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Timestamp != 0 { + i = encodeVarintSolomachine(dAtA, i, uint64(m.Timestamp)) + i-- + dAtA[i] = 0x20 + } + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0x1a + } + if m.DataType != 0 { + i = encodeVarintSolomachine(dAtA, i, uint64(m.DataType)) + i-- + dAtA[i] = 0x10 + } + if len(m.Signature) > 0 { + i -= len(m.Signature) + copy(dAtA[i:], m.Signature) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Signature))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *TimestampedSignatureData) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TimestampedSignatureData) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TimestampedSignatureData) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Timestamp != 0 { + i = encodeVarintSolomachine(dAtA, i, uint64(m.Timestamp)) + i-- + dAtA[i] = 0x10 + } + if len(m.SignatureData) > 0 { + i -= len(m.SignatureData) + copy(dAtA[i:], m.SignatureData) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.SignatureData))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *SignBytes) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SignBytes) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *SignBytes) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0x2a + } + if m.DataType != 0 { + i = encodeVarintSolomachine(dAtA, i, uint64(m.DataType)) + i-- + dAtA[i] = 0x20 + } + if len(m.Diversifier) > 0 { + i -= len(m.Diversifier) + copy(dAtA[i:], m.Diversifier) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Diversifier))) + i-- + dAtA[i] = 0x1a + } + if m.Timestamp != 0 { + i = encodeVarintSolomachine(dAtA, i, uint64(m.Timestamp)) + i-- + dAtA[i] = 0x10 + } + if m.Sequence != 0 { + i = encodeVarintSolomachine(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *HeaderData) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *HeaderData) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *HeaderData) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.NewDiversifier) > 0 { + i -= len(m.NewDiversifier) + copy(dAtA[i:], m.NewDiversifier) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.NewDiversifier))) + i-- + dAtA[i] = 0x12 + } + if m.NewPubKey != nil { + { + size, err := m.NewPubKey.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintSolomachine(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ClientStateData) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ClientStateData) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ClientStateData) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.ClientState != nil { + { + size, err := m.ClientState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintSolomachine(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Path) > 0 { + i -= len(m.Path) + copy(dAtA[i:], m.Path) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Path))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ConsensusStateData) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ConsensusStateData) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ConsensusStateData) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.ConsensusState != nil { + { + size, err := m.ConsensusState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintSolomachine(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Path) > 0 { + i -= len(m.Path) + copy(dAtA[i:], m.Path) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Path))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ConnectionStateData) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ConnectionStateData) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ConnectionStateData) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Connection != nil { + { + size, err := m.Connection.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintSolomachine(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Path) > 0 { + i -= len(m.Path) + copy(dAtA[i:], m.Path) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Path))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ChannelStateData) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ChannelStateData) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ChannelStateData) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Channel != nil { + { + size, err := m.Channel.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintSolomachine(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Path) > 0 { + i -= len(m.Path) + copy(dAtA[i:], m.Path) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Path))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *PacketCommitmentData) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PacketCommitmentData) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PacketCommitmentData) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Commitment) > 0 { + i -= len(m.Commitment) + copy(dAtA[i:], m.Commitment) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Commitment))) + i-- + dAtA[i] = 0x12 + } + if len(m.Path) > 0 { + i -= len(m.Path) + copy(dAtA[i:], m.Path) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Path))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *PacketAcknowledgementData) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PacketAcknowledgementData) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PacketAcknowledgementData) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Acknowledgement) > 0 { + i -= len(m.Acknowledgement) + copy(dAtA[i:], m.Acknowledgement) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Acknowledgement))) + i-- + dAtA[i] = 0x12 + } + if len(m.Path) > 0 { + i -= len(m.Path) + copy(dAtA[i:], m.Path) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Path))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *PacketReceiptAbsenceData) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PacketReceiptAbsenceData) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PacketReceiptAbsenceData) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Path) > 0 { + i -= len(m.Path) + copy(dAtA[i:], m.Path) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Path))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *NextSequenceRecvData) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *NextSequenceRecvData) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *NextSequenceRecvData) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.NextSeqRecv != 0 { + i = encodeVarintSolomachine(dAtA, i, uint64(m.NextSeqRecv)) + i-- + dAtA[i] = 0x10 + } + if len(m.Path) > 0 { + i -= len(m.Path) + copy(dAtA[i:], m.Path) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Path))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintSolomachine(dAtA []byte, offset int, v uint64) int { + offset -= sovSolomachine(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *ClientState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Sequence != 0 { + n += 1 + sovSolomachine(uint64(m.Sequence)) + } + if m.IsFrozen { + n += 2 + } + if m.ConsensusState != nil { + l = m.ConsensusState.Size() + n += 1 + l + sovSolomachine(uint64(l)) + } + if m.AllowUpdateAfterProposal { + n += 2 + } + return n +} + +func (m *ConsensusState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.PublicKey != nil { + l = m.PublicKey.Size() + n += 1 + l + sovSolomachine(uint64(l)) + } + l = len(m.Diversifier) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + if m.Timestamp != 0 { + n += 1 + sovSolomachine(uint64(m.Timestamp)) + } + return n +} + +func (m *Header) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Sequence != 0 { + n += 1 + sovSolomachine(uint64(m.Sequence)) + } + if m.Timestamp != 0 { + n += 1 + sovSolomachine(uint64(m.Timestamp)) + } + l = len(m.Signature) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + if m.NewPublicKey != nil { + l = m.NewPublicKey.Size() + n += 1 + l + sovSolomachine(uint64(l)) + } + l = len(m.NewDiversifier) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + return n +} + +func (m *Misbehaviour) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + if m.Sequence != 0 { + n += 1 + sovSolomachine(uint64(m.Sequence)) + } + if m.SignatureOne != nil { + l = m.SignatureOne.Size() + n += 1 + l + sovSolomachine(uint64(l)) + } + if m.SignatureTwo != nil { + l = m.SignatureTwo.Size() + n += 1 + l + sovSolomachine(uint64(l)) + } + return n +} + +func (m *SignatureAndData) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signature) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + if m.DataType != 0 { + n += 1 + sovSolomachine(uint64(m.DataType)) + } + l = len(m.Data) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + if m.Timestamp != 0 { + n += 1 + sovSolomachine(uint64(m.Timestamp)) + } + return n +} + +func (m *TimestampedSignatureData) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.SignatureData) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + if m.Timestamp != 0 { + n += 1 + sovSolomachine(uint64(m.Timestamp)) + } + return n +} + +func (m *SignBytes) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Sequence != 0 { + n += 1 + sovSolomachine(uint64(m.Sequence)) + } + if m.Timestamp != 0 { + n += 1 + sovSolomachine(uint64(m.Timestamp)) + } + l = len(m.Diversifier) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + if m.DataType != 0 { + n += 1 + sovSolomachine(uint64(m.DataType)) + } + l = len(m.Data) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + return n +} + +func (m *HeaderData) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.NewPubKey != nil { + l = m.NewPubKey.Size() + n += 1 + l + sovSolomachine(uint64(l)) + } + l = len(m.NewDiversifier) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + return n +} + +func (m *ClientStateData) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Path) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + if m.ClientState != nil { + l = m.ClientState.Size() + n += 1 + l + sovSolomachine(uint64(l)) + } + return n +} + +func (m *ConsensusStateData) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Path) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + if m.ConsensusState != nil { + l = m.ConsensusState.Size() + n += 1 + l + sovSolomachine(uint64(l)) + } + return n +} + +func (m *ConnectionStateData) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Path) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + if m.Connection != nil { + l = m.Connection.Size() + n += 1 + l + sovSolomachine(uint64(l)) + } + return n +} + +func (m *ChannelStateData) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Path) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + if m.Channel != nil { + l = m.Channel.Size() + n += 1 + l + sovSolomachine(uint64(l)) + } + return n +} + +func (m *PacketCommitmentData) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Path) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + l = len(m.Commitment) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + return n +} + +func (m *PacketAcknowledgementData) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Path) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + l = len(m.Acknowledgement) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + return n +} + +func (m *PacketReceiptAbsenceData) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Path) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + return n +} + +func (m *NextSequenceRecvData) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Path) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + if m.NextSeqRecv != 0 { + n += 1 + sovSolomachine(uint64(m.NextSeqRecv)) + } + return n +} + +func sovSolomachine(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozSolomachine(x uint64) (n int) { + return sovSolomachine(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *ClientState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ClientState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ClientState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IsFrozen", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.IsFrozen = bool(v != 0) + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConsensusState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ConsensusState == nil { + m.ConsensusState = &ConsensusState{} + } + if err := m.ConsensusState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowUpdateAfterProposal", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.AllowUpdateAfterProposal = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ConsensusState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ConsensusState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ConsensusState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PublicKey", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.PublicKey == nil { + m.PublicKey = &types.Any{} + } + if err := m.PublicKey.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Diversifier", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Diversifier = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Timestamp |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Header) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Header: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Header: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Timestamp |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signature", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signature = append(m.Signature[:0], dAtA[iNdEx:postIndex]...) + if m.Signature == nil { + m.Signature = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NewPublicKey", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.NewPublicKey == nil { + m.NewPublicKey = &types.Any{} + } + if err := m.NewPublicKey.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NewDiversifier", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NewDiversifier = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Misbehaviour) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Misbehaviour: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Misbehaviour: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SignatureOne", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.SignatureOne == nil { + m.SignatureOne = &SignatureAndData{} + } + if err := m.SignatureOne.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SignatureTwo", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.SignatureTwo == nil { + m.SignatureTwo = &SignatureAndData{} + } + if err := m.SignatureTwo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SignatureAndData) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SignatureAndData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SignatureAndData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signature", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signature = append(m.Signature[:0], dAtA[iNdEx:postIndex]...) + if m.Signature == nil { + m.Signature = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DataType", wireType) + } + m.DataType = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DataType |= DataType(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Timestamp |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TimestampedSignatureData) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TimestampedSignatureData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TimestampedSignatureData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SignatureData", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SignatureData = append(m.SignatureData[:0], dAtA[iNdEx:postIndex]...) + if m.SignatureData == nil { + m.SignatureData = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Timestamp |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SignBytes) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SignBytes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SignBytes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Timestamp |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Diversifier", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Diversifier = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DataType", wireType) + } + m.DataType = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DataType |= DataType(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *HeaderData) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: HeaderData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: HeaderData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NewPubKey", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.NewPubKey == nil { + m.NewPubKey = &types.Any{} + } + if err := m.NewPubKey.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NewDiversifier", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NewDiversifier = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ClientStateData) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ClientStateData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ClientStateData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Path", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Path = append(m.Path[:0], dAtA[iNdEx:postIndex]...) + if m.Path == nil { + m.Path = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ClientState == nil { + m.ClientState = &types.Any{} + } + if err := m.ClientState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ConsensusStateData) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ConsensusStateData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ConsensusStateData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Path", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Path = append(m.Path[:0], dAtA[iNdEx:postIndex]...) + if m.Path == nil { + m.Path = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConsensusState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ConsensusState == nil { + m.ConsensusState = &types.Any{} + } + if err := m.ConsensusState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ConnectionStateData) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ConnectionStateData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ConnectionStateData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Path", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Path = append(m.Path[:0], dAtA[iNdEx:postIndex]...) + if m.Path == nil { + m.Path = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Connection", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Connection == nil { + m.Connection = &types1.ConnectionEnd{} + } + if err := m.Connection.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ChannelStateData) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ChannelStateData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ChannelStateData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Path", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Path = append(m.Path[:0], dAtA[iNdEx:postIndex]...) + if m.Path == nil { + m.Path = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Channel", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Channel == nil { + m.Channel = &types2.Channel{} + } + if err := m.Channel.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PacketCommitmentData) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PacketCommitmentData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PacketCommitmentData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Path", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Path = append(m.Path[:0], dAtA[iNdEx:postIndex]...) + if m.Path == nil { + m.Path = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Commitment", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Commitment = append(m.Commitment[:0], dAtA[iNdEx:postIndex]...) + if m.Commitment == nil { + m.Commitment = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PacketAcknowledgementData) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PacketAcknowledgementData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PacketAcknowledgementData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Path", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Path = append(m.Path[:0], dAtA[iNdEx:postIndex]...) + if m.Path == nil { + m.Path = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Acknowledgement", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Acknowledgement = append(m.Acknowledgement[:0], dAtA[iNdEx:postIndex]...) + if m.Acknowledgement == nil { + m.Acknowledgement = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PacketReceiptAbsenceData) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PacketReceiptAbsenceData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PacketReceiptAbsenceData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Path", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Path = append(m.Path[:0], dAtA[iNdEx:postIndex]...) + if m.Path == nil { + m.Path = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *NextSequenceRecvData) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: NextSequenceRecvData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: NextSequenceRecvData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Path", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Path = append(m.Path[:0], dAtA[iNdEx:postIndex]...) + if m.Path == nil { + m.Path = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NextSeqRecv", wireType) + } + m.NextSeqRecv = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.NextSeqRecv |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipSolomachine(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowSolomachine + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowSolomachine + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowSolomachine + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthSolomachine + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupSolomachine + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthSolomachine + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthSolomachine = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowSolomachine = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupSolomachine = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/02-client/migrations/v7/store.go b/modules/core/02-client/migrations/v7/store.go new file mode 100644 index 0000000..ca97efc --- /dev/null +++ b/modules/core/02-client/migrations/v7/store.go @@ -0,0 +1,204 @@ +package v7 + +import ( + "strings" + + corestore "cosmossdk.io/core/store" + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" +) + +// Localhost is the client type for a localhost client. It is also used as the clientID +// for the localhost client. +const Localhost string = "09-localhost" + +// MigrateStore performs in-place store migrations from ibc-go v6 to ibc-go v7. +// The migration includes: +// +// - Migrating solo machine client states from v2 to v3 protobuf definition +// - Pruning all solo machine consensus states +// - Removing the localhost client +// - Asserting existing tendermint clients are properly registered on the chain codec +func MigrateStore(ctx sdk.Context, storeService corestore.KVStoreService, cdc codec.BinaryCodec, clientKeeper ClientKeeper) error { + store := runtime.KVStoreAdapter(storeService.OpenKVStore(ctx)) + + if err := handleSolomachineMigration(ctx, store, cdc, clientKeeper); err != nil { + return err + } + + if err := handleTendermintMigration(ctx, store, clientKeeper); err != nil { + return err + } + + return handleLocalhostMigration(ctx, store, clientKeeper) +} + +// handleSolomachineMigration iterates over the solo machine clients and migrates client state from +// protobuf definition v2 to v3. All consensus states stored outside of the client state are pruned. +func handleSolomachineMigration(ctx sdk.Context, store storetypes.KVStore, cdc codec.BinaryCodec, clientKeeper ClientKeeper) error { + clients, err := collectClients(ctx, store, exported.Solomachine) + if err != nil { + return err + } + + for _, clientID := range clients { + clientStore := clientKeeper.ClientStore(ctx, clientID) + + bz := clientStore.Get(host.ClientStateKey()) + if len(bz) == 0 { + return errorsmod.Wrapf(clienttypes.ErrClientNotFound, "clientID %s", clientID) + } + + var protoAny codectypes.Any + if err := cdc.Unmarshal(bz, &protoAny); err != nil { + return errorsmod.Wrap(err, "failed to unmarshal client state bytes into solo machine client state") + } + + var clientState ClientState + if err := cdc.Unmarshal(protoAny.Value, &clientState); err != nil { + return errorsmod.Wrap(err, "failed to unmarshal client state bytes into solo machine client state") + } + + updatedClientState := migrateSolomachine(clientState) + + // update solomachine in store + clientKeeper.SetClientState(ctx, clientID, &updatedClientState) + + removeAllClientConsensusStates(clientStore) + } + + return nil +} + +// handleTendermintMigration asserts that the tendermint client in state can be decoded properly. +// This ensures the upgrading chain properly registered the tendermint client types on the chain codec. +func handleTendermintMigration(ctx sdk.Context, store storetypes.KVStore, clientKeeper ClientKeeper) error { + clients, err := collectClients(ctx, store, exported.Tendermint) + if err != nil { + return err + } + + if len(clients) == 0 { + return nil // no-op if no tm clients exist + } + + if len(clients) > 1 { + return errorsmod.Wrap(ibcerrors.ErrLogic, "more than one Tendermint client collected") + } + + clientID := clients[0] + + // unregistered tendermint client types will panic when unmarshaling the client state + // in GetClientState + clientState, ok := clientKeeper.GetClientState(ctx, clientID) + if !ok { + return errorsmod.Wrapf(clienttypes.ErrClientNotFound, "clientID %s", clientID) + } + + _, ok = clientState.(*ibctm.ClientState) + if !ok { + return errorsmod.Wrap(clienttypes.ErrInvalidClient, "client state is not tendermint even though client id contains 07-tendermint") + } + + return nil +} + +// handleLocalhostMigration removes all client and consensus states associated with the localhost client type. +func handleLocalhostMigration(ctx sdk.Context, store storetypes.KVStore, clientKeeper ClientKeeper) error { + clients, err := collectClients(ctx, store, Localhost) + if err != nil { + return err + } + + for _, clientID := range clients { + clientStore := clientKeeper.ClientStore(ctx, clientID) + + // delete the client state + clientStore.Delete(host.ClientStateKey()) + + removeAllClientConsensusStates(clientStore) + } + + return nil +} + +// collectClients will iterate over the provided client type prefix in the client store +// and return a list of clientIDs associated with the client type. This is necessary to +// avoid state corruption as modifying state during iteration is unsafe. A special case +// for tendermint clients is included as only one tendermint clientID is required for +// v7 migrations. +func collectClients(ctx sdk.Context, store storetypes.KVStore, clientType string) (clients []string, err error) { + clientPrefix := host.PrefixedClientStoreKey([]byte(clientType)) + iterator := storetypes.KVStorePrefixIterator(store, clientPrefix) + + defer sdk.LogDeferred(ctx.Logger(), func() error { return iterator.Close() }) + for ; iterator.Valid(); iterator.Next() { + path := string(iterator.Key()) + if !strings.Contains(path, host.KeyClientState) { + // skip non client state keys + continue + } + + clientID := host.MustParseClientStatePath(path) + clients = append(clients, clientID) + + // optimization: exit after a single tendermint client iteration + if strings.Contains(clientID, exported.Tendermint) { + return clients, nil + } + } + + return clients, nil +} + +// removeAllClientConsensusStates removes all client consensus states from the associated +// client store. +func removeAllClientConsensusStates(clientStore storetypes.KVStore) { + iterator := storetypes.KVStorePrefixIterator(clientStore, []byte(host.KeyConsensusStatePrefix)) + var heights []exported.Height + + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + keySplit := strings.Split(string(iterator.Key()), "/") + // key is in the format "consensusStates/" + if len(keySplit) != 2 || keySplit[0] != string(host.KeyConsensusStatePrefix) { + continue + } + + // collect consensus states to be pruned + heights = append(heights, clienttypes.MustParseHeight(keySplit[1])) + } + + // delete all consensus states + for _, height := range heights { + clientStore.Delete(host.ConsensusStateKey(height)) + } +} + +// migrateSolomachine migrates the solomachine from v2 to v3 solo machine protobuf definition. +// Notably it drops the AllowUpdateAfterProposal field. +func migrateSolomachine(clientState ClientState) solomachine.ClientState { + consensusState := &solomachine.ConsensusState{ + PublicKey: clientState.ConsensusState.PublicKey, + Diversifier: clientState.ConsensusState.Diversifier, + Timestamp: clientState.ConsensusState.Timestamp, + } + + return solomachine.ClientState{ + Sequence: clientState.Sequence, + IsFrozen: clientState.IsFrozen, + ConsensusState: consensusState, + } +} diff --git a/modules/core/02-client/migrations/v7/store_test.go b/modules/core/02-client/migrations/v7/store_test.go new file mode 100644 index 0000000..b2f11fe --- /dev/null +++ b/modules/core/02-client/migrations/v7/store_test.go @@ -0,0 +1,171 @@ +package v7_test + +import ( + "strconv" + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/runtime" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/migrations/v7" + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +// numCreations is the number of clients/consensus states created for +// solo machine and localhost clients +const numCreations = 10 + +type MigrationsV7TestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain +} + +func (suite *MigrationsV7TestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) +} + +func TestIBCTestSuite(t *testing.T) { + testifysuite.Run(t, new(MigrationsV7TestSuite)) +} + +// create multiple solo machine clients, tendermint and localhost clients +// ensure that solo machine clients are migrated and their consensus states are removed +// ensure the localhost is deleted entirely. +func (suite *MigrationsV7TestSuite) TestMigrateStore() { + paths := []*ibctesting.Path{ + ibctesting.NewPath(suite.chainA, suite.chainB), + ibctesting.NewPath(suite.chainA, suite.chainB), + } + + // create tendermint clients + for _, path := range paths { + path.SetupClients() + } + + solomachines := []*ibctesting.Solomachine{ + ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, ibctesting.DefaultSolomachineClientID, "testing", 1), + ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "06-solomachine-1", "testing", 4), + } + + suite.createSolomachineClients(solomachines) + suite.createLocalhostClients() + + err := v7.MigrateStore(suite.chainA.GetContext(), runtime.NewKVStoreService(suite.chainA.GetSimApp().GetKey(ibcexported.StoreKey)), suite.chainA.App.AppCodec(), suite.chainA.GetSimApp().IBCKeeper.ClientKeeper) + suite.Require().NoError(err) + + suite.assertSolomachineClients(solomachines) + suite.assertNoLocalhostClients() +} + +func (suite *MigrationsV7TestSuite) TestMigrateStoreNoTendermintClients() { + solomachines := []*ibctesting.Solomachine{ + ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, ibctesting.DefaultSolomachineClientID, "testing", 1), + ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "06-solomachine-1", "testing", 4), + } + + suite.createSolomachineClients(solomachines) + suite.createLocalhostClients() + + err := v7.MigrateStore(suite.chainA.GetContext(), runtime.NewKVStoreService(suite.chainA.GetSimApp().GetKey(ibcexported.StoreKey)), suite.chainA.App.AppCodec(), suite.chainA.GetSimApp().IBCKeeper.ClientKeeper) + suite.Require().NoError(err) + + suite.assertSolomachineClients(solomachines) + suite.assertNoLocalhostClients() +} + +func (suite *MigrationsV7TestSuite) createSolomachineClients(solomachines []*ibctesting.Solomachine) { + // manually generate old protobuf definitions and set in store + // NOTE: we cannot use 'CreateClient' and 'UpdateClient' functions since we are + // using client states and consensus states which do not implement the exported.ClientState + // and exported.ConsensusState interface + for _, sm := range solomachines { + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), sm.ClientID) + clientState := sm.ClientState() + + // generate old client state proto definition + legacyClientState := &v7.ClientState{ + Sequence: clientState.Sequence, + ConsensusState: &v7.ConsensusState{ + PublicKey: clientState.ConsensusState.PublicKey, + Diversifier: clientState.ConsensusState.Diversifier, + Timestamp: clientState.ConsensusState.Timestamp, + }, + AllowUpdateAfterProposal: true, + } + + cdc, ok := suite.chainA.App.AppCodec().(*codec.ProtoCodec) + suite.Require().True(ok) + v7.RegisterInterfaces(cdc.InterfaceRegistry()) + + bz, err := cdc.MarshalInterface(legacyClientState) + suite.Require().NoError(err) + clientStore.Set(host.ClientStateKey(), bz) + + bz, err = cdc.MarshalInterface(legacyClientState.ConsensusState) + suite.Require().NoError(err) + + // set some consensus states + for i := uint64(0); i < numCreations; i++ { + height := types.NewHeight(1, i) + clientStore.Set(host.ConsensusStateKey(height), bz) + } + + } +} + +func (suite *MigrationsV7TestSuite) assertSolomachineClients(solomachines []*ibctesting.Solomachine) { + // verify client state has been migrated + for _, sm := range solomachines { + clientState, ok := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), sm.ClientID) + suite.Require().True(ok) + suite.Require().Equal(sm.ClientState(), clientState) + + for i := uint64(0); i < numCreations; i++ { + height := types.NewHeight(1, i) + + consState, ok := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientConsensusState(suite.chainA.GetContext(), sm.ClientID, height) + suite.Require().False(ok) + suite.Require().Empty(consState) + } + } +} + +// createLocalhostClients clients creates multiple localhost clients and multiple consensus states for each +func (suite *MigrationsV7TestSuite) createLocalhostClients() { + for numClients := uint64(0); numClients < numCreations; numClients++ { + clientID := v7.Localhost + "-" + strconv.FormatUint(numClients, 10) + clientStore := suite.chainA.GetSimApp().IBCKeeper.ClientKeeper.ClientStore(suite.chainA.GetContext(), clientID) + + clientStore.Set(host.ClientStateKey(), []byte("clientState")) + + for i := range numCreations { + clientStore.Set(host.ConsensusStateKey(types.NewHeight(1, uint64(i))), []byte("consensusState")) + } + } +} + +// assertNoLocalhostClients asserts that all localhost information has been deleted +func (suite *MigrationsV7TestSuite) assertNoLocalhostClients() { + for numClients := uint64(0); numClients < numCreations; numClients++ { + clientID := v7.Localhost + "-" + strconv.FormatUint(numClients, 10) + clientStore := suite.chainA.GetSimApp().IBCKeeper.ClientKeeper.ClientStore(suite.chainA.GetContext(), clientID) + + suite.Require().False(clientStore.Has(host.ClientStateKey())) + + for i := uint64(0); i < numCreations; i++ { + suite.Require().False(clientStore.Has(host.ConsensusStateKey(types.NewHeight(1, i)))) + } + } +} diff --git a/modules/core/02-client/module.go b/modules/core/02-client/module.go new file mode 100644 index 0000000..6ba8ee5 --- /dev/null +++ b/modules/core/02-client/module.go @@ -0,0 +1,23 @@ +package client + +import ( + "github.com/spf13/cobra" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/client/cli" + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" +) + +// Name returns the IBC client name +func Name() string { + return types.SubModuleName +} + +// GetQueryCmd returns no root query command for the IBC client +func GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd() +} + +// GetTxCmd returns the root tx command for 02-client. +func GetTxCmd() *cobra.Command { + return cli.NewTxCmd() +} diff --git a/modules/core/02-client/simulation/decoder.go b/modules/core/02-client/simulation/decoder.go new file mode 100644 index 0000000..4989fad --- /dev/null +++ b/modules/core/02-client/simulation/decoder.go @@ -0,0 +1,31 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's +// Value to the corresponding client type. +func NewDecodeStore(cdc codec.BinaryCodec, kvA, kvB kv.Pair) (string, bool) { + switch { + case bytes.HasPrefix(kvA.Key, host.KeyClientStorePrefix) && bytes.HasSuffix(kvA.Key, host.ClientStateKey()): + clientStateA := types.MustUnmarshalClientState(cdc, kvA.Value) + clientStateB := types.MustUnmarshalClientState(cdc, kvB.Value) + return fmt.Sprintf("ClientState A: %v\nClientState B: %v", clientStateA, clientStateB), true + + case bytes.HasPrefix(kvA.Key, host.KeyClientStorePrefix) && bytes.Contains(kvA.Key, []byte(host.KeyConsensusStatePrefix)): + consensusStateA := types.MustUnmarshalConsensusState(cdc, kvA.Value) + consensusStateB := types.MustUnmarshalConsensusState(cdc, kvB.Value) + return fmt.Sprintf("ConsensusState A: %v\nConsensusState B: %v", consensusStateA, consensusStateB), true + + default: + return "", false + } +} diff --git a/modules/core/02-client/simulation/decoder_test.go b/modules/core/02-client/simulation/decoder_test.go new file mode 100644 index 0000000..6bdf4ee --- /dev/null +++ b/modules/core/02-client/simulation/decoder_test.go @@ -0,0 +1,70 @@ +package simulation_test + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/simulation" + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + "github.com/cosmos/ibc-go/v10/testing/simapp" +) + +func TestDecodeStore(t *testing.T) { + app := simapp.Setup(t, false) + clientID := "clientidone" + + height := types.NewHeight(0, 10) + + clientState := &ibctm.ClientState{ + FrozenHeight: height, + } + + consState := &ibctm.ConsensusState{ + Timestamp: time.Now().UTC(), + } + + kvPairs := kv.Pairs{ + Pairs: []kv.Pair{ + { + Key: host.FullClientStateKey(clientID), + Value: types.MustMarshalClientState(app.AppCodec(), clientState), + }, + { + Key: host.FullConsensusStateKey(clientID, height), + Value: types.MustMarshalConsensusState(app.AppCodec(), consState), + }, + { + Key: []byte{0x99}, + Value: []byte{0x99}, + }, + }, + } + tests := []struct { + name string + expectedLog string + }{ + {"ClientState", fmt.Sprintf("ClientState A: %v\nClientState B: %v", clientState, clientState)}, + {"ConsensusState", fmt.Sprintf("ConsensusState A: %v\nConsensusState B: %v", consState, consState)}, + {"other", ""}, + } + + for i, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + res, found := simulation.NewDecodeStore(app.AppCodec(), kvPairs.Pairs[i], kvPairs.Pairs[i]) + if i == len(tests)-1 { + require.False(t, found, string(kvPairs.Pairs[i].Key)) + require.Empty(t, res, string(kvPairs.Pairs[i].Key)) + } else { + require.True(t, found, string(kvPairs.Pairs[i].Key)) + require.Equal(t, tt.expectedLog, res, string(kvPairs.Pairs[i].Key)) + } + }) + } +} diff --git a/modules/core/02-client/simulation/genesis.go b/modules/core/02-client/simulation/genesis.go new file mode 100644 index 0000000..0568ac5 --- /dev/null +++ b/modules/core/02-client/simulation/genesis.go @@ -0,0 +1,14 @@ +package simulation + +import ( + "math/rand" + + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" +) + +// GenClientGenesis returns the default client genesis state. +func GenClientGenesis(_ *rand.Rand, _ []simtypes.Account) types.GenesisState { + return types.DefaultGenesisState() +} diff --git a/modules/core/02-client/types/client.go b/modules/core/02-client/types/client.go new file mode 100644 index 0000000..1c35a35 --- /dev/null +++ b/modules/core/02-client/types/client.go @@ -0,0 +1,113 @@ +package types + +import ( + "fmt" + "math" + "sort" + "strings" + + "github.com/cosmos/gogoproto/proto" + + errorsmod "cosmossdk.io/errors" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var ( + _ codectypes.UnpackInterfacesMessage = (*IdentifiedClientState)(nil) + _ codectypes.UnpackInterfacesMessage = (*ConsensusStateWithHeight)(nil) +) + +// NewIdentifiedClientState creates a new IdentifiedClientState instance +func NewIdentifiedClientState(clientID string, clientState exported.ClientState) IdentifiedClientState { + msg, ok := clientState.(proto.Message) + if !ok { + panic(fmt.Errorf("cannot proto marshal %T", clientState)) + } + + anyClientState, err := codectypes.NewAnyWithValue(msg) + if err != nil { + panic(err) + } + + return IdentifiedClientState{ + ClientId: clientID, + ClientState: anyClientState, + } +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (ics IdentifiedClientState) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + return unpacker.UnpackAny(ics.ClientState, new(exported.ClientState)) +} + +var _ sort.Interface = (*IdentifiedClientStates)(nil) + +// IdentifiedClientStates defines a slice of ClientConsensusStates that supports the sort interface +type IdentifiedClientStates []IdentifiedClientState + +// Len implements sort.Interface +func (ics IdentifiedClientStates) Len() int { return len(ics) } + +// Less implements sort.Interface +func (ics IdentifiedClientStates) Less(i, j int) bool { return ics[i].ClientId < ics[j].ClientId } + +// Swap implements sort.Interface +func (ics IdentifiedClientStates) Swap(i, j int) { ics[i], ics[j] = ics[j], ics[i] } + +// Sort is a helper function to sort the set of IdentifiedClientStates in place +func (ics IdentifiedClientStates) Sort() IdentifiedClientStates { + sort.Sort(ics) + return ics +} + +// NewConsensusStateWithHeight creates a new ConsensusStateWithHeight instance +func NewConsensusStateWithHeight(height Height, consensusState exported.ConsensusState) ConsensusStateWithHeight { + msg, ok := consensusState.(proto.Message) + if !ok { + panic(fmt.Errorf("cannot proto marshal %T", consensusState)) + } + + anyConsensusState, err := codectypes.NewAnyWithValue(msg) + if err != nil { + panic(err) + } + + return ConsensusStateWithHeight{ + Height: height, + ConsensusState: anyConsensusState, + } +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (cswh ConsensusStateWithHeight) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + return unpacker.UnpackAny(cswh.ConsensusState, new(exported.ConsensusState)) +} + +// ValidateClientType validates the client type. It cannot be blank or empty. It must be a valid +// client identifier when used with '0' or the maximum uint64 as the sequence. +func ValidateClientType(clientType string) error { + if strings.TrimSpace(clientType) == "" { + return errorsmod.Wrap(ErrInvalidClientType, "client type cannot be blank") + } + + smallestPossibleClientID := FormatClientIdentifier(clientType, 0) + largestPossibleClientID := FormatClientIdentifier(clientType, uint64(math.MaxUint64)) + + // IsValidClientID will check client type format and if the sequence is a uint64 + if !IsValidClientID(smallestPossibleClientID) { + return errorsmod.Wrap(ErrInvalidClientType, "") + } + + if err := host.ClientIdentifierValidator(smallestPossibleClientID); err != nil { + return errorsmod.Wrap(err, "client type results in smallest client identifier being invalid") + } + if err := host.ClientIdentifierValidator(largestPossibleClientID); err != nil { + return errorsmod.Wrap(err, "client type results in largest client identifier being invalid") + } + + return nil +} diff --git a/modules/core/02-client/types/client.pb.go b/modules/core/02-client/types/client.pb.go new file mode 100644 index 0000000..4dea62b --- /dev/null +++ b/modules/core/02-client/types/client.pb.go @@ -0,0 +1,1238 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/client/v1/client.proto + +package types + +import ( + fmt "fmt" + types "github.com/cosmos/cosmos-sdk/codec/types" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// IdentifiedClientState defines a client state with an additional client +// identifier field. +type IdentifiedClientState struct { + // client identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // client state + ClientState *types.Any `protobuf:"bytes,2,opt,name=client_state,json=clientState,proto3" json:"client_state,omitempty"` +} + +func (m *IdentifiedClientState) Reset() { *m = IdentifiedClientState{} } +func (m *IdentifiedClientState) String() string { return proto.CompactTextString(m) } +func (*IdentifiedClientState) ProtoMessage() {} +func (*IdentifiedClientState) Descriptor() ([]byte, []int) { + return fileDescriptor_b6bc4c8185546947, []int{0} +} +func (m *IdentifiedClientState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *IdentifiedClientState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_IdentifiedClientState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *IdentifiedClientState) XXX_Merge(src proto.Message) { + xxx_messageInfo_IdentifiedClientState.Merge(m, src) +} +func (m *IdentifiedClientState) XXX_Size() int { + return m.Size() +} +func (m *IdentifiedClientState) XXX_DiscardUnknown() { + xxx_messageInfo_IdentifiedClientState.DiscardUnknown(m) +} + +var xxx_messageInfo_IdentifiedClientState proto.InternalMessageInfo + +func (m *IdentifiedClientState) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +func (m *IdentifiedClientState) GetClientState() *types.Any { + if m != nil { + return m.ClientState + } + return nil +} + +// ConsensusStateWithHeight defines a consensus state with an additional height +// field. +type ConsensusStateWithHeight struct { + // consensus state height + Height Height `protobuf:"bytes,1,opt,name=height,proto3" json:"height"` + // consensus state + ConsensusState *types.Any `protobuf:"bytes,2,opt,name=consensus_state,json=consensusState,proto3" json:"consensus_state,omitempty"` +} + +func (m *ConsensusStateWithHeight) Reset() { *m = ConsensusStateWithHeight{} } +func (m *ConsensusStateWithHeight) String() string { return proto.CompactTextString(m) } +func (*ConsensusStateWithHeight) ProtoMessage() {} +func (*ConsensusStateWithHeight) Descriptor() ([]byte, []int) { + return fileDescriptor_b6bc4c8185546947, []int{1} +} +func (m *ConsensusStateWithHeight) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ConsensusStateWithHeight) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ConsensusStateWithHeight.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ConsensusStateWithHeight) XXX_Merge(src proto.Message) { + xxx_messageInfo_ConsensusStateWithHeight.Merge(m, src) +} +func (m *ConsensusStateWithHeight) XXX_Size() int { + return m.Size() +} +func (m *ConsensusStateWithHeight) XXX_DiscardUnknown() { + xxx_messageInfo_ConsensusStateWithHeight.DiscardUnknown(m) +} + +var xxx_messageInfo_ConsensusStateWithHeight proto.InternalMessageInfo + +func (m *ConsensusStateWithHeight) GetHeight() Height { + if m != nil { + return m.Height + } + return Height{} +} + +func (m *ConsensusStateWithHeight) GetConsensusState() *types.Any { + if m != nil { + return m.ConsensusState + } + return nil +} + +// ClientConsensusStates defines all the stored consensus states for a given +// client. +type ClientConsensusStates struct { + // client identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // consensus states and their heights associated with the client + ConsensusStates []ConsensusStateWithHeight `protobuf:"bytes,2,rep,name=consensus_states,json=consensusStates,proto3" json:"consensus_states"` +} + +func (m *ClientConsensusStates) Reset() { *m = ClientConsensusStates{} } +func (m *ClientConsensusStates) String() string { return proto.CompactTextString(m) } +func (*ClientConsensusStates) ProtoMessage() {} +func (*ClientConsensusStates) Descriptor() ([]byte, []int) { + return fileDescriptor_b6bc4c8185546947, []int{2} +} +func (m *ClientConsensusStates) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ClientConsensusStates) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ClientConsensusStates.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ClientConsensusStates) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClientConsensusStates.Merge(m, src) +} +func (m *ClientConsensusStates) XXX_Size() int { + return m.Size() +} +func (m *ClientConsensusStates) XXX_DiscardUnknown() { + xxx_messageInfo_ClientConsensusStates.DiscardUnknown(m) +} + +var xxx_messageInfo_ClientConsensusStates proto.InternalMessageInfo + +func (m *ClientConsensusStates) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +func (m *ClientConsensusStates) GetConsensusStates() []ConsensusStateWithHeight { + if m != nil { + return m.ConsensusStates + } + return nil +} + +// Height is a monotonically increasing data type +// that can be compared against another Height for the purposes of updating and +// freezing clients +// +// Normally the RevisionHeight is incremented at each height while keeping +// RevisionNumber the same. However some consensus algorithms may choose to +// reset the height in certain conditions e.g. hard forks, state-machine +// breaking changes In these cases, the RevisionNumber is incremented so that +// height continues to be monitonically increasing even as the RevisionHeight +// gets reset +// +// Please note that json tags for generated Go code are overridden to explicitly exclude the omitempty jsontag. +// This enforces the Go json marshaller to always emit zero values for both revision_number and revision_height. +type Height struct { + // the revision that the client is currently on + RevisionNumber uint64 `protobuf:"varint,1,opt,name=revision_number,json=revisionNumber,proto3" json:"revision_number"` + // the height within the given revision + RevisionHeight uint64 `protobuf:"varint,2,opt,name=revision_height,json=revisionHeight,proto3" json:"revision_height"` +} + +func (m *Height) Reset() { *m = Height{} } +func (*Height) ProtoMessage() {} +func (*Height) Descriptor() ([]byte, []int) { + return fileDescriptor_b6bc4c8185546947, []int{3} +} +func (m *Height) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Height) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Height.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Height) XXX_Merge(src proto.Message) { + xxx_messageInfo_Height.Merge(m, src) +} +func (m *Height) XXX_Size() int { + return m.Size() +} +func (m *Height) XXX_DiscardUnknown() { + xxx_messageInfo_Height.DiscardUnknown(m) +} + +var xxx_messageInfo_Height proto.InternalMessageInfo + +// Params defines the set of IBC light client parameters. +type Params struct { + // allowed_clients defines the list of allowed client state types which can be created + // and interacted with. If a client type is removed from the allowed clients list, usage + // of this client will be disabled until it is added again to the list. + AllowedClients []string `protobuf:"bytes,1,rep,name=allowed_clients,json=allowedClients,proto3" json:"allowed_clients,omitempty"` +} + +func (m *Params) Reset() { *m = Params{} } +func (m *Params) String() string { return proto.CompactTextString(m) } +func (*Params) ProtoMessage() {} +func (*Params) Descriptor() ([]byte, []int) { + return fileDescriptor_b6bc4c8185546947, []int{4} +} +func (m *Params) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Params) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params.Merge(m, src) +} +func (m *Params) XXX_Size() int { + return m.Size() +} +func (m *Params) XXX_DiscardUnknown() { + xxx_messageInfo_Params.DiscardUnknown(m) +} + +var xxx_messageInfo_Params proto.InternalMessageInfo + +func (m *Params) GetAllowedClients() []string { + if m != nil { + return m.AllowedClients + } + return nil +} + +func init() { + proto.RegisterType((*IdentifiedClientState)(nil), "ibc.core.client.v1.IdentifiedClientState") + proto.RegisterType((*ConsensusStateWithHeight)(nil), "ibc.core.client.v1.ConsensusStateWithHeight") + proto.RegisterType((*ClientConsensusStates)(nil), "ibc.core.client.v1.ClientConsensusStates") + proto.RegisterType((*Height)(nil), "ibc.core.client.v1.Height") + proto.RegisterType((*Params)(nil), "ibc.core.client.v1.Params") +} + +func init() { proto.RegisterFile("ibc/core/client/v1/client.proto", fileDescriptor_b6bc4c8185546947) } + +var fileDescriptor_b6bc4c8185546947 = []byte{ + // 458 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x53, 0xbf, 0x6f, 0xd3, 0x40, + 0x14, 0xf6, 0xa5, 0x55, 0xd4, 0x5c, 0x50, 0x82, 0x4c, 0x2b, 0x99, 0x20, 0xd9, 0x51, 0x16, 0x32, + 0xd0, 0xbb, 0x26, 0x0c, 0xfc, 0x10, 0x0c, 0xa4, 0x0b, 0x5d, 0x10, 0x72, 0x07, 0x24, 0x24, 0x14, + 0xd9, 0xe7, 0xab, 0x73, 0x92, 0x7d, 0x57, 0xf9, 0xce, 0x46, 0xf9, 0x0f, 0x98, 0x10, 0x12, 0x0b, + 0x63, 0xff, 0x9c, 0x8e, 0x1d, 0x99, 0x2a, 0x94, 0x6c, 0xfc, 0x15, 0xc8, 0x77, 0x17, 0x55, 0x2e, + 0x01, 0x75, 0x7b, 0xf7, 0xbe, 0xef, 0xbd, 0xef, 0xfb, 0x5e, 0x62, 0x18, 0xb0, 0x98, 0x60, 0x22, + 0x0a, 0x8a, 0x49, 0xc6, 0x28, 0x57, 0xb8, 0x9a, 0xd8, 0x0a, 0x9d, 0x17, 0x42, 0x09, 0xd7, 0x65, + 0x31, 0x41, 0x35, 0x01, 0xd9, 0x76, 0x35, 0x19, 0xec, 0xa7, 0x22, 0x15, 0x1a, 0xc6, 0x75, 0x65, + 0x98, 0x83, 0x87, 0xa9, 0x10, 0x69, 0x46, 0xb1, 0x7e, 0xc5, 0xe5, 0x19, 0x8e, 0xf8, 0xd2, 0x40, + 0xa3, 0x1c, 0x1e, 0x9c, 0x24, 0x94, 0x2b, 0x76, 0xc6, 0x68, 0x72, 0xac, 0xf7, 0x9c, 0xaa, 0x48, + 0x51, 0xf7, 0x11, 0xec, 0x98, 0xb5, 0x73, 0x96, 0x78, 0x60, 0x08, 0xc6, 0x9d, 0x70, 0xcf, 0x34, + 0x4e, 0x12, 0xf7, 0x19, 0xbc, 0x67, 0x41, 0x59, 0x93, 0xbd, 0xd6, 0x10, 0x8c, 0xbb, 0xd3, 0x7d, + 0x64, 0x74, 0xd0, 0x46, 0x07, 0xbd, 0xe1, 0xcb, 0xb0, 0x4b, 0x6e, 0xb6, 0x8e, 0xbe, 0x03, 0xe8, + 0x1d, 0x0b, 0x2e, 0x29, 0x97, 0xa5, 0xd4, 0xad, 0x0f, 0x4c, 0x2d, 0xde, 0x52, 0x96, 0x2e, 0x94, + 0xfb, 0x1c, 0xb6, 0x17, 0xba, 0xd2, 0x7a, 0xdd, 0xe9, 0x00, 0xfd, 0x9d, 0x10, 0x19, 0xee, 0x6c, + 0xf7, 0xf2, 0x3a, 0x70, 0x42, 0xcb, 0x77, 0x5f, 0xc3, 0x3e, 0xd9, 0x6c, 0xbd, 0x83, 0xa5, 0x1e, + 0x69, 0x58, 0xa8, 0x5d, 0x1d, 0x98, 0xec, 0x4d, 0x6f, 0xf2, 0xff, 0x57, 0xf8, 0x04, 0xef, 0xdf, + 0x52, 0x95, 0x5e, 0x6b, 0xb8, 0x33, 0xee, 0x4e, 0x9f, 0x6c, 0x73, 0xfe, 0xaf, 0xdc, 0x36, 0x4b, + 0xbf, 0x69, 0x4a, 0x8e, 0xbe, 0x02, 0xd8, 0xb6, 0x97, 0x79, 0x05, 0xfb, 0x05, 0xad, 0x98, 0x64, + 0x82, 0xcf, 0x79, 0x99, 0xc7, 0xb4, 0xd0, 0x66, 0x76, 0x67, 0x0f, 0x7e, 0x5f, 0x07, 0xb7, 0xa1, + 0xb0, 0xb7, 0x69, 0xbc, 0xd3, 0xef, 0xc6, 0xb4, 0x3d, 0x70, 0x6b, 0xcb, 0xb4, 0x81, 0x6e, 0xa6, + 0x8d, 0xf6, 0xcb, 0xbd, 0x2f, 0x17, 0x81, 0xf3, 0xe3, 0x22, 0x70, 0x46, 0x13, 0xd8, 0x7e, 0x1f, + 0x15, 0x51, 0x2e, 0xdd, 0xc7, 0xb0, 0x1f, 0x65, 0x99, 0xf8, 0x4c, 0x93, 0xb9, 0xc9, 0x27, 0x3d, + 0x30, 0xdc, 0x19, 0x77, 0xc2, 0x9e, 0x6d, 0x9b, 0x6b, 0xca, 0xd9, 0xe9, 0xe5, 0xca, 0x07, 0x57, + 0x2b, 0x1f, 0xfc, 0x5a, 0xf9, 0xe0, 0xdb, 0xda, 0x77, 0xae, 0xd6, 0xbe, 0xf3, 0x73, 0xed, 0x3b, + 0x1f, 0x5f, 0xa4, 0x4c, 0x2d, 0xca, 0x18, 0x11, 0x91, 0x63, 0x22, 0x64, 0x2e, 0x24, 0x66, 0x31, + 0x39, 0x4c, 0x05, 0xae, 0x26, 0x47, 0x38, 0x17, 0x49, 0x99, 0x51, 0x69, 0xfe, 0xff, 0x47, 0xd3, + 0x43, 0xfb, 0x09, 0xa8, 0xe5, 0x39, 0x95, 0x71, 0x5b, 0xff, 0x98, 0x4f, 0xff, 0x04, 0x00, 0x00, + 0xff, 0xff, 0xf3, 0x47, 0x85, 0x95, 0x22, 0x03, 0x00, 0x00, +} + +func (m *IdentifiedClientState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *IdentifiedClientState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *IdentifiedClientState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.ClientState != nil { + { + size, err := m.ClientState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintClient(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintClient(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ConsensusStateWithHeight) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ConsensusStateWithHeight) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ConsensusStateWithHeight) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.ConsensusState != nil { + { + size, err := m.ConsensusState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintClient(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + { + size, err := m.Height.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintClient(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *ClientConsensusStates) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ClientConsensusStates) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ClientConsensusStates) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ConsensusStates) > 0 { + for iNdEx := len(m.ConsensusStates) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ConsensusStates[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintClient(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintClient(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Height) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Height) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Height) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.RevisionHeight != 0 { + i = encodeVarintClient(dAtA, i, uint64(m.RevisionHeight)) + i-- + dAtA[i] = 0x10 + } + if m.RevisionNumber != 0 { + i = encodeVarintClient(dAtA, i, uint64(m.RevisionNumber)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *Params) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Params) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.AllowedClients) > 0 { + for iNdEx := len(m.AllowedClients) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.AllowedClients[iNdEx]) + copy(dAtA[i:], m.AllowedClients[iNdEx]) + i = encodeVarintClient(dAtA, i, uint64(len(m.AllowedClients[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintClient(dAtA []byte, offset int, v uint64) int { + offset -= sovClient(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *IdentifiedClientState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovClient(uint64(l)) + } + if m.ClientState != nil { + l = m.ClientState.Size() + n += 1 + l + sovClient(uint64(l)) + } + return n +} + +func (m *ConsensusStateWithHeight) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Height.Size() + n += 1 + l + sovClient(uint64(l)) + if m.ConsensusState != nil { + l = m.ConsensusState.Size() + n += 1 + l + sovClient(uint64(l)) + } + return n +} + +func (m *ClientConsensusStates) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovClient(uint64(l)) + } + if len(m.ConsensusStates) > 0 { + for _, e := range m.ConsensusStates { + l = e.Size() + n += 1 + l + sovClient(uint64(l)) + } + } + return n +} + +func (m *Height) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.RevisionNumber != 0 { + n += 1 + sovClient(uint64(m.RevisionNumber)) + } + if m.RevisionHeight != 0 { + n += 1 + sovClient(uint64(m.RevisionHeight)) + } + return n +} + +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.AllowedClients) > 0 { + for _, s := range m.AllowedClients { + l = len(s) + n += 1 + l + sovClient(uint64(l)) + } + } + return n +} + +func sovClient(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozClient(x uint64) (n int) { + return sovClient(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *IdentifiedClientState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowClient + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: IdentifiedClientState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: IdentifiedClientState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowClient + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthClient + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthClient + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowClient + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthClient + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthClient + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ClientState == nil { + m.ClientState = &types.Any{} + } + if err := m.ClientState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipClient(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthClient + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ConsensusStateWithHeight) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowClient + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ConsensusStateWithHeight: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ConsensusStateWithHeight: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowClient + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthClient + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthClient + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Height.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConsensusState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowClient + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthClient + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthClient + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ConsensusState == nil { + m.ConsensusState = &types.Any{} + } + if err := m.ConsensusState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipClient(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthClient + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ClientConsensusStates) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowClient + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ClientConsensusStates: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ClientConsensusStates: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowClient + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthClient + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthClient + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConsensusStates", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowClient + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthClient + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthClient + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ConsensusStates = append(m.ConsensusStates, ConsensusStateWithHeight{}) + if err := m.ConsensusStates[len(m.ConsensusStates)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipClient(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthClient + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Height) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowClient + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Height: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Height: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RevisionNumber", wireType) + } + m.RevisionNumber = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowClient + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.RevisionNumber |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RevisionHeight", wireType) + } + m.RevisionHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowClient + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.RevisionHeight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipClient(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthClient + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Params) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowClient + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Params: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowedClients", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowClient + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthClient + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthClient + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AllowedClients = append(m.AllowedClients, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipClient(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthClient + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipClient(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowClient + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowClient + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowClient + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthClient + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupClient + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthClient + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthClient = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowClient = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupClient = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/02-client/types/client_test.go b/modules/core/02-client/types/client_test.go new file mode 100644 index 0000000..a9c07c9 --- /dev/null +++ b/modules/core/02-client/types/client_test.go @@ -0,0 +1,86 @@ +package types_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *TypesTestSuite) TestMarshalConsensusStateWithHeight() { + var cswh types.ConsensusStateWithHeight + + testCases := []struct { + name string + malleate func() + }{ + { + "solo machine client", func() { + soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 1) + cswh = types.NewConsensusStateWithHeight(types.NewHeight(0, soloMachine.Sequence), soloMachine.ConsensusState()) + }, + }, + { + "tendermint client", func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + latestHeight, ok := path.EndpointA.GetClientLatestHeight().(types.Height) + suite.Require().True(ok) + consensusState, ok := suite.chainA.GetConsensusState(path.EndpointA.ClientID, latestHeight) + suite.Require().True(ok) + + cswh = types.NewConsensusStateWithHeight(latestHeight, consensusState) + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + tc.malleate() + + cdc := suite.chainA.App.AppCodec() + + // marshal message + bz, err := cdc.MarshalJSON(&cswh) + suite.Require().NoError(err) + + // unmarshal message + newCswh := &types.ConsensusStateWithHeight{} + err = cdc.UnmarshalJSON(bz, newCswh) + suite.Require().NoError(err) + }) + } +} + +func TestValidateClientType(t *testing.T) { + testCases := []struct { + name string + clientType string + expError error + }{ + {"valid", "tendermint", nil}, + {"valid solomachine", "solomachine-v1", nil}, + {"too large", "tenderminttenderminttenderminttenderminttendermintt", errors.New("client type results in largest client identifier being invalid")}, + {"too short", "t", errors.New("client type results in smallest client identifier being invalid")}, + {"blank id", " ", errors.New("client type cannot be blank")}, + {"empty id", "", errors.New("client type cannot be blank")}, + {"ends with dash", "tendermint-", errors.New("invalid client type")}, + } + + for _, tc := range testCases { + + err := types.ValidateClientType(tc.clientType) + + if tc.expError == nil { + require.NoError(t, err, tc.name) + } else { + require.ErrorContains(t, err, tc.expError.Error()) + } + } +} diff --git a/modules/core/02-client/types/codec.go b/modules/core/02-client/types/codec.go new file mode 100644 index 0000000..81731f0 --- /dev/null +++ b/modules/core/02-client/types/codec.go @@ -0,0 +1,171 @@ +package types + +import ( + "github.com/cosmos/gogoproto/proto" + + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/legacy" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/msgservice" + govtypesv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// RegisterLegacyAminoCodec registers the necessary interfaces and concrete types +// on the provided LegacyAmino codec. These types are used for Amino JSON serialization. +func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + legacy.RegisterAminoMsg(cdc, &MsgRecoverClient{}, "cosmos-sdk/MsgRecoverClient") +} + +// RegisterInterfaces registers the client interfaces to protobuf Any. +func RegisterInterfaces(registry codectypes.InterfaceRegistry) { + registry.RegisterInterface( + "ibc.core.client.v1.ClientState", + (*exported.ClientState)(nil), + ) + registry.RegisterInterface( + "ibc.core.client.v1.ConsensusState", + (*exported.ConsensusState)(nil), + ) + registry.RegisterInterface( + "ibc.core.client.v1.Header", + (*exported.ClientMessage)(nil), + ) + registry.RegisterInterface( + "ibc.core.client.v1.Height", + (*exported.Height)(nil), + &Height{}, + ) + registry.RegisterInterface( + "ibc.core.client.v1.Misbehaviour", + (*exported.ClientMessage)(nil), + ) + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgCreateClient{}, + &MsgUpdateClient{}, + &MsgUpgradeClient{}, + &MsgSubmitMisbehaviour{}, + &MsgRecoverClient{}, + &MsgIBCSoftwareUpgrade{}, + &MsgUpdateParams{}, + ) + registry.RegisterImplementations( + (*govtypesv1beta1.Content)(nil), + &ClientUpdateProposal{}, + &UpgradeProposal{}, + ) + + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} + +// PackClientState constructs a new Any packed with the given client state value. It returns +// an error if the client state can't be casted to a protobuf message or if the concrete +// implementation is not registered to the protobuf codec. +func PackClientState(clientState exported.ClientState) (*codectypes.Any, error) { + msg, ok := clientState.(proto.Message) + if !ok { + return nil, errorsmod.Wrapf(ibcerrors.ErrPackAny, "cannot proto marshal %T", clientState) + } + + anyClientState, err := codectypes.NewAnyWithValue(msg) + if err != nil { + return nil, errorsmod.Wrap(ibcerrors.ErrPackAny, err.Error()) + } + + return anyClientState, nil +} + +// UnpackClientState unpacks an Any into a ClientState. It returns an error if the +// client state can't be unpacked into a ClientState. +func UnpackClientState(protoAny *codectypes.Any) (exported.ClientState, error) { + if protoAny == nil { + return nil, errorsmod.Wrap(ibcerrors.ErrUnpackAny, "protobuf Any message cannot be nil") + } + + clientState, ok := protoAny.GetCachedValue().(exported.ClientState) + if !ok { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnpackAny, "cannot unpack Any into ClientState %T", protoAny) + } + + return clientState, nil +} + +// PackConsensusState constructs a new Any packed with the given consensus state value. It returns +// an error if the consensus state can't be casted to a protobuf message or if the concrete +// implementation is not registered to the protobuf codec. +func PackConsensusState(consensusState exported.ConsensusState) (*codectypes.Any, error) { + msg, ok := consensusState.(proto.Message) + if !ok { + return nil, errorsmod.Wrapf(ibcerrors.ErrPackAny, "cannot proto marshal %T", consensusState) + } + + anyConsensusState, err := codectypes.NewAnyWithValue(msg) + if err != nil { + return nil, errorsmod.Wrap(ibcerrors.ErrPackAny, err.Error()) + } + + return anyConsensusState, nil +} + +// MustPackConsensusState calls PackConsensusState and panics on error. +func MustPackConsensusState(consensusState exported.ConsensusState) *codectypes.Any { + anyConsensusState, err := PackConsensusState(consensusState) + if err != nil { + panic(err) + } + + return anyConsensusState +} + +// UnpackConsensusState unpacks an Any into a ConsensusState. It returns an error if the +// consensus state can't be unpacked into a ConsensusState. +func UnpackConsensusState(protoAny *codectypes.Any) (exported.ConsensusState, error) { + if protoAny == nil { + return nil, errorsmod.Wrap(ibcerrors.ErrUnpackAny, "protobuf Any message cannot be nil") + } + + consensusState, ok := protoAny.GetCachedValue().(exported.ConsensusState) + if !ok { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnpackAny, "cannot unpack Any into ConsensusState %T", protoAny) + } + + return consensusState, nil +} + +// PackClientMessage constructs a new Any packed with the given value. It returns +// an error if the value can't be casted to a protobuf message or if the concrete +// implementation is not registered to the protobuf codec. +func PackClientMessage(clientMessage exported.ClientMessage) (*codectypes.Any, error) { + msg, ok := clientMessage.(proto.Message) + if !ok { + return nil, errorsmod.Wrapf(ibcerrors.ErrPackAny, "cannot proto marshal %T", clientMessage) + } + + protoAny, err := codectypes.NewAnyWithValue(msg) + if err != nil { + return nil, errorsmod.Wrap(ibcerrors.ErrPackAny, err.Error()) + } + + return protoAny, nil +} + +// UnpackClientMessage unpacks an Any into a ClientMessage. It returns an error if the +// consensus state can't be unpacked into a ClientMessage. +func UnpackClientMessage(protoAny *codectypes.Any) (exported.ClientMessage, error) { + if protoAny == nil { + return nil, errorsmod.Wrap(ibcerrors.ErrUnpackAny, "protobuf Any message cannot be nil") + } + + clientMessage, ok := protoAny.GetCachedValue().(exported.ClientMessage) + if !ok { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnpackAny, "cannot unpack Any into Header %T", protoAny) + } + + return clientMessage, nil +} diff --git a/modules/core/02-client/types/codec_test.go b/modules/core/02-client/types/codec_test.go new file mode 100644 index 0000000..8f1e10c --- /dev/null +++ b/modules/core/02-client/types/codec_test.go @@ -0,0 +1,245 @@ +package types_test + +import ( + "errors" + + errorsmod "cosmossdk.io/errors" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +type caseAny struct { + name string + any *codectypes.Any + expErr error +} + +func (suite *TypesTestSuite) TestPackClientState() { + testCases := []struct { + name string + clientState exported.ClientState + expErr error + }{ + { + "solo machine client", + ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2).ClientState(), + nil, + }, + { + "tendermint client", + ibctm.NewClientState(suite.chainA.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), + nil, + }, + { + "nil", + nil, + errorsmod.Wrap(ibcerrors.ErrUnpackAny, "protobuf Any message cannot be nil"), + }, + } + + testCasesAny := []caseAny{} + + for _, tc := range testCases { + + protoAny, err := types.PackClientState(tc.clientState) + if tc.expErr == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().Error(err, tc.name) + } + + testCasesAny = append(testCasesAny, caseAny{tc.name, protoAny, tc.expErr}) + } + + for i, tc := range testCasesAny { + cs, err := types.UnpackClientState(tc.any) + if tc.expErr == nil { + suite.Require().NoError(err, tc.name) + suite.Require().Equal(testCases[i].clientState, cs, tc.name) + } else { + suite.Require().Error(err, tc.name) + suite.Require().ErrorIs(err, tc.expErr) + } + } +} + +func (suite *TypesTestSuite) TestPackConsensusState() { + testCases := []struct { + name string + consensusState exported.ConsensusState + expErr error + }{ + { + "solo machine consensus", + ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2).ConsensusState(), + nil, + }, + { + "tendermint consensus", + suite.chainA.LatestCommittedHeader.ConsensusState(), + nil, + }, + { + "nil", + nil, + errorsmod.Wrap(ibcerrors.ErrUnpackAny, "protobuf Any message cannot be nil"), + }, + } + + testCasesAny := []caseAny{} + + for _, tc := range testCases { + + protoAny, err := types.PackConsensusState(tc.consensusState) + if tc.expErr == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().Error(err, tc.name) + } + testCasesAny = append(testCasesAny, caseAny{tc.name, protoAny, tc.expErr}) + } + + for i, tc := range testCasesAny { + + cs, err := types.UnpackConsensusState(tc.any) + if tc.expErr == nil { + suite.Require().NoError(err, tc.name) + suite.Require().Equal(testCases[i].consensusState, cs, tc.name) + } else { + suite.Require().Error(err, tc.name) + suite.Require().ErrorIs(err, tc.expErr) + } + } +} + +func (suite *TypesTestSuite) TestPackClientMessage() { + testCases := []struct { + name string + clientMessage exported.ClientMessage + expErr error + }{ + { + "solo machine header", + ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2).CreateHeader("solomachine"), + nil, + }, + { + "tendermint header", + suite.chainA.LatestCommittedHeader, + nil, + }, + { + "nil", + nil, + errorsmod.Wrap(ibcerrors.ErrUnpackAny, "protobuf Any message cannot be nil"), + }, + } + + testCasesAny := []caseAny{} + + for _, tc := range testCases { + + protoAny, err := types.PackClientMessage(tc.clientMessage) + if tc.expErr == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().Error(err, tc.name) + } + + testCasesAny = append(testCasesAny, caseAny{tc.name, protoAny, tc.expErr}) + } + + for i, tc := range testCasesAny { + + cs, err := types.UnpackClientMessage(tc.any) + if tc.expErr == nil { + suite.Require().NoError(err, tc.name) + suite.Require().Equal(testCases[i].clientMessage, cs, tc.name) + } else { + suite.Require().Error(err, tc.name) + suite.Require().ErrorIs(err, tc.expErr) + } + } +} + +func (suite *TypesTestSuite) TestCodecTypeRegistration() { + testCases := []struct { + name string + typeURL string + expErr error + }{ + { + "success: MsgCreateClient", + sdk.MsgTypeURL(&types.MsgCreateClient{}), + nil, + }, + { + "success: MsgUpdateClient", + sdk.MsgTypeURL(&types.MsgUpdateClient{}), + nil, + }, + { + "success: MsgUpgradeClient", + sdk.MsgTypeURL(&types.MsgUpgradeClient{}), + nil, + }, + { + "success: MsgSubmitMisbehaviour", + sdk.MsgTypeURL(&types.MsgSubmitMisbehaviour{}), + nil, + }, + { + "success: MsgRecoverClient", + sdk.MsgTypeURL(&types.MsgRecoverClient{}), + nil, + }, + { + "success: MsgIBCSoftwareUpgrade", + sdk.MsgTypeURL(&types.MsgIBCSoftwareUpgrade{}), + nil, + }, + { + "success: MsgUpdateParams", + sdk.MsgTypeURL(&types.MsgUpdateParams{}), + nil, + }, + { + "success: ClientUpdateProposal", + sdk.MsgTypeURL(&types.ClientUpdateProposal{}), + nil, + }, + { + "success: UpgradeProposal", + sdk.MsgTypeURL(&types.UpgradeProposal{}), + nil, + }, + { + "type not registered on codec", + "ibc.invalid.MsgTypeURL", + errors.New("unable to resolve type URL ibc.invalid.MsgTypeURL"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + msg, err := suite.chainA.GetSimApp().AppCodec().InterfaceRegistry().Resolve(tc.typeURL) + + if tc.expErr == nil { + suite.Require().NotNil(msg) + suite.Require().NoError(err) + } else { + suite.Require().Nil(msg) + suite.Require().Error(err) + suite.Require().Equal(err.Error(), tc.expErr.Error()) + } + }) + } +} diff --git a/modules/core/02-client/types/encoding.go b/modules/core/02-client/types/encoding.go new file mode 100644 index 0000000..14aa8d6 --- /dev/null +++ b/modules/core/02-client/types/encoding.go @@ -0,0 +1,114 @@ +package types + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// MustUnmarshalClientState attempts to decode and return an ClientState object from +// raw encoded bytes. It panics on error. +func MustUnmarshalClientState(cdc codec.BinaryCodec, bz []byte) exported.ClientState { + clientState, err := UnmarshalClientState(cdc, bz) + if err != nil { + panic(fmt.Errorf("failed to decode client state: %w", err)) + } + + return clientState +} + +// MustMarshalClientState attempts to encode an ClientState object and returns the +// raw encoded bytes. It panics on error. +func MustMarshalClientState(cdc codec.BinaryCodec, clientState exported.ClientState) []byte { + bz, err := MarshalClientState(cdc, clientState) + if err != nil { + panic(fmt.Errorf("failed to encode client state: %w", err)) + } + + return bz +} + +// MarshalClientState protobuf serializes an ClientState interface +func MarshalClientState(cdc codec.BinaryCodec, clientStateI exported.ClientState) ([]byte, error) { + return cdc.MarshalInterface(clientStateI) +} + +// UnmarshalClientState returns an ClientState interface from raw encoded clientState +// bytes of a Proto-based ClientState type. An error is returned upon decoding +// failure. +func UnmarshalClientState(cdc codec.BinaryCodec, bz []byte) (exported.ClientState, error) { + var clientState exported.ClientState + if err := cdc.UnmarshalInterface(bz, &clientState); err != nil { + return nil, err + } + + return clientState, nil +} + +// MustUnmarshalConsensusState attempts to decode and return an ConsensusState object from +// raw encoded bytes. It panics on error. +func MustUnmarshalConsensusState(cdc codec.BinaryCodec, bz []byte) exported.ConsensusState { + consensusState, err := UnmarshalConsensusState(cdc, bz) + if err != nil { + panic(fmt.Errorf("failed to decode consensus state: %w", err)) + } + + return consensusState +} + +// MustMarshalConsensusState attempts to encode a ConsensusState object and returns the +// raw encoded bytes. It panics on error. +func MustMarshalConsensusState(cdc codec.BinaryCodec, consensusState exported.ConsensusState) []byte { + bz, err := MarshalConsensusState(cdc, consensusState) + if err != nil { + panic(fmt.Errorf("failed to encode consensus state: %w", err)) + } + + return bz +} + +// MarshalConsensusState protobuf serializes a ConsensusState interface +func MarshalConsensusState(cdc codec.BinaryCodec, cs exported.ConsensusState) ([]byte, error) { + return cdc.MarshalInterface(cs) +} + +// UnmarshalConsensusState returns a ConsensusState interface from raw encoded consensus state +// bytes of a Proto-based ConsensusState type. An error is returned upon decoding +// failure. +func UnmarshalConsensusState(cdc codec.BinaryCodec, bz []byte) (exported.ConsensusState, error) { + var consensusState exported.ConsensusState + if err := cdc.UnmarshalInterface(bz, &consensusState); err != nil { + return nil, err + } + + return consensusState, nil +} + +// MarshalClientMessage protobuf serializes a ClientMessage interface +func MarshalClientMessage(cdc codec.BinaryCodec, clientMessage exported.ClientMessage) ([]byte, error) { + return cdc.MarshalInterface(clientMessage) +} + +// MustMarshalClientMessage attempts to encode a ClientMessage object and returns the +// raw encoded bytes. It panics on error. +func MustMarshalClientMessage(cdc codec.BinaryCodec, clientMessage exported.ClientMessage) []byte { + bz, err := MarshalClientMessage(cdc, clientMessage) + if err != nil { + panic(fmt.Errorf("failed to encode ClientMessage: %w", err)) + } + + return bz +} + +// UnmarshalClientMessage returns a ClientMessage interface from raw proto encoded header bytes. +// An error is returned upon decoding failure. +func UnmarshalClientMessage(cdc codec.BinaryCodec, bz []byte) (exported.ClientMessage, error) { + var clientMessage exported.ClientMessage + if err := cdc.UnmarshalInterface(bz, &clientMessage); err != nil { + return nil, err + } + + return clientMessage, nil +} diff --git a/modules/core/02-client/types/encoding_test.go b/modules/core/02-client/types/encoding_test.go new file mode 100644 index 0000000..9ca67a2 --- /dev/null +++ b/modules/core/02-client/types/encoding_test.go @@ -0,0 +1,28 @@ +package types_test + +import ( + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" +) + +func (suite *TypesTestSuite) TestMarshalHeader() { + cdc := suite.chainA.App.AppCodec() + h := &ibctm.Header{ + TrustedHeight: types.NewHeight(4, 100), + } + + // marshal header + bz, err := types.MarshalClientMessage(cdc, h) + suite.Require().NoError(err) + + // unmarshal header + newHeader, err := types.UnmarshalClientMessage(cdc, bz) + suite.Require().NoError(err) + + suite.Require().Equal(h, newHeader) + + // use invalid bytes + invalidHeader, err := types.UnmarshalClientMessage(cdc, []byte("invalid bytes")) + suite.Require().Error(err) + suite.Require().Nil(invalidHeader) +} diff --git a/modules/core/02-client/types/errors.go b/modules/core/02-client/types/errors.go new file mode 100644 index 0000000..2feedaf --- /dev/null +++ b/modules/core/02-client/types/errors.go @@ -0,0 +1,41 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" +) + +// IBC client sentinel errors +var ( + ErrClientExists = errorsmod.Register(SubModuleName, 2, "light client already exists") + ErrInvalidClient = errorsmod.Register(SubModuleName, 3, "light client is invalid") + ErrClientNotFound = errorsmod.Register(SubModuleName, 4, "light client not found") + ErrClientFrozen = errorsmod.Register(SubModuleName, 5, "light client is frozen due to misbehaviour") + ErrInvalidClientMetadata = errorsmod.Register(SubModuleName, 6, "invalid client metadata") + ErrConsensusStateNotFound = errorsmod.Register(SubModuleName, 7, "consensus state not found") + ErrInvalidConsensus = errorsmod.Register(SubModuleName, 8, "invalid consensus state") + ErrClientTypeNotFound = errorsmod.Register(SubModuleName, 9, "client type not found") + ErrInvalidClientType = errorsmod.Register(SubModuleName, 10, "invalid client type") + ErrRootNotFound = errorsmod.Register(SubModuleName, 11, "commitment root not found") + ErrInvalidHeader = errorsmod.Register(SubModuleName, 12, "invalid client header") + ErrInvalidMisbehaviour = errorsmod.Register(SubModuleName, 13, "invalid light client misbehaviour") + ErrFailedClientStateVerification = errorsmod.Register(SubModuleName, 14, "client state verification failed") + ErrFailedClientConsensusStateVerification = errorsmod.Register(SubModuleName, 15, "client consensus state verification failed") + ErrFailedConnectionStateVerification = errorsmod.Register(SubModuleName, 16, "connection state verification failed") + ErrFailedChannelStateVerification = errorsmod.Register(SubModuleName, 17, "channel state verification failed") + ErrFailedPacketCommitmentVerification = errorsmod.Register(SubModuleName, 18, "packet commitment verification failed") + ErrFailedPacketAckVerification = errorsmod.Register(SubModuleName, 19, "packet acknowledgement verification failed") + ErrFailedPacketReceiptVerification = errorsmod.Register(SubModuleName, 20, "packet receipt verification failed") + ErrFailedNextSeqRecvVerification = errorsmod.Register(SubModuleName, 21, "next sequence receive verification failed") + ErrSelfConsensusStateNotFound = errorsmod.Register(SubModuleName, 22, "self consensus state not found") + ErrUpdateClientFailed = errorsmod.Register(SubModuleName, 23, "unable to update light client") + ErrInvalidRecoveryClient = errorsmod.Register(SubModuleName, 24, "invalid recovery client") + ErrInvalidUpgradeClient = errorsmod.Register(SubModuleName, 25, "invalid client upgrade") + ErrInvalidHeight = errorsmod.Register(SubModuleName, 26, "invalid height") + ErrInvalidSubstitute = errorsmod.Register(SubModuleName, 27, "invalid client state substitute") + ErrInvalidUpgradeProposal = errorsmod.Register(SubModuleName, 28, "invalid upgrade proposal") + ErrClientNotActive = errorsmod.Register(SubModuleName, 29, "client state is not active") + ErrFailedMembershipVerification = errorsmod.Register(SubModuleName, 30, "membership verification failed") + ErrFailedNonMembershipVerification = errorsmod.Register(SubModuleName, 31, "non-membership verification failed") + ErrRouteNotFound = errorsmod.Register(SubModuleName, 32, "light client module route not found") + ErrClientTypeNotSupported = errorsmod.Register(SubModuleName, 33, "client type not supported") +) diff --git a/modules/core/02-client/types/events.go b/modules/core/02-client/types/events.go new file mode 100644 index 0000000..8e4f0e0 --- /dev/null +++ b/modules/core/02-client/types/events.go @@ -0,0 +1,32 @@ +package types + +import ( + "fmt" + + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// IBC client events +const ( + AttributeKeyClientID = "client_id" + AttributeKeySubjectClientID = "subject_client_id" + AttributeKeyClientType = "client_type" + AttributeKeyConsensusHeight = "consensus_height" + AttributeKeyConsensusHeights = "consensus_heights" + AttributeKeyUpgradeStore = "upgrade_store" + AttributeKeyUpgradePlanHeight = "upgrade_plan_height" + AttributeKeyUpgradePlanTitle = "title" +) + +// IBC client events vars +var ( + EventTypeCreateClient = "create_client" + EventTypeUpdateClient = "update_client" + EventTypeUpgradeClient = "upgrade_client" + EventTypeSubmitMisbehaviour = "client_misbehaviour" + EventTypeRecoverClient = "recover_client" + EventTypeScheduleIBCSoftwareUpgrade = "schedule_ibc_software_upgrade" + EventTypeUpgradeChain = "upgrade_chain" + + AttributeValueCategory = fmt.Sprintf("%s_%s", ibcexported.ModuleName, SubModuleName) +) diff --git a/modules/core/02-client/types/expected_keepers.go b/modules/core/02-client/types/expected_keepers.go new file mode 100644 index 0000000..6db2bf9 --- /dev/null +++ b/modules/core/02-client/types/expected_keepers.go @@ -0,0 +1,25 @@ +package types + +import ( + "context" + + upgradetypes "cosmossdk.io/x/upgrade/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" +) + +// UpgradeKeeper expected upgrade keeper +type UpgradeKeeper interface { + GetUpgradePlan(ctx context.Context) (plan upgradetypes.Plan, err error) + GetUpgradedClient(ctx context.Context, height int64) ([]byte, error) + SetUpgradedClient(ctx context.Context, planHeight int64, bz []byte) error + GetUpgradedConsensusState(ctx context.Context, lastHeight int64) ([]byte, error) + SetUpgradedConsensusState(ctx context.Context, planHeight int64, bz []byte) error + ScheduleUpgrade(ctx context.Context, plan upgradetypes.Plan) error +} + +// ParamSubspace defines the expected Subspace interface for module parameters. +type ParamSubspace interface { + GetParamSet(ctx sdk.Context, ps paramtypes.ParamSet) +} diff --git a/modules/core/02-client/types/genesis.go b/modules/core/02-client/types/genesis.go new file mode 100644 index 0000000..37ec95d --- /dev/null +++ b/modules/core/02-client/types/genesis.go @@ -0,0 +1,244 @@ +package types + +import ( + "errors" + "fmt" + "sort" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var ( + _ codectypes.UnpackInterfacesMessage = (*IdentifiedClientState)(nil) + _ codectypes.UnpackInterfacesMessage = (*ClientsConsensusStates)(nil) + _ codectypes.UnpackInterfacesMessage = (*ClientConsensusStates)(nil) + _ codectypes.UnpackInterfacesMessage = (*GenesisState)(nil) + + _ sort.Interface = (*ClientsConsensusStates)(nil) + _ exported.GenesisMetadata = (*GenesisMetadata)(nil) +) + +// ClientsConsensusStates defines a slice of ClientConsensusStates that supports the sort interface +type ClientsConsensusStates []ClientConsensusStates + +// Len implements sort.Interface +func (ccs ClientsConsensusStates) Len() int { return len(ccs) } + +// Less implements sort.Interface +func (ccs ClientsConsensusStates) Less(i, j int) bool { return ccs[i].ClientId < ccs[j].ClientId } + +// Swap implements sort.Interface +func (ccs ClientsConsensusStates) Swap(i, j int) { ccs[i], ccs[j] = ccs[j], ccs[i] } + +// Sort is a helper function to sort the set of ClientsConsensusStates in place +func (ccs ClientsConsensusStates) Sort() ClientsConsensusStates { + sort.Sort(ccs) + return ccs +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (ccs ClientsConsensusStates) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + for _, clientConsensus := range ccs { + if err := clientConsensus.UnpackInterfaces(unpacker); err != nil { + return err + } + } + return nil +} + +// NewClientConsensusStates creates a new ClientConsensusStates instance. +func NewClientConsensusStates(clientID string, consensusStates []ConsensusStateWithHeight) ClientConsensusStates { + return ClientConsensusStates{ + ClientId: clientID, + ConsensusStates: consensusStates, + } +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (ccs ClientConsensusStates) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + for _, consStateWithHeight := range ccs.ConsensusStates { + if err := consStateWithHeight.UnpackInterfaces(unpacker); err != nil { + return err + } + } + return nil +} + +// NewGenesisState creates a GenesisState instance. +func NewGenesisState( + clients []IdentifiedClientState, clientsConsensus ClientsConsensusStates, clientsMetadata []IdentifiedGenesisMetadata, + params Params, createLocalhost bool, nextClientSequence uint64, +) GenesisState { + return GenesisState{ + Clients: clients, + ClientsConsensus: clientsConsensus, + ClientsMetadata: clientsMetadata, + Params: params, + CreateLocalhost: createLocalhost, + NextClientSequence: nextClientSequence, + } +} + +// DefaultGenesisState returns the ibc client submodule's default genesis state. +func DefaultGenesisState() GenesisState { + return GenesisState{ + Clients: []IdentifiedClientState{}, + ClientsConsensus: ClientsConsensusStates{}, + Params: DefaultParams(), + CreateLocalhost: false, + NextClientSequence: 0, + } +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (gs GenesisState) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + for _, client := range gs.Clients { + if err := client.UnpackInterfaces(unpacker); err != nil { + return err + } + } + + return gs.ClientsConsensus.UnpackInterfaces(unpacker) +} + +// Validate performs basic genesis state validation returning an error upon any +// failure. +func (gs GenesisState) Validate() error { + // keep track of the max sequence to ensure it is less than + // the next sequence used in creating client identifiers. + var maxSequence uint64 + + if err := gs.Params.Validate(); err != nil { + return err + } + + validClients := make(map[string]string) + for i, client := range gs.Clients { + if err := host.ClientIdentifierValidator(client.ClientId); err != nil { + return fmt.Errorf("invalid client consensus state identifier %s index %d: %w", client.ClientId, i, err) + } + + clientState, ok := client.ClientState.GetCachedValue().(exported.ClientState) + if !ok { + return fmt.Errorf("invalid client state with ID %s", client.ClientId) + } + + if !gs.Params.IsAllowedClient(clientState.ClientType()) { + return fmt.Errorf("client type %s not allowed by genesis params", clientState.ClientType()) + } + if err := clientState.Validate(); err != nil { + return fmt.Errorf("invalid client %v index %d: %w", client, i, err) + } + + clientType, sequence, err := ParseClientIdentifier(client.ClientId) + if err != nil { + return err + } + + if clientType != clientState.ClientType() { + return fmt.Errorf("client state type %s does not equal client type in client identifier %s", clientState.ClientType(), clientType) + } + + if err := ValidateClientType(clientType); err != nil { + return err + } + + if sequence > maxSequence { + maxSequence = sequence + } + + // add client id to validClients map + validClients[client.ClientId] = clientState.ClientType() + } + + for _, cc := range gs.ClientsConsensus { + // check that consensus state is for a client in the genesis clients list + clientType, ok := validClients[cc.ClientId] + if !ok { + return fmt.Errorf("consensus state in genesis has a client id %s that does not map to a genesis client", cc.ClientId) + } + + for i, consensusState := range cc.ConsensusStates { + if consensusState.Height.IsZero() { + return errors.New("consensus state height cannot be zero") + } + + cs, ok := consensusState.ConsensusState.GetCachedValue().(exported.ConsensusState) + if !ok { + return fmt.Errorf("invalid consensus state with client ID %s at height %s", cc.ClientId, consensusState.Height) + } + + if err := cs.ValidateBasic(); err != nil { + return fmt.Errorf("invalid client consensus state %v clientID %s index %d: %w", cs, cc.ClientId, i, err) + } + + // ensure consensus state type matches client state type + if clientType != cs.ClientType() { + return fmt.Errorf("consensus state client type %s does not equal client state client type %s", cs.ClientType(), clientType) + } + + } + } + + for _, clientMetadata := range gs.ClientsMetadata { + // check that metadata is for a client in the genesis clients list + _, ok := validClients[clientMetadata.ClientId] + if !ok { + return fmt.Errorf("metadata in genesis has a client id %s that does not map to a genesis client", clientMetadata.ClientId) + } + + for i, gm := range clientMetadata.ClientMetadata { + if err := gm.Validate(); err != nil { + return fmt.Errorf("invalid client metadata %v clientID %s index %d: %w", gm, clientMetadata.ClientId, i, err) + } + } + + } + + if maxSequence != 0 && maxSequence >= gs.NextClientSequence { + return fmt.Errorf("next client identifier sequence %d must be greater than the maximum sequence used in the provided client identifiers %d", gs.NextClientSequence, maxSequence) + } + + return nil +} + +// NewGenesisMetadata is a constructor for GenesisMetadata +func NewGenesisMetadata(key, val []byte) GenesisMetadata { + return GenesisMetadata{ + Key: key, + Value: val, + } +} + +// GetKey returns the key of metadata. Implements exported.GenesisMetadata interface. +func (gm GenesisMetadata) GetKey() []byte { + return gm.Key +} + +// GetValue returns the value of metadata. Implements exported.GenesisMetadata interface. +func (gm GenesisMetadata) GetValue() []byte { + return gm.Value +} + +// Validate ensures key and value of metadata are not empty +func (gm GenesisMetadata) Validate() error { + if len(gm.Key) == 0 { + return errors.New("genesis metadata key cannot be empty") + } + if len(gm.Value) == 0 { + return errors.New("genesis metadata value cannot be empty") + } + return nil +} + +// NewIdentifiedGenesisMetadata takes in a client ID and list of genesis metadata for that client +// and constructs a new IdentifiedGenesisMetadata. +func NewIdentifiedGenesisMetadata(clientID string, gms []GenesisMetadata) IdentifiedGenesisMetadata { + return IdentifiedGenesisMetadata{ + ClientId: clientID, + ClientMetadata: gms, + } +} diff --git a/modules/core/02-client/types/genesis.pb.go b/modules/core/02-client/types/genesis.pb.go new file mode 100644 index 0000000..2fa8603 --- /dev/null +++ b/modules/core/02-client/types/genesis.pb.go @@ -0,0 +1,1057 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/client/v1/genesis.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// GenesisState defines the ibc client submodule's genesis state. +type GenesisState struct { + // client states with their corresponding identifiers + Clients IdentifiedClientStates `protobuf:"bytes,1,rep,name=clients,proto3,castrepeated=IdentifiedClientStates" json:"clients"` + // consensus states from each client + ClientsConsensus ClientsConsensusStates `protobuf:"bytes,2,rep,name=clients_consensus,json=clientsConsensus,proto3,castrepeated=ClientsConsensusStates" json:"clients_consensus"` + // metadata from each client + ClientsMetadata []IdentifiedGenesisMetadata `protobuf:"bytes,3,rep,name=clients_metadata,json=clientsMetadata,proto3" json:"clients_metadata"` + Params Params `protobuf:"bytes,4,opt,name=params,proto3" json:"params"` + // Deprecated: create_localhost has been deprecated. + // The localhost client is automatically created at genesis. + CreateLocalhost bool `protobuf:"varint,5,opt,name=create_localhost,json=createLocalhost,proto3" json:"create_localhost,omitempty"` // Deprecated: Do not use. + // the sequence for the next generated client identifier + NextClientSequence uint64 `protobuf:"varint,6,opt,name=next_client_sequence,json=nextClientSequence,proto3" json:"next_client_sequence,omitempty"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_bcd0c0f1f2e6a91a, []int{0} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetClients() IdentifiedClientStates { + if m != nil { + return m.Clients + } + return nil +} + +func (m *GenesisState) GetClientsConsensus() ClientsConsensusStates { + if m != nil { + return m.ClientsConsensus + } + return nil +} + +func (m *GenesisState) GetClientsMetadata() []IdentifiedGenesisMetadata { + if m != nil { + return m.ClientsMetadata + } + return nil +} + +func (m *GenesisState) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +// Deprecated: Do not use. +func (m *GenesisState) GetCreateLocalhost() bool { + if m != nil { + return m.CreateLocalhost + } + return false +} + +func (m *GenesisState) GetNextClientSequence() uint64 { + if m != nil { + return m.NextClientSequence + } + return 0 +} + +// GenesisMetadata defines the genesis type for metadata that will be used +// to export all client store keys that are not client or consensus states. +type GenesisMetadata struct { + // store key of metadata without clientID-prefix + Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + // metadata value + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (m *GenesisMetadata) Reset() { *m = GenesisMetadata{} } +func (m *GenesisMetadata) String() string { return proto.CompactTextString(m) } +func (*GenesisMetadata) ProtoMessage() {} +func (*GenesisMetadata) Descriptor() ([]byte, []int) { + return fileDescriptor_bcd0c0f1f2e6a91a, []int{1} +} +func (m *GenesisMetadata) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisMetadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisMetadata.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisMetadata) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisMetadata.Merge(m, src) +} +func (m *GenesisMetadata) XXX_Size() int { + return m.Size() +} +func (m *GenesisMetadata) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisMetadata.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisMetadata proto.InternalMessageInfo + +// IdentifiedGenesisMetadata has the client metadata with the corresponding +// client id. +type IdentifiedGenesisMetadata struct { + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + ClientMetadata []GenesisMetadata `protobuf:"bytes,2,rep,name=client_metadata,json=clientMetadata,proto3" json:"client_metadata"` +} + +func (m *IdentifiedGenesisMetadata) Reset() { *m = IdentifiedGenesisMetadata{} } +func (m *IdentifiedGenesisMetadata) String() string { return proto.CompactTextString(m) } +func (*IdentifiedGenesisMetadata) ProtoMessage() {} +func (*IdentifiedGenesisMetadata) Descriptor() ([]byte, []int) { + return fileDescriptor_bcd0c0f1f2e6a91a, []int{2} +} +func (m *IdentifiedGenesisMetadata) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *IdentifiedGenesisMetadata) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_IdentifiedGenesisMetadata.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *IdentifiedGenesisMetadata) XXX_Merge(src proto.Message) { + xxx_messageInfo_IdentifiedGenesisMetadata.Merge(m, src) +} +func (m *IdentifiedGenesisMetadata) XXX_Size() int { + return m.Size() +} +func (m *IdentifiedGenesisMetadata) XXX_DiscardUnknown() { + xxx_messageInfo_IdentifiedGenesisMetadata.DiscardUnknown(m) +} + +var xxx_messageInfo_IdentifiedGenesisMetadata proto.InternalMessageInfo + +func (m *IdentifiedGenesisMetadata) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +func (m *IdentifiedGenesisMetadata) GetClientMetadata() []GenesisMetadata { + if m != nil { + return m.ClientMetadata + } + return nil +} + +func init() { + proto.RegisterType((*GenesisState)(nil), "ibc.core.client.v1.GenesisState") + proto.RegisterType((*GenesisMetadata)(nil), "ibc.core.client.v1.GenesisMetadata") + proto.RegisterType((*IdentifiedGenesisMetadata)(nil), "ibc.core.client.v1.IdentifiedGenesisMetadata") +} + +func init() { proto.RegisterFile("ibc/core/client/v1/genesis.proto", fileDescriptor_bcd0c0f1f2e6a91a) } + +var fileDescriptor_bcd0c0f1f2e6a91a = []byte{ + // 482 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x53, 0xb1, 0x8e, 0xd3, 0x4c, + 0x18, 0xf4, 0x26, 0xb9, 0xfc, 0x77, 0x7b, 0xa7, 0x3f, 0x61, 0x15, 0x21, 0x13, 0x24, 0xc7, 0x0a, + 0x8d, 0x29, 0x62, 0x27, 0xa1, 0x01, 0x1a, 0xa4, 0x5c, 0x81, 0x4e, 0x02, 0x09, 0xf9, 0x3a, 0x0a, + 0x2c, 0x7b, 0xfd, 0xe1, 0xb3, 0xb0, 0xbd, 0x21, 0xbb, 0xb6, 0xb8, 0x37, 0xa0, 0xa0, 0xe0, 0x11, + 0xa8, 0x79, 0x92, 0x2b, 0xaf, 0xa4, 0x02, 0x94, 0x48, 0x3c, 0x07, 0xf2, 0xee, 0x9a, 0x93, 0x82, + 0x8f, 0xee, 0xcb, 0xcc, 0x7c, 0x33, 0xd9, 0xb1, 0x3e, 0x6c, 0xa7, 0x11, 0xf5, 0x28, 0xdb, 0x80, + 0x47, 0xb3, 0x14, 0x0a, 0xe1, 0x55, 0x0b, 0x2f, 0x81, 0x02, 0x78, 0xca, 0xdd, 0xf5, 0x86, 0x09, + 0x46, 0x48, 0x1a, 0x51, 0xb7, 0x56, 0xb8, 0x4a, 0xe1, 0x56, 0x8b, 0xf1, 0xa4, 0x65, 0x4b, 0xb3, + 0x72, 0x69, 0x3c, 0x4a, 0x58, 0xc2, 0xe4, 0xe8, 0xd5, 0x93, 0x42, 0xa7, 0xbf, 0xba, 0xf8, 0xe4, + 0xb9, 0x32, 0x3f, 0x17, 0xa1, 0x00, 0x42, 0xf1, 0x7f, 0x6a, 0x8d, 0x9b, 0xc8, 0xee, 0x3a, 0xc7, + 0xcb, 0x87, 0xee, 0xdf, 0x69, 0xee, 0x59, 0x0c, 0x85, 0x48, 0xdf, 0xa6, 0x10, 0x9f, 0x4a, 0x4c, + 0xee, 0xae, 0xac, 0xab, 0xef, 0x13, 0xe3, 0xeb, 0x8f, 0xc9, 0xdd, 0x56, 0x9a, 0xfb, 0x8d, 0x33, + 0xa9, 0xf0, 0x1d, 0x3d, 0x06, 0x94, 0x15, 0x1c, 0x0a, 0x5e, 0x72, 0xb3, 0x73, 0x7b, 0x9c, 0x72, + 0x39, 0x6d, 0xa4, 0xca, 0xee, 0x26, 0x4e, 0xd1, 0x7c, 0x8f, 0xf7, 0x87, 0x74, 0x0f, 0x27, 0x6f, + 0x70, 0x83, 0x05, 0x39, 0x88, 0x30, 0x0e, 0x45, 0x68, 0x76, 0x65, 0xec, 0xec, 0xdf, 0xaf, 0xd4, + 0x15, 0xbd, 0xd4, 0x4b, 0xab, 0x5e, 0x1d, 0xed, 0x0f, 0xb4, 0x59, 0x03, 0x93, 0xc7, 0xb8, 0xbf, + 0x0e, 0x37, 0x61, 0xce, 0xcd, 0x9e, 0x8d, 0x9c, 0xe3, 0xe5, 0xb8, 0xcd, 0xf5, 0x95, 0x54, 0x68, + 0x0b, 0xad, 0x27, 0x33, 0x3c, 0xa4, 0x1b, 0x08, 0x05, 0x04, 0x19, 0xa3, 0x61, 0x76, 0xc1, 0xb8, + 0x30, 0x0f, 0x6c, 0xe4, 0x1c, 0xae, 0x3a, 0x26, 0xf2, 0x07, 0x8a, 0x7b, 0xd1, 0x50, 0x64, 0x8e, + 0x47, 0x05, 0x7c, 0x10, 0x81, 0x72, 0x0d, 0x38, 0xbc, 0x2f, 0xa1, 0xa0, 0x60, 0xf6, 0x6d, 0xe4, + 0xf4, 0x7c, 0x52, 0x73, 0xba, 0x79, 0xcd, 0x4c, 0x9f, 0xe1, 0xc1, 0xde, 0x23, 0xc8, 0x10, 0x77, + 0xdf, 0xc1, 0xa5, 0x89, 0x6c, 0xe4, 0x9c, 0xf8, 0xf5, 0x48, 0x46, 0xf8, 0xa0, 0x0a, 0xb3, 0x12, + 0xcc, 0x8e, 0xc4, 0xd4, 0x8f, 0xa7, 0xbd, 0x8f, 0x5f, 0x26, 0xc6, 0xf4, 0x13, 0xc2, 0xf7, 0x6e, + 0x2d, 0x84, 0xdc, 0xc7, 0x47, 0xfa, 0xbf, 0xa4, 0xb1, 0x74, 0x3c, 0xf2, 0x0f, 0x15, 0x70, 0x16, + 0x13, 0x1f, 0xeb, 0xa6, 0x6e, 0x5a, 0x57, 0x1f, 0xfb, 0x41, 0x5b, 0x3f, 0xed, 0x5d, 0xff, 0xaf, + 0x04, 0x7f, 0xd0, 0xf3, 0xab, 0xad, 0x85, 0xae, 0xb7, 0x16, 0xfa, 0xb9, 0xb5, 0xd0, 0xe7, 0x9d, + 0x65, 0x5c, 0xef, 0x2c, 0xe3, 0xdb, 0xce, 0x32, 0x5e, 0x3f, 0x49, 0x52, 0x71, 0x51, 0x46, 0x2e, + 0x65, 0xb9, 0x47, 0x19, 0xcf, 0x19, 0xf7, 0xd2, 0x88, 0xce, 0x12, 0xe6, 0x55, 0x8b, 0xb9, 0x97, + 0xb3, 0xb8, 0xcc, 0x80, 0xab, 0x53, 0x99, 0x2f, 0x67, 0xfa, 0x5a, 0xc4, 0xe5, 0x1a, 0x78, 0xd4, + 0x97, 0x47, 0xf1, 0xe8, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0x9c, 0x86, 0x76, 0xc7, 0x83, 0x03, + 0x00, 0x00, +} + +func (m *GenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.NextClientSequence != 0 { + i = encodeVarintGenesis(dAtA, i, uint64(m.NextClientSequence)) + i-- + dAtA[i] = 0x30 + } + if m.CreateLocalhost { + i-- + if m.CreateLocalhost { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x28 + } + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + if len(m.ClientsMetadata) > 0 { + for iNdEx := len(m.ClientsMetadata) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ClientsMetadata[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + if len(m.ClientsConsensus) > 0 { + for iNdEx := len(m.ClientsConsensus) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ClientsConsensus[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.Clients) > 0 { + for iNdEx := len(m.Clients) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Clients[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *GenesisMetadata) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisMetadata) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisMetadata) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Value) > 0 { + i -= len(m.Value) + copy(dAtA[i:], m.Value) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.Value))) + i-- + dAtA[i] = 0x12 + } + if len(m.Key) > 0 { + i -= len(m.Key) + copy(dAtA[i:], m.Key) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.Key))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *IdentifiedGenesisMetadata) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *IdentifiedGenesisMetadata) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *IdentifiedGenesisMetadata) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ClientMetadata) > 0 { + for iNdEx := len(m.ClientMetadata) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ClientMetadata[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Clients) > 0 { + for _, e := range m.Clients { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.ClientsConsensus) > 0 { + for _, e := range m.ClientsConsensus { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.ClientsMetadata) > 0 { + for _, e := range m.ClientsMetadata { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + l = m.Params.Size() + n += 1 + l + sovGenesis(uint64(l)) + if m.CreateLocalhost { + n += 2 + } + if m.NextClientSequence != 0 { + n += 1 + sovGenesis(uint64(m.NextClientSequence)) + } + return n +} + +func (m *GenesisMetadata) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Key) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + l = len(m.Value) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + return n +} + +func (m *IdentifiedGenesisMetadata) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + if len(m.ClientMetadata) > 0 { + for _, e := range m.ClientMetadata { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Clients", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Clients = append(m.Clients, IdentifiedClientState{}) + if err := m.Clients[len(m.Clients)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientsConsensus", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientsConsensus = append(m.ClientsConsensus, ClientConsensusStates{}) + if err := m.ClientsConsensus[len(m.ClientsConsensus)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientsMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientsMetadata = append(m.ClientsMetadata, IdentifiedGenesisMetadata{}) + if err := m.ClientsMetadata[len(m.ClientsMetadata)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CreateLocalhost", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.CreateLocalhost = bool(v != 0) + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NextClientSequence", wireType) + } + m.NextClientSequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.NextClientSequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GenesisMetadata) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisMetadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Key = append(m.Key[:0], dAtA[iNdEx:postIndex]...) + if m.Key == nil { + m.Key = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Value = append(m.Value[:0], dAtA[iNdEx:postIndex]...) + if m.Value == nil { + m.Value = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *IdentifiedGenesisMetadata) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: IdentifiedGenesisMetadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: IdentifiedGenesisMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientMetadata = append(m.ClientMetadata, GenesisMetadata{}) + if err := m.ClientMetadata[len(m.ClientMetadata)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/02-client/types/genesis_test.go b/modules/core/02-client/types/genesis_test.go new file mode 100644 index 0000000..5f61067 --- /dev/null +++ b/modules/core/02-client/types/genesis_test.go @@ -0,0 +1,488 @@ +package types_test + +import ( + "errors" + "time" + + cmttypes "github.com/cometbft/cometbft/types" + + client "github.com/cosmos/ibc-go/v10/modules/core/02-client" + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +const ( + tmClientID0 = "07-tendermint-0" + tmClientID1 = "07-tendermint-1" + invalidClientID = "myclient-0" + clientID = tmClientID0 + + height = 10 +) + +var clientHeight = types.NewHeight(1, 10) + +func (suite *TypesTestSuite) TestMarshalGenesisState() { + cdc := suite.chainA.App.AppCodec() + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + genesis := client.ExportGenesis(suite.chainA.GetContext(), suite.chainA.App.GetIBCKeeper().ClientKeeper) + + bz, err := cdc.MarshalJSON(&genesis) + suite.Require().NoError(err) + suite.Require().NotNil(bz) + + var gs types.GenesisState + err = cdc.UnmarshalJSON(bz, &gs) + suite.Require().NoError(err) +} + +func (suite *TypesTestSuite) TestValidateGenesis() { + privVal := cmttypes.NewMockPV() + pubKey, err := privVal.GetPubKey() + suite.Require().NoError(err) + + now := time.Now().UTC() + + val := cmttypes.NewValidator(pubKey, 10) + valSet := cmttypes.NewValidatorSet([]*cmttypes.Validator{val}) + + signers := make(map[string]cmttypes.PrivValidator) + signers[val.Address.String()] = privVal + + heightMinus1 := types.NewHeight(1, height-1) + header := suite.chainA.CreateTMClientHeader(suite.chainA.ChainID, int64(clientHeight.RevisionHeight), heightMinus1, now, valSet, valSet, valSet, signers) + + testCases := []struct { + name string + genState types.GenesisState + expError error + }{ + { + name: "default", + genState: types.DefaultGenesisState(), + expError: nil, + }, + { + name: "valid custom genesis", + genState: types.NewGenesisState( + []types.IdentifiedClientState{ + types.NewIdentifiedClientState( + tmClientID0, ibctm.NewClientState(suite.chainA.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), + ), + }, + []types.ClientConsensusStates{ + types.NewClientConsensusStates( + tmClientID0, + []types.ConsensusStateWithHeight{ + types.NewConsensusStateWithHeight( + header.GetHeight().(types.Height), + ibctm.NewConsensusState( + header.GetTime(), commitmenttypes.NewMerkleRoot(header.Header.GetAppHash()), header.Header.NextValidatorsHash, + ), + ), + }, + ), + }, + []types.IdentifiedGenesisMetadata{ + types.NewIdentifiedGenesisMetadata( + clientID, + []types.GenesisMetadata{ + types.NewGenesisMetadata([]byte("key1"), []byte("val1")), + types.NewGenesisMetadata([]byte("key2"), []byte("val2")), + }, + ), + }, + types.NewParams(exported.Tendermint), + false, + 2, + ), + expError: nil, + }, + { + name: "invalid client type", + genState: types.NewGenesisState( + []types.IdentifiedClientState{ + types.NewIdentifiedClientState( + ibctesting.DefaultSolomachineClientID, ibctm.NewClientState(suite.chainA.ChainID, ibctm.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), + ), + types.NewIdentifiedClientState(tmClientID0, solomachine.NewClientState(0, &solomachine.ConsensusState{PublicKey: suite.solomachine.ConsensusState().PublicKey, Diversifier: suite.solomachine.Diversifier, Timestamp: suite.solomachine.Time})), + }, + nil, + nil, + types.NewParams(exported.Tendermint), + false, + 0, + ), + expError: errors.New("client state type 07-tendermint does not equal client type in client identifier 06-solomachine"), + }, + { + name: "invalid clientid", + genState: types.NewGenesisState( + []types.IdentifiedClientState{ + types.NewIdentifiedClientState( + invalidClientID, ibctm.NewClientState(suite.chainA.ChainID, ibctm.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), + ), + }, + []types.ClientConsensusStates{ + types.NewClientConsensusStates( + invalidClientID, + []types.ConsensusStateWithHeight{ + types.NewConsensusStateWithHeight( + header.GetHeight().(types.Height), + ibctm.NewConsensusState( + header.GetTime(), commitmenttypes.NewMerkleRoot(header.Header.GetAppHash()), header.Header.NextValidatorsHash, + ), + ), + }, + ), + }, + nil, + types.NewParams(exported.Tendermint), + false, + 0, + ), + expError: errors.New("client state type 07-tendermint does not equal client type in client identifier myclient"), + }, + { + name: "consensus state client id does not match client id in genesis clients", + genState: types.NewGenesisState( + []types.IdentifiedClientState{ + types.NewIdentifiedClientState( + tmClientID0, ibctm.NewClientState(suite.chainA.ChainID, ibctm.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), + ), + }, + []types.ClientConsensusStates{ + types.NewClientConsensusStates( + tmClientID1, + []types.ConsensusStateWithHeight{ + types.NewConsensusStateWithHeight( + types.NewHeight(1, 1), + ibctm.NewConsensusState( + header.GetTime(), commitmenttypes.NewMerkleRoot(header.Header.GetAppHash()), header.Header.NextValidatorsHash, + ), + ), + }, + ), + }, + nil, + types.NewParams(exported.Tendermint), + false, + 0, + ), + expError: errors.New("consensus state in genesis has a client id 07-tendermint-1 that does not map to a genesis client"), + }, + { + name: "invalid consensus state height", + genState: types.NewGenesisState( + []types.IdentifiedClientState{ + types.NewIdentifiedClientState( + tmClientID0, ibctm.NewClientState(suite.chainA.ChainID, ibctm.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), + ), + }, + []types.ClientConsensusStates{ + types.NewClientConsensusStates( + tmClientID0, + []types.ConsensusStateWithHeight{ + types.NewConsensusStateWithHeight( + types.ZeroHeight(), + ibctm.NewConsensusState( + header.GetTime(), commitmenttypes.NewMerkleRoot(header.Header.GetAppHash()), header.Header.NextValidatorsHash, + ), + ), + }, + ), + }, + nil, + types.NewParams(exported.Tendermint), + false, + 0, + ), + expError: errors.New("consensus state height cannot be zero"), + }, + { + name: "invalid consensus state", + genState: types.NewGenesisState( + []types.IdentifiedClientState{ + types.NewIdentifiedClientState( + tmClientID0, ibctm.NewClientState(suite.chainA.ChainID, ibctm.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), + ), + }, + []types.ClientConsensusStates{ + types.NewClientConsensusStates( + tmClientID0, + []types.ConsensusStateWithHeight{ + types.NewConsensusStateWithHeight( + types.NewHeight(1, 1), + ibctm.NewConsensusState( + time.Time{}, commitmenttypes.NewMerkleRoot(header.Header.GetAppHash()), header.Header.NextValidatorsHash, + ), + ), + }, + ), + }, + nil, + types.NewParams(exported.Tendermint), + false, + 0, + ), + expError: errors.New("invalid client consensus state timestamp"), + }, + { + name: "client in genesis clients is disallowed by params", + genState: types.NewGenesisState( + []types.IdentifiedClientState{ + types.NewIdentifiedClientState( + tmClientID0, ibctm.NewClientState(suite.chainA.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), + ), + }, + []types.ClientConsensusStates{ + types.NewClientConsensusStates( + tmClientID0, + []types.ConsensusStateWithHeight{ + types.NewConsensusStateWithHeight( + header.GetHeight().(types.Height), + ibctm.NewConsensusState( + header.GetTime(), commitmenttypes.NewMerkleRoot(header.Header.GetAppHash()), header.Header.NextValidatorsHash, + ), + ), + }, + ), + }, + nil, + types.NewParams(exported.Solomachine), + false, + 0, + ), + expError: errors.New("client type 07-tendermint not allowed by genesis params"), + }, + { + name: "metadata client-id does not match a genesis client", + genState: types.NewGenesisState( + []types.IdentifiedClientState{ + types.NewIdentifiedClientState( + clientID, ibctm.NewClientState(suite.chainA.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), + ), + }, + []types.ClientConsensusStates{ + types.NewClientConsensusStates( + clientID, + []types.ConsensusStateWithHeight{ + types.NewConsensusStateWithHeight( + header.GetHeight().(types.Height), + ibctm.NewConsensusState( + header.GetTime(), commitmenttypes.NewMerkleRoot(header.Header.GetAppHash()), header.Header.NextValidatorsHash, + ), + ), + }, + ), + }, + []types.IdentifiedGenesisMetadata{ + types.NewIdentifiedGenesisMetadata( + "wrongclientid", + []types.GenesisMetadata{ + types.NewGenesisMetadata([]byte("key1"), []byte("val1")), + types.NewGenesisMetadata([]byte("key2"), []byte("val2")), + }, + ), + }, + types.NewParams(exported.Tendermint), + false, + 0, + ), + expError: errors.New("metadata in genesis has a client id wrongclientid that does not map to a genesis client"), + }, + { + name: "invalid metadata", + genState: types.NewGenesisState( + []types.IdentifiedClientState{ + types.NewIdentifiedClientState( + clientID, ibctm.NewClientState(suite.chainA.ChainID, ibctm.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), + ), + }, + []types.ClientConsensusStates{ + types.NewClientConsensusStates( + clientID, + []types.ConsensusStateWithHeight{ + types.NewConsensusStateWithHeight( + header.GetHeight().(types.Height), + ibctm.NewConsensusState( + header.GetTime(), commitmenttypes.NewMerkleRoot(header.Header.GetAppHash()), header.Header.NextValidatorsHash, + ), + ), + }, + ), + }, + []types.IdentifiedGenesisMetadata{ + types.NewIdentifiedGenesisMetadata( + clientID, + []types.GenesisMetadata{ + types.NewGenesisMetadata([]byte(""), []byte("val1")), + types.NewGenesisMetadata([]byte("key2"), []byte("val2")), + }, + ), + }, + types.NewParams(exported.Tendermint), + false, + 0, + ), + expError: errors.New("invalid client metadata"), + }, + { + name: "invalid params", + genState: types.NewGenesisState( + []types.IdentifiedClientState{ + types.NewIdentifiedClientState( + tmClientID0, ibctm.NewClientState(suite.chainA.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), + ), + }, + []types.ClientConsensusStates{ + types.NewClientConsensusStates( + tmClientID0, + []types.ConsensusStateWithHeight{ + types.NewConsensusStateWithHeight( + header.GetHeight().(types.Height), + ibctm.NewConsensusState( + header.GetTime(), commitmenttypes.NewMerkleRoot(header.Header.GetAppHash()), header.Header.NextValidatorsHash, + ), + ), + }, + ), + }, + nil, + types.NewParams(" "), + false, + 0, + ), + expError: errors.New("client type 0 cannot be blank"), + }, + { + name: "invalid param", + genState: types.NewGenesisState( + []types.IdentifiedClientState{ + types.NewIdentifiedClientState( + tmClientID0, ibctm.NewClientState(suite.chainA.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), + ), + }, + []types.ClientConsensusStates{ + types.NewClientConsensusStates( + tmClientID0, + []types.ConsensusStateWithHeight{ + types.NewConsensusStateWithHeight( + header.GetHeight().(types.Height), + ibctm.NewConsensusState( + header.GetTime(), commitmenttypes.NewMerkleRoot(header.Header.GetAppHash()), header.Header.NextValidatorsHash, + ), + ), + }, + ), + }, + nil, + types.NewParams(" "), + false, + 0, + ), + expError: errors.New("client type 0 cannot be blank"), + }, + { + name: "next sequence too small", + genState: types.NewGenesisState( + []types.IdentifiedClientState{ + types.NewIdentifiedClientState( + tmClientID0, ibctm.NewClientState(suite.chainA.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), + ), + types.NewIdentifiedClientState( + tmClientID1, ibctm.NewClientState(suite.chainA.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), + ), + }, + []types.ClientConsensusStates{ + types.NewClientConsensusStates( + tmClientID0, + []types.ConsensusStateWithHeight{ + types.NewConsensusStateWithHeight( + header.GetHeight().(types.Height), + ibctm.NewConsensusState( + header.GetTime(), commitmenttypes.NewMerkleRoot(header.Header.GetAppHash()), header.Header.NextValidatorsHash, + ), + ), + }, + ), + }, + nil, + types.NewParams(exported.Tendermint), + false, + 0, + ), + expError: errors.New("next client identifier sequence 0 must be greater than the maximum sequence used in the provided client identifiers 1"), + }, + { + name: "failed to parse client identifier in client state loop", + genState: types.NewGenesisState( + []types.IdentifiedClientState{ + types.NewIdentifiedClientState( + "my-client", ibctm.NewClientState(suite.chainA.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), + ), + }, + []types.ClientConsensusStates{ + types.NewClientConsensusStates( + tmClientID0, + []types.ConsensusStateWithHeight{ + types.NewConsensusStateWithHeight( + header.GetHeight().(types.Height), + ibctm.NewConsensusState( + header.GetTime(), commitmenttypes.NewMerkleRoot(header.Header.GetAppHash()), header.Header.NextValidatorsHash, + ), + ), + }, + ), + }, + nil, + types.NewParams(exported.Tendermint), + false, + 5, + ), + expError: errors.New("invalid client identifier my-client is not in format"), + }, + { + name: "consensus state different than client state type", + genState: types.NewGenesisState( + []types.IdentifiedClientState{}, + []types.ClientConsensusStates{ + types.NewClientConsensusStates( + tmClientID0, + []types.ConsensusStateWithHeight{ + types.NewConsensusStateWithHeight( + header.GetHeight().(types.Height), + ibctm.NewConsensusState( + header.GetTime(), commitmenttypes.NewMerkleRoot(header.Header.GetAppHash()), header.Header.NextValidatorsHash, + ), + ), + }, + ), + }, + nil, + types.NewParams(exported.Tendermint), + false, + 5, + ), + expError: errors.New("consensus state in genesis has a client id 07-tendermint-0 that does not map to a genesis client"), + }, + } + + for _, tc := range testCases { + + err := tc.genState.Validate() + if tc.expError == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().ErrorContains(err, tc.expError.Error()) + } + } +} diff --git a/modules/core/02-client/types/height.go b/modules/core/02-client/types/height.go new file mode 100644 index 0000000..f59c812 --- /dev/null +++ b/modules/core/02-client/types/height.go @@ -0,0 +1,191 @@ +package types + +import ( + "fmt" + "math/big" + "regexp" + "strconv" + "strings" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var _ exported.Height = (*Height)(nil) + +// IsRevisionFormat checks if a chainID is in the format required for parsing revisions +// The chainID must be in the form: `{chainID}-{revision}`. +// 24-host may enforce stricter checks on chainID +var IsRevisionFormat = regexp.MustCompile(`^.*[^\n-]-{1}[1-9][0-9]*$`).MatchString + +// ZeroHeight is a helper function which returns an uninitialized height. +func ZeroHeight() Height { + return Height{} +} + +// NewHeight is a constructor for the IBC height type +func NewHeight(revisionNumber, revisionHeight uint64) Height { + return Height{ + RevisionNumber: revisionNumber, + RevisionHeight: revisionHeight, + } +} + +// GetRevisionNumber returns the revision-number of the height +func (h Height) GetRevisionNumber() uint64 { + return h.RevisionNumber +} + +// GetRevisionHeight returns the revision-height of the height +func (h Height) GetRevisionHeight() uint64 { + return h.RevisionHeight +} + +// Compare implements a method to compare two heights. When comparing two heights a, b +// we can call a.Compare(b) which will return +// -1 if a < b +// 0 if a = b +// 1 if a > b +// +// It first compares based on revision numbers, whichever has the higher revision number is the higher height +// If revision number is the same, then the revision height is compared +func (h Height) Compare(other exported.Height) int64 { + height, ok := other.(Height) + if !ok { + panic(fmt.Errorf("cannot compare against invalid height type: %T. expected height type: %T", other, h)) + } + var a, b big.Int + if h.RevisionNumber != height.RevisionNumber { + a.SetUint64(h.RevisionNumber) + b.SetUint64(height.RevisionNumber) + } else { + a.SetUint64(h.RevisionHeight) + b.SetUint64(height.RevisionHeight) + } + return int64(a.Cmp(&b)) +} + +// LT Helper comparison function returns true if h < other +func (h Height) LT(other exported.Height) bool { + return h.Compare(other) == -1 +} + +// LTE Helper comparison function returns true if h <= other +func (h Height) LTE(other exported.Height) bool { + cmp := h.Compare(other) + return cmp <= 0 +} + +// GT Helper comparison function returns true if h > other +func (h Height) GT(other exported.Height) bool { + return h.Compare(other) == 1 +} + +// GTE Helper comparison function returns true if h >= other +func (h Height) GTE(other exported.Height) bool { + cmp := h.Compare(other) + return cmp >= 0 +} + +// EQ Helper comparison function returns true if h == other +func (h Height) EQ(other exported.Height) bool { + return h.Compare(other) == 0 +} + +// String returns a string representation of Height +func (h Height) String() string { + return fmt.Sprintf("%d-%d", h.RevisionNumber, h.RevisionHeight) +} + +// Decrement will return a new height with the RevisionHeight decremented +// If the RevisionHeight is already at lowest value (1), then false success flag is returned +func (h Height) Decrement() (decremented exported.Height, success bool) { + if h.RevisionHeight == 0 { + return Height{}, false + } + return NewHeight(h.RevisionNumber, h.RevisionHeight-1), true +} + +// Increment will return a height with the same revision number but an +// incremented revision height +func (h Height) Increment() exported.Height { + return NewHeight(h.RevisionNumber, h.RevisionHeight+1) +} + +// IsZero returns true if height revision and revision-height are both 0 +func (h Height) IsZero() bool { + return h.RevisionNumber == 0 && h.RevisionHeight == 0 +} + +// MustParseHeight will attempt to parse a string representation of a height and panic if +// parsing fails. +func MustParseHeight(heightStr string) Height { + height, err := ParseHeight(heightStr) + if err != nil { + panic(err) + } + + return height +} + +// ParseHeight is a utility function that takes a string representation of the height +// and returns a Height struct +func ParseHeight(heightStr string) (Height, error) { + splitStr := strings.Split(heightStr, "-") + if len(splitStr) != 2 { + return Height{}, errorsmod.Wrapf(ibcerrors.ErrInvalidHeight, "expected height string format: {revision}-{height}. Got: %s", heightStr) + } + revisionNumber, err := strconv.ParseUint(splitStr[0], 10, 64) + if err != nil { + return Height{}, errorsmod.Wrapf(ibcerrors.ErrInvalidHeight, "invalid revision number. parse err: %s", err) + } + revisionHeight, err := strconv.ParseUint(splitStr[1], 10, 64) + if err != nil { + return Height{}, errorsmod.Wrapf(ibcerrors.ErrInvalidHeight, "invalid revision height. parse err: %s", err) + } + return NewHeight(revisionNumber, revisionHeight), nil +} + +// SetRevisionNumber takes a chainID in valid revision format and swaps the revision number +// in the chainID with the given revision number. +func SetRevisionNumber(chainID string, revision uint64) (string, error) { + if !IsRevisionFormat(chainID) { + return "", errorsmod.Wrapf( + ibcerrors.ErrInvalidChainID, "chainID is not in revision format: %s", chainID, + ) + } + + splitStr := strings.Split(chainID, "-") + // swap out revision number with given revision + splitStr[len(splitStr)-1] = strconv.FormatUint(revision, 10) + return strings.Join(splitStr, "-"), nil +} + +// ParseChainID is a utility function that returns a revision number from the given ChainID. +// ParseChainID attempts to parse a chain id in the format: `{chainID}-{revision}` +// and return the revisionnumber as a uint64. +// If the chainID is not in the expected format, a default revision value of 0 is returned. +func ParseChainID(chainID string) uint64 { + if !IsRevisionFormat(chainID) { + // chainID is not in revision format, return 0 as default + return 0 + } + splitStr := strings.Split(chainID, "-") + revision, err := strconv.ParseUint(splitStr[len(splitStr)-1], 10, 64) + // sanity check: error should always be nil since regex only allows numbers in last element + if err != nil { + panic(fmt.Errorf("regex allowed non-number value as last split element for chainID: %s", chainID)) + } + return revision +} + +// GetSelfHeight is a utility function that returns self height given context +// Revision number is retrieved from ctx.ChainID() +func GetSelfHeight(ctx sdk.Context) Height { + revision := ParseChainID(ctx.ChainID()) + return NewHeight(revision, uint64(ctx.BlockHeight())) +} diff --git a/modules/core/02-client/types/height_test.go b/modules/core/02-client/types/height_test.go new file mode 100644 index 0000000..12312c4 --- /dev/null +++ b/modules/core/02-client/types/height_test.go @@ -0,0 +1,159 @@ +package types_test + +import ( + "math" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" +) + +func TestZeroHeight(t *testing.T) { + require.Equal(t, types.Height{}, types.ZeroHeight()) +} + +func TestCompareHeights(t *testing.T) { + testCases := []struct { + name string + height1 types.Height + height2 types.Height + compareSign int64 + }{ + {"revision number 1 is lesser", types.NewHeight(1, 3), types.NewHeight(3, 4), -1}, + {"revision number 1 is greater", types.NewHeight(7, 5), types.NewHeight(4, 5), 1}, + {"revision height 1 is lesser", types.NewHeight(3, 4), types.NewHeight(3, 9), -1}, + {"revision height 1 is greater", types.NewHeight(3, 8), types.NewHeight(3, 3), 1}, + {"revision number is MaxUint64", types.NewHeight(math.MaxUint64, 1), types.NewHeight(0, 1), 1}, + {"revision height is MaxUint64", types.NewHeight(1, math.MaxUint64), types.NewHeight(1, 0), 1}, + {"height is equal", types.NewHeight(4, 4), types.NewHeight(4, 4), 0}, + } + + for i, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + compare := tc.height1.Compare(tc.height2) + + switch tc.compareSign { + case -1: + require.True(t, compare == -1, "case %d: %s should return negative value on comparison, got: %d", + i, tc.name, compare) + case 0: + require.True(t, compare == 0, "case %d: %s should return zero on comparison, got: %d", + i, tc.name, compare) + case 1: + require.True(t, compare == 1, "case %d: %s should return positive value on comparison, got: %d", + i, tc.name, compare) + } + }) + } +} + +func TestDecrement(t *testing.T) { + validDecrement := types.NewHeight(3, 3) + expected := types.NewHeight(3, 2) + + actual, success := validDecrement.Decrement() + require.Equal(t, expected, actual, "decrementing %s did not return expected height: %s. got %s", + validDecrement, expected, actual) + require.True(t, success, "decrement failed unexpectedly") + + invalidDecrement := types.NewHeight(3, 0) + actual, success = invalidDecrement.Decrement() + + require.Equal(t, types.ZeroHeight(), actual, "invalid decrement returned non-zero height: %s", actual) + require.False(t, success, "invalid decrement passed") +} + +func TestString(t *testing.T) { + _, err := types.ParseHeight("height") + require.Error(t, err, "invalid height string passed") + + _, err = types.ParseHeight("revision-10") + require.Error(t, err, "invalid revision string passed") + + _, err = types.ParseHeight("3-height") + require.Error(t, err, "invalid revision-height string passed") + + height := types.NewHeight(3, 4) + recovered, err := types.ParseHeight(height.String()) + + require.NoError(t, err, "valid height string could not be parsed") + require.Equal(t, height, recovered, "recovered height not equal to original height") + + parse, err := types.ParseHeight("3-10") + require.NoError(t, err, "parse err") + require.Equal(t, types.NewHeight(3, 10), parse, "parse height returns wrong height") +} + +func (suite *TypesTestSuite) TestMustParseHeight() { + suite.Require().Panics(func() { + types.MustParseHeight("height") + }) + + suite.Require().NotPanics(func() { + types.MustParseHeight("111-1") + }) + + suite.Require().NotPanics(func() { + types.MustParseHeight("0-0") + }) +} + +func TestParseChainID(t *testing.T) { + cases := []struct { + chainID string + revision uint64 + formatted bool + }{ + {"gaiamainnet-3", 3, true}, + {"a-1", 1, true}, + {"gaia-mainnet-40", 40, true}, + {"gaiamainnet-3-39", 39, true}, + {"gaiamainnet--", 0, false}, + {"gaiamainnet-03", 0, false}, + {"gaiamainnet--4", 0, false}, + {"gaiamainnet-3.4", 0, false}, + {"gaiamainnet", 0, false}, + {"gaiamain\nnet-1", 0, false}, // newlines not allowed in chainID + {"gaiamainnet-1\n", 0, false}, // newlines not allowed after dash + {"gaiamainnet\n-3", 0, false}, // newlines not allowed before revision number + {"a--1", 0, false}, + {"-1", 0, false}, + {"--1", 0, false}, + } + + for _, tc := range cases { + require.Equal(t, tc.formatted, types.IsRevisionFormat(tc.chainID), "id %s does not match expected format", tc.chainID) + + revision := types.ParseChainID(tc.chainID) + require.Equal(t, tc.revision, revision, "chainID %s returns incorrect revision", tc.chainID) + } +} + +func TestSetRevisionNumber(t *testing.T) { + // Test SetRevisionNumber + chainID, err := types.SetRevisionNumber("gaiamainnet", 3) + require.Error(t, err, "invalid revision format passed SetRevisionNumber") + require.Equal(t, "", chainID, "invalid revision format returned non-empty string on SetRevisionNumber") + chainID = "gaiamainnet-3" + + chainID, err = types.SetRevisionNumber(chainID, 4) + require.NoError(t, err, "valid revision format failed SetRevisionNumber") + require.Equal(t, "gaiamainnet-4", chainID, "valid revision format returned incorrect string on SetRevisionNumber") +} + +func (suite *TypesTestSuite) TestSelfHeight() { + ctx := suite.chainA.GetContext() + + // Test default revision + ctx = ctx.WithChainID("gaiamainnet") + ctx = ctx.WithBlockHeight(10) + height := types.GetSelfHeight(ctx) + suite.Require().Equal(types.NewHeight(0, 10), height, "default self height failed") + + // Test successful revision format + ctx = ctx.WithChainID("gaiamainnet-3") + ctx = ctx.WithBlockHeight(18) + height = types.GetSelfHeight(ctx) + suite.Require().Equal(types.NewHeight(3, 18), height, "valid self height failed") +} diff --git a/modules/core/02-client/types/keys.go b/modules/core/02-client/types/keys.go new file mode 100644 index 0000000..dd09711 --- /dev/null +++ b/modules/core/02-client/types/keys.go @@ -0,0 +1,101 @@ +package types + +import ( + "fmt" + "regexp" + "strconv" + "strings" + + errorsmod "cosmossdk.io/errors" + + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +const ( + // SubModuleName defines the IBC client name + SubModuleName string = "client" + + // RouterKey is the message route for IBC client + RouterKey string = SubModuleName + + // QuerierRoute is the querier route for IBC client + QuerierRoute string = SubModuleName + + // KeyNextClientSequence is the key used to store the next client sequence in + // the keeper. + KeyNextClientSequence = "nextClientSequence" + + // ParamsKey is the store key for the IBC client parameters + ParamsKey = "clientParams" + + // KeyCreator is the key for the creator in the client-specific store + KeyCreator = "creator" + + // AllowAllClients is the value that if set in AllowedClients param + // would allow any wired up light client modules to be allowed + AllowAllClients = "*" +) + +// FormatClientIdentifier returns the client identifier with the sequence appended. +// This is an SDK specific format not enforced by IBC protocol. +func FormatClientIdentifier(clientType string, sequence uint64) string { + return fmt.Sprintf("%s-%d", clientType, sequence) +} + +// IsClientIDFormat checks if a clientID is in the format required on the SDK for +// parsing client identifiers. The client identifier must be in the form: `{client-type}-{N} +// which per the specification only permits ASCII for the {client-type} segment and +// 1 to 20 digits for the {N} segment. +// `([\w-]+\w)?` allows for a letter or hyphen, with the {client-type} starting with a letter +// and ending with a letter, i.e. `letter+(letter|hyphen+letter)?`. +var IsClientIDFormat = regexp.MustCompile(`^\w+([\w-]+\w)?-[0-9]{1,20}$`).MatchString + +// IsValidClientID checks if the clientID is valid and can be parsed into the client +// identifier format. +func IsValidClientID(clientID string) bool { + _, _, err := ParseClientIdentifier(clientID) + return err == nil +} + +// ParseClientIdentifier parses the client type and sequence from the client identifier. +func ParseClientIdentifier(clientID string) (string, uint64, error) { + if clientID == exported.LocalhostClientID { + return clientID, 0, nil + } + + if !IsClientIDFormat(clientID) { + return "", 0, errorsmod.Wrapf(host.ErrInvalidID, "invalid client identifier %s is not in format: `{client-type}-{N}`", clientID) + } + + splitStr := strings.Split(clientID, "-") + lastIndex := len(splitStr) - 1 + + clientType := strings.Join(splitStr[:lastIndex], "-") + if strings.TrimSpace(clientType) == "" { + return "", 0, errorsmod.Wrap(host.ErrInvalidID, "client identifier must be in format: `{client-type}-{N}` and client type cannot be blank") + } + + sequence, err := strconv.ParseUint(splitStr[lastIndex], 10, 64) + if err != nil { + return "", 0, errorsmod.Wrap(err, "failed to parse client identifier sequence") + } + + return clientType, sequence, nil +} + +// MustParseClientIdentifier parses the client type from the provided client identifier. +// If an invalid client identifier is provided this function will panic. +func MustParseClientIdentifier(clientID string) string { + clientType, _, err := ParseClientIdentifier(clientID) + if err != nil { + panic(err) + } + + return clientType +} + +// CreatorKey returns the key under which the client creator is stored in the client store +func CreatorKey() []byte { + return []byte(KeyCreator) +} diff --git a/modules/core/02-client/types/keys_test.go b/modules/core/02-client/types/keys_test.go new file mode 100644 index 0000000..dfa9c06 --- /dev/null +++ b/modules/core/02-client/types/keys_test.go @@ -0,0 +1,67 @@ +package types_test + +import ( + "errors" + "math" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +// tests ParseClientIdentifier and IsValidClientID +func TestParseClientIdentifier(t *testing.T) { + testCases := []struct { + name string + clientID string + clientType string + expSeq uint64 + expErr error + }{ + {"valid 0", "tendermint-0", "tendermint", 0, nil}, + {"valid 1", "tendermint-1", "tendermint", 1, nil}, + {"valid solemachine", "solomachine-v1-1", "solomachine-v1", 1, nil}, + {"valid large sequence", types.FormatClientIdentifier("tendermint", math.MaxUint64), "tendermint", math.MaxUint64, nil}, + {"valid short client type", "t-0", "t", 0, nil}, + // one above uint64 max + {"invalid uint64", "tendermint-18446744073709551616", "tendermint", 0, errors.New("failed to parse client identifier sequence")}, + // uint64 == 20 characters + {"invalid large sequence", "tendermint-2345682193567182931243", "tendermint", 0, host.ErrInvalidID}, + {"invalid newline in clientID", "tendermin\nt-1", "tendermin\nt", 0, host.ErrInvalidID}, + {"invalid newline character before dash", "tendermint\n-1", "tendermint", 0, host.ErrInvalidID}, + {"missing dash", "tendermint0", "tendermint", 0, host.ErrInvalidID}, + {"blank id", " ", " ", 0, host.ErrInvalidID}, + {"empty id", "", "", 0, host.ErrInvalidID}, + {"negative sequence", "tendermint--1", "tendermint", 0, host.ErrInvalidID}, + {"invalid format", "tendermint-tm", "tendermint", 0, host.ErrInvalidID}, + {"empty clientype", " -100", "tendermint", 0, host.ErrInvalidID}, + {"with in the middle tabs", "a\t\t\t-100", "tendermint", 0, host.ErrInvalidID}, + {"leading tabs", "\t\t\ta-100", "tendermint", 0, host.ErrInvalidID}, + {"with whitespace", " a-100", "tendermint", 0, host.ErrInvalidID}, + {"leading hyphens", "-----a-100", "tendermint", 0, host.ErrInvalidID}, + {"with slash", "tendermint/-100", "tendermint", 0, host.ErrInvalidID}, + {"non-ASCII:: emoji", "🚨😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎😎-100", "tendermint", 0, host.ErrInvalidID}, + {"non-ASCII:: others", "世界-100", "tendermint", 0, host.ErrInvalidID}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + clientType, seq, err := types.ParseClientIdentifier(tc.clientID) + valid := types.IsValidClientID(tc.clientID) + require.Equal(t, tc.expSeq, seq, tc.clientID) + + if tc.expErr == nil { + require.NoError(t, err, tc.name) + require.True(t, valid) + require.Equal(t, tc.clientType, clientType) + } else { + require.Error(t, err, tc.name, tc.clientID) + require.False(t, valid) + require.Equal(t, "", clientType) + require.ErrorContains(t, err, tc.expErr.Error()) + } + }) + } +} diff --git a/modules/core/02-client/types/legacy_proposal.go b/modules/core/02-client/types/legacy_proposal.go new file mode 100644 index 0000000..1792351 --- /dev/null +++ b/modules/core/02-client/types/legacy_proposal.go @@ -0,0 +1,150 @@ +package types + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + upgradetypes "cosmossdk.io/x/upgrade/types" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +const ( + // ProposalTypeClientUpdate defines the type for a ClientUpdateProposal + ProposalTypeClientUpdate = "ClientUpdate" + // ProposalTypeUpgrade defines the type for an UpgradeProposal + ProposalTypeUpgrade = "IBCUpgrade" +) + +var ( + _ govtypes.Content = &ClientUpdateProposal{} + _ govtypes.Content = &UpgradeProposal{} + _ codectypes.UnpackInterfacesMessage = &UpgradeProposal{} +) + +// func init() { +// govtypes.RegisterProposalType(ProposalTypeClientUpdate) +// govtypes.RegisterProposalType(ProposalTypeUpgrade) +// } + +// NewClientUpdateProposal creates a new client update proposal. +// +// Deprecated: The legacy v1beta1 gov ClientUpdateProposal is deprecated +// and will be removed in a future release. Please use MsgRecoverClient instead. +func NewClientUpdateProposal(title, description, subjectClientID, substituteClientID string) govtypes.Content { + return &ClientUpdateProposal{ + Title: title, + Description: description, + SubjectClientId: subjectClientID, + SubstituteClientId: substituteClientID, + } +} + +// GetTitle returns the title of a client update proposal. +func (cup *ClientUpdateProposal) GetTitle() string { return cup.Title } + +// GetDescription returns the description of a client update proposal. +func (cup *ClientUpdateProposal) GetDescription() string { return cup.Description } + +// ProposalRoute returns the routing key of a client update proposal. +func (*ClientUpdateProposal) ProposalRoute() string { return RouterKey } + +// ProposalType returns the type of a client update proposal. +func (*ClientUpdateProposal) ProposalType() string { return ProposalTypeClientUpdate } + +// ValidateBasic runs basic stateless validity checks +func (cup *ClientUpdateProposal) ValidateBasic() error { + err := govtypes.ValidateAbstract(cup) + if err != nil { + return err + } + + if cup.SubjectClientId == cup.SubstituteClientId { + return errorsmod.Wrap(ErrInvalidSubstitute, "subject and substitute client identifiers are equal") + } + if _, _, err := ParseClientIdentifier(cup.SubjectClientId); err != nil { + return err + } + if _, _, err := ParseClientIdentifier(cup.SubstituteClientId); err != nil { + return err + } + + return nil +} + +// NewUpgradeProposal creates a new IBC breaking upgrade proposal. +// +// Deprecated: The legacy v1beta1 gov UpgradeProposal is deprecated +// and will be removed in a future release. Please use MsgIBCSoftwareUpgrade instead. +func NewUpgradeProposal(title, description string, plan upgradetypes.Plan, upgradedClientState exported.ClientState) (govtypes.Content, error) { + clientAny, err := PackClientState(upgradedClientState) + if err != nil { + return nil, err + } + + return &UpgradeProposal{ + Title: title, + Description: description, + Plan: plan, + UpgradedClientState: clientAny, + }, nil +} + +// GetTitle returns the title of a upgrade proposal. +func (up *UpgradeProposal) GetTitle() string { return up.Title } + +// GetDescription returns the description of a upgrade proposal. +func (up *UpgradeProposal) GetDescription() string { return up.Description } + +// ProposalRoute returns the routing key of a upgrade proposal. +func (*UpgradeProposal) ProposalRoute() string { return RouterKey } + +// ProposalType returns the upgrade proposal type. +func (*UpgradeProposal) ProposalType() string { return ProposalTypeUpgrade } + +// ValidateBasic runs basic stateless validity checks +func (up *UpgradeProposal) ValidateBasic() error { + if err := govtypes.ValidateAbstract(up); err != nil { + return err + } + + if err := up.Plan.ValidateBasic(); err != nil { + return err + } + + if up.UpgradedClientState == nil { + return errorsmod.Wrap(ErrInvalidUpgradeProposal, "upgraded client state cannot be nil") + } + + _, err := UnpackClientState(up.UpgradedClientState) + if err != nil { + return errorsmod.Wrap(err, "failed to unpack upgraded client state") + } + + return nil +} + +// String returns the string representation of the UpgradeProposal. +func (up UpgradeProposal) String() string { + var upgradedClientStr string + upgradedClient, err := UnpackClientState(up.UpgradedClientState) + if err != nil { + upgradedClientStr = "invalid IBC Client State" + } else { + upgradedClientStr = upgradedClient.String() + } + + return fmt.Sprintf(`IBC Upgrade Proposal + Title: %s + Description: %s + %s + Upgraded IBC Client: %s`, up.Title, up.Description, up.Plan.String(), upgradedClientStr) +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (up UpgradeProposal) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + return unpacker.UnpackAny(up.UpgradedClientState, new(exported.ClientState)) +} diff --git a/modules/core/02-client/types/legacy_proposal_test.go b/modules/core/02-client/types/legacy_proposal_test.go new file mode 100644 index 0000000..abdf831 --- /dev/null +++ b/modules/core/02-client/types/legacy_proposal_test.go @@ -0,0 +1,89 @@ +package types_test + +import ( + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *TypesTestSuite) TestValidateBasic() { + subjectPath := ibctesting.NewPath(suite.chainA, suite.chainB) + subjectPath.SetupClients() + subject := subjectPath.EndpointA.ClientID + + substitutePath := ibctesting.NewPath(suite.chainA, suite.chainB) + substitutePath.SetupClients() + substitute := substitutePath.EndpointA.ClientID + + testCases := []struct { + name string + proposal govv1beta1.Content + expErr error + }{ + { + "success", + types.NewClientUpdateProposal(ibctesting.Title, ibctesting.Description, subject, substitute), + nil, + }, + { + "fails validate abstract - empty title", + types.NewClientUpdateProposal("", ibctesting.Description, subject, substitute), + govtypes.ErrInvalidProposalContent, + }, + { + "subject and substitute use the same identifier", + types.NewClientUpdateProposal(ibctesting.Title, ibctesting.Description, subject, subject), + types.ErrInvalidSubstitute, + }, + { + "invalid subject clientID", + types.NewClientUpdateProposal(ibctesting.Title, ibctesting.Description, ibctesting.InvalidID, substitute), + host.ErrInvalidID, + }, + { + "invalid substitute clientID", + types.NewClientUpdateProposal(ibctesting.Title, ibctesting.Description, subject, ibctesting.InvalidID), + host.ErrInvalidID, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := tc.proposal.ValidateBasic() + + if tc.expErr == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().ErrorIs(err, tc.expErr, tc.name) + } + }) + } +} + +// tests a client update proposal can be marshaled and unmarshaled +func (suite *TypesTestSuite) TestMarshalClientUpdateProposalProposal() { + // create proposal + proposal := types.NewClientUpdateProposal("update IBC client", "description", "subject", "substitute") + + // create codec + ir := codectypes.NewInterfaceRegistry() + types.RegisterInterfaces(ir) + govv1beta1.RegisterInterfaces(ir) + cdc := codec.NewProtoCodec(ir) + + // marshal message + content, ok := proposal.(*types.ClientUpdateProposal) + suite.Require().True(ok) + bz, err := cdc.MarshalJSON(content) + suite.Require().NoError(err) + + // unmarshal proposal + newProposal := &types.ClientUpdateProposal{} + err = cdc.UnmarshalJSON(bz, newProposal) + suite.Require().NoError(err) +} diff --git a/modules/core/02-client/types/legacy_upgrade_proposal.pb.go b/modules/core/02-client/types/legacy_upgrade_proposal.pb.go new file mode 100644 index 0000000..dbf11d9 --- /dev/null +++ b/modules/core/02-client/types/legacy_upgrade_proposal.pb.go @@ -0,0 +1,829 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/client/v1/deprecated.proto + +package types + +import ( + types "cosmossdk.io/x/upgrade/types" + fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + types1 "github.com/cosmos/cosmos-sdk/codec/types" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// ClientUpdateProposal is a legacy governance proposal. If it passes, the substitute +// client's latest consensus state is copied over to the subject client. The proposal +// handler may fail if the subject and the substitute do not match in client and +// chain parameters (with exception to latest height, frozen height, and chain-id). +// +// Deprecated: Please use MsgRecoverClient in favour of this message type. +// +// Deprecated: Do not use. +type ClientUpdateProposal struct { + // the title of the update proposal + Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` + // the description of the proposal + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + // the client identifier for the client to be updated if the proposal passes + SubjectClientId string `protobuf:"bytes,3,opt,name=subject_client_id,json=subjectClientId,proto3" json:"subject_client_id,omitempty" yaml:"subject_client_id"` + // the substitute client identifier for the client standing in for the subject + // client + SubstituteClientId string `protobuf:"bytes,4,opt,name=substitute_client_id,json=substituteClientId,proto3" json:"substitute_client_id,omitempty" yaml:"substitute_client_id"` +} + +func (m *ClientUpdateProposal) Reset() { *m = ClientUpdateProposal{} } +func (m *ClientUpdateProposal) String() string { return proto.CompactTextString(m) } +func (*ClientUpdateProposal) ProtoMessage() {} +func (*ClientUpdateProposal) Descriptor() ([]byte, []int) { + return fileDescriptor_95e37bacc9dd01d3, []int{0} +} +func (m *ClientUpdateProposal) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ClientUpdateProposal) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ClientUpdateProposal.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ClientUpdateProposal) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClientUpdateProposal.Merge(m, src) +} +func (m *ClientUpdateProposal) XXX_Size() int { + return m.Size() +} +func (m *ClientUpdateProposal) XXX_DiscardUnknown() { + xxx_messageInfo_ClientUpdateProposal.DiscardUnknown(m) +} + +var xxx_messageInfo_ClientUpdateProposal proto.InternalMessageInfo + +// UpgradeProposal is a gov Content type for initiating an IBC breaking +// upgrade. +// +// Deprecated: Please use MsgIBCSoftwareUpgrade in favour of this message type. +// +// Deprecated: Do not use. +type UpgradeProposal struct { + Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + Plan types.Plan `protobuf:"bytes,3,opt,name=plan,proto3" json:"plan"` + // An UpgradedClientState must be provided to perform an IBC breaking upgrade. + // This will make the chain commit to the correct upgraded (self) client state + // before the upgrade occurs, so that connecting chains can verify that the + // new upgraded client is valid by verifying a proof on the previous version + // of the chain. This will allow IBC connections to persist smoothly across + // planned chain upgrades + UpgradedClientState *types1.Any `protobuf:"bytes,4,opt,name=upgraded_client_state,json=upgradedClientState,proto3" json:"upgraded_client_state,omitempty" yaml:"upgraded_client_state"` +} + +func (m *UpgradeProposal) Reset() { *m = UpgradeProposal{} } +func (*UpgradeProposal) ProtoMessage() {} +func (*UpgradeProposal) Descriptor() ([]byte, []int) { + return fileDescriptor_95e37bacc9dd01d3, []int{1} +} +func (m *UpgradeProposal) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *UpgradeProposal) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_UpgradeProposal.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *UpgradeProposal) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpgradeProposal.Merge(m, src) +} +func (m *UpgradeProposal) XXX_Size() int { + return m.Size() +} +func (m *UpgradeProposal) XXX_DiscardUnknown() { + xxx_messageInfo_UpgradeProposal.DiscardUnknown(m) +} + +var xxx_messageInfo_UpgradeProposal proto.InternalMessageInfo + +func init() { + proto.RegisterType((*ClientUpdateProposal)(nil), "ibc.core.client.v1.ClientUpdateProposal") + proto.RegisterType((*UpgradeProposal)(nil), "ibc.core.client.v1.UpgradeProposal") +} + +func init() { + proto.RegisterFile("ibc/core/client/v1/deprecated.proto", fileDescriptor_95e37bacc9dd01d3) +} + +var fileDescriptor_95e37bacc9dd01d3 = []byte{ + // 481 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x53, 0xb1, 0x8e, 0xd3, 0x40, + 0x10, 0xb5, 0x43, 0x40, 0xc2, 0x29, 0x4e, 0x18, 0x23, 0x99, 0x10, 0xd9, 0x91, 0xb9, 0x82, 0x82, + 0xec, 0x9e, 0x83, 0x84, 0x44, 0x3a, 0x72, 0x0d, 0x74, 0x87, 0x4f, 0xd7, 0xd0, 0x44, 0xeb, 0xf5, + 0x62, 0xf6, 0xe4, 0x78, 0x2d, 0xef, 0xd8, 0x52, 0xfe, 0x80, 0x12, 0x3a, 0x24, 0x9a, 0x7c, 0x04, + 0x1f, 0x71, 0xa2, 0xba, 0x92, 0x2a, 0x42, 0x49, 0x43, 0x9d, 0x2f, 0x40, 0xde, 0xb5, 0xe1, 0xa4, + 0x20, 0x51, 0xd0, 0xf9, 0xbd, 0x79, 0x33, 0xe3, 0x79, 0xb3, 0x63, 0x3d, 0xe6, 0x31, 0xc5, 0x54, + 0x94, 0x0c, 0xd3, 0x8c, 0xb3, 0x1c, 0x70, 0x1d, 0xe2, 0x84, 0x15, 0x25, 0xa3, 0x04, 0x58, 0x82, + 0x8a, 0x52, 0x80, 0xb0, 0x6d, 0x1e, 0x53, 0xd4, 0x88, 0x90, 0x16, 0xa1, 0x3a, 0x1c, 0x1e, 0x53, + 0x21, 0x97, 0x42, 0xe2, 0xaa, 0x48, 0x4b, 0x92, 0x30, 0x5c, 0x87, 0x31, 0x03, 0x12, 0x76, 0x58, + 0x67, 0x0e, 0x1f, 0x6a, 0xd5, 0x42, 0x21, 0xac, 0x41, 0x1b, 0x72, 0x52, 0x91, 0x0a, 0xcd, 0x37, + 0x5f, 0x5d, 0x42, 0x2a, 0x44, 0x9a, 0x31, 0xac, 0x50, 0x5c, 0xbd, 0xc3, 0x24, 0x5f, 0xe9, 0x50, + 0xf0, 0xa9, 0x67, 0x39, 0xa7, 0xaa, 0xff, 0x45, 0x91, 0x10, 0x60, 0x67, 0xa5, 0x28, 0x84, 0x24, + 0x99, 0xed, 0x58, 0xb7, 0x81, 0x43, 0xc6, 0x5c, 0x73, 0x6c, 0x3e, 0xb9, 0x1b, 0x69, 0x60, 0x8f, + 0xad, 0x41, 0xc2, 0x24, 0x2d, 0x79, 0x01, 0x5c, 0xe4, 0x6e, 0x4f, 0xc5, 0x6e, 0x52, 0xf6, 0x2b, + 0xeb, 0x9e, 0xac, 0xe2, 0x4b, 0x46, 0x61, 0xa1, 0xe7, 0x5a, 0xf0, 0xc4, 0xbd, 0xd5, 0xe8, 0xe6, + 0xa3, 0xfd, 0xc6, 0x77, 0x57, 0x64, 0x99, 0xcd, 0x82, 0x03, 0x49, 0x10, 0x1d, 0xb5, 0x9c, 0xfe, + 0x9b, 0xd7, 0x89, 0xfd, 0xc6, 0x72, 0x64, 0x15, 0x4b, 0xe0, 0x50, 0x01, 0xbb, 0x51, 0xac, 0xaf, + 0x8a, 0xf9, 0xfb, 0x8d, 0xff, 0xe8, 0x77, 0xb1, 0x03, 0x55, 0x10, 0xd9, 0x7f, 0xe8, 0xae, 0xe4, + 0xec, 0xf8, 0xc3, 0xda, 0x37, 0xbe, 0x7d, 0x9d, 0x0c, 0x5b, 0xd3, 0x52, 0x51, 0xa3, 0xd6, 0x63, + 0x74, 0x2a, 0x72, 0x60, 0x39, 0xb8, 0x66, 0xf0, 0xa5, 0x67, 0x1d, 0x5d, 0x68, 0xc7, 0xff, 0xdb, + 0x8e, 0xe7, 0x56, 0xbf, 0xc8, 0x48, 0xae, 0x1c, 0x18, 0x4c, 0x47, 0xa8, 0x6d, 0xdc, 0x2d, 0xb4, + 0x6b, 0x7e, 0x96, 0x91, 0x7c, 0xde, 0xbf, 0xda, 0xf8, 0x46, 0xa4, 0xf4, 0xf6, 0xa5, 0xf5, 0xa0, + 0xd5, 0x24, 0xdd, 0x50, 0x12, 0x08, 0x30, 0x35, 0xfd, 0x60, 0xea, 0x20, 0xbd, 0x52, 0xd4, 0xad, + 0x14, 0xbd, 0xcc, 0x57, 0xf3, 0xf1, 0x7e, 0xe3, 0x8f, 0xb4, 0x27, 0x7f, 0x4d, 0x0e, 0xa2, 0xfb, + 0x1d, 0xaf, 0x2d, 0x39, 0x6f, 0xd8, 0xd9, 0xd3, 0xc6, 0x95, 0xcf, 0x6b, 0xdf, 0xf8, 0xb9, 0xf6, + 0xcd, 0x7f, 0xb9, 0x33, 0x3f, 0xbf, 0xda, 0x7a, 0xe6, 0xf5, 0xd6, 0x33, 0x7f, 0x6c, 0x3d, 0xf3, + 0xe3, 0xce, 0x33, 0xae, 0x77, 0x9e, 0xf1, 0x7d, 0xe7, 0x19, 0x6f, 0x5f, 0xa4, 0x1c, 0xde, 0x57, + 0x31, 0xa2, 0x62, 0xd9, 0xbe, 0x4a, 0xcc, 0x63, 0x3a, 0x49, 0x05, 0xae, 0xc3, 0x13, 0xbc, 0x14, + 0x49, 0x95, 0x31, 0xa9, 0xef, 0xe2, 0x64, 0x3a, 0x69, 0x4f, 0x03, 0x56, 0x05, 0x93, 0xf1, 0x1d, + 0x35, 0xc7, 0xb3, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x4b, 0xb1, 0xbc, 0x55, 0x3a, 0x03, 0x00, + 0x00, +} + +func (this *UpgradeProposal) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*UpgradeProposal) + if !ok { + that2, ok := that.(UpgradeProposal) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.Title != that1.Title { + return false + } + if this.Description != that1.Description { + return false + } + if !this.Plan.Equal(&that1.Plan) { + return false + } + if !this.UpgradedClientState.Equal(that1.UpgradedClientState) { + return false + } + return true +} +func (m *ClientUpdateProposal) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ClientUpdateProposal) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ClientUpdateProposal) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.SubstituteClientId) > 0 { + i -= len(m.SubstituteClientId) + copy(dAtA[i:], m.SubstituteClientId) + i = encodeVarintDeprecated(dAtA, i, uint64(len(m.SubstituteClientId))) + i-- + dAtA[i] = 0x22 + } + if len(m.SubjectClientId) > 0 { + i -= len(m.SubjectClientId) + copy(dAtA[i:], m.SubjectClientId) + i = encodeVarintDeprecated(dAtA, i, uint64(len(m.SubjectClientId))) + i-- + dAtA[i] = 0x1a + } + if len(m.Description) > 0 { + i -= len(m.Description) + copy(dAtA[i:], m.Description) + i = encodeVarintDeprecated(dAtA, i, uint64(len(m.Description))) + i-- + dAtA[i] = 0x12 + } + if len(m.Title) > 0 { + i -= len(m.Title) + copy(dAtA[i:], m.Title) + i = encodeVarintDeprecated(dAtA, i, uint64(len(m.Title))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *UpgradeProposal) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *UpgradeProposal) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *UpgradeProposal) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.UpgradedClientState != nil { + { + size, err := m.UpgradedClientState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintDeprecated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + { + size, err := m.Plan.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintDeprecated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.Description) > 0 { + i -= len(m.Description) + copy(dAtA[i:], m.Description) + i = encodeVarintDeprecated(dAtA, i, uint64(len(m.Description))) + i-- + dAtA[i] = 0x12 + } + if len(m.Title) > 0 { + i -= len(m.Title) + copy(dAtA[i:], m.Title) + i = encodeVarintDeprecated(dAtA, i, uint64(len(m.Title))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintDeprecated(dAtA []byte, offset int, v uint64) int { + offset -= sovDeprecated(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *ClientUpdateProposal) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Title) + if l > 0 { + n += 1 + l + sovDeprecated(uint64(l)) + } + l = len(m.Description) + if l > 0 { + n += 1 + l + sovDeprecated(uint64(l)) + } + l = len(m.SubjectClientId) + if l > 0 { + n += 1 + l + sovDeprecated(uint64(l)) + } + l = len(m.SubstituteClientId) + if l > 0 { + n += 1 + l + sovDeprecated(uint64(l)) + } + return n +} + +func (m *UpgradeProposal) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Title) + if l > 0 { + n += 1 + l + sovDeprecated(uint64(l)) + } + l = len(m.Description) + if l > 0 { + n += 1 + l + sovDeprecated(uint64(l)) + } + l = m.Plan.Size() + n += 1 + l + sovDeprecated(uint64(l)) + if m.UpgradedClientState != nil { + l = m.UpgradedClientState.Size() + n += 1 + l + sovDeprecated(uint64(l)) + } + return n +} + +func sovDeprecated(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozDeprecated(x uint64) (n int) { + return sovDeprecated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *ClientUpdateProposal) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeprecated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ClientUpdateProposal: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ClientUpdateProposal: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Title", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeprecated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDeprecated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthDeprecated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Title = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeprecated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDeprecated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthDeprecated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Description = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SubjectClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeprecated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDeprecated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthDeprecated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SubjectClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SubstituteClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeprecated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDeprecated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthDeprecated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SubstituteClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDeprecated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthDeprecated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *UpgradeProposal) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeprecated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: UpgradeProposal: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: UpgradeProposal: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Title", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeprecated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDeprecated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthDeprecated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Title = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeprecated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDeprecated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthDeprecated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Description = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Plan", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeprecated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDeprecated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDeprecated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Plan.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UpgradedClientState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeprecated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDeprecated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDeprecated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.UpgradedClientState == nil { + m.UpgradedClientState = &types1.Any{} + } + if err := m.UpgradedClientState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDeprecated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthDeprecated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipDeprecated(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDeprecated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDeprecated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDeprecated + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthDeprecated + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupDeprecated + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthDeprecated + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthDeprecated = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowDeprecated = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupDeprecated = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/02-client/types/msgs.go b/modules/core/02-client/types/msgs.go new file mode 100644 index 0000000..596bd27 --- /dev/null +++ b/modules/core/02-client/types/msgs.go @@ -0,0 +1,344 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + upgradetypes "cosmossdk.io/x/upgrade/types" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var ( + _ sdk.Msg = (*MsgCreateClient)(nil) + _ sdk.Msg = (*MsgUpdateClient)(nil) + _ sdk.Msg = (*MsgSubmitMisbehaviour)(nil) + _ sdk.Msg = (*MsgUpgradeClient)(nil) + _ sdk.Msg = (*MsgUpdateParams)(nil) + _ sdk.Msg = (*MsgIBCSoftwareUpgrade)(nil) + _ sdk.Msg = (*MsgRecoverClient)(nil) + _ sdk.Msg = (*MsgDeleteClientCreator)(nil) + + _ sdk.HasValidateBasic = (*MsgCreateClient)(nil) + _ sdk.HasValidateBasic = (*MsgUpdateClient)(nil) + _ sdk.HasValidateBasic = (*MsgSubmitMisbehaviour)(nil) + _ sdk.HasValidateBasic = (*MsgUpgradeClient)(nil) + _ sdk.HasValidateBasic = (*MsgUpdateParams)(nil) + _ sdk.HasValidateBasic = (*MsgIBCSoftwareUpgrade)(nil) + _ sdk.HasValidateBasic = (*MsgRecoverClient)(nil) + _ sdk.HasValidateBasic = (*MsgDeleteClientCreator)(nil) + + _ codectypes.UnpackInterfacesMessage = (*MsgCreateClient)(nil) + _ codectypes.UnpackInterfacesMessage = (*MsgUpdateClient)(nil) + _ codectypes.UnpackInterfacesMessage = (*MsgSubmitMisbehaviour)(nil) + _ codectypes.UnpackInterfacesMessage = (*MsgUpgradeClient)(nil) + _ codectypes.UnpackInterfacesMessage = (*MsgIBCSoftwareUpgrade)(nil) +) + +// NewMsgCreateClient creates a new MsgCreateClient instance +func NewMsgCreateClient( + clientState exported.ClientState, consensusState exported.ConsensusState, signer string, +) (*MsgCreateClient, error) { + anyClientState, err := PackClientState(clientState) + if err != nil { + return nil, err + } + + anyConsensusState, err := PackConsensusState(consensusState) + if err != nil { + return nil, err + } + + return &MsgCreateClient{ + ClientState: anyClientState, + ConsensusState: anyConsensusState, + Signer: signer, + }, nil +} + +// ValidateBasic implements sdk.Msg +func (msg MsgCreateClient) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + clientState, err := UnpackClientState(msg.ClientState) + if err != nil { + return err + } + if err := clientState.Validate(); err != nil { + return err + } + consensusState, err := UnpackConsensusState(msg.ConsensusState) + if err != nil { + return err + } + if clientState.ClientType() != consensusState.ClientType() { + return errorsmod.Wrap(ErrInvalidClientType, "client type for client state and consensus state do not match") + } + if err := ValidateClientType(clientState.ClientType()); err != nil { + return errorsmod.Wrap(err, "client type does not meet naming constraints") + } + return consensusState.ValidateBasic() +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (msg MsgCreateClient) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + var clientState exported.ClientState + err := unpacker.UnpackAny(msg.ClientState, &clientState) + if err != nil { + return err + } + + var consensusState exported.ConsensusState + return unpacker.UnpackAny(msg.ConsensusState, &consensusState) +} + +// NewMsgUpdateClient creates a new MsgUpdateClient instance +func NewMsgUpdateClient(id string, clientMsg exported.ClientMessage, signer string) (*MsgUpdateClient, error) { + anyClientMsg, err := PackClientMessage(clientMsg) + if err != nil { + return nil, err + } + + return &MsgUpdateClient{ + ClientId: id, + ClientMessage: anyClientMsg, + Signer: signer, + }, nil +} + +// ValidateBasic implements sdk.Msg +func (msg MsgUpdateClient) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + clientMsg, err := UnpackClientMessage(msg.ClientMessage) + if err != nil { + return err + } + if err := clientMsg.ValidateBasic(); err != nil { + return err + } + return host.ClientIdentifierValidator(msg.ClientId) +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (msg MsgUpdateClient) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + var clientMsg exported.ClientMessage + return unpacker.UnpackAny(msg.ClientMessage, &clientMsg) +} + +// NewMsgUpgradeClient creates a new MsgUpgradeClient instance +func NewMsgUpgradeClient(clientID string, clientState exported.ClientState, consState exported.ConsensusState, + upgradeClientProof, upgradeConsensusStateProof []byte, signer string, +) (*MsgUpgradeClient, error) { + anyClient, err := PackClientState(clientState) + if err != nil { + return nil, err + } + anyConsState, err := PackConsensusState(consState) + if err != nil { + return nil, err + } + + return &MsgUpgradeClient{ + ClientId: clientID, + ClientState: anyClient, + ConsensusState: anyConsState, + ProofUpgradeClient: upgradeClientProof, + ProofUpgradeConsensusState: upgradeConsensusStateProof, + Signer: signer, + }, nil +} + +// ValidateBasic implements sdk.Msg +func (msg MsgUpgradeClient) ValidateBasic() error { + // will not validate client state as committed client may not form a valid client state. + // client implementations are responsible for ensuring final upgraded client is valid. + clientState, err := UnpackClientState(msg.ClientState) + if err != nil { + return err + } + // will not validate consensus state here since the trusted kernel may not form a valid consensus state. + // client implementations are responsible for ensuring client can submit new headers against this consensus state. + consensusState, err := UnpackConsensusState(msg.ConsensusState) + if err != nil { + return err + } + + if clientState.ClientType() != consensusState.ClientType() { + return errorsmod.Wrapf(ErrInvalidUpgradeClient, "consensus state's client-type does not match client. expected: %s, got: %s", + clientState.ClientType(), consensusState.ClientType()) + } + if len(msg.ProofUpgradeClient) == 0 { + return errorsmod.Wrap(ErrInvalidUpgradeClient, "proof of upgrade client cannot be empty") + } + if len(msg.ProofUpgradeConsensusState) == 0 { + return errorsmod.Wrap(ErrInvalidUpgradeClient, "proof of upgrade consensus state cannot be empty") + } + _, err = sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + return host.ClientIdentifierValidator(msg.ClientId) +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (msg MsgUpgradeClient) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + var ( + clientState exported.ClientState + consState exported.ConsensusState + ) + if err := unpacker.UnpackAny(msg.ClientState, &clientState); err != nil { + return err + } + return unpacker.UnpackAny(msg.ConsensusState, &consState) +} + +// NewMsgSubmitMisbehaviour creates a new MsgSubmitMisbehaviour instance. +func NewMsgSubmitMisbehaviour(clientID string, misbehaviour exported.ClientMessage, signer string) (*MsgSubmitMisbehaviour, error) { + anyMisbehaviour, err := PackClientMessage(misbehaviour) + if err != nil { + return nil, err + } + + return &MsgSubmitMisbehaviour{ + ClientId: clientID, + Misbehaviour: anyMisbehaviour, + Signer: signer, + }, nil +} + +// ValidateBasic performs basic (non-state-dependant) validation on a MsgSubmitMisbehaviour. +func (msg MsgSubmitMisbehaviour) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + misbehaviour, err := UnpackClientMessage(msg.Misbehaviour) + if err != nil { + return err + } + if err := misbehaviour.ValidateBasic(); err != nil { + return err + } + + return host.ClientIdentifierValidator(msg.ClientId) +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (msg MsgSubmitMisbehaviour) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + var misbehaviour exported.ClientMessage + return unpacker.UnpackAny(msg.Misbehaviour, &misbehaviour) +} + +// NewMsgRecoverClient creates a new MsgRecoverClient instance +func NewMsgRecoverClient(signer, subjectClientID, substituteClientID string) *MsgRecoverClient { + return &MsgRecoverClient{ + Signer: signer, + SubjectClientId: subjectClientID, + SubstituteClientId: substituteClientID, + } +} + +// ValidateBasic performs basic checks on a MsgRecoverClient. +func (msg *MsgRecoverClient) ValidateBasic() error { + if _, err := sdk.AccAddressFromBech32(msg.Signer); err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + + if err := host.ClientIdentifierValidator(msg.SubjectClientId); err != nil { + return err + } + + if err := host.ClientIdentifierValidator(msg.SubstituteClientId); err != nil { + return err + } + + if msg.SubjectClientId == msg.SubstituteClientId { + return errorsmod.Wrapf(ErrInvalidSubstitute, "subject and substitute clients must be different") + } + + return nil +} + +// NewMsgIBCSoftwareUpgrade creates a new MsgIBCSoftwareUpgrade instance +func NewMsgIBCSoftwareUpgrade(signer string, plan upgradetypes.Plan, upgradedClientState exported.ClientState) (*MsgIBCSoftwareUpgrade, error) { + anyClient, err := PackClientState(upgradedClientState) + if err != nil { + return nil, err + } + + return &MsgIBCSoftwareUpgrade{ + Signer: signer, + Plan: plan, + UpgradedClientState: anyClient, + }, nil +} + +// ValidateBasic performs basic checks on a MsgIBCSoftwareUpgrade. +func (msg *MsgIBCSoftwareUpgrade) ValidateBasic() error { + if _, err := sdk.AccAddressFromBech32(msg.Signer); err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + + clientState, err := UnpackClientState(msg.UpgradedClientState) + if err != nil { + return err + } + + // for the time being, we should implicitly be on tendermint when using ibc-go + if clientState.ClientType() != exported.Tendermint { + return errorsmod.Wrapf(ErrInvalidUpgradeClient, "upgraded client state must be a Tendermint client") + } + + return msg.Plan.ValidateBasic() +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (msg *MsgIBCSoftwareUpgrade) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + return unpacker.UnpackAny(msg.UpgradedClientState, new(exported.ClientState)) +} + +// NewMsgUpdateParams creates a new instance of MsgUpdateParams. +func NewMsgUpdateParams(signer string, params Params) *MsgUpdateParams { + return &MsgUpdateParams{ + Signer: signer, + Params: params, + } +} + +// ValidateBasic performs basic checks on a MsgUpdateParams. +func (msg *MsgUpdateParams) ValidateBasic() error { + if _, err := sdk.AccAddressFromBech32(msg.Signer); err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + return msg.Params.Validate() +} + +// NewMsgDeleteClientCreator creates a new instance of MsgDeleteClientCreator. +func NewMsgDeleteClientCreator(clientID string, signer string) *MsgDeleteClientCreator { + return &MsgDeleteClientCreator{ + ClientId: clientID, + Signer: signer, + } +} + +// ValidateBasic performs basic validation of the MsgDeleteClientCreator fields. +func (msg *MsgDeleteClientCreator) ValidateBasic() error { + if _, err := sdk.AccAddressFromBech32(msg.Signer); err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + if err := host.ClientIdentifierValidator(msg.ClientId); err != nil { + return err + } + if !IsValidClientID(msg.ClientId) { + return errorsmod.Wrapf(host.ErrInvalidID, "client ID %s must be in valid format: {string}-{number}", msg.ClientId) + } + return nil +} diff --git a/modules/core/02-client/types/msgs_test.go b/modules/core/02-client/types/msgs_test.go new file mode 100644 index 0000000..3074739 --- /dev/null +++ b/modules/core/02-client/types/msgs_test.go @@ -0,0 +1,968 @@ +package types_test + +import ( + "errors" + "testing" + "time" + + "github.com/golang/protobuf/proto" //nolint:staticcheck + "github.com/stretchr/testify/require" + testifysuite "github.com/stretchr/testify/suite" + + errorsmod "cosmossdk.io/errors" + upgradetypes "cosmossdk.io/x/upgrade/types" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + + ibc "github.com/cosmos/ibc-go/v10/modules/core" + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +type TypesTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain + solomachine *ibctesting.Solomachine +} + +func (suite *TypesTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) + suite.solomachine = ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachinesingle", "testing", 1) +} + +func TestTypesTestSuite(t *testing.T) { + testifysuite.Run(t, new(TypesTestSuite)) +} + +// tests that different clients within MsgCreateClient can be marshaled +// and unmarshaled. +func (suite *TypesTestSuite) TestMarshalMsgCreateClient() { + var ( + msg *types.MsgCreateClient + err error + ) + + testCases := []struct { + name string + malleate func() + }{ + { + "solo machine client", func() { + soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2) + msg, err = types.NewMsgCreateClient(soloMachine.ClientState(), soloMachine.ConsensusState(), suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + }, + { + "tendermint client", func() { + tendermintClient := ibctm.NewClientState(suite.chainA.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath) + msg, err = types.NewMsgCreateClient(tendermintClient, suite.chainA.CurrentTMClientHeader().ConsensusState(), suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + tc.malleate() + + cdc := suite.chainA.App.AppCodec() + + // marshal message + bz, err := cdc.MarshalJSON(msg) + suite.Require().NoError(err) + + // unmarshal message + newMsg := &types.MsgCreateClient{} + err = cdc.UnmarshalJSON(bz, newMsg) + suite.Require().NoError(err) + + suite.Require().True(proto.Equal(msg, newMsg)) + }) + } +} + +func (suite *TypesTestSuite) TestMsgCreateClient_ValidateBasic() { + var ( + msg = &types.MsgCreateClient{} + err error + ) + + cases := []struct { + name string + malleate func() + expErr error + }{ + { + "valid - tendermint client", + func() { + tendermintClient := ibctm.NewClientState(suite.chainA.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath) + msg, err = types.NewMsgCreateClient(tendermintClient, suite.chainA.CurrentTMClientHeader().ConsensusState(), suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + nil, + }, + { + "invalid tendermint client", + func() { + msg, err = types.NewMsgCreateClient(&ibctm.ClientState{}, suite.chainA.CurrentTMClientHeader().ConsensusState(), suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + errorsmod.Wrap(ibctm.ErrInvalidChainID, "chain id cannot be empty string"), + }, + { + "failed to unpack client", + func() { + msg.ClientState = nil + }, + errorsmod.Wrap(ibcerrors.ErrUnpackAny, "protobuf Any message cannot be nil"), + }, + { + "failed to unpack consensus state", + func() { + tendermintClient := ibctm.NewClientState(suite.chainA.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath) + msg, err = types.NewMsgCreateClient(tendermintClient, suite.chainA.CurrentTMClientHeader().ConsensusState(), suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + msg.ConsensusState = nil + }, + errorsmod.Wrap(ibcerrors.ErrUnpackAny, "protobuf Any message cannot be nil"), + }, + { + "invalid signer", + func() { + msg.Signer = "" + }, + errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: empty address string is not allowed"), + }, + { + "valid - solomachine client", + func() { + soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2) + msg, err = types.NewMsgCreateClient(soloMachine.ClientState(), soloMachine.ConsensusState(), suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + nil, + }, + { + "invalid solomachine client", + func() { + soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2) + msg, err = types.NewMsgCreateClient(&solomachine.ClientState{}, soloMachine.ConsensusState(), suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + errorsmod.Wrap(types.ErrInvalidClient, "sequence cannot be 0"), + }, + { + "invalid solomachine consensus state", + func() { + soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2) + msg, err = types.NewMsgCreateClient(soloMachine.ClientState(), &solomachine.ConsensusState{}, suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + errorsmod.Wrap(types.ErrInvalidConsensus, "timestamp cannot be 0"), + }, + { + "invalid - client state and consensus state client types do not match", + func() { + tendermintClient := ibctm.NewClientState(suite.chainA.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath) + soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2) + msg, err = types.NewMsgCreateClient(tendermintClient, soloMachine.ConsensusState(), suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + errorsmod.Wrap(types.ErrInvalidClientType, "client type for client state and consensus state do not match"), + }, + } + + for _, tc := range cases { + tc.malleate() + err = msg.ValidateBasic() + if tc.expErr == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().Error(err, tc.name) + suite.Require().ErrorIs(err, tc.expErr) + } + } +} + +// tests that different header within MsgUpdateClient can be marshaled +// and unmarshaled. +func (suite *TypesTestSuite) TestMarshalMsgUpdateClient() { + var ( + msg *types.MsgUpdateClient + err error + ) + + testCases := []struct { + name string + malleate func() + }{ + { + "solo machine client", func() { + soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2) + msg, err = types.NewMsgUpdateClient(soloMachine.ClientID, soloMachine.CreateHeader(soloMachine.Diversifier), suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + }, + { + "tendermint client", func() { + msg, err = types.NewMsgUpdateClient("tendermint", suite.chainA.CurrentTMClientHeader(), suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + tc.malleate() + + cdc := suite.chainA.App.AppCodec() + + // marshal message + bz, err := cdc.MarshalJSON(msg) + suite.Require().NoError(err) + + // unmarshal message + newMsg := &types.MsgUpdateClient{} + err = cdc.UnmarshalJSON(bz, newMsg) + suite.Require().NoError(err) + + suite.Require().True(proto.Equal(msg, newMsg)) + }) + } +} + +func (suite *TypesTestSuite) TestMsgUpdateClient_ValidateBasic() { + var ( + msg = &types.MsgUpdateClient{} + err error + ) + + cases := []struct { + name string + malleate func() + expErr error + }{ + { + "invalid client-id", + func() { + msg.ClientId = "" + }, + errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: empty address string is not allowed"), + }, + { + "valid - tendermint header", + func() { + msg, err = types.NewMsgUpdateClient("tendermint", suite.chainA.CurrentTMClientHeader(), suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + nil, + }, + { + "invalid tendermint header", + func() { + msg, err = types.NewMsgUpdateClient("tendermint", &ibctm.Header{}, suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + errorsmod.Wrap(types.ErrInvalidHeader, "tendermint signed header cannot be nil"), + }, + { + "failed to unpack header", + func() { + msg.ClientMessage = nil + }, + errorsmod.Wrap(ibcerrors.ErrUnpackAny, "protobuf Any message cannot be nil"), + }, + { + "invalid signer", + func() { + msg.Signer = "" + }, + errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: empty address string is not allowed"), + }, + { + "valid - solomachine header", + func() { + soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2) + msg, err = types.NewMsgUpdateClient(soloMachine.ClientID, soloMachine.CreateHeader(soloMachine.Diversifier), suite.chainA.SenderAccount.GetAddress().String()) + + suite.Require().NoError(err) + }, + nil, + }, + { + "invalid solomachine header", + func() { + msg, err = types.NewMsgUpdateClient("solomachine", &solomachine.Header{}, suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + errorsmod.Wrap(types.ErrInvalidHeader, "timestamp cannot be zero"), + }, + } + + for _, tc := range cases { + tc.malleate() + err = msg.ValidateBasic() + if tc.expErr == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().Error(err, tc.name) + suite.Require().ErrorIs(err, tc.expErr) + } + } +} + +func (suite *TypesTestSuite) TestMarshalMsgUpgradeClient() { + var ( + msg *types.MsgUpgradeClient + err error + ) + + testCases := []struct { + name string + malleate func() + }{ + { + "client upgrades to new tendermint client", + func() { + tendermintClient := ibctm.NewClientState(suite.chainA.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath) + tendermintConsState := &ibctm.ConsensusState{NextValidatorsHash: []byte("nextValsHash")} + msg, err = types.NewMsgUpgradeClient("clientid", tendermintClient, tendermintConsState, []byte("proofUpgradeClient"), []byte("proofUpgradeConsState"), suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + }, + { + "client upgrades to new solomachine client", + func() { + soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 1) + msg, err = types.NewMsgUpgradeClient("clientid", soloMachine.ClientState(), soloMachine.ConsensusState(), []byte("proofUpgradeClient"), []byte("proofUpgradeConsState"), suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + tc.malleate() + + cdc := suite.chainA.App.AppCodec() + + // marshal message + bz, err := cdc.MarshalJSON(msg) + suite.Require().NoError(err) + + // unmarshal message + newMsg := &types.MsgUpgradeClient{} + err = cdc.UnmarshalJSON(bz, newMsg) + suite.Require().NoError(err) + }) + } +} + +func (suite *TypesTestSuite) TestMsgUpgradeClient_ValidateBasic() { + cases := []struct { + name string + malleate func(*types.MsgUpgradeClient) + expErr error + }{ + { + name: "success", + malleate: func(msg *types.MsgUpgradeClient) {}, + expErr: nil, + }, + { + name: "client id empty", + malleate: func(msg *types.MsgUpgradeClient) { + msg.ClientId = "" + }, + expErr: errorsmod.Wrap(host.ErrInvalidID, "protobuf Any message cannot be nil"), + }, + { + name: "invalid client id", + malleate: func(msg *types.MsgUpgradeClient) { + msg.ClientId = "invalid~chain/id" + }, + expErr: errorsmod.Wrap(host.ErrInvalidID, "identifier invalid~chain/id cannot contain separator '/'"), + }, + { + name: "unpacking clientstate fails", + malleate: func(msg *types.MsgUpgradeClient) { + msg.ClientState = nil + }, + expErr: errorsmod.Wrap(ibcerrors.ErrUnpackAny, "protobuf Any message cannot be nil"), + }, + { + name: "unpacking consensus state fails", + malleate: func(msg *types.MsgUpgradeClient) { + msg.ConsensusState = nil + }, + expErr: errorsmod.Wrap(ibcerrors.ErrUnpackAny, "protobuf Any message cannot be nil"), + }, + { + name: "client and consensus type does not match", + malleate: func(msg *types.MsgUpgradeClient) { + soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2) + soloConsensus, err := types.PackConsensusState(soloMachine.ConsensusState()) + suite.Require().NoError(err) + msg.ConsensusState = soloConsensus + }, + expErr: errorsmod.Wrap(types.ErrInvalidUpgradeClient, "consensus state's client-type does not match client. expected: 07-tendermint, got: 06-solomachine"), + }, + { + name: "empty client proof", + malleate: func(msg *types.MsgUpgradeClient) { + msg.ProofUpgradeClient = nil + }, + expErr: errorsmod.Wrap(types.ErrInvalidUpgradeClient, "proof of upgrade client cannot be empty"), + }, + { + name: "empty consensus state proof", + malleate: func(msg *types.MsgUpgradeClient) { + msg.ProofUpgradeConsensusState = nil + }, + expErr: errorsmod.Wrap(types.ErrInvalidUpgradeClient, "proof of upgrade consensus state cannot be empty"), + }, + { + name: "empty signer", + malleate: func(msg *types.MsgUpgradeClient) { + msg.Signer = " " + }, + expErr: errorsmod.Wrap(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: empty address string is not allowed"), + }, + } + + for _, tc := range cases { + + clientState := ibctm.NewClientState(suite.chainA.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath) + consState := &ibctm.ConsensusState{NextValidatorsHash: []byte("nextValsHash")} + msg, err := types.NewMsgUpgradeClient("testclientid", clientState, consState, []byte("proofUpgradeClient"), []byte("proofUpgradeConsState"), suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + + tc.malleate(msg) + err = msg.ValidateBasic() + if tc.expErr == nil { + suite.Require().NoError(err, "valid case %s failed", tc.name) + } else { + suite.Require().Error(err, "invalid case %s passed", tc.name) + suite.Require().ErrorIs(err, tc.expErr) + } + } +} + +// tests that different misbehaviours within MsgSubmitMisbehaviour can be marshaled +// and unmarshaled. +func (suite *TypesTestSuite) TestMarshalMsgSubmitMisbehaviour() { + var ( + msg *types.MsgSubmitMisbehaviour + err error + ) + + testCases := []struct { + name string + malleate func() + }{ + { + "solo machine client", func() { + soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2) + msg, err = types.NewMsgSubmitMisbehaviour(soloMachine.ClientID, soloMachine.CreateMisbehaviour(), suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + }, + { + "tendermint client", func() { + height := types.NewHeight(0, uint64(suite.chainA.ProposedHeader.Height)) + heightMinus1 := types.NewHeight(0, uint64(suite.chainA.ProposedHeader.Height)-1) + header1 := suite.chainA.CreateTMClientHeader(suite.chainA.ChainID, int64(height.RevisionHeight), heightMinus1, suite.chainA.ProposedHeader.Time, suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) + header2 := suite.chainA.CreateTMClientHeader(suite.chainA.ChainID, int64(height.RevisionHeight), heightMinus1, suite.chainA.ProposedHeader.Time.Add(time.Minute), suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) + + misbehaviour := ibctm.NewMisbehaviour("tendermint", header1, header2) + msg, err = types.NewMsgSubmitMisbehaviour("tendermint", misbehaviour, suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + tc.malleate() + + cdc := suite.chainA.App.AppCodec() + + // marshal message + bz, err := cdc.MarshalJSON(msg) + suite.Require().NoError(err) + + // unmarshal message + newMsg := &types.MsgSubmitMisbehaviour{} + err = cdc.UnmarshalJSON(bz, newMsg) + suite.Require().NoError(err) + + suite.Require().True(proto.Equal(msg, newMsg)) + }) + } +} + +func (suite *TypesTestSuite) TestMsgSubmitMisbehaviour_ValidateBasic() { + var ( + msg = &types.MsgSubmitMisbehaviour{} + err error + ) + + cases := []struct { + name string + malleate func() + expErr error + }{ + { + "invalid client-id", + func() { + msg.ClientId = "" + }, + errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: empty address string is not allowed"), + }, + { + "valid - tendermint misbehaviour", + func() { + height := types.NewHeight(0, uint64(suite.chainA.ProposedHeader.Height)) + heightMinus1 := types.NewHeight(0, uint64(suite.chainA.ProposedHeader.Height)-1) + header1 := suite.chainA.CreateTMClientHeader(suite.chainA.ChainID, int64(height.RevisionHeight), heightMinus1, suite.chainA.ProposedHeader.Time, suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) + header2 := suite.chainA.CreateTMClientHeader(suite.chainA.ChainID, int64(height.RevisionHeight), heightMinus1, suite.chainA.ProposedHeader.Time.Add(time.Minute), suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) + + misbehaviour := ibctm.NewMisbehaviour("tendermint", header1, header2) + msg, err = types.NewMsgSubmitMisbehaviour("tendermint", misbehaviour, suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + nil, + }, + { + "invalid tendermint misbehaviour", + func() { + msg, err = types.NewMsgSubmitMisbehaviour("tendermint", &ibctm.Misbehaviour{}, suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + errorsmod.Wrap(ibctm.ErrInvalidHeader, "misbehaviour Header1 cannot be nil"), + }, + { + "failed to unpack misbehaviourt", + func() { + msg.Misbehaviour = nil + }, + errorsmod.Wrap(ibcerrors.ErrUnpackAny, "protobuf Any message cannot be nil"), + }, + { + "invalid signer", + func() { + msg.Signer = "" + }, + errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: empty address string is not allowed"), + }, + { + "valid - solomachine misbehaviour", + func() { + soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2) + msg, err = types.NewMsgSubmitMisbehaviour(soloMachine.ClientID, soloMachine.CreateMisbehaviour(), suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + nil, + }, + { + "invalid solomachine misbehaviour", + func() { + msg, err = types.NewMsgSubmitMisbehaviour("solomachine", &solomachine.Misbehaviour{}, suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + errorsmod.Wrapf(types.ErrInvalidMisbehaviour, "sequence cannot be 0"), + }, + { + "client-id too short", + func() { + soloMachineMisbehaviour := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2).CreateMisbehaviour() + msg, err = types.NewMsgSubmitMisbehaviour("ext", soloMachineMisbehaviour, suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + errorsmod.Wrapf(host.ErrInvalidID, "identifier external has invalid length: 3, must be between 4-64 characters"), + }, + } + + for _, tc := range cases { + tc.malleate() + err = msg.ValidateBasic() + if tc.expErr == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().Error(err, tc.name) + suite.Require().ErrorIs(err, tc.expErr) + } + } +} + +// TestMsgRecoverClientValidateBasic tests ValidateBasic for MsgRecoverClient +func (suite *TypesTestSuite) TestMsgRecoverClientValidateBasic() { + var msg *types.MsgRecoverClient + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success: valid signer and client identifiers", + func() {}, + nil, + }, + { + "failure: invalid signer address", + func() { + msg.Signer = "invalid" + }, + ibcerrors.ErrInvalidAddress, + }, + { + "failure: invalid subject client ID", + func() { + msg.SubjectClientId = "" + }, + host.ErrInvalidID, + }, + { + "failure: invalid substitute client ID", + func() { + msg.SubstituteClientId = "" + }, + host.ErrInvalidID, + }, + { + "failure: subject and substribute client IDs are the same", + func() { + msg.SubstituteClientId = ibctesting.FirstClientID + }, + types.ErrInvalidSubstitute, + }, + } + + for _, tc := range testCases { + msg = types.NewMsgRecoverClient( + ibctesting.TestAccAddress, + ibctesting.FirstClientID, + ibctesting.SecondClientID, + ) + + tc.malleate() + + err := msg.ValidateBasic() + if tc.expError == nil { + suite.Require().NoError(err, "valid case %s failed", tc.name) + } else { + suite.Require().ErrorIs(err, tc.expError, "invalid case %s passed", tc.name) + } + } +} + +// TestMsgRecoverClientGetSigners tests GetSigners for MsgRecoverClient +func TestMsgRecoverClientGetSigners(t *testing.T) { + testCases := []struct { + name string + address sdk.AccAddress + expError error + }{ + {"success: valid address", sdk.AccAddress(ibctesting.TestAccAddress), nil}, + {"failure: nil address", nil, errors.New("empty address string is not allowed")}, + } + + for _, tc := range testCases { + // Leave subject client ID and substitute client ID as empty strings + msg := types.MsgRecoverClient{ + Signer: tc.address.String(), + } + encodingCfg := moduletestutil.MakeTestEncodingConfig(ibc.AppModuleBasic{}) + signers, _, err := encodingCfg.Codec.GetMsgV1Signers(&msg) + if tc.expError == nil { + require.NoError(t, err) + require.Equal(t, tc.address.Bytes(), signers[0]) + } else { + require.Error(t, err) + require.Equal(t, err.Error(), tc.expError.Error()) + } + } +} + +// TestMsgIBCSoftwareUpgrade_NewMsgIBCSoftwareUpgrade tests NewMsgIBCSoftwareUpgrade +func (suite *TypesTestSuite) TestMsgIBCSoftwareUpgrade_NewMsgIBCSoftwareUpgrade() { + testCases := []struct { + name string + upgradedClientState exported.ClientState + expErr error + }{ + { + "success", + ibctm.NewClientState(suite.chainA.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), + nil, + }, + { + "fail: failed to pack ClientState", + nil, + errorsmod.Wrap(ibcerrors.ErrPackAny, "cannot proto marshal "), + }, + } + + for _, tc := range testCases { + plan := upgradetypes.Plan{ + Name: "upgrade IBC clients", + Height: 1000, + } + msg, err := types.NewMsgIBCSoftwareUpgrade( + ibctesting.TestAccAddress, + plan, + tc.upgradedClientState, + ) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Assert().Equal(ibctesting.TestAccAddress, msg.Signer) + suite.Assert().Equal(plan, msg.Plan) + unpackedClientState, err := types.UnpackClientState(msg.UpgradedClientState) + suite.Require().NoError(err) + suite.Assert().Equal(tc.upgradedClientState, unpackedClientState) + } else { + suite.Require().True(errors.Is(err, ibcerrors.ErrPackAny)) + suite.Require().ErrorIs(err, tc.expErr) + } + } +} + +// TestMsgIBCSoftwareUpgrade_GetSigners tests GetSigners for MsgIBCSoftwareUpgrade +func TestMsgIBCSoftwareUpgrade_GetSigners(t *testing.T) { + testCases := []struct { + name string + address sdk.AccAddress + expErr error + }{ + { + "success: valid address", + sdk.AccAddress(ibctesting.TestAccAddress), + nil, + }, + { + "failure: nil address", + nil, + errors.New("empty address string is not allowed"), + }, + } + + for _, tc := range testCases { + clientState := ibctm.NewClientState("testchain1-1", ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath) + plan := upgradetypes.Plan{ + Name: "upgrade IBC clients", + Height: 1000, + } + msg, err := types.NewMsgIBCSoftwareUpgrade( + tc.address.String(), + plan, + clientState, + ) + require.NoError(t, err) + + encodingCfg := moduletestutil.MakeTestEncodingConfig(ibc.AppModuleBasic{}) + signers, _, err := encodingCfg.Codec.GetMsgV1Signers(msg) + if tc.expErr == nil { + require.NoError(t, err) + require.Equal(t, tc.address.Bytes(), signers[0]) + } else { + require.Error(t, err) + require.Equal(t, err.Error(), tc.expErr.Error()) + } + } +} + +// TestMsgIBCSoftwareUpgrade_ValidateBasic tests ValidateBasic for MsgIBCSoftwareUpgrade +func (suite *TypesTestSuite) TestMsgIBCSoftwareUpgrade_ValidateBasic() { + var ( + signer string + plan upgradetypes.Plan + anyClient *codectypes.Any + ) + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() {}, + nil, + }, + { + "failure: invalid authority address", + func() { + signer = "invalid" + }, + ibcerrors.ErrInvalidAddress, + }, + { + "failure: error unpacking client state", + func() { + anyClient = &codectypes.Any{} + }, + ibcerrors.ErrUnpackAny, + }, + { + "failure: error validating upgrade plan, height is not greater than zero", + func() { + plan.Height = 0 + }, + sdkerrors.ErrInvalidRequest, + }, + } + + for _, tc := range testCases { + signer = ibctesting.TestAccAddress + plan = upgradetypes.Plan{ + Name: "upgrade IBC clients", + Height: 1000, + } + upgradedClientState := ibctm.NewClientState(suite.chainA.ChainID, ibctesting.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath) + var err error + anyClient, err = types.PackClientState(upgradedClientState) + suite.Require().NoError(err) + + tc.malleate() + + msg := types.MsgIBCSoftwareUpgrade{ + plan, + anyClient, + signer, + } + + err = msg.ValidateBasic() + + if tc.expError == nil { + suite.Require().NoError(err) + } + if tc.expError != nil { + suite.Require().True(errors.Is(err, tc.expError)) + } + } +} + +// tests a MsgIBCSoftwareUpgrade can be marshaled and unmarshaled, and the +// client state can be unpacked +func (suite *TypesTestSuite) TestMarshalMsgIBCSoftwareUpgrade() { + cdc := suite.chainA.App.AppCodec() + + // create proposal + plan := upgradetypes.Plan{ + Name: "upgrade ibc", + Height: 1000, + } + + msg, err := types.NewMsgIBCSoftwareUpgrade(ibctesting.TestAccAddress, plan, &ibctm.ClientState{}) + suite.Require().NoError(err) + + // marshal message + bz, err := cdc.MarshalJSON(msg) + suite.Require().NoError(err) + + // unmarshal proposal + newMsg := &types.MsgIBCSoftwareUpgrade{} + err = cdc.UnmarshalJSON(bz, newMsg) + suite.Require().NoError(err) + + // unpack client state + _, err = types.UnpackClientState(newMsg.UpgradedClientState) + suite.Require().NoError(err) +} + +// TestMsgUpdateParamsValidateBasic tests ValidateBasic for MsgUpdateParams +func (suite *TypesTestSuite) TestMsgUpdateParamsValidateBasic() { + signer := suite.chainA.App.GetIBCKeeper().GetAuthority() + testCases := []struct { + name string + msg *types.MsgUpdateParams + expErr error + }{ + { + "success: valid signer and params", + types.NewMsgUpdateParams(signer, types.DefaultParams()), + nil, + }, + { + "success: valid signer empty params", + types.NewMsgUpdateParams(signer, types.Params{}), + nil, + }, + { + "failure: invalid signer address", + types.NewMsgUpdateParams("invalid", types.DefaultParams()), + errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: decoding bech32 failed: invalid bech32 string length 7"), + }, + { + "failure: invalid allowed client", + types.NewMsgUpdateParams(signer, types.NewParams("")), + errors.New("client type 0 cannot be blank"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := tc.msg.ValidateBasic() + if tc.expErr == nil { + suite.Require().NoError(err, "valid case %s failed", tc.name) + } else { + suite.Require().Error(err, "invalid case %s passed", tc.name) + suite.Require().Equal(tc.expErr.Error(), err.Error()) + } + }) + } +} + +// TestMsgUpdateParamsGetSigners tests GetSigners for MsgUpdateParams +func TestMsgUpdateParamsGetSigners(t *testing.T) { + testCases := []struct { + name string + address sdk.AccAddress + expErr error + }{ + {"success: valid address", sdk.AccAddress(ibctesting.TestAccAddress), nil}, + {"failure: nil address", nil, errors.New("empty address string is not allowed")}, + } + + for _, tc := range testCases { + + msg := types.MsgUpdateParams{ + Signer: tc.address.String(), + Params: types.DefaultParams(), + } + encodingCfg := moduletestutil.MakeTestEncodingConfig(ibc.AppModuleBasic{}) + signers, _, err := encodingCfg.Codec.GetMsgV1Signers(&msg) + if tc.expErr == nil { + require.NoError(t, err) + require.Equal(t, tc.address.Bytes(), signers[0]) + } else { + require.Error(t, err) + require.Equal(t, err.Error(), tc.expErr.Error()) + } + } +} diff --git a/modules/core/02-client/types/params.go b/modules/core/02-client/types/params.go new file mode 100644 index 0000000..9b75937 --- /dev/null +++ b/modules/core/02-client/types/params.go @@ -0,0 +1,72 @@ +package types + +import ( + "fmt" + "slices" + "strings" +) + +// Maximum length of the allowed clients list +const MaxAllowedClientsLength = 200 + +// DefaultAllowedClients are the default clients for the AllowedClients parameter. +// By default it allows all client types. +var DefaultAllowedClients = []string{AllowAllClients} + +// NewParams creates a new parameter configuration for the ibc client module +func NewParams(allowedClients ...string) Params { + return Params{ + AllowedClients: allowedClients, + } +} + +// DefaultParams is the default parameter configuration for the ibc-client module. +func DefaultParams() Params { + return NewParams(DefaultAllowedClients...) +} + +// Validate all ibc-client module parameters +func (p Params) Validate() error { + return validateClients(p.AllowedClients) +} + +// IsAllowedClient checks if the given client type is registered on the allowlist. +func (p Params) IsAllowedClient(clientType string) bool { + // Still need to check for blank client type + if strings.TrimSpace(clientType) == "" { + return false + } + + // Check for allow all client wildcard + // If exist then allow all type of client + if len(p.AllowedClients) == 1 && p.AllowedClients[0] == AllowAllClients { + return true + } + + return slices.Contains(p.AllowedClients, clientType) +} + +// validateClients checks that the given clients are not blank and there are no duplicates. +// If AllowAllClients wildcard (*) is used, then there should no other client types in the allow list +func validateClients(clients []string) error { + if len(clients) > MaxAllowedClientsLength { + return fmt.Errorf("allowed clients length must not exceed %d items", MaxAllowedClientsLength) + } + + if slices.Contains(clients, AllowAllClients) && len(clients) > 1 { + return fmt.Errorf("allow list must have only one element because the allow all clients wildcard (%s) is present", AllowAllClients) + } + + foundClients := make(map[string]bool, len(clients)) + for i, clientType := range clients { + if strings.TrimSpace(clientType) == "" { + return fmt.Errorf("client type %d cannot be blank", i) + } + if foundClients[clientType] { + return fmt.Errorf("duplicate client type: %s", clientType) + } + foundClients[clientType] = true + } + + return nil +} diff --git a/modules/core/02-client/types/params_legacy.go b/modules/core/02-client/types/params_legacy.go new file mode 100644 index 0000000..e61f0ff --- /dev/null +++ b/modules/core/02-client/types/params_legacy.go @@ -0,0 +1,37 @@ +/* +NOTE: Usage of x/params to manage parameters is deprecated in favor of x/gov +controlled execution of MsgUpdateParams messages. These types remains solely +for migration purposes and will be removed in a future release. +[#3621](https://github.com/cosmos/ibc-go/issues/3621) +*/ +package types + +import ( + "fmt" + + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" +) + +// KeyAllowedClients is store's key for AllowedClients Params +var KeyAllowedClients = []byte("AllowedClients") + +// ParamKeyTable type declaration for parameters +func ParamKeyTable() paramtypes.KeyTable { + return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) +} + +// ParamSetPairs implements params.ParamSet +func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { + return paramtypes.ParamSetPairs{ + paramtypes.NewParamSetPair(KeyAllowedClients, &p.AllowedClients, validateClientsLegacy), + } +} + +func validateClientsLegacy(i any) error { + clients, ok := i.([]string) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + return validateClients(clients) +} diff --git a/modules/core/02-client/types/params_test.go b/modules/core/02-client/types/params_test.go new file mode 100644 index 0000000..9d8c029 --- /dev/null +++ b/modules/core/02-client/types/params_test.go @@ -0,0 +1,56 @@ +package types + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +func TestIsAllowedClient(t *testing.T) { + testCases := []struct { + name string + clientType string + params Params + expPass bool + }{ + {"success: valid client", exported.Tendermint, DefaultParams(), true}, + {"success: valid client with custom params", exported.Tendermint, NewParams(exported.Tendermint), true}, + {"success: invalid blank client", " ", DefaultParams(), false}, + {"success: invalid client with custom params", exported.Localhost, NewParams(exported.Tendermint), false}, + {"success: wildcard allow all clients", "test-client-type", NewParams(AllowAllClients), true}, + {"success: wildcard allow all clients with blank client", " ", NewParams(AllowAllClients), false}, + } + + for _, tc := range testCases { + require.Equal(t, tc.expPass, tc.params.IsAllowedClient(tc.clientType), tc.name) + } +} + +func TestValidateParams(t *testing.T) { + testCases := []struct { + name string + params Params + expError error + }{ + {"default params", DefaultParams(), nil}, + {"custom params", NewParams(exported.Tendermint), nil}, + {"blank client", NewParams(" "), errors.New("client type 0 cannot be blank")}, + {"duplicate clients", NewParams(exported.Tendermint, exported.Tendermint), errors.New("duplicate client type: 07-tendermint")}, + {"allow all clients plus valid client", NewParams(AllowAllClients, exported.Tendermint), errors.New("allow list must have only one element because the allow all clients wildcard (*) is present")}, + {"too many allowed clients", NewParams(make([]string, MaxAllowedClientsLength+1)...), errors.New("allowed clients length must not exceed 200 items")}, + } + + for _, tc := range testCases { + + err := tc.params.Validate() + if tc.expError == nil { + require.NoError(t, err, tc.name) + } else { + require.Error(t, err, tc.name) + require.ErrorContains(t, err, tc.expError.Error()) + } + } +} diff --git a/modules/core/02-client/types/query.go b/modules/core/02-client/types/query.go new file mode 100644 index 0000000..a262b23 --- /dev/null +++ b/modules/core/02-client/types/query.go @@ -0,0 +1,66 @@ +package types + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var ( + _ codectypes.UnpackInterfacesMessage = (*QueryClientStateResponse)(nil) + _ codectypes.UnpackInterfacesMessage = (*QueryClientStatesResponse)(nil) + _ codectypes.UnpackInterfacesMessage = (*QueryConsensusStateResponse)(nil) + _ codectypes.UnpackInterfacesMessage = (*QueryConsensusStatesResponse)(nil) +) + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (qcsr QueryClientStatesResponse) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + for _, cs := range qcsr.ClientStates { + if err := cs.UnpackInterfaces(unpacker); err != nil { + return err + } + } + return nil +} + +// NewQueryClientStateResponse creates a new QueryClientStateResponse instance. +func NewQueryClientStateResponse( + clientStateAny *codectypes.Any, proof []byte, height Height, +) *QueryClientStateResponse { + return &QueryClientStateResponse{ + ClientState: clientStateAny, + Proof: proof, + ProofHeight: height, + } +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (qcsr QueryClientStateResponse) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + return unpacker.UnpackAny(qcsr.ClientState, new(exported.ClientState)) +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (qcsr QueryConsensusStatesResponse) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + for _, cs := range qcsr.ConsensusStates { + if err := cs.UnpackInterfaces(unpacker); err != nil { + return err + } + } + return nil +} + +// NewQueryConsensusStateResponse creates a new QueryConsensusStateResponse instance. +func NewQueryConsensusStateResponse( + consensusStateAny *codectypes.Any, proof []byte, height Height, +) *QueryConsensusStateResponse { + return &QueryConsensusStateResponse{ + ConsensusState: consensusStateAny, + Proof: proof, + ProofHeight: height, + } +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (qcsr QueryConsensusStateResponse) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + return unpacker.UnpackAny(qcsr.ConsensusState, new(exported.ConsensusState)) +} diff --git a/modules/core/02-client/types/query.pb.go b/modules/core/02-client/types/query.pb.go new file mode 100644 index 0000000..136917c --- /dev/null +++ b/modules/core/02-client/types/query.pb.go @@ -0,0 +1,5342 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/client/v1/query.proto + +package types + +import ( + context "context" + fmt "fmt" + types "github.com/cosmos/cosmos-sdk/codec/types" + query "github.com/cosmos/cosmos-sdk/types/query" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + v2 "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types/v2" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// QueryClientStateRequest is the request type for the Query/ClientState RPC +// method +type QueryClientStateRequest struct { + // client state unique identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` +} + +func (m *QueryClientStateRequest) Reset() { *m = QueryClientStateRequest{} } +func (m *QueryClientStateRequest) String() string { return proto.CompactTextString(m) } +func (*QueryClientStateRequest) ProtoMessage() {} +func (*QueryClientStateRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_dc42cdfd1d52d76e, []int{0} +} +func (m *QueryClientStateRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryClientStateRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryClientStateRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryClientStateRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryClientStateRequest.Merge(m, src) +} +func (m *QueryClientStateRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryClientStateRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryClientStateRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryClientStateRequest proto.InternalMessageInfo + +func (m *QueryClientStateRequest) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +// QueryClientStateResponse is the response type for the Query/ClientState RPC +// method. Besides the client state, it includes a proof and the height from +// which the proof was retrieved. +type QueryClientStateResponse struct { + // client state associated with the request identifier + ClientState *types.Any `protobuf:"bytes,1,opt,name=client_state,json=clientState,proto3" json:"client_state,omitempty"` + // merkle proof of existence + Proof []byte `protobuf:"bytes,2,opt,name=proof,proto3" json:"proof,omitempty"` + // height at which the proof was retrieved + ProofHeight Height `protobuf:"bytes,3,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` +} + +func (m *QueryClientStateResponse) Reset() { *m = QueryClientStateResponse{} } +func (m *QueryClientStateResponse) String() string { return proto.CompactTextString(m) } +func (*QueryClientStateResponse) ProtoMessage() {} +func (*QueryClientStateResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_dc42cdfd1d52d76e, []int{1} +} +func (m *QueryClientStateResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryClientStateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryClientStateResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryClientStateResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryClientStateResponse.Merge(m, src) +} +func (m *QueryClientStateResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryClientStateResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryClientStateResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryClientStateResponse proto.InternalMessageInfo + +func (m *QueryClientStateResponse) GetClientState() *types.Any { + if m != nil { + return m.ClientState + } + return nil +} + +func (m *QueryClientStateResponse) GetProof() []byte { + if m != nil { + return m.Proof + } + return nil +} + +func (m *QueryClientStateResponse) GetProofHeight() Height { + if m != nil { + return m.ProofHeight + } + return Height{} +} + +// QueryClientStatesRequest is the request type for the Query/ClientStates RPC +// method +type QueryClientStatesRequest struct { + // pagination request + Pagination *query.PageRequest `protobuf:"bytes,1,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryClientStatesRequest) Reset() { *m = QueryClientStatesRequest{} } +func (m *QueryClientStatesRequest) String() string { return proto.CompactTextString(m) } +func (*QueryClientStatesRequest) ProtoMessage() {} +func (*QueryClientStatesRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_dc42cdfd1d52d76e, []int{2} +} +func (m *QueryClientStatesRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryClientStatesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryClientStatesRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryClientStatesRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryClientStatesRequest.Merge(m, src) +} +func (m *QueryClientStatesRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryClientStatesRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryClientStatesRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryClientStatesRequest proto.InternalMessageInfo + +func (m *QueryClientStatesRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryClientStatesResponse is the response type for the Query/ClientStates RPC +// method. +type QueryClientStatesResponse struct { + // list of stored ClientStates of the chain. + ClientStates IdentifiedClientStates `protobuf:"bytes,1,rep,name=client_states,json=clientStates,proto3,castrepeated=IdentifiedClientStates" json:"client_states"` + // pagination response + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryClientStatesResponse) Reset() { *m = QueryClientStatesResponse{} } +func (m *QueryClientStatesResponse) String() string { return proto.CompactTextString(m) } +func (*QueryClientStatesResponse) ProtoMessage() {} +func (*QueryClientStatesResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_dc42cdfd1d52d76e, []int{3} +} +func (m *QueryClientStatesResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryClientStatesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryClientStatesResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryClientStatesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryClientStatesResponse.Merge(m, src) +} +func (m *QueryClientStatesResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryClientStatesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryClientStatesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryClientStatesResponse proto.InternalMessageInfo + +func (m *QueryClientStatesResponse) GetClientStates() IdentifiedClientStates { + if m != nil { + return m.ClientStates + } + return nil +} + +func (m *QueryClientStatesResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryConsensusStateRequest is the request type for the Query/ConsensusState +// RPC method. Besides the consensus state, it includes a proof and the height +// from which the proof was retrieved. +type QueryConsensusStateRequest struct { + // client identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // consensus state revision number + RevisionNumber uint64 `protobuf:"varint,2,opt,name=revision_number,json=revisionNumber,proto3" json:"revision_number,omitempty"` + // consensus state revision height + RevisionHeight uint64 `protobuf:"varint,3,opt,name=revision_height,json=revisionHeight,proto3" json:"revision_height,omitempty"` + // latest_height overrides the height field and queries the latest stored + // ConsensusState + LatestHeight bool `protobuf:"varint,4,opt,name=latest_height,json=latestHeight,proto3" json:"latest_height,omitempty"` +} + +func (m *QueryConsensusStateRequest) Reset() { *m = QueryConsensusStateRequest{} } +func (m *QueryConsensusStateRequest) String() string { return proto.CompactTextString(m) } +func (*QueryConsensusStateRequest) ProtoMessage() {} +func (*QueryConsensusStateRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_dc42cdfd1d52d76e, []int{4} +} +func (m *QueryConsensusStateRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryConsensusStateRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryConsensusStateRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryConsensusStateRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryConsensusStateRequest.Merge(m, src) +} +func (m *QueryConsensusStateRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryConsensusStateRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryConsensusStateRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryConsensusStateRequest proto.InternalMessageInfo + +func (m *QueryConsensusStateRequest) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +func (m *QueryConsensusStateRequest) GetRevisionNumber() uint64 { + if m != nil { + return m.RevisionNumber + } + return 0 +} + +func (m *QueryConsensusStateRequest) GetRevisionHeight() uint64 { + if m != nil { + return m.RevisionHeight + } + return 0 +} + +func (m *QueryConsensusStateRequest) GetLatestHeight() bool { + if m != nil { + return m.LatestHeight + } + return false +} + +// QueryConsensusStateResponse is the response type for the Query/ConsensusState +// RPC method +type QueryConsensusStateResponse struct { + // consensus state associated with the client identifier at the given height + ConsensusState *types.Any `protobuf:"bytes,1,opt,name=consensus_state,json=consensusState,proto3" json:"consensus_state,omitempty"` + // merkle proof of existence + Proof []byte `protobuf:"bytes,2,opt,name=proof,proto3" json:"proof,omitempty"` + // height at which the proof was retrieved + ProofHeight Height `protobuf:"bytes,3,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` +} + +func (m *QueryConsensusStateResponse) Reset() { *m = QueryConsensusStateResponse{} } +func (m *QueryConsensusStateResponse) String() string { return proto.CompactTextString(m) } +func (*QueryConsensusStateResponse) ProtoMessage() {} +func (*QueryConsensusStateResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_dc42cdfd1d52d76e, []int{5} +} +func (m *QueryConsensusStateResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryConsensusStateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryConsensusStateResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryConsensusStateResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryConsensusStateResponse.Merge(m, src) +} +func (m *QueryConsensusStateResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryConsensusStateResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryConsensusStateResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryConsensusStateResponse proto.InternalMessageInfo + +func (m *QueryConsensusStateResponse) GetConsensusState() *types.Any { + if m != nil { + return m.ConsensusState + } + return nil +} + +func (m *QueryConsensusStateResponse) GetProof() []byte { + if m != nil { + return m.Proof + } + return nil +} + +func (m *QueryConsensusStateResponse) GetProofHeight() Height { + if m != nil { + return m.ProofHeight + } + return Height{} +} + +// QueryConsensusStatesRequest is the request type for the Query/ConsensusStates +// RPC method. +type QueryConsensusStatesRequest struct { + // client identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // pagination request + Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryConsensusStatesRequest) Reset() { *m = QueryConsensusStatesRequest{} } +func (m *QueryConsensusStatesRequest) String() string { return proto.CompactTextString(m) } +func (*QueryConsensusStatesRequest) ProtoMessage() {} +func (*QueryConsensusStatesRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_dc42cdfd1d52d76e, []int{6} +} +func (m *QueryConsensusStatesRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryConsensusStatesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryConsensusStatesRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryConsensusStatesRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryConsensusStatesRequest.Merge(m, src) +} +func (m *QueryConsensusStatesRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryConsensusStatesRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryConsensusStatesRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryConsensusStatesRequest proto.InternalMessageInfo + +func (m *QueryConsensusStatesRequest) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +func (m *QueryConsensusStatesRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryConsensusStatesResponse is the response type for the +// Query/ConsensusStates RPC method +type QueryConsensusStatesResponse struct { + // consensus states associated with the identifier + ConsensusStates []ConsensusStateWithHeight `protobuf:"bytes,1,rep,name=consensus_states,json=consensusStates,proto3" json:"consensus_states"` + // pagination response + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryConsensusStatesResponse) Reset() { *m = QueryConsensusStatesResponse{} } +func (m *QueryConsensusStatesResponse) String() string { return proto.CompactTextString(m) } +func (*QueryConsensusStatesResponse) ProtoMessage() {} +func (*QueryConsensusStatesResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_dc42cdfd1d52d76e, []int{7} +} +func (m *QueryConsensusStatesResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryConsensusStatesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryConsensusStatesResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryConsensusStatesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryConsensusStatesResponse.Merge(m, src) +} +func (m *QueryConsensusStatesResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryConsensusStatesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryConsensusStatesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryConsensusStatesResponse proto.InternalMessageInfo + +func (m *QueryConsensusStatesResponse) GetConsensusStates() []ConsensusStateWithHeight { + if m != nil { + return m.ConsensusStates + } + return nil +} + +func (m *QueryConsensusStatesResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryConsensusStateHeightsRequest is the request type for Query/ConsensusStateHeights +// RPC method. +type QueryConsensusStateHeightsRequest struct { + // client identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // pagination request + Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryConsensusStateHeightsRequest) Reset() { *m = QueryConsensusStateHeightsRequest{} } +func (m *QueryConsensusStateHeightsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryConsensusStateHeightsRequest) ProtoMessage() {} +func (*QueryConsensusStateHeightsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_dc42cdfd1d52d76e, []int{8} +} +func (m *QueryConsensusStateHeightsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryConsensusStateHeightsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryConsensusStateHeightsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryConsensusStateHeightsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryConsensusStateHeightsRequest.Merge(m, src) +} +func (m *QueryConsensusStateHeightsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryConsensusStateHeightsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryConsensusStateHeightsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryConsensusStateHeightsRequest proto.InternalMessageInfo + +func (m *QueryConsensusStateHeightsRequest) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +func (m *QueryConsensusStateHeightsRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryConsensusStateHeightsResponse is the response type for the +// Query/ConsensusStateHeights RPC method +type QueryConsensusStateHeightsResponse struct { + // consensus state heights + ConsensusStateHeights []Height `protobuf:"bytes,1,rep,name=consensus_state_heights,json=consensusStateHeights,proto3" json:"consensus_state_heights"` + // pagination response + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryConsensusStateHeightsResponse) Reset() { *m = QueryConsensusStateHeightsResponse{} } +func (m *QueryConsensusStateHeightsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryConsensusStateHeightsResponse) ProtoMessage() {} +func (*QueryConsensusStateHeightsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_dc42cdfd1d52d76e, []int{9} +} +func (m *QueryConsensusStateHeightsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryConsensusStateHeightsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryConsensusStateHeightsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryConsensusStateHeightsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryConsensusStateHeightsResponse.Merge(m, src) +} +func (m *QueryConsensusStateHeightsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryConsensusStateHeightsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryConsensusStateHeightsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryConsensusStateHeightsResponse proto.InternalMessageInfo + +func (m *QueryConsensusStateHeightsResponse) GetConsensusStateHeights() []Height { + if m != nil { + return m.ConsensusStateHeights + } + return nil +} + +func (m *QueryConsensusStateHeightsResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryClientStatusRequest is the request type for the Query/ClientStatus RPC +// method +type QueryClientStatusRequest struct { + // client unique identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` +} + +func (m *QueryClientStatusRequest) Reset() { *m = QueryClientStatusRequest{} } +func (m *QueryClientStatusRequest) String() string { return proto.CompactTextString(m) } +func (*QueryClientStatusRequest) ProtoMessage() {} +func (*QueryClientStatusRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_dc42cdfd1d52d76e, []int{10} +} +func (m *QueryClientStatusRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryClientStatusRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryClientStatusRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryClientStatusRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryClientStatusRequest.Merge(m, src) +} +func (m *QueryClientStatusRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryClientStatusRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryClientStatusRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryClientStatusRequest proto.InternalMessageInfo + +func (m *QueryClientStatusRequest) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +// QueryClientStatusResponse is the response type for the Query/ClientStatus RPC +// method. It returns the current status of the IBC client. +type QueryClientStatusResponse struct { + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` +} + +func (m *QueryClientStatusResponse) Reset() { *m = QueryClientStatusResponse{} } +func (m *QueryClientStatusResponse) String() string { return proto.CompactTextString(m) } +func (*QueryClientStatusResponse) ProtoMessage() {} +func (*QueryClientStatusResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_dc42cdfd1d52d76e, []int{11} +} +func (m *QueryClientStatusResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryClientStatusResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryClientStatusResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryClientStatusResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryClientStatusResponse.Merge(m, src) +} +func (m *QueryClientStatusResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryClientStatusResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryClientStatusResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryClientStatusResponse proto.InternalMessageInfo + +func (m *QueryClientStatusResponse) GetStatus() string { + if m != nil { + return m.Status + } + return "" +} + +// QueryClientParamsRequest is the request type for the Query/ClientParams RPC +// method. +type QueryClientParamsRequest struct { +} + +func (m *QueryClientParamsRequest) Reset() { *m = QueryClientParamsRequest{} } +func (m *QueryClientParamsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryClientParamsRequest) ProtoMessage() {} +func (*QueryClientParamsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_dc42cdfd1d52d76e, []int{12} +} +func (m *QueryClientParamsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryClientParamsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryClientParamsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryClientParamsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryClientParamsRequest.Merge(m, src) +} +func (m *QueryClientParamsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryClientParamsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryClientParamsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryClientParamsRequest proto.InternalMessageInfo + +// QueryClientParamsResponse is the response type for the Query/ClientParams RPC +// method. +type QueryClientParamsResponse struct { + // params defines the parameters of the module. + Params *Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params,omitempty"` +} + +func (m *QueryClientParamsResponse) Reset() { *m = QueryClientParamsResponse{} } +func (m *QueryClientParamsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryClientParamsResponse) ProtoMessage() {} +func (*QueryClientParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_dc42cdfd1d52d76e, []int{13} +} +func (m *QueryClientParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryClientParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryClientParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryClientParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryClientParamsResponse.Merge(m, src) +} +func (m *QueryClientParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryClientParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryClientParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryClientParamsResponse proto.InternalMessageInfo + +func (m *QueryClientParamsResponse) GetParams() *Params { + if m != nil { + return m.Params + } + return nil +} + +// QueryClientCreatorRequest is the request type for the Query/ClientCreator RPC +// method. +type QueryClientCreatorRequest struct { + // client unique identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` +} + +func (m *QueryClientCreatorRequest) Reset() { *m = QueryClientCreatorRequest{} } +func (m *QueryClientCreatorRequest) String() string { return proto.CompactTextString(m) } +func (*QueryClientCreatorRequest) ProtoMessage() {} +func (*QueryClientCreatorRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_dc42cdfd1d52d76e, []int{14} +} +func (m *QueryClientCreatorRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryClientCreatorRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryClientCreatorRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryClientCreatorRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryClientCreatorRequest.Merge(m, src) +} +func (m *QueryClientCreatorRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryClientCreatorRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryClientCreatorRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryClientCreatorRequest proto.InternalMessageInfo + +func (m *QueryClientCreatorRequest) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +// QueryClientCreatorResponse is the response type for the Query/ClientCreator RPC +// method. +type QueryClientCreatorResponse struct { + // creator of the client + Creator string `protobuf:"bytes,1,opt,name=creator,proto3" json:"creator,omitempty"` +} + +func (m *QueryClientCreatorResponse) Reset() { *m = QueryClientCreatorResponse{} } +func (m *QueryClientCreatorResponse) String() string { return proto.CompactTextString(m) } +func (*QueryClientCreatorResponse) ProtoMessage() {} +func (*QueryClientCreatorResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_dc42cdfd1d52d76e, []int{15} +} +func (m *QueryClientCreatorResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryClientCreatorResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryClientCreatorResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryClientCreatorResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryClientCreatorResponse.Merge(m, src) +} +func (m *QueryClientCreatorResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryClientCreatorResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryClientCreatorResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryClientCreatorResponse proto.InternalMessageInfo + +func (m *QueryClientCreatorResponse) GetCreator() string { + if m != nil { + return m.Creator + } + return "" +} + +// QueryUpgradedClientStateRequest is the request type for the +// Query/UpgradedClientState RPC method +type QueryUpgradedClientStateRequest struct { +} + +func (m *QueryUpgradedClientStateRequest) Reset() { *m = QueryUpgradedClientStateRequest{} } +func (m *QueryUpgradedClientStateRequest) String() string { return proto.CompactTextString(m) } +func (*QueryUpgradedClientStateRequest) ProtoMessage() {} +func (*QueryUpgradedClientStateRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_dc42cdfd1d52d76e, []int{16} +} +func (m *QueryUpgradedClientStateRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryUpgradedClientStateRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryUpgradedClientStateRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryUpgradedClientStateRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryUpgradedClientStateRequest.Merge(m, src) +} +func (m *QueryUpgradedClientStateRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryUpgradedClientStateRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryUpgradedClientStateRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryUpgradedClientStateRequest proto.InternalMessageInfo + +// QueryUpgradedClientStateResponse is the response type for the +// Query/UpgradedClientState RPC method. +type QueryUpgradedClientStateResponse struct { + // client state associated with the request identifier + UpgradedClientState *types.Any `protobuf:"bytes,1,opt,name=upgraded_client_state,json=upgradedClientState,proto3" json:"upgraded_client_state,omitempty"` +} + +func (m *QueryUpgradedClientStateResponse) Reset() { *m = QueryUpgradedClientStateResponse{} } +func (m *QueryUpgradedClientStateResponse) String() string { return proto.CompactTextString(m) } +func (*QueryUpgradedClientStateResponse) ProtoMessage() {} +func (*QueryUpgradedClientStateResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_dc42cdfd1d52d76e, []int{17} +} +func (m *QueryUpgradedClientStateResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryUpgradedClientStateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryUpgradedClientStateResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryUpgradedClientStateResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryUpgradedClientStateResponse.Merge(m, src) +} +func (m *QueryUpgradedClientStateResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryUpgradedClientStateResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryUpgradedClientStateResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryUpgradedClientStateResponse proto.InternalMessageInfo + +func (m *QueryUpgradedClientStateResponse) GetUpgradedClientState() *types.Any { + if m != nil { + return m.UpgradedClientState + } + return nil +} + +// QueryUpgradedConsensusStateRequest is the request type for the +// Query/UpgradedConsensusState RPC method +type QueryUpgradedConsensusStateRequest struct { +} + +func (m *QueryUpgradedConsensusStateRequest) Reset() { *m = QueryUpgradedConsensusStateRequest{} } +func (m *QueryUpgradedConsensusStateRequest) String() string { return proto.CompactTextString(m) } +func (*QueryUpgradedConsensusStateRequest) ProtoMessage() {} +func (*QueryUpgradedConsensusStateRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_dc42cdfd1d52d76e, []int{18} +} +func (m *QueryUpgradedConsensusStateRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryUpgradedConsensusStateRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryUpgradedConsensusStateRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryUpgradedConsensusStateRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryUpgradedConsensusStateRequest.Merge(m, src) +} +func (m *QueryUpgradedConsensusStateRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryUpgradedConsensusStateRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryUpgradedConsensusStateRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryUpgradedConsensusStateRequest proto.InternalMessageInfo + +// QueryUpgradedConsensusStateResponse is the response type for the +// Query/UpgradedConsensusState RPC method. +type QueryUpgradedConsensusStateResponse struct { + // Consensus state associated with the request identifier + UpgradedConsensusState *types.Any `protobuf:"bytes,1,opt,name=upgraded_consensus_state,json=upgradedConsensusState,proto3" json:"upgraded_consensus_state,omitempty"` +} + +func (m *QueryUpgradedConsensusStateResponse) Reset() { *m = QueryUpgradedConsensusStateResponse{} } +func (m *QueryUpgradedConsensusStateResponse) String() string { return proto.CompactTextString(m) } +func (*QueryUpgradedConsensusStateResponse) ProtoMessage() {} +func (*QueryUpgradedConsensusStateResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_dc42cdfd1d52d76e, []int{19} +} +func (m *QueryUpgradedConsensusStateResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryUpgradedConsensusStateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryUpgradedConsensusStateResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryUpgradedConsensusStateResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryUpgradedConsensusStateResponse.Merge(m, src) +} +func (m *QueryUpgradedConsensusStateResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryUpgradedConsensusStateResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryUpgradedConsensusStateResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryUpgradedConsensusStateResponse proto.InternalMessageInfo + +func (m *QueryUpgradedConsensusStateResponse) GetUpgradedConsensusState() *types.Any { + if m != nil { + return m.UpgradedConsensusState + } + return nil +} + +// QueryVerifyMembershipRequest is the request type for the Query/VerifyMembership RPC method +type QueryVerifyMembershipRequest struct { + // client unique identifier. + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // the proof to be verified by the client. + Proof []byte `protobuf:"bytes,2,opt,name=proof,proto3" json:"proof,omitempty"` + // the height of the commitment root at which the proof is verified. + ProofHeight Height `protobuf:"bytes,3,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` + // the value which is proven. + Value []byte `protobuf:"bytes,5,opt,name=value,proto3" json:"value,omitempty"` + // optional time delay + TimeDelay uint64 `protobuf:"varint,6,opt,name=time_delay,json=timeDelay,proto3" json:"time_delay,omitempty"` + // optional block delay + BlockDelay uint64 `protobuf:"varint,7,opt,name=block_delay,json=blockDelay,proto3" json:"block_delay,omitempty"` + // the commitment key path. + MerklePath v2.MerklePath `protobuf:"bytes,8,opt,name=merkle_path,json=merklePath,proto3" json:"merkle_path"` +} + +func (m *QueryVerifyMembershipRequest) Reset() { *m = QueryVerifyMembershipRequest{} } +func (m *QueryVerifyMembershipRequest) String() string { return proto.CompactTextString(m) } +func (*QueryVerifyMembershipRequest) ProtoMessage() {} +func (*QueryVerifyMembershipRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_dc42cdfd1d52d76e, []int{20} +} +func (m *QueryVerifyMembershipRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryVerifyMembershipRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryVerifyMembershipRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryVerifyMembershipRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryVerifyMembershipRequest.Merge(m, src) +} +func (m *QueryVerifyMembershipRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryVerifyMembershipRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryVerifyMembershipRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryVerifyMembershipRequest proto.InternalMessageInfo + +func (m *QueryVerifyMembershipRequest) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +func (m *QueryVerifyMembershipRequest) GetProof() []byte { + if m != nil { + return m.Proof + } + return nil +} + +func (m *QueryVerifyMembershipRequest) GetProofHeight() Height { + if m != nil { + return m.ProofHeight + } + return Height{} +} + +func (m *QueryVerifyMembershipRequest) GetValue() []byte { + if m != nil { + return m.Value + } + return nil +} + +func (m *QueryVerifyMembershipRequest) GetTimeDelay() uint64 { + if m != nil { + return m.TimeDelay + } + return 0 +} + +func (m *QueryVerifyMembershipRequest) GetBlockDelay() uint64 { + if m != nil { + return m.BlockDelay + } + return 0 +} + +func (m *QueryVerifyMembershipRequest) GetMerklePath() v2.MerklePath { + if m != nil { + return m.MerklePath + } + return v2.MerklePath{} +} + +// QueryVerifyMembershipResponse is the response type for the Query/VerifyMembership RPC method +type QueryVerifyMembershipResponse struct { + // boolean indicating success or failure of proof verification. + Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` +} + +func (m *QueryVerifyMembershipResponse) Reset() { *m = QueryVerifyMembershipResponse{} } +func (m *QueryVerifyMembershipResponse) String() string { return proto.CompactTextString(m) } +func (*QueryVerifyMembershipResponse) ProtoMessage() {} +func (*QueryVerifyMembershipResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_dc42cdfd1d52d76e, []int{21} +} +func (m *QueryVerifyMembershipResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryVerifyMembershipResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryVerifyMembershipResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryVerifyMembershipResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryVerifyMembershipResponse.Merge(m, src) +} +func (m *QueryVerifyMembershipResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryVerifyMembershipResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryVerifyMembershipResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryVerifyMembershipResponse proto.InternalMessageInfo + +func (m *QueryVerifyMembershipResponse) GetSuccess() bool { + if m != nil { + return m.Success + } + return false +} + +func init() { + proto.RegisterType((*QueryClientStateRequest)(nil), "ibc.core.client.v1.QueryClientStateRequest") + proto.RegisterType((*QueryClientStateResponse)(nil), "ibc.core.client.v1.QueryClientStateResponse") + proto.RegisterType((*QueryClientStatesRequest)(nil), "ibc.core.client.v1.QueryClientStatesRequest") + proto.RegisterType((*QueryClientStatesResponse)(nil), "ibc.core.client.v1.QueryClientStatesResponse") + proto.RegisterType((*QueryConsensusStateRequest)(nil), "ibc.core.client.v1.QueryConsensusStateRequest") + proto.RegisterType((*QueryConsensusStateResponse)(nil), "ibc.core.client.v1.QueryConsensusStateResponse") + proto.RegisterType((*QueryConsensusStatesRequest)(nil), "ibc.core.client.v1.QueryConsensusStatesRequest") + proto.RegisterType((*QueryConsensusStatesResponse)(nil), "ibc.core.client.v1.QueryConsensusStatesResponse") + proto.RegisterType((*QueryConsensusStateHeightsRequest)(nil), "ibc.core.client.v1.QueryConsensusStateHeightsRequest") + proto.RegisterType((*QueryConsensusStateHeightsResponse)(nil), "ibc.core.client.v1.QueryConsensusStateHeightsResponse") + proto.RegisterType((*QueryClientStatusRequest)(nil), "ibc.core.client.v1.QueryClientStatusRequest") + proto.RegisterType((*QueryClientStatusResponse)(nil), "ibc.core.client.v1.QueryClientStatusResponse") + proto.RegisterType((*QueryClientParamsRequest)(nil), "ibc.core.client.v1.QueryClientParamsRequest") + proto.RegisterType((*QueryClientParamsResponse)(nil), "ibc.core.client.v1.QueryClientParamsResponse") + proto.RegisterType((*QueryClientCreatorRequest)(nil), "ibc.core.client.v1.QueryClientCreatorRequest") + proto.RegisterType((*QueryClientCreatorResponse)(nil), "ibc.core.client.v1.QueryClientCreatorResponse") + proto.RegisterType((*QueryUpgradedClientStateRequest)(nil), "ibc.core.client.v1.QueryUpgradedClientStateRequest") + proto.RegisterType((*QueryUpgradedClientStateResponse)(nil), "ibc.core.client.v1.QueryUpgradedClientStateResponse") + proto.RegisterType((*QueryUpgradedConsensusStateRequest)(nil), "ibc.core.client.v1.QueryUpgradedConsensusStateRequest") + proto.RegisterType((*QueryUpgradedConsensusStateResponse)(nil), "ibc.core.client.v1.QueryUpgradedConsensusStateResponse") + proto.RegisterType((*QueryVerifyMembershipRequest)(nil), "ibc.core.client.v1.QueryVerifyMembershipRequest") + proto.RegisterType((*QueryVerifyMembershipResponse)(nil), "ibc.core.client.v1.QueryVerifyMembershipResponse") +} + +func init() { proto.RegisterFile("ibc/core/client/v1/query.proto", fileDescriptor_dc42cdfd1d52d76e) } + +var fileDescriptor_dc42cdfd1d52d76e = []byte{ + // 1308 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0xcd, 0x6f, 0x1b, 0x45, + 0x14, 0xcf, 0xa4, 0x49, 0x9a, 0x3e, 0xbb, 0x4d, 0x35, 0x6d, 0x53, 0x77, 0x93, 0x3a, 0xc9, 0x06, + 0x48, 0x1a, 0x92, 0xdd, 0xd8, 0xa1, 0x49, 0x5a, 0x09, 0x09, 0x12, 0x54, 0x1a, 0xa4, 0x96, 0xe0, + 0x8a, 0x0f, 0x21, 0x21, 0x6b, 0xbd, 0x9e, 0xd8, 0xab, 0xda, 0xbb, 0xee, 0xce, 0xae, 0xa5, 0xa8, + 0xca, 0xa5, 0xa7, 0xde, 0x40, 0x42, 0xe2, 0x8a, 0xc4, 0x81, 0x03, 0x48, 0x55, 0x0f, 0x48, 0x9c, + 0x90, 0x38, 0x41, 0x8e, 0x95, 0xe0, 0xc0, 0x89, 0xa2, 0x04, 0x89, 0x7f, 0x03, 0xed, 0xcc, 0xac, + 0xbd, 0x6b, 0x8f, 0xe3, 0x35, 0x6a, 0xb9, 0x79, 0xde, 0xe7, 0xef, 0x7d, 0xec, 0x7b, 0x4f, 0x86, + 0xac, 0x55, 0x32, 0x75, 0xd3, 0x71, 0x89, 0x6e, 0xd6, 0x2c, 0x62, 0x7b, 0x7a, 0x33, 0xa7, 0x3f, + 0xf0, 0x89, 0xbb, 0xaf, 0x35, 0x5c, 0xc7, 0x73, 0x30, 0xb6, 0x4a, 0xa6, 0x16, 0xf0, 0x35, 0xce, + 0xd7, 0x9a, 0x39, 0x65, 0xc9, 0x74, 0x68, 0xdd, 0xa1, 0x7a, 0xc9, 0xa0, 0x84, 0x0b, 0xeb, 0xcd, + 0x5c, 0x89, 0x78, 0x46, 0x4e, 0x6f, 0x18, 0x15, 0xcb, 0x36, 0x3c, 0xcb, 0xb1, 0xb9, 0xbe, 0x32, + 0x25, 0x64, 0x43, 0xb1, 0xa8, 0x71, 0x65, 0x46, 0xe2, 0x5c, 0xb8, 0xe1, 0x02, 0x0b, 0x6d, 0x01, + 0xa7, 0x5e, 0xb7, 0xbc, 0x3a, 0x13, 0xca, 0x47, 0x5e, 0x42, 0xf0, 0x4a, 0xc5, 0x71, 0x2a, 0x35, + 0xa2, 0xb3, 0x57, 0xc9, 0xdf, 0xd3, 0x0d, 0x3b, 0x74, 0x32, 0x2d, 0x58, 0x46, 0xc3, 0xd2, 0x0d, + 0xdb, 0x76, 0x3c, 0x06, 0x8f, 0x0a, 0xee, 0xc5, 0x8a, 0x53, 0x71, 0xd8, 0x4f, 0x3d, 0xf8, 0xc5, + 0xa9, 0xea, 0x3a, 0x5c, 0xfe, 0x20, 0xc0, 0xb9, 0xcd, 0xc0, 0xdc, 0xf3, 0x0c, 0x8f, 0x14, 0xc8, + 0x03, 0x9f, 0x50, 0x0f, 0x4f, 0xc1, 0x19, 0x0e, 0xb1, 0x68, 0x95, 0x33, 0x68, 0x16, 0x2d, 0x9e, + 0x29, 0x8c, 0x73, 0xc2, 0x4e, 0x59, 0x7d, 0x82, 0x20, 0xd3, 0xad, 0x48, 0x1b, 0x8e, 0x4d, 0x09, + 0xde, 0x80, 0xb4, 0xd0, 0xa4, 0x01, 0x9d, 0x29, 0xa7, 0xf2, 0x17, 0x35, 0x8e, 0x4f, 0x0b, 0xa1, + 0x6b, 0x6f, 0xdb, 0xfb, 0x85, 0x94, 0xd9, 0x36, 0x80, 0x2f, 0xc2, 0x68, 0xc3, 0x75, 0x9c, 0xbd, + 0xcc, 0xf0, 0x2c, 0x5a, 0x4c, 0x17, 0xf8, 0x03, 0x6f, 0x43, 0x9a, 0xfd, 0x28, 0x56, 0x89, 0x55, + 0xa9, 0x7a, 0x99, 0x53, 0xcc, 0x9c, 0xa2, 0x75, 0x17, 0x4c, 0xbb, 0xcd, 0x24, 0xb6, 0x46, 0x0e, + 0xff, 0x9c, 0x19, 0x2a, 0xa4, 0x98, 0x16, 0x27, 0xa9, 0xa5, 0x6e, 0xbc, 0x34, 0x8c, 0xf4, 0x16, + 0x40, 0xbb, 0x9c, 0x02, 0xed, 0x6b, 0x1a, 0xaf, 0xa7, 0x16, 0xd4, 0x5e, 0xe3, 0xb5, 0x14, 0xb5, + 0xd7, 0x76, 0x8d, 0x4a, 0x98, 0xa5, 0x42, 0x44, 0x53, 0xfd, 0x1d, 0xc1, 0x15, 0x89, 0x13, 0x91, + 0x15, 0x1b, 0xce, 0x46, 0xb3, 0x42, 0x33, 0x68, 0xf6, 0xd4, 0x62, 0x2a, 0x7f, 0x4d, 0x16, 0xc7, + 0x4e, 0x99, 0xd8, 0x9e, 0xb5, 0x67, 0x91, 0x72, 0xc4, 0xd4, 0x56, 0x36, 0x08, 0xeb, 0xbb, 0xe7, + 0x33, 0x93, 0x52, 0x36, 0x2d, 0xa4, 0x23, 0xb9, 0xa4, 0xf8, 0xdd, 0x58, 0x54, 0xc3, 0x2c, 0xaa, + 0x85, 0xbe, 0x51, 0x71, 0xb0, 0xb1, 0xb0, 0x9e, 0x22, 0x50, 0x78, 0x58, 0x01, 0xcb, 0xa6, 0x3e, + 0x4d, 0xdc, 0x27, 0x78, 0x01, 0x26, 0x5c, 0xd2, 0xb4, 0xa8, 0xe5, 0xd8, 0x45, 0xdb, 0xaf, 0x97, + 0x88, 0xcb, 0x90, 0x8c, 0x14, 0xce, 0x85, 0xe4, 0xbb, 0x8c, 0x1a, 0x13, 0x8c, 0xd4, 0x39, 0x22, + 0xc8, 0x0b, 0x89, 0xe7, 0xe1, 0x6c, 0x2d, 0x88, 0xcf, 0x0b, 0xc5, 0x46, 0x66, 0xd1, 0xe2, 0x78, + 0x21, 0xcd, 0x89, 0xa2, 0xda, 0x3f, 0x22, 0x98, 0x92, 0x42, 0x16, 0xb5, 0x78, 0x13, 0x26, 0xcc, + 0x90, 0x93, 0xa0, 0x49, 0xcf, 0x99, 0x31, 0x33, 0x2f, 0xb3, 0x4f, 0x1f, 0xc9, 0x91, 0xd3, 0x44, + 0xd9, 0xbe, 0x25, 0x29, 0xf9, 0x7f, 0x69, 0xe4, 0x5f, 0x10, 0x4c, 0xcb, 0x41, 0x88, 0xfc, 0x7d, + 0x06, 0xe7, 0x3b, 0xf2, 0x17, 0xb6, 0xf3, 0xb2, 0x2c, 0xdc, 0xb8, 0x99, 0x8f, 0x2d, 0xaf, 0x1a, + 0x4b, 0xc0, 0x44, 0x3c, 0xbd, 0x2f, 0xb0, 0x75, 0x1f, 0x23, 0x98, 0x93, 0x04, 0xc2, 0xbd, 0xff, + 0xbf, 0x39, 0xfd, 0x15, 0x81, 0x7a, 0x12, 0x14, 0x91, 0xd9, 0x4f, 0xe0, 0x72, 0x47, 0x66, 0x45, + 0x3b, 0x85, 0x09, 0xee, 0xdf, 0x4f, 0x97, 0x4c, 0x99, 0x87, 0x17, 0x97, 0xd4, 0x8d, 0xae, 0x51, + 0xea, 0x27, 0x4a, 0xa5, 0xba, 0xd6, 0x35, 0x1e, 0xfd, 0x76, 0xe0, 0x93, 0x30, 0x46, 0x19, 0x45, + 0xa8, 0x89, 0x97, 0xaa, 0xc4, 0xbc, 0xed, 0x1a, 0xae, 0x51, 0x0f, 0xbd, 0xa9, 0xef, 0xc7, 0x0c, + 0x86, 0x3c, 0x61, 0x30, 0x0f, 0x63, 0x0d, 0x46, 0x11, 0x9f, 0xb6, 0x34, 0x71, 0x42, 0x47, 0x48, + 0xaa, 0x9b, 0x31, 0x83, 0xdb, 0x2e, 0x31, 0x3c, 0xc7, 0x4d, 0x14, 0xdb, 0x7a, 0x38, 0x23, 0xe3, + 0x9a, 0x02, 0x4b, 0x06, 0x4e, 0x9b, 0x9c, 0x24, 0x14, 0xc3, 0xa7, 0x3a, 0x07, 0x33, 0x4c, 0xef, + 0xc3, 0x46, 0xc5, 0x35, 0xca, 0xb1, 0x81, 0x1e, 0x46, 0x59, 0x83, 0xd9, 0xde, 0x22, 0xc2, 0xc1, + 0x6d, 0xb8, 0xe4, 0x0b, 0x76, 0x31, 0xf1, 0xee, 0xbd, 0xe0, 0x77, 0x5b, 0x54, 0x5f, 0x11, 0x6d, + 0xda, 0xf2, 0x26, 0x1b, 0xfa, 0xaa, 0x0f, 0xf3, 0x27, 0x4a, 0x09, 0x58, 0x77, 0x21, 0xd3, 0x86, + 0x35, 0xc0, 0xc0, 0x9d, 0xf4, 0xa5, 0x76, 0xd5, 0x9f, 0x86, 0xc5, 0x60, 0xfa, 0x88, 0xb8, 0xd6, + 0xde, 0xfe, 0x1d, 0x12, 0xec, 0x0e, 0x5a, 0xb5, 0x1a, 0x89, 0x3e, 0xe5, 0x97, 0x37, 0xb6, 0x03, + 0xd3, 0x4d, 0xa3, 0xe6, 0x93, 0xcc, 0x28, 0x37, 0xcd, 0x1e, 0xf8, 0x2a, 0x80, 0x67, 0xd5, 0x49, + 0xb1, 0x4c, 0x6a, 0xc6, 0x7e, 0x66, 0x8c, 0xed, 0xb3, 0x33, 0x01, 0xe5, 0x9d, 0x80, 0x80, 0x67, + 0x20, 0x55, 0xaa, 0x39, 0xe6, 0x7d, 0xc1, 0x3f, 0xcd, 0xf8, 0xc0, 0x48, 0x5c, 0x60, 0x07, 0x52, + 0x75, 0xe2, 0xde, 0xaf, 0x91, 0x62, 0xc3, 0xf0, 0xaa, 0x99, 0x71, 0x86, 0x4c, 0x8d, 0x20, 0x6b, + 0x5f, 0x87, 0xcd, 0xbc, 0x76, 0x87, 0x89, 0xee, 0x1a, 0x5e, 0x55, 0x20, 0x84, 0x7a, 0x8b, 0xf2, + 0xde, 0xc8, 0xf8, 0xc8, 0xf9, 0x51, 0xf5, 0x06, 0x5c, 0xed, 0x91, 0xbe, 0x76, 0xa3, 0x52, 0xdf, + 0x34, 0x09, 0xe5, 0x5f, 0xcd, 0x78, 0x21, 0x7c, 0xe6, 0xbf, 0x9f, 0x80, 0x51, 0xa6, 0x8b, 0xbf, + 0x46, 0x90, 0x8a, 0x74, 0x0c, 0x7e, 0x5d, 0x96, 0xaa, 0x1e, 0x57, 0xa5, 0xb2, 0x9c, 0x4c, 0x98, + 0xc3, 0x51, 0xaf, 0x3f, 0xfa, 0xed, 0xef, 0x2f, 0x87, 0x75, 0xbc, 0xa2, 0xf7, 0x3c, 0xa0, 0xc5, + 0xfa, 0xd1, 0x1f, 0xb6, 0xea, 0x7e, 0x80, 0xbf, 0x42, 0x90, 0x8e, 0x5e, 0x46, 0x38, 0x91, 0xd7, + 0x70, 0xac, 0x28, 0x2b, 0x09, 0xa5, 0x05, 0xc8, 0x6b, 0x0c, 0xe4, 0x3c, 0x9e, 0xeb, 0x0b, 0x12, + 0x3f, 0x47, 0x70, 0x2e, 0xde, 0xd2, 0x58, 0xeb, 0xed, 0x4c, 0xf6, 0xe5, 0x29, 0x7a, 0x62, 0x79, + 0x01, 0xaf, 0xc6, 0xe0, 0xed, 0xe1, 0xb2, 0x14, 0x5e, 0xc7, 0x16, 0x8f, 0xa6, 0x51, 0x0f, 0x2f, + 0x2f, 0xfd, 0x61, 0xc7, 0x0d, 0x77, 0xa0, 0xf3, 0x6f, 0x25, 0xc2, 0xe0, 0x84, 0x03, 0xfc, 0x04, + 0xc1, 0x44, 0xc7, 0xd5, 0x80, 0x93, 0x42, 0x6e, 0x15, 0x60, 0x35, 0xb9, 0x82, 0x08, 0x72, 0x93, + 0x05, 0x99, 0xc7, 0xab, 0x83, 0x06, 0x89, 0x0f, 0x11, 0x5c, 0x92, 0xae, 0x64, 0x7c, 0x3d, 0x21, + 0x8a, 0xf8, 0x35, 0xa1, 0xac, 0x0f, 0xaa, 0x26, 0x42, 0x78, 0x8b, 0x85, 0x70, 0x13, 0x6f, 0x0e, + 0x5c, 0x27, 0x71, 0x20, 0xe0, 0x6f, 0x62, 0x6d, 0xef, 0x27, 0x6b, 0x7b, 0x7f, 0xa0, 0xb6, 0x6f, + 0x2f, 0xec, 0xc4, 0xdf, 0xa6, 0x1f, 0xcf, 0xf7, 0xe7, 0x2d, 0x90, 0x7c, 0xf7, 0xf6, 0x05, 0x19, + 0x5b, 0xf9, 0x7d, 0x41, 0xc6, 0x8f, 0x00, 0x55, 0x65, 0x20, 0xa7, 0xb1, 0x22, 0x03, 0xc9, 0x97, + 0x3e, 0xfe, 0x16, 0xc1, 0xd9, 0xd8, 0xda, 0xc6, 0xfd, 0x9c, 0xc4, 0x0f, 0x03, 0x45, 0x4b, 0x2a, + 0x2e, 0x40, 0xad, 0x33, 0x50, 0xab, 0x58, 0x3b, 0x21, 0x73, 0xe2, 0x3e, 0x88, 0xa5, 0xee, 0x07, + 0x04, 0x17, 0x24, 0x47, 0x00, 0x5e, 0xeb, 0xe9, 0xbf, 0xf7, 0x55, 0xa1, 0xbc, 0x31, 0x98, 0x92, + 0x80, 0x9e, 0x67, 0xd0, 0x97, 0xf1, 0x92, 0x0c, 0xba, 0xf4, 0x02, 0xa1, 0xf8, 0x67, 0x04, 0x93, + 0xf2, 0x3b, 0x01, 0xaf, 0xf7, 0x07, 0x21, 0x1d, 0x82, 0x1b, 0x03, 0xeb, 0x25, 0x69, 0xda, 0x5e, + 0xa7, 0x0a, 0x0d, 0xa6, 0xda, 0xf9, 0xce, 0x9d, 0x89, 0x7b, 0x4f, 0xa9, 0x1e, 0xd7, 0x89, 0x92, + 0x1b, 0x40, 0x23, 0x04, 0xfc, 0xf8, 0x9f, 0xa7, 0x4b, 0x88, 0xa1, 0x5e, 0xba, 0x89, 0x96, 0xd4, + 0x57, 0x65, 0xc0, 0x9b, 0x4c, 0xbb, 0x58, 0x6f, 0xa9, 0x6f, 0xdd, 0x3b, 0x3c, 0xca, 0xa2, 0x67, + 0x47, 0x59, 0xf4, 0xd7, 0x51, 0x16, 0x7d, 0x71, 0x9c, 0x1d, 0x7a, 0x76, 0x9c, 0x1d, 0xfa, 0xe3, + 0x38, 0x3b, 0xf4, 0xe9, 0x8d, 0x8a, 0xe5, 0x55, 0xfd, 0x52, 0x70, 0x3b, 0xe8, 0xe2, 0x2f, 0x2b, + 0xab, 0x64, 0xae, 0x54, 0x1c, 0xbd, 0x99, 0x5b, 0xd5, 0xeb, 0x4e, 0xd9, 0xaf, 0x11, 0xca, 0x1d, + 0xac, 0xe6, 0x57, 0x84, 0x0f, 0x6f, 0xbf, 0x41, 0x68, 0x69, 0x8c, 0xdd, 0x68, 0x6b, 0xff, 0x06, + 0x00, 0x00, 0xff, 0xff, 0x33, 0xfb, 0x80, 0xbf, 0x4b, 0x13, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // ClientState queries an IBC light client. + ClientState(ctx context.Context, in *QueryClientStateRequest, opts ...grpc.CallOption) (*QueryClientStateResponse, error) + // ClientStates queries all the IBC light clients of a chain. + ClientStates(ctx context.Context, in *QueryClientStatesRequest, opts ...grpc.CallOption) (*QueryClientStatesResponse, error) + // ConsensusState queries a consensus state associated with a client state at + // a given height. + ConsensusState(ctx context.Context, in *QueryConsensusStateRequest, opts ...grpc.CallOption) (*QueryConsensusStateResponse, error) + // ConsensusStates queries all the consensus state associated with a given + // client. + ConsensusStates(ctx context.Context, in *QueryConsensusStatesRequest, opts ...grpc.CallOption) (*QueryConsensusStatesResponse, error) + // ConsensusStateHeights queries the height of every consensus states associated with a given client. + ConsensusStateHeights(ctx context.Context, in *QueryConsensusStateHeightsRequest, opts ...grpc.CallOption) (*QueryConsensusStateHeightsResponse, error) + // Status queries the status of an IBC client. + ClientStatus(ctx context.Context, in *QueryClientStatusRequest, opts ...grpc.CallOption) (*QueryClientStatusResponse, error) + // ClientParams queries all parameters of the ibc client submodule. + ClientParams(ctx context.Context, in *QueryClientParamsRequest, opts ...grpc.CallOption) (*QueryClientParamsResponse, error) + // ClientCreator queries the creator of a given client. + ClientCreator(ctx context.Context, in *QueryClientCreatorRequest, opts ...grpc.CallOption) (*QueryClientCreatorResponse, error) + // UpgradedClientState queries an Upgraded IBC light client. + UpgradedClientState(ctx context.Context, in *QueryUpgradedClientStateRequest, opts ...grpc.CallOption) (*QueryUpgradedClientStateResponse, error) + // UpgradedConsensusState queries an Upgraded IBC consensus state. + UpgradedConsensusState(ctx context.Context, in *QueryUpgradedConsensusStateRequest, opts ...grpc.CallOption) (*QueryUpgradedConsensusStateResponse, error) + // VerifyMembership queries an IBC light client for proof verification of a value at a given key path. + VerifyMembership(ctx context.Context, in *QueryVerifyMembershipRequest, opts ...grpc.CallOption) (*QueryVerifyMembershipResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) ClientState(ctx context.Context, in *QueryClientStateRequest, opts ...grpc.CallOption) (*QueryClientStateResponse, error) { + out := new(QueryClientStateResponse) + err := c.cc.Invoke(ctx, "/ibc.core.client.v1.Query/ClientState", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) ClientStates(ctx context.Context, in *QueryClientStatesRequest, opts ...grpc.CallOption) (*QueryClientStatesResponse, error) { + out := new(QueryClientStatesResponse) + err := c.cc.Invoke(ctx, "/ibc.core.client.v1.Query/ClientStates", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) ConsensusState(ctx context.Context, in *QueryConsensusStateRequest, opts ...grpc.CallOption) (*QueryConsensusStateResponse, error) { + out := new(QueryConsensusStateResponse) + err := c.cc.Invoke(ctx, "/ibc.core.client.v1.Query/ConsensusState", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) ConsensusStates(ctx context.Context, in *QueryConsensusStatesRequest, opts ...grpc.CallOption) (*QueryConsensusStatesResponse, error) { + out := new(QueryConsensusStatesResponse) + err := c.cc.Invoke(ctx, "/ibc.core.client.v1.Query/ConsensusStates", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) ConsensusStateHeights(ctx context.Context, in *QueryConsensusStateHeightsRequest, opts ...grpc.CallOption) (*QueryConsensusStateHeightsResponse, error) { + out := new(QueryConsensusStateHeightsResponse) + err := c.cc.Invoke(ctx, "/ibc.core.client.v1.Query/ConsensusStateHeights", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) ClientStatus(ctx context.Context, in *QueryClientStatusRequest, opts ...grpc.CallOption) (*QueryClientStatusResponse, error) { + out := new(QueryClientStatusResponse) + err := c.cc.Invoke(ctx, "/ibc.core.client.v1.Query/ClientStatus", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) ClientParams(ctx context.Context, in *QueryClientParamsRequest, opts ...grpc.CallOption) (*QueryClientParamsResponse, error) { + out := new(QueryClientParamsResponse) + err := c.cc.Invoke(ctx, "/ibc.core.client.v1.Query/ClientParams", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) ClientCreator(ctx context.Context, in *QueryClientCreatorRequest, opts ...grpc.CallOption) (*QueryClientCreatorResponse, error) { + out := new(QueryClientCreatorResponse) + err := c.cc.Invoke(ctx, "/ibc.core.client.v1.Query/ClientCreator", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) UpgradedClientState(ctx context.Context, in *QueryUpgradedClientStateRequest, opts ...grpc.CallOption) (*QueryUpgradedClientStateResponse, error) { + out := new(QueryUpgradedClientStateResponse) + err := c.cc.Invoke(ctx, "/ibc.core.client.v1.Query/UpgradedClientState", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) UpgradedConsensusState(ctx context.Context, in *QueryUpgradedConsensusStateRequest, opts ...grpc.CallOption) (*QueryUpgradedConsensusStateResponse, error) { + out := new(QueryUpgradedConsensusStateResponse) + err := c.cc.Invoke(ctx, "/ibc.core.client.v1.Query/UpgradedConsensusState", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) VerifyMembership(ctx context.Context, in *QueryVerifyMembershipRequest, opts ...grpc.CallOption) (*QueryVerifyMembershipResponse, error) { + out := new(QueryVerifyMembershipResponse) + err := c.cc.Invoke(ctx, "/ibc.core.client.v1.Query/VerifyMembership", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // ClientState queries an IBC light client. + ClientState(context.Context, *QueryClientStateRequest) (*QueryClientStateResponse, error) + // ClientStates queries all the IBC light clients of a chain. + ClientStates(context.Context, *QueryClientStatesRequest) (*QueryClientStatesResponse, error) + // ConsensusState queries a consensus state associated with a client state at + // a given height. + ConsensusState(context.Context, *QueryConsensusStateRequest) (*QueryConsensusStateResponse, error) + // ConsensusStates queries all the consensus state associated with a given + // client. + ConsensusStates(context.Context, *QueryConsensusStatesRequest) (*QueryConsensusStatesResponse, error) + // ConsensusStateHeights queries the height of every consensus states associated with a given client. + ConsensusStateHeights(context.Context, *QueryConsensusStateHeightsRequest) (*QueryConsensusStateHeightsResponse, error) + // Status queries the status of an IBC client. + ClientStatus(context.Context, *QueryClientStatusRequest) (*QueryClientStatusResponse, error) + // ClientParams queries all parameters of the ibc client submodule. + ClientParams(context.Context, *QueryClientParamsRequest) (*QueryClientParamsResponse, error) + // ClientCreator queries the creator of a given client. + ClientCreator(context.Context, *QueryClientCreatorRequest) (*QueryClientCreatorResponse, error) + // UpgradedClientState queries an Upgraded IBC light client. + UpgradedClientState(context.Context, *QueryUpgradedClientStateRequest) (*QueryUpgradedClientStateResponse, error) + // UpgradedConsensusState queries an Upgraded IBC consensus state. + UpgradedConsensusState(context.Context, *QueryUpgradedConsensusStateRequest) (*QueryUpgradedConsensusStateResponse, error) + // VerifyMembership queries an IBC light client for proof verification of a value at a given key path. + VerifyMembership(context.Context, *QueryVerifyMembershipRequest) (*QueryVerifyMembershipResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) ClientState(ctx context.Context, req *QueryClientStateRequest) (*QueryClientStateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ClientState not implemented") +} +func (*UnimplementedQueryServer) ClientStates(ctx context.Context, req *QueryClientStatesRequest) (*QueryClientStatesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ClientStates not implemented") +} +func (*UnimplementedQueryServer) ConsensusState(ctx context.Context, req *QueryConsensusStateRequest) (*QueryConsensusStateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ConsensusState not implemented") +} +func (*UnimplementedQueryServer) ConsensusStates(ctx context.Context, req *QueryConsensusStatesRequest) (*QueryConsensusStatesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ConsensusStates not implemented") +} +func (*UnimplementedQueryServer) ConsensusStateHeights(ctx context.Context, req *QueryConsensusStateHeightsRequest) (*QueryConsensusStateHeightsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ConsensusStateHeights not implemented") +} +func (*UnimplementedQueryServer) ClientStatus(ctx context.Context, req *QueryClientStatusRequest) (*QueryClientStatusResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ClientStatus not implemented") +} +func (*UnimplementedQueryServer) ClientParams(ctx context.Context, req *QueryClientParamsRequest) (*QueryClientParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ClientParams not implemented") +} +func (*UnimplementedQueryServer) ClientCreator(ctx context.Context, req *QueryClientCreatorRequest) (*QueryClientCreatorResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ClientCreator not implemented") +} +func (*UnimplementedQueryServer) UpgradedClientState(ctx context.Context, req *QueryUpgradedClientStateRequest) (*QueryUpgradedClientStateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpgradedClientState not implemented") +} +func (*UnimplementedQueryServer) UpgradedConsensusState(ctx context.Context, req *QueryUpgradedConsensusStateRequest) (*QueryUpgradedConsensusStateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpgradedConsensusState not implemented") +} +func (*UnimplementedQueryServer) VerifyMembership(ctx context.Context, req *QueryVerifyMembershipRequest) (*QueryVerifyMembershipResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method VerifyMembership not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_ClientState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryClientStateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).ClientState(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.client.v1.Query/ClientState", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).ClientState(ctx, req.(*QueryClientStateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_ClientStates_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryClientStatesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).ClientStates(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.client.v1.Query/ClientStates", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).ClientStates(ctx, req.(*QueryClientStatesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_ConsensusState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryConsensusStateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).ConsensusState(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.client.v1.Query/ConsensusState", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).ConsensusState(ctx, req.(*QueryConsensusStateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_ConsensusStates_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryConsensusStatesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).ConsensusStates(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.client.v1.Query/ConsensusStates", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).ConsensusStates(ctx, req.(*QueryConsensusStatesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_ConsensusStateHeights_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryConsensusStateHeightsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).ConsensusStateHeights(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.client.v1.Query/ConsensusStateHeights", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).ConsensusStateHeights(ctx, req.(*QueryConsensusStateHeightsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_ClientStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryClientStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).ClientStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.client.v1.Query/ClientStatus", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).ClientStatus(ctx, req.(*QueryClientStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_ClientParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryClientParamsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).ClientParams(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.client.v1.Query/ClientParams", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).ClientParams(ctx, req.(*QueryClientParamsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_ClientCreator_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryClientCreatorRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).ClientCreator(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.client.v1.Query/ClientCreator", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).ClientCreator(ctx, req.(*QueryClientCreatorRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_UpgradedClientState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryUpgradedClientStateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).UpgradedClientState(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.client.v1.Query/UpgradedClientState", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).UpgradedClientState(ctx, req.(*QueryUpgradedClientStateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_UpgradedConsensusState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryUpgradedConsensusStateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).UpgradedConsensusState(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.client.v1.Query/UpgradedConsensusState", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).UpgradedConsensusState(ctx, req.(*QueryUpgradedConsensusStateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_VerifyMembership_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryVerifyMembershipRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).VerifyMembership(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.client.v1.Query/VerifyMembership", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).VerifyMembership(ctx, req.(*QueryVerifyMembershipRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ibc.core.client.v1.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ClientState", + Handler: _Query_ClientState_Handler, + }, + { + MethodName: "ClientStates", + Handler: _Query_ClientStates_Handler, + }, + { + MethodName: "ConsensusState", + Handler: _Query_ConsensusState_Handler, + }, + { + MethodName: "ConsensusStates", + Handler: _Query_ConsensusStates_Handler, + }, + { + MethodName: "ConsensusStateHeights", + Handler: _Query_ConsensusStateHeights_Handler, + }, + { + MethodName: "ClientStatus", + Handler: _Query_ClientStatus_Handler, + }, + { + MethodName: "ClientParams", + Handler: _Query_ClientParams_Handler, + }, + { + MethodName: "ClientCreator", + Handler: _Query_ClientCreator_Handler, + }, + { + MethodName: "UpgradedClientState", + Handler: _Query_UpgradedClientState_Handler, + }, + { + MethodName: "UpgradedConsensusState", + Handler: _Query_UpgradedConsensusState_Handler, + }, + { + MethodName: "VerifyMembership", + Handler: _Query_VerifyMembership_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "ibc/core/client/v1/query.proto", +} + +func (m *QueryClientStateRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryClientStateRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryClientStateRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryClientStateResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryClientStateResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryClientStateResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.Proof) > 0 { + i -= len(m.Proof) + copy(dAtA[i:], m.Proof) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Proof))) + i-- + dAtA[i] = 0x12 + } + if m.ClientState != nil { + { + size, err := m.ClientState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryClientStatesRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryClientStatesRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryClientStatesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryClientStatesResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryClientStatesResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryClientStatesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.ClientStates) > 0 { + for iNdEx := len(m.ClientStates) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ClientStates[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *QueryConsensusStateRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryConsensusStateRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryConsensusStateRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.LatestHeight { + i-- + if m.LatestHeight { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x20 + } + if m.RevisionHeight != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.RevisionHeight)) + i-- + dAtA[i] = 0x18 + } + if m.RevisionNumber != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.RevisionNumber)) + i-- + dAtA[i] = 0x10 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryConsensusStateResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryConsensusStateResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryConsensusStateResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.Proof) > 0 { + i -= len(m.Proof) + copy(dAtA[i:], m.Proof) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Proof))) + i-- + dAtA[i] = 0x12 + } + if m.ConsensusState != nil { + { + size, err := m.ConsensusState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryConsensusStatesRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryConsensusStatesRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryConsensusStatesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryConsensusStatesResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryConsensusStatesResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryConsensusStatesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.ConsensusStates) > 0 { + for iNdEx := len(m.ConsensusStates) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ConsensusStates[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *QueryConsensusStateHeightsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryConsensusStateHeightsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryConsensusStateHeightsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryConsensusStateHeightsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryConsensusStateHeightsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryConsensusStateHeightsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.ConsensusStateHeights) > 0 { + for iNdEx := len(m.ConsensusStateHeights) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ConsensusStateHeights[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *QueryClientStatusRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryClientStatusRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryClientStatusRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryClientStatusResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryClientStatusResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryClientStatusResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Status) > 0 { + i -= len(m.Status) + copy(dAtA[i:], m.Status) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Status))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryClientParamsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryClientParamsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryClientParamsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryClientParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryClientParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryClientParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Params != nil { + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryClientCreatorRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryClientCreatorRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryClientCreatorRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryClientCreatorResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryClientCreatorResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryClientCreatorResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Creator) > 0 { + i -= len(m.Creator) + copy(dAtA[i:], m.Creator) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Creator))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryUpgradedClientStateRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryUpgradedClientStateRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryUpgradedClientStateRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryUpgradedClientStateResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryUpgradedClientStateResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryUpgradedClientStateResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.UpgradedClientState != nil { + { + size, err := m.UpgradedClientState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryUpgradedConsensusStateRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryUpgradedConsensusStateRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryUpgradedConsensusStateRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryUpgradedConsensusStateResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryUpgradedConsensusStateResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryUpgradedConsensusStateResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.UpgradedConsensusState != nil { + { + size, err := m.UpgradedConsensusState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryVerifyMembershipRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryVerifyMembershipRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryVerifyMembershipRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.MerklePath.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x42 + if m.BlockDelay != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.BlockDelay)) + i-- + dAtA[i] = 0x38 + } + if m.TimeDelay != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.TimeDelay)) + i-- + dAtA[i] = 0x30 + } + if len(m.Value) > 0 { + i -= len(m.Value) + copy(dAtA[i:], m.Value) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Value))) + i-- + dAtA[i] = 0x2a + } + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.Proof) > 0 { + i -= len(m.Proof) + copy(dAtA[i:], m.Proof) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Proof))) + i-- + dAtA[i] = 0x12 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryVerifyMembershipResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryVerifyMembershipResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryVerifyMembershipResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Success { + i-- + if m.Success { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryClientStateRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryClientStateResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ClientState != nil { + l = m.ClientState.Size() + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.Proof) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryClientStatesRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryClientStatesResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.ClientStates) > 0 { + for _, e := range m.ClientStates { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryConsensusStateRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.RevisionNumber != 0 { + n += 1 + sovQuery(uint64(m.RevisionNumber)) + } + if m.RevisionHeight != 0 { + n += 1 + sovQuery(uint64(m.RevisionHeight)) + } + if m.LatestHeight { + n += 2 + } + return n +} + +func (m *QueryConsensusStateResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ConsensusState != nil { + l = m.ConsensusState.Size() + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.Proof) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryConsensusStatesRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryConsensusStatesResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.ConsensusStates) > 0 { + for _, e := range m.ConsensusStates { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryConsensusStateHeightsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryConsensusStateHeightsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.ConsensusStateHeights) > 0 { + for _, e := range m.ConsensusStateHeights { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryClientStatusRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryClientStatusResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Status) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryClientParamsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryClientParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Params != nil { + l = m.Params.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryClientCreatorRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryClientCreatorResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Creator) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryUpgradedClientStateRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryUpgradedClientStateResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.UpgradedClientState != nil { + l = m.UpgradedClientState.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryUpgradedConsensusStateRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryUpgradedConsensusStateResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.UpgradedConsensusState != nil { + l = m.UpgradedConsensusState.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryVerifyMembershipRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.Proof) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovQuery(uint64(l)) + l = len(m.Value) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.TimeDelay != 0 { + n += 1 + sovQuery(uint64(m.TimeDelay)) + } + if m.BlockDelay != 0 { + n += 1 + sovQuery(uint64(m.BlockDelay)) + } + l = m.MerklePath.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryVerifyMembershipResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Success { + n += 2 + } + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryClientStateRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryClientStateRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryClientStateRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryClientStateResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryClientStateResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryClientStateResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ClientState == nil { + m.ClientState = &types.Any{} + } + if err := m.ClientState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proof", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Proof = append(m.Proof[:0], dAtA[iNdEx:postIndex]...) + if m.Proof == nil { + m.Proof = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryClientStatesRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryClientStatesRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryClientStatesRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryClientStatesResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryClientStatesResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryClientStatesResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientStates", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientStates = append(m.ClientStates, IdentifiedClientState{}) + if err := m.ClientStates[len(m.ClientStates)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryConsensusStateRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryConsensusStateRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryConsensusStateRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RevisionNumber", wireType) + } + m.RevisionNumber = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.RevisionNumber |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RevisionHeight", wireType) + } + m.RevisionHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.RevisionHeight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field LatestHeight", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.LatestHeight = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryConsensusStateResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryConsensusStateResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryConsensusStateResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConsensusState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ConsensusState == nil { + m.ConsensusState = &types.Any{} + } + if err := m.ConsensusState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proof", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Proof = append(m.Proof[:0], dAtA[iNdEx:postIndex]...) + if m.Proof == nil { + m.Proof = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryConsensusStatesRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryConsensusStatesRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryConsensusStatesRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryConsensusStatesResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryConsensusStatesResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryConsensusStatesResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConsensusStates", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ConsensusStates = append(m.ConsensusStates, ConsensusStateWithHeight{}) + if err := m.ConsensusStates[len(m.ConsensusStates)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryConsensusStateHeightsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryConsensusStateHeightsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryConsensusStateHeightsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryConsensusStateHeightsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryConsensusStateHeightsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryConsensusStateHeightsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConsensusStateHeights", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ConsensusStateHeights = append(m.ConsensusStateHeights, Height{}) + if err := m.ConsensusStateHeights[len(m.ConsensusStateHeights)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryClientStatusRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryClientStatusRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryClientStatusRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryClientStatusResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryClientStatusResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryClientStatusResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Status = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryClientParamsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryClientParamsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryClientParamsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryClientParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryClientParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryClientParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Params == nil { + m.Params = &Params{} + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryClientCreatorRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryClientCreatorRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryClientCreatorRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryClientCreatorResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryClientCreatorResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryClientCreatorResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Creator", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Creator = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryUpgradedClientStateRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryUpgradedClientStateRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryUpgradedClientStateRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryUpgradedClientStateResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryUpgradedClientStateResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryUpgradedClientStateResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UpgradedClientState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.UpgradedClientState == nil { + m.UpgradedClientState = &types.Any{} + } + if err := m.UpgradedClientState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryUpgradedConsensusStateRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryUpgradedConsensusStateRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryUpgradedConsensusStateRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryUpgradedConsensusStateResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryUpgradedConsensusStateResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryUpgradedConsensusStateResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UpgradedConsensusState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.UpgradedConsensusState == nil { + m.UpgradedConsensusState = &types.Any{} + } + if err := m.UpgradedConsensusState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryVerifyMembershipRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryVerifyMembershipRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryVerifyMembershipRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proof", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Proof = append(m.Proof[:0], dAtA[iNdEx:postIndex]...) + if m.Proof == nil { + m.Proof = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Value = append(m.Value[:0], dAtA[iNdEx:postIndex]...) + if m.Value == nil { + m.Value = []byte{} + } + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeDelay", wireType) + } + m.TimeDelay = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TimeDelay |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BlockDelay", wireType) + } + m.BlockDelay = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.BlockDelay |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MerklePath", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.MerklePath.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryVerifyMembershipResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryVerifyMembershipResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryVerifyMembershipResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Success", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Success = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/02-client/types/query.pb.gw.go b/modules/core/02-client/types/query.pb.gw.go new file mode 100644 index 0000000..b1d68b1 --- /dev/null +++ b/modules/core/02-client/types/query.pb.gw.go @@ -0,0 +1,1151 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: ibc/core/client/v1/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +func request_Query_ClientState_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryClientStateRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + msg, err := client.ClientState(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_ClientState_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryClientStateRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + msg, err := server.ClientState(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Query_ClientStates_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Query_ClientStates_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryClientStatesRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ClientStates_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ClientStates(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_ClientStates_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryClientStatesRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ClientStates_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ClientStates(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Query_ConsensusState_0 = &utilities.DoubleArray{Encoding: map[string]int{"client_id": 0, "revision_number": 1, "revision_height": 2}, Base: []int{1, 1, 2, 3, 0, 0, 0}, Check: []int{0, 1, 1, 1, 2, 3, 4}} +) + +func request_Query_ConsensusState_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryConsensusStateRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + val, ok = pathParams["revision_number"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "revision_number") + } + + protoReq.RevisionNumber, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "revision_number", err) + } + + val, ok = pathParams["revision_height"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "revision_height") + } + + protoReq.RevisionHeight, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "revision_height", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ConsensusState_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ConsensusState(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_ConsensusState_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryConsensusStateRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + val, ok = pathParams["revision_number"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "revision_number") + } + + protoReq.RevisionNumber, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "revision_number", err) + } + + val, ok = pathParams["revision_height"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "revision_height") + } + + protoReq.RevisionHeight, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "revision_height", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ConsensusState_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ConsensusState(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Query_ConsensusStates_0 = &utilities.DoubleArray{Encoding: map[string]int{"client_id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} +) + +func request_Query_ConsensusStates_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryConsensusStatesRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ConsensusStates_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ConsensusStates(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_ConsensusStates_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryConsensusStatesRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ConsensusStates_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ConsensusStates(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Query_ConsensusStateHeights_0 = &utilities.DoubleArray{Encoding: map[string]int{"client_id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} +) + +func request_Query_ConsensusStateHeights_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryConsensusStateHeightsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ConsensusStateHeights_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ConsensusStateHeights(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_ConsensusStateHeights_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryConsensusStateHeightsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ConsensusStateHeights_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ConsensusStateHeights(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_ClientStatus_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryClientStatusRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + msg, err := client.ClientStatus(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_ClientStatus_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryClientStatusRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + msg, err := server.ClientStatus(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_ClientParams_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryClientParamsRequest + var metadata runtime.ServerMetadata + + msg, err := client.ClientParams(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_ClientParams_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryClientParamsRequest + var metadata runtime.ServerMetadata + + msg, err := server.ClientParams(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_ClientCreator_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryClientCreatorRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + msg, err := client.ClientCreator(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_ClientCreator_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryClientCreatorRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + msg, err := server.ClientCreator(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_UpgradedClientState_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryUpgradedClientStateRequest + var metadata runtime.ServerMetadata + + msg, err := client.UpgradedClientState(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_UpgradedClientState_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryUpgradedClientStateRequest + var metadata runtime.ServerMetadata + + msg, err := server.UpgradedClientState(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_UpgradedConsensusState_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryUpgradedConsensusStateRequest + var metadata runtime.ServerMetadata + + msg, err := client.UpgradedConsensusState(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_UpgradedConsensusState_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryUpgradedConsensusStateRequest + var metadata runtime.ServerMetadata + + msg, err := server.UpgradedConsensusState(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_VerifyMembership_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryVerifyMembershipRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.VerifyMembership(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_VerifyMembership_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryVerifyMembershipRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.VerifyMembership(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_ClientState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_ClientState_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ClientState_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ClientStates_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_ClientStates_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ClientStates_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ConsensusState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_ConsensusState_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ConsensusState_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ConsensusStates_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_ConsensusStates_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ConsensusStates_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ConsensusStateHeights_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_ConsensusStateHeights_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ConsensusStateHeights_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ClientStatus_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_ClientStatus_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ClientStatus_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ClientParams_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_ClientParams_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ClientParams_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ClientCreator_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_ClientCreator_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ClientCreator_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_UpgradedClientState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_UpgradedClientState_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_UpgradedClientState_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_UpgradedConsensusState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_UpgradedConsensusState_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_UpgradedConsensusState_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Query_VerifyMembership_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_VerifyMembership_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_VerifyMembership_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_ClientState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_ClientState_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ClientState_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ClientStates_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_ClientStates_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ClientStates_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ConsensusState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_ConsensusState_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ConsensusState_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ConsensusStates_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_ConsensusStates_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ConsensusStates_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ConsensusStateHeights_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_ConsensusStateHeights_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ConsensusStateHeights_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ClientStatus_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_ClientStatus_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ClientStatus_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ClientParams_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_ClientParams_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ClientParams_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ClientCreator_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_ClientCreator_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ClientCreator_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_UpgradedClientState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_UpgradedClientState_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_UpgradedClientState_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_UpgradedConsensusState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_UpgradedConsensusState_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_UpgradedConsensusState_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_Query_VerifyMembership_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_VerifyMembership_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_VerifyMembership_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_ClientState_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"ibc", "core", "client", "v1", "client_states", "client_id"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_ClientStates_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"ibc", "core", "client", "v1", "client_states"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_ConsensusState_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 1, 0, 4, 1, 5, 7, 2, 8, 1, 0, 4, 1, 5, 9}, []string{"ibc", "core", "client", "v1", "consensus_states", "client_id", "revision", "revision_number", "height", "revision_height"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_ConsensusStates_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"ibc", "core", "client", "v1", "consensus_states", "client_id"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_ConsensusStateHeights_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6}, []string{"ibc", "core", "client", "v1", "consensus_states", "client_id", "heights"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_ClientStatus_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"ibc", "core", "client", "v1", "client_status", "client_id"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_ClientParams_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"ibc", "core", "client", "v1", "params"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_ClientCreator_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"ibc", "core", "client", "v1", "client_creator", "client_id"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_UpgradedClientState_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"ibc", "core", "client", "v1", "upgraded_client_states"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_UpgradedConsensusState_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"ibc", "core", "client", "v1", "upgraded_consensus_states"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_VerifyMembership_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"ibc", "core", "client", "v1", "verify_membership"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Query_ClientState_0 = runtime.ForwardResponseMessage + + forward_Query_ClientStates_0 = runtime.ForwardResponseMessage + + forward_Query_ConsensusState_0 = runtime.ForwardResponseMessage + + forward_Query_ConsensusStates_0 = runtime.ForwardResponseMessage + + forward_Query_ConsensusStateHeights_0 = runtime.ForwardResponseMessage + + forward_Query_ClientStatus_0 = runtime.ForwardResponseMessage + + forward_Query_ClientParams_0 = runtime.ForwardResponseMessage + + forward_Query_ClientCreator_0 = runtime.ForwardResponseMessage + + forward_Query_UpgradedClientState_0 = runtime.ForwardResponseMessage + + forward_Query_UpgradedConsensusState_0 = runtime.ForwardResponseMessage + + forward_Query_VerifyMembership_0 = runtime.ForwardResponseMessage +) diff --git a/modules/core/02-client/types/router.go b/modules/core/02-client/types/router.go new file mode 100644 index 0000000..5e57727 --- /dev/null +++ b/modules/core/02-client/types/router.go @@ -0,0 +1,51 @@ +package types + +import ( + "fmt" + + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// Router is a map from a clientType to a LightClientModule instance. +type Router struct { + routes map[string]exported.LightClientModule +} + +// NewRouter returns an instance of the Router. +func NewRouter() *Router { + return &Router{ + routes: make(map[string]exported.LightClientModule), + } +} + +// AddRoute adds LightClientModule for a given module name. It returns the Router +// so AddRoute calls can be linked. This function will panic if: +// - the Router is sealed, +// - or a module is already registered for the provided client type, +// - or the client type is invalid. +func (rtr *Router) AddRoute(clientType string, module exported.LightClientModule) *Router { + if rtr.HasRoute(clientType) { + panic(fmt.Errorf("route %s has already been registered", module)) + } + + if err := ValidateClientType(clientType); err != nil { + panic(fmt.Errorf("failed to add route: %w", err)) + } + + rtr.routes[clientType] = module + return rtr +} + +// HasRoute returns true if the Router has a module registered or false otherwise. +func (rtr *Router) HasRoute(clientType string) bool { + _, ok := rtr.routes[clientType] + return ok +} + +// GetRoute returns the LightClientModule registered for the provided client type or false otherwise. +func (rtr *Router) GetRoute(clientType string) (exported.LightClientModule, bool) { + if !rtr.HasRoute(clientType) { + return nil, false + } + return rtr.routes[clientType], true +} diff --git a/modules/core/02-client/types/router_test.go b/modules/core/02-client/types/router_test.go new file mode 100644 index 0000000..6cba099 --- /dev/null +++ b/modules/core/02-client/types/router_test.go @@ -0,0 +1,132 @@ +package types_test + +import ( + "errors" + "fmt" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/runtime" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" +) + +func (suite *TypesTestSuite) TestAddRoute() { + var ( + clientType string + router *types.Router + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + clientType = exported.Tendermint + }, + nil, + }, + { + "failure: route has already been imported", + func() { + clientType = exported.Tendermint + router.AddRoute(exported.Tendermint, &ibctm.LightClientModule{}) + }, + fmt.Errorf("route %s has already been registered", exported.Tendermint), + }, + { + "failure: client type is invalid", + func() { + clientType = "" + }, + errors.New("failed to add route: client type cannot be blank"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + cdc := suite.chainA.App.AppCodec() + + storeProvider := types.NewStoreProvider(runtime.NewKVStoreService(suite.chainA.GetSimApp().GetKey(exported.StoreKey))) + tmLightClientModule := ibctm.NewLightClientModule(cdc, storeProvider) + router = types.NewRouter() + + tc.malleate() + + if tc.expError == nil { + router.AddRoute(clientType, &tmLightClientModule) + suite.Require().True(router.HasRoute(clientType)) + } else { + require.Panics(suite.T(), func() { + router.AddRoute(clientType, &tmLightClientModule) + }, tc.expError.Error()) + } + }) + } +} + +func (suite *TypesTestSuite) TestHasGetRoute() { + var clientType string + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "success", + func() { + clientType = exported.Tendermint + }, + true, + }, + { + "failure: route does not exist", + func() { + clientType = exported.Solomachine + }, + false, + }, + { + "failure: invalid client ID", + func() { + clientType = "invalid-client-type" + }, + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + cdc := suite.chainA.App.AppCodec() + + storeProvider := types.NewStoreProvider(runtime.NewKVStoreService(suite.chainA.GetSimApp().GetKey(exported.StoreKey))) + tmLightClientModule := ibctm.NewLightClientModule(cdc, storeProvider) + router := types.NewRouter() + router.AddRoute(exported.Tendermint, &tmLightClientModule) + + tc.malleate() + + hasRoute := router.HasRoute(clientType) + route, ok := router.GetRoute(clientType) + + if tc.expPass { + suite.Require().True(hasRoute) + suite.Require().True(ok) + suite.Require().NotNil(route) + suite.Require().IsType(&ibctm.LightClientModule{}, route) + } else { + suite.Require().False(hasRoute) + suite.Require().False(ok) + suite.Require().Nil(route) + } + }) + } +} diff --git a/modules/core/02-client/types/store.go b/modules/core/02-client/types/store.go new file mode 100644 index 0000000..edb152a --- /dev/null +++ b/modules/core/02-client/types/store.go @@ -0,0 +1,37 @@ +package types + +import ( + "fmt" + + corestore "cosmossdk.io/core/store" + "cosmossdk.io/store/prefix" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +// StoreProvider encapsulates the IBC core store service and offers convenience methods for LightClientModules. +type StoreProvider struct { + storeService corestore.KVStoreService +} + +// NewStoreProvider creates and returns a new client StoreProvider. +func NewStoreProvider(storeService corestore.KVStoreService) StoreProvider { + return StoreProvider{ + storeService: storeService, + } +} + +// ClientStore returns isolated prefix store for each client so they can read/write in separate namespaces. +func (s StoreProvider) ClientStore(ctx sdk.Context, clientID string) storetypes.KVStore { + clientPrefix := fmt.Appendf(nil, "%s/%s/", host.KeyClientStorePrefix, clientID) + return prefix.NewStore(runtime.KVStoreAdapter(s.storeService.OpenKVStore(ctx)), clientPrefix) +} + +// ClientModuleStore returns the module store for a provided client type. +func (s StoreProvider) ClientModuleStore(ctx sdk.Context, clientType string) storetypes.KVStore { + return prefix.NewStore(runtime.KVStoreAdapter(s.storeService.OpenKVStore(ctx)), host.PrefixedClientStoreKey([]byte(clientType))) +} diff --git a/modules/core/02-client/types/tx.pb.go b/modules/core/02-client/types/tx.pb.go new file mode 100644 index 0000000..ea943e7 --- /dev/null +++ b/modules/core/02-client/types/tx.pb.go @@ -0,0 +1,3748 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/client/v1/tx.proto + +package types + +import ( + context "context" + types1 "cosmossdk.io/x/upgrade/types" + fmt "fmt" + types "github.com/cosmos/cosmos-sdk/codec/types" + _ "github.com/cosmos/cosmos-sdk/types/msgservice" + _ "github.com/cosmos/cosmos-sdk/types/tx/amino" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// MsgCreateClient defines a message to create an IBC client +type MsgCreateClient struct { + // light client state + ClientState *types.Any `protobuf:"bytes,1,opt,name=client_state,json=clientState,proto3" json:"client_state,omitempty"` + // consensus state associated with the client that corresponds to a given + // height. + ConsensusState *types.Any `protobuf:"bytes,2,opt,name=consensus_state,json=consensusState,proto3" json:"consensus_state,omitempty"` + // signer address + Signer string `protobuf:"bytes,3,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgCreateClient) Reset() { *m = MsgCreateClient{} } +func (m *MsgCreateClient) String() string { return proto.CompactTextString(m) } +func (*MsgCreateClient) ProtoMessage() {} +func (*MsgCreateClient) Descriptor() ([]byte, []int) { + return fileDescriptor_cb5dc4651eb49a04, []int{0} +} +func (m *MsgCreateClient) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgCreateClient) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgCreateClient.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgCreateClient) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCreateClient.Merge(m, src) +} +func (m *MsgCreateClient) XXX_Size() int { + return m.Size() +} +func (m *MsgCreateClient) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCreateClient.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgCreateClient proto.InternalMessageInfo + +// MsgCreateClientResponse defines the Msg/CreateClient response type. +type MsgCreateClientResponse struct { + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` +} + +func (m *MsgCreateClientResponse) Reset() { *m = MsgCreateClientResponse{} } +func (m *MsgCreateClientResponse) String() string { return proto.CompactTextString(m) } +func (*MsgCreateClientResponse) ProtoMessage() {} +func (*MsgCreateClientResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_cb5dc4651eb49a04, []int{1} +} +func (m *MsgCreateClientResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgCreateClientResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgCreateClientResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgCreateClientResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCreateClientResponse.Merge(m, src) +} +func (m *MsgCreateClientResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgCreateClientResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCreateClientResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgCreateClientResponse proto.InternalMessageInfo + +// MsgUpdateClient defines an sdk.Msg to update a IBC client state using +// the given client message. +type MsgUpdateClient struct { + // client unique identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // client message to update the light client + ClientMessage *types.Any `protobuf:"bytes,2,opt,name=client_message,json=clientMessage,proto3" json:"client_message,omitempty"` + // signer address + Signer string `protobuf:"bytes,3,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgUpdateClient) Reset() { *m = MsgUpdateClient{} } +func (m *MsgUpdateClient) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateClient) ProtoMessage() {} +func (*MsgUpdateClient) Descriptor() ([]byte, []int) { + return fileDescriptor_cb5dc4651eb49a04, []int{2} +} +func (m *MsgUpdateClient) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateClient) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateClient.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateClient) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateClient.Merge(m, src) +} +func (m *MsgUpdateClient) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateClient) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateClient.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateClient proto.InternalMessageInfo + +// MsgUpdateClientResponse defines the Msg/UpdateClient response type. +type MsgUpdateClientResponse struct { +} + +func (m *MsgUpdateClientResponse) Reset() { *m = MsgUpdateClientResponse{} } +func (m *MsgUpdateClientResponse) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateClientResponse) ProtoMessage() {} +func (*MsgUpdateClientResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_cb5dc4651eb49a04, []int{3} +} +func (m *MsgUpdateClientResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateClientResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateClientResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateClientResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateClientResponse.Merge(m, src) +} +func (m *MsgUpdateClientResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateClientResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateClientResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateClientResponse proto.InternalMessageInfo + +// MsgUpgradeClient defines an sdk.Msg to upgrade an IBC client to a new client +// state +type MsgUpgradeClient struct { + // client unique identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // upgraded client state + ClientState *types.Any `protobuf:"bytes,2,opt,name=client_state,json=clientState,proto3" json:"client_state,omitempty"` + // upgraded consensus state, only contains enough information to serve as a + // basis of trust in update logic + ConsensusState *types.Any `protobuf:"bytes,3,opt,name=consensus_state,json=consensusState,proto3" json:"consensus_state,omitempty"` + // proof that old chain committed to new client + ProofUpgradeClient []byte `protobuf:"bytes,4,opt,name=proof_upgrade_client,json=proofUpgradeClient,proto3" json:"proof_upgrade_client,omitempty"` + // proof that old chain committed to new consensus state + ProofUpgradeConsensusState []byte `protobuf:"bytes,5,opt,name=proof_upgrade_consensus_state,json=proofUpgradeConsensusState,proto3" json:"proof_upgrade_consensus_state,omitempty"` + // signer address + Signer string `protobuf:"bytes,6,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgUpgradeClient) Reset() { *m = MsgUpgradeClient{} } +func (m *MsgUpgradeClient) String() string { return proto.CompactTextString(m) } +func (*MsgUpgradeClient) ProtoMessage() {} +func (*MsgUpgradeClient) Descriptor() ([]byte, []int) { + return fileDescriptor_cb5dc4651eb49a04, []int{4} +} +func (m *MsgUpgradeClient) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpgradeClient) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpgradeClient.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpgradeClient) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpgradeClient.Merge(m, src) +} +func (m *MsgUpgradeClient) XXX_Size() int { + return m.Size() +} +func (m *MsgUpgradeClient) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpgradeClient.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpgradeClient proto.InternalMessageInfo + +// MsgUpgradeClientResponse defines the Msg/UpgradeClient response type. +type MsgUpgradeClientResponse struct { +} + +func (m *MsgUpgradeClientResponse) Reset() { *m = MsgUpgradeClientResponse{} } +func (m *MsgUpgradeClientResponse) String() string { return proto.CompactTextString(m) } +func (*MsgUpgradeClientResponse) ProtoMessage() {} +func (*MsgUpgradeClientResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_cb5dc4651eb49a04, []int{5} +} +func (m *MsgUpgradeClientResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpgradeClientResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpgradeClientResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpgradeClientResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpgradeClientResponse.Merge(m, src) +} +func (m *MsgUpgradeClientResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgUpgradeClientResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpgradeClientResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpgradeClientResponse proto.InternalMessageInfo + +// MsgSubmitMisbehaviour defines an sdk.Msg type that submits Evidence for +// light client misbehaviour. +// This message has been deprecated. Use MsgUpdateClient instead. +// +// Deprecated: Do not use. +type MsgSubmitMisbehaviour struct { + // client unique identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // misbehaviour used for freezing the light client + Misbehaviour *types.Any `protobuf:"bytes,2,opt,name=misbehaviour,proto3" json:"misbehaviour,omitempty"` + // signer address + Signer string `protobuf:"bytes,3,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgSubmitMisbehaviour) Reset() { *m = MsgSubmitMisbehaviour{} } +func (m *MsgSubmitMisbehaviour) String() string { return proto.CompactTextString(m) } +func (*MsgSubmitMisbehaviour) ProtoMessage() {} +func (*MsgSubmitMisbehaviour) Descriptor() ([]byte, []int) { + return fileDescriptor_cb5dc4651eb49a04, []int{6} +} +func (m *MsgSubmitMisbehaviour) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSubmitMisbehaviour) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSubmitMisbehaviour.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgSubmitMisbehaviour) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSubmitMisbehaviour.Merge(m, src) +} +func (m *MsgSubmitMisbehaviour) XXX_Size() int { + return m.Size() +} +func (m *MsgSubmitMisbehaviour) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSubmitMisbehaviour.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSubmitMisbehaviour proto.InternalMessageInfo + +// MsgSubmitMisbehaviourResponse defines the Msg/SubmitMisbehaviour response +// type. +type MsgSubmitMisbehaviourResponse struct { +} + +func (m *MsgSubmitMisbehaviourResponse) Reset() { *m = MsgSubmitMisbehaviourResponse{} } +func (m *MsgSubmitMisbehaviourResponse) String() string { return proto.CompactTextString(m) } +func (*MsgSubmitMisbehaviourResponse) ProtoMessage() {} +func (*MsgSubmitMisbehaviourResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_cb5dc4651eb49a04, []int{7} +} +func (m *MsgSubmitMisbehaviourResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSubmitMisbehaviourResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSubmitMisbehaviourResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgSubmitMisbehaviourResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSubmitMisbehaviourResponse.Merge(m, src) +} +func (m *MsgSubmitMisbehaviourResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgSubmitMisbehaviourResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSubmitMisbehaviourResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSubmitMisbehaviourResponse proto.InternalMessageInfo + +// MsgRecoverClient defines the message used to recover a frozen or expired client. +type MsgRecoverClient struct { + // the client identifier for the client to be updated if the proposal passes + SubjectClientId string `protobuf:"bytes,1,opt,name=subject_client_id,json=subjectClientId,proto3" json:"subject_client_id,omitempty"` + // the substitute client identifier for the client which will replace the subject + // client + SubstituteClientId string `protobuf:"bytes,2,opt,name=substitute_client_id,json=substituteClientId,proto3" json:"substitute_client_id,omitempty"` + // signer address + Signer string `protobuf:"bytes,3,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgRecoverClient) Reset() { *m = MsgRecoverClient{} } +func (m *MsgRecoverClient) String() string { return proto.CompactTextString(m) } +func (*MsgRecoverClient) ProtoMessage() {} +func (*MsgRecoverClient) Descriptor() ([]byte, []int) { + return fileDescriptor_cb5dc4651eb49a04, []int{8} +} +func (m *MsgRecoverClient) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgRecoverClient) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgRecoverClient.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgRecoverClient) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgRecoverClient.Merge(m, src) +} +func (m *MsgRecoverClient) XXX_Size() int { + return m.Size() +} +func (m *MsgRecoverClient) XXX_DiscardUnknown() { + xxx_messageInfo_MsgRecoverClient.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgRecoverClient proto.InternalMessageInfo + +// MsgRecoverClientResponse defines the Msg/RecoverClient response type. +type MsgRecoverClientResponse struct { +} + +func (m *MsgRecoverClientResponse) Reset() { *m = MsgRecoverClientResponse{} } +func (m *MsgRecoverClientResponse) String() string { return proto.CompactTextString(m) } +func (*MsgRecoverClientResponse) ProtoMessage() {} +func (*MsgRecoverClientResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_cb5dc4651eb49a04, []int{9} +} +func (m *MsgRecoverClientResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgRecoverClientResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgRecoverClientResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgRecoverClientResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgRecoverClientResponse.Merge(m, src) +} +func (m *MsgRecoverClientResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgRecoverClientResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgRecoverClientResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgRecoverClientResponse proto.InternalMessageInfo + +// MsgIBCSoftwareUpgrade defines the message used to schedule an upgrade of an IBC client using a v1 governance proposal +type MsgIBCSoftwareUpgrade struct { + Plan types1.Plan `protobuf:"bytes,1,opt,name=plan,proto3" json:"plan"` + // An UpgradedClientState must be provided to perform an IBC breaking upgrade. + // This will make the chain commit to the correct upgraded (self) client state + // before the upgrade occurs, so that connecting chains can verify that the + // new upgraded client is valid by verifying a proof on the previous version + // of the chain. This will allow IBC connections to persist smoothly across + // planned chain upgrades. Correspondingly, the UpgradedClientState field has been + // deprecated in the Cosmos SDK to allow for this logic to exist solely in + // the 02-client module. + UpgradedClientState *types.Any `protobuf:"bytes,2,opt,name=upgraded_client_state,json=upgradedClientState,proto3" json:"upgraded_client_state,omitempty"` + // signer address + Signer string `protobuf:"bytes,3,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgIBCSoftwareUpgrade) Reset() { *m = MsgIBCSoftwareUpgrade{} } +func (m *MsgIBCSoftwareUpgrade) String() string { return proto.CompactTextString(m) } +func (*MsgIBCSoftwareUpgrade) ProtoMessage() {} +func (*MsgIBCSoftwareUpgrade) Descriptor() ([]byte, []int) { + return fileDescriptor_cb5dc4651eb49a04, []int{10} +} +func (m *MsgIBCSoftwareUpgrade) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgIBCSoftwareUpgrade) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgIBCSoftwareUpgrade.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgIBCSoftwareUpgrade) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgIBCSoftwareUpgrade.Merge(m, src) +} +func (m *MsgIBCSoftwareUpgrade) XXX_Size() int { + return m.Size() +} +func (m *MsgIBCSoftwareUpgrade) XXX_DiscardUnknown() { + xxx_messageInfo_MsgIBCSoftwareUpgrade.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgIBCSoftwareUpgrade proto.InternalMessageInfo + +func (m *MsgIBCSoftwareUpgrade) GetPlan() types1.Plan { + if m != nil { + return m.Plan + } + return types1.Plan{} +} + +func (m *MsgIBCSoftwareUpgrade) GetUpgradedClientState() *types.Any { + if m != nil { + return m.UpgradedClientState + } + return nil +} + +func (m *MsgIBCSoftwareUpgrade) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +// MsgIBCSoftwareUpgradeResponse defines the Msg/IBCSoftwareUpgrade response type. +type MsgIBCSoftwareUpgradeResponse struct { +} + +func (m *MsgIBCSoftwareUpgradeResponse) Reset() { *m = MsgIBCSoftwareUpgradeResponse{} } +func (m *MsgIBCSoftwareUpgradeResponse) String() string { return proto.CompactTextString(m) } +func (*MsgIBCSoftwareUpgradeResponse) ProtoMessage() {} +func (*MsgIBCSoftwareUpgradeResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_cb5dc4651eb49a04, []int{11} +} +func (m *MsgIBCSoftwareUpgradeResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgIBCSoftwareUpgradeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgIBCSoftwareUpgradeResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgIBCSoftwareUpgradeResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgIBCSoftwareUpgradeResponse.Merge(m, src) +} +func (m *MsgIBCSoftwareUpgradeResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgIBCSoftwareUpgradeResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgIBCSoftwareUpgradeResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgIBCSoftwareUpgradeResponse proto.InternalMessageInfo + +// MsgUpdateParams defines the sdk.Msg type to update the client parameters. +type MsgUpdateParams struct { + // signer address + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // params defines the client parameters to update. + // + // NOTE: All parameters must be supplied. + Params Params `protobuf:"bytes,2,opt,name=params,proto3" json:"params"` +} + +func (m *MsgUpdateParams) Reset() { *m = MsgUpdateParams{} } +func (m *MsgUpdateParams) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParams) ProtoMessage() {} +func (*MsgUpdateParams) Descriptor() ([]byte, []int) { + return fileDescriptor_cb5dc4651eb49a04, []int{12} +} +func (m *MsgUpdateParams) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParams.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParams.Merge(m, src) +} +func (m *MsgUpdateParams) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParams) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParams.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParams proto.InternalMessageInfo + +// MsgUpdateParamsResponse defines the MsgUpdateParams response type. +type MsgUpdateParamsResponse struct { +} + +func (m *MsgUpdateParamsResponse) Reset() { *m = MsgUpdateParamsResponse{} } +func (m *MsgUpdateParamsResponse) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParamsResponse) ProtoMessage() {} +func (*MsgUpdateParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_cb5dc4651eb49a04, []int{13} +} +func (m *MsgUpdateParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParamsResponse.Merge(m, src) +} +func (m *MsgUpdateParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParamsResponse proto.InternalMessageInfo + +// MsgDeleteClientCreator defines a message to delete the client creator of a client +type MsgDeleteClientCreator struct { + // client identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // signer address + Signer string `protobuf:"bytes,2,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgDeleteClientCreator) Reset() { *m = MsgDeleteClientCreator{} } +func (m *MsgDeleteClientCreator) String() string { return proto.CompactTextString(m) } +func (*MsgDeleteClientCreator) ProtoMessage() {} +func (*MsgDeleteClientCreator) Descriptor() ([]byte, []int) { + return fileDescriptor_cb5dc4651eb49a04, []int{14} +} +func (m *MsgDeleteClientCreator) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgDeleteClientCreator) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgDeleteClientCreator.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgDeleteClientCreator) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgDeleteClientCreator.Merge(m, src) +} +func (m *MsgDeleteClientCreator) XXX_Size() int { + return m.Size() +} +func (m *MsgDeleteClientCreator) XXX_DiscardUnknown() { + xxx_messageInfo_MsgDeleteClientCreator.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgDeleteClientCreator proto.InternalMessageInfo + +// MsgDeleteClientCreatorResponse defines the Msg/DeleteClientCreator response type. +type MsgDeleteClientCreatorResponse struct { +} + +func (m *MsgDeleteClientCreatorResponse) Reset() { *m = MsgDeleteClientCreatorResponse{} } +func (m *MsgDeleteClientCreatorResponse) String() string { return proto.CompactTextString(m) } +func (*MsgDeleteClientCreatorResponse) ProtoMessage() {} +func (*MsgDeleteClientCreatorResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_cb5dc4651eb49a04, []int{15} +} +func (m *MsgDeleteClientCreatorResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgDeleteClientCreatorResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgDeleteClientCreatorResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgDeleteClientCreatorResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgDeleteClientCreatorResponse.Merge(m, src) +} +func (m *MsgDeleteClientCreatorResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgDeleteClientCreatorResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgDeleteClientCreatorResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgDeleteClientCreatorResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*MsgCreateClient)(nil), "ibc.core.client.v1.MsgCreateClient") + proto.RegisterType((*MsgCreateClientResponse)(nil), "ibc.core.client.v1.MsgCreateClientResponse") + proto.RegisterType((*MsgUpdateClient)(nil), "ibc.core.client.v1.MsgUpdateClient") + proto.RegisterType((*MsgUpdateClientResponse)(nil), "ibc.core.client.v1.MsgUpdateClientResponse") + proto.RegisterType((*MsgUpgradeClient)(nil), "ibc.core.client.v1.MsgUpgradeClient") + proto.RegisterType((*MsgUpgradeClientResponse)(nil), "ibc.core.client.v1.MsgUpgradeClientResponse") + proto.RegisterType((*MsgSubmitMisbehaviour)(nil), "ibc.core.client.v1.MsgSubmitMisbehaviour") + proto.RegisterType((*MsgSubmitMisbehaviourResponse)(nil), "ibc.core.client.v1.MsgSubmitMisbehaviourResponse") + proto.RegisterType((*MsgRecoverClient)(nil), "ibc.core.client.v1.MsgRecoverClient") + proto.RegisterType((*MsgRecoverClientResponse)(nil), "ibc.core.client.v1.MsgRecoverClientResponse") + proto.RegisterType((*MsgIBCSoftwareUpgrade)(nil), "ibc.core.client.v1.MsgIBCSoftwareUpgrade") + proto.RegisterType((*MsgIBCSoftwareUpgradeResponse)(nil), "ibc.core.client.v1.MsgIBCSoftwareUpgradeResponse") + proto.RegisterType((*MsgUpdateParams)(nil), "ibc.core.client.v1.MsgUpdateParams") + proto.RegisterType((*MsgUpdateParamsResponse)(nil), "ibc.core.client.v1.MsgUpdateParamsResponse") + proto.RegisterType((*MsgDeleteClientCreator)(nil), "ibc.core.client.v1.MsgDeleteClientCreator") + proto.RegisterType((*MsgDeleteClientCreatorResponse)(nil), "ibc.core.client.v1.MsgDeleteClientCreatorResponse") +} + +func init() { proto.RegisterFile("ibc/core/client/v1/tx.proto", fileDescriptor_cb5dc4651eb49a04) } + +var fileDescriptor_cb5dc4651eb49a04 = []byte{ + // 898 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0xbf, 0x73, 0xe3, 0x44, + 0x14, 0xb6, 0x1c, 0x9f, 0x87, 0x6c, 0x7c, 0x67, 0xb2, 0xe7, 0xbb, 0xf3, 0x29, 0x9c, 0xed, 0x31, + 0x29, 0x82, 0x21, 0x92, 0x6d, 0x66, 0x20, 0x04, 0x28, 0x12, 0x53, 0x90, 0xc2, 0x33, 0x19, 0x7b, + 0x68, 0x28, 0x70, 0x24, 0x79, 0xad, 0x08, 0x2c, 0xad, 0x46, 0xbb, 0x32, 0xa4, 0x63, 0xa8, 0x18, + 0x2a, 0x0a, 0x1a, 0x3a, 0xfe, 0x84, 0x0c, 0x1d, 0x0d, 0x1d, 0x33, 0x29, 0x53, 0x52, 0x31, 0x90, + 0x14, 0xf9, 0x37, 0x18, 0xef, 0xae, 0x14, 0x49, 0x96, 0x14, 0x33, 0x34, 0x1e, 0x4b, 0xef, 0x7b, + 0xef, 0x7d, 0xdf, 0xbe, 0x1f, 0x2b, 0xb0, 0x63, 0xe9, 0x86, 0x6a, 0x60, 0x0f, 0xa9, 0xc6, 0xdc, + 0x42, 0x0e, 0x55, 0x17, 0x3d, 0x95, 0x7e, 0xa3, 0xb8, 0x1e, 0xa6, 0x18, 0x42, 0x4b, 0x37, 0x94, + 0xa5, 0x51, 0xe1, 0x46, 0x65, 0xd1, 0x93, 0xb7, 0x35, 0xdb, 0x72, 0xb0, 0xca, 0x7e, 0x39, 0x4c, + 0x7e, 0x61, 0x60, 0x62, 0x63, 0xa2, 0xda, 0xc4, 0x5c, 0xba, 0xdb, 0xc4, 0x14, 0x86, 0x5d, 0x61, + 0xf0, 0x5d, 0xd3, 0xd3, 0xa6, 0x48, 0x5d, 0xf4, 0x74, 0x44, 0xb5, 0x5e, 0xf0, 0x2c, 0x50, 0x35, + 0x13, 0x9b, 0x98, 0xfd, 0x55, 0x97, 0xff, 0xc4, 0xdb, 0x97, 0x26, 0xc6, 0xe6, 0x1c, 0xa9, 0xec, + 0x49, 0xf7, 0x67, 0xaa, 0xe6, 0x5c, 0x08, 0x53, 0x33, 0x85, 0xb3, 0x20, 0xc8, 0x00, 0xed, 0x5f, + 0x25, 0x50, 0x1d, 0x12, 0x73, 0xe0, 0x21, 0x8d, 0xa2, 0x01, 0xb3, 0xc0, 0xf7, 0x41, 0x85, 0x63, + 0x26, 0x84, 0x6a, 0x14, 0xd5, 0xa5, 0x96, 0xb4, 0xb7, 0xd5, 0xaf, 0x29, 0x3c, 0x8d, 0x12, 0xa4, + 0x51, 0x8e, 0x9c, 0x8b, 0xd1, 0x16, 0x47, 0x8e, 0x97, 0x40, 0xf8, 0x31, 0xa8, 0x1a, 0xd8, 0x21, + 0xc8, 0x21, 0x3e, 0x11, 0xbe, 0xc5, 0x1c, 0xdf, 0x27, 0x21, 0x98, 0xbb, 0x3f, 0x07, 0x65, 0x62, + 0x99, 0x0e, 0xf2, 0xea, 0x1b, 0x2d, 0x69, 0x6f, 0x73, 0x24, 0x9e, 0x0e, 0xab, 0xdf, 0xff, 0xd2, + 0x2c, 0x7c, 0x77, 0x77, 0xd9, 0x11, 0x2f, 0xda, 0x1f, 0x81, 0x17, 0x09, 0xce, 0x23, 0x44, 0xdc, + 0x65, 0x30, 0xb8, 0x03, 0x36, 0x05, 0x77, 0x6b, 0xca, 0x88, 0x6f, 0x8e, 0x5e, 0xe3, 0x2f, 0x4e, + 0xa6, 0x87, 0xa5, 0x65, 0xa0, 0xf6, 0x4f, 0x5c, 0xf2, 0x67, 0xee, 0xf4, 0x5e, 0x72, 0x9e, 0x1b, + 0xfc, 0x10, 0x3c, 0x11, 0x46, 0x1b, 0x11, 0xa2, 0x99, 0xf9, 0xaa, 0x1e, 0x73, 0xec, 0x90, 0x43, + 0xd7, 0x17, 0xf5, 0x92, 0x89, 0x8a, 0xb2, 0x0a, 0x44, 0xb5, 0xff, 0x28, 0x82, 0xd7, 0x99, 0x8d, + 0xf5, 0xc2, 0x3a, 0x94, 0x93, 0x25, 0x2c, 0xfe, 0x8f, 0x12, 0x6e, 0xfc, 0x87, 0x12, 0x76, 0x41, + 0xcd, 0xf5, 0x30, 0x9e, 0x4d, 0x44, 0xdf, 0x4e, 0x78, 0xec, 0x7a, 0xa9, 0x25, 0xed, 0x55, 0x46, + 0x90, 0xd9, 0xe2, 0x32, 0x8e, 0xc0, 0xab, 0x84, 0x47, 0x22, 0xfd, 0x23, 0xe6, 0x2a, 0xc7, 0x5c, + 0xb3, 0xfa, 0xa6, 0x9c, 0x7f, 0xc4, 0x32, 0xa8, 0x27, 0x8f, 0x31, 0x3c, 0xe3, 0x9f, 0x25, 0xf0, + 0x6c, 0x48, 0xcc, 0xb1, 0xaf, 0xdb, 0x16, 0x1d, 0x5a, 0x44, 0x47, 0xe7, 0xda, 0xc2, 0xc2, 0xbe, + 0x97, 0x7f, 0xd0, 0x07, 0xa0, 0x62, 0x47, 0xc0, 0xb9, 0x07, 0x1d, 0x43, 0x66, 0x36, 0xc6, 0x76, + 0x82, 0x75, 0x5d, 0x6a, 0x37, 0xc1, 0xab, 0x54, 0x6a, 0x21, 0xf9, 0xdf, 0x24, 0xd6, 0x20, 0x23, + 0x64, 0xe0, 0x05, 0xf2, 0xc4, 0xc9, 0x76, 0xc0, 0x36, 0xf1, 0xf5, 0x2f, 0x91, 0x41, 0x27, 0x49, + 0xfe, 0x55, 0x61, 0x18, 0x04, 0x32, 0xba, 0xa0, 0x46, 0x7c, 0x9d, 0x50, 0x8b, 0xfa, 0x14, 0x45, + 0xe0, 0x45, 0x06, 0x87, 0xf7, 0xb6, 0xd0, 0x23, 0x8b, 0xbe, 0x9a, 0xa0, 0xff, 0xc3, 0xdd, 0x65, + 0x67, 0x87, 0x2f, 0xb7, 0x7d, 0x32, 0xfd, 0x4a, 0x4d, 0xd2, 0x14, 0x45, 0x89, 0xbd, 0x0b, 0x75, + 0xfd, 0xce, 0x8b, 0x72, 0x72, 0x3c, 0x18, 0xe3, 0x19, 0xfd, 0x5a, 0xf3, 0x90, 0x28, 0x1e, 0x7c, + 0x0f, 0x94, 0xdc, 0xb9, 0xe6, 0x88, 0xdd, 0xf4, 0x86, 0xc2, 0x33, 0x28, 0xc1, 0xba, 0x14, 0xeb, + 0x53, 0x39, 0x9d, 0x6b, 0xce, 0x71, 0xe9, 0xea, 0xaf, 0x66, 0x61, 0xc4, 0xf0, 0xf0, 0x53, 0xf0, + 0x4c, 0x60, 0xa6, 0x93, 0xb5, 0x27, 0xe4, 0x69, 0xe0, 0x32, 0x88, 0x4c, 0x4a, 0xd6, 0x01, 0x6c, + 0x45, 0x3b, 0x8e, 0x57, 0x6e, 0x95, 0x7f, 0xa8, 0x90, 0x46, 0x76, 0xd1, 0xa9, 0xe6, 0x69, 0x36, + 0x89, 0x04, 0x96, 0xa2, 0x81, 0xe1, 0x01, 0x28, 0xbb, 0x0c, 0x21, 0xb8, 0xca, 0xca, 0xea, 0x9d, + 0xa3, 0xf0, 0x18, 0x42, 0xb2, 0xc0, 0xe7, 0xef, 0x1a, 0xee, 0x11, 0x12, 0xfa, 0x02, 0x3c, 0x1f, + 0x12, 0xf3, 0x13, 0x34, 0x47, 0x41, 0xb1, 0xd9, 0x9e, 0xc5, 0x0f, 0xcc, 0xc1, 0x3d, 0xe9, 0x62, + 0xfe, 0x0c, 0xb6, 0x40, 0x23, 0x3d, 0x7e, 0xc0, 0xa0, 0xff, 0x4f, 0x19, 0x6c, 0x0c, 0x89, 0x09, + 0xcf, 0x40, 0x25, 0x76, 0x2d, 0xbd, 0x99, 0xa6, 0x37, 0x71, 0x0f, 0xc8, 0x6f, 0xaf, 0x01, 0x0a, + 0x2f, 0x8b, 0x33, 0x50, 0x89, 0xdd, 0x02, 0x59, 0x19, 0xa2, 0xa0, 0xcc, 0x0c, 0x69, 0x9b, 0x1b, + 0x1a, 0xe0, 0x71, 0x7c, 0xdd, 0xed, 0x66, 0x7a, 0x47, 0x50, 0xf2, 0x3b, 0xeb, 0xa0, 0xc2, 0x24, + 0x1e, 0x80, 0x29, 0x6b, 0xeb, 0xad, 0x8c, 0x18, 0xab, 0x50, 0xb9, 0xb7, 0x36, 0x34, 0x2a, 0x2c, + 0xbe, 0x6d, 0xb2, 0x84, 0xc5, 0x50, 0x99, 0xc2, 0x52, 0xc7, 0x7f, 0x29, 0x2c, 0x65, 0xf4, 0xb3, + 0x84, 0xad, 0x42, 0x33, 0x85, 0x65, 0x0f, 0x24, 0x9c, 0x01, 0x18, 0xad, 0xa4, 0x98, 0xc9, 0xfc, + 0xce, 0xe0, 0xa0, 0x07, 0x3a, 0x23, 0x3e, 0x67, 0xd0, 0x07, 0x4f, 0xd3, 0x86, 0xac, 0x93, 0x11, + 0x23, 0x05, 0x2b, 0xf7, 0xd7, 0xc7, 0x06, 0x69, 0xe5, 0x47, 0xdf, 0xde, 0x5d, 0x76, 0xa4, 0xe3, + 0xf1, 0xd5, 0x4d, 0x43, 0xba, 0xbe, 0x69, 0x48, 0x7f, 0xdf, 0x34, 0xa4, 0x1f, 0x6f, 0x1b, 0x85, + 0xeb, 0xdb, 0x46, 0xe1, 0xcf, 0xdb, 0x46, 0xe1, 0xf3, 0x0f, 0x4c, 0x8b, 0x9e, 0xfb, 0xba, 0x62, + 0x60, 0x5b, 0x15, 0xdf, 0xa4, 0x96, 0x6e, 0xec, 0x9b, 0x58, 0x5d, 0xf4, 0xba, 0xaa, 0x8d, 0xa7, + 0xfe, 0x1c, 0x11, 0xfe, 0x49, 0xd9, 0xed, 0xef, 0x8b, 0xaf, 0x4a, 0x7a, 0xe1, 0x22, 0xa2, 0x97, + 0xd9, 0xd2, 0x7c, 0xf7, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6e, 0xd6, 0x8e, 0x27, 0x29, 0x0b, + 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MsgClient is the client API for Msg service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MsgClient interface { + // CreateClient defines a rpc handler method for MsgCreateClient. + CreateClient(ctx context.Context, in *MsgCreateClient, opts ...grpc.CallOption) (*MsgCreateClientResponse, error) + // UpdateClient defines a rpc handler method for MsgUpdateClient. + UpdateClient(ctx context.Context, in *MsgUpdateClient, opts ...grpc.CallOption) (*MsgUpdateClientResponse, error) + // UpgradeClient defines a rpc handler method for MsgUpgradeClient. + UpgradeClient(ctx context.Context, in *MsgUpgradeClient, opts ...grpc.CallOption) (*MsgUpgradeClientResponse, error) + // SubmitMisbehaviour defines a rpc handler method for MsgSubmitMisbehaviour. + SubmitMisbehaviour(ctx context.Context, in *MsgSubmitMisbehaviour, opts ...grpc.CallOption) (*MsgSubmitMisbehaviourResponse, error) + // RecoverClient defines a rpc handler method for MsgRecoverClient. + RecoverClient(ctx context.Context, in *MsgRecoverClient, opts ...grpc.CallOption) (*MsgRecoverClientResponse, error) + // IBCSoftwareUpgrade defines a rpc handler method for MsgIBCSoftwareUpgrade. + IBCSoftwareUpgrade(ctx context.Context, in *MsgIBCSoftwareUpgrade, opts ...grpc.CallOption) (*MsgIBCSoftwareUpgradeResponse, error) + // UpdateClientParams defines a rpc handler method for MsgUpdateParams. + UpdateClientParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) + // DeleteClientCreator defines a rpc handler method for MsgDeleteClientCreator. + DeleteClientCreator(ctx context.Context, in *MsgDeleteClientCreator, opts ...grpc.CallOption) (*MsgDeleteClientCreatorResponse, error) +} + +type msgClient struct { + cc grpc1.ClientConn +} + +func NewMsgClient(cc grpc1.ClientConn) MsgClient { + return &msgClient{cc} +} + +func (c *msgClient) CreateClient(ctx context.Context, in *MsgCreateClient, opts ...grpc.CallOption) (*MsgCreateClientResponse, error) { + out := new(MsgCreateClientResponse) + err := c.cc.Invoke(ctx, "/ibc.core.client.v1.Msg/CreateClient", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) UpdateClient(ctx context.Context, in *MsgUpdateClient, opts ...grpc.CallOption) (*MsgUpdateClientResponse, error) { + out := new(MsgUpdateClientResponse) + err := c.cc.Invoke(ctx, "/ibc.core.client.v1.Msg/UpdateClient", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) UpgradeClient(ctx context.Context, in *MsgUpgradeClient, opts ...grpc.CallOption) (*MsgUpgradeClientResponse, error) { + out := new(MsgUpgradeClientResponse) + err := c.cc.Invoke(ctx, "/ibc.core.client.v1.Msg/UpgradeClient", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) SubmitMisbehaviour(ctx context.Context, in *MsgSubmitMisbehaviour, opts ...grpc.CallOption) (*MsgSubmitMisbehaviourResponse, error) { + out := new(MsgSubmitMisbehaviourResponse) + err := c.cc.Invoke(ctx, "/ibc.core.client.v1.Msg/SubmitMisbehaviour", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) RecoverClient(ctx context.Context, in *MsgRecoverClient, opts ...grpc.CallOption) (*MsgRecoverClientResponse, error) { + out := new(MsgRecoverClientResponse) + err := c.cc.Invoke(ctx, "/ibc.core.client.v1.Msg/RecoverClient", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) IBCSoftwareUpgrade(ctx context.Context, in *MsgIBCSoftwareUpgrade, opts ...grpc.CallOption) (*MsgIBCSoftwareUpgradeResponse, error) { + out := new(MsgIBCSoftwareUpgradeResponse) + err := c.cc.Invoke(ctx, "/ibc.core.client.v1.Msg/IBCSoftwareUpgrade", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) UpdateClientParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { + out := new(MsgUpdateParamsResponse) + err := c.cc.Invoke(ctx, "/ibc.core.client.v1.Msg/UpdateClientParams", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) DeleteClientCreator(ctx context.Context, in *MsgDeleteClientCreator, opts ...grpc.CallOption) (*MsgDeleteClientCreatorResponse, error) { + out := new(MsgDeleteClientCreatorResponse) + err := c.cc.Invoke(ctx, "/ibc.core.client.v1.Msg/DeleteClientCreator", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MsgServer is the server API for Msg service. +type MsgServer interface { + // CreateClient defines a rpc handler method for MsgCreateClient. + CreateClient(context.Context, *MsgCreateClient) (*MsgCreateClientResponse, error) + // UpdateClient defines a rpc handler method for MsgUpdateClient. + UpdateClient(context.Context, *MsgUpdateClient) (*MsgUpdateClientResponse, error) + // UpgradeClient defines a rpc handler method for MsgUpgradeClient. + UpgradeClient(context.Context, *MsgUpgradeClient) (*MsgUpgradeClientResponse, error) + // SubmitMisbehaviour defines a rpc handler method for MsgSubmitMisbehaviour. + SubmitMisbehaviour(context.Context, *MsgSubmitMisbehaviour) (*MsgSubmitMisbehaviourResponse, error) + // RecoverClient defines a rpc handler method for MsgRecoverClient. + RecoverClient(context.Context, *MsgRecoverClient) (*MsgRecoverClientResponse, error) + // IBCSoftwareUpgrade defines a rpc handler method for MsgIBCSoftwareUpgrade. + IBCSoftwareUpgrade(context.Context, *MsgIBCSoftwareUpgrade) (*MsgIBCSoftwareUpgradeResponse, error) + // UpdateClientParams defines a rpc handler method for MsgUpdateParams. + UpdateClientParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) + // DeleteClientCreator defines a rpc handler method for MsgDeleteClientCreator. + DeleteClientCreator(context.Context, *MsgDeleteClientCreator) (*MsgDeleteClientCreatorResponse, error) +} + +// UnimplementedMsgServer can be embedded to have forward compatible implementations. +type UnimplementedMsgServer struct { +} + +func (*UnimplementedMsgServer) CreateClient(ctx context.Context, req *MsgCreateClient) (*MsgCreateClientResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateClient not implemented") +} +func (*UnimplementedMsgServer) UpdateClient(ctx context.Context, req *MsgUpdateClient) (*MsgUpdateClientResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateClient not implemented") +} +func (*UnimplementedMsgServer) UpgradeClient(ctx context.Context, req *MsgUpgradeClient) (*MsgUpgradeClientResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpgradeClient not implemented") +} +func (*UnimplementedMsgServer) SubmitMisbehaviour(ctx context.Context, req *MsgSubmitMisbehaviour) (*MsgSubmitMisbehaviourResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SubmitMisbehaviour not implemented") +} +func (*UnimplementedMsgServer) RecoverClient(ctx context.Context, req *MsgRecoverClient) (*MsgRecoverClientResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RecoverClient not implemented") +} +func (*UnimplementedMsgServer) IBCSoftwareUpgrade(ctx context.Context, req *MsgIBCSoftwareUpgrade) (*MsgIBCSoftwareUpgradeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method IBCSoftwareUpgrade not implemented") +} +func (*UnimplementedMsgServer) UpdateClientParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateClientParams not implemented") +} +func (*UnimplementedMsgServer) DeleteClientCreator(ctx context.Context, req *MsgDeleteClientCreator) (*MsgDeleteClientCreatorResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteClientCreator not implemented") +} + +func RegisterMsgServer(s grpc1.Server, srv MsgServer) { + s.RegisterService(&_Msg_serviceDesc, srv) +} + +func _Msg_CreateClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgCreateClient) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).CreateClient(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.client.v1.Msg/CreateClient", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).CreateClient(ctx, req.(*MsgCreateClient)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_UpdateClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUpdateClient) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).UpdateClient(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.client.v1.Msg/UpdateClient", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).UpdateClient(ctx, req.(*MsgUpdateClient)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_UpgradeClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUpgradeClient) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).UpgradeClient(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.client.v1.Msg/UpgradeClient", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).UpgradeClient(ctx, req.(*MsgUpgradeClient)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_SubmitMisbehaviour_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgSubmitMisbehaviour) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).SubmitMisbehaviour(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.client.v1.Msg/SubmitMisbehaviour", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).SubmitMisbehaviour(ctx, req.(*MsgSubmitMisbehaviour)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_RecoverClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgRecoverClient) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).RecoverClient(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.client.v1.Msg/RecoverClient", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).RecoverClient(ctx, req.(*MsgRecoverClient)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_IBCSoftwareUpgrade_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgIBCSoftwareUpgrade) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).IBCSoftwareUpgrade(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.client.v1.Msg/IBCSoftwareUpgrade", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).IBCSoftwareUpgrade(ctx, req.(*MsgIBCSoftwareUpgrade)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_UpdateClientParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUpdateParams) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).UpdateClientParams(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.client.v1.Msg/UpdateClientParams", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).UpdateClientParams(ctx, req.(*MsgUpdateParams)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_DeleteClientCreator_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgDeleteClientCreator) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).DeleteClientCreator(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.client.v1.Msg/DeleteClientCreator", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).DeleteClientCreator(ctx, req.(*MsgDeleteClientCreator)) + } + return interceptor(ctx, in, info, handler) +} + +var _Msg_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ibc.core.client.v1.Msg", + HandlerType: (*MsgServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreateClient", + Handler: _Msg_CreateClient_Handler, + }, + { + MethodName: "UpdateClient", + Handler: _Msg_UpdateClient_Handler, + }, + { + MethodName: "UpgradeClient", + Handler: _Msg_UpgradeClient_Handler, + }, + { + MethodName: "SubmitMisbehaviour", + Handler: _Msg_SubmitMisbehaviour_Handler, + }, + { + MethodName: "RecoverClient", + Handler: _Msg_RecoverClient_Handler, + }, + { + MethodName: "IBCSoftwareUpgrade", + Handler: _Msg_IBCSoftwareUpgrade_Handler, + }, + { + MethodName: "UpdateClientParams", + Handler: _Msg_UpdateClientParams_Handler, + }, + { + MethodName: "DeleteClientCreator", + Handler: _Msg_DeleteClientCreator_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "ibc/core/client/v1/tx.proto", +} + +func (m *MsgCreateClient) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgCreateClient) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgCreateClient) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x1a + } + if m.ConsensusState != nil { + { + size, err := m.ConsensusState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.ClientState != nil { + { + size, err := m.ClientState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgCreateClientResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgCreateClientResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgCreateClientResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgUpdateClient) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateClient) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateClient) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x1a + } + if m.ClientMessage != nil { + { + size, err := m.ClientMessage.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgUpdateClientResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateClientResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateClientResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgUpgradeClient) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpgradeClient) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpgradeClient) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x32 + } + if len(m.ProofUpgradeConsensusState) > 0 { + i -= len(m.ProofUpgradeConsensusState) + copy(dAtA[i:], m.ProofUpgradeConsensusState) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProofUpgradeConsensusState))) + i-- + dAtA[i] = 0x2a + } + if len(m.ProofUpgradeClient) > 0 { + i -= len(m.ProofUpgradeClient) + copy(dAtA[i:], m.ProofUpgradeClient) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProofUpgradeClient))) + i-- + dAtA[i] = 0x22 + } + if m.ConsensusState != nil { + { + size, err := m.ConsensusState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.ClientState != nil { + { + size, err := m.ClientState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgUpgradeClientResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpgradeClientResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpgradeClientResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgSubmitMisbehaviour) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgSubmitMisbehaviour) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSubmitMisbehaviour) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x1a + } + if m.Misbehaviour != nil { + { + size, err := m.Misbehaviour.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgSubmitMisbehaviourResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgSubmitMisbehaviourResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSubmitMisbehaviourResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgRecoverClient) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgRecoverClient) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgRecoverClient) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x1a + } + if len(m.SubstituteClientId) > 0 { + i -= len(m.SubstituteClientId) + copy(dAtA[i:], m.SubstituteClientId) + i = encodeVarintTx(dAtA, i, uint64(len(m.SubstituteClientId))) + i-- + dAtA[i] = 0x12 + } + if len(m.SubjectClientId) > 0 { + i -= len(m.SubjectClientId) + copy(dAtA[i:], m.SubjectClientId) + i = encodeVarintTx(dAtA, i, uint64(len(m.SubjectClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgRecoverClientResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgRecoverClientResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgRecoverClientResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgIBCSoftwareUpgrade) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgIBCSoftwareUpgrade) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgIBCSoftwareUpgrade) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x1a + } + if m.UpgradedClientState != nil { + { + size, err := m.UpgradedClientState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + { + size, err := m.Plan.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *MsgIBCSoftwareUpgradeResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgIBCSoftwareUpgradeResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgIBCSoftwareUpgradeResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgUpdateParams) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParams) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgUpdateParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgDeleteClientCreator) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgDeleteClientCreator) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgDeleteClientCreator) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x12 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgDeleteClientCreatorResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgDeleteClientCreatorResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgDeleteClientCreatorResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgCreateClient) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ClientState != nil { + l = m.ClientState.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.ConsensusState != nil { + l = m.ConsensusState.Size() + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgCreateClientResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgUpdateClient) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.ClientMessage != nil { + l = m.ClientMessage.Size() + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgUpdateClientResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgUpgradeClient) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.ClientState != nil { + l = m.ClientState.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.ConsensusState != nil { + l = m.ConsensusState.Size() + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ProofUpgradeClient) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ProofUpgradeConsensusState) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgUpgradeClientResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgSubmitMisbehaviour) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.Misbehaviour != nil { + l = m.Misbehaviour.Size() + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgSubmitMisbehaviourResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgRecoverClient) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.SubjectClientId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.SubstituteClientId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgRecoverClientResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgIBCSoftwareUpgrade) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Plan.Size() + n += 1 + l + sovTx(uint64(l)) + if m.UpgradedClientState != nil { + l = m.UpgradedClientState.Size() + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgIBCSoftwareUpgradeResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgUpdateParams) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Params.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgUpdateParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgDeleteClientCreator) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgDeleteClientCreatorResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgCreateClient) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgCreateClient: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgCreateClient: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ClientState == nil { + m.ClientState = &types.Any{} + } + if err := m.ClientState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConsensusState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ConsensusState == nil { + m.ConsensusState = &types.Any{} + } + if err := m.ConsensusState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgCreateClientResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgCreateClientResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgCreateClientResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateClient) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateClient: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateClient: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientMessage", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ClientMessage == nil { + m.ClientMessage = &types.Any{} + } + if err := m.ClientMessage.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateClientResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateClientResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateClientResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpgradeClient) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpgradeClient: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpgradeClient: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ClientState == nil { + m.ClientState = &types.Any{} + } + if err := m.ClientState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConsensusState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ConsensusState == nil { + m.ConsensusState = &types.Any{} + } + if err := m.ConsensusState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofUpgradeClient", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProofUpgradeClient = append(m.ProofUpgradeClient[:0], dAtA[iNdEx:postIndex]...) + if m.ProofUpgradeClient == nil { + m.ProofUpgradeClient = []byte{} + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofUpgradeConsensusState", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProofUpgradeConsensusState = append(m.ProofUpgradeConsensusState[:0], dAtA[iNdEx:postIndex]...) + if m.ProofUpgradeConsensusState == nil { + m.ProofUpgradeConsensusState = []byte{} + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpgradeClientResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpgradeClientResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpgradeClientResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgSubmitMisbehaviour) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgSubmitMisbehaviour: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSubmitMisbehaviour: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Misbehaviour", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Misbehaviour == nil { + m.Misbehaviour = &types.Any{} + } + if err := m.Misbehaviour.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgSubmitMisbehaviourResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgSubmitMisbehaviourResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSubmitMisbehaviourResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgRecoverClient) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgRecoverClient: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgRecoverClient: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SubjectClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SubjectClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SubstituteClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SubstituteClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgRecoverClientResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgRecoverClientResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgRecoverClientResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgIBCSoftwareUpgrade) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgIBCSoftwareUpgrade: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgIBCSoftwareUpgrade: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Plan", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Plan.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UpgradedClientState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.UpgradedClientState == nil { + m.UpgradedClientState = &types.Any{} + } + if err := m.UpgradedClientState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgIBCSoftwareUpgradeResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgIBCSoftwareUpgradeResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgIBCSoftwareUpgradeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateParams: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParams: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgDeleteClientCreator) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgDeleteClientCreator: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgDeleteClientCreator: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgDeleteClientCreatorResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgDeleteClientCreatorResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgDeleteClientCreatorResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTx(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/02-client/v2/genesis.go b/modules/core/02-client/v2/genesis.go new file mode 100644 index 0000000..9543a40 --- /dev/null +++ b/modules/core/02-client/v2/genesis.go @@ -0,0 +1,41 @@ +package clientv2 + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/keeper" + "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" +) + +// InitGenesis initializes the ibc client/v2 submodule's state from a provided genesis +// state. +func InitGenesis(ctx sdk.Context, k *keeper.Keeper, gs types.GenesisState) { + if err := gs.Validate(); err != nil { + panic(fmt.Errorf("invalid genesis state: %w", err)) + } + + for _, info := range gs.CounterpartyInfos { + k.SetClientCounterparty(ctx, info.ClientId, info.CounterpartyInfo) + } +} + +// ExportGenesis returns the ibc client/v2 submodule's exported genesis. +func ExportGenesis(ctx sdk.Context, k *keeper.Keeper) types.GenesisState { + clients := k.ClientV1Keeper.GetAllGenesisClients(ctx) + gs := types.GenesisState{ + CounterpartyInfos: make([]types.GenesisCounterpartyInfo, 0), + } + for _, client := range clients { + counterpartyInfo, found := k.GetClientCounterparty(ctx, client.ClientId) + if found { + gs.CounterpartyInfos = append(gs.CounterpartyInfos, types.GenesisCounterpartyInfo{ + ClientId: client.ClientId, + CounterpartyInfo: counterpartyInfo, + }) + } + } + + return gs +} diff --git a/modules/core/02-client/v2/genesis_test.go b/modules/core/02-client/v2/genesis_test.go new file mode 100644 index 0000000..6a18e85 --- /dev/null +++ b/modules/core/02-client/v2/genesis_test.go @@ -0,0 +1,54 @@ +package clientv2_test + +import ( + clientv2 "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2" + "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +// TestInitExportGenesis tests the import and export flow for the channel v2 keeper. +func (suite *ModuleTestSuite) TestInitExportGenesis() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + path2 := ibctesting.NewPath(suite.chainA, suite.chainC) + path2.SetupV2() + + path3 := ibctesting.NewPath(suite.chainB, suite.chainC) + path3.SetupV2() + + app := suite.chainA.App + + emptyGenesis := types.DefaultGenesisState() + + // create a valid genesis state that uses the counterparty info set during setup + existingGS := clientv2.ExportGenesis(suite.chainA.GetContext(), app.GetIBCKeeper().ClientV2Keeper) + + tests := []struct { + name string + genState types.GenesisState + expectedState types.GenesisState + }{ + { + name: "no modifications genesis", + genState: emptyGenesis, + expectedState: existingGS, + }, + { + name: "valid - default genesis", + genState: types.DefaultGenesisState(), + expectedState: existingGS, + }, + } + + for _, tt := range tests { + suite.Run(tt.name, func() { + clientV2Keeper := app.GetIBCKeeper().ClientV2Keeper + + clientv2.InitGenesis(suite.chainA.GetContext(), clientV2Keeper, tt.genState) + + exported := clientv2.ExportGenesis(suite.chainA.GetContext(), clientV2Keeper) + suite.Require().Equal(tt.expectedState, exported) + }) + } +} diff --git a/modules/core/02-client/v2/keeper/grpc_query.go b/modules/core/02-client/v2/keeper/grpc_query.go new file mode 100644 index 0000000..1967af0 --- /dev/null +++ b/modules/core/02-client/v2/keeper/grpc_query.go @@ -0,0 +1,66 @@ +package keeper + +import ( + "context" + "fmt" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +var _ types.QueryServer = (*queryServer)(nil) + +// queryServer implements the 02-client/v2 types.QueryServer interface. +// It embeds the client keeper to leverage store access while limiting the api of the client keeper. +type queryServer struct { + *Keeper +} + +// NewQueryServer returns a new 02-client/v2 types.QueryServer implementation. +func NewQueryServer(k *Keeper) types.QueryServer { + return &queryServer{ + Keeper: k, + } +} + +// CounterpartyInfo gets the CounterpartyInfo from the store corresponding to the request client ID. +func (q queryServer) CounterpartyInfo(goCtx context.Context, req *types.QueryCounterpartyInfoRequest) (*types.QueryCounterpartyInfoResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + if err := host.ClientIdentifierValidator(req.ClientId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + info, found := q.GetClientCounterparty(ctx, req.ClientId) + if !found { + return nil, status.Error(codes.NotFound, fmt.Sprintf("client %s counterparty not found", req.ClientId)) + } + + return &types.QueryCounterpartyInfoResponse{CounterpartyInfo: &info}, nil +} + +// Config queries the configuration of the ibc client v2 module. +func (q queryServer) Config(goCtx context.Context, req *types.QueryConfigRequest) (*types.QueryConfigResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + if err := host.ClientIdentifierValidator(req.ClientId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + config := q.GetConfig(ctx, req.ClientId) + + return &types.QueryConfigResponse{Config: &config}, nil +} diff --git a/modules/core/02-client/v2/keeper/grpc_query_test.go b/modules/core/02-client/v2/keeper/grpc_query_test.go new file mode 100644 index 0000000..103a1f8 --- /dev/null +++ b/modules/core/02-client/v2/keeper/grpc_query_test.go @@ -0,0 +1,161 @@ +package keeper_test + +import ( + "fmt" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/keeper" + "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *KeeperTestSuite) TestQueryCounterPartyInfo() { + var ( + req *types.QueryCounterpartyInfoRequest + expInfo = types.CounterpartyInfo{} + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "req is nil", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "req has no ID", + func() { + req = &types.QueryCounterpartyInfoRequest{} + }, + status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"), + }, + { + "counterparty not found", + func() { + path1 := ibctesting.NewPath(suite.chainA, suite.chainB) + path1.SetupClients() + // counter party not set up + + expInfo = types.NewCounterpartyInfo(path1.EndpointA.Counterparty.MerklePathPrefix.KeyPath, path1.EndpointA.ClientID) + req = &types.QueryCounterpartyInfoRequest{ + ClientId: path1.EndpointA.ClientID, + } + }, + status.Error(codes.NotFound, "client 07-tendermint-0 counterparty not found"), + }, + { + "success", + func() { + path1 := ibctesting.NewPath(suite.chainA, suite.chainB) + path1.SetupClients() + path1.SetupCounterparties() + + expInfo = types.NewCounterpartyInfo(path1.EndpointA.Counterparty.MerklePathPrefix.KeyPath, path1.EndpointA.ClientID) + req = &types.QueryCounterpartyInfoRequest{ + ClientId: path1.EndpointA.ClientID, + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + tc.malleate() + + ctx := suite.chainA.GetContext() + queryServer := keeper.NewQueryServer(suite.chainA.GetSimApp().IBCKeeper.ClientV2Keeper) + res, err := queryServer.CounterpartyInfo(ctx, req) + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expInfo, *res.CounterpartyInfo) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryConfig() { + var ( + req *types.QueryConfigRequest + expConfig = types.Config{} + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "req is nil", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "req has no ID", + func() { + req = &types.QueryConfigRequest{} + }, + status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"), + }, + { + "success with default config", + func() { + path1 := ibctesting.NewPath(suite.chainA, suite.chainB) + path1.SetupClients() + + expConfig = types.DefaultConfig() + req = &types.QueryConfigRequest{ + ClientId: path1.EndpointA.ClientID, + } + }, + nil, + }, + { + "success with custom config", + func() { + path1 := ibctesting.NewPath(suite.chainA, suite.chainB) + path1.SetupClients() + + expConfig = types.NewConfig(ibctesting.TestAccAddress) + suite.chainA.App.GetIBCKeeper().ClientV2Keeper.SetConfig(suite.chainA.GetContext(), path1.EndpointA.ClientID, expConfig) + req = &types.QueryConfigRequest{ + ClientId: path1.EndpointA.ClientID, + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + tc.malleate() + + ctx := suite.chainA.GetContext() + queryServer := keeper.NewQueryServer(suite.chainA.GetSimApp().IBCKeeper.ClientV2Keeper) + res, err := queryServer.Config(ctx, req) + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expConfig, *res.Config) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} diff --git a/modules/core/02-client/v2/keeper/keeper.go b/modules/core/02-client/v2/keeper/keeper.go new file mode 100644 index 0000000..2917d0f --- /dev/null +++ b/modules/core/02-client/v2/keeper/keeper.go @@ -0,0 +1,64 @@ +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + clientv1keeper "github.com/cosmos/ibc-go/v10/modules/core/02-client/keeper" + "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" +) + +type Keeper struct { + cdc codec.BinaryCodec + ClientV1Keeper *clientv1keeper.Keeper +} + +// NewKeeper creates a new client v2 keeper +func NewKeeper( + cdc codec.BinaryCodec, + clientV1Keeper *clientv1keeper.Keeper, +) *Keeper { + return &Keeper{ + cdc: cdc, + ClientV1Keeper: clientV1Keeper, + } +} + +// SetClientCounterparty sets counterpartyInfo for a given clientID +func (k *Keeper) SetClientCounterparty(ctx sdk.Context, clientID string, counterparty types.CounterpartyInfo) { + store := k.ClientV1Keeper.ClientStore(ctx, clientID) + store.Set(types.CounterpartyKey(), k.cdc.MustMarshal(&counterparty)) +} + +// GetClientCounterparty gets counterpartyInfo for a given clientID +func (k *Keeper) GetClientCounterparty(ctx sdk.Context, clientID string) (types.CounterpartyInfo, bool) { + store := k.ClientV1Keeper.ClientStore(ctx, clientID) + bz := store.Get(types.CounterpartyKey()) + if len(bz) == 0 { + return types.CounterpartyInfo{}, false + } + + var counterparty types.CounterpartyInfo + k.cdc.MustUnmarshal(bz, &counterparty) + return counterparty, true +} + +// GetConfig returns the ibc-client v2 configuration for the given clientID. +func (k *Keeper) GetConfig(ctx sdk.Context, clientID string) types.Config { + store := k.ClientV1Keeper.ClientStore(ctx, clientID) + bz := store.Get(types.ConfigKey()) + if len(bz) == 0 { + return types.NewConfig() + } + + var config types.Config + k.cdc.MustUnmarshal(bz, &config) + return config +} + +// SetConfig sets ibc-client v2 configuration for the given clientID. +func (k *Keeper) SetConfig(ctx sdk.Context, clientID string, config types.Config) { + store := k.ClientV1Keeper.ClientStore(ctx, clientID) + bz := k.cdc.MustMarshal(&config) + store.Set(types.ConfigKey(), bz) +} diff --git a/modules/core/02-client/v2/keeper/keeper_test.go b/modules/core/02-client/v2/keeper/keeper_test.go new file mode 100644 index 0000000..fb111e3 --- /dev/null +++ b/modules/core/02-client/v2/keeper/keeper_test.go @@ -0,0 +1,86 @@ +package keeper_test + +import ( + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/keeper" + "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + "github.com/cosmos/ibc-go/v10/testing/simapp" +) + +const ( + testClientID = "tendermint-0" + testClientID2 = "tendermint-1" +) + +type KeeperTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain + + cdc codec.Codec + ctx sdk.Context + keeper *keeper.Keeper +} + +func TestKeeperTestSuite(t *testing.T) { + testifysuite.Run(t, new(KeeperTestSuite)) +} + +func (suite *KeeperTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) + + isCheckTx := false + app := simapp.Setup(suite.T(), isCheckTx) + + suite.cdc = app.AppCodec() + suite.ctx = app.NewContext(isCheckTx) + suite.keeper = app.IBCKeeper.ClientV2Keeper +} + +func (suite *KeeperTestSuite) TestSetClientCounterparty() { + counterparty := types.NewCounterpartyInfo([][]byte{[]byte("ibc"), []byte("channel-7")}, testClientID2) + suite.keeper.SetClientCounterparty(suite.ctx, testClientID, counterparty) + + retrievedCounterparty, found := suite.keeper.GetClientCounterparty(suite.ctx, testClientID) + suite.Require().True(found, "GetCounterparty failed") + suite.Require().Equal(counterparty, retrievedCounterparty, "Counterparties are not equal") +} + +func (suite *KeeperTestSuite) TestSetConfig() { + config := suite.keeper.GetConfig(suite.ctx, testClientID) + suite.Require().Equal(config, types.DefaultConfig(), "did not return default config on initialization") + + newConfig := types.NewConfig(ibctesting.TestAccAddress) + suite.keeper.SetConfig(suite.ctx, testClientID, newConfig) + + config = suite.keeper.GetConfig(suite.ctx, testClientID) + suite.Require().Equal(newConfig, config, "config not set correctly") + + // config should be empty for a different clientID + config = suite.keeper.GetConfig(suite.ctx, testClientID2) + suite.Require().Equal(types.DefaultConfig(), config, "config should be empty for different clientID") + + // set config for a different clientID + newConfig2 := types.NewConfig(ibctesting.TestAccAddress, suite.chainA.SenderAccount.GetAddress().String()) + suite.keeper.SetConfig(suite.ctx, testClientID2, newConfig2) + + config = suite.keeper.GetConfig(suite.ctx, testClientID2) + suite.Require().Equal(newConfig2, config, "config not set correctly for different clientID") + + // config for original client unaffected + config = suite.keeper.GetConfig(suite.ctx, testClientID) + suite.Require().Equal(newConfig, config, "config not set correctly for original clientID") +} diff --git a/modules/core/02-client/v2/module.go b/modules/core/02-client/v2/module.go new file mode 100644 index 0000000..dbe4710 --- /dev/null +++ b/modules/core/02-client/v2/module.go @@ -0,0 +1,22 @@ +package clientv2 + +import ( + "github.com/spf13/cobra" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" +) + +// Name returns the IBC channel ICS name. +func Name() string { + return types.SubModuleName +} + +// GetTxCmd returns the root tx command for IBC channels. +func GetTxCmd() *cobra.Command { + return nil // TODO +} + +// GetQueryCmd returns the root query command for IBC channels. +func GetQueryCmd() *cobra.Command { + return nil // TODO +} diff --git a/modules/core/02-client/v2/module_test.go b/modules/core/02-client/v2/module_test.go new file mode 100644 index 0000000..873afc9 --- /dev/null +++ b/modules/core/02-client/v2/module_test.go @@ -0,0 +1,31 @@ +package clientv2_test + +import ( + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func TestModuleTestSuite(t *testing.T) { + testifysuite.Run(t, new(ModuleTestSuite)) +} + +type ModuleTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains used for convenience and readability + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain + chainC *ibctesting.TestChain +} + +func (suite *ModuleTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 3) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) + suite.chainC = suite.coordinator.GetChain(ibctesting.GetChainID(3)) +} diff --git a/modules/core/02-client/v2/types/codec.go b/modules/core/02-client/v2/types/codec.go new file mode 100644 index 0000000..979666a --- /dev/null +++ b/modules/core/02-client/v2/types/codec.go @@ -0,0 +1,17 @@ +package types + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/msgservice" +) + +// RegisterInterfaces registers the client interfaces to protobuf Any. +func RegisterInterfaces(registry codectypes.InterfaceRegistry) { + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgRegisterCounterparty{}, + ) + + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} diff --git a/modules/core/02-client/v2/types/config.go b/modules/core/02-client/v2/types/config.go new file mode 100644 index 0000000..af3f3f4 --- /dev/null +++ b/modules/core/02-client/v2/types/config.go @@ -0,0 +1,53 @@ +package types + +import ( + fmt "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Maximum length of the allowed relayers list +const MaxAllowedRelayersLength = 20 + +// NewConfig instantiates a new allowed relayer list for a client with provided addresses +func NewConfig(allowedRelayers ...string) Config { + return Config{ + AllowedRelayers: allowedRelayers, + } +} + +// DefaultConfig is empty and therefore permissionless +func DefaultConfig() Config { + return NewConfig() +} + +// Validate ensures all provided addresses are valid sdk Addresses +func (c Config) Validate() error { + return validateRelayers(c.AllowedRelayers) +} + +// IsAllowedRelayer checks if the given address is registered on the allowlist. +func (c Config) IsAllowedRelayer(relayer sdk.AccAddress) bool { + if len(c.AllowedRelayers) == 0 { + return true + } + for _, r := range c.AllowedRelayers { + if relayer.Equals(sdk.MustAccAddressFromBech32(r)) { + return true + } + } + return false +} + +func validateRelayers(allowedRelayers []string) error { + if len(allowedRelayers) > MaxAllowedRelayersLength { + return fmt.Errorf("allowed relayers length must not exceed %d items", MaxAllowedRelayersLength) + } + + for _, r := range allowedRelayers { + if _, err := sdk.AccAddressFromBech32(r); err != nil { + return fmt.Errorf("invalid relayer address: %s", r) + } + } + return nil +} diff --git a/modules/core/02-client/v2/types/config.pb.go b/modules/core/02-client/v2/types/config.pb.go new file mode 100644 index 0000000..26bc583 --- /dev/null +++ b/modules/core/02-client/v2/types/config.pb.go @@ -0,0 +1,326 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/client/v2/config.proto + +package types + +import ( + fmt "fmt" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Config is a **per-client** configuration struct that sets which relayers are allowed to relay v2 IBC messages +// for a given client. +// If it is set, then only relayers in the allow list can send v2 messages +// If it is not set, then the client allows permissionless relaying of v2 messages +type Config struct { + // allowed_relayers defines the set of allowed relayers for IBC V2 protocol for the given client + AllowedRelayers []string `protobuf:"bytes,1,rep,name=allowed_relayers,json=allowedRelayers,proto3" json:"allowed_relayers,omitempty"` +} + +func (m *Config) Reset() { *m = Config{} } +func (m *Config) String() string { return proto.CompactTextString(m) } +func (*Config) ProtoMessage() {} +func (*Config) Descriptor() ([]byte, []int) { + return fileDescriptor_e89b8f1b1dcb51cb, []int{0} +} +func (m *Config) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Config) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Config.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Config) XXX_Merge(src proto.Message) { + xxx_messageInfo_Config.Merge(m, src) +} +func (m *Config) XXX_Size() int { + return m.Size() +} +func (m *Config) XXX_DiscardUnknown() { + xxx_messageInfo_Config.DiscardUnknown(m) +} + +var xxx_messageInfo_Config proto.InternalMessageInfo + +func (m *Config) GetAllowedRelayers() []string { + if m != nil { + return m.AllowedRelayers + } + return nil +} + +func init() { + proto.RegisterType((*Config)(nil), "ibc.core.client.v2.Config") +} + +func init() { proto.RegisterFile("ibc/core/client/v2/config.proto", fileDescriptor_e89b8f1b1dcb51cb) } + +var fileDescriptor_e89b8f1b1dcb51cb = []byte{ + // 194 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xcf, 0x4c, 0x4a, 0xd6, + 0x4f, 0xce, 0x2f, 0x4a, 0xd5, 0x4f, 0xce, 0xc9, 0x4c, 0xcd, 0x2b, 0xd1, 0x2f, 0x33, 0xd2, 0x4f, + 0xce, 0xcf, 0x4b, 0xcb, 0x4c, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0xca, 0x4c, 0x4a, + 0xd6, 0x03, 0x29, 0xd0, 0x83, 0x28, 0xd0, 0x2b, 0x33, 0x52, 0x32, 0xe6, 0x62, 0x73, 0x06, 0xab, + 0x11, 0xd2, 0xe4, 0x12, 0x48, 0xcc, 0xc9, 0xc9, 0x2f, 0x4f, 0x4d, 0x89, 0x2f, 0x4a, 0xcd, 0x49, + 0xac, 0x4c, 0x2d, 0x2a, 0x96, 0x60, 0x54, 0x60, 0xd6, 0xe0, 0x0c, 0xe2, 0x87, 0x8a, 0x07, 0x41, + 0x85, 0x9d, 0xc2, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, + 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0xca, 0x26, 0x3d, + 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x3f, 0x39, 0xbf, 0x38, 0x37, 0xbf, 0x58, + 0x3f, 0x33, 0x29, 0x59, 0x37, 0x3d, 0x5f, 0xbf, 0xcc, 0xd0, 0x40, 0x3f, 0x37, 0x3f, 0xa5, 0x34, + 0x27, 0xb5, 0x18, 0xe2, 0x48, 0x03, 0x23, 0x5d, 0x84, 0x3b, 0x4b, 0x2a, 0x0b, 0x52, 0x8b, 0x93, + 0xd8, 0xc0, 0xee, 0x34, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xd4, 0x51, 0xba, 0xd2, 0xca, 0x00, + 0x00, 0x00, +} + +func (m *Config) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Config) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Config) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.AllowedRelayers) > 0 { + for iNdEx := len(m.AllowedRelayers) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.AllowedRelayers[iNdEx]) + copy(dAtA[i:], m.AllowedRelayers[iNdEx]) + i = encodeVarintConfig(dAtA, i, uint64(len(m.AllowedRelayers[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintConfig(dAtA []byte, offset int, v uint64) int { + offset -= sovConfig(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Config) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.AllowedRelayers) > 0 { + for _, s := range m.AllowedRelayers { + l = len(s) + n += 1 + l + sovConfig(uint64(l)) + } + } + return n +} + +func sovConfig(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozConfig(x uint64) (n int) { + return sovConfig(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Config) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Config: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Config: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowedRelayers", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConfig + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthConfig + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthConfig + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AllowedRelayers = append(m.AllowedRelayers, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipConfig(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthConfig + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipConfig(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowConfig + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthConfig + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupConfig + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthConfig + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthConfig = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowConfig = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupConfig = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/02-client/v2/types/config_test.go b/modules/core/02-client/v2/types/config_test.go new file mode 100644 index 0000000..ef09967 --- /dev/null +++ b/modules/core/02-client/v2/types/config_test.go @@ -0,0 +1,84 @@ +package types_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func TestIsAllowedRelayer(t *testing.T) { + testCases := []struct { + name string + relayer sdk.AccAddress + config types.Config + expPass bool + }{ + {"success: valid relayer with default config", signer1, types.DefaultConfig(), true}, + {"success: valid relayer with custom config", signer1, types.NewConfig(ibctesting.TestAccAddress), true}, + {"success: valid relayer with multiple relayers in config", signer1, types.NewConfig(signer2.String(), ibctesting.TestAccAddress), true}, + {"failure: invalid relayer with custom config", signer2, types.NewConfig(ibctesting.TestAccAddress, signer3.String()), false}, + } + + for _, tc := range testCases { + require.Equal(t, tc.expPass, tc.config.IsAllowedRelayer(tc.relayer), tc.name) + } +} + +func TestValidateConfig(t *testing.T) { + tooManyRelayers := make([]string, types.MaxAllowedRelayersLength+1) + for i := range tooManyRelayers { + tooManyRelayers[i] = ibctesting.TestAccAddress + } + testCases := []struct { + name string + config types.Config + expErr error + }{ + { + name: "default config", + config: types.DefaultConfig(), + expErr: nil, + }, + { + name: "custom config", + config: types.NewConfig(ibctesting.TestAccAddress), + expErr: nil, + }, + { + name: "multiple relayers", + config: types.NewConfig(ibctesting.TestAccAddress, signer2.String()), + expErr: nil, + }, + { + name: "too many allowed relayers", + config: types.NewConfig(tooManyRelayers...), + expErr: errors.New("allowed relayers length must not exceed 20 items"), + }, + { + name: "invalid relayer address", + config: types.NewConfig("invalidAddress"), + expErr: errors.New("invalid relayer address"), + }, + { + name: "invalid relayer address with valid ones", + config: types.NewConfig("invalidAddress", ibctesting.TestAccAddress), + expErr: errors.New("invalid relayer address"), + }, + } + + for _, tc := range testCases { + err := tc.config.Validate() + if tc.expErr == nil { + require.NoError(t, err, tc.name) + } else { + require.Error(t, err, tc.name) + ibctesting.RequireErrorIsOrContains(t, err, tc.expErr, err.Error()) + } + } +} diff --git a/modules/core/02-client/v2/types/counterparty.go b/modules/core/02-client/v2/types/counterparty.go new file mode 100644 index 0000000..1604e31 --- /dev/null +++ b/modules/core/02-client/v2/types/counterparty.go @@ -0,0 +1,9 @@ +package types + +// NewCounterpartyInfo creates a new counterparty info instance from merlePrefix and clientID +func NewCounterpartyInfo(merklePrefix [][]byte, clientID string) CounterpartyInfo { + return CounterpartyInfo{ + MerklePrefix: merklePrefix, + ClientId: clientID, + } +} diff --git a/modules/core/02-client/v2/types/counterparty.pb.go b/modules/core/02-client/v2/types/counterparty.pb.go new file mode 100644 index 0000000..2b7c7f3 --- /dev/null +++ b/modules/core/02-client/v2/types/counterparty.pb.go @@ -0,0 +1,378 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/client/v2/counterparty.proto + +package types + +import ( + fmt "fmt" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// CounterpartyInfo defines the key that the counterparty will use to message our client +type CounterpartyInfo struct { + // merkle prefix key is the prefix that ics provable keys are stored under + MerklePrefix [][]byte `protobuf:"bytes,1,rep,name=merkle_prefix,json=merklePrefix,proto3" json:"merkle_prefix,omitempty"` + // client identifier is the identifier used to send packet messages to our client + ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` +} + +func (m *CounterpartyInfo) Reset() { *m = CounterpartyInfo{} } +func (m *CounterpartyInfo) String() string { return proto.CompactTextString(m) } +func (*CounterpartyInfo) ProtoMessage() {} +func (*CounterpartyInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_bc4a81c3d2196cf1, []int{0} +} +func (m *CounterpartyInfo) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *CounterpartyInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_CounterpartyInfo.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *CounterpartyInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_CounterpartyInfo.Merge(m, src) +} +func (m *CounterpartyInfo) XXX_Size() int { + return m.Size() +} +func (m *CounterpartyInfo) XXX_DiscardUnknown() { + xxx_messageInfo_CounterpartyInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_CounterpartyInfo proto.InternalMessageInfo + +func (m *CounterpartyInfo) GetMerklePrefix() [][]byte { + if m != nil { + return m.MerklePrefix + } + return nil +} + +func (m *CounterpartyInfo) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +func init() { + proto.RegisterType((*CounterpartyInfo)(nil), "ibc.core.client.v2.CounterpartyInfo") +} + +func init() { + proto.RegisterFile("ibc/core/client/v2/counterparty.proto", fileDescriptor_bc4a81c3d2196cf1) +} + +var fileDescriptor_bc4a81c3d2196cf1 = []byte{ + // 222 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xcd, 0x4c, 0x4a, 0xd6, + 0x4f, 0xce, 0x2f, 0x4a, 0xd5, 0x4f, 0xce, 0xc9, 0x4c, 0xcd, 0x2b, 0xd1, 0x2f, 0x33, 0xd2, 0x4f, + 0xce, 0x2f, 0xcd, 0x2b, 0x49, 0x2d, 0x2a, 0x48, 0x2c, 0x2a, 0xa9, 0xd4, 0x2b, 0x28, 0xca, 0x2f, + 0xc9, 0x17, 0x12, 0xca, 0x4c, 0x4a, 0xd6, 0x03, 0x29, 0xd3, 0x83, 0x28, 0xd3, 0x2b, 0x33, 0x52, + 0x0a, 0xe1, 0x12, 0x70, 0x46, 0x52, 0xe9, 0x99, 0x97, 0x96, 0x2f, 0xa4, 0xcc, 0xc5, 0x9b, 0x9b, + 0x5a, 0x94, 0x9d, 0x93, 0x1a, 0x5f, 0x50, 0x94, 0x9a, 0x96, 0x59, 0x21, 0xc1, 0xa8, 0xc0, 0xac, + 0xc1, 0x13, 0xc4, 0x03, 0x11, 0x0c, 0x00, 0x8b, 0x09, 0x49, 0x73, 0x71, 0x42, 0x4c, 0x89, 0xcf, + 0x4c, 0x91, 0x60, 0x52, 0x60, 0xd4, 0xe0, 0x0c, 0xe2, 0x80, 0x08, 0x78, 0xa6, 0x38, 0x85, 0x9d, + 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x13, 0x1e, 0xcb, 0x31, + 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x43, 0x94, 0x4d, 0x7a, 0x66, 0x49, 0x46, 0x69, + 0x92, 0x5e, 0x72, 0x7e, 0xae, 0x7e, 0x72, 0x7e, 0x71, 0x6e, 0x7e, 0xb1, 0x7e, 0x66, 0x52, 0xb2, + 0x6e, 0x7a, 0xbe, 0x7e, 0x99, 0xa1, 0x81, 0x7e, 0x6e, 0x7e, 0x4a, 0x69, 0x4e, 0x6a, 0x31, 0xc4, + 0x2f, 0x06, 0x46, 0xba, 0x08, 0xef, 0x94, 0x54, 0x16, 0xa4, 0x16, 0x27, 0xb1, 0x81, 0x3d, 0x62, + 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x26, 0x53, 0xc5, 0x7f, 0xf1, 0x00, 0x00, 0x00, +} + +func (m *CounterpartyInfo) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CounterpartyInfo) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *CounterpartyInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintCounterparty(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0x12 + } + if len(m.MerklePrefix) > 0 { + for iNdEx := len(m.MerklePrefix) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.MerklePrefix[iNdEx]) + copy(dAtA[i:], m.MerklePrefix[iNdEx]) + i = encodeVarintCounterparty(dAtA, i, uint64(len(m.MerklePrefix[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintCounterparty(dAtA []byte, offset int, v uint64) int { + offset -= sovCounterparty(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *CounterpartyInfo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.MerklePrefix) > 0 { + for _, b := range m.MerklePrefix { + l = len(b) + n += 1 + l + sovCounterparty(uint64(l)) + } + } + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovCounterparty(uint64(l)) + } + return n +} + +func sovCounterparty(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozCounterparty(x uint64) (n int) { + return sovCounterparty(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *CounterpartyInfo) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCounterparty + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CounterpartyInfo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CounterpartyInfo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MerklePrefix", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCounterparty + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthCounterparty + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthCounterparty + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.MerklePrefix = append(m.MerklePrefix, make([]byte, postIndex-iNdEx)) + copy(m.MerklePrefix[len(m.MerklePrefix)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCounterparty + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCounterparty + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthCounterparty + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipCounterparty(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthCounterparty + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipCounterparty(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowCounterparty + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowCounterparty + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowCounterparty + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthCounterparty + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupCounterparty + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthCounterparty + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthCounterparty = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowCounterparty = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupCounterparty = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/02-client/v2/types/errors.go b/modules/core/02-client/v2/types/errors.go new file mode 100644 index 0000000..a72ea68 --- /dev/null +++ b/modules/core/02-client/v2/types/errors.go @@ -0,0 +1,8 @@ +package types + +import errorsmod "cosmossdk.io/errors" + +var ( + ErrInvalidCounterparty = errorsmod.Register(SubModuleName, 34, "invalid counterparty") + ErrCounterpartyNotFound = errorsmod.Register(SubModuleName, 35, "counterparty not found") +) diff --git a/modules/core/02-client/v2/types/genesis.go b/modules/core/02-client/v2/types/genesis.go new file mode 100644 index 0000000..1cc7fe9 --- /dev/null +++ b/modules/core/02-client/v2/types/genesis.go @@ -0,0 +1,36 @@ +package types + +import "errors" + +// DefaultGenesisState returns the ibc client submodule's default genesis state. +func DefaultGenesisState() GenesisState { + return GenesisState{ + CounterpartyInfos: []GenesisCounterpartyInfo{}, + } +} + +// Validate checks the CounterpartyInfos added to the genesis for validity. +func (gs GenesisState) Validate() error { + seenIDs := make(map[string]struct{}) + + for _, genInfo := range gs.CounterpartyInfos { + if len(genInfo.ClientId) == 0 { + return errors.New("counterparty client id cannot be empty") + } + + if genInfo.ClientId == genInfo.CounterpartyInfo.ClientId { + return errors.New("counterparty client id and client id cannot be the same") + } + + if len(genInfo.CounterpartyInfo.MerklePrefix) == 0 { + return errors.New("counterparty merkle prefix cannot be empty") + } + + if _, ok := seenIDs[genInfo.ClientId]; ok { + return errors.New("duplicate counterparty client id %s found") + } + seenIDs[genInfo.ClientId] = struct{}{} + } + + return nil +} diff --git a/modules/core/02-client/v2/types/genesis.pb.go b/modules/core/02-client/v2/types/genesis.pb.go new file mode 100644 index 0000000..4a11c31 --- /dev/null +++ b/modules/core/02-client/v2/types/genesis.pb.go @@ -0,0 +1,563 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/client/v2/genesis.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// GenesisCounterpartyInfo defines the state associating a client with a counterparty. +type GenesisCounterpartyInfo struct { + // ClientId is the ID of the given client. + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // CounterpartyInfo is the counterparty info of the given client. + CounterpartyInfo CounterpartyInfo `protobuf:"bytes,2,opt,name=counterparty_info,json=counterpartyInfo,proto3" json:"counterparty_info"` +} + +func (m *GenesisCounterpartyInfo) Reset() { *m = GenesisCounterpartyInfo{} } +func (m *GenesisCounterpartyInfo) String() string { return proto.CompactTextString(m) } +func (*GenesisCounterpartyInfo) ProtoMessage() {} +func (*GenesisCounterpartyInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_f50417e4e07077fc, []int{0} +} +func (m *GenesisCounterpartyInfo) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisCounterpartyInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisCounterpartyInfo.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisCounterpartyInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisCounterpartyInfo.Merge(m, src) +} +func (m *GenesisCounterpartyInfo) XXX_Size() int { + return m.Size() +} +func (m *GenesisCounterpartyInfo) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisCounterpartyInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisCounterpartyInfo proto.InternalMessageInfo + +func (m *GenesisCounterpartyInfo) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +func (m *GenesisCounterpartyInfo) GetCounterpartyInfo() CounterpartyInfo { + if m != nil { + return m.CounterpartyInfo + } + return CounterpartyInfo{} +} + +// GenesisState defines the ibc client v2 submodule's genesis state. +type GenesisState struct { + // counterparty info for each client + CounterpartyInfos []GenesisCounterpartyInfo `protobuf:"bytes,1,rep,name=counterparty_infos,json=counterpartyInfos,proto3" json:"counterparty_infos"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_f50417e4e07077fc, []int{1} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetCounterpartyInfos() []GenesisCounterpartyInfo { + if m != nil { + return m.CounterpartyInfos + } + return nil +} + +func init() { + proto.RegisterType((*GenesisCounterpartyInfo)(nil), "ibc.core.client.v2.GenesisCounterpartyInfo") + proto.RegisterType((*GenesisState)(nil), "ibc.core.client.v2.GenesisState") +} + +func init() { proto.RegisterFile("ibc/core/client/v2/genesis.proto", fileDescriptor_f50417e4e07077fc) } + +var fileDescriptor_f50417e4e07077fc = []byte{ + // 297 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xc8, 0x4c, 0x4a, 0xd6, + 0x4f, 0xce, 0x2f, 0x4a, 0xd5, 0x4f, 0xce, 0xc9, 0x4c, 0xcd, 0x2b, 0xd1, 0x2f, 0x33, 0xd2, 0x4f, + 0x4f, 0xcd, 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0xca, 0x4c, + 0x4a, 0xd6, 0x03, 0xa9, 0xd0, 0x83, 0xa8, 0xd0, 0x2b, 0x33, 0x92, 0x52, 0xc5, 0xa2, 0x2b, 0x39, + 0xbf, 0x34, 0xaf, 0x24, 0xb5, 0xa8, 0x20, 0xb1, 0xa8, 0xa4, 0x12, 0xa2, 0x55, 0x4a, 0x24, 0x3d, + 0x3f, 0x3d, 0x1f, 0xcc, 0xd4, 0x07, 0xb1, 0x20, 0xa2, 0x4a, 0xfd, 0x8c, 0x5c, 0xe2, 0xee, 0x10, + 0x2b, 0x9c, 0x91, 0xf4, 0x78, 0xe6, 0xa5, 0xe5, 0x0b, 0x49, 0x73, 0x71, 0x42, 0x4c, 0x8c, 0xcf, + 0x4c, 0x91, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0xe2, 0x80, 0x08, 0x78, 0xa6, 0x08, 0x85, 0x73, + 0x09, 0x22, 0x5b, 0x12, 0x9f, 0x99, 0x97, 0x96, 0x2f, 0xc1, 0xa4, 0xc0, 0xa8, 0xc1, 0x6d, 0xa4, + 0xa2, 0x87, 0xe9, 0x4a, 0x3d, 0x74, 0xd3, 0x9d, 0x58, 0x4e, 0xdc, 0x93, 0x67, 0x08, 0x12, 0x48, + 0x46, 0x13, 0x57, 0x2a, 0xe0, 0xe2, 0x81, 0x3a, 0x28, 0xb8, 0x24, 0xb1, 0x24, 0x55, 0x28, 0x81, + 0x4b, 0x08, 0xc3, 0xa2, 0x62, 0x09, 0x46, 0x05, 0x66, 0x0d, 0x6e, 0x23, 0x6d, 0x6c, 0x36, 0xe1, + 0xf0, 0x0e, 0xd4, 0x42, 0x41, 0x74, 0x0b, 0x8b, 0x9d, 0xc2, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, + 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, + 0xf1, 0x58, 0x8e, 0x21, 0xca, 0x26, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, + 0x3f, 0x39, 0xbf, 0x38, 0x37, 0xbf, 0x58, 0x3f, 0x33, 0x29, 0x59, 0x37, 0x3d, 0x5f, 0xbf, 0xcc, + 0xd0, 0x40, 0x3f, 0x37, 0x3f, 0xa5, 0x34, 0x27, 0xb5, 0x18, 0x12, 0xf6, 0x06, 0x46, 0xba, 0x88, + 0xe0, 0x2f, 0xa9, 0x2c, 0x48, 0x2d, 0x4e, 0x62, 0x03, 0x07, 0xb1, 0x31, 0x20, 0x00, 0x00, 0xff, + 0xff, 0x40, 0xf9, 0xe3, 0xa9, 0xd7, 0x01, 0x00, 0x00, +} + +func (m *GenesisCounterpartyInfo) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisCounterpartyInfo) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisCounterpartyInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.CounterpartyInfo.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *GenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.CounterpartyInfos) > 0 { + for iNdEx := len(m.CounterpartyInfos) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.CounterpartyInfos[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisCounterpartyInfo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + l = m.CounterpartyInfo.Size() + n += 1 + l + sovGenesis(uint64(l)) + return n +} + +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.CounterpartyInfos) > 0 { + for _, e := range m.CounterpartyInfos { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisCounterpartyInfo) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisCounterpartyInfo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisCounterpartyInfo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CounterpartyInfo", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.CounterpartyInfo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CounterpartyInfos", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CounterpartyInfos = append(m.CounterpartyInfos, GenesisCounterpartyInfo{}) + if err := m.CounterpartyInfos[len(m.CounterpartyInfos)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/02-client/v2/types/genesis_test.go b/modules/core/02-client/v2/types/genesis_test.go new file mode 100644 index 0000000..bf62177 --- /dev/null +++ b/modules/core/02-client/v2/types/genesis_test.go @@ -0,0 +1,108 @@ +package types_test + +import ( + "testing" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" +) + +func TestGenesisState_Validate(t *testing.T) { + tests := []struct { + name string + genState types.GenesisState + wantErr bool + }{ + { + name: "default genesis", + genState: types.DefaultGenesisState(), + wantErr: false, + }, + { + name: "valid genesis", + genState: types.GenesisState{ + CounterpartyInfos: []types.GenesisCounterpartyInfo{ + { + ClientId: "test-1", + CounterpartyInfo: types.NewCounterpartyInfo([][]byte{{0o1}}, "test-0"), + }, + { + ClientId: "test-0", + CounterpartyInfo: types.NewCounterpartyInfo([][]byte{{0o1}}, "test-1"), + }, + }, + }, + wantErr: false, + }, + { + name: "invalid - duplicate client IDs", + genState: types.GenesisState{ + CounterpartyInfos: []types.GenesisCounterpartyInfo{ + { + ClientId: "test-1", + CounterpartyInfo: types.NewCounterpartyInfo([][]byte{{0o1}}, "test-0"), + }, + { + ClientId: "test-1", + CounterpartyInfo: types.NewCounterpartyInfo([][]byte{{0o1}}, "test-0"), + }, + }, + }, + wantErr: true, + }, + { + name: "client has itself as counterparty info", + genState: types.GenesisState{ + CounterpartyInfos: []types.GenesisCounterpartyInfo{ + { + ClientId: "test-1", + CounterpartyInfo: types.NewCounterpartyInfo([][]byte{{0o1}}, "test-1"), + }, + { + ClientId: "test-0", + CounterpartyInfo: types.NewCounterpartyInfo([][]byte{{0o1}}, "test-1"), + }, + }, + }, + wantErr: true, + }, + { + name: "invalid - invalid client ID", + genState: types.GenesisState{ + CounterpartyInfos: []types.GenesisCounterpartyInfo{ + { + ClientId: "", + CounterpartyInfo: types.NewCounterpartyInfo([][]byte{{0o1}}, "test-0"), + }, + { + ClientId: "test-0", + CounterpartyInfo: types.NewCounterpartyInfo([][]byte{{0o1}}, "test-1"), + }, + }, + }, + wantErr: true, + }, + { + name: "invalid - invalid merkle prefix", + genState: types.GenesisState{ + CounterpartyInfos: []types.GenesisCounterpartyInfo{ + { + ClientId: "test-1", + CounterpartyInfo: types.NewCounterpartyInfo(nil, "test-0"), + }, + { + ClientId: "test-0", + CounterpartyInfo: types.NewCounterpartyInfo([][]byte{{0o1}}, "test-1"), + }, + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := tt.genState.Validate(); (err != nil) != tt.wantErr { + t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/modules/core/02-client/v2/types/keys.go b/modules/core/02-client/v2/types/keys.go new file mode 100644 index 0000000..4dc380b --- /dev/null +++ b/modules/core/02-client/v2/types/keys.go @@ -0,0 +1,21 @@ +package types + +const ( + // SubModuleName defines the IBC client name + SubModuleName string = "clientv2" + // KeyCounterparty is the key for the counterpartyInfo in the client-specific store + KeyCounterparty = "counterparty" + // KeyConfig is the key for the v2 configuration of the client + // NOTE: v1 params were global parameters, whereas this is a configuration per client + KeyConfig = "config" +) + +// CounterpartyKey returns the key under which the counterparty is stored in the client store +func CounterpartyKey() []byte { + return []byte(KeyCounterparty) +} + +// ConfigKey returns the key under which the v2 configuration are stored in the client store +func ConfigKey() []byte { + return []byte(KeyConfig) +} diff --git a/modules/core/02-client/v2/types/msgs.go b/modules/core/02-client/v2/types/msgs.go new file mode 100644 index 0000000..050f232 --- /dev/null +++ b/modules/core/02-client/v2/types/msgs.go @@ -0,0 +1,73 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +var ( + _ sdk.Msg = (*MsgRegisterCounterparty)(nil) + _ sdk.Msg = (*MsgUpdateClientConfig)(nil) + + _ sdk.HasValidateBasic = (*MsgRegisterCounterparty)(nil) + _ sdk.HasValidateBasic = (*MsgUpdateClientConfig)(nil) +) + +// NewMsgRegisterCounterparty creates a new instance of MsgRegisterCounterparty. +func NewMsgRegisterCounterparty(clientID string, merklePrefix [][]byte, counterpartyClientID string, signer string) *MsgRegisterCounterparty { + return &MsgRegisterCounterparty{ + ClientId: clientID, + CounterpartyMerklePrefix: merklePrefix, + CounterpartyClientId: counterpartyClientID, + Signer: signer, + } +} + +// ValidateBasic performs basic checks on a MsgRegisterCounterparty. +func (msg *MsgRegisterCounterparty) ValidateBasic() error { + if _, err := sdk.AccAddressFromBech32(msg.Signer); err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + if len(msg.CounterpartyMerklePrefix) == 0 { + return errorsmod.Wrap(ErrInvalidCounterparty, "counterparty messaging key cannot be empty") + } + if err := host.ClientIdentifierValidator(msg.ClientId); err != nil { + return err + } + if err := host.ClientIdentifierValidator(msg.CounterpartyClientId); err != nil { + return err + } + // This check must be done because the transfer v2 module assumes that the client IDs in the packet + // are in the format {clientID}-{sequence} + if !types.IsValidClientID(msg.ClientId) || !types.IsValidClientID(msg.CounterpartyClientId) { + return errorsmod.Wrapf(host.ErrInvalidID, "%s and %s must be in valid format: {string}-{number}", msg.ClientId, msg.CounterpartyClientId) + } + return nil +} + +func NewMsgUpdateClientConfig(clientID string, signer string, config Config) *MsgUpdateClientConfig { + return &MsgUpdateClientConfig{ + ClientId: clientID, + Signer: signer, + Config: config, + } +} + +// ValidateBasic performs basic validation of the MsgUpdateClientConfig fields. +func (msg *MsgUpdateClientConfig) ValidateBasic() error { + if _, err := sdk.AccAddressFromBech32(msg.Signer); err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + if err := host.ClientIdentifierValidator(msg.ClientId); err != nil { + return err + } + if !types.IsValidClientID(msg.ClientId) { + return errorsmod.Wrapf(host.ErrInvalidID, "client ID %s must be in valid format: {string}-{number}", msg.ClientId) + } + return msg.Config.Validate() +} diff --git a/modules/core/02-client/v2/types/msgs_test.go b/modules/core/02-client/v2/types/msgs_test.go new file mode 100644 index 0000000..85b739c --- /dev/null +++ b/modules/core/02-client/v2/types/msgs_test.go @@ -0,0 +1,229 @@ +package types_test + +import ( + fmt "fmt" + "testing" + + "github.com/stretchr/testify/require" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +var ( + signer1 = sdk.MustAccAddressFromBech32(ibctesting.TestAccAddress) + signer2 = sdk.AccAddress([]byte("signer2")) + signer3 = sdk.AccAddress([]byte("signer3")) +) + +func TestMsgRegisterCounterpartyValidateBasic(t *testing.T) { + signer := ibctesting.TestAccAddress + testCases := []struct { + name string + msg *types.MsgRegisterCounterparty + expError error + }{ + { + "success", + types.NewMsgRegisterCounterparty( + "testclientid-3", + [][]byte{[]byte("ibc"), []byte("channel-9")}, + "testclientid-2", + signer, + ), + nil, + }, + { + "failure: client id does not match clientID format", + types.NewMsgRegisterCounterparty( + "testclientid1", + [][]byte{[]byte("ibc"), []byte("channel-9")}, + "testclientid-3", + signer, + ), + host.ErrInvalidID, + }, + { + "failure: counterparty client id does not match clientID format", + types.NewMsgRegisterCounterparty( + "testclientid-1", + [][]byte{[]byte("ibc"), []byte("channel-9")}, + "testclientid2", + signer, + ), + host.ErrInvalidID, + }, + { + "failure: empty client id", + types.NewMsgRegisterCounterparty( + "", + [][]byte{[]byte("ibc"), []byte("channel-9")}, + "testclientid-3", + signer, + ), + host.ErrInvalidID, + }, + { + "failure: empty counterparty client id", + types.NewMsgRegisterCounterparty( + "testclientid-1", + [][]byte{[]byte("ibc"), []byte("channel-9")}, + "", + signer, + ), + host.ErrInvalidID, + }, + { + "failure: empty counterparty messaging key", + types.NewMsgRegisterCounterparty( + "testclientid-1", + [][]byte{}, + "testclientid-3", + signer, + ), + types.ErrInvalidCounterparty, + }, + { + "failure: empty signer", + types.NewMsgRegisterCounterparty( + "testclientid-2", + [][]byte{[]byte("ibc"), []byte("channel-9")}, + "testclientid-3", + "badsigner", + ), + ibcerrors.ErrInvalidAddress, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + if tc.expError == nil { + require.NoError(t, err) + } else { + require.ErrorIs(t, err, tc.expError) + } + }) + } +} + +func TestMsgUpdateClientConfigValidateBasic(t *testing.T) { + tooManyRelayers := make([]string, types.MaxAllowedRelayersLength+1) + for i := range tooManyRelayers { + tooManyRelayers[i] = ibctesting.TestAccAddress + } + signer := ibctesting.TestAccAddress + testCases := []struct { + name string + msg *types.MsgUpdateClientConfig + expError error + }{ + { + "success", + types.NewMsgUpdateClientConfig( + "testclientid-2", + signer, + types.NewConfig(ibctesting.TestAccAddress), + ), + nil, + }, + { + "success with multiple relayers", + types.NewMsgUpdateClientConfig( + "testclientid-2", + signer, + types.NewConfig(ibctesting.TestAccAddress, signer2.String(), signer3.String()), + ), + nil, + }, + { + "success with no relayers", + types.NewMsgUpdateClientConfig( + "testclientid-2", + signer, + types.DefaultConfig(), + ), + nil, + }, + { + "failure: client id does not match clientID format", + types.NewMsgUpdateClientConfig( + "testclientid1", + signer, + types.NewConfig(ibctesting.TestAccAddress), + ), + errorsmod.Wrapf(host.ErrInvalidID, "client ID %s must be in valid format: {string}-{number}", "testclientid1"), + }, + { + "failure: empty client id", + types.NewMsgUpdateClientConfig( + "", + signer, + types.NewConfig(ibctesting.TestAccAddress), + ), + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank"), + }, + { + "failure: empty signer", + types.NewMsgUpdateClientConfig( + "testclientid-2", + "", + types.NewConfig(ibctesting.TestAccAddress), + ), + errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: empty address string is not allowed"), + }, + { + "failure: invalid signer", + types.NewMsgUpdateClientConfig( + "testclientid-2", + "badsigner", + types.NewConfig(ibctesting.TestAccAddress), + ), + errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: decoding bech32 failed: invalid separator index -1"), + }, + { + "failure: invalid allowed relayer address", + types.NewMsgUpdateClientConfig( + "testclientid-2", + signer, + types.NewConfig("invalidAddress"), + ), + fmt.Errorf("invalid relayer address: %s", "invalidAddress"), + }, + { + "failure: invalid allowed relayer address with valid ones", + types.NewMsgUpdateClientConfig( + "testclientid-2", + signer, + types.NewConfig("invalidAddress", ibctesting.TestAccAddress), + ), + fmt.Errorf("invalid relayer address: %s", "invalidAddress"), + }, + { + "failure: too many allowed relayers", + types.NewMsgUpdateClientConfig( + "testclientid-2", + signer, + types.NewConfig(tooManyRelayers...), + ), + fmt.Errorf("allowed relayers length must not exceed %d items", types.MaxAllowedRelayersLength), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + if tc.expError == nil { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Equal(t, tc.expError.Error(), err.Error()) + } + }) + } +} diff --git a/modules/core/02-client/v2/types/query.pb.go b/modules/core/02-client/v2/types/query.pb.go new file mode 100644 index 0000000..5ef7563 --- /dev/null +++ b/modules/core/02-client/v2/types/query.pb.go @@ -0,0 +1,991 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/client/v2/query.proto + +package types + +import ( + context "context" + fmt "fmt" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// QueryCounterpartyInfoRequest is the request type for the Query/CounterpartyInfo RPC +// method +type QueryCounterpartyInfoRequest struct { + // client state unique identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` +} + +func (m *QueryCounterpartyInfoRequest) Reset() { *m = QueryCounterpartyInfoRequest{} } +func (m *QueryCounterpartyInfoRequest) String() string { return proto.CompactTextString(m) } +func (*QueryCounterpartyInfoRequest) ProtoMessage() {} +func (*QueryCounterpartyInfoRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ce2f0dd58f99aaa6, []int{0} +} +func (m *QueryCounterpartyInfoRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryCounterpartyInfoRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryCounterpartyInfoRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryCounterpartyInfoRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryCounterpartyInfoRequest.Merge(m, src) +} +func (m *QueryCounterpartyInfoRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryCounterpartyInfoRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryCounterpartyInfoRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryCounterpartyInfoRequest proto.InternalMessageInfo + +func (m *QueryCounterpartyInfoRequest) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +// QueryCounterpartyInfoResponse is the response type for the +// Query/CounterpartyInfo RPC method. +type QueryCounterpartyInfoResponse struct { + CounterpartyInfo *CounterpartyInfo `protobuf:"bytes,1,opt,name=counterparty_info,json=counterpartyInfo,proto3" json:"counterparty_info,omitempty"` +} + +func (m *QueryCounterpartyInfoResponse) Reset() { *m = QueryCounterpartyInfoResponse{} } +func (m *QueryCounterpartyInfoResponse) String() string { return proto.CompactTextString(m) } +func (*QueryCounterpartyInfoResponse) ProtoMessage() {} +func (*QueryCounterpartyInfoResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ce2f0dd58f99aaa6, []int{1} +} +func (m *QueryCounterpartyInfoResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryCounterpartyInfoResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryCounterpartyInfoResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryCounterpartyInfoResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryCounterpartyInfoResponse.Merge(m, src) +} +func (m *QueryCounterpartyInfoResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryCounterpartyInfoResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryCounterpartyInfoResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryCounterpartyInfoResponse proto.InternalMessageInfo + +func (m *QueryCounterpartyInfoResponse) GetCounterpartyInfo() *CounterpartyInfo { + if m != nil { + return m.CounterpartyInfo + } + return nil +} + +// QueryConfigRequest is the request type for the Query/Config RPC method +type QueryConfigRequest struct { + // client state unique identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` +} + +func (m *QueryConfigRequest) Reset() { *m = QueryConfigRequest{} } +func (m *QueryConfigRequest) String() string { return proto.CompactTextString(m) } +func (*QueryConfigRequest) ProtoMessage() {} +func (*QueryConfigRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_ce2f0dd58f99aaa6, []int{2} +} +func (m *QueryConfigRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryConfigRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryConfigRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryConfigRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryConfigRequest.Merge(m, src) +} +func (m *QueryConfigRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryConfigRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryConfigRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryConfigRequest proto.InternalMessageInfo + +func (m *QueryConfigRequest) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +// QueryConfigResponse is the response type for the Query/Config RPC method +type QueryConfigResponse struct { + Config *Config `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` +} + +func (m *QueryConfigResponse) Reset() { *m = QueryConfigResponse{} } +func (m *QueryConfigResponse) String() string { return proto.CompactTextString(m) } +func (*QueryConfigResponse) ProtoMessage() {} +func (*QueryConfigResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ce2f0dd58f99aaa6, []int{3} +} +func (m *QueryConfigResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryConfigResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryConfigResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryConfigResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryConfigResponse.Merge(m, src) +} +func (m *QueryConfigResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryConfigResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryConfigResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryConfigResponse proto.InternalMessageInfo + +func (m *QueryConfigResponse) GetConfig() *Config { + if m != nil { + return m.Config + } + return nil +} + +func init() { + proto.RegisterType((*QueryCounterpartyInfoRequest)(nil), "ibc.core.client.v2.QueryCounterpartyInfoRequest") + proto.RegisterType((*QueryCounterpartyInfoResponse)(nil), "ibc.core.client.v2.QueryCounterpartyInfoResponse") + proto.RegisterType((*QueryConfigRequest)(nil), "ibc.core.client.v2.QueryConfigRequest") + proto.RegisterType((*QueryConfigResponse)(nil), "ibc.core.client.v2.QueryConfigResponse") +} + +func init() { proto.RegisterFile("ibc/core/client/v2/query.proto", fileDescriptor_ce2f0dd58f99aaa6) } + +var fileDescriptor_ce2f0dd58f99aaa6 = []byte{ + // 396 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x92, 0x31, 0xaf, 0x12, 0x41, + 0x10, 0xc7, 0x39, 0x12, 0x89, 0xac, 0x0d, 0xae, 0x8d, 0x39, 0xf1, 0x34, 0x17, 0x45, 0x1a, 0x76, + 0xb9, 0xa3, 0x32, 0x5a, 0x69, 0x45, 0x09, 0x85, 0x85, 0x0d, 0xb9, 0x5b, 0x96, 0x73, 0x13, 0xd8, + 0x39, 0x6e, 0xf7, 0x48, 0x88, 0xb1, 0xb1, 0xb3, 0x33, 0xf1, 0x9b, 0xf8, 0x29, 0x28, 0x49, 0x6c, + 0x2c, 0x0d, 0xf8, 0x41, 0x5e, 0xee, 0xf6, 0xf2, 0xde, 0x3d, 0x38, 0xe0, 0xbd, 0x76, 0x66, 0xfe, + 0xff, 0xf9, 0xcd, 0x3f, 0x83, 0x1c, 0x11, 0x32, 0xca, 0x20, 0xe1, 0x94, 0xcd, 0x05, 0x97, 0x9a, + 0xae, 0x7c, 0xba, 0x4c, 0x79, 0xb2, 0x26, 0x71, 0x02, 0x1a, 0x30, 0x16, 0x21, 0x23, 0x59, 0x9f, + 0x98, 0x3e, 0x59, 0xf9, 0xf6, 0xeb, 0x0a, 0x0d, 0x83, 0x54, 0x6a, 0x9e, 0xc4, 0x41, 0xa2, 0x0b, + 0xa9, 0xfd, 0xa2, 0x72, 0x4c, 0xce, 0x44, 0x54, 0x0c, 0xb4, 0x23, 0x80, 0x68, 0xce, 0x69, 0x10, + 0x0b, 0x1a, 0x48, 0x09, 0x3a, 0xd0, 0x02, 0xa4, 0x32, 0x5d, 0xf7, 0x1d, 0x6a, 0x8f, 0x32, 0x90, + 0x8f, 0x25, 0xe7, 0xa1, 0x9c, 0xc1, 0x98, 0x2f, 0x53, 0xae, 0x34, 0x7e, 0x86, 0x9a, 0xc6, 0x77, + 0x22, 0xa6, 0x4f, 0xad, 0x97, 0x56, 0xb7, 0x39, 0x7e, 0x68, 0x0a, 0xc3, 0xa9, 0x9b, 0xa0, 0xe7, + 0x27, 0xc4, 0x2a, 0x06, 0xa9, 0x38, 0x1e, 0xa1, 0xc7, 0x65, 0xe4, 0x89, 0x90, 0x33, 0xc8, 0x5d, + 0x1e, 0xf9, 0xaf, 0xc8, 0xf1, 0xcd, 0xe4, 0xc8, 0xa8, 0xc5, 0x0e, 0x2a, 0xae, 0x87, 0x70, 0xb1, + 0x33, 0xbb, 0xf1, 0x4e, 0x98, 0x43, 0xf4, 0xe4, 0x96, 0xa4, 0x80, 0xf3, 0x51, 0xc3, 0x04, 0x55, + 0x10, 0xd9, 0xd5, 0x44, 0xb9, 0xa6, 0x98, 0xf4, 0x37, 0x75, 0xf4, 0x20, 0xf7, 0xc2, 0xbf, 0x2d, + 0xd4, 0x3a, 0xc4, 0xc5, 0xfd, 0x2a, 0x8b, 0x73, 0xf9, 0xda, 0xde, 0x3d, 0x14, 0x86, 0xdb, 0x7d, + 0xfb, 0xfd, 0xcf, 0xff, 0x5f, 0xf5, 0x01, 0xf6, 0xe8, 0x85, 0x0f, 0xc9, 0xe3, 0xa6, 0x5f, 0xaf, + 0x83, 0xf9, 0x86, 0x7f, 0x58, 0xa8, 0x61, 0x2e, 0xc2, 0x9d, 0x33, 0x8b, 0x4b, 0xc9, 0xda, 0x6f, + 0x2e, 0xce, 0x15, 0x58, 0x24, 0xc7, 0xea, 0xe2, 0x0e, 0x3d, 0xf9, 0x91, 0x65, 0x96, 0x0f, 0x9f, + 0x36, 0x3b, 0xc7, 0xda, 0xee, 0x1c, 0xeb, 0xdf, 0xce, 0xb1, 0x7e, 0xee, 0x9d, 0xda, 0x76, 0xef, + 0xd4, 0xfe, 0xee, 0x9d, 0xda, 0xe7, 0xf7, 0x91, 0xd0, 0x5f, 0xd2, 0x90, 0x30, 0x58, 0x50, 0x06, + 0x6a, 0x01, 0x2a, 0xb3, 0xec, 0x45, 0x40, 0x57, 0x5e, 0x9f, 0x2e, 0x60, 0x9a, 0xce, 0xb9, 0x32, + 0x1b, 0xfa, 0x7e, 0xef, 0x66, 0x89, 0x5e, 0xc7, 0x5c, 0x85, 0x8d, 0xfc, 0xb1, 0x07, 0x57, 0x01, + 0x00, 0x00, 0xff, 0xff, 0xc6, 0x3b, 0x7a, 0x22, 0x74, 0x03, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // CounterpartyInfo queries an IBC light counter party info. + CounterpartyInfo(ctx context.Context, in *QueryCounterpartyInfoRequest, opts ...grpc.CallOption) (*QueryCounterpartyInfoResponse, error) + // Config queries the IBC client v2 configuration for a given client. + Config(ctx context.Context, in *QueryConfigRequest, opts ...grpc.CallOption) (*QueryConfigResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) CounterpartyInfo(ctx context.Context, in *QueryCounterpartyInfoRequest, opts ...grpc.CallOption) (*QueryCounterpartyInfoResponse, error) { + out := new(QueryCounterpartyInfoResponse) + err := c.cc.Invoke(ctx, "/ibc.core.client.v2.Query/CounterpartyInfo", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) Config(ctx context.Context, in *QueryConfigRequest, opts ...grpc.CallOption) (*QueryConfigResponse, error) { + out := new(QueryConfigResponse) + err := c.cc.Invoke(ctx, "/ibc.core.client.v2.Query/Config", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // CounterpartyInfo queries an IBC light counter party info. + CounterpartyInfo(context.Context, *QueryCounterpartyInfoRequest) (*QueryCounterpartyInfoResponse, error) + // Config queries the IBC client v2 configuration for a given client. + Config(context.Context, *QueryConfigRequest) (*QueryConfigResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) CounterpartyInfo(ctx context.Context, req *QueryCounterpartyInfoRequest) (*QueryCounterpartyInfoResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CounterpartyInfo not implemented") +} +func (*UnimplementedQueryServer) Config(ctx context.Context, req *QueryConfigRequest) (*QueryConfigResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Config not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_CounterpartyInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryCounterpartyInfoRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).CounterpartyInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.client.v2.Query/CounterpartyInfo", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).CounterpartyInfo(ctx, req.(*QueryCounterpartyInfoRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_Config_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Config(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.client.v2.Query/Config", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Config(ctx, req.(*QueryConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ibc.core.client.v2.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CounterpartyInfo", + Handler: _Query_CounterpartyInfo_Handler, + }, + { + MethodName: "Config", + Handler: _Query_Config_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "ibc/core/client/v2/query.proto", +} + +func (m *QueryCounterpartyInfoRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryCounterpartyInfoRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryCounterpartyInfoRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryCounterpartyInfoResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryCounterpartyInfoResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryCounterpartyInfoResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.CounterpartyInfo != nil { + { + size, err := m.CounterpartyInfo.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryConfigRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryConfigRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryConfigRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryConfigResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryConfigResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryConfigResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Config != nil { + { + size, err := m.Config.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryCounterpartyInfoRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryCounterpartyInfoResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.CounterpartyInfo != nil { + l = m.CounterpartyInfo.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryConfigRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryConfigResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Config != nil { + l = m.Config.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryCounterpartyInfoRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryCounterpartyInfoRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryCounterpartyInfoRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryCounterpartyInfoResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryCounterpartyInfoResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryCounterpartyInfoResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CounterpartyInfo", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.CounterpartyInfo == nil { + m.CounterpartyInfo = &CounterpartyInfo{} + } + if err := m.CounterpartyInfo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryConfigRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryConfigRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryConfigRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryConfigResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryConfigResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryConfigResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Config", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Config == nil { + m.Config = &Config{} + } + if err := m.Config.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/02-client/v2/types/query.pb.gw.go b/modules/core/02-client/v2/types/query.pb.gw.go new file mode 100644 index 0000000..ba7c24e --- /dev/null +++ b/modules/core/02-client/v2/types/query.pb.gw.go @@ -0,0 +1,290 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: ibc/core/client/v2/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +func request_Query_CounterpartyInfo_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryCounterpartyInfoRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + msg, err := client.CounterpartyInfo(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_CounterpartyInfo_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryCounterpartyInfoRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + msg, err := server.CounterpartyInfo(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_Config_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryConfigRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + msg, err := client.Config(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Config_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryConfigRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + msg, err := server.Config(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_CounterpartyInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_CounterpartyInfo_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_CounterpartyInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Config_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Config_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Config_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_CounterpartyInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_CounterpartyInfo_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_CounterpartyInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Config_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Config_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Config_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_CounterpartyInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"ibc", "core", "client", "v2", "counterparty_info", "client_id"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_Config_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"ibc", "core", "client", "v2", "config", "client_id"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Query_CounterpartyInfo_0 = runtime.ForwardResponseMessage + + forward_Query_Config_0 = runtime.ForwardResponseMessage +) diff --git a/modules/core/02-client/v2/types/tx.pb.go b/modules/core/02-client/v2/types/tx.pb.go new file mode 100644 index 0000000..69a0437 --- /dev/null +++ b/modules/core/02-client/v2/types/tx.pb.go @@ -0,0 +1,1093 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/client/v2/tx.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/cosmos/cosmos-sdk/types/msgservice" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// MsgRegisterCounterparty defines a message to register a counterparty on a client +type MsgRegisterCounterparty struct { + // client identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // counterparty merkle prefix + CounterpartyMerklePrefix [][]byte `protobuf:"bytes,2,rep,name=counterparty_merkle_prefix,json=counterpartyMerklePrefix,proto3" json:"counterparty_merkle_prefix,omitempty"` + // counterparty client identifier + CounterpartyClientId string `protobuf:"bytes,3,opt,name=counterparty_client_id,json=counterpartyClientId,proto3" json:"counterparty_client_id,omitempty"` + // signer address + Signer string `protobuf:"bytes,4,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgRegisterCounterparty) Reset() { *m = MsgRegisterCounterparty{} } +func (m *MsgRegisterCounterparty) String() string { return proto.CompactTextString(m) } +func (*MsgRegisterCounterparty) ProtoMessage() {} +func (*MsgRegisterCounterparty) Descriptor() ([]byte, []int) { + return fileDescriptor_f63146ac703bba45, []int{0} +} +func (m *MsgRegisterCounterparty) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgRegisterCounterparty) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgRegisterCounterparty.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgRegisterCounterparty) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgRegisterCounterparty.Merge(m, src) +} +func (m *MsgRegisterCounterparty) XXX_Size() int { + return m.Size() +} +func (m *MsgRegisterCounterparty) XXX_DiscardUnknown() { + xxx_messageInfo_MsgRegisterCounterparty.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgRegisterCounterparty proto.InternalMessageInfo + +// MsgRegisterCounterpartyResponse defines the Msg/RegisterCounterparty response type. +type MsgRegisterCounterpartyResponse struct { +} + +func (m *MsgRegisterCounterpartyResponse) Reset() { *m = MsgRegisterCounterpartyResponse{} } +func (m *MsgRegisterCounterpartyResponse) String() string { return proto.CompactTextString(m) } +func (*MsgRegisterCounterpartyResponse) ProtoMessage() {} +func (*MsgRegisterCounterpartyResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_f63146ac703bba45, []int{1} +} +func (m *MsgRegisterCounterpartyResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgRegisterCounterpartyResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgRegisterCounterpartyResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgRegisterCounterpartyResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgRegisterCounterpartyResponse.Merge(m, src) +} +func (m *MsgRegisterCounterpartyResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgRegisterCounterpartyResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgRegisterCounterpartyResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgRegisterCounterpartyResponse proto.InternalMessageInfo + +// MsgUpdateClientConfig defines the sdk.Msg type to update the configuration for a given client +type MsgUpdateClientConfig struct { + // client identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // allowed relayers + // + // NOTE: All fields in the config must be supplied. + Config Config `protobuf:"bytes,2,opt,name=config,proto3" json:"config"` + // signer address + Signer string `protobuf:"bytes,3,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgUpdateClientConfig) Reset() { *m = MsgUpdateClientConfig{} } +func (m *MsgUpdateClientConfig) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateClientConfig) ProtoMessage() {} +func (*MsgUpdateClientConfig) Descriptor() ([]byte, []int) { + return fileDescriptor_f63146ac703bba45, []int{2} +} +func (m *MsgUpdateClientConfig) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateClientConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateClientConfig.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateClientConfig) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateClientConfig.Merge(m, src) +} +func (m *MsgUpdateClientConfig) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateClientConfig) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateClientConfig.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateClientConfig proto.InternalMessageInfo + +// MsgUpdateClientConfigResponse defines the MsgUpdateClientConfig response type. +type MsgUpdateClientConfigResponse struct { +} + +func (m *MsgUpdateClientConfigResponse) Reset() { *m = MsgUpdateClientConfigResponse{} } +func (m *MsgUpdateClientConfigResponse) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateClientConfigResponse) ProtoMessage() {} +func (*MsgUpdateClientConfigResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_f63146ac703bba45, []int{3} +} +func (m *MsgUpdateClientConfigResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateClientConfigResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateClientConfigResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateClientConfigResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateClientConfigResponse.Merge(m, src) +} +func (m *MsgUpdateClientConfigResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateClientConfigResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateClientConfigResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateClientConfigResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*MsgRegisterCounterparty)(nil), "ibc.core.client.v2.MsgRegisterCounterparty") + proto.RegisterType((*MsgRegisterCounterpartyResponse)(nil), "ibc.core.client.v2.MsgRegisterCounterpartyResponse") + proto.RegisterType((*MsgUpdateClientConfig)(nil), "ibc.core.client.v2.MsgUpdateClientConfig") + proto.RegisterType((*MsgUpdateClientConfigResponse)(nil), "ibc.core.client.v2.MsgUpdateClientConfigResponse") +} + +func init() { proto.RegisterFile("ibc/core/client/v2/tx.proto", fileDescriptor_f63146ac703bba45) } + +var fileDescriptor_f63146ac703bba45 = []byte{ + // 450 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x93, 0x31, 0x6f, 0xd3, 0x40, + 0x14, 0xc7, 0x7d, 0x4d, 0x89, 0xe8, 0x81, 0x84, 0x74, 0x0a, 0xad, 0xe5, 0x0a, 0x3b, 0x64, 0x0a, + 0x45, 0xf5, 0x35, 0x2e, 0x03, 0x42, 0x9d, 0x9a, 0x89, 0xc1, 0x12, 0xb2, 0x04, 0x03, 0x4b, 0x54, + 0x9f, 0xaf, 0xc7, 0x89, 0xd8, 0x67, 0xdd, 0x5d, 0xac, 0x74, 0x43, 0x4c, 0x8c, 0x6c, 0xac, 0x7c, + 0x84, 0x7e, 0x8c, 0x8e, 0x95, 0x58, 0x98, 0x10, 0x4a, 0x86, 0x7e, 0x07, 0x26, 0x14, 0x9f, 0x2d, + 0x8c, 0xea, 0x44, 0x30, 0xf9, 0xfc, 0xde, 0xef, 0xbd, 0xff, 0xff, 0xde, 0xe9, 0xc1, 0x7d, 0x1e, + 0x13, 0x4c, 0x84, 0xa4, 0x98, 0x4c, 0x39, 0xcd, 0x34, 0x2e, 0x02, 0xac, 0xe7, 0x7e, 0x2e, 0x85, + 0x16, 0x08, 0xf1, 0x98, 0xf8, 0xab, 0xa4, 0x6f, 0x92, 0x7e, 0x11, 0x38, 0x7b, 0x44, 0xa8, 0x54, + 0x28, 0x9c, 0x2a, 0x86, 0x8b, 0xd1, 0xea, 0x63, 0x60, 0xa7, 0xc7, 0x04, 0x13, 0xe5, 0x11, 0xaf, + 0x4e, 0x55, 0xd4, 0x6b, 0xe9, 0x4f, 0x44, 0x76, 0xce, 0xab, 0xb2, 0xc1, 0x37, 0x00, 0xf7, 0x42, + 0xc5, 0x22, 0xca, 0xb8, 0xd2, 0x54, 0x8e, 0xc5, 0x2c, 0xd3, 0x54, 0xe6, 0x67, 0x52, 0x5f, 0xa0, + 0x7d, 0xb8, 0x63, 0xaa, 0x26, 0x3c, 0xb1, 0x41, 0x1f, 0x0c, 0x77, 0xa2, 0xbb, 0x26, 0xf0, 0x32, + 0x41, 0x27, 0xd0, 0x21, 0x0d, 0x78, 0x92, 0x52, 0xf9, 0x7e, 0x4a, 0x27, 0xb9, 0xa4, 0xe7, 0x7c, + 0x6e, 0x6f, 0xf5, 0x3b, 0xc3, 0xfb, 0x91, 0xdd, 0x24, 0xc2, 0x12, 0x78, 0x55, 0xe6, 0xd1, 0x33, + 0xb8, 0xfb, 0x57, 0xf5, 0x1f, 0x9d, 0x4e, 0xa9, 0xd3, 0x6b, 0x66, 0xc7, 0xb5, 0xe6, 0x2e, 0xec, + 0x2a, 0xce, 0x32, 0x2a, 0xed, 0xed, 0x92, 0xaa, 0xfe, 0x5e, 0x3c, 0xf8, 0xf4, 0xd5, 0xb3, 0x3e, + 0xde, 0x5c, 0x1e, 0x54, 0x81, 0xc1, 0x63, 0xe8, 0xad, 0xb9, 0x54, 0x44, 0x55, 0x2e, 0x32, 0x45, + 0x07, 0x5f, 0x00, 0x7c, 0x18, 0x2a, 0xf6, 0x3a, 0x4f, 0xce, 0x34, 0x35, 0x0a, 0xe3, 0x72, 0x30, + 0x9b, 0xaf, 0xfd, 0x1c, 0x76, 0xcd, 0xfc, 0xec, 0xad, 0x3e, 0x18, 0xde, 0x0b, 0x1c, 0xff, 0xf6, + 0x23, 0xf9, 0xa6, 0xd1, 0xe9, 0xf6, 0xd5, 0x0f, 0xcf, 0x8a, 0x2a, 0xbe, 0x61, 0xbe, 0xb3, 0xd9, + 0xbc, 0x07, 0x1f, 0xb5, 0x1a, 0xab, 0xad, 0x07, 0xbf, 0x00, 0xec, 0x84, 0x8a, 0xa1, 0x39, 0xec, + 0xb5, 0xbe, 0xdb, 0xd3, 0x36, 0x4f, 0x6b, 0xe6, 0xe1, 0x1c, 0xff, 0x07, 0x5c, 0x3b, 0x40, 0x12, + 0xa2, 0x96, 0xc1, 0x3d, 0x59, 0xd3, 0xea, 0x36, 0xea, 0x8c, 0xfe, 0x19, 0xad, 0x35, 0x9d, 0x3b, + 0x1f, 0x6e, 0x2e, 0x0f, 0xc0, 0xe9, 0x9b, 0xab, 0x85, 0x0b, 0xae, 0x17, 0x2e, 0xf8, 0xb9, 0x70, + 0xc1, 0xe7, 0xa5, 0x6b, 0x5d, 0x2f, 0x5d, 0xeb, 0xfb, 0xd2, 0xb5, 0xde, 0x9e, 0x30, 0xae, 0xdf, + 0xcd, 0x62, 0x9f, 0x88, 0x14, 0x57, 0x5b, 0xc2, 0x63, 0x72, 0xc8, 0x04, 0x2e, 0x46, 0x47, 0x38, + 0x15, 0xc9, 0x6c, 0x4a, 0x95, 0x59, 0x86, 0xa3, 0xe0, 0xb0, 0xb1, 0x6f, 0x17, 0x39, 0x55, 0x71, + 0xb7, 0xdc, 0x87, 0xe3, 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x92, 0x2f, 0xac, 0x90, 0x92, 0x03, + 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MsgClient is the client API for Msg service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MsgClient interface { + // RegisterCounterparty defines a rpc handler method for MsgRegisterCounterparty. + RegisterCounterparty(ctx context.Context, in *MsgRegisterCounterparty, opts ...grpc.CallOption) (*MsgRegisterCounterpartyResponse, error) + // UpdateClientConfig defines a rpc handler method for MsgUpdateClientConfig. + UpdateClientConfig(ctx context.Context, in *MsgUpdateClientConfig, opts ...grpc.CallOption) (*MsgUpdateClientConfigResponse, error) +} + +type msgClient struct { + cc grpc1.ClientConn +} + +func NewMsgClient(cc grpc1.ClientConn) MsgClient { + return &msgClient{cc} +} + +func (c *msgClient) RegisterCounterparty(ctx context.Context, in *MsgRegisterCounterparty, opts ...grpc.CallOption) (*MsgRegisterCounterpartyResponse, error) { + out := new(MsgRegisterCounterpartyResponse) + err := c.cc.Invoke(ctx, "/ibc.core.client.v2.Msg/RegisterCounterparty", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) UpdateClientConfig(ctx context.Context, in *MsgUpdateClientConfig, opts ...grpc.CallOption) (*MsgUpdateClientConfigResponse, error) { + out := new(MsgUpdateClientConfigResponse) + err := c.cc.Invoke(ctx, "/ibc.core.client.v2.Msg/UpdateClientConfig", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MsgServer is the server API for Msg service. +type MsgServer interface { + // RegisterCounterparty defines a rpc handler method for MsgRegisterCounterparty. + RegisterCounterparty(context.Context, *MsgRegisterCounterparty) (*MsgRegisterCounterpartyResponse, error) + // UpdateClientConfig defines a rpc handler method for MsgUpdateClientConfig. + UpdateClientConfig(context.Context, *MsgUpdateClientConfig) (*MsgUpdateClientConfigResponse, error) +} + +// UnimplementedMsgServer can be embedded to have forward compatible implementations. +type UnimplementedMsgServer struct { +} + +func (*UnimplementedMsgServer) RegisterCounterparty(ctx context.Context, req *MsgRegisterCounterparty) (*MsgRegisterCounterpartyResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RegisterCounterparty not implemented") +} +func (*UnimplementedMsgServer) UpdateClientConfig(ctx context.Context, req *MsgUpdateClientConfig) (*MsgUpdateClientConfigResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateClientConfig not implemented") +} + +func RegisterMsgServer(s grpc1.Server, srv MsgServer) { + s.RegisterService(&_Msg_serviceDesc, srv) +} + +func _Msg_RegisterCounterparty_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgRegisterCounterparty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).RegisterCounterparty(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.client.v2.Msg/RegisterCounterparty", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).RegisterCounterparty(ctx, req.(*MsgRegisterCounterparty)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_UpdateClientConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUpdateClientConfig) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).UpdateClientConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.client.v2.Msg/UpdateClientConfig", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).UpdateClientConfig(ctx, req.(*MsgUpdateClientConfig)) + } + return interceptor(ctx, in, info, handler) +} + +var _Msg_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ibc.core.client.v2.Msg", + HandlerType: (*MsgServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "RegisterCounterparty", + Handler: _Msg_RegisterCounterparty_Handler, + }, + { + MethodName: "UpdateClientConfig", + Handler: _Msg_UpdateClientConfig_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "ibc/core/client/v2/tx.proto", +} + +func (m *MsgRegisterCounterparty) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgRegisterCounterparty) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgRegisterCounterparty) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x22 + } + if len(m.CounterpartyClientId) > 0 { + i -= len(m.CounterpartyClientId) + copy(dAtA[i:], m.CounterpartyClientId) + i = encodeVarintTx(dAtA, i, uint64(len(m.CounterpartyClientId))) + i-- + dAtA[i] = 0x1a + } + if len(m.CounterpartyMerklePrefix) > 0 { + for iNdEx := len(m.CounterpartyMerklePrefix) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.CounterpartyMerklePrefix[iNdEx]) + copy(dAtA[i:], m.CounterpartyMerklePrefix[iNdEx]) + i = encodeVarintTx(dAtA, i, uint64(len(m.CounterpartyMerklePrefix[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgRegisterCounterpartyResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgRegisterCounterpartyResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgRegisterCounterpartyResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgUpdateClientConfig) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateClientConfig) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateClientConfig) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x1a + } + { + size, err := m.Config.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgUpdateClientConfigResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateClientConfigResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateClientConfigResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgRegisterCounterparty) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if len(m.CounterpartyMerklePrefix) > 0 { + for _, b := range m.CounterpartyMerklePrefix { + l = len(b) + n += 1 + l + sovTx(uint64(l)) + } + } + l = len(m.CounterpartyClientId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgRegisterCounterpartyResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgUpdateClientConfig) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Config.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgUpdateClientConfigResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgRegisterCounterparty) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgRegisterCounterparty: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgRegisterCounterparty: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CounterpartyMerklePrefix", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CounterpartyMerklePrefix = append(m.CounterpartyMerklePrefix, make([]byte, postIndex-iNdEx)) + copy(m.CounterpartyMerklePrefix[len(m.CounterpartyMerklePrefix)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CounterpartyClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CounterpartyClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgRegisterCounterpartyResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgRegisterCounterpartyResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgRegisterCounterpartyResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateClientConfig) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateClientConfig: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateClientConfig: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Config", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Config.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateClientConfigResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateClientConfigResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateClientConfigResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTx(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/03-connection/client/cli/cli.go b/modules/core/03-connection/client/cli/cli.go new file mode 100644 index 0000000..dd1ee27 --- /dev/null +++ b/modules/core/03-connection/client/cli/cli.go @@ -0,0 +1,26 @@ +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" +) + +// GetQueryCmd returns the query commands for IBC connections +func GetQueryCmd() *cobra.Command { + queryCmd := &cobra.Command{ + Use: types.SubModuleName, + Short: "IBC connection query subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + } + + queryCmd.AddCommand( + GetCmdQueryConnections(), + GetCmdQueryConnection(), + GetCmdQueryClientConnections(), + GetCmdConnectionParams(), + ) + + return queryCmd +} diff --git a/modules/core/03-connection/client/cli/query.go b/modules/core/03-connection/client/cli/query.go new file mode 100644 index 0000000..ae6f888 --- /dev/null +++ b/modules/core/03-connection/client/cli/query.go @@ -0,0 +1,144 @@ +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/version" + + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/client/utils" + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// GetCmdQueryConnections defines the command to query all the connection ends +// that this chain maintains. +func GetCmdQueryConnections() *cobra.Command { + cmd := &cobra.Command{ + Use: "connections", + Short: "Query all connections", + Long: "Query all connections ends from a chain", + Example: fmt.Sprintf("%s query %s %s connections", version.AppName, ibcexported.ModuleName, types.SubModuleName), + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + req := &types.QueryConnectionsRequest{ + Pagination: pageReq, + } + + res, err := queryClient.Connections(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "connection ends") + + return cmd +} + +// GetCmdQueryConnection defines the command to query a connection end +func GetCmdQueryConnection() *cobra.Command { + cmd := &cobra.Command{ + Use: "end [connection-id]", + Short: "Query stored connection end", + Long: "Query stored connection end", + Example: fmt.Sprintf("%s query %s %s end [connection-id]", version.AppName, ibcexported.ModuleName, types.SubModuleName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + connectionID := args[0] + prove, _ := cmd.Flags().GetBool(flags.FlagProve) + + connRes, err := utils.QueryConnection(clientCtx, connectionID, prove) + if err != nil { + return err + } + + clientCtx = clientCtx.WithHeight(int64(connRes.ProofHeight.RevisionHeight)) + return clientCtx.PrintProto(connRes) + }, + } + + cmd.Flags().Bool(flags.FlagProve, true, "show proofs for the query results") + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdQueryClientConnections defines the command to query a client connections +func GetCmdQueryClientConnections() *cobra.Command { + cmd := &cobra.Command{ + Use: "path [client-id]", + Short: "Query stored client connection paths", + Long: "Query stored client connection paths", + Example: fmt.Sprintf("%s query %s %s path [client-id]", version.AppName, ibcexported.ModuleName, types.SubModuleName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + clientID := args[0] + prove, _ := cmd.Flags().GetBool(flags.FlagProve) + + connPathsRes, err := utils.QueryClientConnections(clientCtx, clientID, prove) + if err != nil { + return err + } + + clientCtx = clientCtx.WithHeight(int64(connPathsRes.ProofHeight.RevisionHeight)) + return clientCtx.PrintProto(connPathsRes) + }, + } + + cmd.Flags().Bool(flags.FlagProve, true, "show proofs for the query results") + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdConnectionParams returns the command handler for ibc connection parameter querying. +func GetCmdConnectionParams() *cobra.Command { + cmd := &cobra.Command{ + Use: "params", + Short: "Query the current ibc connection parameters", + Long: "Query the current ibc connection parameters", + Args: cobra.NoArgs, + Example: fmt.Sprintf("%s query %s %s params", version.AppName, ibcexported.ModuleName, types.SubModuleName), + RunE: func(cmd *cobra.Command, _ []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + res, _ := queryClient.ConnectionParams(cmd.Context(), &types.QueryConnectionParamsRequest{}) + return clientCtx.PrintProto(res.Params) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/modules/core/03-connection/client/utils/utils.go b/modules/core/03-connection/client/utils/utils.go new file mode 100644 index 0000000..5373e78 --- /dev/null +++ b/modules/core/03-connection/client/utils/utils.go @@ -0,0 +1,218 @@ +package utils + +import ( + "context" + "errors" + "fmt" + "os" + + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + + clientutils "github.com/cosmos/ibc-go/v10/modules/core/02-client/client/utils" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcclient "github.com/cosmos/ibc-go/v10/modules/core/client" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// QueryConnection returns a connection end. +// If prove is true, it performs an ABCI store query in order to retrieve the merkle proof. Otherwise, +// it uses the gRPC query client. +func QueryConnection( + clientCtx client.Context, connectionID string, prove bool, +) (*types.QueryConnectionResponse, error) { + if prove { + return queryConnectionABCI(clientCtx, connectionID) + } + + queryClient := types.NewQueryClient(clientCtx) + req := &types.QueryConnectionRequest{ + ConnectionId: connectionID, + } + + return queryClient.Connection(context.Background(), req) +} + +func queryConnectionABCI(clientCtx client.Context, connectionID string) (*types.QueryConnectionResponse, error) { + key := host.ConnectionKey(connectionID) + + value, proofBz, proofHeight, err := ibcclient.QueryTendermintProof(clientCtx, key) + if err != nil { + return nil, err + } + + // check if connection exists + if len(value) == 0 { + return nil, errorsmod.Wrap(types.ErrConnectionNotFound, connectionID) + } + + cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry) + + var connection types.ConnectionEnd + if err := cdc.Unmarshal(value, &connection); err != nil { + return nil, err + } + + return types.NewQueryConnectionResponse(connection, proofBz, proofHeight), nil +} + +// QueryClientConnections queries the connection paths registered for a particular client. +// If prove is true, it performs an ABCI store query in order to retrieve the merkle proof. Otherwise, +// it uses the gRPC query client. +func QueryClientConnections( + clientCtx client.Context, clientID string, prove bool, +) (*types.QueryClientConnectionsResponse, error) { + if prove { + return queryClientConnectionsABCI(clientCtx, clientID) + } + + queryClient := types.NewQueryClient(clientCtx) + req := &types.QueryClientConnectionsRequest{ + ClientId: clientID, + } + + return queryClient.ClientConnections(context.Background(), req) +} + +func queryClientConnectionsABCI(clientCtx client.Context, clientID string) (*types.QueryClientConnectionsResponse, error) { + key := host.ClientConnectionsKey(clientID) + + value, proofBz, proofHeight, err := ibcclient.QueryTendermintProof(clientCtx, key) + if err != nil { + return nil, err + } + + // check if connection paths exist + if len(value) == 0 { + return nil, errorsmod.Wrap(types.ErrClientConnectionPathsNotFound, clientID) + } + + var paths []string + if err := clientCtx.LegacyAmino.Unmarshal(value, &paths); err != nil { + return nil, err + } + + return types.NewQueryClientConnectionsResponse(paths, proofBz, proofHeight), nil +} + +// QueryConnectionClientState returns the ClientState of a connection end. If +// prove is true, it performs an ABCI store query in order to retrieve the +// merkle proof. Otherwise, it uses the gRPC query client. +func QueryConnectionClientState( + clientCtx client.Context, connectionID string, prove bool, +) (*types.QueryConnectionClientStateResponse, error) { + queryClient := types.NewQueryClient(clientCtx) + req := &types.QueryConnectionClientStateRequest{ + ConnectionId: connectionID, + } + + res, err := queryClient.ConnectionClientState(context.Background(), req) + if err != nil { + return nil, err + } + + if prove { + clientStateRes, err := clientutils.QueryClientStateABCI(clientCtx, res.IdentifiedClientState.ClientId) + if err != nil { + return nil, err + } + + // use client state returned from ABCI query in case query height differs + identifiedClientState := clienttypes.IdentifiedClientState{ + ClientId: res.IdentifiedClientState.ClientId, + ClientState: clientStateRes.ClientState, + } + + res = types.NewQueryConnectionClientStateResponse(identifiedClientState, clientStateRes.Proof, clientStateRes.ProofHeight) + } + + return res, nil +} + +// QueryConnectionConsensusState returns the ConsensusState of a connection end. If +// prove is true, it performs an ABCI store query in order to retrieve the +// merkle proof. Otherwise, it uses the gRPC query client. +func QueryConnectionConsensusState( + clientCtx client.Context, connectionID string, height clienttypes.Height, prove bool, +) (*types.QueryConnectionConsensusStateResponse, error) { + queryClient := types.NewQueryClient(clientCtx) + req := &types.QueryConnectionConsensusStateRequest{ + ConnectionId: connectionID, + RevisionNumber: height.RevisionNumber, + RevisionHeight: height.RevisionHeight, + } + + res, err := queryClient.ConnectionConsensusState(context.Background(), req) + if err != nil { + return nil, err + } + + if prove { + consensusStateRes, err := clientutils.QueryConsensusStateABCI(clientCtx, res.ClientId, height) + if err != nil { + return nil, err + } + + res = types.NewQueryConnectionConsensusStateResponse(res.ClientId, consensusStateRes.ConsensusState, height, consensusStateRes.Proof, consensusStateRes.ProofHeight) + } + + return res, nil +} + +// ParseClientState unmarshals a cmd input argument from a JSON string to a client state +// If the input is not a JSON, it looks for a path to the JSON file +func ParseClientState(cdc *codec.LegacyAmino, arg string) (exported.ClientState, error) { + var clientState exported.ClientState + if err := cdc.UnmarshalJSON([]byte(arg), &clientState); err != nil { + // check for file path if JSON input is not provided + contents, err := os.ReadFile(arg) + if err != nil { + return nil, errors.New("either JSON input nor path to .json file were provided") + } + if err := cdc.UnmarshalJSON(contents, &clientState); err != nil { + return nil, fmt.Errorf("error unmarshalling client state: %w", err) + } + } + return clientState, nil +} + +// ParsePrefix unmarshals an cmd input argument from a JSON string to a commitment +// Prefix. If the input is not a JSON, it looks for a path to the JSON file. +func ParsePrefix(cdc *codec.LegacyAmino, arg string) (commitmenttypes.MerklePrefix, error) { + var prefix commitmenttypes.MerklePrefix + if err := cdc.UnmarshalJSON([]byte(arg), &prefix); err != nil { + // check for file path if JSON input is not provided + contents, err := os.ReadFile(arg) + if err != nil { + return commitmenttypes.MerklePrefix{}, errors.New("neither JSON input nor path to .json file were provided") + } + if err := cdc.UnmarshalJSON(contents, &prefix); err != nil { + return commitmenttypes.MerklePrefix{}, fmt.Errorf("error unmarshalling commitment prefix: %w", err) + } + } + return prefix, nil +} + +// ParseProof unmarshals a cmd input argument from a JSON string to a commitment +// Proof. If the input is not a JSON, it looks for a path to the JSON file. It +// then marshals the commitment proof into a proto encoded byte array. +func ParseProof(cdc *codec.LegacyAmino, arg string) ([]byte, error) { + var merkleProof commitmenttypes.MerkleProof + if err := cdc.UnmarshalJSON([]byte(arg), &merkleProof); err != nil { + // check for file path if JSON input is not provided + contents, err := os.ReadFile(arg) + if err != nil { + return nil, errors.New("neither JSON input nor path to .json file were provided") + } + if err := cdc.UnmarshalJSON(contents, &merkleProof); err != nil { + return nil, fmt.Errorf("error unmarshalling commitment proof: %w", err) + } + } + + return cdc.Marshal(&merkleProof) +} diff --git a/modules/core/03-connection/doc.go b/modules/core/03-connection/doc.go new file mode 100644 index 0000000..4e86aae --- /dev/null +++ b/modules/core/03-connection/doc.go @@ -0,0 +1,12 @@ +/* +Package connection implements the ICS 03 - Connection Semantics specification +(https://github.com/cosmos/ibc/tree/main/spec/core/ics-003-connection-semantics). This +concrete implementation defines types and methods for safely creating two +stateful objects (connection ends) on two separate chains, each associated with a +light client of the other chain, which together facilitate cross-chain +sub-state verification and packet association (through channels). + +The main type is ConnectionEnd, which defines a stateful object on a +chain connected to another. +*/ +package connection diff --git a/modules/core/03-connection/genesis.go b/modules/core/03-connection/genesis.go new file mode 100644 index 0000000..ec2e167 --- /dev/null +++ b/modules/core/03-connection/genesis.go @@ -0,0 +1,34 @@ +package connection + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/keeper" + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" +) + +// InitGenesis initializes the ibc connection submodule's state from a provided genesis +// state. +func InitGenesis(ctx sdk.Context, k *keeper.Keeper, gs types.GenesisState) { + for _, connection := range gs.Connections { + conn := types.NewConnectionEnd(connection.State, connection.ClientId, connection.Counterparty, connection.Versions, connection.DelayPeriod) + k.SetConnection(ctx, connection.Id, conn) + } + for _, connPaths := range gs.ClientConnectionPaths { + k.SetClientConnectionPaths(ctx, connPaths.ClientId, connPaths.Paths) + } + k.SetNextConnectionSequence(ctx, gs.NextConnectionSequence) + k.SetParams(ctx, gs.Params) + + k.CreateSentinelLocalhostConnection(ctx) +} + +// ExportGenesis returns the ibc connection submodule's exported genesis. +func ExportGenesis(ctx sdk.Context, k *keeper.Keeper) types.GenesisState { + return types.GenesisState{ + Connections: k.GetAllConnections(ctx), + ClientConnectionPaths: k.GetAllClientConnectionPaths(ctx), + NextConnectionSequence: k.GetNextConnectionSequence(ctx), + Params: k.GetParams(ctx), + } +} diff --git a/modules/core/03-connection/keeper/events.go b/modules/core/03-connection/keeper/events.go new file mode 100644 index 0000000..900c041 --- /dev/null +++ b/modules/core/03-connection/keeper/events.go @@ -0,0 +1,74 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" +) + +// emitConnectionOpenInitEvent emits a connection open init event +func emitConnectionOpenInitEvent(ctx sdk.Context, connectionID string, clientID string, counterparty types.Counterparty) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeConnectionOpenInit, + sdk.NewAttribute(types.AttributeKeyConnectionID, connectionID), + sdk.NewAttribute(types.AttributeKeyClientID, clientID), + sdk.NewAttribute(types.AttributeKeyCounterpartyClientID, counterparty.ClientId), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitConnectionOpenTryEvent emits a connection open try event +func emitConnectionOpenTryEvent(ctx sdk.Context, connectionID string, clientID string, counterparty types.Counterparty) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeConnectionOpenTry, + sdk.NewAttribute(types.AttributeKeyConnectionID, connectionID), + sdk.NewAttribute(types.AttributeKeyClientID, clientID), + sdk.NewAttribute(types.AttributeKeyCounterpartyClientID, counterparty.ClientId), + sdk.NewAttribute(types.AttributeKeyCounterpartyConnectionID, counterparty.ConnectionId), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitConnectionOpenAckEvent emits a connection open acknowledge event +func emitConnectionOpenAckEvent(ctx sdk.Context, connectionID string, connectionEnd types.ConnectionEnd) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeConnectionOpenAck, + sdk.NewAttribute(types.AttributeKeyConnectionID, connectionID), + sdk.NewAttribute(types.AttributeKeyClientID, connectionEnd.ClientId), + sdk.NewAttribute(types.AttributeKeyCounterpartyClientID, connectionEnd.Counterparty.ClientId), + sdk.NewAttribute(types.AttributeKeyCounterpartyConnectionID, connectionEnd.Counterparty.ConnectionId), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitConnectionOpenConfirmEvent emits a connection open confirm event +func emitConnectionOpenConfirmEvent(ctx sdk.Context, connectionID string, connectionEnd types.ConnectionEnd) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeConnectionOpenConfirm, + sdk.NewAttribute(types.AttributeKeyConnectionID, connectionID), + sdk.NewAttribute(types.AttributeKeyClientID, connectionEnd.ClientId), + sdk.NewAttribute(types.AttributeKeyCounterpartyClientID, connectionEnd.Counterparty.ClientId), + sdk.NewAttribute(types.AttributeKeyCounterpartyConnectionID, connectionEnd.Counterparty.ConnectionId), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} diff --git a/modules/core/03-connection/keeper/events_test.go b/modules/core/03-connection/keeper/events_test.go new file mode 100644 index 0000000..ec3859a --- /dev/null +++ b/modules/core/03-connection/keeper/events_test.go @@ -0,0 +1,156 @@ +package keeper_test + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *KeeperTestSuite) TestMsgConnectionOpenInitEvents() { + suite.SetupTest() + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + msg := types.NewMsgConnectionOpenInit( + path.EndpointA.ClientID, + path.EndpointA.Counterparty.ClientID, + path.EndpointA.Counterparty.Chain.GetPrefix(), ibctesting.DefaultOpenInitVersion, path.EndpointA.ConnectionConfig.DelayPeriod, + path.EndpointA.Chain.SenderAccount.GetAddress().String(), + ) + + res, err := suite.chainA.SendMsgs(msg) + suite.Require().NoError(err) + suite.Require().NotNil(res) + + events := res.Events + expectedEvents := sdk.Events{ + sdk.NewEvent( + types.EventTypeConnectionOpenInit, + sdk.NewAttribute(types.AttributeKeyConnectionID, ibctesting.FirstConnectionID), + sdk.NewAttribute(types.AttributeKeyClientID, path.EndpointA.ClientID), + sdk.NewAttribute(types.AttributeKeyCounterpartyClientID, path.EndpointB.ClientID), + ), + }.ToABCIEvents() + + var indexSet map[string]struct{} + expectedEvents = sdk.MarkEventsToIndex(expectedEvents, indexSet) + ibctesting.AssertEvents(&suite.Suite, expectedEvents, events) +} + +func (suite *KeeperTestSuite) TestMsgConnectionOpenTryEvents() { + suite.SetupTest() + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + suite.Require().NoError(path.EndpointA.ConnOpenInit()) + + suite.Require().NoError(path.EndpointB.UpdateClient()) + + initProof, proofHeight := path.EndpointB.QueryConnectionHandshakeProof() + + msg := types.NewMsgConnectionOpenTry( + path.EndpointB.ClientID, path.EndpointB.Counterparty.ConnectionID, path.EndpointB.Counterparty.ClientID, + path.EndpointB.Counterparty.Chain.GetPrefix(), []*types.Version{ibctesting.ConnectionVersion}, + path.EndpointB.ConnectionConfig.DelayPeriod, initProof, proofHeight, + path.EndpointB.Chain.SenderAccount.GetAddress().String(), + ) + + res, err := path.EndpointB.Chain.SendMsgs(msg) + suite.Require().NoError(err) + suite.Require().NotNil(res) + + events := res.Events + expectedEvents := sdk.Events{ + sdk.NewEvent( + types.EventTypeConnectionOpenTry, + sdk.NewAttribute(types.AttributeKeyConnectionID, ibctesting.FirstConnectionID), + sdk.NewAttribute(types.AttributeKeyClientID, path.EndpointB.ClientID), + sdk.NewAttribute(types.AttributeKeyCounterpartyClientID, path.EndpointA.ClientID), + sdk.NewAttribute(types.AttributeKeyCounterpartyConnectionID, path.EndpointA.ConnectionID), + ), + }.ToABCIEvents() + + var indexSet map[string]struct{} + expectedEvents = sdk.MarkEventsToIndex(expectedEvents, indexSet) + ibctesting.AssertEvents(&suite.Suite, expectedEvents, events) +} + +func (suite *KeeperTestSuite) TestMsgConnectionOpenAckEvents() { + suite.SetupTest() + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + suite.Require().NoError(path.EndpointA.ConnOpenInit()) + suite.Require().NoError(path.EndpointB.ConnOpenTry()) + + suite.Require().NoError(path.EndpointA.UpdateClient()) + + tryProof, proofHeight := path.EndpointA.QueryConnectionHandshakeProof() + + msg := types.NewMsgConnectionOpenAck( + path.EndpointA.ConnectionID, path.EndpointA.Counterparty.ConnectionID, + tryProof, proofHeight, ibctesting.ConnectionVersion, + path.EndpointA.Chain.SenderAccount.GetAddress().String(), + ) + + res, err := path.EndpointA.Chain.SendMsgs(msg) + suite.Require().NoError(err) + suite.Require().NotNil(res) + + events := res.Events + expectedEvents := sdk.Events{ + sdk.NewEvent( + types.EventTypeConnectionOpenAck, + sdk.NewAttribute(types.AttributeKeyConnectionID, ibctesting.FirstConnectionID), + sdk.NewAttribute(types.AttributeKeyClientID, path.EndpointA.ClientID), + sdk.NewAttribute(types.AttributeKeyCounterpartyClientID, path.EndpointB.ClientID), + sdk.NewAttribute(types.AttributeKeyCounterpartyConnectionID, path.EndpointB.ConnectionID), + ), + }.ToABCIEvents() + + var indexSet map[string]struct{} + expectedEvents = sdk.MarkEventsToIndex(expectedEvents, indexSet) + ibctesting.AssertEvents(&suite.Suite, expectedEvents, events) +} + +func (suite *KeeperTestSuite) TestMsgConnectionOpenConfirmEvents() { + suite.SetupTest() + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + suite.Require().NoError(path.EndpointA.ConnOpenInit()) + suite.Require().NoError(path.EndpointB.ConnOpenTry()) + suite.Require().NoError(path.EndpointA.ConnOpenAck()) + + suite.Require().NoError(path.EndpointB.UpdateClient()) + + connectionKey := host.ConnectionKey(path.EndpointB.Counterparty.ConnectionID) + proof, height := path.EndpointB.Counterparty.Chain.QueryProof(connectionKey) + + msg := types.NewMsgConnectionOpenConfirm( + path.EndpointB.ConnectionID, + proof, height, + path.EndpointB.Chain.SenderAccount.GetAddress().String(), + ) + + res, err := path.EndpointB.Chain.SendMsgs(msg) + suite.Require().NoError(err) + suite.Require().NotNil(res) + + events := res.Events + expectedEvents := sdk.Events{ + sdk.NewEvent( + types.EventTypeConnectionOpenConfirm, + sdk.NewAttribute(types.AttributeKeyConnectionID, ibctesting.FirstConnectionID), + sdk.NewAttribute(types.AttributeKeyClientID, path.EndpointB.ClientID), + sdk.NewAttribute(types.AttributeKeyCounterpartyClientID, path.EndpointA.ClientID), + sdk.NewAttribute(types.AttributeKeyCounterpartyConnectionID, path.EndpointA.ConnectionID), + ), + }.ToABCIEvents() + + var indexSet map[string]struct{} + expectedEvents = sdk.MarkEventsToIndex(expectedEvents, indexSet) + ibctesting.AssertEvents(&suite.Suite, expectedEvents, events) +} diff --git a/modules/core/03-connection/keeper/grpc_query.go b/modules/core/03-connection/keeper/grpc_query.go new file mode 100644 index 0000000..1248783 --- /dev/null +++ b/modules/core/03-connection/keeper/grpc_query.go @@ -0,0 +1,204 @@ +package keeper + +import ( + "context" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/store/prefix" + + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +var _ types.QueryServer = (*queryServer)(nil) + +// queryServer implements the 03-connection types.QueryServer interface. +// It embeds the connection keeper to leverage store access while limiting the api of the connection keeper. +type queryServer struct { + *Keeper +} + +// NewQueryServer returns a new 03-connection types.QueryServer implementation. +func NewQueryServer(k *Keeper) types.QueryServer { + return &queryServer{ + Keeper: k, + } +} + +// Connection implements the Query/Connection gRPC method +func (q *queryServer) Connection(goCtx context.Context, req *types.QueryConnectionRequest) (*types.QueryConnectionResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + if err := host.ConnectionIdentifierValidator(req.ConnectionId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + connection, found := q.GetConnection(ctx, req.ConnectionId) + if !found { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrap(types.ErrConnectionNotFound, req.ConnectionId).Error(), + ) + } + + return &types.QueryConnectionResponse{ + Connection: &connection, + ProofHeight: clienttypes.GetSelfHeight(ctx), + }, nil +} + +// Connections implements the Query/Connections gRPC method +func (q *queryServer) Connections(goCtx context.Context, req *types.QueryConnectionsRequest) (*types.QueryConnectionsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + var connections []*types.IdentifiedConnection + + store := prefix.NewStore(runtime.KVStoreAdapter(q.storeService.OpenKVStore(ctx)), []byte(host.KeyConnectionPrefix)) + + pageRes, err := query.Paginate(store, req.Pagination, func(key, value []byte) error { + var result types.ConnectionEnd + if err := q.cdc.Unmarshal(value, &result); err != nil { + return err + } + + connectionID, err := host.ParseConnectionPath(string(key)) + if err != nil { + return err + } + + identifiedConnection := types.NewIdentifiedConnection(connectionID, result) + connections = append(connections, &identifiedConnection) + return nil + }) + if err != nil { + return nil, err + } + + return &types.QueryConnectionsResponse{ + Connections: connections, + Pagination: pageRes, + Height: clienttypes.GetSelfHeight(ctx), + }, nil +} + +// ClientConnections implements the Query/ClientConnections gRPC method +func (q *queryServer) ClientConnections(goCtx context.Context, req *types.QueryClientConnectionsRequest) (*types.QueryClientConnectionsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + if err := host.ClientIdentifierValidator(req.ClientId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + clientConnectionPaths, found := q.GetClientConnectionPaths(ctx, req.ClientId) + if !found { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrap(types.ErrClientConnectionPathsNotFound, req.ClientId).Error(), + ) + } + + return &types.QueryClientConnectionsResponse{ + ConnectionPaths: clientConnectionPaths, + ProofHeight: clienttypes.GetSelfHeight(ctx), + }, nil +} + +// ConnectionClientState implements the Query/ConnectionClientState gRPC method +func (q *queryServer) ConnectionClientState(goCtx context.Context, req *types.QueryConnectionClientStateRequest) (*types.QueryConnectionClientStateResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + if err := host.ConnectionIdentifierValidator(req.ConnectionId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + connection, found := q.GetConnection(ctx, req.ConnectionId) + if !found { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrConnectionNotFound, "connection-id: %s", req.ConnectionId).Error(), + ) + } + + clientState, found := q.clientKeeper.GetClientState(ctx, connection.ClientId) + if !found { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrapf(clienttypes.ErrClientNotFound, "client-id: %s", connection.ClientId).Error(), + ) + } + + identifiedClientState := clienttypes.NewIdentifiedClientState(connection.ClientId, clientState) + + height := clienttypes.GetSelfHeight(ctx) + return types.NewQueryConnectionClientStateResponse(identifiedClientState, nil, height), nil +} + +// ConnectionConsensusState implements the Query/ConnectionConsensusState gRPC method +func (q *queryServer) ConnectionConsensusState(goCtx context.Context, req *types.QueryConnectionConsensusStateRequest) (*types.QueryConnectionConsensusStateResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + if err := host.ConnectionIdentifierValidator(req.ConnectionId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + connection, found := q.GetConnection(ctx, req.ConnectionId) + if !found { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrConnectionNotFound, "connection-id: %s", req.ConnectionId).Error(), + ) + } + + height := clienttypes.NewHeight(req.RevisionNumber, req.RevisionHeight) + consensusState, found := q.clientKeeper.GetClientConsensusState(ctx, connection.ClientId, height) + if !found { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrapf(clienttypes.ErrConsensusStateNotFound, "client-id: %s", connection.ClientId).Error(), + ) + } + + anyConsensusState, err := clienttypes.PackConsensusState(consensusState) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + proofHeight := clienttypes.GetSelfHeight(ctx) + return types.NewQueryConnectionConsensusStateResponse(connection.ClientId, anyConsensusState, height, nil, proofHeight), nil +} + +// ConnectionParams implements the Query/ConnectionParams gRPC method. +func (q *queryServer) ConnectionParams(goCtx context.Context, req *types.QueryConnectionParamsRequest) (*types.QueryConnectionParamsResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + params := q.GetParams(ctx) + + return &types.QueryConnectionParamsResponse{ + Params: ¶ms, + }, nil +} diff --git a/modules/core/03-connection/keeper/grpc_query_test.go b/modules/core/03-connection/keeper/grpc_query_test.go new file mode 100644 index 0000000..325333b --- /dev/null +++ b/modules/core/03-connection/keeper/grpc_query_test.go @@ -0,0 +1,492 @@ +package keeper_test + +import ( + "fmt" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/cosmos-sdk/types/query" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/keeper" + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *KeeperTestSuite) TestQueryConnection() { + var ( + req *types.QueryConnectionRequest + expConnection types.ConnectionEnd + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid connectionID", + func() { + req = &types.QueryConnectionRequest{} + }, + status.Error(codes.InvalidArgument, errorsmod.Wrap(host.ErrInvalidID, "identifier cannot be blank").Error()), + }, + { + "connection not found", + func() { + req = &types.QueryConnectionRequest{ + ConnectionId: ibctesting.InvalidID, + } + }, + status.Error( + codes.NotFound, + errorsmod.Wrap(types.ErrConnectionNotFound, "IDisInvalid").Error(), + ), + }, + { + "success", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + err := path.EndpointA.ConnOpenInit() + suite.Require().NoError(err) + + counterparty := types.NewCounterparty(path.EndpointB.ClientID, "", suite.chainB.GetPrefix()) + expConnection = types.NewConnectionEnd(types.INIT, path.EndpointA.ClientID, counterparty, types.GetCompatibleVersions(), 500) + suite.chainA.App.GetIBCKeeper().ConnectionKeeper.SetConnection(suite.chainA.GetContext(), path.EndpointA.ConnectionID, expConnection) + + req = &types.QueryConnectionRequest{ + ConnectionId: path.EndpointA.ConnectionID, + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ConnectionKeeper) + res, err := queryServer.Connection(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(&expConnection, res.Connection) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryConnections() { + suite.chainA.App.GetIBCKeeper().ConnectionKeeper.CreateSentinelLocalhostConnection(suite.chainA.GetContext()) + localhostConn, found := suite.chainA.App.GetIBCKeeper().ConnectionKeeper.GetConnection(suite.chainA.GetContext(), exported.LocalhostConnectionID) + suite.Require().True(found) + + identifiedConn := types.NewIdentifiedConnection(exported.LocalhostConnectionID, localhostConn) + + var ( + req *types.QueryConnectionsRequest + expConnections = []*types.IdentifiedConnection{&identifiedConn} + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "empty pagination", + func() { + req = &types.QueryConnectionsRequest{} + }, + nil, + }, + { + "success", + func() { + path1 := ibctesting.NewPath(suite.chainA, suite.chainB) + path2 := ibctesting.NewPath(suite.chainA, suite.chainB) + path3 := ibctesting.NewPath(suite.chainA, suite.chainB) + path1.SetupConnections() + path2.SetupConnections() + path3.SetupClients() + + err := path3.EndpointA.ConnOpenInit() + suite.Require().NoError(err) + + counterparty1 := types.NewCounterparty(path1.EndpointB.ClientID, path1.EndpointB.ConnectionID, suite.chainB.GetPrefix()) + counterparty2 := types.NewCounterparty(path2.EndpointB.ClientID, path2.EndpointB.ConnectionID, suite.chainB.GetPrefix()) + // counterparty connection id is blank after open init + counterparty3 := types.NewCounterparty(path3.EndpointB.ClientID, "", suite.chainB.GetPrefix()) + + conn1 := types.NewConnectionEnd(types.OPEN, path1.EndpointA.ClientID, counterparty1, types.GetCompatibleVersions(), 0) + conn2 := types.NewConnectionEnd(types.OPEN, path2.EndpointA.ClientID, counterparty2, types.GetCompatibleVersions(), 0) + conn3 := types.NewConnectionEnd(types.INIT, path3.EndpointA.ClientID, counterparty3, types.GetCompatibleVersions(), 0) + + iconn1 := types.NewIdentifiedConnection(path1.EndpointA.ConnectionID, conn1) + iconn2 := types.NewIdentifiedConnection(path2.EndpointA.ConnectionID, conn2) + iconn3 := types.NewIdentifiedConnection(path3.EndpointA.ConnectionID, conn3) + + expConnections = []*types.IdentifiedConnection{&iconn1, &iconn2, &iconn3, &identifiedConn} + + req = &types.QueryConnectionsRequest{ + Pagination: &query.PageRequest{ + Limit: 4, + CountTotal: true, + }, + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ConnectionKeeper) + res, err := queryServer.Connections(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expConnections, res.Connections) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryClientConnections() { + var ( + req *types.QueryClientConnectionsRequest + expPaths []string + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid connectionID", + func() { + req = &types.QueryClientConnectionsRequest{} + }, + status.Error(codes.InvalidArgument, errorsmod.Wrap(host.ErrInvalidID, "identifier cannot be blank").Error()), + }, + { + "connection not found", + func() { + req = &types.QueryClientConnectionsRequest{ + ClientId: ibctesting.InvalidID, + } + }, + status.Error( + codes.NotFound, + errorsmod.Wrap(types.ErrClientConnectionPathsNotFound, "IDisInvalid").Error(), + ), + }, + { + "success", + func() { + path1 := ibctesting.NewPath(suite.chainA, suite.chainB) + path1.SetupConnections() + + // create another connection using same underlying clients + path2 := ibctesting.NewPath(suite.chainA, suite.chainB) + path2.EndpointA.ClientID = path1.EndpointA.ClientID + path2.EndpointB.ClientID = path1.EndpointB.ClientID + + path2.CreateConnections() + + expPaths = []string{path1.EndpointA.ConnectionID, path2.EndpointA.ConnectionID} + suite.chainA.App.GetIBCKeeper().ConnectionKeeper.SetClientConnectionPaths(suite.chainA.GetContext(), path1.EndpointA.ClientID, expPaths) + + req = &types.QueryClientConnectionsRequest{ + ClientId: path1.EndpointA.ClientID, + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ConnectionKeeper) + res, err := queryServer.ClientConnections(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expPaths, res.ConnectionPaths) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryConnectionClientState() { + var ( + req *types.QueryConnectionClientStateRequest + expIdentifiedClientState clienttypes.IdentifiedClientState + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid connection ID", + func() { + req = &types.QueryConnectionClientStateRequest{ + ConnectionId: "", + } + }, + status.Error(codes.InvalidArgument, errorsmod.Wrap(host.ErrInvalidID, "identifier cannot be blank").Error()), + }, + { + "connection not found", + func() { + req = &types.QueryConnectionClientStateRequest{ + ConnectionId: "test-connection-id", + } + }, + status.Error( + codes.NotFound, + errorsmod.Wrap(types.ErrConnectionNotFound, "connection-id: test-connection-id").Error(), + ), + }, + { + "client state not found", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + // set connection to empty so clientID is empty + suite.chainA.App.GetIBCKeeper().ConnectionKeeper.SetConnection(suite.chainA.GetContext(), path.EndpointA.ConnectionID, types.ConnectionEnd{}) + + req = &types.QueryConnectionClientStateRequest{ + ConnectionId: path.EndpointA.ConnectionID, + } + }, status.Error( + codes.NotFound, + errorsmod.Wrap(clienttypes.ErrClientNotFound, "client-id: ").Error(), + ), + }, + { + "success", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupConnections() + + expClientState := suite.chainA.GetClientState(path.EndpointA.ClientID) + expIdentifiedClientState = clienttypes.NewIdentifiedClientState(path.EndpointA.ClientID, expClientState) + + req = &types.QueryConnectionClientStateRequest{ + ConnectionId: path.EndpointA.ConnectionID, + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ConnectionKeeper) + res, err := queryServer.ConnectionClientState(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(&expIdentifiedClientState, res.IdentifiedClientState) + + // ensure UnpackInterfaces is defined + cachedValue := res.IdentifiedClientState.ClientState.GetCachedValue() + suite.Require().NotNil(cachedValue) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryConnectionConsensusState() { + var ( + req *types.QueryConnectionConsensusStateRequest + expConsensusState exported.ConsensusState + expClientID string + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid connection ID", + func() { + req = &types.QueryConnectionConsensusStateRequest{ + ConnectionId: "", + RevisionNumber: 0, + RevisionHeight: 1, + } + }, + status.Error(codes.InvalidArgument, errorsmod.Wrap(host.ErrInvalidID, "identifier cannot be blank").Error()), + }, + { + "connection not found", + func() { + req = &types.QueryConnectionConsensusStateRequest{ + ConnectionId: "test-connection-id", + RevisionNumber: 0, + RevisionHeight: 1, + } + }, + status.Error( + codes.NotFound, + errorsmod.Wrap(types.ErrConnectionNotFound, "connection-id: test-connection-id").Error(), + ), + }, + { + "consensus state not found", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + req = &types.QueryConnectionConsensusStateRequest{ + ConnectionId: path.EndpointA.ConnectionID, + RevisionNumber: 0, + RevisionHeight: uint64(suite.chainA.GetContext().BlockHeight()), // use current height + } + }, status.Error( + codes.NotFound, + errorsmod.Wrap(clienttypes.ErrConsensusStateNotFound, "client-id: 07-tendermint-0").Error(), + ), + }, + { + "success", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupConnections() + + clientHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + expConsensusState, _ = suite.chainA.GetConsensusState(path.EndpointA.ClientID, clientHeight) + suite.Require().NotNil(expConsensusState) + expClientID = path.EndpointA.ClientID + + req = &types.QueryConnectionConsensusStateRequest{ + ConnectionId: path.EndpointA.ConnectionID, + RevisionNumber: clientHeight.GetRevisionNumber(), + RevisionHeight: clientHeight.GetRevisionHeight(), + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ConnectionKeeper) + res, err := queryServer.ConnectionConsensusState(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + consensusState, err := clienttypes.UnpackConsensusState(res.ConsensusState) + suite.Require().NoError(err) + suite.Require().Equal(expConsensusState, consensusState) + suite.Require().Equal(expClientID, res.ClientId) + + // ensure UnpackInterfaces is defined + cachedValue := res.ConsensusState.GetCachedValue() + suite.Require().NotNil(cachedValue) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryConnectionParams() { + expParams := types.DefaultParams() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ConnectionKeeper) + res, err := queryServer.ConnectionParams(suite.chainA.GetContext(), &types.QueryConnectionParamsRequest{}) + suite.Require().NoError(err) + suite.Require().Equal(&expParams, res.Params) +} diff --git a/modules/core/03-connection/keeper/handshake.go b/modules/core/03-connection/keeper/handshake.go new file mode 100644 index 0000000..6de8e6f --- /dev/null +++ b/modules/core/03-connection/keeper/handshake.go @@ -0,0 +1,224 @@ +package keeper + +import ( + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/cosmos-sdk/telemetry" + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// ConnOpenInit initialises a connection attempt on chain A. The generated connection identifier +// is returned. +// +// NOTE: Msg validation verifies the supplied identifiers and ensures that the counterparty +// connection identifier is empty. +func (k *Keeper) ConnOpenInit( + ctx sdk.Context, + clientID string, + counterparty types.Counterparty, // counterpartyPrefix, counterpartyClientIdentifier + version *types.Version, + delayPeriod uint64, +) (string, error) { + versions := types.GetCompatibleVersions() + if version != nil { + if !types.IsSupportedVersion(types.GetCompatibleVersions(), version) { + return "", errorsmod.Wrap(types.ErrInvalidVersion, "version is not supported") + } + + versions = []*types.Version{version} + } + + if status := k.clientKeeper.GetClientStatus(ctx, clientID); status != exported.Active { + return "", errorsmod.Wrapf(clienttypes.ErrClientNotActive, "client (%s) status is %s", clientID, status) + } + + connectionID := k.GenerateConnectionIdentifier(ctx) + if err := k.addConnectionToClient(ctx, clientID, connectionID); err != nil { + return "", err + } + + // connection defines chain A's ConnectionEnd + connection := types.NewConnectionEnd(types.INIT, clientID, counterparty, versions, delayPeriod) + k.SetConnection(ctx, connectionID, connection) + + k.Logger(ctx).Info("connection state updated", "connection-id", connectionID, "previous-state", types.UNINITIALIZED, "new-state", types.INIT) + + defer telemetry.IncrCounter(1, "ibc", "connection", "open-init") + + emitConnectionOpenInitEvent(ctx, connectionID, clientID, counterparty) + + return connectionID, nil +} + +// ConnOpenTry relays notice of a connection attempt on chain A to chain B (this +// code is executed on chain B). +// +// NOTE: +// - Here chain A acts as the counterparty +// - Identifiers are checked on msg validation +func (k *Keeper) ConnOpenTry( + ctx sdk.Context, + counterparty types.Counterparty, // counterpartyConnectionIdentifier, counterpartyPrefix and counterpartyClientIdentifier + delayPeriod uint64, + clientID string, // clientID of chainA + counterpartyVersions []*types.Version, // supported versions of chain A + initProof []byte, // proof that chainA stored connectionEnd in state (on ConnOpenInit) + proofHeight exported.Height, // height at which relayer constructs proof of A storing connectionEnd in state +) (string, error) { + // generate a new connection + connectionID := k.GenerateConnectionIdentifier(ctx) + + // expectedConnection defines Chain A's ConnectionEnd + // NOTE: chain A's counterparty is chain B (i.e where this code is executed) + // NOTE: chainA and chainB must have the same delay period + prefix := k.GetCommitmentPrefix() + expectedCounterparty := types.NewCounterparty(clientID, "", commitmenttypes.NewMerklePrefix(prefix.Bytes())) + expectedConnection := types.NewConnectionEnd(types.INIT, counterparty.ClientId, expectedCounterparty, counterpartyVersions, delayPeriod) + + // chain B picks a version from Chain A's available versions that is compatible + // with Chain B's supported IBC versions. PickVersion will select the intersection + // of the supported versions and the counterparty versions. + version, err := types.PickVersion(types.GetCompatibleVersions(), counterpartyVersions) + if err != nil { + return "", err + } + + // connection defines chain B's ConnectionEnd + connection := types.NewConnectionEnd(types.TRYOPEN, clientID, counterparty, []*types.Version{version}, delayPeriod) + + // Check that ChainA committed expectedConnectionEnd to its state + if err := k.VerifyConnectionState( + ctx, connection, proofHeight, initProof, counterparty.ConnectionId, + expectedConnection, + ); err != nil { + return "", err + } + + // store connection in chainB state + if err := k.addConnectionToClient(ctx, clientID, connectionID); err != nil { + return "", errorsmod.Wrapf(err, "failed to add connection with ID %s to client with ID %s", connectionID, clientID) + } + + k.SetConnection(ctx, connectionID, connection) + k.Logger(ctx).Info("connection state updated", "connection-id", connectionID, "previous-state", types.UNINITIALIZED, "new-state", types.TRYOPEN) + + defer telemetry.IncrCounter(1, "ibc", "connection", "open-try") + + emitConnectionOpenTryEvent(ctx, connectionID, clientID, counterparty) + + return connectionID, nil +} + +// ConnOpenAck relays acceptance of a connection open attempt from chain B back +// to chain A (this code is executed on chain A). +// +// NOTE: Identifiers are checked on msg validation. +func (k *Keeper) ConnOpenAck( + ctx sdk.Context, + connectionID string, + version *types.Version, // version that ChainB chose in ConnOpenTry + counterpartyConnectionID string, + tryProof []byte, // proof that connectionEnd was added to ChainB state in ConnOpenTry + proofHeight exported.Height, // height that relayer constructed proofTry +) error { + // Retrieve connection + connection, found := k.GetConnection(ctx, connectionID) + if !found { + return errorsmod.Wrap(types.ErrConnectionNotFound, connectionID) + } + + // verify the previously set connection state + if connection.State != types.INIT { + return errorsmod.Wrapf( + types.ErrInvalidConnectionState, + "connection state is not INIT (got %s)", connection.State, + ) + } + + // ensure selected version is supported + if !types.IsSupportedVersion(connection.Versions, version) { + return errorsmod.Wrapf( + types.ErrInvalidConnectionState, + "the counterparty selected version %s is not supported by versions selected on INIT", version, + ) + } + + prefix := k.GetCommitmentPrefix() + expectedCounterparty := types.NewCounterparty(connection.ClientId, connectionID, commitmenttypes.NewMerklePrefix(prefix.Bytes())) + expectedConnection := types.NewConnectionEnd(types.TRYOPEN, connection.Counterparty.ClientId, expectedCounterparty, []*types.Version{version}, connection.DelayPeriod) + + // Ensure that ChainB stored expected connectionEnd in its state during ConnOpenTry + if err := k.VerifyConnectionState( + ctx, connection, proofHeight, tryProof, counterpartyConnectionID, + expectedConnection, + ); err != nil { + return err + } + + k.Logger(ctx).Info("connection state updated", "connection-id", connectionID, "previous-state", types.INIT, "new-state", types.OPEN) + + defer telemetry.IncrCounter(1, "ibc", "connection", "open-ack") + + // Update connection state to Open + connection.State = types.OPEN + connection.Versions = []*types.Version{version} + connection.Counterparty.ConnectionId = counterpartyConnectionID + k.SetConnection(ctx, connectionID, connection) + + emitConnectionOpenAckEvent(ctx, connectionID, connection) + + return nil +} + +// ConnOpenConfirm confirms opening of a connection on chain A to chain B, after +// which the connection is open on both chains (this code is executed on chain B). +// +// NOTE: Identifiers are checked on msg validation. +func (k *Keeper) ConnOpenConfirm( + ctx sdk.Context, + connectionID string, + ackProof []byte, // proof that connection opened on ChainA during ConnOpenAck + proofHeight exported.Height, // height that relayer constructed proofAck +) error { + // Retrieve connection + connection, found := k.GetConnection(ctx, connectionID) + if !found { + return errorsmod.Wrap(types.ErrConnectionNotFound, connectionID) + } + + // Check that connection state on ChainB is on state: TRYOPEN + if connection.State != types.TRYOPEN { + return errorsmod.Wrapf( + types.ErrInvalidConnectionState, + "connection state is not TRYOPEN (got %s)", connection.State, + ) + } + + prefix := k.GetCommitmentPrefix() + expectedCounterparty := types.NewCounterparty(connection.ClientId, connectionID, commitmenttypes.NewMerklePrefix(prefix.Bytes())) + expectedConnection := types.NewConnectionEnd(types.OPEN, connection.Counterparty.ClientId, expectedCounterparty, connection.Versions, connection.DelayPeriod) + + // Check that connection on ChainA is open + if err := k.VerifyConnectionState( + ctx, connection, proofHeight, ackProof, connection.Counterparty.ConnectionId, + expectedConnection, + ); err != nil { + return err + } + + // Update ChainB's connection to Open + connection.State = types.OPEN + k.SetConnection(ctx, connectionID, connection) + k.Logger(ctx).Info("connection state updated", "connection-id", connectionID, "previous-state", types.TRYOPEN, "new-state", types.OPEN) + + defer telemetry.IncrCounter(1, "ibc", "connection", "open-confirm") + + emitConnectionOpenConfirmEvent(ctx, connectionID, connection) + + return nil +} diff --git a/modules/core/03-connection/keeper/handshake_test.go b/modules/core/03-connection/keeper/handshake_test.go new file mode 100644 index 0000000..e32240c --- /dev/null +++ b/modules/core/03-connection/keeper/handshake_test.go @@ -0,0 +1,370 @@ +package keeper_test + +import ( + "time" + + errorsmod "cosmossdk.io/errors" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +// TestConnOpenInit - chainA initializes (INIT state) a connection with +// chainB which is yet UNINITIALIZED +func (suite *KeeperTestSuite) TestConnOpenInit() { + var ( + path *ibctesting.Path + version *types.Version + delayPeriod uint64 + emptyConnBID bool + expErrorMsgSubstring string + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + {"success", func() { + }, nil}, + {"success with empty counterparty identifier", func() { + emptyConnBID = true + }, nil}, + {"success with non empty version", func() { + version = types.GetCompatibleVersions()[0] + }, nil}, + {"success with non zero delayPeriod", func() { + delayPeriod = uint64(time.Hour.Nanoseconds()) + }, nil}, + + {"invalid version", func() { + version = &types.Version{} + }, errorsmod.Wrap(types.ErrInvalidVersion, "version is not supported")}, + {"couldn't add connection to client", func() { + // set path.EndpointA.ClientID to invalid client identifier + path.EndpointA.ClientID = "clientidentifier" + }, errorsmod.Wrap(clienttypes.ErrClientNotActive, "client (clientidentifier) status is Unauthorized")}, + { + msg: "unauthorized client", + expErr: errorsmod.Wrap(clienttypes.ErrClientNotActive, "client (07-tendermint-0) status is Unauthorized"), + malleate: func() { + expErrorMsgSubstring = "status is Unauthorized" + // remove client from allowed list + params := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetParams(suite.chainA.GetContext()) + params.AllowedClients = []string{} + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetParams(suite.chainA.GetContext(), params) + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.msg, func() { + suite.SetupTest() // reset + emptyConnBID = false // must be explicitly changed + version = nil // must be explicitly changed + expErrorMsgSubstring = "" + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + tc.malleate() + + if emptyConnBID { + path.EndpointB.ConnectionID = "" + } + counterparty := types.NewCounterparty(path.EndpointB.ClientID, path.EndpointB.ConnectionID, suite.chainB.GetPrefix()) + + connectionID, err := suite.chainA.App.GetIBCKeeper().ConnectionKeeper.ConnOpenInit(suite.chainA.GetContext(), path.EndpointA.ClientID, counterparty, version, delayPeriod) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().Equal(types.FormatConnectionIdentifier(0), connectionID) + } else { + suite.Require().Error(err) + suite.Contains(err.Error(), expErrorMsgSubstring) + suite.Require().Equal("", connectionID) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +// TestConnOpenTry - chainB calls ConnOpenTry to verify the state of +// connection on chainA is INIT +func (suite *KeeperTestSuite) TestConnOpenTry() { + var ( + path *ibctesting.Path + delayPeriod uint64 + versions []*types.Version + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + {"success", func() { + err := path.EndpointA.ConnOpenInit() + suite.Require().NoError(err) + }, nil}, + {"success with delay period", func() { + err := path.EndpointA.ConnOpenInit() + suite.Require().NoError(err) + + delayPeriod = uint64(time.Hour.Nanoseconds()) + + // set delay period on counterparty to non-zero value + path.EndpointA.UpdateConnection(func(connection *types.ConnectionEnd) { connection.DelayPeriod = delayPeriod }) + + // commit in order for proof to return correct value + suite.coordinator.CommitBlock(suite.chainA) + err = path.EndpointB.UpdateClient() + suite.Require().NoError(err) + }, nil}, + {"counterparty versions is empty", func() { + err := path.EndpointA.ConnOpenInit() + suite.Require().NoError(err) + + versions = nil + }, errorsmod.Wrap(types.ErrVersionNegotiationFailed, "failed to find a matching counterparty version ([]) from the supported version list ([identifier:\"1\" features:\"ORDER_ORDERED\" features:\"ORDER_UNORDERED\" ])")}, + {"counterparty versions don't have a match", func() { + err := path.EndpointA.ConnOpenInit() + suite.Require().NoError(err) + + version := types.NewVersion("0.0", nil) + versions = []*types.Version{version} + }, errorsmod.Wrap(types.ErrVersionNegotiationFailed, "failed to find a matching counterparty version ([identifier:\"0.0\" ]) from the supported version list ([identifier:\"1\" features:\"ORDER_ORDERED\" features:\"ORDER_UNORDERED\" ])")}, + {"connection state verification failed", func() { + // chainA connection not created + }, errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "failed connection state verification for client (07-tendermint-0): commitment proof must be existence proof. got: int at index &{1374402732384}")}, + } + + for _, tc := range testCases { + suite.Run(tc.msg, func() { + suite.SetupTest() // reset + versions = types.GetCompatibleVersions() // may be changed in malleate + delayPeriod = 0 // may be changed in malleate + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + tc.malleate() + + counterparty := types.NewCounterparty(path.EndpointA.ClientID, path.EndpointA.ConnectionID, suite.chainA.GetPrefix()) + + // ensure client is up to date to receive proof + err := path.EndpointB.UpdateClient() + suite.Require().NoError(err) + + connectionKey := host.ConnectionKey(path.EndpointA.ConnectionID) + initProof, proofHeight := suite.chainA.QueryProof(connectionKey) + + connectionID, err := suite.chainB.App.GetIBCKeeper().ConnectionKeeper.ConnOpenTry( + suite.chainB.GetContext(), counterparty, delayPeriod, path.EndpointB.ClientID, + versions, initProof, proofHeight, + ) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().Equal(types.FormatConnectionIdentifier(0), connectionID) + } else { + suite.Require().Error(err) + suite.Require().Equal("", connectionID) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +// TestConnOpenAck - Chain A (ID #1) calls TestConnOpenAck to acknowledge (ACK state) +// the initialization (TRYINIT) of the connection on Chain B (ID #2). +func (suite *KeeperTestSuite) TestConnOpenAck() { + var ( + path *ibctesting.Path + version *types.Version + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + {"success", func() { + err := path.EndpointA.ConnOpenInit() + suite.Require().NoError(err) + + err = path.EndpointB.ConnOpenTry() + suite.Require().NoError(err) + }, nil}, + {"connection not found", func() { + // connections are never created + }, errorsmod.Wrap(types.ErrConnectionNotFound, "")}, + {"invalid counterparty connection ID", func() { + err := path.EndpointA.ConnOpenInit() + suite.Require().NoError(err) + + err = path.EndpointB.ConnOpenTry() + suite.Require().NoError(err) + + // modify connB to set counterparty connection identifier to wrong identifier + path.EndpointA.UpdateConnection(func(c *types.ConnectionEnd) { c.Counterparty.ConnectionId = ibctesting.InvalidID }) + path.EndpointB.ConnectionID = ibctesting.InvalidID + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + err = path.EndpointB.UpdateClient() + suite.Require().NoError(err) + }, errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "failed connection state verification for client (07-tendermint-0): commitment proof must be existence proof. got: int at index &{1374412614704}")}, + {"connection state is not INIT", func() { + // connection state is already OPEN on chainA + err := path.EndpointA.ConnOpenInit() + suite.Require().NoError(err) + + err = path.EndpointB.ConnOpenTry() + suite.Require().NoError(err) + + err = path.EndpointA.ConnOpenAck() + suite.Require().NoError(err) + }, errorsmod.Wrap(types.ErrInvalidConnectionState, "connection state is not INIT (got STATE_OPEN)")}, + {"connection is in INIT but the proposed version is invalid", func() { + // chainA is in INIT, chainB is in TRYOPEN + err := path.EndpointA.ConnOpenInit() + suite.Require().NoError(err) + + err = path.EndpointB.ConnOpenTry() + suite.Require().NoError(err) + + version = types.NewVersion("2.0", nil) + }, errorsmod.Wrap(types.ErrInvalidConnectionState, "the counterparty selected version identifier:\"2.0\" is not supported by versions selected on INIT")}, + {"incompatible IBC versions", func() { + err := path.EndpointA.ConnOpenInit() + suite.Require().NoError(err) + + err = path.EndpointB.ConnOpenTry() + suite.Require().NoError(err) + + // set version to a non-compatible version + version = types.NewVersion("2.0", nil) + }, errorsmod.Wrap(types.ErrInvalidConnectionState, "the counterparty selected version identifier:\"2.0\" is not supported by versions selected on INIT")}, + {"empty version", func() { + err := path.EndpointA.ConnOpenInit() + suite.Require().NoError(err) + + err = path.EndpointB.ConnOpenTry() + suite.Require().NoError(err) + + version = &types.Version{} + }, errorsmod.Wrap(types.ErrInvalidConnectionState, "the counterparty selected version is not supported by versions selected on INIT")}, + {"feature set verification failed - unsupported feature", func() { + err := path.EndpointA.ConnOpenInit() + suite.Require().NoError(err) + + err = path.EndpointB.ConnOpenTry() + suite.Require().NoError(err) + + version = types.NewVersion(types.DefaultIBCVersionIdentifier, []string{"ORDER_ORDERED", "ORDER_UNORDERED", "ORDER_DAG"}) + }, errorsmod.Wrap(types.ErrInvalidConnectionState, "the counterparty selected version identifier:\"1\" features:\"ORDER_ORDERED\" features:\"ORDER_UNORDERED\" features:\"ORDER_DAG\" is not supported by versions selected on INIT")}, + {"connection state verification failed", func() { + // chainB connection is not in INIT + err := path.EndpointA.ConnOpenInit() + suite.Require().NoError(err) + }, errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "failed connection state verification for client (07-tendermint-0): commitment proof must be existence proof. got: int at index &{1374414228888}")}, + } + + for _, tc := range testCases { + suite.Run(tc.msg, func() { + suite.SetupTest() // reset + version = types.GetCompatibleVersions()[0] // must be explicitly changed in malleate + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + tc.malleate() + + // ensure client is up to date to receive proof + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + connectionKey := host.ConnectionKey(path.EndpointB.ConnectionID) + tryProof, proofHeight := suite.chainB.QueryProof(connectionKey) + + err = suite.chainA.App.GetIBCKeeper().ConnectionKeeper.ConnOpenAck( + suite.chainA.GetContext(), path.EndpointA.ConnectionID, version, + path.EndpointB.ConnectionID, tryProof, proofHeight, + ) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +// TestConnOpenConfirm - chainB calls ConnOpenConfirm to confirm that +// chainA state is now OPEN. +func (suite *KeeperTestSuite) TestConnOpenConfirm() { + var path *ibctesting.Path + testCases := []struct { + msg string + malleate func() + expErr error + }{ + {"success", func() { + err := path.EndpointA.ConnOpenInit() + suite.Require().NoError(err) + + err = path.EndpointB.ConnOpenTry() + suite.Require().NoError(err) + + err = path.EndpointA.ConnOpenAck() + suite.Require().NoError(err) + }, nil}, + {"connection not found", func() { + // connections are never created + }, errorsmod.Wrap(types.ErrConnectionNotFound, "")}, + {"chain B's connection state is not TRYOPEN", func() { + // connections are OPEN + path.CreateConnections() + }, errorsmod.Wrap(types.ErrInvalidConnectionState, "connection state is not TRYOPEN (got STATE_OPEN)")}, + {"connection state verification failed", func() { + // chainA is in INIT + err := path.EndpointA.ConnOpenInit() + suite.Require().NoError(err) + + err = path.EndpointB.ConnOpenTry() + suite.Require().NoError(err) + }, errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "failed connection state verification for client (07-tendermint-0): failed to verify membership proof at index 0: provided value doesn't match proof")}, + } + + for _, tc := range testCases { + suite.Run(tc.msg, func() { + suite.SetupTest() // reset + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + tc.malleate() + + // ensure client is up to date to receive proof + err := path.EndpointB.UpdateClient() + suite.Require().NoError(err) + + connectionKey := host.ConnectionKey(path.EndpointA.ConnectionID) + ackProof, proofHeight := suite.chainA.QueryProof(connectionKey) + + err = suite.chainB.App.GetIBCKeeper().ConnectionKeeper.ConnOpenConfirm( + suite.chainB.GetContext(), path.EndpointB.ConnectionID, ackProof, proofHeight, + ) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} diff --git a/modules/core/03-connection/keeper/keeper.go b/modules/core/03-connection/keeper/keeper.go new file mode 100644 index 0000000..e27e1ae --- /dev/null +++ b/modules/core/03-connection/keeper/keeper.go @@ -0,0 +1,253 @@ +package keeper + +import ( + "errors" + + corestore "cosmossdk.io/core/store" + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/log" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// Keeper defines the IBC connection keeper +type Keeper struct { + // implements gRPC QueryServer interface + types.QueryServer + + storeService corestore.KVStoreService + legacySubspace types.ParamSubspace + cdc codec.BinaryCodec + clientKeeper types.ClientKeeper +} + +// NewKeeper creates a new IBC connection Keeper instance +func NewKeeper(cdc codec.BinaryCodec, storeService corestore.KVStoreService, legacySubspace types.ParamSubspace, ck types.ClientKeeper) *Keeper { + return &Keeper{ + storeService: storeService, + cdc: cdc, + legacySubspace: legacySubspace, + clientKeeper: ck, + } +} + +// Logger returns a module-specific logger. +func (Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", "x/"+exported.ModuleName+"/"+types.SubModuleName) +} + +// GetCommitmentPrefix returns the IBC connection store prefix as a commitment +// Prefix +func (*Keeper) GetCommitmentPrefix() exported.Prefix { + return commitmenttypes.NewMerklePrefix([]byte(exported.StoreKey)) +} + +// GenerateConnectionIdentifier returns the next connection identifier. +func (k *Keeper) GenerateConnectionIdentifier(ctx sdk.Context) string { + nextConnSeq := k.GetNextConnectionSequence(ctx) + connectionID := types.FormatConnectionIdentifier(nextConnSeq) + + nextConnSeq++ + k.SetNextConnectionSequence(ctx, nextConnSeq) + return connectionID +} + +// GetConnection returns a connection with a particular identifier +func (k *Keeper) GetConnection(ctx sdk.Context, connectionID string) (types.ConnectionEnd, bool) { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(host.ConnectionKey(connectionID)) + if err != nil { + panic(err) + } + + if len(bz) == 0 { + return types.ConnectionEnd{}, false + } + + var connection types.ConnectionEnd + k.cdc.MustUnmarshal(bz, &connection) + + return connection, true +} + +// HasConnection returns a true if the connection with the given identifier +// exists in the store. +func (k *Keeper) HasConnection(ctx sdk.Context, connectionID string) bool { + store := k.storeService.OpenKVStore(ctx) + has, err := store.Has(host.ConnectionKey(connectionID)) + if err != nil { + return false + } + return has +} + +// SetConnection sets a connection to the store +func (k *Keeper) SetConnection(ctx sdk.Context, connectionID string, connection types.ConnectionEnd) { + store := k.storeService.OpenKVStore(ctx) + bz := k.cdc.MustMarshal(&connection) + if err := store.Set(host.ConnectionKey(connectionID), bz); err != nil { + panic(err) + } +} + +// GetClientConnectionPaths returns all the connection paths stored under a +// particular client +func (k *Keeper) GetClientConnectionPaths(ctx sdk.Context, clientID string) ([]string, bool) { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(host.ClientConnectionsKey(clientID)) + if err != nil { + panic(err) + } + + if len(bz) == 0 { + return nil, false + } + + var clientPaths types.ClientPaths + k.cdc.MustUnmarshal(bz, &clientPaths) + return clientPaths.Paths, true +} + +// SetClientConnectionPaths sets the connections paths for client +func (k *Keeper) SetClientConnectionPaths(ctx sdk.Context, clientID string, paths []string) { + store := k.storeService.OpenKVStore(ctx) + clientPaths := types.ClientPaths{Paths: paths} + bz := k.cdc.MustMarshal(&clientPaths) + if err := store.Set(host.ClientConnectionsKey(clientID), bz); err != nil { + panic(err) + } +} + +// GetNextConnectionSequence gets the next connection sequence from the store. +func (k *Keeper) GetNextConnectionSequence(ctx sdk.Context) uint64 { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get([]byte(types.KeyNextConnectionSequence)) + if err != nil { + panic(err) + } + + if len(bz) == 0 { + panic(errors.New("next connection sequence is nil")) + } + + return sdk.BigEndianToUint64(bz) +} + +// SetNextConnectionSequence sets the next connection sequence to the store. +func (k *Keeper) SetNextConnectionSequence(ctx sdk.Context, sequence uint64) { + store := k.storeService.OpenKVStore(ctx) + bz := sdk.Uint64ToBigEndian(sequence) + if err := store.Set([]byte(types.KeyNextConnectionSequence), bz); err != nil { + panic(err) + } +} + +// GetAllClientConnectionPaths returns all stored clients connection id paths. It +// will ignore the clients that haven't initialized a connection handshake since +// no paths are stored. +func (k *Keeper) GetAllClientConnectionPaths(ctx sdk.Context) []types.ConnectionPaths { + var allConnectionPaths []types.ConnectionPaths + k.clientKeeper.IterateClientStates(ctx, nil, func(clientID string, cs exported.ClientState) bool { + paths, found := k.GetClientConnectionPaths(ctx, clientID) + if !found { + // continue when connection handshake is not initialized + return false + } + connPaths := types.NewConnectionPaths(clientID, paths) + allConnectionPaths = append(allConnectionPaths, connPaths) + return false + }) + + return allConnectionPaths +} + +// IterateConnections provides an iterator over all ConnectionEnd objects. +// For each ConnectionEnd, cb will be called. If the cb returns true, the +// iterator will close and stop. +func (k *Keeper) IterateConnections(ctx sdk.Context, cb func(types.IdentifiedConnection) bool) { + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + + iterator := storetypes.KVStorePrefixIterator(store, []byte(host.KeyConnectionPrefix)) + + defer sdk.LogDeferred(k.Logger(ctx), func() error { return iterator.Close() }) + for ; iterator.Valid(); iterator.Next() { + var connection types.ConnectionEnd + k.cdc.MustUnmarshal(iterator.Value(), &connection) + + connectionID := host.MustParseConnectionPath(string(iterator.Key())) + identifiedConnection := types.NewIdentifiedConnection(connectionID, connection) + if cb(identifiedConnection) { + break + } + } +} + +// GetAllConnections returns all stored ConnectionEnd objects. +func (k *Keeper) GetAllConnections(ctx sdk.Context) (connections []types.IdentifiedConnection) { + k.IterateConnections(ctx, func(connection types.IdentifiedConnection) bool { + connections = append(connections, connection) + return false + }) + return connections +} + +// CreateSentinelLocalhostConnection creates and sets the sentinel localhost connection end in the IBC store. +func (k *Keeper) CreateSentinelLocalhostConnection(ctx sdk.Context) { + counterparty := types.NewCounterparty(exported.LocalhostClientID, exported.LocalhostConnectionID, commitmenttypes.NewMerklePrefix(k.GetCommitmentPrefix().Bytes())) + connectionEnd := types.NewConnectionEnd(types.OPEN, exported.LocalhostClientID, counterparty, types.GetCompatibleVersions(), 0) + + k.SetConnection(ctx, exported.LocalhostConnectionID, connectionEnd) +} + +// addConnectionToClient is used to add a connection identifier to the set of +// connections associated with a client. +func (k *Keeper) addConnectionToClient(ctx sdk.Context, clientID, connectionID string) error { + _, found := k.clientKeeper.GetClientState(ctx, clientID) + if !found { + return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) + } + + conns, found := k.GetClientConnectionPaths(ctx, clientID) + if !found { + conns = []string{} + } + + conns = append(conns, connectionID) + k.SetClientConnectionPaths(ctx, clientID, conns) + return nil +} + +// GetParams returns the total set of ibc-connection parameters. +func (k *Keeper) GetParams(ctx sdk.Context) types.Params { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get([]byte(types.ParamsKey)) + if err != nil { + panic(err) + } + + if bz == nil { // only panic on unset params and not on empty params + panic(errors.New("connection params are not set in store")) + } + + var params types.Params + k.cdc.MustUnmarshal(bz, ¶ms) + return params +} + +// SetParams sets the total set of ibc-connection parameters. +func (k *Keeper) SetParams(ctx sdk.Context, params types.Params) { + store := k.storeService.OpenKVStore(ctx) + bz := k.cdc.MustMarshal(¶ms) + if err := store.Set([]byte(types.ParamsKey), bz); err != nil { + panic(err) + } +} diff --git a/modules/core/03-connection/keeper/keeper_test.go b/modules/core/03-connection/keeper/keeper_test.go new file mode 100644 index 0000000..acd9989 --- /dev/null +++ b/modules/core/03-connection/keeper/keeper_test.go @@ -0,0 +1,181 @@ +package keeper_test + +import ( + "errors" + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +type KeeperTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains used for convenience and readability + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain +} + +func (suite *KeeperTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) +} + +func TestKeeperTestSuite(t *testing.T) { + testifysuite.Run(t, new(KeeperTestSuite)) +} + +func (suite *KeeperTestSuite) TestSetAndGetConnection() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + firstConnection := "connection-0" + + // check first connection does not exist + _, existed := suite.chainA.App.GetIBCKeeper().ConnectionKeeper.GetConnection(suite.chainA.GetContext(), firstConnection) + suite.Require().False(existed) + + path.CreateConnections() + _, existed = suite.chainA.App.GetIBCKeeper().ConnectionKeeper.GetConnection(suite.chainA.GetContext(), firstConnection) + suite.Require().True(existed) +} + +func (suite *KeeperTestSuite) TestSetAndGetClientConnectionPaths() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + _, existed := suite.chainA.App.GetIBCKeeper().ConnectionKeeper.GetClientConnectionPaths(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.False(existed) + + connections := []string{"connectionA", "connectionB"} + suite.chainA.App.GetIBCKeeper().ConnectionKeeper.SetClientConnectionPaths(suite.chainA.GetContext(), path.EndpointA.ClientID, connections) + paths, existed := suite.chainA.App.GetIBCKeeper().ConnectionKeeper.GetClientConnectionPaths(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.True(existed) + suite.EqualValues(connections, paths) +} + +// create 2 connections: A0 - B0, A1 - B1 +func (suite *KeeperTestSuite) TestGetAllConnections() { + path1 := ibctesting.NewPath(suite.chainA, suite.chainB) + path1.SetupConnections() + + path2 := ibctesting.NewPath(suite.chainA, suite.chainB) + path2.EndpointA.ClientID = path1.EndpointA.ClientID + path2.EndpointB.ClientID = path1.EndpointB.ClientID + + path2.CreateConnections() + + counterpartyB0 := types.NewCounterparty(path1.EndpointB.ClientID, path1.EndpointB.ConnectionID, suite.chainB.GetPrefix()) // connection B0 + counterpartyB1 := types.NewCounterparty(path2.EndpointB.ClientID, path2.EndpointB.ConnectionID, suite.chainB.GetPrefix()) // connection B1 + + conn1 := types.NewConnectionEnd(types.OPEN, path1.EndpointA.ClientID, counterpartyB0, types.GetCompatibleVersions(), 0) // A0 - B0 + conn2 := types.NewConnectionEnd(types.OPEN, path2.EndpointA.ClientID, counterpartyB1, types.GetCompatibleVersions(), 0) // A1 - B1 + + iconn1 := types.NewIdentifiedConnection(path1.EndpointA.ConnectionID, conn1) + iconn2 := types.NewIdentifiedConnection(path2.EndpointA.ConnectionID, conn2) + + suite.chainA.App.GetIBCKeeper().ConnectionKeeper.CreateSentinelLocalhostConnection(suite.chainA.GetContext()) + localhostConn, found := suite.chainA.App.GetIBCKeeper().ConnectionKeeper.GetConnection(suite.chainA.GetContext(), exported.LocalhostConnectionID) + suite.Require().True(found) + + expConnections := []types.IdentifiedConnection{iconn1, iconn2, types.NewIdentifiedConnection(exported.LocalhostConnectionID, localhostConn)} + + connections := suite.chainA.App.GetIBCKeeper().ConnectionKeeper.GetAllConnections(suite.chainA.GetContext()) + suite.Require().Len(connections, len(expConnections)) + suite.Require().Equal(expConnections, connections) +} + +// the test creates 2 clients path.EndpointA.ClientID0 and path.EndpointA.ClientID1. path.EndpointA.ClientID0 has a single +// connection and path.EndpointA.ClientID1 has 2 connections. +func (suite *KeeperTestSuite) TestGetAllClientConnectionPaths() { + path1 := ibctesting.NewPath(suite.chainA, suite.chainB) + path2 := ibctesting.NewPath(suite.chainA, suite.chainB) + path1.SetupConnections() + path2.SetupConnections() + + path3 := ibctesting.NewPath(suite.chainA, suite.chainB) + path3.EndpointA.ClientID = path2.EndpointA.ClientID + path3.EndpointB.ClientID = path2.EndpointB.ClientID + path3.CreateConnections() + + expPaths := []types.ConnectionPaths{ + types.NewConnectionPaths(path1.EndpointA.ClientID, []string{path1.EndpointA.ConnectionID}), + types.NewConnectionPaths(path2.EndpointA.ClientID, []string{path2.EndpointA.ConnectionID, path3.EndpointA.ConnectionID}), + } + + connPaths := suite.chainA.App.GetIBCKeeper().ConnectionKeeper.GetAllClientConnectionPaths(suite.chainA.GetContext()) + suite.Require().Len(connPaths, 2) + suite.Require().Equal(expPaths, connPaths) +} + +func (suite *KeeperTestSuite) TestLocalhostConnectionEndCreation() { + ctx := suite.chainA.GetContext() + connectionKeeper := suite.chainA.App.GetIBCKeeper().ConnectionKeeper + connectionKeeper.CreateSentinelLocalhostConnection(ctx) + + connectionEnd, found := connectionKeeper.GetConnection(ctx, exported.LocalhostConnectionID) + + suite.Require().True(found) + suite.Require().Equal(types.OPEN, connectionEnd.State) + suite.Require().Equal(exported.LocalhostClientID, connectionEnd.ClientId) + suite.Require().Equal(types.GetCompatibleVersions(), connectionEnd.Versions) + expectedCounterParty := types.NewCounterparty(exported.LocalhostClientID, exported.LocalhostConnectionID, commitmenttypes.NewMerklePrefix(connectionKeeper.GetCommitmentPrefix().Bytes())) + suite.Require().Equal(expectedCounterParty, connectionEnd.Counterparty) +} + +// TestDefaultSetParams tests the default params set are what is expected +func (suite *KeeperTestSuite) TestDefaultSetParams() { + expParams := types.DefaultParams() + + params := suite.chainA.App.GetIBCKeeper().ConnectionKeeper.GetParams(suite.chainA.GetContext()) + suite.Require().Equal(expParams, params) +} + +// TestSetAndGetParams tests that param setting and retrieval works properly +func (suite *KeeperTestSuite) TestSetAndGetParams() { + testCases := []struct { + name string + input types.Params + expErr error + }{ + {"success: set default params", types.DefaultParams(), nil}, + {"success: valid value for MaxExpectedTimePerBlock", types.NewParams(10), nil}, + {"failure: invalid value for MaxExpectedTimePerBlock", types.NewParams(0), errors.New("MaxExpectedTimePerBlock cannot be zero")}, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + ctx := suite.chainA.GetContext() + err := tc.input.Validate() + suite.chainA.GetSimApp().IBCKeeper.ConnectionKeeper.SetParams(ctx, tc.input) + if tc.expErr == nil { + suite.Require().NoError(err) + expected := tc.input + p := suite.chainA.GetSimApp().IBCKeeper.ConnectionKeeper.GetParams(ctx) + suite.Require().Equal(expected, p) + } else { + suite.Require().Error(err) + suite.Require().Equal(err.Error(), tc.expErr.Error()) + } + }) + } +} + +// TestUnsetParams tests that trying to get params that are not set panics. +func (suite *KeeperTestSuite) TestUnsetParams() { + suite.SetupTest() + ctx := suite.chainA.GetContext() + store := ctx.KVStore(suite.chainA.GetSimApp().GetKey(exported.StoreKey)) + store.Delete([]byte(types.ParamsKey)) + + suite.Require().Panics(func() { + suite.chainA.GetSimApp().IBCKeeper.ConnectionKeeper.GetParams(ctx) + }) +} diff --git a/modules/core/03-connection/keeper/migrations.go b/modules/core/03-connection/keeper/migrations.go new file mode 100644 index 0000000..d011ff4 --- /dev/null +++ b/modules/core/03-connection/keeper/migrations.go @@ -0,0 +1,40 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + connectionv7 "github.com/cosmos/ibc-go/v10/modules/core/03-connection/migrations/v7" + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" +) + +// Migrator is a struct for handling in-place store migrations. +type Migrator struct { + keeper *Keeper +} + +// NewMigrator returns a new Migrator. +func NewMigrator(keeper *Keeper) Migrator { + return Migrator{keeper: keeper} +} + +// Migrate3to4 migrates from version 3 to 4. +// This migration writes the sentinel localhost connection end to state. +func (m Migrator) Migrate3to4(ctx sdk.Context) error { + connectionv7.MigrateLocalhostConnection(ctx, m.keeper) + return nil +} + +// MigrateParams migrates from consensus version 4 to 5. +// This migration takes the parameters that are currently stored and managed by x/params +// and stores them directly in the ibc module's state. +func (m Migrator) MigrateParams(ctx sdk.Context) error { + var params types.Params + m.keeper.legacySubspace.GetParamSet(ctx, ¶ms) + if err := params.Validate(); err != nil { + return err + } + + m.keeper.SetParams(ctx, params) + m.keeper.Logger(ctx).Info("successfully migrated connection to self-manage params") + return nil +} diff --git a/modules/core/03-connection/keeper/migrations_test.go b/modules/core/03-connection/keeper/migrations_test.go new file mode 100644 index 0000000..74e882f --- /dev/null +++ b/modules/core/03-connection/keeper/migrations_test.go @@ -0,0 +1,42 @@ +package keeper_test + +import ( + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/keeper" + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// TestMigrateParams tests that the params for the connection are properly migrated +func (suite *KeeperTestSuite) TestMigrateParams() { + testCases := []struct { + name string + malleate func() + expectedParams types.Params + }{ + { + "success: default params", + func() { + params := types.DefaultParams() + subspace := suite.chainA.GetSimApp().GetSubspace(ibcexported.ModuleName) + subspace.SetParamSet(suite.chainA.GetContext(), ¶ms) + }, + types.DefaultParams(), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + tc.malleate() + + ctx := suite.chainA.GetContext() + migrator := keeper.NewMigrator(suite.chainA.GetSimApp().IBCKeeper.ConnectionKeeper) + err := migrator.MigrateParams(ctx) + suite.Require().NoError(err) + + params := suite.chainA.GetSimApp().IBCKeeper.ConnectionKeeper.GetParams(ctx) + suite.Require().Equal(tc.expectedParams, params) + }) + } +} diff --git a/modules/core/03-connection/keeper/verify.go b/modules/core/03-connection/keeper/verify.go new file mode 100644 index 0000000..17066b9 --- /dev/null +++ b/modules/core/03-connection/keeper/verify.go @@ -0,0 +1,227 @@ +package keeper + +import ( + "math" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// VerifyConnectionState verifies a proof of the connection state of the +// specified connection end stored on the target machine. +func (k *Keeper) VerifyConnectionState( + ctx sdk.Context, + connection types.ConnectionEnd, + height exported.Height, + proof []byte, + connectionID string, + counterpartyConnection types.ConnectionEnd, // opposite connection +) error { + clientID := connection.ClientId + merklePath := commitmenttypes.NewMerklePath(host.ConnectionKey(connectionID)) + merklePath, err := commitmenttypes.ApplyPrefix(connection.Counterparty.Prefix, merklePath) + if err != nil { + return err + } + + bz, err := k.cdc.Marshal(&counterpartyConnection) + if err != nil { + return err + } + + if err := k.clientKeeper.VerifyMembership( + ctx, clientID, height, + 0, 0, // skip delay period checks for non-packet processing verification + proof, merklePath, bz, + ); err != nil { + return errorsmod.Wrapf(err, "failed connection state verification for client (%s)", clientID) + } + + return nil +} + +// VerifyChannelState verifies a proof of the channel state of the specified +// channel end, under the specified port, stored on the target machine. +func (k *Keeper) VerifyChannelState( + ctx sdk.Context, + connection types.ConnectionEnd, + height exported.Height, + proof []byte, + portID, + channelID string, + channel channeltypes.Channel, +) error { + clientID := connection.ClientId + merklePath := commitmenttypes.NewMerklePath(host.ChannelKey(portID, channelID)) + merklePath, err := commitmenttypes.ApplyPrefix(connection.Counterparty.Prefix, merklePath) + if err != nil { + return err + } + + bz, err := k.cdc.Marshal(&channel) + if err != nil { + return err + } + + if err := k.clientKeeper.VerifyMembership( + ctx, clientID, height, + 0, 0, // skip delay period checks for non-packet processing verification + proof, merklePath, bz, + ); err != nil { + return errorsmod.Wrapf(err, "failed channel state verification for client (%s)", clientID) + } + + return nil +} + +// VerifyPacketCommitment verifies a proof of an outgoing packet commitment at +// the specified port, specified channel, and specified sequence. +func (k *Keeper) VerifyPacketCommitment( + ctx sdk.Context, + connection types.ConnectionEnd, + height exported.Height, + proof []byte, + portID, + channelID string, + sequence uint64, + commitmentBytes []byte, +) error { + clientID := connection.ClientId + // get time and block delays + timeDelay := connection.DelayPeriod + blockDelay := k.getBlockDelay(ctx, connection) + + merklePath := commitmenttypes.NewMerklePath(host.PacketCommitmentKey(portID, channelID, sequence)) + merklePath, err := commitmenttypes.ApplyPrefix(connection.Counterparty.Prefix, merklePath) + if err != nil { + return err + } + + if err := k.clientKeeper.VerifyMembership( + ctx, clientID, height, timeDelay, blockDelay, proof, merklePath, commitmentBytes, + ); err != nil { + return errorsmod.Wrapf(err, "failed packet commitment verification for client (%s)", clientID) + } + + return nil +} + +// VerifyPacketAcknowledgement verifies a proof of an incoming packet +// acknowledgement at the specified port, specified channel, and specified sequence. +func (k *Keeper) VerifyPacketAcknowledgement( + ctx sdk.Context, + connection types.ConnectionEnd, + height exported.Height, + proof []byte, + portID, + channelID string, + sequence uint64, + acknowledgement []byte, +) error { + clientID := connection.ClientId + // get time and block delays + timeDelay := connection.DelayPeriod + blockDelay := k.getBlockDelay(ctx, connection) + + merklePath := commitmenttypes.NewMerklePath(host.PacketAcknowledgementKey(portID, channelID, sequence)) + merklePath, err := commitmenttypes.ApplyPrefix(connection.Counterparty.Prefix, merklePath) + if err != nil { + return err + } + + if err := k.clientKeeper.VerifyMembership( + ctx, clientID, height, timeDelay, blockDelay, + proof, merklePath, channeltypes.CommitAcknowledgement(acknowledgement), + ); err != nil { + return errorsmod.Wrapf(err, "failed packet acknowledgement verification for client (%s)", clientID) + } + + return nil +} + +// VerifyPacketReceiptAbsence verifies a proof of the absence of an +// incoming packet receipt at the specified port, specified channel, and +// specified sequence. +func (k *Keeper) VerifyPacketReceiptAbsence( + ctx sdk.Context, + connection types.ConnectionEnd, + height exported.Height, + proof []byte, + portID, + channelID string, + sequence uint64, +) error { + clientID := connection.ClientId + // get time and block delays + timeDelay := connection.DelayPeriod + blockDelay := k.getBlockDelay(ctx, connection) + + merklePath := commitmenttypes.NewMerklePath(host.PacketReceiptKey(portID, channelID, sequence)) + merklePath, err := commitmenttypes.ApplyPrefix(connection.Counterparty.Prefix, merklePath) + if err != nil { + return err + } + + if err := k.clientKeeper.VerifyNonMembership( + ctx, clientID, height, timeDelay, blockDelay, proof, merklePath, + ); err != nil { + return errorsmod.Wrapf(err, "failed packet receipt absence verification for client (%s)", clientID) + } + + return nil +} + +// VerifyNextSequenceRecv verifies a proof of the next sequence number to be +// received of the specified channel at the specified port. +func (k *Keeper) VerifyNextSequenceRecv( + ctx sdk.Context, + connection types.ConnectionEnd, + height exported.Height, + proof []byte, + portID, + channelID string, + nextSequenceRecv uint64, +) error { + clientID := connection.ClientId + // get time and block delays + timeDelay := connection.DelayPeriod + blockDelay := k.getBlockDelay(ctx, connection) + + merklePath := commitmenttypes.NewMerklePath(host.NextSequenceRecvKey(portID, channelID)) + merklePath, err := commitmenttypes.ApplyPrefix(connection.Counterparty.Prefix, merklePath) + if err != nil { + return err + } + + if err := k.clientKeeper.VerifyMembership( + ctx, clientID, height, + timeDelay, blockDelay, + proof, merklePath, sdk.Uint64ToBigEndian(nextSequenceRecv), + ); err != nil { + return errorsmod.Wrapf(err, "failed next sequence receive verification for client (%s)", clientID) + } + + return nil +} + +// getBlockDelay calculates the block delay period from the time delay of the connection +// and the maximum expected time per block. +func (k *Keeper) getBlockDelay(ctx sdk.Context, connection types.ConnectionEnd) uint64 { + // expectedTimePerBlock should never be zero, however if it is then return a 0 block delay for safety + // as the expectedTimePerBlock parameter was not set. + expectedTimePerBlock := k.GetParams(ctx).MaxExpectedTimePerBlock + if expectedTimePerBlock == 0 { + return 0 + } + // calculate minimum block delay by dividing time delay period + // by the expected time per block. Round up the block delay. + timeDelay := connection.DelayPeriod + return uint64(math.Ceil(float64(timeDelay) / float64(expectedTimePerBlock))) +} diff --git a/modules/core/03-connection/keeper/verify_test.go b/modules/core/03-connection/keeper/verify_test.go new file mode 100644 index 0000000..aeedc9c --- /dev/null +++ b/modules/core/03-connection/keeper/verify_test.go @@ -0,0 +1,518 @@ +package keeper_test + +import ( + "fmt" + "time" + + errorsmod "cosmossdk.io/errors" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + ibcmock "github.com/cosmos/ibc-go/v10/testing/mock" +) + +var defaultTimeoutHeight = clienttypes.NewHeight(1, 100000) + +// TestVerifyConnectionState verifies the connection state of the connection +// on chainB. The connections on chainA and chainB are fully opened. +func (suite *KeeperTestSuite) TestVerifyConnectionState() { + var ( + path *ibctesting.Path + heightDiff uint64 + ) + cases := []struct { + name string + malleate func() + expErr error + }{ + {"verification success", func() {}, nil}, + {"consensus state not found - increased proof height", func() { + heightDiff = 5 + }, errorsmod.Wrap(ibcerrors.ErrInvalidHeight, "failed connection state verification for client (07-tendermint-0): client state height < proof height ({1 9} < {1 14}), please ensure the client has been updated")}, + {"verification failed - connection state is different than proof", func() { + path.EndpointA.UpdateConnection(func(c *types.ConnectionEnd) { c.State = types.TRYOPEN }) + }, errorsmod.Wrap(ibcerrors.ErrInvalidHeight, "failed connection state verification for client (07-tendermint-0): client state height < proof height ({1 9} < {1 14}), please ensure the client has been updated")}, + {"client status is not active - client is expired", func() { + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + clientState.FrozenHeight = clienttypes.NewHeight(0, 1) + path.EndpointA.SetClientState(clientState) + }, errorsmod.Wrap(clienttypes.ErrClientNotActive, "client (07-tendermint-0) status is Frozen")}, + } + + for _, tc := range cases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupConnections() + + connectionKey := host.ConnectionKey(path.EndpointB.ConnectionID) + proof, proofHeight := suite.chainB.QueryProof(connectionKey) + + tc.malleate() + + connection := path.EndpointA.GetConnection() + + expectedConnection := path.EndpointB.GetConnection() + + err := suite.chainA.App.GetIBCKeeper().ConnectionKeeper.VerifyConnectionState( + suite.chainA.GetContext(), connection, + malleateHeight(proofHeight, heightDiff), proof, path.EndpointB.ConnectionID, expectedConnection, + ) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +// TestVerifyChannelState verifies the channel state of the channel on +// chainB. The channels on chainA and chainB are fully opened. +func (suite *KeeperTestSuite) TestVerifyChannelState() { + var ( + path *ibctesting.Path + heightDiff uint64 + ) + cases := []struct { + name string + malleate func() + expErr error + }{ + {"verification success", func() {}, nil}, + {"consensus state not found - increased proof height", func() { + heightDiff = 5 + }, errorsmod.Wrap(ibcerrors.ErrInvalidHeight, "failed channel state verification for client (07-tendermint-0): client state height < proof height ({1 15} < {1 20}), please ensure the client has been updated")}, + {"verification failed - changed channel state", func() { + path.EndpointA.UpdateChannel(func(channel *channeltypes.Channel) { channel.State = channeltypes.TRYOPEN }) + }, errorsmod.Wrap(ibcerrors.ErrInvalidHeight, "failed channel state verification for client (07-tendermint-0): client state height < proof height ({1 15} < {1 20}), please ensure the client has been updated")}, + {"client status is not active - client is expired", func() { + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + clientState.FrozenHeight = clienttypes.NewHeight(0, 1) + path.EndpointA.SetClientState(clientState) + }, errorsmod.Wrap(clienttypes.ErrClientNotActive, "client (07-tendermint-0) status is Frozen")}, + } + + for _, tc := range cases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + suite.SetupTest() // reset + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + channelKey := host.ChannelKey(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + proof, proofHeight := suite.chainB.QueryProof(channelKey) + + tc.malleate() + connection := path.EndpointA.GetConnection() + + channel := path.EndpointB.GetChannel() + + err := suite.chainA.App.GetIBCKeeper().ConnectionKeeper.VerifyChannelState( + suite.chainA.GetContext(), connection, malleateHeight(proofHeight, heightDiff), proof, + path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, channel, + ) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +// TestVerifyPacketCommitment has chainB verify the packet commitment +// on channelA. The channels on chainA and chainB are fully opened and a +// packet is sent from chainA to chainB, but has not been received. +func (suite *KeeperTestSuite) TestVerifyPacketCommitment() { + var ( + path *ibctesting.Path + packet channeltypes.Packet + heightDiff uint64 + delayTimePeriod uint64 + timePerBlock uint64 + ) + cases := []struct { + name string + malleate func() + expErr error + }{ + {"verification success", func() {}, nil}, + {"verification success: delay period passed", func() { + delayTimePeriod = uint64(1 * time.Second.Nanoseconds()) + }, nil}, + {"delay time period has not passed", func() { + delayTimePeriod = uint64(1 * time.Hour.Nanoseconds()) + }, errorsmod.Wrap(ibctm.ErrDelayPeriodNotPassed, "failed packet commitment verification for client (07-tendermint-0): cannot verify packet until time: 1577926940000000000, current time: 1577923345000000000")}, + {"delay block period has not passed", func() { + // make timePerBlock 1 nanosecond so that block delay is not passed. + // must also set a non-zero time delay to ensure block delay is enforced. + delayTimePeriod = uint64(1 * time.Second.Nanoseconds()) + timePerBlock = 1 + }, errorsmod.Wrap(ibctm.ErrDelayPeriodNotPassed, "failed packet commitment verification for client (07-tendermint-0): cannot verify packet until height: 1-1000000016, current height: 1-17")}, + {"consensus state not found - increased proof height", func() { + heightDiff = 5 + }, errorsmod.Wrap(ibcerrors.ErrInvalidHeight, "failed packet commitment verification for client (07-tendermint-0): client state height < proof height ({1 17} < {1 22}), please ensure the client has been updated")}, + {"verification failed - changed packet commitment state", func() { + packet.Data = []byte(ibctesting.InvalidID) + }, errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "failed packet commitment verification for client (07-tendermint-0): failed to verify membership proof at index 0: provided value doesn't match proof")}, + {"client status is not active - client is expired", func() { + clientState, ok := path.EndpointB.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + clientState.FrozenHeight = clienttypes.NewHeight(0, 1) + path.EndpointB.SetClientState(clientState) + }, errorsmod.Wrap(clienttypes.ErrClientNotActive, "client (07-tendermint-0) status is Frozen")}, + } + + for _, tc := range cases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, 0) + + // reset variables + heightDiff = 0 + delayTimePeriod = 0 + timePerBlock = 0 + tc.malleate() + + connection := path.EndpointB.GetConnection() + connection.DelayPeriod = delayTimePeriod + commitmentKey := host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + proof, proofHeight := suite.chainA.QueryProof(commitmentKey) + + // set time per block param + if timePerBlock != 0 { + suite.chainB.App.GetIBCKeeper().ConnectionKeeper.SetParams(suite.chainB.GetContext(), types.NewParams(timePerBlock)) + } + + commitment := channeltypes.CommitPacket(packet) + err = suite.chainB.App.GetIBCKeeper().ConnectionKeeper.VerifyPacketCommitment( + suite.chainB.GetContext(), connection, malleateHeight(proofHeight, heightDiff), proof, + packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(), commitment, + ) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +// TestVerifyPacketAcknowledgement has chainA verify the acknowledgement on +// channelB. The channels on chainA and chainB are fully opened and a packet +// is sent from chainA to chainB and received. +func (suite *KeeperTestSuite) TestVerifyPacketAcknowledgement() { + var ( + path *ibctesting.Path + ack exported.Acknowledgement + heightDiff uint64 + delayTimePeriod uint64 + timePerBlock uint64 + ) + + cases := []struct { + name string + malleate func() + expErr error + }{ + {"verification success", func() {}, nil}, + {"verification success: delay period passed", func() { + delayTimePeriod = uint64(1 * time.Second.Nanoseconds()) + }, nil}, + {"delay time period has not passed", func() { + delayTimePeriod = uint64(1 * time.Hour.Nanoseconds()) + }, errorsmod.Wrap(ibctm.ErrDelayPeriodNotPassed, "failed packet acknowledgement verification for client (07-tendermint-0): cannot verify packet until time: 1577934160000000000, current time: 1577930565000000000")}, + {"delay block period has not passed", func() { + // make timePerBlock 1 nanosecond so that block delay is not passed. + // must also set a non-zero time delay to ensure block delay is enforced. + delayTimePeriod = uint64(1 * time.Second.Nanoseconds()) + timePerBlock = 1 + }, errorsmod.Wrap(ibctm.ErrDelayPeriodNotPassed, "failed packet acknowledgement verification for client (07-tendermint-0): cannot verify packet until height: 1-1000000018, current height: 1-19")}, + {"consensus state not found - increased proof height", func() { + heightDiff = 5 + }, errorsmod.Wrap(ibcerrors.ErrInvalidHeight, "failed packet acknowledgement verification for client (07-tendermint-0): client state height < proof height ({1 19} < {1 24}), please ensure the client has been updated")}, + {"verification failed - changed acknowledgement", func() { + ack = ibcmock.MockFailAcknowledgement + }, errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "failed packet acknowledgement verification for client (07-tendermint-0): failed to verify membership proof at index 0: provided value doesn't match proof")}, + {"client status is not active - client is expired", func() { + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + clientState.FrozenHeight = clienttypes.NewHeight(0, 1) + path.EndpointA.SetClientState(clientState) + }, errorsmod.Wrap(clienttypes.ErrClientNotActive, "client (07-tendermint-0) status is Frozen")}, + } + + for _, tc := range cases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + ack = ibcmock.MockAcknowledgement // must be explicitly changed + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + // send and receive packet + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + // increment receiving chain's (chainB) time by 2 hour to always pass receive + suite.coordinator.IncrementTimeBy(time.Hour * 2) + suite.coordinator.CommitBlock(suite.chainB) + + packet := channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, 0) + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + + packetAckKey := host.PacketAcknowledgementKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + proof, proofHeight := suite.chainB.QueryProof(packetAckKey) + + // reset variables + heightDiff = 0 + delayTimePeriod = 0 + timePerBlock = 0 + tc.malleate() + + connection := path.EndpointA.GetConnection() + connection.DelayPeriod = delayTimePeriod + + // set time per block param + if timePerBlock != 0 { + suite.chainA.App.GetIBCKeeper().ConnectionKeeper.SetParams(suite.chainA.GetContext(), types.NewParams(timePerBlock)) + } + + err = suite.chainA.App.GetIBCKeeper().ConnectionKeeper.VerifyPacketAcknowledgement( + suite.chainA.GetContext(), connection, malleateHeight(proofHeight, heightDiff), proof, + packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), ack.Acknowledgement(), + ) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.ErrorIs(err, tc.expErr) + } + }) + } +} + +// TestVerifyPacketReceiptAbsence has chainA verify the receipt +// absence on channelB. The channels on chainA and chainB are fully opened and +// a packet is sent from chainA to chainB and not received. +func (suite *KeeperTestSuite) TestVerifyPacketReceiptAbsence() { + var ( + path *ibctesting.Path + packet channeltypes.Packet + heightDiff uint64 + delayTimePeriod uint64 + timePerBlock uint64 + ) + + cases := []struct { + name string + malleate func() + expErr error + }{ + {"verification success", func() {}, nil}, + {"verification success: delay period passed", func() { + delayTimePeriod = uint64(1 * time.Second.Nanoseconds()) + }, nil}, + {"delay time period has not passed", func() { + delayTimePeriod = uint64(1 * time.Hour.Nanoseconds()) + }, errorsmod.Wrap(ibctm.ErrDelayPeriodNotPassed, "failed packet commitment verification for client (07-tendermint-0): cannot verify packet until time: 1577926940000000000, current time: 1577923345000000000")}, + {"delay block period has not passed", func() { + // make timePerBlock 1 nanosecond so that block delay is not passed. + // must also set a non-zero time delay to ensure block delay is enforced. + delayTimePeriod = uint64(1 * time.Second.Nanoseconds()) + timePerBlock = 1 + }, errorsmod.Wrap(ibctm.ErrDelayPeriodNotPassed, "failed packet commitment verification for client (07-tendermint-0): cannot verify packet until height: 1-1000000016, current height: 1-17")}, + {"consensus state not found - increased proof height", func() { + heightDiff = 5 + }, errorsmod.Wrap(ibcerrors.ErrInvalidHeight, "failed packet commitment verification for client (07-tendermint-0): client state height < proof height ({1 17} < {1 22}), please ensure the client has been updated")}, + {"verification failed - acknowledgement was received", func() { + // increment receiving chain's (chainB) time by 2 hour to always pass receive + suite.coordinator.IncrementTimeBy(time.Hour * 2) + suite.coordinator.CommitBlock(suite.chainB) + + err := path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + }, errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "failed packet commitment verification for client (07-tendermint-0): failed to verify membership proof at index 0: provided value doesn't match proof")}, + {"client status is not active - client is expired", func() { + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + clientState.FrozenHeight = clienttypes.NewHeight(0, 1) + path.EndpointA.SetClientState(clientState) + }, errorsmod.Wrap(clienttypes.ErrClientNotActive, "client (07-tendermint-0) status is Frozen")}, + } + + for _, tc := range cases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + // send, only receive in malleate if applicable + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, 0) + + // reset variables + heightDiff = 0 + delayTimePeriod = 0 + timePerBlock = 0 + tc.malleate() + + connection := path.EndpointA.GetConnection() + connection.DelayPeriod = delayTimePeriod + + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + if clientState.FrozenHeight.IsZero() { + // need to update height to prove absence or receipt + suite.coordinator.CommitBlock(suite.chainA, suite.chainB) + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + } + + packetReceiptKey := host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + proof, proofHeight := suite.chainB.QueryProof(packetReceiptKey) + + // set time per block param + if timePerBlock != 0 { + suite.chainA.App.GetIBCKeeper().ConnectionKeeper.SetParams(suite.chainA.GetContext(), types.NewParams(timePerBlock)) + } + + err = suite.chainA.App.GetIBCKeeper().ConnectionKeeper.VerifyPacketReceiptAbsence( + suite.chainA.GetContext(), connection, malleateHeight(proofHeight, heightDiff), proof, + packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), + ) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +// TestVerifyNextSequenceRecv has chainA verify the next sequence receive on +// channelB. The channels on chainA and chainB are fully opened and a packet +// is sent from chainA to chainB and received. +func (suite *KeeperTestSuite) TestVerifyNextSequenceRecv() { + var ( + path *ibctesting.Path + heightDiff uint64 + delayTimePeriod uint64 + timePerBlock uint64 + offsetSeq uint64 + ) + + cases := []struct { + name string + malleate func() + expErr error + }{ + {"verification success", func() {}, nil}, + {"verification success: delay period passed", func() { + delayTimePeriod = uint64(1 * time.Second.Nanoseconds()) + }, nil}, + {"delay time period has not passed", func() { + delayTimePeriod = uint64(1 * time.Hour.Nanoseconds()) + }, errorsmod.Wrap(ibctm.ErrDelayPeriodNotPassed, "failed packet commitment verification for client (07-tendermint-0): cannot verify packet until time: 1577926940000000000, current time: 1577923345000000000")}, + {"delay block period has not passed", func() { + // make timePerBlock 1 nanosecond so that block delay is not passed. + // must also set a non-zero time delay to ensure block delay is enforced. + delayTimePeriod = uint64(1 * time.Second.Nanoseconds()) + timePerBlock = 1 + }, errorsmod.Wrap(ibctm.ErrDelayPeriodNotPassed, "failed packet commitment verification for client (07-tendermint-0): cannot verify packet until height: 1-1000000016, current height: 1-17")}, + {"consensus state not found - increased proof height", func() { + heightDiff = 5 + }, errorsmod.Wrap(ibcerrors.ErrInvalidHeight, "failed packet commitment verification for client (07-tendermint-0): client state height < proof height ({1 17} < {1 22}), please ensure the client has been updated")}, + {"verification failed - wrong expected next seq recv", func() { + offsetSeq = 1 + }, errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "failed packet commitment verification for client (07-tendermint-0): failed to verify membership proof at index 0: provided value doesn't match proof")}, + {"client status is not active - client is expired", func() { + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + clientState.FrozenHeight = clienttypes.NewHeight(0, 1) + path.EndpointA.SetClientState(clientState) + }, errorsmod.Wrap(clienttypes.ErrClientNotActive, "client (07-tendermint-0) status is Frozen")}, + } + + for _, tc := range cases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + // send and receive packet + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + // increment receiving chain's (chainB) time by 2 hour to always pass receive + suite.coordinator.IncrementTimeBy(time.Hour * 2) + suite.coordinator.CommitBlock(suite.chainB) + + packet := channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, 0) + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + + nextSeqRecvKey := host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) + proof, proofHeight := suite.chainB.QueryProof(nextSeqRecvKey) + + // reset variables + heightDiff = 0 + delayTimePeriod = 0 + timePerBlock = 0 + tc.malleate() + + // set time per block param + if timePerBlock != 0 { + suite.chainA.App.GetIBCKeeper().ConnectionKeeper.SetParams(suite.chainA.GetContext(), types.NewParams(timePerBlock)) + } + + connection := path.EndpointA.GetConnection() + connection.DelayPeriod = delayTimePeriod + err = suite.chainA.App.GetIBCKeeper().ConnectionKeeper.VerifyNextSequenceRecv( + suite.chainA.GetContext(), connection, malleateHeight(proofHeight, heightDiff), proof, + packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()+offsetSeq, + ) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func malleateHeight(height exported.Height, diff uint64) exported.Height { + return clienttypes.NewHeight(height.GetRevisionNumber(), height.GetRevisionHeight()+diff) +} diff --git a/modules/core/03-connection/migrations/v7/expected_keepers.go b/modules/core/03-connection/migrations/v7/expected_keepers.go new file mode 100644 index 0000000..4c53bbc --- /dev/null +++ b/modules/core/03-connection/migrations/v7/expected_keepers.go @@ -0,0 +1,8 @@ +package v7 + +import sdk "github.com/cosmos/cosmos-sdk/types" + +// ConnectionKeeper expected IBC connection keeper +type ConnectionKeeper interface { + CreateSentinelLocalhostConnection(ctx sdk.Context) +} diff --git a/modules/core/03-connection/migrations/v7/localhost.go b/modules/core/03-connection/migrations/v7/localhost.go new file mode 100644 index 0000000..9a09e69 --- /dev/null +++ b/modules/core/03-connection/migrations/v7/localhost.go @@ -0,0 +1,9 @@ +package v7 + +import sdk "github.com/cosmos/cosmos-sdk/types" + +// MigrateLocalhostConnection creates the sentinel localhost connection end to enable +// localhost ibc functionality. +func MigrateLocalhostConnection(ctx sdk.Context, connectionKeeper ConnectionKeeper) { + connectionKeeper.CreateSentinelLocalhostConnection(ctx) +} diff --git a/modules/core/03-connection/module.go b/modules/core/03-connection/module.go new file mode 100644 index 0000000..c0af619 --- /dev/null +++ b/modules/core/03-connection/module.go @@ -0,0 +1,18 @@ +package connection + +import ( + "github.com/spf13/cobra" + + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/client/cli" + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" +) + +// Name returns the IBC connection ICS name. +func Name() string { + return types.SubModuleName +} + +// GetQueryCmd returns the root query command for the IBC connections. +func GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd() +} diff --git a/modules/core/03-connection/simulation/decoder.go b/modules/core/03-connection/simulation/decoder.go new file mode 100644 index 0000000..bb7ee59 --- /dev/null +++ b/modules/core/03-connection/simulation/decoder.go @@ -0,0 +1,33 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's +// Value to the corresponding connection type. +func NewDecodeStore(cdc codec.BinaryCodec, kvA, kvB kv.Pair) (string, bool) { + switch { + case bytes.HasPrefix(kvA.Key, host.KeyClientStorePrefix) && bytes.HasSuffix(kvA.Key, []byte(host.KeyConnectionPrefix)): + var clientConnectionsA, clientConnectionsB types.ClientPaths + cdc.MustUnmarshal(kvA.Value, &clientConnectionsA) + cdc.MustUnmarshal(kvB.Value, &clientConnectionsB) + return fmt.Sprintf("ClientPaths A: %v\nClientPaths B: %v", clientConnectionsA, clientConnectionsB), true + + case bytes.HasPrefix(kvA.Key, []byte(host.KeyConnectionPrefix)): + var connectionA, connectionB types.ConnectionEnd + cdc.MustUnmarshal(kvA.Value, &connectionA) + cdc.MustUnmarshal(kvB.Value, &connectionB) + return fmt.Sprintf("ConnectionEnd A: %v\nConnectionEnd B: %v", connectionA, connectionB), true + + default: + return "", false + } +} diff --git a/modules/core/03-connection/simulation/decoder_test.go b/modules/core/03-connection/simulation/decoder_test.go new file mode 100644 index 0000000..d2a7703 --- /dev/null +++ b/modules/core/03-connection/simulation/decoder_test.go @@ -0,0 +1,69 @@ +package simulation_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/simulation" + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/testing/simapp" +) + +func TestDecodeStore(t *testing.T) { + app := simapp.Setup(t, false) + cdc := app.AppCodec() + + connectionID := "connectionidone" + + connection := types.ConnectionEnd{ + ClientId: "clientidone", + Versions: types.GetCompatibleVersions(), + } + + paths := types.ClientPaths{ + Paths: []string{connectionID}, + } + + kvPairs := kv.Pairs{ + Pairs: []kv.Pair{ + { + Key: host.ClientConnectionsKey(connection.ClientId), + Value: cdc.MustMarshal(&paths), + }, + { + Key: host.ConnectionKey(connectionID), + Value: cdc.MustMarshal(&connection), + }, + { + Key: []byte{0x99}, + Value: []byte{0x99}, + }, + }, + } + tests := []struct { + name string + expectedLog string + }{ + {"ClientPaths", fmt.Sprintf("ClientPaths A: %v\nClientPaths B: %v", paths, paths)}, + {"ConnectionEnd", fmt.Sprintf("ConnectionEnd A: %v\nConnectionEnd B: %v", connection, connection)}, + {"other", ""}, + } + + for i, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + res, found := simulation.NewDecodeStore(cdc, kvPairs.Pairs[i], kvPairs.Pairs[i]) + if i == len(tests)-1 { + require.False(t, found, string(kvPairs.Pairs[i].Key)) + require.Empty(t, res, string(kvPairs.Pairs[i].Key)) + } else { + require.True(t, found, string(kvPairs.Pairs[i].Key)) + require.Equal(t, tt.expectedLog, res, string(kvPairs.Pairs[i].Key)) + } + }) + } +} diff --git a/modules/core/03-connection/simulation/genesis.go b/modules/core/03-connection/simulation/genesis.go new file mode 100644 index 0000000..5c3c023 --- /dev/null +++ b/modules/core/03-connection/simulation/genesis.go @@ -0,0 +1,14 @@ +package simulation + +import ( + "math/rand" + + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" +) + +// GenConnectionGenesis returns the default connection genesis state. +func GenConnectionGenesis(_ *rand.Rand, _ []simtypes.Account) types.GenesisState { + return types.DefaultGenesisState() +} diff --git a/modules/core/03-connection/types/codec.go b/modules/core/03-connection/types/codec.go new file mode 100644 index 0000000..2e14114 --- /dev/null +++ b/modules/core/03-connection/types/codec.go @@ -0,0 +1,30 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/msgservice" +) + +// RegisterInterfaces register the ibc interfaces submodule implementations to protobuf +// Any. +func RegisterInterfaces(registry codectypes.InterfaceRegistry) { + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgConnectionOpenInit{}, + &MsgConnectionOpenTry{}, + &MsgConnectionOpenAck{}, + &MsgConnectionOpenConfirm{}, + &MsgUpdateParams{}, + ) + + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} + +// SubModuleCdc references the global x/ibc/core/03-connection module codec. Note, the codec should +// ONLY be used in certain instances of tests and for JSON encoding. +// +// The actual codec used for serialization should be provided to x/ibc/core/03-connection and +// defined at the application level. +var SubModuleCdc = codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) diff --git a/modules/core/03-connection/types/codec_test.go b/modules/core/03-connection/types/codec_test.go new file mode 100644 index 0000000..2bd45e6 --- /dev/null +++ b/modules/core/03-connection/types/codec_test.go @@ -0,0 +1,68 @@ +package types_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + + ibc "github.com/cosmos/ibc-go/v10/modules/core" + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" +) + +func TestCodecTypeRegistration(t *testing.T) { + testCases := []struct { + name string + typeURL string + expError error + }{ + { + "success: MsgConnectionOpenInit", + sdk.MsgTypeURL(&types.MsgConnectionOpenInit{}), + nil, + }, + { + "success: MsgConnectionOpenTry", + sdk.MsgTypeURL(&types.MsgConnectionOpenTry{}), + nil, + }, + { + "success: MsgConnectionOpenAck", + sdk.MsgTypeURL(&types.MsgConnectionOpenAck{}), + nil, + }, + { + "success: MsgConnectionOpenConfirm", + sdk.MsgTypeURL(&types.MsgConnectionOpenConfirm{}), + nil, + }, + { + "success: MsgUpdateParams", + sdk.MsgTypeURL(&types.MsgUpdateParams{}), + nil, + }, + { + "type not registered on codec", + "ibc.invalid.MsgTypeURL", + errors.New("unable to resolve type URL ibc.invalid.MsgTypeURL"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + encodingCfg := moduletestutil.MakeTestEncodingConfig(ibc.AppModuleBasic{}) + msg, err := encodingCfg.Codec.InterfaceRegistry().Resolve(tc.typeURL) + + if tc.expError == nil { + require.NotNil(t, msg) + require.NoError(t, err) + } else { + require.Nil(t, msg) + require.ErrorContains(t, err, tc.expError.Error()) + } + }) + } +} diff --git a/modules/core/03-connection/types/connection.go b/modules/core/03-connection/types/connection.go new file mode 100644 index 0000000..e904bdf --- /dev/null +++ b/modules/core/03-connection/types/connection.go @@ -0,0 +1,84 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +// NewConnectionEnd creates a new ConnectionEnd instance. +func NewConnectionEnd(state State, clientID string, counterparty Counterparty, versions []*Version, delayPeriod uint64) ConnectionEnd { + return ConnectionEnd{ + ClientId: clientID, + Versions: versions, + State: state, + Counterparty: counterparty, + DelayPeriod: delayPeriod, + } +} + +// ValidateBasic implements the Connection interface. +// NOTE: the protocol supports that the connection and client IDs match the +// counterparty's. +func (c ConnectionEnd) ValidateBasic() error { + if err := host.ClientIdentifierValidator(c.ClientId); err != nil { + return errorsmod.Wrap(err, "invalid client ID") + } + if len(c.Versions) == 0 { + return errorsmod.Wrap(ibcerrors.ErrInvalidVersion, "empty connection versions") + } + for _, version := range c.Versions { + if err := ValidateVersion(version); err != nil { + return err + } + } + return c.Counterparty.ValidateBasic() +} + +// NewCounterparty creates a new Counterparty instance. +func NewCounterparty(clientID, connectionID string, prefix commitmenttypes.MerklePrefix) Counterparty { + return Counterparty{ + ClientId: clientID, + ConnectionId: connectionID, + Prefix: prefix, + } +} + +// ValidateBasic performs a basic validation check of the identifiers and prefix +func (c Counterparty) ValidateBasic() error { + if c.ConnectionId != "" { + if err := host.ConnectionIdentifierValidator(c.ConnectionId); err != nil { + return errorsmod.Wrap(err, "invalid counterparty connection ID") + } + } + if err := host.ClientIdentifierValidator(c.ClientId); err != nil { + return errorsmod.Wrap(err, "invalid counterparty client ID") + } + if c.Prefix.Empty() { + return errorsmod.Wrap(ErrInvalidCounterparty, "counterparty prefix cannot be empty") + } + return nil +} + +// NewIdentifiedConnection creates a new IdentifiedConnection instance +func NewIdentifiedConnection(connectionID string, conn ConnectionEnd) IdentifiedConnection { + return IdentifiedConnection{ + Id: connectionID, + ClientId: conn.ClientId, + Versions: conn.Versions, + State: conn.State, + Counterparty: conn.Counterparty, + DelayPeriod: conn.DelayPeriod, + } +} + +// ValidateBasic performs a basic validation of the connection identifier and connection fields. +func (ic IdentifiedConnection) ValidateBasic() error { + if err := host.ConnectionIdentifierValidator(ic.Id); err != nil { + return errorsmod.Wrap(err, "invalid connection ID") + } + connection := NewConnectionEnd(ic.State, ic.ClientId, ic.Counterparty, ic.Versions, ic.DelayPeriod) + return connection.ValidateBasic() +} diff --git a/modules/core/03-connection/types/connection.pb.go b/modules/core/03-connection/types/connection.pb.go new file mode 100644 index 0000000..81f6f2e --- /dev/null +++ b/modules/core/03-connection/types/connection.pb.go @@ -0,0 +1,1960 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/connection/v1/connection.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + types "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// State defines if a connection is in one of the following states: +// INIT, TRYOPEN, OPEN or UNINITIALIZED. +type State int32 + +const ( + // Default State + UNINITIALIZED State = 0 + // A connection end has just started the opening handshake. + INIT State = 1 + // A connection end has acknowledged the handshake step on the counterparty + // chain. + TRYOPEN State = 2 + // A connection end has completed the handshake. + OPEN State = 3 +) + +var State_name = map[int32]string{ + 0: "STATE_UNINITIALIZED_UNSPECIFIED", + 1: "STATE_INIT", + 2: "STATE_TRYOPEN", + 3: "STATE_OPEN", +} + +var State_value = map[string]int32{ + "STATE_UNINITIALIZED_UNSPECIFIED": 0, + "STATE_INIT": 1, + "STATE_TRYOPEN": 2, + "STATE_OPEN": 3, +} + +func (x State) String() string { + return proto.EnumName(State_name, int32(x)) +} + +func (State) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_90572467c054e43a, []int{0} +} + +// ConnectionEnd defines a stateful object on a chain connected to another +// separate one. +// NOTE: there must only be 2 defined ConnectionEnds to establish +// a connection between two chains. +type ConnectionEnd struct { + // client associated with this connection. + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // IBC version which can be utilised to determine encodings or protocols for + // channels or packets utilising this connection. + Versions []*Version `protobuf:"bytes,2,rep,name=versions,proto3" json:"versions,omitempty"` + // current state of the connection end. + State State `protobuf:"varint,3,opt,name=state,proto3,enum=ibc.core.connection.v1.State" json:"state,omitempty"` + // counterparty chain associated with this connection. + Counterparty Counterparty `protobuf:"bytes,4,opt,name=counterparty,proto3" json:"counterparty"` + // delay period that must pass before a consensus state can be used for + // packet-verification NOTE: delay period logic is only implemented by some + // clients. + DelayPeriod uint64 `protobuf:"varint,5,opt,name=delay_period,json=delayPeriod,proto3" json:"delay_period,omitempty"` +} + +func (m *ConnectionEnd) Reset() { *m = ConnectionEnd{} } +func (m *ConnectionEnd) String() string { return proto.CompactTextString(m) } +func (*ConnectionEnd) ProtoMessage() {} +func (*ConnectionEnd) Descriptor() ([]byte, []int) { + return fileDescriptor_90572467c054e43a, []int{0} +} +func (m *ConnectionEnd) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ConnectionEnd) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ConnectionEnd.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ConnectionEnd) XXX_Merge(src proto.Message) { + xxx_messageInfo_ConnectionEnd.Merge(m, src) +} +func (m *ConnectionEnd) XXX_Size() int { + return m.Size() +} +func (m *ConnectionEnd) XXX_DiscardUnknown() { + xxx_messageInfo_ConnectionEnd.DiscardUnknown(m) +} + +var xxx_messageInfo_ConnectionEnd proto.InternalMessageInfo + +// IdentifiedConnection defines a connection with additional connection +// identifier field. +type IdentifiedConnection struct { + // connection identifier. + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // client associated with this connection. + ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // IBC version which can be utilised to determine encodings or protocols for + // channels or packets utilising this connection + Versions []*Version `protobuf:"bytes,3,rep,name=versions,proto3" json:"versions,omitempty"` + // current state of the connection end. + State State `protobuf:"varint,4,opt,name=state,proto3,enum=ibc.core.connection.v1.State" json:"state,omitempty"` + // counterparty chain associated with this connection. + Counterparty Counterparty `protobuf:"bytes,5,opt,name=counterparty,proto3" json:"counterparty"` + // delay period associated with this connection. + DelayPeriod uint64 `protobuf:"varint,6,opt,name=delay_period,json=delayPeriod,proto3" json:"delay_period,omitempty"` +} + +func (m *IdentifiedConnection) Reset() { *m = IdentifiedConnection{} } +func (m *IdentifiedConnection) String() string { return proto.CompactTextString(m) } +func (*IdentifiedConnection) ProtoMessage() {} +func (*IdentifiedConnection) Descriptor() ([]byte, []int) { + return fileDescriptor_90572467c054e43a, []int{1} +} +func (m *IdentifiedConnection) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *IdentifiedConnection) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_IdentifiedConnection.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *IdentifiedConnection) XXX_Merge(src proto.Message) { + xxx_messageInfo_IdentifiedConnection.Merge(m, src) +} +func (m *IdentifiedConnection) XXX_Size() int { + return m.Size() +} +func (m *IdentifiedConnection) XXX_DiscardUnknown() { + xxx_messageInfo_IdentifiedConnection.DiscardUnknown(m) +} + +var xxx_messageInfo_IdentifiedConnection proto.InternalMessageInfo + +// Counterparty defines the counterparty chain associated with a connection end. +type Counterparty struct { + // identifies the client on the counterparty chain associated with a given + // connection. + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // identifies the connection end on the counterparty chain associated with a + // given connection. + ConnectionId string `protobuf:"bytes,2,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"` + // commitment merkle prefix of the counterparty chain. + Prefix types.MerklePrefix `protobuf:"bytes,3,opt,name=prefix,proto3" json:"prefix"` +} + +func (m *Counterparty) Reset() { *m = Counterparty{} } +func (m *Counterparty) String() string { return proto.CompactTextString(m) } +func (*Counterparty) ProtoMessage() {} +func (*Counterparty) Descriptor() ([]byte, []int) { + return fileDescriptor_90572467c054e43a, []int{2} +} +func (m *Counterparty) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Counterparty) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Counterparty.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Counterparty) XXX_Merge(src proto.Message) { + xxx_messageInfo_Counterparty.Merge(m, src) +} +func (m *Counterparty) XXX_Size() int { + return m.Size() +} +func (m *Counterparty) XXX_DiscardUnknown() { + xxx_messageInfo_Counterparty.DiscardUnknown(m) +} + +var xxx_messageInfo_Counterparty proto.InternalMessageInfo + +// ClientPaths define all the connection paths for a client state. +type ClientPaths struct { + // list of connection paths + Paths []string `protobuf:"bytes,1,rep,name=paths,proto3" json:"paths,omitempty"` +} + +func (m *ClientPaths) Reset() { *m = ClientPaths{} } +func (m *ClientPaths) String() string { return proto.CompactTextString(m) } +func (*ClientPaths) ProtoMessage() {} +func (*ClientPaths) Descriptor() ([]byte, []int) { + return fileDescriptor_90572467c054e43a, []int{3} +} +func (m *ClientPaths) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ClientPaths) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ClientPaths.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ClientPaths) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClientPaths.Merge(m, src) +} +func (m *ClientPaths) XXX_Size() int { + return m.Size() +} +func (m *ClientPaths) XXX_DiscardUnknown() { + xxx_messageInfo_ClientPaths.DiscardUnknown(m) +} + +var xxx_messageInfo_ClientPaths proto.InternalMessageInfo + +func (m *ClientPaths) GetPaths() []string { + if m != nil { + return m.Paths + } + return nil +} + +// ConnectionPaths define all the connection paths for a given client state. +type ConnectionPaths struct { + // client state unique identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // list of connection paths + Paths []string `protobuf:"bytes,2,rep,name=paths,proto3" json:"paths,omitempty"` +} + +func (m *ConnectionPaths) Reset() { *m = ConnectionPaths{} } +func (m *ConnectionPaths) String() string { return proto.CompactTextString(m) } +func (*ConnectionPaths) ProtoMessage() {} +func (*ConnectionPaths) Descriptor() ([]byte, []int) { + return fileDescriptor_90572467c054e43a, []int{4} +} +func (m *ConnectionPaths) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ConnectionPaths) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ConnectionPaths.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ConnectionPaths) XXX_Merge(src proto.Message) { + xxx_messageInfo_ConnectionPaths.Merge(m, src) +} +func (m *ConnectionPaths) XXX_Size() int { + return m.Size() +} +func (m *ConnectionPaths) XXX_DiscardUnknown() { + xxx_messageInfo_ConnectionPaths.DiscardUnknown(m) +} + +var xxx_messageInfo_ConnectionPaths proto.InternalMessageInfo + +func (m *ConnectionPaths) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +func (m *ConnectionPaths) GetPaths() []string { + if m != nil { + return m.Paths + } + return nil +} + +// Version defines the versioning scheme used to negotiate the IBC version in +// the connection handshake. +type Version struct { + // unique version identifier + Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"` + // list of features compatible with the specified identifier + Features []string `protobuf:"bytes,2,rep,name=features,proto3" json:"features,omitempty"` +} + +func (m *Version) Reset() { *m = Version{} } +func (m *Version) String() string { return proto.CompactTextString(m) } +func (*Version) ProtoMessage() {} +func (*Version) Descriptor() ([]byte, []int) { + return fileDescriptor_90572467c054e43a, []int{5} +} +func (m *Version) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Version) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Version.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Version) XXX_Merge(src proto.Message) { + xxx_messageInfo_Version.Merge(m, src) +} +func (m *Version) XXX_Size() int { + return m.Size() +} +func (m *Version) XXX_DiscardUnknown() { + xxx_messageInfo_Version.DiscardUnknown(m) +} + +var xxx_messageInfo_Version proto.InternalMessageInfo + +// Params defines the set of Connection parameters. +type Params struct { + // maximum expected time per block (in nanoseconds), used to enforce block delay. This parameter should reflect the + // largest amount of time that the chain might reasonably take to produce the next block under normal operating + // conditions. A safe choice is 3-5x the expected time per block. + MaxExpectedTimePerBlock uint64 `protobuf:"varint,1,opt,name=max_expected_time_per_block,json=maxExpectedTimePerBlock,proto3" json:"max_expected_time_per_block,omitempty"` +} + +func (m *Params) Reset() { *m = Params{} } +func (m *Params) String() string { return proto.CompactTextString(m) } +func (*Params) ProtoMessage() {} +func (*Params) Descriptor() ([]byte, []int) { + return fileDescriptor_90572467c054e43a, []int{6} +} +func (m *Params) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Params) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params.Merge(m, src) +} +func (m *Params) XXX_Size() int { + return m.Size() +} +func (m *Params) XXX_DiscardUnknown() { + xxx_messageInfo_Params.DiscardUnknown(m) +} + +var xxx_messageInfo_Params proto.InternalMessageInfo + +func (m *Params) GetMaxExpectedTimePerBlock() uint64 { + if m != nil { + return m.MaxExpectedTimePerBlock + } + return 0 +} + +func init() { + proto.RegisterEnum("ibc.core.connection.v1.State", State_name, State_value) + proto.RegisterType((*ConnectionEnd)(nil), "ibc.core.connection.v1.ConnectionEnd") + proto.RegisterType((*IdentifiedConnection)(nil), "ibc.core.connection.v1.IdentifiedConnection") + proto.RegisterType((*Counterparty)(nil), "ibc.core.connection.v1.Counterparty") + proto.RegisterType((*ClientPaths)(nil), "ibc.core.connection.v1.ClientPaths") + proto.RegisterType((*ConnectionPaths)(nil), "ibc.core.connection.v1.ConnectionPaths") + proto.RegisterType((*Version)(nil), "ibc.core.connection.v1.Version") + proto.RegisterType((*Params)(nil), "ibc.core.connection.v1.Params") +} + +func init() { + proto.RegisterFile("ibc/core/connection/v1/connection.proto", fileDescriptor_90572467c054e43a) +} + +var fileDescriptor_90572467c054e43a = []byte{ + // 663 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x5d, 0x4f, 0xd3, 0x50, + 0x18, 0x6e, 0xbb, 0x6e, 0x8c, 0xb3, 0x0d, 0xf1, 0x84, 0x68, 0x53, 0x62, 0x57, 0xc1, 0xc4, 0xc5, + 0x84, 0x95, 0x41, 0xe2, 0x85, 0x1f, 0x17, 0x6c, 0x94, 0xa4, 0x51, 0x67, 0x53, 0x86, 0x46, 0x6e, + 0x9a, 0xae, 0x3d, 0x8c, 0x13, 0xd6, 0x9e, 0xa6, 0x3d, 0x5b, 0xc6, 0x3f, 0x20, 0x5c, 0x79, 0x67, + 0x34, 0x21, 0x31, 0xf1, 0xde, 0xdf, 0xc1, 0x25, 0x97, 0x5e, 0x19, 0x03, 0x7f, 0xc4, 0xf4, 0x83, + 0xb5, 0x10, 0xe1, 0x42, 0xbd, 0x7b, 0x3f, 0x9e, 0xe7, 0xe9, 0xdb, 0xe7, 0x7d, 0x73, 0xc0, 0x63, + 0xdc, 0xb7, 0x15, 0x9b, 0x04, 0x48, 0xb1, 0x89, 0xe7, 0x21, 0x9b, 0x62, 0xe2, 0x29, 0xe3, 0x56, + 0x2e, 0x6b, 0xfa, 0x01, 0xa1, 0x04, 0xde, 0xc3, 0x7d, 0xbb, 0x19, 0x01, 0x9b, 0xb9, 0xd6, 0xb8, + 0x25, 0x2e, 0x0c, 0xc8, 0x80, 0xc4, 0x10, 0x25, 0x8a, 0x12, 0xb4, 0x98, 0x97, 0x75, 0x5d, 0x4c, + 0x5d, 0xe4, 0xd1, 0x44, 0xf6, 0x32, 0x4b, 0x80, 0x4b, 0x9f, 0x38, 0x50, 0xeb, 0x4c, 0x05, 0x55, + 0xcf, 0x81, 0x8b, 0x60, 0xd6, 0x1e, 0x62, 0xe4, 0x51, 0x13, 0x3b, 0x02, 0x2b, 0xb3, 0x8d, 0x59, + 0xa3, 0x9c, 0x14, 0x34, 0x07, 0x3e, 0x07, 0xe5, 0x31, 0x0a, 0x42, 0x4c, 0xbc, 0x50, 0xe0, 0xe4, + 0x42, 0xa3, 0xb2, 0x56, 0x6f, 0xfe, 0x79, 0xb0, 0xe6, 0xbb, 0x04, 0x67, 0x4c, 0x09, 0x70, 0x1d, + 0x14, 0x43, 0x6a, 0x51, 0x24, 0x14, 0x64, 0xb6, 0x31, 0xb7, 0xf6, 0xe0, 0x26, 0xe6, 0x76, 0x04, + 0x32, 0x12, 0x2c, 0xec, 0x82, 0xaa, 0x4d, 0x46, 0x1e, 0x45, 0x81, 0x6f, 0x05, 0xf4, 0x50, 0xe0, + 0x65, 0xb6, 0x51, 0x59, 0x7b, 0x74, 0x13, 0xb7, 0x93, 0xc3, 0xb6, 0xf9, 0xd3, 0x9f, 0x75, 0xc6, + 0xb8, 0xc2, 0x87, 0x0f, 0x41, 0xd5, 0x41, 0x43, 0xeb, 0xd0, 0xf4, 0x51, 0x80, 0x89, 0x23, 0x14, + 0x65, 0xb6, 0xc1, 0x1b, 0x95, 0xb8, 0xa6, 0xc7, 0xa5, 0x67, 0xfc, 0xd1, 0xd7, 0x3a, 0xb3, 0xf4, + 0x9d, 0x03, 0x0b, 0x9a, 0x83, 0x3c, 0x8a, 0xf7, 0x30, 0x72, 0x32, 0x8f, 0xe0, 0x1c, 0xe0, 0xa6, + 0xce, 0x70, 0xf8, 0x9a, 0x61, 0xdc, 0x2d, 0x86, 0x15, 0xfe, 0xda, 0x30, 0xfe, 0x1f, 0x0c, 0x2b, + 0xfe, 0x67, 0xc3, 0x4a, 0x37, 0x19, 0xf6, 0x85, 0x05, 0xd5, 0xbc, 0xda, 0xed, 0x97, 0xb4, 0x0c, + 0x6a, 0xd9, 0x20, 0x99, 0x73, 0xd5, 0xac, 0xa8, 0x39, 0xb0, 0x0d, 0x4a, 0x7e, 0x80, 0xf6, 0xf0, + 0x24, 0x3e, 0x99, 0x6b, 0x7f, 0x31, 0xbd, 0xe4, 0x71, 0xab, 0xf9, 0x06, 0x05, 0x07, 0x43, 0xa4, + 0xc7, 0xd8, 0xf4, 0x2f, 0x52, 0x66, 0x3a, 0xdc, 0x32, 0xa8, 0x74, 0xe2, 0x4f, 0xeb, 0x16, 0xdd, + 0x0f, 0xe1, 0x02, 0x28, 0xfa, 0x51, 0x20, 0xb0, 0x72, 0xa1, 0x31, 0x6b, 0x24, 0xc9, 0xd2, 0x26, + 0xb8, 0x93, 0xed, 0x39, 0x01, 0xde, 0xfa, 0x0f, 0x53, 0x15, 0x2e, 0xaf, 0xf2, 0x0a, 0xcc, 0xa4, + 0xab, 0x84, 0x12, 0x00, 0xf8, 0xf2, 0x84, 0x82, 0x94, 0x9e, 0xab, 0x40, 0x11, 0x94, 0xf7, 0x90, + 0x45, 0x47, 0x01, 0xba, 0xd4, 0x98, 0xe6, 0xe9, 0xdc, 0x5b, 0xa0, 0xa4, 0x5b, 0x81, 0xe5, 0x86, + 0xf0, 0x05, 0x58, 0x74, 0xad, 0x89, 0x89, 0x26, 0x3e, 0xb2, 0x29, 0x72, 0x4c, 0x8a, 0x5d, 0x14, + 0xed, 0xc4, 0xec, 0x0f, 0x89, 0x7d, 0x10, 0x8b, 0xf3, 0xc6, 0x7d, 0xd7, 0x9a, 0xa8, 0x29, 0xa2, + 0x87, 0x5d, 0xa4, 0xa3, 0xa0, 0x1d, 0xb5, 0x9f, 0x7c, 0x66, 0x41, 0x31, 0x3e, 0x13, 0xf8, 0x14, + 0xd4, 0xb7, 0x7b, 0x1b, 0x3d, 0xd5, 0xdc, 0xe9, 0x6a, 0x5d, 0xad, 0xa7, 0x6d, 0xbc, 0xd6, 0x76, + 0xd5, 0x4d, 0x73, 0xa7, 0xbb, 0xad, 0xab, 0x1d, 0x6d, 0x4b, 0x53, 0x37, 0xe7, 0x19, 0xf1, 0xee, + 0xf1, 0x89, 0x5c, 0xbb, 0x02, 0x80, 0x02, 0x00, 0x09, 0x2f, 0x2a, 0xce, 0xb3, 0x62, 0xf9, 0xf8, + 0x44, 0xe6, 0xa3, 0x18, 0x4a, 0xa0, 0x96, 0x74, 0x7a, 0xc6, 0x87, 0xb7, 0xba, 0xda, 0x9d, 0xe7, + 0xc4, 0xca, 0xf1, 0x89, 0x3c, 0x93, 0xa6, 0x19, 0x33, 0x6e, 0x16, 0x12, 0x66, 0x14, 0x8b, 0xfc, + 0xd1, 0x37, 0x89, 0x69, 0xbf, 0x3f, 0x3d, 0x97, 0xd8, 0xb3, 0x73, 0x89, 0xfd, 0x75, 0x2e, 0xb1, + 0x1f, 0x2f, 0x24, 0xe6, 0xec, 0x42, 0x62, 0x7e, 0x5c, 0x48, 0xcc, 0xee, 0xcb, 0x01, 0xa6, 0xfb, + 0xa3, 0x7e, 0xb4, 0x6c, 0xc5, 0x26, 0xa1, 0x4b, 0x42, 0x05, 0xf7, 0xed, 0x95, 0x01, 0x51, 0xc6, + 0xad, 0x55, 0xc5, 0x25, 0xce, 0x68, 0x88, 0xc2, 0xe4, 0x9d, 0x5b, 0x5d, 0x5f, 0xc9, 0xbd, 0xa0, + 0xf4, 0xd0, 0x47, 0x61, 0xbf, 0x14, 0xbf, 0x71, 0xeb, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0xdd, + 0xab, 0x8e, 0xbe, 0x65, 0x05, 0x00, 0x00, +} + +func (m *ConnectionEnd) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ConnectionEnd) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ConnectionEnd) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.DelayPeriod != 0 { + i = encodeVarintConnection(dAtA, i, uint64(m.DelayPeriod)) + i-- + dAtA[i] = 0x28 + } + { + size, err := m.Counterparty.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintConnection(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + if m.State != 0 { + i = encodeVarintConnection(dAtA, i, uint64(m.State)) + i-- + dAtA[i] = 0x18 + } + if len(m.Versions) > 0 { + for iNdEx := len(m.Versions) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Versions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintConnection(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintConnection(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *IdentifiedConnection) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *IdentifiedConnection) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *IdentifiedConnection) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.DelayPeriod != 0 { + i = encodeVarintConnection(dAtA, i, uint64(m.DelayPeriod)) + i-- + dAtA[i] = 0x30 + } + { + size, err := m.Counterparty.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintConnection(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + if m.State != 0 { + i = encodeVarintConnection(dAtA, i, uint64(m.State)) + i-- + dAtA[i] = 0x20 + } + if len(m.Versions) > 0 { + for iNdEx := len(m.Versions) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Versions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintConnection(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintConnection(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0x12 + } + if len(m.Id) > 0 { + i -= len(m.Id) + copy(dAtA[i:], m.Id) + i = encodeVarintConnection(dAtA, i, uint64(len(m.Id))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Counterparty) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Counterparty) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Counterparty) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Prefix.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintConnection(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.ConnectionId) > 0 { + i -= len(m.ConnectionId) + copy(dAtA[i:], m.ConnectionId) + i = encodeVarintConnection(dAtA, i, uint64(len(m.ConnectionId))) + i-- + dAtA[i] = 0x12 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintConnection(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ClientPaths) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ClientPaths) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ClientPaths) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Paths) > 0 { + for iNdEx := len(m.Paths) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Paths[iNdEx]) + copy(dAtA[i:], m.Paths[iNdEx]) + i = encodeVarintConnection(dAtA, i, uint64(len(m.Paths[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *ConnectionPaths) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ConnectionPaths) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ConnectionPaths) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Paths) > 0 { + for iNdEx := len(m.Paths) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Paths[iNdEx]) + copy(dAtA[i:], m.Paths[iNdEx]) + i = encodeVarintConnection(dAtA, i, uint64(len(m.Paths[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintConnection(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Version) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Version) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Version) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Features) > 0 { + for iNdEx := len(m.Features) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Features[iNdEx]) + copy(dAtA[i:], m.Features[iNdEx]) + i = encodeVarintConnection(dAtA, i, uint64(len(m.Features[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + if len(m.Identifier) > 0 { + i -= len(m.Identifier) + copy(dAtA[i:], m.Identifier) + i = encodeVarintConnection(dAtA, i, uint64(len(m.Identifier))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Params) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Params) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.MaxExpectedTimePerBlock != 0 { + i = encodeVarintConnection(dAtA, i, uint64(m.MaxExpectedTimePerBlock)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintConnection(dAtA []byte, offset int, v uint64) int { + offset -= sovConnection(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *ConnectionEnd) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovConnection(uint64(l)) + } + if len(m.Versions) > 0 { + for _, e := range m.Versions { + l = e.Size() + n += 1 + l + sovConnection(uint64(l)) + } + } + if m.State != 0 { + n += 1 + sovConnection(uint64(m.State)) + } + l = m.Counterparty.Size() + n += 1 + l + sovConnection(uint64(l)) + if m.DelayPeriod != 0 { + n += 1 + sovConnection(uint64(m.DelayPeriod)) + } + return n +} + +func (m *IdentifiedConnection) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Id) + if l > 0 { + n += 1 + l + sovConnection(uint64(l)) + } + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovConnection(uint64(l)) + } + if len(m.Versions) > 0 { + for _, e := range m.Versions { + l = e.Size() + n += 1 + l + sovConnection(uint64(l)) + } + } + if m.State != 0 { + n += 1 + sovConnection(uint64(m.State)) + } + l = m.Counterparty.Size() + n += 1 + l + sovConnection(uint64(l)) + if m.DelayPeriod != 0 { + n += 1 + sovConnection(uint64(m.DelayPeriod)) + } + return n +} + +func (m *Counterparty) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovConnection(uint64(l)) + } + l = len(m.ConnectionId) + if l > 0 { + n += 1 + l + sovConnection(uint64(l)) + } + l = m.Prefix.Size() + n += 1 + l + sovConnection(uint64(l)) + return n +} + +func (m *ClientPaths) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Paths) > 0 { + for _, s := range m.Paths { + l = len(s) + n += 1 + l + sovConnection(uint64(l)) + } + } + return n +} + +func (m *ConnectionPaths) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovConnection(uint64(l)) + } + if len(m.Paths) > 0 { + for _, s := range m.Paths { + l = len(s) + n += 1 + l + sovConnection(uint64(l)) + } + } + return n +} + +func (m *Version) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Identifier) + if l > 0 { + n += 1 + l + sovConnection(uint64(l)) + } + if len(m.Features) > 0 { + for _, s := range m.Features { + l = len(s) + n += 1 + l + sovConnection(uint64(l)) + } + } + return n +} + +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.MaxExpectedTimePerBlock != 0 { + n += 1 + sovConnection(uint64(m.MaxExpectedTimePerBlock)) + } + return n +} + +func sovConnection(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozConnection(x uint64) (n int) { + return sovConnection(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *ConnectionEnd) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ConnectionEnd: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ConnectionEnd: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthConnection + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthConnection + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Versions", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthConnection + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthConnection + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Versions = append(m.Versions, &Version{}) + if err := m.Versions[len(m.Versions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field State", wireType) + } + m.State = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.State |= State(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Counterparty", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthConnection + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthConnection + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Counterparty.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DelayPeriod", wireType) + } + m.DelayPeriod = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DelayPeriod |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipConnection(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthConnection + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *IdentifiedConnection) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: IdentifiedConnection: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: IdentifiedConnection: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthConnection + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthConnection + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Id = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthConnection + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthConnection + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Versions", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthConnection + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthConnection + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Versions = append(m.Versions, &Version{}) + if err := m.Versions[len(m.Versions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field State", wireType) + } + m.State = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.State |= State(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Counterparty", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthConnection + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthConnection + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Counterparty.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DelayPeriod", wireType) + } + m.DelayPeriod = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DelayPeriod |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipConnection(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthConnection + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Counterparty) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Counterparty: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Counterparty: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthConnection + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthConnection + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectionId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthConnection + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthConnection + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ConnectionId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Prefix", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthConnection + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthConnection + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Prefix.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipConnection(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthConnection + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ClientPaths) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ClientPaths: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ClientPaths: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Paths", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthConnection + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthConnection + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Paths = append(m.Paths, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipConnection(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthConnection + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ConnectionPaths) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ConnectionPaths: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ConnectionPaths: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthConnection + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthConnection + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Paths", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthConnection + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthConnection + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Paths = append(m.Paths, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipConnection(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthConnection + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Version) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Version: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Version: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Identifier", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthConnection + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthConnection + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Identifier = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Features", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthConnection + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthConnection + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Features = append(m.Features, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipConnection(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthConnection + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Params) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Params: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxExpectedTimePerBlock", wireType) + } + m.MaxExpectedTimePerBlock = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowConnection + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxExpectedTimePerBlock |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipConnection(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthConnection + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipConnection(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowConnection + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowConnection + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowConnection + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthConnection + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupConnection + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthConnection + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthConnection = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowConnection = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupConnection = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/03-connection/types/connection_test.go b/modules/core/03-connection/types/connection_test.go new file mode 100644 index 0000000..f62a5fd --- /dev/null +++ b/modules/core/03-connection/types/connection_test.go @@ -0,0 +1,119 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +var ( + connectionID = "connection-0" + clientID = "clientidone" + connectionID2 = "connectionidtwo" + clientID2 = "clientidtwo" + invalidConnectionID = "(invalidConnectionID)" + clientHeight = clienttypes.NewHeight(0, 6) +) + +func TestConnectionValidateBasic(t *testing.T) { + testCases := []struct { + name string + connection types.ConnectionEnd + expError error + }{ + { + "valid connection", + types.ConnectionEnd{clientID, []*types.Version{ibctesting.ConnectionVersion}, types.INIT, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, 500}, + nil, + }, + { + "invalid client id", + types.ConnectionEnd{"(clientID1)", []*types.Version{ibctesting.ConnectionVersion}, types.INIT, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, 500}, + host.ErrInvalidID, + }, + { + "empty versions", + types.ConnectionEnd{clientID, nil, types.INIT, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, 500}, + ibcerrors.ErrInvalidVersion, + }, + { + "invalid version", + types.ConnectionEnd{clientID, []*types.Version{{}}, types.INIT, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, 500}, + types.ErrInvalidVersion, + }, + { + "invalid counterparty", + types.ConnectionEnd{clientID, []*types.Version{ibctesting.ConnectionVersion}, types.INIT, types.Counterparty{clientID2, connectionID2, emptyPrefix}, 500}, + types.ErrInvalidCounterparty, + }, + } + + for i, tc := range testCases { + + err := tc.connection.ValidateBasic() + if tc.expError == nil { + require.NoError(t, err, "valid test case %d failed: %s", i, tc.name) + } else { + require.ErrorIs(t, err, tc.expError) + } + } +} + +func TestCounterpartyValidateBasic(t *testing.T) { + testCases := []struct { + name string + counterparty types.Counterparty + expError error + }{ + {"valid counterparty", types.Counterparty{clientID, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, nil}, + {"invalid client id", types.Counterparty{"(InvalidClient)", connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, host.ErrInvalidID}, + {"invalid connection id", types.Counterparty{clientID, "(InvalidConnection)", commitmenttypes.NewMerklePrefix([]byte("prefix"))}, host.ErrInvalidID}, + {"invalid prefix", types.Counterparty{clientID, connectionID2, emptyPrefix}, types.ErrInvalidCounterparty}, + } + + for i, tc := range testCases { + + err := tc.counterparty.ValidateBasic() + if tc.expError == nil { + require.NoError(t, err, "valid test case %d failed: %s", i, tc.name) + } else { + require.ErrorIs(t, err, tc.expError) + } + } +} + +func TestIdentifiedConnectionValidateBasic(t *testing.T) { + testCases := []struct { + name string + connection types.IdentifiedConnection + expError error + }{ + { + "valid connection", + types.NewIdentifiedConnection(clientID, types.ConnectionEnd{clientID, []*types.Version{ibctesting.ConnectionVersion}, types.INIT, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, 500}), + nil, + }, + { + "invalid connection id", + types.NewIdentifiedConnection("(connectionIDONE)", types.ConnectionEnd{clientID, []*types.Version{ibctesting.ConnectionVersion}, types.INIT, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, 500}), + host.ErrInvalidID, + }, + } + + for i, tc := range testCases { + + err := tc.connection.ValidateBasic() + if tc.expError == nil { + require.NoError(t, err, "valid test case %d failed: %s", i, tc.name) + } else { + require.ErrorIs(t, err, tc.expError) + } + } +} diff --git a/modules/core/03-connection/types/errors.go b/modules/core/03-connection/types/errors.go new file mode 100644 index 0000000..9e81705 --- /dev/null +++ b/modules/core/03-connection/types/errors.go @@ -0,0 +1,19 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" +) + +// IBC connection sentinel errors +var ( + ErrConnectionExists = errorsmod.Register(SubModuleName, 2, "connection already exists") + ErrConnectionNotFound = errorsmod.Register(SubModuleName, 3, "connection not found") + ErrClientConnectionPathsNotFound = errorsmod.Register(SubModuleName, 4, "light client connection paths not found") + ErrConnectionPath = errorsmod.Register(SubModuleName, 5, "connection path is not associated to the given light client") + ErrInvalidConnectionState = errorsmod.Register(SubModuleName, 6, "invalid connection state") + ErrInvalidCounterparty = errorsmod.Register(SubModuleName, 7, "invalid counterparty connection") + ErrInvalidConnection = errorsmod.Register(SubModuleName, 8, "invalid connection") + ErrInvalidVersion = errorsmod.Register(SubModuleName, 9, "invalid connection version") + ErrVersionNegotiationFailed = errorsmod.Register(SubModuleName, 10, "connection version negotiation failed") + ErrInvalidConnectionIdentifier = errorsmod.Register(SubModuleName, 11, "invalid connection identifier") +) diff --git a/modules/core/03-connection/types/events.go b/modules/core/03-connection/types/events.go new file mode 100644 index 0000000..bf4f891 --- /dev/null +++ b/modules/core/03-connection/types/events.go @@ -0,0 +1,25 @@ +package types + +import ( + "fmt" + + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// IBC connection events +const ( + AttributeKeyConnectionID = "connection_id" + AttributeKeyClientID = "client_id" + AttributeKeyCounterpartyClientID = "counterparty_client_id" + AttributeKeyCounterpartyConnectionID = "counterparty_connection_id" +) + +// IBC connection events vars +var ( + EventTypeConnectionOpenInit = "connection_open_init" + EventTypeConnectionOpenTry = "connection_open_try" + EventTypeConnectionOpenAck = "connection_open_ack" + EventTypeConnectionOpenConfirm = "connection_open_confirm" + + AttributeValueCategory = fmt.Sprintf("%s_%s", ibcexported.ModuleName, SubModuleName) +) diff --git a/modules/core/03-connection/types/expected_keepers.go b/modules/core/03-connection/types/expected_keepers.go new file mode 100644 index 0000000..3ce1502 --- /dev/null +++ b/modules/core/03-connection/types/expected_keepers.go @@ -0,0 +1,23 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// ClientKeeper expected account IBC client keeper +type ClientKeeper interface { + GetClientStatus(ctx sdk.Context, clientID string) exported.Status + GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool) + GetClientConsensusState(ctx sdk.Context, clientID string, height exported.Height) (exported.ConsensusState, bool) + VerifyMembership(ctx sdk.Context, clientID string, height exported.Height, delayTimePeriod uint64, delayBlockPeriod uint64, proof []byte, path exported.Path, value []byte) error + VerifyNonMembership(ctx sdk.Context, clientID string, height exported.Height, delayTimePeriod uint64, delayBlockPeriod uint64, proof []byte, path exported.Path) error + IterateClientStates(ctx sdk.Context, prefix []byte, cb func(string, exported.ClientState) bool) +} + +// ParamSubspace defines the expected Subspace interface for module parameters. +type ParamSubspace interface { + GetParamSet(ctx sdk.Context, ps paramtypes.ParamSet) +} diff --git a/modules/core/03-connection/types/genesis.go b/modules/core/03-connection/types/genesis.go new file mode 100644 index 0000000..08eac83 --- /dev/null +++ b/modules/core/03-connection/types/genesis.go @@ -0,0 +1,80 @@ +package types + +import ( + "fmt" + + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// NewConnectionPaths creates a ConnectionPaths instance. +func NewConnectionPaths(id string, paths []string) ConnectionPaths { + return ConnectionPaths{ + ClientId: id, + Paths: paths, + } +} + +// NewGenesisState creates a GenesisState instance. +func NewGenesisState( + connections []IdentifiedConnection, connPaths []ConnectionPaths, + nextConnectionSequence uint64, params Params, +) GenesisState { + return GenesisState{ + Connections: connections, + ClientConnectionPaths: connPaths, + NextConnectionSequence: nextConnectionSequence, + Params: params, + } +} + +// DefaultGenesisState returns the ibc connection submodule's default genesis state. +func DefaultGenesisState() GenesisState { + return GenesisState{ + Connections: []IdentifiedConnection{}, + ClientConnectionPaths: []ConnectionPaths{}, + NextConnectionSequence: 0, + Params: DefaultParams(), + } +} + +// Validate performs basic genesis state validation returning an error upon any +// failure. +func (gs GenesisState) Validate() error { + // keep track of the max sequence to ensure it is less than + // the next sequence used in creating connection identifiers. + var maxSequence uint64 + + for i, conn := range gs.Connections { + if conn.Id != exported.LocalhostConnectionID { + sequence, err := ParseConnectionSequence(conn.Id) + if err != nil { + return err + } + if sequence > maxSequence { + maxSequence = sequence + } + } + + if err := conn.ValidateBasic(); err != nil { + return fmt.Errorf("invalid connection %v index %d: %w", conn, i, err) + } + } + + for i, conPaths := range gs.ClientConnectionPaths { + if err := host.ClientIdentifierValidator(conPaths.ClientId); err != nil { + return fmt.Errorf("invalid client connection path %d: %w", i, err) + } + for _, connectionID := range conPaths.Paths { + if err := host.ConnectionIdentifierValidator(connectionID); err != nil { + return fmt.Errorf("invalid client connection ID (%s) in connection paths %d: %w", connectionID, i, err) + } + } + } + + if maxSequence != 0 && maxSequence >= gs.NextConnectionSequence { + return fmt.Errorf("next connection sequence %d must be greater than maximum sequence used in connection identifier %d", gs.NextConnectionSequence, maxSequence) + } + + return gs.Params.Validate() +} diff --git a/modules/core/03-connection/types/genesis.pb.go b/modules/core/03-connection/types/genesis.pb.go new file mode 100644 index 0000000..77ed33a --- /dev/null +++ b/modules/core/03-connection/types/genesis.pb.go @@ -0,0 +1,491 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/connection/v1/genesis.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// GenesisState defines the ibc connection submodule's genesis state. +type GenesisState struct { + Connections []IdentifiedConnection `protobuf:"bytes,1,rep,name=connections,proto3" json:"connections"` + ClientConnectionPaths []ConnectionPaths `protobuf:"bytes,2,rep,name=client_connection_paths,json=clientConnectionPaths,proto3" json:"client_connection_paths"` + // the sequence for the next generated connection identifier + NextConnectionSequence uint64 `protobuf:"varint,3,opt,name=next_connection_sequence,json=nextConnectionSequence,proto3" json:"next_connection_sequence,omitempty"` + Params Params `protobuf:"bytes,4,opt,name=params,proto3" json:"params"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_1879d34bc6ac3cd7, []int{0} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetConnections() []IdentifiedConnection { + if m != nil { + return m.Connections + } + return nil +} + +func (m *GenesisState) GetClientConnectionPaths() []ConnectionPaths { + if m != nil { + return m.ClientConnectionPaths + } + return nil +} + +func (m *GenesisState) GetNextConnectionSequence() uint64 { + if m != nil { + return m.NextConnectionSequence + } + return 0 +} + +func (m *GenesisState) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +func init() { + proto.RegisterType((*GenesisState)(nil), "ibc.core.connection.v1.GenesisState") +} + +func init() { + proto.RegisterFile("ibc/core/connection/v1/genesis.proto", fileDescriptor_1879d34bc6ac3cd7) +} + +var fileDescriptor_1879d34bc6ac3cd7 = []byte{ + // 330 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0xbd, 0x4e, 0x3a, 0x41, + 0x14, 0xc5, 0x77, 0x81, 0x50, 0x0c, 0xff, 0x6a, 0xf3, 0x17, 0x37, 0x14, 0x23, 0x31, 0x26, 0x50, + 0xc8, 0x0c, 0x1f, 0x8d, 0x85, 0x36, 0x58, 0x18, 0x3b, 0x02, 0x26, 0x26, 0x36, 0x64, 0x77, 0xb8, + 0x2e, 0x93, 0xb0, 0x73, 0x57, 0x66, 0x20, 0xfa, 0x16, 0xbe, 0x93, 0x0d, 0x25, 0xa5, 0x95, 0x31, + 0xf0, 0x22, 0x66, 0x3f, 0xe2, 0xa2, 0x71, 0xbb, 0xc9, 0x9c, 0xdf, 0x39, 0xe7, 0x26, 0x87, 0x9c, + 0x49, 0x5f, 0x70, 0x81, 0x4b, 0xe0, 0x02, 0x95, 0x02, 0x61, 0x24, 0x2a, 0xbe, 0xee, 0xf1, 0x00, + 0x14, 0x68, 0xa9, 0x59, 0xb4, 0x44, 0x83, 0x4e, 0x5d, 0xfa, 0x82, 0xc5, 0x14, 0xcb, 0x29, 0xb6, + 0xee, 0x35, 0xfe, 0x07, 0x18, 0x60, 0x82, 0xf0, 0xf8, 0x95, 0xd2, 0x8d, 0x56, 0x41, 0xe6, 0x81, + 0x37, 0x01, 0x4f, 0xdf, 0x4a, 0xe4, 0xdf, 0x4d, 0x5a, 0x34, 0x31, 0x9e, 0x01, 0xe7, 0x8e, 0xd4, + 0x72, 0x48, 0xbb, 0x76, 0xb3, 0xdc, 0xae, 0xf5, 0xcf, 0xd9, 0xdf, 0xed, 0xec, 0x76, 0x06, 0xca, + 0xc8, 0x47, 0x09, 0xb3, 0xeb, 0xef, 0xff, 0x61, 0x65, 0xf3, 0x71, 0x62, 0x8d, 0x0f, 0x63, 0x1c, + 0x20, 0xc7, 0x62, 0x21, 0x41, 0x99, 0x69, 0xfe, 0x3b, 0x8d, 0x3c, 0x33, 0xd7, 0x6e, 0x29, 0x69, + 0x68, 0x15, 0x35, 0xe4, 0xb9, 0xa3, 0x18, 0xcf, 0xc2, 0x8f, 0xd2, 0xb4, 0x5f, 0xa2, 0x73, 0x41, + 0x5c, 0x05, 0xcf, 0x3f, 0x4a, 0x34, 0x3c, 0xad, 0x40, 0x09, 0x70, 0xcb, 0x4d, 0xbb, 0x5d, 0x19, + 0xd7, 0x63, 0x3d, 0xb7, 0x4d, 0x32, 0xd5, 0xb9, 0x24, 0xd5, 0xc8, 0x5b, 0x7a, 0xa1, 0x76, 0x2b, + 0x4d, 0xbb, 0x5d, 0xeb, 0xd3, 0xa2, 0x7b, 0x46, 0x09, 0x95, 0x9d, 0x91, 0x79, 0x86, 0xf7, 0x9b, + 0x1d, 0xb5, 0xb7, 0x3b, 0x6a, 0x7f, 0xee, 0xa8, 0xfd, 0xba, 0xa7, 0xd6, 0x76, 0x4f, 0xad, 0xf7, + 0x3d, 0xb5, 0x1e, 0xae, 0x02, 0x69, 0xe6, 0x2b, 0x9f, 0x09, 0x0c, 0xb9, 0x40, 0x1d, 0xa2, 0xe6, + 0xd2, 0x17, 0x9d, 0x00, 0xf9, 0xba, 0xd7, 0xe5, 0x21, 0xce, 0x56, 0x0b, 0xd0, 0xe9, 0x52, 0xdd, + 0x41, 0xe7, 0x60, 0x2c, 0xf3, 0x12, 0x81, 0xf6, 0xab, 0xc9, 0x4a, 0x83, 0xaf, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xb8, 0xb8, 0x24, 0x96, 0x24, 0x02, 0x00, 0x00, +} + +func (m *GenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + if m.NextConnectionSequence != 0 { + i = encodeVarintGenesis(dAtA, i, uint64(m.NextConnectionSequence)) + i-- + dAtA[i] = 0x18 + } + if len(m.ClientConnectionPaths) > 0 { + for iNdEx := len(m.ClientConnectionPaths) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ClientConnectionPaths[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.Connections) > 0 { + for iNdEx := len(m.Connections) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Connections[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Connections) > 0 { + for _, e := range m.Connections { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.ClientConnectionPaths) > 0 { + for _, e := range m.ClientConnectionPaths { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if m.NextConnectionSequence != 0 { + n += 1 + sovGenesis(uint64(m.NextConnectionSequence)) + } + l = m.Params.Size() + n += 1 + l + sovGenesis(uint64(l)) + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Connections", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Connections = append(m.Connections, IdentifiedConnection{}) + if err := m.Connections[len(m.Connections)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientConnectionPaths", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientConnectionPaths = append(m.ClientConnectionPaths, ConnectionPaths{}) + if err := m.ClientConnectionPaths[len(m.ClientConnectionPaths)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NextConnectionSequence", wireType) + } + m.NextConnectionSequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.NextConnectionSequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/03-connection/types/genesis_test.go b/modules/core/03-connection/types/genesis_test.go new file mode 100644 index 0000000..8ed612e --- /dev/null +++ b/modules/core/03-connection/types/genesis_test.go @@ -0,0 +1,149 @@ +package types_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func TestValidateGenesis(t *testing.T) { + testCases := []struct { + name string + genState types.GenesisState + expError error + }{ + { + name: "default", + genState: types.DefaultGenesisState(), + expError: nil, + }, + { + name: "valid genesis", + genState: types.NewGenesisState( + []types.IdentifiedConnection{ + types.NewIdentifiedConnection(connectionID, types.NewConnectionEnd(types.INIT, clientID, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []*types.Version{ibctesting.ConnectionVersion}, 500)), + }, + []types.ConnectionPaths{ + {clientID, []string{connectionID}}, + }, + 0, + types.DefaultParams(), + ), + expError: nil, + }, + { + name: "invalid connection", + genState: types.NewGenesisState( + []types.IdentifiedConnection{ + types.NewIdentifiedConnection(connectionID, types.NewConnectionEnd(types.INIT, "(CLIENTIDONE)", types.Counterparty{clientID, connectionID, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []*types.Version{ibctesting.ConnectionVersion}, 500)), + }, + []types.ConnectionPaths{ + {clientID, []string{connectionID}}, + }, + 0, + types.DefaultParams(), + ), + expError: host.ErrInvalidID, + }, + { + name: "invalid client id", + genState: types.NewGenesisState( + []types.IdentifiedConnection{ + types.NewIdentifiedConnection(connectionID, types.NewConnectionEnd(types.INIT, clientID, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []*types.Version{ibctesting.ConnectionVersion}, 500)), + }, + []types.ConnectionPaths{ + {"(CLIENTIDONE)", []string{connectionID}}, + }, + 0, + types.DefaultParams(), + ), + expError: host.ErrInvalidID, + }, + { + name: "invalid path", + genState: types.NewGenesisState( + []types.IdentifiedConnection{ + types.NewIdentifiedConnection(connectionID, types.NewConnectionEnd(types.INIT, clientID, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []*types.Version{ibctesting.ConnectionVersion}, 500)), + }, + []types.ConnectionPaths{ + {clientID, []string{invalidConnectionID}}, + }, + 0, + types.DefaultParams(), + ), + expError: host.ErrInvalidID, + }, + { + name: "invalid connection identifier", + genState: types.NewGenesisState( + []types.IdentifiedConnection{ + types.NewIdentifiedConnection("conn-0", types.NewConnectionEnd(types.INIT, clientID, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []*types.Version{ibctesting.ConnectionVersion}, 500)), + }, + []types.ConnectionPaths{ + {clientID, []string{connectionID}}, + }, + 0, + types.DefaultParams(), + ), + expError: host.ErrInvalidID, + }, + { + name: "localhost connection identifier", + genState: types.NewGenesisState( + []types.IdentifiedConnection{ + types.NewIdentifiedConnection(exported.LocalhostConnectionID, types.NewConnectionEnd(types.INIT, clientID, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []*types.Version{ibctesting.ConnectionVersion}, 500)), + }, + []types.ConnectionPaths{ + {clientID, []string{connectionID}}, + }, + 0, + types.DefaultParams(), + ), + expError: nil, + }, + { + name: "next connection sequence is not greater than maximum connection identifier sequence provided", + genState: types.NewGenesisState( + []types.IdentifiedConnection{ + types.NewIdentifiedConnection(types.FormatConnectionIdentifier(10), types.NewConnectionEnd(types.INIT, clientID, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []*types.Version{ibctesting.ConnectionVersion}, 500)), + }, + []types.ConnectionPaths{ + {clientID, []string{connectionID}}, + }, + 0, + types.DefaultParams(), + ), + expError: errors.New("next connection sequence 0 must be greater than maximum sequence used in connection identifier 10"), + }, + { + name: "invalid params", + genState: types.NewGenesisState( + []types.IdentifiedConnection{ + types.NewIdentifiedConnection(connectionID, types.NewConnectionEnd(types.INIT, clientID, types.Counterparty{clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))}, []*types.Version{ibctesting.ConnectionVersion}, 500)), + }, + []types.ConnectionPaths{ + {clientID, []string{connectionID}}, + }, + 0, + types.Params{}, + ), + expError: errors.New("MaxExpectedTimePerBlock cannot be zero"), + }, + } + + for _, tc := range testCases { + err := tc.genState.Validate() + if tc.expError == nil { + require.NoError(t, err, tc.name) + } else { + require.ErrorContains(t, err, tc.expError.Error()) + } + } +} diff --git a/modules/core/03-connection/types/keys.go b/modules/core/03-connection/types/keys.go new file mode 100644 index 0000000..808f9cf --- /dev/null +++ b/modules/core/03-connection/types/keys.go @@ -0,0 +1,65 @@ +package types + +import ( + "fmt" + "regexp" + + errorsmod "cosmossdk.io/errors" + + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +const ( + // SubModuleName defines the IBC connection name + SubModuleName = "connection" + + // StoreKey is the store key string for IBC connections + StoreKey = SubModuleName + + // RouterKey is the message route for IBC connections + RouterKey = SubModuleName + + // QuerierRoute is the querier route for IBC connections + QuerierRoute = SubModuleName + + // KeyNextConnectionSequence is the key used to store the next connection sequence in + // the keeper. + KeyNextConnectionSequence = "nextConnectionSequence" + + // ConnectionPrefix is the prefix used when creating a connection identifier + ConnectionPrefix = "connection-" + + // ParamsKey is the store key for the IBC connection parameters + ParamsKey = "connectionParams" +) + +// FormatConnectionIdentifier returns the connection identifier with the sequence appended. +// This is an SDK specific format not enforced by IBC protocol. +func FormatConnectionIdentifier(sequence uint64) string { + return fmt.Sprintf("%s%d", ConnectionPrefix, sequence) +} + +// IsConnectionIDFormat checks if a connectionID is in the format required on the SDK for +// parsing connection identifiers. The connection identifier must be in the form: `connection-{N} +var IsConnectionIDFormat = regexp.MustCompile(`^connection-[0-9]{1,20}$`).MatchString + +// IsValidConnectionID checks if the connection identifier is valid and can be parsed to +// the connection identifier format. +func IsValidConnectionID(connectionID string) bool { + _, err := ParseConnectionSequence(connectionID) + return err == nil +} + +// ParseConnectionSequence parses the connection sequence from the connection identifier. +func ParseConnectionSequence(connectionID string) (uint64, error) { + if !IsConnectionIDFormat(connectionID) { + return 0, errorsmod.Wrap(host.ErrInvalidID, "connection identifier is not in the format: `connection-{N}`") + } + + sequence, err := host.ParseIdentifier(connectionID, ConnectionPrefix) + if err != nil { + return 0, errorsmod.Wrap(err, "invalid connection identifier") + } + + return sequence, nil +} diff --git a/modules/core/03-connection/types/keys_test.go b/modules/core/03-connection/types/keys_test.go new file mode 100644 index 0000000..afc22f7 --- /dev/null +++ b/modules/core/03-connection/types/keys_test.go @@ -0,0 +1,52 @@ +package types_test + +import ( + "errors" + "math" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +// tests ParseConnectionSequence and IsValidConnectionID +func TestParseConnectionSequence(t *testing.T) { + testCases := []struct { + name string + connectionID string + expSeq uint64 + expError error + }{ + {"valid 0", "connection-0", 0, nil}, + {"valid 1", "connection-1", 1, nil}, + {"valid large sequence", types.FormatConnectionIdentifier(math.MaxUint64), math.MaxUint64, nil}, + // one above uint64 max + {"invalid uint64", "connection-18446744073709551616", 0, errors.New("invalid connection identifier: failed to parse identifier sequence")}, + // uint64 == 20 characters + {"invalid large sequence", "connection-2345682193567182931243", 0, host.ErrInvalidID}, + {"capital prefix", "Connection-0", 0, host.ErrInvalidID}, + {"double prefix", "connection-connection-0", 0, host.ErrInvalidID}, + {"missing dash", "connection0", 0, host.ErrInvalidID}, + {"blank id", " ", 0, host.ErrInvalidID}, + {"empty id", "", 0, host.ErrInvalidID}, + {"negative sequence", "connection--1", 0, host.ErrInvalidID}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + seq, err := types.ParseConnectionSequence(tc.connectionID) + valid := types.IsValidConnectionID(tc.connectionID) + require.Equal(t, tc.expSeq, seq) + + if tc.expError == nil { + require.NoError(t, err, tc.name) + require.True(t, valid) + } else { + require.ErrorContains(t, err, tc.expError.Error()) + require.False(t, valid) + } + }) + } +} diff --git a/modules/core/03-connection/types/msgs.go b/modules/core/03-connection/types/msgs.go new file mode 100644 index 0000000..43cb172 --- /dev/null +++ b/modules/core/03-connection/types/msgs.go @@ -0,0 +1,204 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var ( + _ sdk.Msg = (*MsgConnectionOpenInit)(nil) + _ sdk.Msg = (*MsgConnectionOpenConfirm)(nil) + _ sdk.Msg = (*MsgConnectionOpenAck)(nil) + _ sdk.Msg = (*MsgConnectionOpenTry)(nil) + _ sdk.Msg = (*MsgUpdateParams)(nil) + + _ sdk.HasValidateBasic = (*MsgConnectionOpenInit)(nil) + _ sdk.HasValidateBasic = (*MsgConnectionOpenConfirm)(nil) + _ sdk.HasValidateBasic = (*MsgConnectionOpenAck)(nil) + _ sdk.HasValidateBasic = (*MsgConnectionOpenTry)(nil) + _ sdk.HasValidateBasic = (*MsgUpdateParams)(nil) +) + +// NewMsgConnectionOpenInit creates a new MsgConnectionOpenInit instance. It sets the +// counterparty connection identifier to be empty. +func NewMsgConnectionOpenInit( + clientID, counterpartyClientID string, + counterpartyPrefix commitmenttypes.MerklePrefix, + version *Version, delayPeriod uint64, signer string, +) *MsgConnectionOpenInit { + // counterparty must have the same delay period + counterparty := NewCounterparty(counterpartyClientID, "", counterpartyPrefix) + return &MsgConnectionOpenInit{ + ClientId: clientID, + Counterparty: counterparty, + Version: version, + DelayPeriod: delayPeriod, + Signer: signer, + } +} + +// ValidateBasic implements sdk.Msg. +func (msg MsgConnectionOpenInit) ValidateBasic() error { + if msg.ClientId == exported.LocalhostClientID { + return errorsmod.Wrap(clienttypes.ErrInvalidClientType, "localhost connection handshakes are disallowed") + } + + if err := host.ClientIdentifierValidator(msg.ClientId); err != nil { + return errorsmod.Wrap(err, "invalid client ID") + } + if msg.Counterparty.ConnectionId != "" { + return errorsmod.Wrap(ErrInvalidCounterparty, "counterparty connection identifier must be empty") + } + + // NOTE: Version can be nil on MsgConnectionOpenInit + if msg.Version != nil { + if err := ValidateVersion(msg.Version); err != nil { + return errorsmod.Wrap(err, "basic validation of the provided version failed") + } + } + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + return msg.Counterparty.ValidateBasic() +} + +// NewMsgConnectionOpenTry creates a new MsgConnectionOpenTry instance +func NewMsgConnectionOpenTry( + clientID, counterpartyConnectionID, counterpartyClientID string, + counterpartyPrefix commitmenttypes.MerklePrefix, + counterpartyVersions []*Version, delayPeriod uint64, + initProof []byte, proofHeight clienttypes.Height, signer string, +) *MsgConnectionOpenTry { + counterparty := NewCounterparty(counterpartyClientID, counterpartyConnectionID, counterpartyPrefix) + return &MsgConnectionOpenTry{ + ClientId: clientID, + Counterparty: counterparty, + CounterpartyVersions: counterpartyVersions, + DelayPeriod: delayPeriod, + ProofInit: initProof, + ProofHeight: proofHeight, + Signer: signer, + } +} + +// ValidateBasic implements sdk.Msg +func (msg MsgConnectionOpenTry) ValidateBasic() error { + if msg.ClientId == exported.LocalhostClientID { + return errorsmod.Wrap(clienttypes.ErrInvalidClientType, "localhost connection handshakes are disallowed") + } + + if err := host.ClientIdentifierValidator(msg.ClientId); err != nil { + return errorsmod.Wrap(err, "invalid client ID") + } + // counterparty validate basic allows empty counterparty connection identifiers + if err := host.ConnectionIdentifierValidator(msg.Counterparty.ConnectionId); err != nil { + return errorsmod.Wrap(err, "invalid counterparty connection ID") + } + if len(msg.CounterpartyVersions) == 0 { + return errorsmod.Wrap(ibcerrors.ErrInvalidVersion, "empty counterparty versions") + } + if len(msg.CounterpartyVersions) > MaxCounterpartyVersionsLength { + return errorsmod.Wrapf(ibcerrors.ErrInvalidVersion, "counterparty versions must not exceed %d items", MaxCounterpartyVersionsLength) + } + for i, version := range msg.CounterpartyVersions { + if err := ValidateVersion(version); err != nil { + return errorsmod.Wrapf(err, "basic validation failed on version with index %d", i) + } + } + if len(msg.ProofInit) == 0 { + return errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty proof init") + } + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + return msg.Counterparty.ValidateBasic() +} + +// NewMsgConnectionOpenAck creates a new MsgConnectionOpenAck instance +func NewMsgConnectionOpenAck( + connectionID, counterpartyConnectionID string, tryProof []byte, + proofHeight clienttypes.Height, version *Version, signer string, +) *MsgConnectionOpenAck { + return &MsgConnectionOpenAck{ + ConnectionId: connectionID, + CounterpartyConnectionId: counterpartyConnectionID, + ProofTry: tryProof, + ProofHeight: proofHeight, + Version: version, + Signer: signer, + } +} + +// ValidateBasic implements sdk.Msg +func (msg MsgConnectionOpenAck) ValidateBasic() error { + if !IsValidConnectionID(msg.ConnectionId) { + return ErrInvalidConnectionIdentifier + } + if err := host.ConnectionIdentifierValidator(msg.CounterpartyConnectionId); err != nil { + return errorsmod.Wrap(err, "invalid counterparty connection ID") + } + if err := ValidateVersion(msg.Version); err != nil { + return err + } + if len(msg.ProofTry) == 0 { + return errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty proof try") + } + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + return nil +} + +// NewMsgConnectionOpenConfirm creates a new MsgConnectionOpenConfirm instance +func NewMsgConnectionOpenConfirm( + connectionID string, ackProof []byte, proofHeight clienttypes.Height, + signer string, +) *MsgConnectionOpenConfirm { + return &MsgConnectionOpenConfirm{ + ConnectionId: connectionID, + ProofAck: ackProof, + ProofHeight: proofHeight, + Signer: signer, + } +} + +// ValidateBasic implements sdk.Msg +func (msg MsgConnectionOpenConfirm) ValidateBasic() error { + if !IsValidConnectionID(msg.ConnectionId) { + return ErrInvalidConnectionIdentifier + } + if len(msg.ProofAck) == 0 { + return errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty proof ack") + } + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + return nil +} + +// NewMsgUpdateParams creates a new MsgUpdateParams instance +func NewMsgUpdateParams(signer string, params Params) *MsgUpdateParams { + return &MsgUpdateParams{ + Signer: signer, + Params: params, + } +} + +// ValidateBasic performs basic checks on a MsgUpdateParams. +func (msg *MsgUpdateParams) ValidateBasic() error { + if _, err := sdk.AccAddressFromBech32(msg.Signer); err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + return msg.Params.Validate() +} diff --git a/modules/core/03-connection/types/msgs_test.go b/modules/core/03-connection/types/msgs_test.go new file mode 100644 index 0000000..cae6f23 --- /dev/null +++ b/modules/core/03-connection/types/msgs_test.go @@ -0,0 +1,269 @@ +package types_test + +import ( + "errors" + "fmt" + "testing" + + dbm "github.com/cosmos/cosmos-db" + "github.com/stretchr/testify/require" + testifysuite "github.com/stretchr/testify/suite" + + "cosmossdk.io/log" + "cosmossdk.io/store/iavl" + "cosmossdk.io/store/metrics" + "cosmossdk.io/store/rootmulti" + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + + ibc "github.com/cosmos/ibc-go/v10/modules/core" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + "github.com/cosmos/ibc-go/v10/testing/simapp" +) + +var ( + signer = "cosmos1ckgw5d7jfj7wwxjzs9fdrdev9vc8dzcw3n2lht" + + emptyPrefix = commitmenttypes.MerklePrefix{} + emptyProof = []byte{} +) + +type MsgTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain + + proof []byte +} + +func (suite *MsgTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) + + app := simapp.Setup(suite.T(), false) + db := dbm.NewMemDB() + store := rootmulti.NewStore(db, log.NewNopLogger(), metrics.NewNoOpMetrics()) + storeKey := storetypes.NewKVStoreKey("iavlStoreKey") + + store.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, nil) + err := store.LoadVersion(0) + suite.Require().NoError(err) + iavlStore, ok := store.GetCommitStore(storeKey).(*iavl.Store) + suite.Require().True(ok) + + iavlStore.Set([]byte("KEY"), []byte("VALUE")) + _ = store.Commit() + + res, err := store.Query(&storetypes.RequestQuery{ + Data: []byte("KEY"), + Path: fmt.Sprintf("/%s/key", storeKey.Name()), // required path to get key/value+proof + Prove: true, + }) + suite.Require().NoError(err) + + merkleProof, err := commitmenttypes.ConvertProofs(res.ProofOps) + suite.Require().NoError(err) + proof, err := app.AppCodec().Marshal(&merkleProof) + suite.Require().NoError(err) + + suite.proof = proof +} + +func TestMsgTestSuite(t *testing.T) { + testifysuite.Run(t, new(MsgTestSuite)) +} + +func (suite *MsgTestSuite) TestNewMsgConnectionOpenInit() { + prefix := commitmenttypes.NewMerklePrefix([]byte("storePrefixKey")) + // empty versions are considered valid, the default compatible versions + // will be used in protocol. + var version *types.Version + + testCases := []struct { + name string + msg *types.MsgConnectionOpenInit + expError error + }{ + {"localhost client ID", types.NewMsgConnectionOpenInit(exported.LocalhostClientID, "clienttotest", prefix, version, 500, signer), clienttypes.ErrInvalidClientType}, + {"invalid client ID", types.NewMsgConnectionOpenInit("test/iris", "clienttotest", prefix, version, 500, signer), host.ErrInvalidID}, + {"invalid counterparty client ID", types.NewMsgConnectionOpenInit("clienttotest", "(clienttotest)", prefix, version, 500, signer), host.ErrInvalidID}, + {"invalid counterparty connection ID", &types.MsgConnectionOpenInit{connectionID, types.NewCounterparty("clienttotest", "connectiontotest", prefix), version, 500, signer}, types.ErrInvalidCounterparty}, + {"empty counterparty prefix", types.NewMsgConnectionOpenInit("clienttotest", "clienttotest", emptyPrefix, version, 500, signer), types.ErrInvalidCounterparty}, + {"supplied version fails basic validation", types.NewMsgConnectionOpenInit("clienttotest", "clienttotest", prefix, &types.Version{}, 500, signer), types.ErrInvalidVersion}, + {"empty singer", types.NewMsgConnectionOpenInit("clienttotest", "clienttotest", prefix, version, 500, ""), ibcerrors.ErrInvalidAddress}, + {"success", types.NewMsgConnectionOpenInit("clienttotest", "clienttotest", prefix, version, 500, signer), nil}, + } + + for _, tc := range testCases { + + err := tc.msg.ValidateBasic() + + if tc.expError == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + } +} + +func (suite *MsgTestSuite) TestNewMsgConnectionOpenTry() { + prefix := commitmenttypes.NewMerklePrefix([]byte("storePrefixKey")) + + testCases := []struct { + name string + msg *types.MsgConnectionOpenTry + expError error + }{ + {"success", types.NewMsgConnectionOpenTry("clienttotesta", "connectiontotest", "clienttotest", prefix, []*types.Version{ibctesting.ConnectionVersion}, 500, suite.proof, clientHeight, signer), nil}, + {"localhost client ID", types.NewMsgConnectionOpenTry(exported.LocalhostClientID, "connectiontotest", "clienttotest", prefix, []*types.Version{ibctesting.ConnectionVersion}, 500, suite.proof, clientHeight, signer), clienttypes.ErrInvalidClientType}, + {"invalid client ID", types.NewMsgConnectionOpenTry("test/iris", "connectiontotest", "clienttotest", prefix, []*types.Version{ibctesting.ConnectionVersion}, 500, suite.proof, clientHeight, signer), host.ErrInvalidID}, + {"invalid counterparty connection ID", types.NewMsgConnectionOpenTry("clienttotesta", "ibc/test", "clienttotest", prefix, []*types.Version{ibctesting.ConnectionVersion}, 500, suite.proof, clientHeight, signer), host.ErrInvalidID}, + {"invalid counterparty client ID", types.NewMsgConnectionOpenTry("clienttotesta", "connectiontotest", "test/conn1", prefix, []*types.Version{ibctesting.ConnectionVersion}, 500, suite.proof, clientHeight, signer), host.ErrInvalidID}, + {"empty counterparty prefix", types.NewMsgConnectionOpenTry("clienttotesta", "connectiontotest", "clienttotest", emptyPrefix, []*types.Version{ibctesting.ConnectionVersion}, 500, suite.proof, clientHeight, signer), types.ErrInvalidCounterparty}, + {"empty counterpartyVersions", types.NewMsgConnectionOpenTry("clienttotesta", "connectiontotest", "clienttotest", prefix, []*types.Version{}, 500, suite.proof, clientHeight, signer), ibcerrors.ErrInvalidVersion}, + {"empty proofInit", types.NewMsgConnectionOpenTry("clienttotesta", "connectiontotest", "clienttotest", prefix, []*types.Version{ibctesting.ConnectionVersion}, 500, emptyProof, clientHeight, signer), commitmenttypes.ErrInvalidProof}, + {"empty singer", types.NewMsgConnectionOpenTry("clienttotesta", "connectiontotest", "clienttotest", prefix, []*types.Version{ibctesting.ConnectionVersion}, 500, suite.proof, clientHeight, ""), ibcerrors.ErrInvalidAddress}, + {"invalid version", types.NewMsgConnectionOpenTry("clienttotesta", "connectiontotest", "clienttotest", prefix, []*types.Version{{}}, 500, suite.proof, clientHeight, signer), types.ErrInvalidVersion}, + {"too many counterparty versions", types.NewMsgConnectionOpenTry("clienttotesta", "connectiontotest", "clienttotest", prefix, make([]*types.Version, types.MaxCounterpartyVersionsLength+1), 500, suite.proof, clientHeight, signer), ibcerrors.ErrInvalidVersion}, + {"too many features in counterparty version", types.NewMsgConnectionOpenTry("clienttotesta", "connectiontotest", "clienttotest", prefix, []*types.Version{{"v1", make([]string, types.MaxFeaturesLength+1)}}, 500, suite.proof, clientHeight, signer), types.ErrInvalidVersion}, + } + + for _, tc := range testCases { + + err := tc.msg.ValidateBasic() + + if tc.expError == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + } +} + +func (suite *MsgTestSuite) TestNewMsgConnectionOpenAck() { + testCases := []struct { + name string + msg *types.MsgConnectionOpenAck + expError error + }{ + {"success", types.NewMsgConnectionOpenAck(connectionID, connectionID, suite.proof, clientHeight, ibctesting.ConnectionVersion, signer), nil}, + {"invalid connection ID", types.NewMsgConnectionOpenAck("test/conn1", connectionID, suite.proof, clientHeight, ibctesting.ConnectionVersion, signer), types.ErrInvalidConnectionIdentifier}, + {"invalid counterparty connection ID", types.NewMsgConnectionOpenAck(connectionID, "test/conn1", suite.proof, clientHeight, ibctesting.ConnectionVersion, signer), host.ErrInvalidID}, + {"empty proofTry", types.NewMsgConnectionOpenAck(connectionID, connectionID, emptyProof, clientHeight, ibctesting.ConnectionVersion, signer), commitmenttypes.ErrInvalidProof}, + {"invalid version", types.NewMsgConnectionOpenAck(connectionID, connectionID, suite.proof, clientHeight, &types.Version{}, signer), types.ErrInvalidVersion}, + {"empty signer", types.NewMsgConnectionOpenAck(connectionID, connectionID, suite.proof, clientHeight, ibctesting.ConnectionVersion, ""), ibcerrors.ErrInvalidAddress}, + } + + for _, tc := range testCases { + + err := tc.msg.ValidateBasic() + + if tc.expError == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + } +} + +func (suite *MsgTestSuite) TestNewMsgConnectionOpenConfirm() { + testCases := []struct { + name string + msg *types.MsgConnectionOpenConfirm + expError error + }{ + {"invalid connection ID", types.NewMsgConnectionOpenConfirm("test/conn1", suite.proof, clientHeight, signer), types.ErrInvalidConnectionIdentifier}, + {"empty proofTry", types.NewMsgConnectionOpenConfirm(connectionID, emptyProof, clientHeight, signer), commitmenttypes.ErrInvalidProof}, + {"empty signer", types.NewMsgConnectionOpenConfirm(connectionID, suite.proof, clientHeight, ""), ibcerrors.ErrInvalidAddress}, + {"success", types.NewMsgConnectionOpenConfirm(connectionID, suite.proof, clientHeight, signer), nil}, + } + + for _, tc := range testCases { + + err := tc.msg.ValidateBasic() + + if tc.expError == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + } +} + +// TestMsgUpdateParamsValidateBasic tests ValidateBasic for MsgUpdateParams +func (suite *MsgTestSuite) TestMsgUpdateParamsValidateBasic() { + signer := suite.chainA.App.GetIBCKeeper().GetAuthority() + testCases := []struct { + name string + msg *types.MsgUpdateParams + expError error + }{ + { + "success: valid signer and params", + types.NewMsgUpdateParams(signer, types.DefaultParams()), + nil, + }, + { + "failure: invalid signer address", + types.NewMsgUpdateParams("invalid", types.DefaultParams()), + ibcerrors.ErrInvalidAddress, + }, + { + "failure: invalid time per block", + types.NewMsgUpdateParams(signer, types.NewParams(0)), + errors.New("MaxExpectedTimePerBlock cannot be zero"), + }, + } + + for _, tc := range testCases { + + err := tc.msg.ValidateBasic() + if tc.expError == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().ErrorContains(err, tc.expError.Error()) + } + } +} + +// TestMsgUpdateParamsGetSigners tests GetSigners for MsgUpdateParams +func TestMsgUpdateParamsGetSigners(t *testing.T) { + testCases := []struct { + name string + address sdk.AccAddress + errMsg string + }{ + {"success: valid address", sdk.AccAddress(ibctesting.TestAccAddress), ""}, + {"failure: nil address", nil, "empty address string is not allowed"}, + } + + for _, tc := range testCases { + + msg := types.MsgUpdateParams{ + Signer: tc.address.String(), + Params: types.DefaultParams(), + } + + encodingCfg := moduletestutil.MakeTestEncodingConfig(ibc.AppModuleBasic{}) + signers, _, err := encodingCfg.Codec.GetMsgV1Signers(&msg) + if tc.errMsg == "" { + require.NoError(t, err) + require.Equal(t, tc.address.Bytes(), signers[0]) + } else { + require.ErrorContains(t, err, tc.errMsg) + } + } +} diff --git a/modules/core/03-connection/types/params.go b/modules/core/03-connection/types/params.go new file mode 100644 index 0000000..c14e31d --- /dev/null +++ b/modules/core/03-connection/types/params.go @@ -0,0 +1,29 @@ +package types + +import ( + "errors" + "time" +) + +// DefaultTimePerBlock is the default value for maximum expected time per block (in nanoseconds). +const DefaultTimePerBlock = 30 * time.Second + +// NewParams creates a new parameter configuration for the ibc connection module +func NewParams(timePerBlock uint64) Params { + return Params{ + MaxExpectedTimePerBlock: timePerBlock, + } +} + +// DefaultParams is the default parameter configuration for the ibc connection module +func DefaultParams() Params { + return NewParams(uint64(DefaultTimePerBlock)) +} + +// Validate ensures MaxExpectedTimePerBlock is non-zero +func (p Params) Validate() error { + if p.MaxExpectedTimePerBlock == 0 { + return errors.New("MaxExpectedTimePerBlock cannot be zero") + } + return nil +} diff --git a/modules/core/03-connection/types/params_legacy.go b/modules/core/03-connection/types/params_legacy.go new file mode 100644 index 0000000..195d56d --- /dev/null +++ b/modules/core/03-connection/types/params_legacy.go @@ -0,0 +1,36 @@ +/* +NOTE: Usage of x/params to manage parameters is deprecated in favor of x/gov +controlled execution of MsgUpdateParams messages. These types remains solely +for migration purposes and will be removed in a future release. +[#3621](https://github.com/cosmos/ibc-go/issues/3621) +*/ +package types + +import ( + "fmt" + + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" +) + +// KeyMaxExpectedTimePerBlock is store's key for MaxExpectedTimePerBlock parameter +var KeyMaxExpectedTimePerBlock = []byte("MaxExpectedTimePerBlock") + +// ParamKeyTable type declaration for parameters +func ParamKeyTable() paramtypes.KeyTable { + return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) +} + +// ParamSetPairs implements params.ParamSet +func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { + return paramtypes.ParamSetPairs{ + paramtypes.NewParamSetPair(KeyMaxExpectedTimePerBlock, &p.MaxExpectedTimePerBlock, validateParams), + } +} + +func validateParams(i any) error { + _, ok := i.(uint64) + if !ok { + return fmt.Errorf("invalid parameter. expected %T, got type: %T", uint64(1), i) + } + return nil +} diff --git a/modules/core/03-connection/types/params_test.go b/modules/core/03-connection/types/params_test.go new file mode 100644 index 0000000..963a2a2 --- /dev/null +++ b/modules/core/03-connection/types/params_test.go @@ -0,0 +1,32 @@ +package types_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" +) + +func TestValidateParams(t *testing.T) { + testCases := []struct { + name string + params types.Params + expError error + }{ + {"default params", types.DefaultParams(), nil}, + {"custom params", types.NewParams(10), nil}, + {"blank client", types.NewParams(0), errors.New("MaxExpectedTimePerBlock cannot be zero")}, + } + + for _, tc := range testCases { + + err := tc.params.Validate() + if tc.expError == nil { + require.NoError(t, err, tc.name) + } else { + require.ErrorContains(t, err, tc.expError.Error()) + } + } +} diff --git a/modules/core/03-connection/types/query.go b/modules/core/03-connection/types/query.go new file mode 100644 index 0000000..14771ea --- /dev/null +++ b/modules/core/03-connection/types/query.go @@ -0,0 +1,71 @@ +package types + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var ( + _ codectypes.UnpackInterfacesMessage = (*QueryConnectionClientStateResponse)(nil) + _ codectypes.UnpackInterfacesMessage = (*QueryConnectionConsensusStateResponse)(nil) +) + +// NewQueryConnectionResponse creates a new QueryConnectionResponse instance +func NewQueryConnectionResponse( + connection ConnectionEnd, proof []byte, height clienttypes.Height, +) *QueryConnectionResponse { + return &QueryConnectionResponse{ + Connection: &connection, + Proof: proof, + ProofHeight: height, + } +} + +// NewQueryClientConnectionsResponse creates a new ConnectionPaths instance +func NewQueryClientConnectionsResponse( + connectionPaths []string, proof []byte, height clienttypes.Height, +) *QueryClientConnectionsResponse { + return &QueryClientConnectionsResponse{ + ConnectionPaths: connectionPaths, + Proof: proof, + ProofHeight: height, + } +} + +// NewQueryClientConnectionsRequest creates a new QueryClientConnectionsRequest instance +func NewQueryClientConnectionsRequest(clientID string) *QueryClientConnectionsRequest { + return &QueryClientConnectionsRequest{ + ClientId: clientID, + } +} + +// NewQueryConnectionClientStateResponse creates a newQueryConnectionClientStateResponse instance +func NewQueryConnectionClientStateResponse(identifiedClientState clienttypes.IdentifiedClientState, proof []byte, height clienttypes.Height) *QueryConnectionClientStateResponse { + return &QueryConnectionClientStateResponse{ + IdentifiedClientState: &identifiedClientState, + Proof: proof, + ProofHeight: height, + } +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (qccsr QueryConnectionClientStateResponse) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + return qccsr.IdentifiedClientState.UnpackInterfaces(unpacker) +} + +// NewQueryConnectionConsensusStateResponse creates a newQueryConnectionConsensusStateResponse instance +func NewQueryConnectionConsensusStateResponse(clientID string, anyConsensusState *codectypes.Any, consensusStateHeight exported.Height, proof []byte, height clienttypes.Height) *QueryConnectionConsensusStateResponse { + return &QueryConnectionConsensusStateResponse{ + ConsensusState: anyConsensusState, + ClientId: clientID, + Proof: proof, + ProofHeight: height, + } +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (qccsr QueryConnectionConsensusStateResponse) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + return unpacker.UnpackAny(qccsr.ConsensusState, new(exported.ConsensusState)) +} diff --git a/modules/core/03-connection/types/query.pb.go b/modules/core/03-connection/types/query.pb.go new file mode 100644 index 0000000..0e0a8c0 --- /dev/null +++ b/modules/core/03-connection/types/query.pb.go @@ -0,0 +1,3234 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/connection/v1/query.proto + +package types + +import ( + context "context" + fmt "fmt" + types1 "github.com/cosmos/cosmos-sdk/codec/types" + query "github.com/cosmos/cosmos-sdk/types/query" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + types "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// QueryConnectionRequest is the request type for the Query/Connection RPC +// method +type QueryConnectionRequest struct { + // connection unique identifier + ConnectionId string `protobuf:"bytes,1,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"` +} + +func (m *QueryConnectionRequest) Reset() { *m = QueryConnectionRequest{} } +func (m *QueryConnectionRequest) String() string { return proto.CompactTextString(m) } +func (*QueryConnectionRequest) ProtoMessage() {} +func (*QueryConnectionRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_cd8d529f8c7cd06b, []int{0} +} +func (m *QueryConnectionRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryConnectionRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryConnectionRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryConnectionRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryConnectionRequest.Merge(m, src) +} +func (m *QueryConnectionRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryConnectionRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryConnectionRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryConnectionRequest proto.InternalMessageInfo + +func (m *QueryConnectionRequest) GetConnectionId() string { + if m != nil { + return m.ConnectionId + } + return "" +} + +// QueryConnectionResponse is the response type for the Query/Connection RPC +// method. Besides the connection end, it includes a proof and the height from +// which the proof was retrieved. +type QueryConnectionResponse struct { + // connection associated with the request identifier + Connection *ConnectionEnd `protobuf:"bytes,1,opt,name=connection,proto3" json:"connection,omitempty"` + // merkle proof of existence + Proof []byte `protobuf:"bytes,2,opt,name=proof,proto3" json:"proof,omitempty"` + // height at which the proof was retrieved + ProofHeight types.Height `protobuf:"bytes,3,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` +} + +func (m *QueryConnectionResponse) Reset() { *m = QueryConnectionResponse{} } +func (m *QueryConnectionResponse) String() string { return proto.CompactTextString(m) } +func (*QueryConnectionResponse) ProtoMessage() {} +func (*QueryConnectionResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_cd8d529f8c7cd06b, []int{1} +} +func (m *QueryConnectionResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryConnectionResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryConnectionResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryConnectionResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryConnectionResponse.Merge(m, src) +} +func (m *QueryConnectionResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryConnectionResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryConnectionResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryConnectionResponse proto.InternalMessageInfo + +func (m *QueryConnectionResponse) GetConnection() *ConnectionEnd { + if m != nil { + return m.Connection + } + return nil +} + +func (m *QueryConnectionResponse) GetProof() []byte { + if m != nil { + return m.Proof + } + return nil +} + +func (m *QueryConnectionResponse) GetProofHeight() types.Height { + if m != nil { + return m.ProofHeight + } + return types.Height{} +} + +// QueryConnectionsRequest is the request type for the Query/Connections RPC +// method +type QueryConnectionsRequest struct { + Pagination *query.PageRequest `protobuf:"bytes,1,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryConnectionsRequest) Reset() { *m = QueryConnectionsRequest{} } +func (m *QueryConnectionsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryConnectionsRequest) ProtoMessage() {} +func (*QueryConnectionsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_cd8d529f8c7cd06b, []int{2} +} +func (m *QueryConnectionsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryConnectionsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryConnectionsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryConnectionsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryConnectionsRequest.Merge(m, src) +} +func (m *QueryConnectionsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryConnectionsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryConnectionsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryConnectionsRequest proto.InternalMessageInfo + +func (m *QueryConnectionsRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryConnectionsResponse is the response type for the Query/Connections RPC +// method. +type QueryConnectionsResponse struct { + // list of stored connections of the chain. + Connections []*IdentifiedConnection `protobuf:"bytes,1,rep,name=connections,proto3" json:"connections,omitempty"` + // pagination response + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` + // query block height + Height types.Height `protobuf:"bytes,3,opt,name=height,proto3" json:"height"` +} + +func (m *QueryConnectionsResponse) Reset() { *m = QueryConnectionsResponse{} } +func (m *QueryConnectionsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryConnectionsResponse) ProtoMessage() {} +func (*QueryConnectionsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_cd8d529f8c7cd06b, []int{3} +} +func (m *QueryConnectionsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryConnectionsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryConnectionsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryConnectionsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryConnectionsResponse.Merge(m, src) +} +func (m *QueryConnectionsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryConnectionsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryConnectionsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryConnectionsResponse proto.InternalMessageInfo + +func (m *QueryConnectionsResponse) GetConnections() []*IdentifiedConnection { + if m != nil { + return m.Connections + } + return nil +} + +func (m *QueryConnectionsResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + +func (m *QueryConnectionsResponse) GetHeight() types.Height { + if m != nil { + return m.Height + } + return types.Height{} +} + +// QueryClientConnectionsRequest is the request type for the +// Query/ClientConnections RPC method +type QueryClientConnectionsRequest struct { + // client identifier associated with a connection + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` +} + +func (m *QueryClientConnectionsRequest) Reset() { *m = QueryClientConnectionsRequest{} } +func (m *QueryClientConnectionsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryClientConnectionsRequest) ProtoMessage() {} +func (*QueryClientConnectionsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_cd8d529f8c7cd06b, []int{4} +} +func (m *QueryClientConnectionsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryClientConnectionsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryClientConnectionsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryClientConnectionsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryClientConnectionsRequest.Merge(m, src) +} +func (m *QueryClientConnectionsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryClientConnectionsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryClientConnectionsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryClientConnectionsRequest proto.InternalMessageInfo + +func (m *QueryClientConnectionsRequest) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +// QueryClientConnectionsResponse is the response type for the +// Query/ClientConnections RPC method +type QueryClientConnectionsResponse struct { + // slice of all the connection paths associated with a client. + ConnectionPaths []string `protobuf:"bytes,1,rep,name=connection_paths,json=connectionPaths,proto3" json:"connection_paths,omitempty"` + // merkle proof of existence + Proof []byte `protobuf:"bytes,2,opt,name=proof,proto3" json:"proof,omitempty"` + // height at which the proof was generated + ProofHeight types.Height `protobuf:"bytes,3,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` +} + +func (m *QueryClientConnectionsResponse) Reset() { *m = QueryClientConnectionsResponse{} } +func (m *QueryClientConnectionsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryClientConnectionsResponse) ProtoMessage() {} +func (*QueryClientConnectionsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_cd8d529f8c7cd06b, []int{5} +} +func (m *QueryClientConnectionsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryClientConnectionsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryClientConnectionsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryClientConnectionsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryClientConnectionsResponse.Merge(m, src) +} +func (m *QueryClientConnectionsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryClientConnectionsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryClientConnectionsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryClientConnectionsResponse proto.InternalMessageInfo + +func (m *QueryClientConnectionsResponse) GetConnectionPaths() []string { + if m != nil { + return m.ConnectionPaths + } + return nil +} + +func (m *QueryClientConnectionsResponse) GetProof() []byte { + if m != nil { + return m.Proof + } + return nil +} + +func (m *QueryClientConnectionsResponse) GetProofHeight() types.Height { + if m != nil { + return m.ProofHeight + } + return types.Height{} +} + +// QueryConnectionClientStateRequest is the request type for the +// Query/ConnectionClientState RPC method +type QueryConnectionClientStateRequest struct { + // connection identifier + ConnectionId string `protobuf:"bytes,1,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"` +} + +func (m *QueryConnectionClientStateRequest) Reset() { *m = QueryConnectionClientStateRequest{} } +func (m *QueryConnectionClientStateRequest) String() string { return proto.CompactTextString(m) } +func (*QueryConnectionClientStateRequest) ProtoMessage() {} +func (*QueryConnectionClientStateRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_cd8d529f8c7cd06b, []int{6} +} +func (m *QueryConnectionClientStateRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryConnectionClientStateRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryConnectionClientStateRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryConnectionClientStateRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryConnectionClientStateRequest.Merge(m, src) +} +func (m *QueryConnectionClientStateRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryConnectionClientStateRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryConnectionClientStateRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryConnectionClientStateRequest proto.InternalMessageInfo + +func (m *QueryConnectionClientStateRequest) GetConnectionId() string { + if m != nil { + return m.ConnectionId + } + return "" +} + +// QueryConnectionClientStateResponse is the response type for the +// Query/ConnectionClientState RPC method +type QueryConnectionClientStateResponse struct { + // client state associated with the channel + IdentifiedClientState *types.IdentifiedClientState `protobuf:"bytes,1,opt,name=identified_client_state,json=identifiedClientState,proto3" json:"identified_client_state,omitempty"` + // merkle proof of existence + Proof []byte `protobuf:"bytes,2,opt,name=proof,proto3" json:"proof,omitempty"` + // height at which the proof was retrieved + ProofHeight types.Height `protobuf:"bytes,3,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` +} + +func (m *QueryConnectionClientStateResponse) Reset() { *m = QueryConnectionClientStateResponse{} } +func (m *QueryConnectionClientStateResponse) String() string { return proto.CompactTextString(m) } +func (*QueryConnectionClientStateResponse) ProtoMessage() {} +func (*QueryConnectionClientStateResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_cd8d529f8c7cd06b, []int{7} +} +func (m *QueryConnectionClientStateResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryConnectionClientStateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryConnectionClientStateResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryConnectionClientStateResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryConnectionClientStateResponse.Merge(m, src) +} +func (m *QueryConnectionClientStateResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryConnectionClientStateResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryConnectionClientStateResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryConnectionClientStateResponse proto.InternalMessageInfo + +func (m *QueryConnectionClientStateResponse) GetIdentifiedClientState() *types.IdentifiedClientState { + if m != nil { + return m.IdentifiedClientState + } + return nil +} + +func (m *QueryConnectionClientStateResponse) GetProof() []byte { + if m != nil { + return m.Proof + } + return nil +} + +func (m *QueryConnectionClientStateResponse) GetProofHeight() types.Height { + if m != nil { + return m.ProofHeight + } + return types.Height{} +} + +// QueryConnectionConsensusStateRequest is the request type for the +// Query/ConnectionConsensusState RPC method +type QueryConnectionConsensusStateRequest struct { + // connection identifier + ConnectionId string `protobuf:"bytes,1,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"` + RevisionNumber uint64 `protobuf:"varint,2,opt,name=revision_number,json=revisionNumber,proto3" json:"revision_number,omitempty"` + RevisionHeight uint64 `protobuf:"varint,3,opt,name=revision_height,json=revisionHeight,proto3" json:"revision_height,omitempty"` +} + +func (m *QueryConnectionConsensusStateRequest) Reset() { *m = QueryConnectionConsensusStateRequest{} } +func (m *QueryConnectionConsensusStateRequest) String() string { return proto.CompactTextString(m) } +func (*QueryConnectionConsensusStateRequest) ProtoMessage() {} +func (*QueryConnectionConsensusStateRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_cd8d529f8c7cd06b, []int{8} +} +func (m *QueryConnectionConsensusStateRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryConnectionConsensusStateRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryConnectionConsensusStateRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryConnectionConsensusStateRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryConnectionConsensusStateRequest.Merge(m, src) +} +func (m *QueryConnectionConsensusStateRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryConnectionConsensusStateRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryConnectionConsensusStateRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryConnectionConsensusStateRequest proto.InternalMessageInfo + +func (m *QueryConnectionConsensusStateRequest) GetConnectionId() string { + if m != nil { + return m.ConnectionId + } + return "" +} + +func (m *QueryConnectionConsensusStateRequest) GetRevisionNumber() uint64 { + if m != nil { + return m.RevisionNumber + } + return 0 +} + +func (m *QueryConnectionConsensusStateRequest) GetRevisionHeight() uint64 { + if m != nil { + return m.RevisionHeight + } + return 0 +} + +// QueryConnectionConsensusStateResponse is the response type for the +// Query/ConnectionConsensusState RPC method +type QueryConnectionConsensusStateResponse struct { + // consensus state associated with the channel + ConsensusState *types1.Any `protobuf:"bytes,1,opt,name=consensus_state,json=consensusState,proto3" json:"consensus_state,omitempty"` + // client ID associated with the consensus state + ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // merkle proof of existence + Proof []byte `protobuf:"bytes,3,opt,name=proof,proto3" json:"proof,omitempty"` + // height at which the proof was retrieved + ProofHeight types.Height `protobuf:"bytes,4,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` +} + +func (m *QueryConnectionConsensusStateResponse) Reset() { *m = QueryConnectionConsensusStateResponse{} } +func (m *QueryConnectionConsensusStateResponse) String() string { return proto.CompactTextString(m) } +func (*QueryConnectionConsensusStateResponse) ProtoMessage() {} +func (*QueryConnectionConsensusStateResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_cd8d529f8c7cd06b, []int{9} +} +func (m *QueryConnectionConsensusStateResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryConnectionConsensusStateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryConnectionConsensusStateResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryConnectionConsensusStateResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryConnectionConsensusStateResponse.Merge(m, src) +} +func (m *QueryConnectionConsensusStateResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryConnectionConsensusStateResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryConnectionConsensusStateResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryConnectionConsensusStateResponse proto.InternalMessageInfo + +func (m *QueryConnectionConsensusStateResponse) GetConsensusState() *types1.Any { + if m != nil { + return m.ConsensusState + } + return nil +} + +func (m *QueryConnectionConsensusStateResponse) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +func (m *QueryConnectionConsensusStateResponse) GetProof() []byte { + if m != nil { + return m.Proof + } + return nil +} + +func (m *QueryConnectionConsensusStateResponse) GetProofHeight() types.Height { + if m != nil { + return m.ProofHeight + } + return types.Height{} +} + +// QueryConnectionParamsRequest is the request type for the Query/ConnectionParams RPC method. +type QueryConnectionParamsRequest struct { +} + +func (m *QueryConnectionParamsRequest) Reset() { *m = QueryConnectionParamsRequest{} } +func (m *QueryConnectionParamsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryConnectionParamsRequest) ProtoMessage() {} +func (*QueryConnectionParamsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_cd8d529f8c7cd06b, []int{10} +} +func (m *QueryConnectionParamsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryConnectionParamsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryConnectionParamsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryConnectionParamsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryConnectionParamsRequest.Merge(m, src) +} +func (m *QueryConnectionParamsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryConnectionParamsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryConnectionParamsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryConnectionParamsRequest proto.InternalMessageInfo + +// QueryConnectionParamsResponse is the response type for the Query/ConnectionParams RPC method. +type QueryConnectionParamsResponse struct { + // params defines the parameters of the module. + Params *Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params,omitempty"` +} + +func (m *QueryConnectionParamsResponse) Reset() { *m = QueryConnectionParamsResponse{} } +func (m *QueryConnectionParamsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryConnectionParamsResponse) ProtoMessage() {} +func (*QueryConnectionParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_cd8d529f8c7cd06b, []int{11} +} +func (m *QueryConnectionParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryConnectionParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryConnectionParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryConnectionParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryConnectionParamsResponse.Merge(m, src) +} +func (m *QueryConnectionParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryConnectionParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryConnectionParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryConnectionParamsResponse proto.InternalMessageInfo + +func (m *QueryConnectionParamsResponse) GetParams() *Params { + if m != nil { + return m.Params + } + return nil +} + +func init() { + proto.RegisterType((*QueryConnectionRequest)(nil), "ibc.core.connection.v1.QueryConnectionRequest") + proto.RegisterType((*QueryConnectionResponse)(nil), "ibc.core.connection.v1.QueryConnectionResponse") + proto.RegisterType((*QueryConnectionsRequest)(nil), "ibc.core.connection.v1.QueryConnectionsRequest") + proto.RegisterType((*QueryConnectionsResponse)(nil), "ibc.core.connection.v1.QueryConnectionsResponse") + proto.RegisterType((*QueryClientConnectionsRequest)(nil), "ibc.core.connection.v1.QueryClientConnectionsRequest") + proto.RegisterType((*QueryClientConnectionsResponse)(nil), "ibc.core.connection.v1.QueryClientConnectionsResponse") + proto.RegisterType((*QueryConnectionClientStateRequest)(nil), "ibc.core.connection.v1.QueryConnectionClientStateRequest") + proto.RegisterType((*QueryConnectionClientStateResponse)(nil), "ibc.core.connection.v1.QueryConnectionClientStateResponse") + proto.RegisterType((*QueryConnectionConsensusStateRequest)(nil), "ibc.core.connection.v1.QueryConnectionConsensusStateRequest") + proto.RegisterType((*QueryConnectionConsensusStateResponse)(nil), "ibc.core.connection.v1.QueryConnectionConsensusStateResponse") + proto.RegisterType((*QueryConnectionParamsRequest)(nil), "ibc.core.connection.v1.QueryConnectionParamsRequest") + proto.RegisterType((*QueryConnectionParamsResponse)(nil), "ibc.core.connection.v1.QueryConnectionParamsResponse") +} + +func init() { + proto.RegisterFile("ibc/core/connection/v1/query.proto", fileDescriptor_cd8d529f8c7cd06b) +} + +var fileDescriptor_cd8d529f8c7cd06b = []byte{ + // 935 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0x41, 0x6f, 0x1b, 0x45, + 0x14, 0xce, 0x38, 0x69, 0x44, 0x9e, 0x43, 0x5b, 0x46, 0x69, 0x6b, 0x96, 0xb2, 0x0d, 0x5b, 0xd2, + 0xa4, 0x40, 0x67, 0xe2, 0x84, 0x44, 0x05, 0x1a, 0x04, 0xa9, 0x0a, 0xc9, 0xa5, 0x0a, 0xcb, 0xa1, + 0x12, 0x97, 0x68, 0x77, 0x3d, 0xd9, 0xac, 0x14, 0xef, 0xb8, 0x9e, 0xb5, 0x51, 0x54, 0x45, 0x48, + 0xfc, 0x02, 0x24, 0x2e, 0x5c, 0x7a, 0x05, 0x89, 0x7f, 0x80, 0xb8, 0x71, 0xea, 0xb1, 0x12, 0x97, + 0x9e, 0x2a, 0xe4, 0x70, 0xe5, 0x3f, 0xa0, 0x9d, 0x99, 0xed, 0xce, 0xda, 0xde, 0xc4, 0xb6, 0xd4, + 0xdb, 0xfa, 0xcd, 0x7b, 0x6f, 0xbe, 0xef, 0x7b, 0x6f, 0xbf, 0x35, 0x38, 0x91, 0x1f, 0xd0, 0x80, + 0xb7, 0x19, 0x0d, 0x78, 0x1c, 0xb3, 0x20, 0x89, 0x78, 0x4c, 0xbb, 0x75, 0xfa, 0xb8, 0xc3, 0xda, + 0xc7, 0xa4, 0xd5, 0xe6, 0x09, 0xc7, 0x57, 0x23, 0x3f, 0x20, 0x69, 0x0e, 0xc9, 0x73, 0x48, 0xb7, + 0x6e, 0x2d, 0x84, 0x3c, 0xe4, 0x32, 0x85, 0xa6, 0x4f, 0x2a, 0xdb, 0xfa, 0x20, 0xe0, 0xa2, 0xc9, + 0x05, 0xf5, 0x3d, 0xc1, 0x54, 0x1b, 0xda, 0xad, 0xfb, 0x2c, 0xf1, 0xea, 0xb4, 0xe5, 0x85, 0x51, + 0xec, 0xc9, 0x72, 0x95, 0x7b, 0x23, 0xbf, 0xfd, 0x28, 0x62, 0x71, 0x92, 0xde, 0xac, 0x9e, 0x74, + 0xc2, 0x72, 0x09, 0x3c, 0x03, 0x88, 0x4a, 0xbc, 0x1e, 0x72, 0x1e, 0x1e, 0x31, 0xea, 0xb5, 0x22, + 0xea, 0xc5, 0x31, 0x4f, 0xe4, 0x35, 0x42, 0x9f, 0xbe, 0xad, 0x4f, 0xe5, 0x2f, 0xbf, 0x73, 0x40, + 0xbd, 0x58, 0x93, 0x73, 0xb6, 0xe0, 0xea, 0x37, 0x29, 0xc8, 0xfb, 0xaf, 0x3a, 0xba, 0xec, 0x71, + 0x87, 0x89, 0x04, 0xdf, 0x84, 0x37, 0xf3, 0x6b, 0xf6, 0xa3, 0x46, 0x0d, 0x2d, 0xa2, 0x95, 0x39, + 0x77, 0x3e, 0x0f, 0xee, 0x36, 0x9c, 0x3f, 0x11, 0x5c, 0x1b, 0xa8, 0x17, 0x2d, 0x1e, 0x0b, 0x86, + 0x1f, 0x00, 0xe4, 0xb9, 0xb2, 0xba, 0xba, 0xb6, 0x44, 0x86, 0x8b, 0x49, 0xf2, 0xfa, 0x07, 0x71, + 0xc3, 0x35, 0x0a, 0xf1, 0x02, 0x5c, 0x68, 0xb5, 0x39, 0x3f, 0xa8, 0x55, 0x16, 0xd1, 0xca, 0xbc, + 0xab, 0x7e, 0xe0, 0xfb, 0x30, 0x2f, 0x1f, 0xf6, 0x0f, 0x59, 0x14, 0x1e, 0x26, 0xb5, 0x69, 0xd9, + 0xde, 0x32, 0xda, 0x2b, 0x1d, 0xbb, 0x75, 0xb2, 0x23, 0x33, 0xb6, 0x67, 0x9e, 0xbd, 0xbc, 0x31, + 0xe5, 0x56, 0x65, 0x95, 0x0a, 0x39, 0xde, 0x00, 0x78, 0x91, 0xb1, 0xff, 0x0a, 0x20, 0x1f, 0x97, + 0x06, 0x7f, 0x8b, 0xa8, 0xd9, 0x92, 0x74, 0xb6, 0x44, 0xad, 0x88, 0x9e, 0x2d, 0xd9, 0xf3, 0x42, + 0xa6, 0x6b, 0x5d, 0xa3, 0xd2, 0xf9, 0x0f, 0x41, 0x6d, 0xf0, 0x0e, 0xad, 0xd0, 0x43, 0xa8, 0xe6, + 0x44, 0x45, 0x0d, 0x2d, 0x4e, 0xaf, 0x54, 0xd7, 0x3e, 0x2a, 0x93, 0x68, 0xb7, 0xc1, 0xe2, 0x24, + 0x3a, 0x88, 0x58, 0xc3, 0x10, 0xdb, 0x6c, 0x80, 0xbf, 0x2e, 0x80, 0xae, 0x48, 0xd0, 0xcb, 0xe7, + 0x82, 0x56, 0x60, 0x4c, 0xd4, 0xf8, 0x2e, 0xcc, 0x8e, 0xa9, 0xab, 0xce, 0x77, 0xee, 0xc1, 0xbb, + 0x8a, 0xae, 0x4c, 0x1b, 0x22, 0xec, 0x3b, 0x30, 0xa7, 0x5a, 0xe4, 0x2b, 0xf5, 0x86, 0x0a, 0xec, + 0x36, 0x9c, 0x5f, 0x11, 0xd8, 0x65, 0xe5, 0x5a, 0xb3, 0xdb, 0x70, 0xd9, 0x58, 0xcb, 0x96, 0x97, + 0x1c, 0x2a, 0xe1, 0xe6, 0xdc, 0x4b, 0x79, 0x7c, 0x2f, 0x0d, 0xbf, 0xce, 0xcd, 0xd9, 0x81, 0xf7, + 0xfa, 0xa6, 0xaa, 0x10, 0x7f, 0x9b, 0x78, 0x09, 0x1b, 0xeb, 0x0d, 0xea, 0x21, 0x70, 0xce, 0x6a, + 0xa5, 0x69, 0x7b, 0x70, 0x2d, 0x7a, 0x35, 0xff, 0x7d, 0xad, 0xa0, 0x48, 0x53, 0xf4, 0x72, 0xde, + 0x1e, 0x46, 0xc0, 0x58, 0x19, 0xa3, 0xe7, 0x95, 0x68, 0x58, 0xf8, 0x75, 0xca, 0xf5, 0x14, 0xc1, + 0xfb, 0xfd, 0x24, 0x53, 0x5a, 0xb1, 0xe8, 0x88, 0xb1, 0x25, 0xc3, 0xcb, 0x70, 0xa9, 0xcd, 0xba, + 0x91, 0x48, 0x53, 0xe2, 0x4e, 0xd3, 0x67, 0x6d, 0x09, 0x79, 0xc6, 0xbd, 0x98, 0x85, 0x1f, 0xca, + 0x68, 0x21, 0xd1, 0x80, 0x6f, 0x24, 0x6a, 0x7c, 0x2f, 0x11, 0x2c, 0x9d, 0x83, 0x4f, 0xcf, 0x61, + 0x0b, 0xd2, 0x35, 0x53, 0x27, 0x05, 0xfd, 0x17, 0x88, 0x32, 0x59, 0x92, 0x99, 0x2c, 0xf9, 0x32, + 0x3e, 0x76, 0x2f, 0x06, 0x85, 0x36, 0xc5, 0xed, 0xaf, 0x14, 0xb7, 0x3f, 0x1f, 0xc0, 0xf4, 0x59, + 0x03, 0x98, 0x99, 0x64, 0x00, 0x36, 0x5c, 0xef, 0xe3, 0xb7, 0xe7, 0xb5, 0xbd, 0x66, 0xf6, 0x56, + 0x3a, 0x8f, 0xb2, 0xd7, 0x76, 0xe0, 0x5c, 0xf3, 0xde, 0x84, 0xd9, 0x96, 0x8c, 0x68, 0xba, 0x76, + 0x99, 0x4b, 0xe9, 0x3a, 0x9d, 0xbd, 0xf6, 0xc7, 0x1c, 0x5c, 0x90, 0x9d, 0xf1, 0xef, 0x08, 0x20, + 0x6f, 0x8f, 0x49, 0x59, 0x83, 0xe1, 0x9f, 0x23, 0x8b, 0x8e, 0x9c, 0xaf, 0x10, 0x3b, 0x9f, 0xfd, + 0xf8, 0xf7, 0xbf, 0x3f, 0x57, 0x36, 0xf0, 0x3a, 0x3d, 0xf7, 0x23, 0x2a, 0xe8, 0x93, 0xc2, 0xd6, + 0x9d, 0xe0, 0xa7, 0x08, 0xaa, 0x86, 0xfb, 0xe0, 0x51, 0x6f, 0xcf, 0x04, 0xb5, 0x56, 0x47, 0x2f, + 0xd0, 0x78, 0x3f, 0x94, 0x78, 0x97, 0xf0, 0xcd, 0x11, 0xf0, 0xe2, 0xbf, 0x10, 0xbc, 0x35, 0xe0, + 0x91, 0x78, 0xe3, 0xec, 0x4b, 0x4b, 0x2c, 0xd9, 0xda, 0x1c, 0xb7, 0x4c, 0x23, 0xfe, 0x5c, 0x22, + 0xbe, 0x8b, 0x37, 0x4b, 0x11, 0xab, 0x55, 0x2f, 0x0a, 0x9d, 0xad, 0xff, 0x09, 0x7e, 0x81, 0xe0, + 0xca, 0x50, 0xd7, 0xc3, 0x9f, 0x8c, 0xa8, 0xde, 0xa0, 0xe9, 0x5a, 0x9f, 0x4e, 0x52, 0xaa, 0x09, + 0xed, 0x48, 0x42, 0xdb, 0xf8, 0x8b, 0x09, 0x56, 0x86, 0x9a, 0x9e, 0x8c, 0x7f, 0xa9, 0x40, 0xad, + 0xcc, 0x4b, 0xf0, 0xbd, 0x51, 0x21, 0x0e, 0xb3, 0x48, 0x6b, 0x6b, 0xc2, 0x6a, 0xcd, 0xf1, 0x07, + 0xc9, 0xf1, 0x18, 0x7f, 0x3f, 0x11, 0xc7, 0xa2, 0xf5, 0xd1, 0xcc, 0x46, 0xe9, 0x93, 0x3e, 0x43, + 0x3e, 0xa1, 0xca, 0xad, 0x8c, 0x03, 0x15, 0x38, 0xc1, 0xbf, 0x21, 0xb8, 0xdc, 0x6f, 0x33, 0xf8, + 0xe3, 0x11, 0x49, 0x15, 0x5c, 0xcb, 0xda, 0x18, 0xb3, 0x4a, 0x4b, 0x70, 0x4b, 0x4a, 0xb0, 0x88, + 0xed, 0x32, 0x09, 0x94, 0x77, 0x6d, 0x3f, 0x7a, 0xd6, 0xb3, 0xd1, 0xf3, 0x9e, 0x8d, 0xfe, 0xe9, + 0xd9, 0xe8, 0xa7, 0x53, 0x7b, 0xea, 0xf9, 0xa9, 0x3d, 0xf5, 0xe2, 0xd4, 0x9e, 0xfa, 0x6e, 0x2b, + 0x8c, 0x92, 0xc3, 0x8e, 0x4f, 0x02, 0xde, 0xa4, 0xfa, 0xff, 0x7e, 0xe4, 0x07, 0x77, 0x42, 0x4e, + 0xbb, 0xf5, 0x55, 0xda, 0xe4, 0x8d, 0xce, 0x11, 0x13, 0xaa, 0xf3, 0xea, 0xfa, 0x1d, 0xa3, 0x79, + 0x72, 0xdc, 0x62, 0xc2, 0x9f, 0x95, 0xdf, 0x88, 0xf5, 0xff, 0x03, 0x00, 0x00, 0xff, 0xff, 0x1f, + 0x4f, 0x5d, 0x8e, 0x7e, 0x0c, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // Connection queries an IBC connection end. + Connection(ctx context.Context, in *QueryConnectionRequest, opts ...grpc.CallOption) (*QueryConnectionResponse, error) + // Connections queries all the IBC connections of a chain. + Connections(ctx context.Context, in *QueryConnectionsRequest, opts ...grpc.CallOption) (*QueryConnectionsResponse, error) + // ClientConnections queries the connection paths associated with a client + // state. + ClientConnections(ctx context.Context, in *QueryClientConnectionsRequest, opts ...grpc.CallOption) (*QueryClientConnectionsResponse, error) + // ConnectionClientState queries the client state associated with the + // connection. + ConnectionClientState(ctx context.Context, in *QueryConnectionClientStateRequest, opts ...grpc.CallOption) (*QueryConnectionClientStateResponse, error) + // ConnectionConsensusState queries the consensus state associated with the + // connection. + ConnectionConsensusState(ctx context.Context, in *QueryConnectionConsensusStateRequest, opts ...grpc.CallOption) (*QueryConnectionConsensusStateResponse, error) + // ConnectionParams queries all parameters of the ibc connection submodule. + ConnectionParams(ctx context.Context, in *QueryConnectionParamsRequest, opts ...grpc.CallOption) (*QueryConnectionParamsResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) Connection(ctx context.Context, in *QueryConnectionRequest, opts ...grpc.CallOption) (*QueryConnectionResponse, error) { + out := new(QueryConnectionResponse) + err := c.cc.Invoke(ctx, "/ibc.core.connection.v1.Query/Connection", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) Connections(ctx context.Context, in *QueryConnectionsRequest, opts ...grpc.CallOption) (*QueryConnectionsResponse, error) { + out := new(QueryConnectionsResponse) + err := c.cc.Invoke(ctx, "/ibc.core.connection.v1.Query/Connections", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) ClientConnections(ctx context.Context, in *QueryClientConnectionsRequest, opts ...grpc.CallOption) (*QueryClientConnectionsResponse, error) { + out := new(QueryClientConnectionsResponse) + err := c.cc.Invoke(ctx, "/ibc.core.connection.v1.Query/ClientConnections", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) ConnectionClientState(ctx context.Context, in *QueryConnectionClientStateRequest, opts ...grpc.CallOption) (*QueryConnectionClientStateResponse, error) { + out := new(QueryConnectionClientStateResponse) + err := c.cc.Invoke(ctx, "/ibc.core.connection.v1.Query/ConnectionClientState", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) ConnectionConsensusState(ctx context.Context, in *QueryConnectionConsensusStateRequest, opts ...grpc.CallOption) (*QueryConnectionConsensusStateResponse, error) { + out := new(QueryConnectionConsensusStateResponse) + err := c.cc.Invoke(ctx, "/ibc.core.connection.v1.Query/ConnectionConsensusState", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) ConnectionParams(ctx context.Context, in *QueryConnectionParamsRequest, opts ...grpc.CallOption) (*QueryConnectionParamsResponse, error) { + out := new(QueryConnectionParamsResponse) + err := c.cc.Invoke(ctx, "/ibc.core.connection.v1.Query/ConnectionParams", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // Connection queries an IBC connection end. + Connection(context.Context, *QueryConnectionRequest) (*QueryConnectionResponse, error) + // Connections queries all the IBC connections of a chain. + Connections(context.Context, *QueryConnectionsRequest) (*QueryConnectionsResponse, error) + // ClientConnections queries the connection paths associated with a client + // state. + ClientConnections(context.Context, *QueryClientConnectionsRequest) (*QueryClientConnectionsResponse, error) + // ConnectionClientState queries the client state associated with the + // connection. + ConnectionClientState(context.Context, *QueryConnectionClientStateRequest) (*QueryConnectionClientStateResponse, error) + // ConnectionConsensusState queries the consensus state associated with the + // connection. + ConnectionConsensusState(context.Context, *QueryConnectionConsensusStateRequest) (*QueryConnectionConsensusStateResponse, error) + // ConnectionParams queries all parameters of the ibc connection submodule. + ConnectionParams(context.Context, *QueryConnectionParamsRequest) (*QueryConnectionParamsResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) Connection(ctx context.Context, req *QueryConnectionRequest) (*QueryConnectionResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Connection not implemented") +} +func (*UnimplementedQueryServer) Connections(ctx context.Context, req *QueryConnectionsRequest) (*QueryConnectionsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Connections not implemented") +} +func (*UnimplementedQueryServer) ClientConnections(ctx context.Context, req *QueryClientConnectionsRequest) (*QueryClientConnectionsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ClientConnections not implemented") +} +func (*UnimplementedQueryServer) ConnectionClientState(ctx context.Context, req *QueryConnectionClientStateRequest) (*QueryConnectionClientStateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ConnectionClientState not implemented") +} +func (*UnimplementedQueryServer) ConnectionConsensusState(ctx context.Context, req *QueryConnectionConsensusStateRequest) (*QueryConnectionConsensusStateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ConnectionConsensusState not implemented") +} +func (*UnimplementedQueryServer) ConnectionParams(ctx context.Context, req *QueryConnectionParamsRequest) (*QueryConnectionParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ConnectionParams not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_Connection_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryConnectionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Connection(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.connection.v1.Query/Connection", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Connection(ctx, req.(*QueryConnectionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_Connections_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryConnectionsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Connections(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.connection.v1.Query/Connections", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Connections(ctx, req.(*QueryConnectionsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_ClientConnections_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryClientConnectionsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).ClientConnections(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.connection.v1.Query/ClientConnections", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).ClientConnections(ctx, req.(*QueryClientConnectionsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_ConnectionClientState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryConnectionClientStateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).ConnectionClientState(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.connection.v1.Query/ConnectionClientState", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).ConnectionClientState(ctx, req.(*QueryConnectionClientStateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_ConnectionConsensusState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryConnectionConsensusStateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).ConnectionConsensusState(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.connection.v1.Query/ConnectionConsensusState", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).ConnectionConsensusState(ctx, req.(*QueryConnectionConsensusStateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_ConnectionParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryConnectionParamsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).ConnectionParams(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.connection.v1.Query/ConnectionParams", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).ConnectionParams(ctx, req.(*QueryConnectionParamsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ibc.core.connection.v1.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Connection", + Handler: _Query_Connection_Handler, + }, + { + MethodName: "Connections", + Handler: _Query_Connections_Handler, + }, + { + MethodName: "ClientConnections", + Handler: _Query_ClientConnections_Handler, + }, + { + MethodName: "ConnectionClientState", + Handler: _Query_ConnectionClientState_Handler, + }, + { + MethodName: "ConnectionConsensusState", + Handler: _Query_ConnectionConsensusState_Handler, + }, + { + MethodName: "ConnectionParams", + Handler: _Query_ConnectionParams_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "ibc/core/connection/v1/query.proto", +} + +func (m *QueryConnectionRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryConnectionRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryConnectionRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ConnectionId) > 0 { + i -= len(m.ConnectionId) + copy(dAtA[i:], m.ConnectionId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ConnectionId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryConnectionResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryConnectionResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryConnectionResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.Proof) > 0 { + i -= len(m.Proof) + copy(dAtA[i:], m.Proof) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Proof))) + i-- + dAtA[i] = 0x12 + } + if m.Connection != nil { + { + size, err := m.Connection.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryConnectionsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryConnectionsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryConnectionsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryConnectionsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryConnectionsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryConnectionsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Height.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Connections) > 0 { + for iNdEx := len(m.Connections) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Connections[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *QueryClientConnectionsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryClientConnectionsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryClientConnectionsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryClientConnectionsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryClientConnectionsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryClientConnectionsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.Proof) > 0 { + i -= len(m.Proof) + copy(dAtA[i:], m.Proof) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Proof))) + i-- + dAtA[i] = 0x12 + } + if len(m.ConnectionPaths) > 0 { + for iNdEx := len(m.ConnectionPaths) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.ConnectionPaths[iNdEx]) + copy(dAtA[i:], m.ConnectionPaths[iNdEx]) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ConnectionPaths[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *QueryConnectionClientStateRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryConnectionClientStateRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryConnectionClientStateRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ConnectionId) > 0 { + i -= len(m.ConnectionId) + copy(dAtA[i:], m.ConnectionId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ConnectionId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryConnectionClientStateResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryConnectionClientStateResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryConnectionClientStateResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.Proof) > 0 { + i -= len(m.Proof) + copy(dAtA[i:], m.Proof) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Proof))) + i-- + dAtA[i] = 0x12 + } + if m.IdentifiedClientState != nil { + { + size, err := m.IdentifiedClientState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryConnectionConsensusStateRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryConnectionConsensusStateRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryConnectionConsensusStateRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.RevisionHeight != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.RevisionHeight)) + i-- + dAtA[i] = 0x18 + } + if m.RevisionNumber != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.RevisionNumber)) + i-- + dAtA[i] = 0x10 + } + if len(m.ConnectionId) > 0 { + i -= len(m.ConnectionId) + copy(dAtA[i:], m.ConnectionId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ConnectionId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryConnectionConsensusStateResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryConnectionConsensusStateResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryConnectionConsensusStateResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + if len(m.Proof) > 0 { + i -= len(m.Proof) + copy(dAtA[i:], m.Proof) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Proof))) + i-- + dAtA[i] = 0x1a + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0x12 + } + if m.ConsensusState != nil { + { + size, err := m.ConsensusState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryConnectionParamsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryConnectionParamsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryConnectionParamsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryConnectionParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryConnectionParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryConnectionParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Params != nil { + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryConnectionRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ConnectionId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryConnectionResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Connection != nil { + l = m.Connection.Size() + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.Proof) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryConnectionsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryConnectionsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Connections) > 0 { + for _, e := range m.Connections { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + l = m.Height.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryClientConnectionsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryClientConnectionsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.ConnectionPaths) > 0 { + for _, s := range m.ConnectionPaths { + l = len(s) + n += 1 + l + sovQuery(uint64(l)) + } + } + l = len(m.Proof) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryConnectionClientStateRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ConnectionId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryConnectionClientStateResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.IdentifiedClientState != nil { + l = m.IdentifiedClientState.Size() + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.Proof) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryConnectionConsensusStateRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ConnectionId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.RevisionNumber != 0 { + n += 1 + sovQuery(uint64(m.RevisionNumber)) + } + if m.RevisionHeight != 0 { + n += 1 + sovQuery(uint64(m.RevisionHeight)) + } + return n +} + +func (m *QueryConnectionConsensusStateResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ConsensusState != nil { + l = m.ConsensusState.Size() + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.Proof) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryConnectionParamsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryConnectionParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Params != nil { + l = m.Params.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryConnectionRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryConnectionRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryConnectionRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectionId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ConnectionId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryConnectionResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryConnectionResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryConnectionResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Connection", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Connection == nil { + m.Connection = &ConnectionEnd{} + } + if err := m.Connection.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proof", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Proof = append(m.Proof[:0], dAtA[iNdEx:postIndex]...) + if m.Proof == nil { + m.Proof = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryConnectionsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryConnectionsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryConnectionsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryConnectionsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryConnectionsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryConnectionsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Connections", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Connections = append(m.Connections, &IdentifiedConnection{}) + if err := m.Connections[len(m.Connections)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Height.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryClientConnectionsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryClientConnectionsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryClientConnectionsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryClientConnectionsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryClientConnectionsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryClientConnectionsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectionPaths", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ConnectionPaths = append(m.ConnectionPaths, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proof", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Proof = append(m.Proof[:0], dAtA[iNdEx:postIndex]...) + if m.Proof == nil { + m.Proof = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryConnectionClientStateRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryConnectionClientStateRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryConnectionClientStateRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectionId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ConnectionId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryConnectionClientStateResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryConnectionClientStateResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryConnectionClientStateResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field IdentifiedClientState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.IdentifiedClientState == nil { + m.IdentifiedClientState = &types.IdentifiedClientState{} + } + if err := m.IdentifiedClientState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proof", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Proof = append(m.Proof[:0], dAtA[iNdEx:postIndex]...) + if m.Proof == nil { + m.Proof = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryConnectionConsensusStateRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryConnectionConsensusStateRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryConnectionConsensusStateRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectionId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ConnectionId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RevisionNumber", wireType) + } + m.RevisionNumber = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.RevisionNumber |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RevisionHeight", wireType) + } + m.RevisionHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.RevisionHeight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryConnectionConsensusStateResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryConnectionConsensusStateResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryConnectionConsensusStateResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConsensusState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ConsensusState == nil { + m.ConsensusState = &types1.Any{} + } + if err := m.ConsensusState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proof", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Proof = append(m.Proof[:0], dAtA[iNdEx:postIndex]...) + if m.Proof == nil { + m.Proof = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryConnectionParamsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryConnectionParamsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryConnectionParamsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryConnectionParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryConnectionParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryConnectionParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Params == nil { + m.Params = &Params{} + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/03-connection/types/query.pb.gw.go b/modules/core/03-connection/types/query.pb.gw.go new file mode 100644 index 0000000..86ba135 --- /dev/null +++ b/modules/core/03-connection/types/query.pb.gw.go @@ -0,0 +1,684 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: ibc/core/connection/v1/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +func request_Query_Connection_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryConnectionRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["connection_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "connection_id") + } + + protoReq.ConnectionId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "connection_id", err) + } + + msg, err := client.Connection(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Connection_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryConnectionRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["connection_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "connection_id") + } + + protoReq.ConnectionId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "connection_id", err) + } + + msg, err := server.Connection(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Query_Connections_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Query_Connections_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryConnectionsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Connections_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Connections(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Connections_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryConnectionsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Connections_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Connections(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_ClientConnections_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryClientConnectionsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + msg, err := client.ClientConnections(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_ClientConnections_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryClientConnectionsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + msg, err := server.ClientConnections(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_ConnectionClientState_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryConnectionClientStateRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["connection_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "connection_id") + } + + protoReq.ConnectionId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "connection_id", err) + } + + msg, err := client.ConnectionClientState(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_ConnectionClientState_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryConnectionClientStateRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["connection_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "connection_id") + } + + protoReq.ConnectionId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "connection_id", err) + } + + msg, err := server.ConnectionClientState(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_ConnectionConsensusState_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryConnectionConsensusStateRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["connection_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "connection_id") + } + + protoReq.ConnectionId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "connection_id", err) + } + + val, ok = pathParams["revision_number"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "revision_number") + } + + protoReq.RevisionNumber, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "revision_number", err) + } + + val, ok = pathParams["revision_height"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "revision_height") + } + + protoReq.RevisionHeight, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "revision_height", err) + } + + msg, err := client.ConnectionConsensusState(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_ConnectionConsensusState_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryConnectionConsensusStateRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["connection_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "connection_id") + } + + protoReq.ConnectionId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "connection_id", err) + } + + val, ok = pathParams["revision_number"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "revision_number") + } + + protoReq.RevisionNumber, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "revision_number", err) + } + + val, ok = pathParams["revision_height"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "revision_height") + } + + protoReq.RevisionHeight, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "revision_height", err) + } + + msg, err := server.ConnectionConsensusState(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_ConnectionParams_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryConnectionParamsRequest + var metadata runtime.ServerMetadata + + msg, err := client.ConnectionParams(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_ConnectionParams_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryConnectionParamsRequest + var metadata runtime.ServerMetadata + + msg, err := server.ConnectionParams(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_Connection_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Connection_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Connection_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Connections_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Connections_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Connections_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ClientConnections_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_ClientConnections_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ClientConnections_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ConnectionClientState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_ConnectionClientState_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ConnectionClientState_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ConnectionConsensusState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_ConnectionConsensusState_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ConnectionConsensusState_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ConnectionParams_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_ConnectionParams_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ConnectionParams_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_Connection_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Connection_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Connection_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Connections_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Connections_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Connections_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ClientConnections_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_ClientConnections_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ClientConnections_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ConnectionClientState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_ConnectionClientState_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ConnectionClientState_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ConnectionConsensusState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_ConnectionConsensusState_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ConnectionConsensusState_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ConnectionParams_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_ConnectionParams_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ConnectionParams_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_Connection_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"ibc", "core", "connection", "v1", "connections", "connection_id"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_Connections_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"ibc", "core", "connection", "v1", "connections"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_ClientConnections_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"ibc", "core", "connection", "v1", "client_connections", "client_id"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_ConnectionClientState_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6}, []string{"ibc", "core", "connection", "v1", "connections", "connection_id", "client_state"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_ConnectionConsensusState_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 2, 7, 1, 0, 4, 1, 5, 8, 2, 9, 1, 0, 4, 1, 5, 10}, []string{"ibc", "core", "connection", "v1", "connections", "connection_id", "consensus_state", "revision", "revision_number", "height", "revision_height"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_ConnectionParams_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"ibc", "core", "connection", "v1", "params"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Query_Connection_0 = runtime.ForwardResponseMessage + + forward_Query_Connections_0 = runtime.ForwardResponseMessage + + forward_Query_ClientConnections_0 = runtime.ForwardResponseMessage + + forward_Query_ConnectionClientState_0 = runtime.ForwardResponseMessage + + forward_Query_ConnectionConsensusState_0 = runtime.ForwardResponseMessage + + forward_Query_ConnectionParams_0 = runtime.ForwardResponseMessage +) diff --git a/modules/core/03-connection/types/tx.pb.go b/modules/core/03-connection/types/tx.pb.go new file mode 100644 index 0000000..bb8cb24 --- /dev/null +++ b/modules/core/03-connection/types/tx.pb.go @@ -0,0 +1,3255 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/connection/v1/tx.proto + +package types + +import ( + context "context" + fmt "fmt" + types "github.com/cosmos/cosmos-sdk/codec/types" + _ "github.com/cosmos/cosmos-sdk/types/msgservice" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + types1 "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// MsgConnectionOpenInit defines the msg sent by an account on Chain A to +// initialize a connection with Chain B. +type MsgConnectionOpenInit struct { + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + Counterparty Counterparty `protobuf:"bytes,2,opt,name=counterparty,proto3" json:"counterparty"` + Version *Version `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` + DelayPeriod uint64 `protobuf:"varint,4,opt,name=delay_period,json=delayPeriod,proto3" json:"delay_period,omitempty"` + Signer string `protobuf:"bytes,5,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgConnectionOpenInit) Reset() { *m = MsgConnectionOpenInit{} } +func (m *MsgConnectionOpenInit) String() string { return proto.CompactTextString(m) } +func (*MsgConnectionOpenInit) ProtoMessage() {} +func (*MsgConnectionOpenInit) Descriptor() ([]byte, []int) { + return fileDescriptor_5d00fde5fc97399e, []int{0} +} +func (m *MsgConnectionOpenInit) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgConnectionOpenInit) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgConnectionOpenInit.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgConnectionOpenInit) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgConnectionOpenInit.Merge(m, src) +} +func (m *MsgConnectionOpenInit) XXX_Size() int { + return m.Size() +} +func (m *MsgConnectionOpenInit) XXX_DiscardUnknown() { + xxx_messageInfo_MsgConnectionOpenInit.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgConnectionOpenInit proto.InternalMessageInfo + +// MsgConnectionOpenInitResponse defines the Msg/ConnectionOpenInit response +// type. +type MsgConnectionOpenInitResponse struct { +} + +func (m *MsgConnectionOpenInitResponse) Reset() { *m = MsgConnectionOpenInitResponse{} } +func (m *MsgConnectionOpenInitResponse) String() string { return proto.CompactTextString(m) } +func (*MsgConnectionOpenInitResponse) ProtoMessage() {} +func (*MsgConnectionOpenInitResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_5d00fde5fc97399e, []int{1} +} +func (m *MsgConnectionOpenInitResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgConnectionOpenInitResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgConnectionOpenInitResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgConnectionOpenInitResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgConnectionOpenInitResponse.Merge(m, src) +} +func (m *MsgConnectionOpenInitResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgConnectionOpenInitResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgConnectionOpenInitResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgConnectionOpenInitResponse proto.InternalMessageInfo + +// MsgConnectionOpenTry defines a msg sent by a Relayer to try to open a +// connection on Chain B. +type MsgConnectionOpenTry struct { + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // Deprecated: this field is unused. Crossing hellos are no longer supported in core IBC. + PreviousConnectionId string `protobuf:"bytes,2,opt,name=previous_connection_id,json=previousConnectionId,proto3" json:"previous_connection_id,omitempty"` // Deprecated: Do not use. + // Deprecated: this field is unused. + ClientState *types.Any `protobuf:"bytes,3,opt,name=client_state,json=clientState,proto3" json:"client_state,omitempty"` // Deprecated: Do not use. + Counterparty Counterparty `protobuf:"bytes,4,opt,name=counterparty,proto3" json:"counterparty"` + DelayPeriod uint64 `protobuf:"varint,5,opt,name=delay_period,json=delayPeriod,proto3" json:"delay_period,omitempty"` + CounterpartyVersions []*Version `protobuf:"bytes,6,rep,name=counterparty_versions,json=counterpartyVersions,proto3" json:"counterparty_versions,omitempty"` + ProofHeight types1.Height `protobuf:"bytes,7,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` + // proof of the initialization the connection on Chain A: `UNINITIALIZED -> + // INIT` + ProofInit []byte `protobuf:"bytes,8,opt,name=proof_init,json=proofInit,proto3" json:"proof_init,omitempty"` + // Deprecated: this field is unused. + ProofClient []byte `protobuf:"bytes,9,opt,name=proof_client,json=proofClient,proto3" json:"proof_client,omitempty"` // Deprecated: Do not use. + // Deprecated: this field is unused. + ProofConsensus []byte `protobuf:"bytes,10,opt,name=proof_consensus,json=proofConsensus,proto3" json:"proof_consensus,omitempty"` // Deprecated: Do not use. + // Deprecated: this field is unused. + ConsensusHeight types1.Height `protobuf:"bytes,11,opt,name=consensus_height,json=consensusHeight,proto3" json:"consensus_height"` // Deprecated: Do not use. + Signer string `protobuf:"bytes,12,opt,name=signer,proto3" json:"signer,omitempty"` + // Deprecated: this field is unused. + HostConsensusStateProof []byte `protobuf:"bytes,13,opt,name=host_consensus_state_proof,json=hostConsensusStateProof,proto3" json:"host_consensus_state_proof,omitempty"` // Deprecated: Do not use. +} + +func (m *MsgConnectionOpenTry) Reset() { *m = MsgConnectionOpenTry{} } +func (m *MsgConnectionOpenTry) String() string { return proto.CompactTextString(m) } +func (*MsgConnectionOpenTry) ProtoMessage() {} +func (*MsgConnectionOpenTry) Descriptor() ([]byte, []int) { + return fileDescriptor_5d00fde5fc97399e, []int{2} +} +func (m *MsgConnectionOpenTry) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgConnectionOpenTry) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgConnectionOpenTry.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgConnectionOpenTry) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgConnectionOpenTry.Merge(m, src) +} +func (m *MsgConnectionOpenTry) XXX_Size() int { + return m.Size() +} +func (m *MsgConnectionOpenTry) XXX_DiscardUnknown() { + xxx_messageInfo_MsgConnectionOpenTry.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgConnectionOpenTry proto.InternalMessageInfo + +// MsgConnectionOpenTryResponse defines the Msg/ConnectionOpenTry response type. +type MsgConnectionOpenTryResponse struct { +} + +func (m *MsgConnectionOpenTryResponse) Reset() { *m = MsgConnectionOpenTryResponse{} } +func (m *MsgConnectionOpenTryResponse) String() string { return proto.CompactTextString(m) } +func (*MsgConnectionOpenTryResponse) ProtoMessage() {} +func (*MsgConnectionOpenTryResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_5d00fde5fc97399e, []int{3} +} +func (m *MsgConnectionOpenTryResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgConnectionOpenTryResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgConnectionOpenTryResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgConnectionOpenTryResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgConnectionOpenTryResponse.Merge(m, src) +} +func (m *MsgConnectionOpenTryResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgConnectionOpenTryResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgConnectionOpenTryResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgConnectionOpenTryResponse proto.InternalMessageInfo + +// MsgConnectionOpenAck defines a msg sent by a Relayer to Chain A to +// acknowledge the change of connection state to TRYOPEN on Chain B. +type MsgConnectionOpenAck struct { + ConnectionId string `protobuf:"bytes,1,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"` + CounterpartyConnectionId string `protobuf:"bytes,2,opt,name=counterparty_connection_id,json=counterpartyConnectionId,proto3" json:"counterparty_connection_id,omitempty"` + Version *Version `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` + // Deprecated: this field is unused. + ClientState *types.Any `protobuf:"bytes,4,opt,name=client_state,json=clientState,proto3" json:"client_state,omitempty"` // Deprecated: Do not use. + ProofHeight types1.Height `protobuf:"bytes,5,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` + // proof of the initialization the connection on Chain B: `UNINITIALIZED -> + // TRYOPEN` + ProofTry []byte `protobuf:"bytes,6,opt,name=proof_try,json=proofTry,proto3" json:"proof_try,omitempty"` + // Deprecated: this field is unused. + ProofClient []byte `protobuf:"bytes,7,opt,name=proof_client,json=proofClient,proto3" json:"proof_client,omitempty"` // Deprecated: Do not use. + // Deprecated: this field is unused. + ProofConsensus []byte `protobuf:"bytes,8,opt,name=proof_consensus,json=proofConsensus,proto3" json:"proof_consensus,omitempty"` // Deprecated: Do not use. + // Deprecated: this field is unused. + ConsensusHeight types1.Height `protobuf:"bytes,9,opt,name=consensus_height,json=consensusHeight,proto3" json:"consensus_height"` // Deprecated: Do not use. + Signer string `protobuf:"bytes,10,opt,name=signer,proto3" json:"signer,omitempty"` + // Deprecated: this field is unused. + HostConsensusStateProof []byte `protobuf:"bytes,11,opt,name=host_consensus_state_proof,json=hostConsensusStateProof,proto3" json:"host_consensus_state_proof,omitempty"` // Deprecated: Do not use. +} + +func (m *MsgConnectionOpenAck) Reset() { *m = MsgConnectionOpenAck{} } +func (m *MsgConnectionOpenAck) String() string { return proto.CompactTextString(m) } +func (*MsgConnectionOpenAck) ProtoMessage() {} +func (*MsgConnectionOpenAck) Descriptor() ([]byte, []int) { + return fileDescriptor_5d00fde5fc97399e, []int{4} +} +func (m *MsgConnectionOpenAck) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgConnectionOpenAck) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgConnectionOpenAck.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgConnectionOpenAck) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgConnectionOpenAck.Merge(m, src) +} +func (m *MsgConnectionOpenAck) XXX_Size() int { + return m.Size() +} +func (m *MsgConnectionOpenAck) XXX_DiscardUnknown() { + xxx_messageInfo_MsgConnectionOpenAck.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgConnectionOpenAck proto.InternalMessageInfo + +// MsgConnectionOpenAckResponse defines the Msg/ConnectionOpenAck response type. +type MsgConnectionOpenAckResponse struct { +} + +func (m *MsgConnectionOpenAckResponse) Reset() { *m = MsgConnectionOpenAckResponse{} } +func (m *MsgConnectionOpenAckResponse) String() string { return proto.CompactTextString(m) } +func (*MsgConnectionOpenAckResponse) ProtoMessage() {} +func (*MsgConnectionOpenAckResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_5d00fde5fc97399e, []int{5} +} +func (m *MsgConnectionOpenAckResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgConnectionOpenAckResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgConnectionOpenAckResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgConnectionOpenAckResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgConnectionOpenAckResponse.Merge(m, src) +} +func (m *MsgConnectionOpenAckResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgConnectionOpenAckResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgConnectionOpenAckResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgConnectionOpenAckResponse proto.InternalMessageInfo + +// MsgConnectionOpenConfirm defines a msg sent by a Relayer to Chain B to +// acknowledge the change of connection state to OPEN on Chain A. +type MsgConnectionOpenConfirm struct { + ConnectionId string `protobuf:"bytes,1,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"` + // proof for the change of the connection state on Chain A: `INIT -> OPEN` + ProofAck []byte `protobuf:"bytes,2,opt,name=proof_ack,json=proofAck,proto3" json:"proof_ack,omitempty"` + ProofHeight types1.Height `protobuf:"bytes,3,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` + Signer string `protobuf:"bytes,4,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgConnectionOpenConfirm) Reset() { *m = MsgConnectionOpenConfirm{} } +func (m *MsgConnectionOpenConfirm) String() string { return proto.CompactTextString(m) } +func (*MsgConnectionOpenConfirm) ProtoMessage() {} +func (*MsgConnectionOpenConfirm) Descriptor() ([]byte, []int) { + return fileDescriptor_5d00fde5fc97399e, []int{6} +} +func (m *MsgConnectionOpenConfirm) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgConnectionOpenConfirm) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgConnectionOpenConfirm.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgConnectionOpenConfirm) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgConnectionOpenConfirm.Merge(m, src) +} +func (m *MsgConnectionOpenConfirm) XXX_Size() int { + return m.Size() +} +func (m *MsgConnectionOpenConfirm) XXX_DiscardUnknown() { + xxx_messageInfo_MsgConnectionOpenConfirm.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgConnectionOpenConfirm proto.InternalMessageInfo + +// MsgConnectionOpenConfirmResponse defines the Msg/ConnectionOpenConfirm +// response type. +type MsgConnectionOpenConfirmResponse struct { +} + +func (m *MsgConnectionOpenConfirmResponse) Reset() { *m = MsgConnectionOpenConfirmResponse{} } +func (m *MsgConnectionOpenConfirmResponse) String() string { return proto.CompactTextString(m) } +func (*MsgConnectionOpenConfirmResponse) ProtoMessage() {} +func (*MsgConnectionOpenConfirmResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_5d00fde5fc97399e, []int{7} +} +func (m *MsgConnectionOpenConfirmResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgConnectionOpenConfirmResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgConnectionOpenConfirmResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgConnectionOpenConfirmResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgConnectionOpenConfirmResponse.Merge(m, src) +} +func (m *MsgConnectionOpenConfirmResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgConnectionOpenConfirmResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgConnectionOpenConfirmResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgConnectionOpenConfirmResponse proto.InternalMessageInfo + +// MsgUpdateParams defines the sdk.Msg type to update the connection parameters. +type MsgUpdateParams struct { + // signer address + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // params defines the connection parameters to update. + // + // NOTE: All parameters must be supplied. + Params Params `protobuf:"bytes,2,opt,name=params,proto3" json:"params"` +} + +func (m *MsgUpdateParams) Reset() { *m = MsgUpdateParams{} } +func (m *MsgUpdateParams) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParams) ProtoMessage() {} +func (*MsgUpdateParams) Descriptor() ([]byte, []int) { + return fileDescriptor_5d00fde5fc97399e, []int{8} +} +func (m *MsgUpdateParams) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParams.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParams.Merge(m, src) +} +func (m *MsgUpdateParams) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParams) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParams.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParams proto.InternalMessageInfo + +// MsgUpdateParamsResponse defines the MsgUpdateParams response type. +type MsgUpdateParamsResponse struct { +} + +func (m *MsgUpdateParamsResponse) Reset() { *m = MsgUpdateParamsResponse{} } +func (m *MsgUpdateParamsResponse) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParamsResponse) ProtoMessage() {} +func (*MsgUpdateParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_5d00fde5fc97399e, []int{9} +} +func (m *MsgUpdateParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParamsResponse.Merge(m, src) +} +func (m *MsgUpdateParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParamsResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*MsgConnectionOpenInit)(nil), "ibc.core.connection.v1.MsgConnectionOpenInit") + proto.RegisterType((*MsgConnectionOpenInitResponse)(nil), "ibc.core.connection.v1.MsgConnectionOpenInitResponse") + proto.RegisterType((*MsgConnectionOpenTry)(nil), "ibc.core.connection.v1.MsgConnectionOpenTry") + proto.RegisterType((*MsgConnectionOpenTryResponse)(nil), "ibc.core.connection.v1.MsgConnectionOpenTryResponse") + proto.RegisterType((*MsgConnectionOpenAck)(nil), "ibc.core.connection.v1.MsgConnectionOpenAck") + proto.RegisterType((*MsgConnectionOpenAckResponse)(nil), "ibc.core.connection.v1.MsgConnectionOpenAckResponse") + proto.RegisterType((*MsgConnectionOpenConfirm)(nil), "ibc.core.connection.v1.MsgConnectionOpenConfirm") + proto.RegisterType((*MsgConnectionOpenConfirmResponse)(nil), "ibc.core.connection.v1.MsgConnectionOpenConfirmResponse") + proto.RegisterType((*MsgUpdateParams)(nil), "ibc.core.connection.v1.MsgUpdateParams") + proto.RegisterType((*MsgUpdateParamsResponse)(nil), "ibc.core.connection.v1.MsgUpdateParamsResponse") +} + +func init() { proto.RegisterFile("ibc/core/connection/v1/tx.proto", fileDescriptor_5d00fde5fc97399e) } + +var fileDescriptor_5d00fde5fc97399e = []byte{ + // 939 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x96, 0xdd, 0x6e, 0x1b, 0x45, + 0x14, 0xc7, 0xbd, 0x89, 0xed, 0xc4, 0xc7, 0x2e, 0x81, 0x91, 0x93, 0x4c, 0xb7, 0xd4, 0x36, 0x01, + 0xd4, 0xa8, 0x90, 0xdd, 0xa4, 0x05, 0xa9, 0x40, 0x11, 0x4a, 0x7c, 0x43, 0x2e, 0x02, 0xd5, 0x12, + 0x40, 0xe2, 0xc6, 0xb2, 0xd7, 0x93, 0xcd, 0x28, 0xf1, 0xce, 0x6a, 0x67, 0x6d, 0x6a, 0xae, 0x10, + 0xdc, 0x20, 0x71, 0xc3, 0x23, 0xf0, 0x08, 0x7d, 0x8c, 0x8a, 0xab, 0x5e, 0x72, 0x85, 0xaa, 0x04, + 0xa9, 0x2f, 0xc0, 0x03, 0xa0, 0xf9, 0xd8, 0xf5, 0xda, 0x5e, 0xa7, 0x76, 0x9b, 0xbb, 0xdd, 0xb3, + 0xff, 0x73, 0xe6, 0xcc, 0x39, 0xbf, 0xb3, 0x33, 0x50, 0xa7, 0x1d, 0xd7, 0x76, 0x59, 0x48, 0x6c, + 0x97, 0xf9, 0x3e, 0x71, 0x23, 0xca, 0x7c, 0x7b, 0xb0, 0x67, 0x47, 0x8f, 0xad, 0x20, 0x64, 0x11, + 0x43, 0x1b, 0xb4, 0xe3, 0x5a, 0x42, 0x60, 0x8d, 0x04, 0xd6, 0x60, 0xcf, 0xac, 0x7a, 0xcc, 0x63, + 0x52, 0x62, 0x8b, 0x27, 0xa5, 0x36, 0x37, 0x5d, 0xc6, 0x7b, 0x8c, 0xdb, 0x3d, 0xee, 0x89, 0x28, + 0x3d, 0xee, 0xe9, 0x0f, 0x37, 0x3d, 0xc6, 0xbc, 0x73, 0x62, 0xcb, 0xb7, 0x4e, 0xff, 0xc4, 0x6e, + 0xfb, 0x43, 0xfd, 0x29, 0x95, 0xc2, 0x39, 0x25, 0x7e, 0x24, 0x1c, 0xd5, 0x93, 0x16, 0xdc, 0x99, + 0x91, 0x63, 0x2a, 0x21, 0x29, 0xdc, 0xfa, 0x7d, 0x09, 0xd6, 0x8f, 0xb8, 0xd7, 0x4c, 0xec, 0x5f, + 0x07, 0xc4, 0x3f, 0xf4, 0x69, 0x84, 0x6e, 0x41, 0x49, 0x85, 0x6c, 0xd1, 0x2e, 0x36, 0x1a, 0xc6, + 0x76, 0xc9, 0x59, 0x55, 0x86, 0xc3, 0x2e, 0xfa, 0x0a, 0x2a, 0x2e, 0xeb, 0xfb, 0x11, 0x09, 0x83, + 0x76, 0x18, 0x0d, 0xf1, 0x52, 0xc3, 0xd8, 0x2e, 0xdf, 0x7b, 0xcf, 0xca, 0xde, 0xb9, 0xd5, 0x4c, + 0x69, 0x0f, 0xf2, 0x4f, 0xff, 0xa9, 0xe7, 0x9c, 0x31, 0x7f, 0xf4, 0x09, 0xac, 0x0c, 0x48, 0xc8, + 0x29, 0xf3, 0xf1, 0xb2, 0x0c, 0x55, 0x9f, 0x15, 0xea, 0x3b, 0x25, 0x73, 0x62, 0x3d, 0x7a, 0x07, + 0x2a, 0x5d, 0x72, 0xde, 0x1e, 0xb6, 0x02, 0x12, 0x52, 0xd6, 0xc5, 0xf9, 0x86, 0xb1, 0x9d, 0x77, + 0xca, 0xd2, 0xf6, 0x48, 0x9a, 0xd0, 0x06, 0x14, 0x39, 0xf5, 0x7c, 0x12, 0xe2, 0x82, 0xdc, 0x87, + 0x7e, 0xfb, 0x74, 0xed, 0xb7, 0x3f, 0xeb, 0xb9, 0x5f, 0x5e, 0x3c, 0xb9, 0xab, 0x0d, 0x5b, 0x75, + 0xb8, 0x9d, 0x59, 0x0c, 0x87, 0xf0, 0x80, 0xf9, 0x9c, 0x6c, 0xfd, 0x5b, 0x80, 0xea, 0x94, 0xe2, + 0x38, 0x1c, 0x5e, 0x5d, 0xad, 0x07, 0xb0, 0x11, 0x84, 0x64, 0x40, 0x59, 0x9f, 0xb7, 0x46, 0xbb, + 0x11, 0x4a, 0x51, 0xb7, 0xd2, 0xc1, 0x12, 0x36, 0x9c, 0x6a, 0xac, 0x18, 0xc5, 0x3e, 0xec, 0xa2, + 0xcf, 0xa0, 0xa2, 0xc3, 0xf2, 0xa8, 0x1d, 0x11, 0x5d, 0x9c, 0xaa, 0xa5, 0xd0, 0xb0, 0x62, 0x34, + 0xac, 0x7d, 0x7f, 0x28, 0xa3, 0x94, 0x95, 0xfa, 0x1b, 0x21, 0x9e, 0x6a, 0x52, 0xfe, 0x35, 0x9b, + 0x34, 0x59, 0xe9, 0xc2, 0x74, 0xa5, 0x8f, 0x61, 0x3d, 0xed, 0xd2, 0xd2, 0x4d, 0xe2, 0xb8, 0xd8, + 0x58, 0x9e, 0xa7, 0xab, 0xd5, 0xb4, 0xb7, 0x36, 0x72, 0xd4, 0x84, 0x4a, 0x10, 0x32, 0x76, 0xd2, + 0x3a, 0x25, 0xd4, 0x3b, 0x8d, 0xf0, 0x8a, 0xdc, 0x88, 0x99, 0x0a, 0xa6, 0xd8, 0x1f, 0xec, 0x59, + 0x5f, 0x4a, 0x85, 0x4e, 0xbf, 0x2c, 0xbd, 0x94, 0x09, 0xdd, 0x06, 0x50, 0x41, 0xa8, 0x4f, 0x23, + 0xbc, 0xda, 0x30, 0xb6, 0x2b, 0x4e, 0x49, 0x5a, 0x24, 0xee, 0xef, 0xc7, 0x6b, 0xa8, 0x58, 0xb8, + 0x24, 0x04, 0xaa, 0xa6, 0xd2, 0xde, 0x94, 0x66, 0xf4, 0x01, 0xac, 0x69, 0x99, 0xe0, 0xc1, 0xe7, + 0x7d, 0x8e, 0x21, 0x51, 0xbe, 0xa1, 0x94, 0xf1, 0x17, 0x74, 0x04, 0x6f, 0x26, 0xb2, 0x38, 0xf7, + 0xf2, 0x4b, 0x73, 0x2f, 0x8a, 0xdc, 0xb1, 0xe1, 0xac, 0x25, 0xbe, 0x7a, 0x07, 0x23, 0x8c, 0x2b, + 0x69, 0x8c, 0xd1, 0x17, 0x60, 0x9e, 0x32, 0x1e, 0x8d, 0x52, 0x52, 0xb0, 0xb4, 0x64, 0x36, 0xf8, + 0x46, 0x92, 0xde, 0xa6, 0x50, 0x25, 0xd9, 0x49, 0x46, 0x1e, 0x09, 0xc9, 0xf4, 0x1c, 0xd4, 0xe0, + 0xed, 0x2c, 0xca, 0x93, 0x31, 0x78, 0x9e, 0xcf, 0x18, 0x83, 0x7d, 0xf7, 0x0c, 0xbd, 0x0b, 0x37, + 0xc6, 0x01, 0x57, 0xa3, 0x50, 0x71, 0xd3, 0x50, 0x3f, 0x04, 0x73, 0x0c, 0x92, 0x8c, 0x91, 0x70, + 0x70, 0x5a, 0x31, 0x36, 0x12, 0xaf, 0xf1, 0xab, 0x98, 0x9c, 0xa6, 0xfc, 0x22, 0xd3, 0x34, 0x09, + 0x61, 0xe1, 0x55, 0x20, 0xbc, 0x05, 0x0a, 0xb9, 0x56, 0x14, 0x0e, 0x71, 0x51, 0x32, 0xb8, 0x2a, + 0x0d, 0xe2, 0x1f, 0x32, 0x89, 0xe0, 0xca, 0xdc, 0x08, 0xae, 0x2e, 0x84, 0x60, 0xe9, 0x3a, 0x10, + 0x84, 0x05, 0x10, 0x2c, 0x5f, 0x13, 0x82, 0xfb, 0xee, 0x59, 0x82, 0xe0, 0x5f, 0x06, 0xe0, 0x29, + 0x41, 0x93, 0xf9, 0x27, 0x34, 0xec, 0xcd, 0x87, 0x61, 0xd2, 0x8b, 0xb6, 0x7b, 0x26, 0xa9, 0x8b, + 0x7b, 0x21, 0x40, 0x9e, 0xec, 0xf6, 0xf2, 0xab, 0x74, 0x7b, 0x54, 0xad, 0xfc, 0xd5, 0xe7, 0xce, + 0x16, 0x34, 0x66, 0xed, 0x25, 0xd9, 0xf0, 0x63, 0x58, 0x3b, 0xe2, 0xde, 0xb7, 0x41, 0x57, 0xd4, + 0xac, 0x1d, 0xb6, 0x7b, 0x3c, 0x15, 0xdf, 0x18, 0xeb, 0xc6, 0x43, 0x28, 0x06, 0x52, 0xa1, 0xcf, + 0xe5, 0xda, 0xac, 0x09, 0x51, 0x71, 0x74, 0xea, 0xda, 0x67, 0x3a, 0xbb, 0x9b, 0xb0, 0x39, 0xb1, + 0x72, 0x9c, 0xd4, 0xbd, 0xff, 0xf2, 0xb0, 0x7c, 0xc4, 0x3d, 0xf4, 0x13, 0xa0, 0x8c, 0x2b, 0xc4, + 0xce, 0xac, 0x75, 0x33, 0x0f, 0x59, 0xf3, 0xe3, 0x85, 0xe4, 0x71, 0x0e, 0xe8, 0x47, 0x78, 0x6b, + 0xfa, 0x3c, 0xfe, 0x70, 0xee, 0x58, 0xc7, 0xe1, 0xd0, 0xfc, 0x68, 0x11, 0xf5, 0xec, 0x85, 0x05, + 0x38, 0xf3, 0x2f, 0xbc, 0xef, 0x9e, 0x2d, 0xb0, 0x70, 0x8a, 0x7d, 0xf4, 0xab, 0x01, 0xeb, 0xd9, + 0xe0, 0xef, 0xce, 0x1d, 0x4f, 0x7b, 0x98, 0x0f, 0x16, 0xf5, 0x48, 0xb2, 0x08, 0x61, 0x43, 0x31, + 0x31, 0x92, 0x69, 0x2e, 0xef, 0x5c, 0x11, 0x33, 0x8d, 0x91, 0x69, 0xcf, 0x29, 0x8c, 0xd7, 0x34, + 0x0b, 0x3f, 0xbf, 0x78, 0x72, 0xd7, 0x38, 0xf8, 0xfe, 0xe9, 0x45, 0xcd, 0x78, 0x76, 0x51, 0x33, + 0x9e, 0x5f, 0xd4, 0x8c, 0x3f, 0x2e, 0x6b, 0xb9, 0x67, 0x97, 0xb5, 0xdc, 0xdf, 0x97, 0xb5, 0xdc, + 0x0f, 0x9f, 0x7b, 0x34, 0x3a, 0xed, 0x77, 0x2c, 0x97, 0xf5, 0x6c, 0x7d, 0xb1, 0xa6, 0x1d, 0x77, + 0xc7, 0x63, 0xf6, 0x60, 0x6f, 0xd7, 0xee, 0xb1, 0x6e, 0xff, 0x9c, 0x70, 0x75, 0x33, 0xde, 0xbd, + 0xbf, 0x93, 0xba, 0x1c, 0x47, 0xc3, 0x80, 0xf0, 0x4e, 0x51, 0x9e, 0x01, 0xf7, 0xff, 0x0f, 0x00, + 0x00, 0xff, 0xff, 0xe1, 0x79, 0x36, 0x5d, 0xe4, 0x0b, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MsgClient is the client API for Msg service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MsgClient interface { + // ConnectionOpenInit defines a rpc handler method for MsgConnectionOpenInit. + ConnectionOpenInit(ctx context.Context, in *MsgConnectionOpenInit, opts ...grpc.CallOption) (*MsgConnectionOpenInitResponse, error) + // ConnectionOpenTry defines a rpc handler method for MsgConnectionOpenTry. + ConnectionOpenTry(ctx context.Context, in *MsgConnectionOpenTry, opts ...grpc.CallOption) (*MsgConnectionOpenTryResponse, error) + // ConnectionOpenAck defines a rpc handler method for MsgConnectionOpenAck. + ConnectionOpenAck(ctx context.Context, in *MsgConnectionOpenAck, opts ...grpc.CallOption) (*MsgConnectionOpenAckResponse, error) + // ConnectionOpenConfirm defines a rpc handler method for + // MsgConnectionOpenConfirm. + ConnectionOpenConfirm(ctx context.Context, in *MsgConnectionOpenConfirm, opts ...grpc.CallOption) (*MsgConnectionOpenConfirmResponse, error) + // UpdateConnectionParams defines a rpc handler method for + // MsgUpdateParams. + UpdateConnectionParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) +} + +type msgClient struct { + cc grpc1.ClientConn +} + +func NewMsgClient(cc grpc1.ClientConn) MsgClient { + return &msgClient{cc} +} + +func (c *msgClient) ConnectionOpenInit(ctx context.Context, in *MsgConnectionOpenInit, opts ...grpc.CallOption) (*MsgConnectionOpenInitResponse, error) { + out := new(MsgConnectionOpenInitResponse) + err := c.cc.Invoke(ctx, "/ibc.core.connection.v1.Msg/ConnectionOpenInit", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) ConnectionOpenTry(ctx context.Context, in *MsgConnectionOpenTry, opts ...grpc.CallOption) (*MsgConnectionOpenTryResponse, error) { + out := new(MsgConnectionOpenTryResponse) + err := c.cc.Invoke(ctx, "/ibc.core.connection.v1.Msg/ConnectionOpenTry", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) ConnectionOpenAck(ctx context.Context, in *MsgConnectionOpenAck, opts ...grpc.CallOption) (*MsgConnectionOpenAckResponse, error) { + out := new(MsgConnectionOpenAckResponse) + err := c.cc.Invoke(ctx, "/ibc.core.connection.v1.Msg/ConnectionOpenAck", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) ConnectionOpenConfirm(ctx context.Context, in *MsgConnectionOpenConfirm, opts ...grpc.CallOption) (*MsgConnectionOpenConfirmResponse, error) { + out := new(MsgConnectionOpenConfirmResponse) + err := c.cc.Invoke(ctx, "/ibc.core.connection.v1.Msg/ConnectionOpenConfirm", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) UpdateConnectionParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { + out := new(MsgUpdateParamsResponse) + err := c.cc.Invoke(ctx, "/ibc.core.connection.v1.Msg/UpdateConnectionParams", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MsgServer is the server API for Msg service. +type MsgServer interface { + // ConnectionOpenInit defines a rpc handler method for MsgConnectionOpenInit. + ConnectionOpenInit(context.Context, *MsgConnectionOpenInit) (*MsgConnectionOpenInitResponse, error) + // ConnectionOpenTry defines a rpc handler method for MsgConnectionOpenTry. + ConnectionOpenTry(context.Context, *MsgConnectionOpenTry) (*MsgConnectionOpenTryResponse, error) + // ConnectionOpenAck defines a rpc handler method for MsgConnectionOpenAck. + ConnectionOpenAck(context.Context, *MsgConnectionOpenAck) (*MsgConnectionOpenAckResponse, error) + // ConnectionOpenConfirm defines a rpc handler method for + // MsgConnectionOpenConfirm. + ConnectionOpenConfirm(context.Context, *MsgConnectionOpenConfirm) (*MsgConnectionOpenConfirmResponse, error) + // UpdateConnectionParams defines a rpc handler method for + // MsgUpdateParams. + UpdateConnectionParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) +} + +// UnimplementedMsgServer can be embedded to have forward compatible implementations. +type UnimplementedMsgServer struct { +} + +func (*UnimplementedMsgServer) ConnectionOpenInit(ctx context.Context, req *MsgConnectionOpenInit) (*MsgConnectionOpenInitResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ConnectionOpenInit not implemented") +} +func (*UnimplementedMsgServer) ConnectionOpenTry(ctx context.Context, req *MsgConnectionOpenTry) (*MsgConnectionOpenTryResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ConnectionOpenTry not implemented") +} +func (*UnimplementedMsgServer) ConnectionOpenAck(ctx context.Context, req *MsgConnectionOpenAck) (*MsgConnectionOpenAckResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ConnectionOpenAck not implemented") +} +func (*UnimplementedMsgServer) ConnectionOpenConfirm(ctx context.Context, req *MsgConnectionOpenConfirm) (*MsgConnectionOpenConfirmResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ConnectionOpenConfirm not implemented") +} +func (*UnimplementedMsgServer) UpdateConnectionParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateConnectionParams not implemented") +} + +func RegisterMsgServer(s grpc1.Server, srv MsgServer) { + s.RegisterService(&_Msg_serviceDesc, srv) +} + +func _Msg_ConnectionOpenInit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgConnectionOpenInit) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).ConnectionOpenInit(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.connection.v1.Msg/ConnectionOpenInit", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).ConnectionOpenInit(ctx, req.(*MsgConnectionOpenInit)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_ConnectionOpenTry_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgConnectionOpenTry) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).ConnectionOpenTry(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.connection.v1.Msg/ConnectionOpenTry", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).ConnectionOpenTry(ctx, req.(*MsgConnectionOpenTry)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_ConnectionOpenAck_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgConnectionOpenAck) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).ConnectionOpenAck(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.connection.v1.Msg/ConnectionOpenAck", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).ConnectionOpenAck(ctx, req.(*MsgConnectionOpenAck)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_ConnectionOpenConfirm_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgConnectionOpenConfirm) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).ConnectionOpenConfirm(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.connection.v1.Msg/ConnectionOpenConfirm", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).ConnectionOpenConfirm(ctx, req.(*MsgConnectionOpenConfirm)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_UpdateConnectionParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUpdateParams) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).UpdateConnectionParams(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.connection.v1.Msg/UpdateConnectionParams", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).UpdateConnectionParams(ctx, req.(*MsgUpdateParams)) + } + return interceptor(ctx, in, info, handler) +} + +var _Msg_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ibc.core.connection.v1.Msg", + HandlerType: (*MsgServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ConnectionOpenInit", + Handler: _Msg_ConnectionOpenInit_Handler, + }, + { + MethodName: "ConnectionOpenTry", + Handler: _Msg_ConnectionOpenTry_Handler, + }, + { + MethodName: "ConnectionOpenAck", + Handler: _Msg_ConnectionOpenAck_Handler, + }, + { + MethodName: "ConnectionOpenConfirm", + Handler: _Msg_ConnectionOpenConfirm_Handler, + }, + { + MethodName: "UpdateConnectionParams", + Handler: _Msg_UpdateConnectionParams_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "ibc/core/connection/v1/tx.proto", +} + +func (m *MsgConnectionOpenInit) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgConnectionOpenInit) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgConnectionOpenInit) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x2a + } + if m.DelayPeriod != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.DelayPeriod)) + i-- + dAtA[i] = 0x20 + } + if m.Version != nil { + { + size, err := m.Version.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + { + size, err := m.Counterparty.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgConnectionOpenInitResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgConnectionOpenInitResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgConnectionOpenInitResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgConnectionOpenTry) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgConnectionOpenTry) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgConnectionOpenTry) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.HostConsensusStateProof) > 0 { + i -= len(m.HostConsensusStateProof) + copy(dAtA[i:], m.HostConsensusStateProof) + i = encodeVarintTx(dAtA, i, uint64(len(m.HostConsensusStateProof))) + i-- + dAtA[i] = 0x6a + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x62 + } + { + size, err := m.ConsensusHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x5a + if len(m.ProofConsensus) > 0 { + i -= len(m.ProofConsensus) + copy(dAtA[i:], m.ProofConsensus) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProofConsensus))) + i-- + dAtA[i] = 0x52 + } + if len(m.ProofClient) > 0 { + i -= len(m.ProofClient) + copy(dAtA[i:], m.ProofClient) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProofClient))) + i-- + dAtA[i] = 0x4a + } + if len(m.ProofInit) > 0 { + i -= len(m.ProofInit) + copy(dAtA[i:], m.ProofInit) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProofInit))) + i-- + dAtA[i] = 0x42 + } + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + if len(m.CounterpartyVersions) > 0 { + for iNdEx := len(m.CounterpartyVersions) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.CounterpartyVersions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + } + if m.DelayPeriod != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.DelayPeriod)) + i-- + dAtA[i] = 0x28 + } + { + size, err := m.Counterparty.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + if m.ClientState != nil { + { + size, err := m.ClientState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if len(m.PreviousConnectionId) > 0 { + i -= len(m.PreviousConnectionId) + copy(dAtA[i:], m.PreviousConnectionId) + i = encodeVarintTx(dAtA, i, uint64(len(m.PreviousConnectionId))) + i-- + dAtA[i] = 0x12 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgConnectionOpenTryResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgConnectionOpenTryResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgConnectionOpenTryResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgConnectionOpenAck) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgConnectionOpenAck) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgConnectionOpenAck) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.HostConsensusStateProof) > 0 { + i -= len(m.HostConsensusStateProof) + copy(dAtA[i:], m.HostConsensusStateProof) + i = encodeVarintTx(dAtA, i, uint64(len(m.HostConsensusStateProof))) + i-- + dAtA[i] = 0x5a + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x52 + } + { + size, err := m.ConsensusHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x4a + if len(m.ProofConsensus) > 0 { + i -= len(m.ProofConsensus) + copy(dAtA[i:], m.ProofConsensus) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProofConsensus))) + i-- + dAtA[i] = 0x42 + } + if len(m.ProofClient) > 0 { + i -= len(m.ProofClient) + copy(dAtA[i:], m.ProofClient) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProofClient))) + i-- + dAtA[i] = 0x3a + } + if len(m.ProofTry) > 0 { + i -= len(m.ProofTry) + copy(dAtA[i:], m.ProofTry) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProofTry))) + i-- + dAtA[i] = 0x32 + } + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + if m.ClientState != nil { + { + size, err := m.ClientState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + if m.Version != nil { + { + size, err := m.Version.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if len(m.CounterpartyConnectionId) > 0 { + i -= len(m.CounterpartyConnectionId) + copy(dAtA[i:], m.CounterpartyConnectionId) + i = encodeVarintTx(dAtA, i, uint64(len(m.CounterpartyConnectionId))) + i-- + dAtA[i] = 0x12 + } + if len(m.ConnectionId) > 0 { + i -= len(m.ConnectionId) + copy(dAtA[i:], m.ConnectionId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ConnectionId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgConnectionOpenAckResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgConnectionOpenAckResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgConnectionOpenAckResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgConnectionOpenConfirm) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgConnectionOpenConfirm) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgConnectionOpenConfirm) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x22 + } + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.ProofAck) > 0 { + i -= len(m.ProofAck) + copy(dAtA[i:], m.ProofAck) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProofAck))) + i-- + dAtA[i] = 0x12 + } + if len(m.ConnectionId) > 0 { + i -= len(m.ConnectionId) + copy(dAtA[i:], m.ConnectionId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ConnectionId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgConnectionOpenConfirmResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgConnectionOpenConfirmResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgConnectionOpenConfirmResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgUpdateParams) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParams) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgUpdateParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgConnectionOpenInit) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Counterparty.Size() + n += 1 + l + sovTx(uint64(l)) + if m.Version != nil { + l = m.Version.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.DelayPeriod != 0 { + n += 1 + sovTx(uint64(m.DelayPeriod)) + } + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgConnectionOpenInitResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgConnectionOpenTry) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.PreviousConnectionId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.ClientState != nil { + l = m.ClientState.Size() + n += 1 + l + sovTx(uint64(l)) + } + l = m.Counterparty.Size() + n += 1 + l + sovTx(uint64(l)) + if m.DelayPeriod != 0 { + n += 1 + sovTx(uint64(m.DelayPeriod)) + } + if len(m.CounterpartyVersions) > 0 { + for _, e := range m.CounterpartyVersions { + l = e.Size() + n += 1 + l + sovTx(uint64(l)) + } + } + l = m.ProofHeight.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.ProofInit) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ProofClient) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ProofConsensus) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.ConsensusHeight.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.HostConsensusStateProof) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgConnectionOpenTryResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgConnectionOpenAck) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ConnectionId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.CounterpartyConnectionId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.Version != nil { + l = m.Version.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.ClientState != nil { + l = m.ClientState.Size() + n += 1 + l + sovTx(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.ProofTry) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ProofClient) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ProofConsensus) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.ConsensusHeight.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.HostConsensusStateProof) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgConnectionOpenAckResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgConnectionOpenConfirm) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ConnectionId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ProofAck) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgConnectionOpenConfirmResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgUpdateParams) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Params.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgUpdateParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgConnectionOpenInit) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgConnectionOpenInit: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgConnectionOpenInit: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Counterparty", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Counterparty.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Version == nil { + m.Version = &Version{} + } + if err := m.Version.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DelayPeriod", wireType) + } + m.DelayPeriod = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DelayPeriod |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgConnectionOpenInitResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgConnectionOpenInitResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgConnectionOpenInitResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgConnectionOpenTry) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgConnectionOpenTry: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgConnectionOpenTry: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PreviousConnectionId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PreviousConnectionId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ClientState == nil { + m.ClientState = &types.Any{} + } + if err := m.ClientState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Counterparty", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Counterparty.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DelayPeriod", wireType) + } + m.DelayPeriod = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DelayPeriod |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CounterpartyVersions", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CounterpartyVersions = append(m.CounterpartyVersions, &Version{}) + if err := m.CounterpartyVersions[len(m.CounterpartyVersions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofInit", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProofInit = append(m.ProofInit[:0], dAtA[iNdEx:postIndex]...) + if m.ProofInit == nil { + m.ProofInit = []byte{} + } + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofClient", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProofClient = append(m.ProofClient[:0], dAtA[iNdEx:postIndex]...) + if m.ProofClient == nil { + m.ProofClient = []byte{} + } + iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofConsensus", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProofConsensus = append(m.ProofConsensus[:0], dAtA[iNdEx:postIndex]...) + if m.ProofConsensus == nil { + m.ProofConsensus = []byte{} + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConsensusHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ConsensusHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field HostConsensusStateProof", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.HostConsensusStateProof = append(m.HostConsensusStateProof[:0], dAtA[iNdEx:postIndex]...) + if m.HostConsensusStateProof == nil { + m.HostConsensusStateProof = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgConnectionOpenTryResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgConnectionOpenTryResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgConnectionOpenTryResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgConnectionOpenAck) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgConnectionOpenAck: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgConnectionOpenAck: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectionId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ConnectionId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CounterpartyConnectionId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CounterpartyConnectionId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Version == nil { + m.Version = &Version{} + } + if err := m.Version.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ClientState == nil { + m.ClientState = &types.Any{} + } + if err := m.ClientState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofTry", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProofTry = append(m.ProofTry[:0], dAtA[iNdEx:postIndex]...) + if m.ProofTry == nil { + m.ProofTry = []byte{} + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofClient", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProofClient = append(m.ProofClient[:0], dAtA[iNdEx:postIndex]...) + if m.ProofClient == nil { + m.ProofClient = []byte{} + } + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofConsensus", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProofConsensus = append(m.ProofConsensus[:0], dAtA[iNdEx:postIndex]...) + if m.ProofConsensus == nil { + m.ProofConsensus = []byte{} + } + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConsensusHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ConsensusHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field HostConsensusStateProof", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.HostConsensusStateProof = append(m.HostConsensusStateProof[:0], dAtA[iNdEx:postIndex]...) + if m.HostConsensusStateProof == nil { + m.HostConsensusStateProof = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgConnectionOpenAckResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgConnectionOpenAckResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgConnectionOpenAckResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgConnectionOpenConfirm) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgConnectionOpenConfirm: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgConnectionOpenConfirm: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectionId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ConnectionId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofAck", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProofAck = append(m.ProofAck[:0], dAtA[iNdEx:postIndex]...) + if m.ProofAck == nil { + m.ProofAck = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgConnectionOpenConfirmResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgConnectionOpenConfirmResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgConnectionOpenConfirmResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateParams: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParams: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTx(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/03-connection/types/version.go b/modules/core/03-connection/types/version.go new file mode 100644 index 0000000..b516b79 --- /dev/null +++ b/modules/core/03-connection/types/version.go @@ -0,0 +1,192 @@ +package types + +import ( + "slices" + "strings" + + errorsmod "cosmossdk.io/errors" +) + +var ( + // DefaultIBCVersion represents the latest supported version of IBC used + // in connection version negotiation. The current version supports the list + // of orderings defined in SupportedOrderings and requires at least one channel type + // to be agreed upon. + DefaultIBCVersion = NewVersion(DefaultIBCVersionIdentifier, SupportedOrderings) + + // DefaultIBCVersionIdentifier is the IBC v1.0.0 protocol version identifier + DefaultIBCVersionIdentifier = "1" + + // SupportedOrderings is the list of orderings supported by IBC. The current + // version supports only ORDERED and UNORDERED channels. + SupportedOrderings = []string{"ORDER_ORDERED", "ORDER_UNORDERED"} + + // AllowNilFeatureSet is a helper map to indicate if a specified version + // identifier is allowed to have a nil feature set. Any versions supported, + // but not included in the map default to not supporting nil feature sets. + allowNilFeatureSet = map[string]bool{ + DefaultIBCVersionIdentifier: false, + } + + // MaxVersionsLength is the maximum number of versions that can be supported + MaxCounterpartyVersionsLength = 100 + // MaxFeaturesLength is the maximum number of features that can be supported + MaxFeaturesLength = 100 +) + +// NewVersion returns a new instance of Version. +func NewVersion(identifier string, features []string) *Version { + return &Version{ + Identifier: identifier, + Features: features, + } +} + +// GetIdentifier implements the VersionI interface +func (version Version) GetIdentifier() string { + return version.Identifier +} + +// GetFeatures implements the VersionI interface +func (version Version) GetFeatures() []string { + return version.Features +} + +// ValidateVersion does basic validation of the version identifier and +// features. It unmarshals the version string into a Version object. +func ValidateVersion(version *Version) error { + if version == nil { + return errorsmod.Wrap(ErrInvalidVersion, "version cannot be nil") + } + if strings.TrimSpace(version.Identifier) == "" { + return errorsmod.Wrap(ErrInvalidVersion, "version identifier cannot be blank") + } + if len(version.Features) > MaxFeaturesLength { + return errorsmod.Wrapf(ErrInvalidVersion, "features length must not exceed %d items", MaxFeaturesLength) + } + for i, feature := range version.Features { + if strings.TrimSpace(feature) == "" { + return errorsmod.Wrapf(ErrInvalidVersion, "feature cannot be blank, index %d", i) + } + } + + return nil +} + +// VerifyProposedVersion verifies that the entire feature set in the +// proposed version is supported by this chain. If the feature set is +// empty it verifies that this is allowed for the specified version +// identifier. +func (version Version) VerifyProposedVersion(proposedVersion *Version) error { + if proposedVersion.GetIdentifier() != version.GetIdentifier() { + return errorsmod.Wrapf( + ErrVersionNegotiationFailed, + "proposed version identifier does not equal supported version identifier (%s != %s)", proposedVersion.GetIdentifier(), version.GetIdentifier(), + ) + } + + if len(proposedVersion.GetFeatures()) == 0 && !allowNilFeatureSet[proposedVersion.GetIdentifier()] { + return errorsmod.Wrapf( + ErrVersionNegotiationFailed, + "nil feature sets are not supported for version identifier (%s)", proposedVersion.GetIdentifier(), + ) + } + + for _, proposedFeature := range proposedVersion.GetFeatures() { + if !slices.Contains(version.GetFeatures(), proposedFeature) { + return errorsmod.Wrapf( + ErrVersionNegotiationFailed, + "proposed feature (%s) is not a supported feature set (%s)", proposedFeature, version.GetFeatures(), + ) + } + } + + return nil +} + +// VerifySupportedFeature takes in a version and feature string and returns +// true if the feature is supported by the version and false otherwise. +func VerifySupportedFeature(version *Version, feature string) bool { + return slices.Contains(version.GetFeatures(), feature) +} + +// GetCompatibleVersions returns a descending ordered set of compatible IBC +// versions for the caller chain's connection end. The latest supported +// version should be first element and the set should descend to the oldest +// supported version. +func GetCompatibleVersions() []*Version { + return []*Version{DefaultIBCVersion} +} + +// IsSupportedVersion returns true if the proposed version has a matching version +// identifier and its entire feature set is supported or the version identifier +// supports an empty feature set. +func IsSupportedVersion(supportedVersions []*Version, proposedVersion *Version) bool { + supportedVersion, found := FindSupportedVersion(proposedVersion, supportedVersions) + if !found { + return false + } + + if err := supportedVersion.VerifyProposedVersion(proposedVersion); err != nil { + return false + } + + return true +} + +// FindSupportedVersion returns the version with a matching version identifier +// if it exists. The returned boolean is true if the version is found and +// false otherwise. +func FindSupportedVersion(version *Version, supportedVersions []*Version) (*Version, bool) { + for _, supportedVersion := range supportedVersions { + if version.GetIdentifier() == supportedVersion.GetIdentifier() { + return supportedVersion, true + } + } + + return nil, false +} + +// PickVersion iterates over the descending ordered set of compatible IBC +// versions and selects the first version with a version identifier that is +// supported by the counterparty. The returned version contains a feature +// set with the intersection of the features supported by the source and +// counterparty chains. If the feature set intersection is nil and this is +// not allowed for the chosen version identifier then the search for a +// compatible version continues. This function is called in the ConnOpenTry +// handshake procedure. +// +// CONTRACT: PickVersion must only provide a version that is in the +// intersection of the supported versions and the counterparty versions. +func PickVersion(supportedVersions, counterpartyVersions []*Version) (*Version, error) { + for _, supportedVersion := range supportedVersions { + // check if the source version is supported by the counterparty + if counterpartyVersion, found := FindSupportedVersion(supportedVersion, counterpartyVersions); found { + featureSet := GetFeatureSetIntersection(supportedVersion.GetFeatures(), counterpartyVersion.GetFeatures()) + if len(featureSet) == 0 && !allowNilFeatureSet[supportedVersion.GetIdentifier()] { + continue + } + + return NewVersion(supportedVersion.GetIdentifier(), featureSet), nil + } + } + + return nil, errorsmod.Wrapf( + ErrVersionNegotiationFailed, + "failed to find a matching counterparty version (%v) from the supported version list (%v)", counterpartyVersions, supportedVersions, + ) +} + +// GetFeatureSetIntersection returns the intersections of source feature set +// and the counterparty feature set. This is done by iterating over all the +// features in the source version and seeing if they exist in the feature +// set for the counterparty version. +func GetFeatureSetIntersection(sourceFeatureSet, counterpartyFeatureSet []string) (featureSet []string) { + for _, feature := range sourceFeatureSet { + if slices.Contains(counterpartyFeatureSet, feature) { + featureSet = append(featureSet, feature) + } + } + + return featureSet +} diff --git a/modules/core/03-connection/types/version_test.go b/modules/core/03-connection/types/version_test.go new file mode 100644 index 0000000..91ec155 --- /dev/null +++ b/modules/core/03-connection/types/version_test.go @@ -0,0 +1,165 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func TestValidateVersion(t *testing.T) { + testCases := []struct { + name string + version *types.Version + expError error + }{ + {"valid version", types.DefaultIBCVersion, nil}, + {"valid empty feature set", types.NewVersion(types.DefaultIBCVersionIdentifier, []string{}), nil}, + {"empty version identifier", types.NewVersion(" ", []string{"ORDER_UNORDERED"}), types.ErrInvalidVersion}, + {"empty feature", types.NewVersion(types.DefaultIBCVersionIdentifier, []string{"ORDER_UNORDERED", " "}), types.ErrInvalidVersion}, + } + + for i, tc := range testCases { + err := types.ValidateVersion(tc.version) + + if tc.expError == nil { + require.NoError(t, err, "valid test case %d failed: %s", i, tc.name) + } else { + require.ErrorIs(t, err, tc.expError) + } + } +} + +func TestIsSupportedVersion(t *testing.T) { + testCases := []struct { + name string + version *types.Version + expPass bool + }{ + { + "version is supported", + types.GetCompatibleVersions()[0], + true, + }, + { + "version is not supported", + &types.Version{}, + false, + }, + { + "version feature is not supported", + types.NewVersion(types.DefaultIBCVersionIdentifier, []string{"ORDER_DAG"}), + false, + }, + } + + for _, tc := range testCases { + require.Equal(t, tc.expPass, types.IsSupportedVersion(types.GetCompatibleVersions(), tc.version)) + } +} + +func TestFindSupportedVersion(t *testing.T) { + testCases := []struct { + name string + version *types.Version + supportedVersions []*types.Version + expVersion *types.Version + expFound bool + }{ + {"valid supported version", types.DefaultIBCVersion, types.GetCompatibleVersions(), types.DefaultIBCVersion, true}, + {"empty (invalid) version", &types.Version{}, types.GetCompatibleVersions(), &types.Version{}, false}, + {"empty supported versions", types.DefaultIBCVersion, []*types.Version{}, &types.Version{}, false}, + {"desired version is last", types.DefaultIBCVersion, []*types.Version{types.NewVersion("1.1", nil), types.NewVersion("2", []string{"ORDER_UNORDERED"}), types.NewVersion("3", nil), types.DefaultIBCVersion}, types.DefaultIBCVersion, true}, + {"desired version identifier with different feature set", types.NewVersion(types.DefaultIBCVersionIdentifier, []string{"ORDER_DAG"}), types.GetCompatibleVersions(), types.DefaultIBCVersion, true}, + {"version not supported", types.NewVersion("2", []string{"ORDER_DAG"}), types.GetCompatibleVersions(), &types.Version{}, false}, + } + + for i, tc := range testCases { + + version, found := types.FindSupportedVersion(tc.version, tc.supportedVersions) + if tc.expFound { + require.Equal(t, tc.expVersion.GetIdentifier(), version.GetIdentifier(), "test case %d: %s", i, tc.name) + require.True(t, found, "test case %d: %s", i, tc.name) + } else { + require.False(t, found, "test case: %s", tc.name) + require.Nil(t, version, "test case: %s", tc.name) + } + } +} + +func TestPickVersion(t *testing.T) { + testCases := []struct { + name string + supportedVersions []*types.Version + counterpartyVersions []*types.Version + expVer *types.Version + expError error + }{ + {"valid default ibc version", types.GetCompatibleVersions(), types.GetCompatibleVersions(), types.DefaultIBCVersion, nil}, + {"valid version in counterparty versions", types.GetCompatibleVersions(), []*types.Version{types.NewVersion("version1", nil), types.NewVersion("2.0.0", []string{"ORDER_UNORDERED-ZK"}), types.DefaultIBCVersion}, types.DefaultIBCVersion, nil}, + {"valid identifier match but empty feature set not allowed", types.GetCompatibleVersions(), []*types.Version{types.NewVersion(types.DefaultIBCVersionIdentifier, []string{"DAG", "ORDERED-ZK", "UNORDERED-zk]"})}, types.NewVersion(types.DefaultIBCVersionIdentifier, nil), types.ErrVersionNegotiationFailed}, + {"empty counterparty versions", types.GetCompatibleVersions(), []*types.Version{}, &types.Version{}, types.ErrVersionNegotiationFailed}, + {"non-matching counterparty versions", types.GetCompatibleVersions(), []*types.Version{types.NewVersion("2.0.0", nil)}, &types.Version{}, types.ErrVersionNegotiationFailed}, + {"non-matching counterparty versions (uses ordered channels only) contained in supported versions (uses unordered channels only)", []*types.Version{types.NewVersion(types.DefaultIBCVersionIdentifier, []string{"ORDER_UNORDERED"})}, []*types.Version{types.NewVersion(types.DefaultIBCVersionIdentifier, []string{"ORDER_ORDERED"})}, &types.Version{}, types.ErrVersionNegotiationFailed}, + } + + for i, tc := range testCases { + version, err := types.PickVersion(tc.supportedVersions, tc.counterpartyVersions) + + if tc.expError == nil { + require.NoError(t, err, "valid test case %d failed: %s", i, tc.name) + } else { + require.ErrorIs(t, err, tc.expError) + var emptyVersion *types.Version + require.Equal(t, emptyVersion, version, "invalid test case %d passed: %s", i, tc.name) + } + } +} + +func TestVerifyProposedVersion(t *testing.T) { + testCases := []struct { + name string + proposedVersion *types.Version + supportedVersion *types.Version + expError error + }{ + {"entire feature set supported", types.DefaultIBCVersion, types.NewVersion("1", []string{"ORDER_ORDERED", "ORDER_UNORDERED", "ORDER_DAG"}), nil}, + {"empty feature sets not supported", types.NewVersion("1", []string{}), types.DefaultIBCVersion, types.ErrVersionNegotiationFailed}, + {"one feature missing", types.DefaultIBCVersion, types.NewVersion("1", []string{"ORDER_UNORDERED", "ORDER_DAG"}), types.ErrVersionNegotiationFailed}, + {"both features missing", types.DefaultIBCVersion, types.NewVersion("1", []string{"ORDER_DAG"}), types.ErrVersionNegotiationFailed}, + {"identifiers do not match", types.NewVersion("2", []string{"ORDER_UNORDERED", "ORDER_ORDERED"}), types.DefaultIBCVersion, types.ErrVersionNegotiationFailed}, + } + + for i, tc := range testCases { + err := tc.supportedVersion.VerifyProposedVersion(tc.proposedVersion) + + if tc.expError == nil { + require.NoError(t, err, "test case %d: %s", i, tc.name) + } else { + require.ErrorIs(t, err, tc.expError) + } + } +} + +func TestVerifySupportedFeature(t *testing.T) { + nilFeatures := types.NewVersion(types.DefaultIBCVersionIdentifier, nil) + + testCases := []struct { + name string + version *types.Version + feature string + expPass bool + }{ + {"check ORDERED supported", ibctesting.ConnectionVersion, "ORDER_ORDERED", true}, + {"check UNORDERED supported", ibctesting.ConnectionVersion, "ORDER_UNORDERED", true}, + {"check DAG unsupported", ibctesting.ConnectionVersion, "ORDER_DAG", false}, + {"check empty feature set returns false", nilFeatures, "ORDER_ORDERED", false}, + } + + for i, tc := range testCases { + supported := types.VerifySupportedFeature(tc.version, tc.feature) + require.Equal(t, tc.expPass, supported, "test case %d: %s", i, tc.name) + } +} diff --git a/modules/core/04-channel/client/cli/cli.go b/modules/core/04-channel/client/cli/cli.go new file mode 100644 index 0000000..fa9b886 --- /dev/null +++ b/modules/core/04-channel/client/cli/cli.go @@ -0,0 +1,37 @@ +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" +) + +// GetQueryCmd returns the query commands for IBC channels +func GetQueryCmd() *cobra.Command { + queryCmd := &cobra.Command{ + Use: types.SubModuleName, + Short: "IBC channel query subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + queryCmd.AddCommand( + GetCmdQueryChannels(), + GetCmdQueryChannel(), + GetCmdQueryConnectionChannels(), + GetCmdQueryChannelClientState(), + GetCmdQueryPacketCommitment(), + GetCmdQueryPacketCommitments(), + GetCmdQueryPacketReceipt(), + GetCmdQueryPacketAcknowledgement(), + GetCmdQueryUnreceivedPackets(), + GetCmdQueryUnreceivedAcks(), + GetCmdQueryNextSequenceReceive(), + GetCmdQueryNextSequenceSend(), + ) + + return queryCmd +} diff --git a/modules/core/04-channel/client/cli/query.go b/modules/core/04-channel/client/cli/query.go new file mode 100644 index 0000000..3e97237 --- /dev/null +++ b/modules/core/04-channel/client/cli/query.go @@ -0,0 +1,493 @@ +package cli + +import ( + "fmt" + "strconv" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/version" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/client/utils" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +const ( + flagSequences = "sequences" +) + +// GetCmdQueryChannels defines the command to query all the channels ends +// that this chain maintains. +func GetCmdQueryChannels() *cobra.Command { + cmd := &cobra.Command{ + Use: "channels", + Short: "Query all channels", + Long: "Query all channels from a chain", + Example: fmt.Sprintf("%s query %s %s channels", version.AppName, ibcexported.ModuleName, types.SubModuleName), + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + req := &types.QueryChannelsRequest{ + Pagination: pageReq, + } + + res, err := queryClient.Channels(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "channels") + + return cmd +} + +// GetCmdQueryChannel defines the command to query a channel end +func GetCmdQueryChannel() *cobra.Command { + cmd := &cobra.Command{ + Use: "end [port-id] [channel-id]", + Short: "Query a channel end", + Long: "Query an IBC channel end from a port and channel identifiers", + Example: fmt.Sprintf( + "%s query %s %s end [port-id] [channel-id]", version.AppName, ibcexported.ModuleName, types.SubModuleName, + ), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + portID := args[0] + channelID := args[1] + prove, _ := cmd.Flags().GetBool(flags.FlagProve) + + channelRes, err := utils.QueryChannel(clientCtx, portID, channelID, prove) + if err != nil { + return err + } + + return clientCtx.PrintProto(channelRes) + }, + } + + cmd.Flags().Bool(flags.FlagProve, true, "show proofs for the query results") + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdQueryConnectionChannels defines the command to query all the channels associated with a +// connection +func GetCmdQueryConnectionChannels() *cobra.Command { + cmd := &cobra.Command{ + Use: "connections [connection-id]", + Short: "Query all channels associated with a connection", + Long: "Query all channels associated with a connection", + Example: fmt.Sprintf("%s query %s %s connections [connection-id]", version.AppName, ibcexported.ModuleName, types.SubModuleName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + req := &types.QueryConnectionChannelsRequest{ + Connection: args[0], + Pagination: pageReq, + } + + res, err := queryClient.ConnectionChannels(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "channels associated with a connection") + + return cmd +} + +// GetCmdQueryChannelClientState defines the command to query a client state from a channel +func GetCmdQueryChannelClientState() *cobra.Command { + cmd := &cobra.Command{ + Use: "client-state [port-id] [channel-id]", + Short: "Query the client state associated with a channel", + Long: "Query the client state associated with a channel, by providing its port and channel identifiers.", + Example: fmt.Sprintf("%s query ibc channel client-state [port-id] [channel-id]", version.AppName), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + portID := args[0] + channelID := args[1] + + res, err := utils.QueryChannelClientState(clientCtx, portID, channelID, false) + if err != nil { + return err + } + + return clientCtx.PrintProto(res.IdentifiedClientState) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdQueryPacketCommitments defines the command to query all packet commitments associated with +// a channel +func GetCmdQueryPacketCommitments() *cobra.Command { + cmd := &cobra.Command{ + Use: "packet-commitments [port-id] [channel-id]", + Short: "Query all packet commitments associated with a channel", + Long: "Query all packet commitments associated with a channel", + Example: fmt.Sprintf("%s query %s %s packet-commitments [port-id] [channel-id]", version.AppName, ibcexported.ModuleName, types.SubModuleName), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + req := &types.QueryPacketCommitmentsRequest{ + PortId: args[0], + ChannelId: args[1], + Pagination: pageReq, + } + + res, err := queryClient.PacketCommitments(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "packet commitments associated with a channel") + + return cmd +} + +// GetCmdQueryPacketCommitment defines the command to query a packet commitment +func GetCmdQueryPacketCommitment() *cobra.Command { + cmd := &cobra.Command{ + Use: "packet-commitment [port-id] [channel-id] [sequence]", + Short: "Query a packet commitment", + Long: "Query a packet commitment", + Example: fmt.Sprintf( + "%s query %s %s packet-commitment [port-id] [channel-id] [sequence]", version.AppName, ibcexported.ModuleName, types.SubModuleName, + ), + Args: cobra.ExactArgs(3), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + portID := args[0] + channelID := args[1] + prove, _ := cmd.Flags().GetBool(flags.FlagProve) + + seq, err := strconv.ParseUint(args[2], 10, 64) + if err != nil { + return err + } + + res, err := utils.QueryPacketCommitment(clientCtx, portID, channelID, seq, prove) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + cmd.Flags().Bool(flags.FlagProve, true, "show proofs for the query results") + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdQueryPacketReceipt defines the command to query a packet receipt +func GetCmdQueryPacketReceipt() *cobra.Command { + cmd := &cobra.Command{ + Use: "packet-receipt [port-id] [channel-id] [sequence]", + Short: "Query a packet receipt", + Long: "Query a packet receipt", + Example: fmt.Sprintf( + "%s query %s %s packet-receipt [port-id] [channel-id] [sequence]", version.AppName, ibcexported.ModuleName, types.SubModuleName, + ), + Args: cobra.ExactArgs(3), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + portID := args[0] + channelID := args[1] + prove, _ := cmd.Flags().GetBool(flags.FlagProve) + + seq, err := strconv.ParseUint(args[2], 10, 64) + if err != nil { + return err + } + + res, err := utils.QueryPacketReceipt(clientCtx, portID, channelID, seq, prove) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + cmd.Flags().Bool(flags.FlagProve, true, "show proofs for the query results") + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdQueryPacketAcknowledgement defines the command to query a packet acknowledgement +func GetCmdQueryPacketAcknowledgement() *cobra.Command { + cmd := &cobra.Command{ + Use: "packet-ack [port-id] [channel-id] [sequence]", + Short: "Query a packet acknowledgement", + Long: "Query a packet acknowledgement", + Example: fmt.Sprintf( + "%s query %s %s packet-ack [port-id] [channel-id] [sequence]", version.AppName, ibcexported.ModuleName, types.SubModuleName, + ), + Args: cobra.ExactArgs(3), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + portID := args[0] + channelID := args[1] + prove, _ := cmd.Flags().GetBool(flags.FlagProve) + + seq, err := strconv.ParseUint(args[2], 10, 64) + if err != nil { + return err + } + + res, err := utils.QueryPacketAcknowledgement(clientCtx, portID, channelID, seq, prove) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + cmd.Flags().Bool(flags.FlagProve, true, "show proofs for the query results") + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdQueryUnreceivedPackets defines the command to query all the unreceived +// packets on the receiving chain +func GetCmdQueryUnreceivedPackets() *cobra.Command { + cmd := &cobra.Command{ + Use: "unreceived-packets [port-id] [channel-id]", + Short: "Query all the unreceived packets associated with a channel", + Long: `Determine if a packet, given a list of packet commitment sequences, is unreceived. + +The return value represents: +- Unreceived packet commitments: no acknowledgement exists on receiving chain for the given packet commitment sequence on sending chain. +`, + Example: fmt.Sprintf("%s query %s %s unreceived-packets [port-id] [channel-id] --sequences=1,2,3", version.AppName, ibcexported.ModuleName, types.SubModuleName), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + seqSlice, err := cmd.Flags().GetInt64Slice(flagSequences) + if err != nil { + return err + } + + seqs := make([]uint64, len(seqSlice)) + for i := range seqSlice { + seqs[i] = uint64(seqSlice[i]) + } + + req := &types.QueryUnreceivedPacketsRequest{ + PortId: args[0], + ChannelId: args[1], + PacketCommitmentSequences: seqs, + } + + res, err := queryClient.UnreceivedPackets(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + cmd.Flags().Int64Slice(flagSequences, []int64{}, "comma separated list of packet sequence numbers") + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdQueryUnreceivedAcks defines the command to query all the unreceived acks on the original sending chain +func GetCmdQueryUnreceivedAcks() *cobra.Command { + cmd := &cobra.Command{ + Use: "unreceived-acks [port-id] [channel-id]", + Short: "Query all the unreceived acks associated with a channel", + Long: `Given a list of acknowledgement sequences from counterparty, determine if an ack on the counterparty chain has been received on the executing chain. + +The return value represents: +- Unreceived packet acknowledgement: packet commitment exists on original sending (executing) chain and ack exists on receiving chain. +`, + Example: fmt.Sprintf("%s query %s %s unreceived-acks [port-id] [channel-id] --sequences=1,2,3", version.AppName, ibcexported.ModuleName, types.SubModuleName), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + seqSlice, err := cmd.Flags().GetInt64Slice(flagSequences) + if err != nil { + return err + } + + seqs := make([]uint64, len(seqSlice)) + for i := range seqSlice { + seqs[i] = uint64(seqSlice[i]) + } + + req := &types.QueryUnreceivedAcksRequest{ + PortId: args[0], + ChannelId: args[1], + PacketAckSequences: seqs, + } + + res, err := queryClient.UnreceivedAcks(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + cmd.Flags().Int64Slice(flagSequences, []int64{}, "comma separated list of packet sequence numbers") + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdQueryNextSequenceReceive defines the command to query a next receive sequence for a given channel +func GetCmdQueryNextSequenceReceive() *cobra.Command { + cmd := &cobra.Command{ + Use: "next-sequence-receive [port-id] [channel-id]", + Short: "Query a next receive sequence", + Long: "Query the next receive sequence for a given channel", + Example: fmt.Sprintf( + "%s query %s %s next-sequence-receive [port-id] [channel-id]", version.AppName, ibcexported.ModuleName, types.SubModuleName, + ), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + portID := args[0] + channelID := args[1] + prove, _ := cmd.Flags().GetBool(flags.FlagProve) + + sequenceRes, err := utils.QueryNextSequenceReceive(clientCtx, portID, channelID, prove) + if err != nil { + return err + } + + clientCtx = clientCtx.WithHeight(int64(sequenceRes.ProofHeight.RevisionHeight)) + return clientCtx.PrintProto(sequenceRes) + }, + } + + cmd.Flags().Bool(flags.FlagProve, true, "show proofs for the query results") + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdQueryNextSequenceSend defines the command to query a next send sequence for a given channel +func GetCmdQueryNextSequenceSend() *cobra.Command { + cmd := &cobra.Command{ + Use: "next-sequence-send [port-id] [channel-id]", + Short: "Query a next send sequence", + Long: "Query the next sequence send for a given channel", + Example: fmt.Sprintf( + "%s query %s %s next-sequence-send [port-id] [channel-id]", version.AppName, ibcexported.ModuleName, types.SubModuleName, + ), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + portID := args[0] + channelID := args[1] + prove, _ := cmd.Flags().GetBool(flags.FlagProve) + + sequenceRes, err := utils.QueryNextSequenceSend(clientCtx, portID, channelID, prove) + if err != nil { + return err + } + + clientCtx = clientCtx.WithHeight(int64(sequenceRes.ProofHeight.RevisionHeight)) + return clientCtx.PrintProto(sequenceRes) + }, + } + + cmd.Flags().Bool(flags.FlagProve, true, "show proofs for the query results") + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/modules/core/04-channel/client/utils/utils.go b/modules/core/04-channel/client/utils/utils.go new file mode 100644 index 0000000..f3616f3 --- /dev/null +++ b/modules/core/04-channel/client/utils/utils.go @@ -0,0 +1,303 @@ +package utils + +import ( + "context" + "encoding/binary" + + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + + clientutils "github.com/cosmos/ibc-go/v10/modules/core/02-client/client/utils" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcclient "github.com/cosmos/ibc-go/v10/modules/core/client" +) + +// QueryChannel returns a channel end. +// If prove is true, it performs an ABCI store query in order to retrieve the merkle proof. Otherwise, +// it uses the gRPC query client. +func QueryChannel( + clientCtx client.Context, portID, channelID string, prove bool, +) (*types.QueryChannelResponse, error) { + if prove { + return queryChannelABCI(clientCtx, portID, channelID) + } + + queryClient := types.NewQueryClient(clientCtx) + req := &types.QueryChannelRequest{ + PortId: portID, + ChannelId: channelID, + } + + return queryClient.Channel(context.Background(), req) +} + +func queryChannelABCI(clientCtx client.Context, portID, channelID string) (*types.QueryChannelResponse, error) { + key := host.ChannelKey(portID, channelID) + + value, proofBz, proofHeight, err := ibcclient.QueryTendermintProof(clientCtx, key) + if err != nil { + return nil, err + } + + // check if channel exists + if len(value) == 0 { + return nil, errorsmod.Wrapf(types.ErrChannelNotFound, "portID (%s), channelID (%s)", portID, channelID) + } + + cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry) + + var channel types.Channel + if err := cdc.Unmarshal(value, &channel); err != nil { + return nil, err + } + + return types.NewQueryChannelResponse(channel, proofBz, proofHeight), nil +} + +// QueryChannelClientState returns the ClientState of a channel end. If +// prove is true, it performs an ABCI store query in order to retrieve the +// merkle proof. Otherwise, it uses the gRPC query client. +func QueryChannelClientState( + clientCtx client.Context, portID, channelID string, prove bool, +) (*types.QueryChannelClientStateResponse, error) { + queryClient := types.NewQueryClient(clientCtx) + req := &types.QueryChannelClientStateRequest{ + PortId: portID, + ChannelId: channelID, + } + + res, err := queryClient.ChannelClientState(context.Background(), req) + if err != nil { + return nil, err + } + + if prove { + clientStateRes, err := clientutils.QueryClientStateABCI(clientCtx, res.IdentifiedClientState.ClientId) + if err != nil { + return nil, err + } + + // use client state returned from ABCI query in case query height differs + identifiedClientState := clienttypes.IdentifiedClientState{ + ClientId: res.IdentifiedClientState.ClientId, + ClientState: clientStateRes.ClientState, + } + res = types.NewQueryChannelClientStateResponse(identifiedClientState, clientStateRes.Proof, clientStateRes.ProofHeight) + } + + return res, nil +} + +// QueryChannelConsensusState returns the ConsensusState of a channel end. If +// prove is true, it performs an ABCI store query in order to retrieve the +// merkle proof. Otherwise, it uses the gRPC query client. +func QueryChannelConsensusState( + clientCtx client.Context, portID, channelID string, height clienttypes.Height, prove bool, +) (*types.QueryChannelConsensusStateResponse, error) { + queryClient := types.NewQueryClient(clientCtx) + req := &types.QueryChannelConsensusStateRequest{ + PortId: portID, + ChannelId: channelID, + RevisionNumber: height.RevisionNumber, + RevisionHeight: height.RevisionHeight, + } + + res, err := queryClient.ChannelConsensusState(context.Background(), req) + if err != nil { + return nil, err + } + + if prove { + consensusStateRes, err := clientutils.QueryConsensusStateABCI(clientCtx, res.ClientId, height) + if err != nil { + return nil, err + } + + res = types.NewQueryChannelConsensusStateResponse(res.ClientId, consensusStateRes.ConsensusState, height, consensusStateRes.Proof, consensusStateRes.ProofHeight) + } + + return res, nil +} + +// QueryNextSequenceReceive returns the next sequence receive. +// If prove is true, it performs an ABCI store query in order to retrieve the merkle proof. Otherwise, +// it uses the gRPC query client. +func QueryNextSequenceReceive( + clientCtx client.Context, portID, channelID string, prove bool, +) (*types.QueryNextSequenceReceiveResponse, error) { + if prove { + return queryNextSequenceRecvABCI(clientCtx, portID, channelID) + } + + queryClient := types.NewQueryClient(clientCtx) + req := &types.QueryNextSequenceReceiveRequest{ + PortId: portID, + ChannelId: channelID, + } + + return queryClient.NextSequenceReceive(context.Background(), req) +} + +func queryNextSequenceRecvABCI(clientCtx client.Context, portID, channelID string) (*types.QueryNextSequenceReceiveResponse, error) { + key := host.NextSequenceRecvKey(portID, channelID) + + value, proofBz, proofHeight, err := ibcclient.QueryTendermintProof(clientCtx, key) + if err != nil { + return nil, err + } + + // check if next sequence receive exists + if len(value) == 0 { + return nil, errorsmod.Wrapf(types.ErrChannelNotFound, "portID (%s), channelID (%s)", portID, channelID) + } + + sequence := binary.BigEndian.Uint64(value) + + return types.NewQueryNextSequenceReceiveResponse(sequence, proofBz, proofHeight), nil +} + +// QueryNextSequenceSend returns the next sequence send. +// If prove is true, it performs an ABCI store query in order to retrieve the merkle proof. Otherwise, +// it uses the gRPC query client. +func QueryNextSequenceSend( + clientCtx client.Context, portID, channelID string, prove bool, +) (*types.QueryNextSequenceSendResponse, error) { + if prove { + return queryNextSequenceSendABCI(clientCtx, portID, channelID) + } + + queryClient := types.NewQueryClient(clientCtx) + req := &types.QueryNextSequenceSendRequest{ + PortId: portID, + ChannelId: channelID, + } + return queryClient.NextSequenceSend(context.Background(), req) +} + +func queryNextSequenceSendABCI(clientCtx client.Context, portID, channelID string) (*types.QueryNextSequenceSendResponse, error) { + key := host.NextSequenceSendKey(portID, channelID) + + value, proofBz, proofHeight, err := ibcclient.QueryTendermintProof(clientCtx, key) + if err != nil { + return nil, err + } + + // check if next sequence send exists + if len(value) == 0 { + return nil, errorsmod.Wrapf(types.ErrChannelNotFound, "portID (%s), channelID (%s)", portID, channelID) + } + + sequence := binary.BigEndian.Uint64(value) + + return types.NewQueryNextSequenceSendResponse(sequence, proofBz, proofHeight), nil +} + +// QueryPacketCommitment returns a packet commitment. +// If prove is true, it performs an ABCI store query in order to retrieve the merkle proof. Otherwise, +// it uses the gRPC query client. +func QueryPacketCommitment( + clientCtx client.Context, portID, channelID string, + sequence uint64, prove bool, +) (*types.QueryPacketCommitmentResponse, error) { + if prove { + return queryPacketCommitmentABCI(clientCtx, portID, channelID, sequence) + } + + queryClient := types.NewQueryClient(clientCtx) + req := &types.QueryPacketCommitmentRequest{ + PortId: portID, + ChannelId: channelID, + Sequence: sequence, + } + + return queryClient.PacketCommitment(context.Background(), req) +} + +func queryPacketCommitmentABCI( + clientCtx client.Context, portID, channelID string, sequence uint64, +) (*types.QueryPacketCommitmentResponse, error) { + key := host.PacketCommitmentKey(portID, channelID, sequence) + + value, proofBz, proofHeight, err := ibcclient.QueryTendermintProof(clientCtx, key) + if err != nil { + return nil, err + } + + // check if packet commitment exists + if len(value) == 0 { + return nil, errorsmod.Wrapf(types.ErrPacketCommitmentNotFound, "portID (%s), channelID (%s), sequence (%d)", portID, channelID, sequence) + } + + return types.NewQueryPacketCommitmentResponse(value, proofBz, proofHeight), nil +} + +// QueryPacketReceipt returns data about a packet receipt. +// If prove is true, it performs an ABCI store query in order to retrieve the merkle proof. Otherwise, +// it uses the gRPC query client. +func QueryPacketReceipt( + clientCtx client.Context, portID, channelID string, + sequence uint64, prove bool, +) (*types.QueryPacketReceiptResponse, error) { + if prove { + return queryPacketReceiptABCI(clientCtx, portID, channelID, sequence) + } + + queryClient := types.NewQueryClient(clientCtx) + req := &types.QueryPacketReceiptRequest{ + PortId: portID, + ChannelId: channelID, + Sequence: sequence, + } + + return queryClient.PacketReceipt(context.Background(), req) +} + +func queryPacketReceiptABCI( + clientCtx client.Context, portID, channelID string, sequence uint64, +) (*types.QueryPacketReceiptResponse, error) { + key := host.PacketReceiptKey(portID, channelID, sequence) + + value, proofBz, proofHeight, err := ibcclient.QueryTendermintProof(clientCtx, key) + if err != nil { + return nil, err + } + + return types.NewQueryPacketReceiptResponse(value != nil, proofBz, proofHeight), nil +} + +// QueryPacketAcknowledgement returns the data about a packet acknowledgement. +// If prove is true, it performs an ABCI store query in order to retrieve the merkle proof. Otherwise, +// it uses the gRPC query client +func QueryPacketAcknowledgement(clientCtx client.Context, portID, channelID string, sequence uint64, prove bool) (*types.QueryPacketAcknowledgementResponse, error) { + if prove { + return queryPacketAcknowledgementABCI(clientCtx, portID, channelID, sequence) + } + + queryClient := types.NewQueryClient(clientCtx) + req := &types.QueryPacketAcknowledgementRequest{ + PortId: portID, + ChannelId: channelID, + Sequence: sequence, + } + + return queryClient.PacketAcknowledgement(context.Background(), req) +} + +func queryPacketAcknowledgementABCI(clientCtx client.Context, portID, channelID string, sequence uint64) (*types.QueryPacketAcknowledgementResponse, error) { + key := host.PacketAcknowledgementKey(portID, channelID, sequence) + + value, proofBz, proofHeight, err := ibcclient.QueryTendermintProof(clientCtx, key) + if err != nil { + return nil, err + } + + if len(value) == 0 { + return nil, errorsmod.Wrapf(types.ErrInvalidAcknowledgement, "portID (%s), channelID (%s), sequence (%d)", portID, channelID, sequence) + } + + return types.NewQueryPacketAcknowledgementResponse(value, proofBz, proofHeight), nil +} diff --git a/modules/core/04-channel/doc.go b/modules/core/04-channel/doc.go new file mode 100644 index 0000000..a05c9c8 --- /dev/null +++ b/modules/core/04-channel/doc.go @@ -0,0 +1,17 @@ +/* +Package channel implements the ICS 04 - Channel and Packet Semantics specification +(https://github.com/cosmos/ibc/tree/main/spec/core/ics-004-channel-and-packet-semantics). This +concrete implementation defines types and methods for safely creating two +stateful objects (channel ends) on two separate chains, each associated with a +particular connection. A channel serves as a conduit for packets passing between a +module on one chain and a module on another, ensuring that packets are executed +only once, delivered in the order in which they were sent (if necessary), +and delivered only to the corresponding module owning the other end of the +channel on the destination chain. + +The main types are Channel, which defines a stateful object on a +chain that allows for exactly-once packet delivery between specific modules +on separate blockchains, and Packet, which defines the data carried +across different chains through IBC. +*/ +package channel diff --git a/modules/core/04-channel/genesis.go b/modules/core/04-channel/genesis.go new file mode 100644 index 0000000..3ee7faf --- /dev/null +++ b/modules/core/04-channel/genesis.go @@ -0,0 +1,50 @@ +package channel + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/keeper" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" +) + +// InitGenesis initializes the ibc channel submodule's state from a provided genesis +// state. +func InitGenesis(ctx sdk.Context, k *keeper.Keeper, gs types.GenesisState) { + for _, channel := range gs.Channels { + ch := types.NewChannel(channel.State, channel.Ordering, channel.Counterparty, channel.ConnectionHops, channel.Version) + k.SetChannel(ctx, channel.PortId, channel.ChannelId, ch) + } + for _, ack := range gs.Acknowledgements { + k.SetPacketAcknowledgement(ctx, ack.PortId, ack.ChannelId, ack.Sequence, ack.Data) + } + for _, commitment := range gs.Commitments { + k.SetPacketCommitment(ctx, commitment.PortId, commitment.ChannelId, commitment.Sequence, commitment.Data) + } + for _, receipt := range gs.Receipts { + k.SetPacketReceipt(ctx, receipt.PortId, receipt.ChannelId, receipt.Sequence) + } + for _, ss := range gs.SendSequences { + k.SetNextSequenceSend(ctx, ss.PortId, ss.ChannelId, ss.Sequence) + } + for _, rs := range gs.RecvSequences { + k.SetNextSequenceRecv(ctx, rs.PortId, rs.ChannelId, rs.Sequence) + } + for _, as := range gs.AckSequences { + k.SetNextSequenceAck(ctx, as.PortId, as.ChannelId, as.Sequence) + } + k.SetNextChannelSequence(ctx, gs.NextChannelSequence) +} + +// ExportGenesis returns the ibc channel submodule's exported genesis. +func ExportGenesis(ctx sdk.Context, k *keeper.Keeper) types.GenesisState { + return types.GenesisState{ + Channels: k.GetAllChannels(ctx), + Acknowledgements: k.GetAllPacketAcks(ctx), + Commitments: k.GetAllPacketCommitments(ctx), + Receipts: k.GetAllPacketReceipts(ctx), + SendSequences: k.GetAllPacketSendSeqs(ctx), + RecvSequences: k.GetAllPacketRecvSeqs(ctx), + AckSequences: k.GetAllPacketAckSeqs(ctx), + NextChannelSequence: k.GetNextChannelSequence(ctx), + } +} diff --git a/modules/core/04-channel/keeper/ante.go b/modules/core/04-channel/keeper/ante.go new file mode 100644 index 0000000..14a8ff6 --- /dev/null +++ b/modules/core/04-channel/keeper/ante.go @@ -0,0 +1,24 @@ +package keeper + +import ( + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" +) + +// RecvPacketReCheckTx applies replay protection ensuring that when relay messages are +// re-executed in ReCheckTx, we can appropriately filter out redundant relay transactions. +func (k *Keeper) RecvPacketReCheckTx(ctx sdk.Context, packet types.Packet) error { + channel, found := k.GetChannel(ctx, packet.GetDestPort(), packet.GetDestChannel()) + if !found { + return errorsmod.Wrap(types.ErrChannelNotFound, packet.GetDestChannel()) + } + + if err := k.applyReplayProtection(ctx, packet, channel); err != nil { + return err + } + + return nil +} diff --git a/modules/core/04-channel/keeper/ante_test.go b/modules/core/04-channel/keeper/ante_test.go new file mode 100644 index 0000000..7149c83 --- /dev/null +++ b/modules/core/04-channel/keeper/ante_test.go @@ -0,0 +1,62 @@ +package keeper_test + +import ( + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *KeeperTestSuite) TestRecvPacketReCheckTx() { + var ( + path *ibctesting.Path + packet types.Packet + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() {}, + nil, + }, + { + "channel not found", + func() { + packet.DestinationPort = "invalid-port" //nolint:goconst + }, + types.ErrChannelNotFound, + }, + { + "redundant relay", + func() { + err := suite.chainB.App.GetIBCKeeper().ChannelKeeper.RecvPacketReCheckTx(suite.chainB.GetContext(), packet) + suite.Require().NoError(err) + }, + types.ErrNoOpMsg, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + + tc.malleate() + + err = suite.chainB.App.GetIBCKeeper().ChannelKeeper.RecvPacketReCheckTx(suite.chainB.GetContext(), packet) + + if tc.expError == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} diff --git a/modules/core/04-channel/keeper/events.go b/modules/core/04-channel/keeper/events.go new file mode 100644 index 0000000..1535b47 --- /dev/null +++ b/modules/core/04-channel/keeper/events.go @@ -0,0 +1,269 @@ +package keeper + +import ( + "encoding/hex" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// emitChannelOpenInitEvent emits a channel open init event +func emitChannelOpenInitEvent(ctx sdk.Context, portID string, channelID string, channel types.Channel) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeChannelOpenInit, + sdk.NewAttribute(types.AttributeKeyPortID, portID), + sdk.NewAttribute(types.AttributeKeyChannelID, channelID), + sdk.NewAttribute(types.AttributeCounterpartyPortID, channel.Counterparty.PortId), + sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]), + sdk.NewAttribute(types.AttributeKeyVersion, channel.Version), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitChannelOpenTryEvent emits a channel open try event +func emitChannelOpenTryEvent(ctx sdk.Context, portID string, channelID string, channel types.Channel) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeChannelOpenTry, + sdk.NewAttribute(types.AttributeKeyPortID, portID), + sdk.NewAttribute(types.AttributeKeyChannelID, channelID), + sdk.NewAttribute(types.AttributeCounterpartyPortID, channel.Counterparty.PortId), + sdk.NewAttribute(types.AttributeCounterpartyChannelID, channel.Counterparty.ChannelId), + sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]), + sdk.NewAttribute(types.AttributeKeyVersion, channel.Version), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitChannelOpenAckEvent emits a channel open acknowledge event +func emitChannelOpenAckEvent(ctx sdk.Context, portID string, channelID string, channel types.Channel) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeChannelOpenAck, + sdk.NewAttribute(types.AttributeKeyPortID, portID), + sdk.NewAttribute(types.AttributeKeyChannelID, channelID), + sdk.NewAttribute(types.AttributeCounterpartyPortID, channel.Counterparty.PortId), + sdk.NewAttribute(types.AttributeCounterpartyChannelID, channel.Counterparty.ChannelId), + sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitChannelOpenConfirmEvent emits a channel open confirm event +func emitChannelOpenConfirmEvent(ctx sdk.Context, portID string, channelID string, channel types.Channel) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeChannelOpenConfirm, + sdk.NewAttribute(types.AttributeKeyPortID, portID), + sdk.NewAttribute(types.AttributeKeyChannelID, channelID), + sdk.NewAttribute(types.AttributeCounterpartyPortID, channel.Counterparty.PortId), + sdk.NewAttribute(types.AttributeCounterpartyChannelID, channel.Counterparty.ChannelId), + sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitChannelCloseInitEvent emits a channel close init event +func emitChannelCloseInitEvent(ctx sdk.Context, portID string, channelID string, channel types.Channel) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeChannelCloseInit, + sdk.NewAttribute(types.AttributeKeyPortID, portID), + sdk.NewAttribute(types.AttributeKeyChannelID, channelID), + sdk.NewAttribute(types.AttributeCounterpartyPortID, channel.Counterparty.PortId), + sdk.NewAttribute(types.AttributeCounterpartyChannelID, channel.Counterparty.ChannelId), + sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitChannelCloseConfirmEvent emits a channel close confirm event +func emitChannelCloseConfirmEvent(ctx sdk.Context, portID string, channelID string, channel types.Channel) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeChannelCloseConfirm, + sdk.NewAttribute(types.AttributeKeyPortID, portID), + sdk.NewAttribute(types.AttributeKeyChannelID, channelID), + sdk.NewAttribute(types.AttributeCounterpartyPortID, channel.Counterparty.PortId), + sdk.NewAttribute(types.AttributeCounterpartyChannelID, channel.Counterparty.ChannelId), + sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitSendPacketEvent emits an event with packet data along with other packet information for relayer +// to pick up and relay to other chain +func emitSendPacketEvent(ctx sdk.Context, packet types.Packet, channel types.Channel, timeoutHeight exported.Height) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeSendPacket, + sdk.NewAttribute(types.AttributeKeyDataHex, hex.EncodeToString(packet.GetData())), + sdk.NewAttribute(types.AttributeKeyTimeoutHeight, timeoutHeight.String()), + sdk.NewAttribute(types.AttributeKeyTimeoutTimestamp, fmt.Sprintf("%d", packet.GetTimeoutTimestamp())), + sdk.NewAttribute(types.AttributeKeySequence, fmt.Sprintf("%d", packet.GetSequence())), + sdk.NewAttribute(types.AttributeKeySrcPort, packet.GetSourcePort()), + sdk.NewAttribute(types.AttributeKeySrcChannel, packet.GetSourceChannel()), + sdk.NewAttribute(types.AttributeKeyDstPort, packet.GetDestPort()), + sdk.NewAttribute(types.AttributeKeyDstChannel, packet.GetDestChannel()), + sdk.NewAttribute(types.AttributeKeyChannelOrdering, channel.Ordering.String()), + // we only support 1-hop packets now, and that is the most important hop for a relayer + // (is it going to a chain I am connected to) + sdk.NewAttribute(types.AttributeKeyConnection, channel.ConnectionHops[0]), // DEPRECATED + sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitRecvPacketEvent emits a receive packet event. It will be emitted both the first time a packet +// is received for a certain sequence and for all duplicate receives. +func emitRecvPacketEvent(ctx sdk.Context, packet types.Packet, channel types.Channel) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeRecvPacket, + sdk.NewAttribute(types.AttributeKeyDataHex, hex.EncodeToString(packet.GetData())), + sdk.NewAttribute(types.AttributeKeyTimeoutHeight, packet.GetTimeoutHeight().String()), + sdk.NewAttribute(types.AttributeKeyTimeoutTimestamp, fmt.Sprintf("%d", packet.GetTimeoutTimestamp())), + sdk.NewAttribute(types.AttributeKeySequence, fmt.Sprintf("%d", packet.GetSequence())), + sdk.NewAttribute(types.AttributeKeySrcPort, packet.GetSourcePort()), + sdk.NewAttribute(types.AttributeKeySrcChannel, packet.GetSourceChannel()), + sdk.NewAttribute(types.AttributeKeyDstPort, packet.GetDestPort()), + sdk.NewAttribute(types.AttributeKeyDstChannel, packet.GetDestChannel()), + sdk.NewAttribute(types.AttributeKeyChannelOrdering, channel.Ordering.String()), + // we only support 1-hop packets now, and that is the most important hop for a relayer + // (is it going to a chain I am connected to) + sdk.NewAttribute(types.AttributeKeyConnection, channel.ConnectionHops[0]), // DEPRECATED + sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitWriteAcknowledgementEvent emits an event that the relayer can query for +func emitWriteAcknowledgementEvent(ctx sdk.Context, packet types.Packet, channel types.Channel, acknowledgement []byte) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeWriteAck, + sdk.NewAttribute(types.AttributeKeyDataHex, hex.EncodeToString(packet.GetData())), + sdk.NewAttribute(types.AttributeKeyTimeoutHeight, packet.GetTimeoutHeight().String()), + sdk.NewAttribute(types.AttributeKeyTimeoutTimestamp, fmt.Sprintf("%d", packet.GetTimeoutTimestamp())), + sdk.NewAttribute(types.AttributeKeySequence, fmt.Sprintf("%d", packet.GetSequence())), + sdk.NewAttribute(types.AttributeKeySrcPort, packet.GetSourcePort()), + sdk.NewAttribute(types.AttributeKeySrcChannel, packet.GetSourceChannel()), + sdk.NewAttribute(types.AttributeKeyDstPort, packet.GetDestPort()), + sdk.NewAttribute(types.AttributeKeyDstChannel, packet.GetDestChannel()), + sdk.NewAttribute(types.AttributeKeyAckHex, hex.EncodeToString(acknowledgement)), + sdk.NewAttribute(types.AttributeKeyChannelOrdering, channel.Ordering.String()), + // we only support 1-hop packets now, and that is the most important hop for a relayer + // (is it going to a chain I am connected to) + sdk.NewAttribute(types.AttributeKeyConnection, channel.ConnectionHops[0]), // DEPRECATED + sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitAcknowledgePacketEvent emits an acknowledge packet event. It will be emitted both the first time +// a packet is acknowledged for a certain sequence and for all duplicate acknowledgements. +func emitAcknowledgePacketEvent(ctx sdk.Context, packet types.Packet, channel types.Channel) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeAcknowledgePacket, + sdk.NewAttribute(types.AttributeKeyTimeoutHeight, packet.GetTimeoutHeight().String()), + sdk.NewAttribute(types.AttributeKeyTimeoutTimestamp, fmt.Sprintf("%d", packet.GetTimeoutTimestamp())), + sdk.NewAttribute(types.AttributeKeySequence, fmt.Sprintf("%d", packet.GetSequence())), + sdk.NewAttribute(types.AttributeKeySrcPort, packet.GetSourcePort()), + sdk.NewAttribute(types.AttributeKeySrcChannel, packet.GetSourceChannel()), + sdk.NewAttribute(types.AttributeKeyDstPort, packet.GetDestPort()), + sdk.NewAttribute(types.AttributeKeyDstChannel, packet.GetDestChannel()), + sdk.NewAttribute(types.AttributeKeyChannelOrdering, channel.Ordering.String()), + // we only support 1-hop packets now, and that is the most important hop for a relayer + // (is it going to a chain I am connected to) + sdk.NewAttribute(types.AttributeKeyConnection, channel.ConnectionHops[0]), // DEPRECATED + sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitTimeoutPacketEvent emits a timeout packet event. It will be emitted both the first time a packet +// is timed out for a certain sequence and for all duplicate timeouts. +func emitTimeoutPacketEvent(ctx sdk.Context, packet types.Packet, channel types.Channel) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeTimeoutPacket, + sdk.NewAttribute(types.AttributeKeyTimeoutHeight, packet.GetTimeoutHeight().String()), + sdk.NewAttribute(types.AttributeKeyTimeoutTimestamp, fmt.Sprintf("%d", packet.GetTimeoutTimestamp())), + sdk.NewAttribute(types.AttributeKeySequence, fmt.Sprintf("%d", packet.GetSequence())), + sdk.NewAttribute(types.AttributeKeySrcPort, packet.GetSourcePort()), + sdk.NewAttribute(types.AttributeKeySrcChannel, packet.GetSourceChannel()), + sdk.NewAttribute(types.AttributeKeyDstPort, packet.GetDestPort()), + sdk.NewAttribute(types.AttributeKeyDstChannel, packet.GetDestChannel()), + sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]), + sdk.NewAttribute(types.AttributeKeyChannelOrdering, channel.Ordering.String()), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitChannelClosedEvent emits a channel closed event. +func emitChannelClosedEvent(ctx sdk.Context, packet types.Packet, channel types.Channel) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeChannelClosed, + sdk.NewAttribute(types.AttributeKeyPortID, packet.GetSourcePort()), + sdk.NewAttribute(types.AttributeKeyChannelID, packet.GetSourceChannel()), + sdk.NewAttribute(types.AttributeCounterpartyPortID, channel.Counterparty.PortId), + sdk.NewAttribute(types.AttributeCounterpartyChannelID, channel.Counterparty.ChannelId), + sdk.NewAttribute(types.AttributeKeyConnectionID, channel.ConnectionHops[0]), + sdk.NewAttribute(types.AttributeKeyChannelOrdering, channel.Ordering.String()), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} diff --git a/modules/core/04-channel/keeper/export_test.go b/modules/core/04-channel/keeper/export_test.go new file mode 100644 index 0000000..d0093fd --- /dev/null +++ b/modules/core/04-channel/keeper/export_test.go @@ -0,0 +1,21 @@ +package keeper + +/* + This file is to allow for unexported functions to be accessible to the testing package. +*/ + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" +) + +// SetRecvStartSequence is a wrapper around setRecvStartSequence to allow the function to be directly called in tests. +func (k *Keeper) SetRecvStartSequence(ctx sdk.Context, portID, channelID string, sequence uint64) { + k.setRecvStartSequence(ctx, portID, channelID, sequence) +} + +// TimeoutExecuted is a wrapper around timeoutExecuted to allow the function to be directly called in tests. +func (k *Keeper) TimeoutExecuted(ctx sdk.Context, channel types.Channel, packet types.Packet) error { + return k.timeoutExecuted(ctx, channel, packet) +} diff --git a/modules/core/04-channel/keeper/grpc_query.go b/modules/core/04-channel/keeper/grpc_query.go new file mode 100644 index 0000000..ae98f91 --- /dev/null +++ b/modules/core/04-channel/keeper/grpc_query.go @@ -0,0 +1,622 @@ +package keeper + +import ( + "context" + "strconv" + "strings" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/store/prefix" + + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + + "github.com/cosmos/ibc-go/v10/internal/validate" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +var _ types.QueryServer = (*queryServer)(nil) + +// queryServer implements the 04-channel types.QueryServer interface. +// It embeds the channel keeper to leverage store access while limiting the api of the channel keeper. +type queryServer struct { + *Keeper +} + +// NewQueryServer returns a new 04-channel types.QueryServer implementation. +func NewQueryServer(k *Keeper) types.QueryServer { + return &queryServer{ + Keeper: k, + } +} + +// Channel implements the Query/Channel gRPC method +func (q *queryServer) Channel(goCtx context.Context, req *types.QueryChannelRequest) (*types.QueryChannelResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + if err := validate.GRPCRequest(req.PortId, req.ChannelId); err != nil { + return nil, err + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + channel, found := q.GetChannel(ctx, req.PortId, req.ChannelId) + if !found { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrChannelNotFound, "port-id: %s, channel-id %s", req.PortId, req.ChannelId).Error(), + ) + } + + selfHeight := clienttypes.GetSelfHeight(ctx) + return types.NewQueryChannelResponse(channel, nil, selfHeight), nil +} + +// Channels implements the Query/Channels gRPC method +func (q *queryServer) Channels(goCtx context.Context, req *types.QueryChannelsRequest) (*types.QueryChannelsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + var channels []*types.IdentifiedChannel + store := prefix.NewStore(runtime.KVStoreAdapter(q.storeService.OpenKVStore(ctx)), []byte(host.KeyChannelEndPrefix)) + + pageRes, err := query.Paginate(store, req.Pagination, func(key, value []byte) error { + var result types.Channel + if err := q.cdc.Unmarshal(value, &result); err != nil { + return err + } + + portID, channelID, err := host.ParseChannelPath(string(key)) + if err != nil { + return err + } + + identifiedChannel := types.NewIdentifiedChannel(portID, channelID, result) + channels = append(channels, &identifiedChannel) + return nil + }) + if err != nil { + return nil, err + } + + selfHeight := clienttypes.GetSelfHeight(ctx) + return &types.QueryChannelsResponse{ + Channels: channels, + Pagination: pageRes, + Height: selfHeight, + }, nil +} + +// ConnectionChannels implements the Query/ConnectionChannels gRPC method +func (q *queryServer) ConnectionChannels(goCtx context.Context, req *types.QueryConnectionChannelsRequest) (*types.QueryConnectionChannelsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := host.ConnectionIdentifierValidator(req.Connection); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + var channels []*types.IdentifiedChannel + + store := prefix.NewStore(runtime.KVStoreAdapter(q.storeService.OpenKVStore(ctx)), []byte(host.KeyChannelEndPrefix)) + + pageRes, err := query.FilteredPaginate(store, req.Pagination, func(key, value []byte, accumulate bool) (bool, error) { + // filter any metadata stored under channel key + var result types.Channel + if err := q.cdc.Unmarshal(value, &result); err != nil { + return false, err + } + + // ignore channel and continue to the next item if the connection is + // different than the requested one + if result.ConnectionHops[0] != req.Connection { + return false, nil + } + + portID, channelID, err := host.ParseChannelPath(string(key)) + if err != nil { + return false, err + } + + identifiedChannel := types.NewIdentifiedChannel(portID, channelID, result) + channels = append(channels, &identifiedChannel) + return true, nil + }) + if err != nil { + return nil, err + } + + selfHeight := clienttypes.GetSelfHeight(ctx) + return &types.QueryConnectionChannelsResponse{ + Channels: channels, + Pagination: pageRes, + Height: selfHeight, + }, nil +} + +// ChannelClientState implements the Query/ChannelClientState gRPC method +func (q *queryServer) ChannelClientState(goCtx context.Context, req *types.QueryChannelClientStateRequest) (*types.QueryChannelClientStateResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := validate.GRPCRequest(req.PortId, req.ChannelId); err != nil { + return nil, err + } + + clientID, clientState, err := q.GetChannelClientState(ctx, req.PortId, req.ChannelId) + if err != nil { + return nil, status.Error(codes.NotFound, err.Error()) + } + + identifiedClientState := clienttypes.NewIdentifiedClientState(clientID, clientState) + + selfHeight := clienttypes.GetSelfHeight(ctx) + return types.NewQueryChannelClientStateResponse(identifiedClientState, nil, selfHeight), nil +} + +// ChannelConsensusState implements the Query/ChannelConsensusState gRPC method +func (q *queryServer) ChannelConsensusState(goCtx context.Context, req *types.QueryChannelConsensusStateRequest) (*types.QueryChannelConsensusStateResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := validate.GRPCRequest(req.PortId, req.ChannelId); err != nil { + return nil, err + } + + channel, found := q.GetChannel(ctx, req.PortId, req.ChannelId) + if !found { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrChannelNotFound, "port-id: %s, channel-id %s", req.PortId, req.ChannelId).Error(), + ) + } + + connection, found := q.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + if !found { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrapf(connectiontypes.ErrConnectionNotFound, "connection-id: %s", channel.ConnectionHops[0]).Error(), + ) + } + + consHeight := clienttypes.NewHeight(req.RevisionNumber, req.RevisionHeight) + consensusState, found := q.clientKeeper.GetClientConsensusState(ctx, connection.ClientId, consHeight) + if !found { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrapf(clienttypes.ErrConsensusStateNotFound, "client-id: %s", connection.ClientId).Error(), + ) + } + + anyConsensusState, err := clienttypes.PackConsensusState(consensusState) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + selfHeight := clienttypes.GetSelfHeight(ctx) + return types.NewQueryChannelConsensusStateResponse(connection.ClientId, anyConsensusState, consHeight, nil, selfHeight), nil +} + +// PacketCommitment implements the Query/PacketCommitment gRPC method +func (q *queryServer) PacketCommitment(goCtx context.Context, req *types.QueryPacketCommitmentRequest) (*types.QueryPacketCommitmentResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := validate.GRPCRequest(req.PortId, req.ChannelId); err != nil { + return nil, err + } + + if req.Sequence == 0 { + return nil, status.Error(codes.InvalidArgument, "packet sequence cannot be 0") + } + + if !q.HasChannel(ctx, req.PortId, req.ChannelId) { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", req.PortId, req.ChannelId).Error(), + ) + } + + commitmentBz := q.GetPacketCommitment(ctx, req.PortId, req.ChannelId, req.Sequence) + if len(commitmentBz) == 0 { + return nil, status.Error(codes.NotFound, "packet commitment hash not found") + } + + selfHeight := clienttypes.GetSelfHeight(ctx) + return types.NewQueryPacketCommitmentResponse(commitmentBz, nil, selfHeight), nil +} + +// PacketCommitments implements the Query/PacketCommitments gRPC method +func (q *queryServer) PacketCommitments(goCtx context.Context, req *types.QueryPacketCommitmentsRequest) (*types.QueryPacketCommitmentsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := validate.GRPCRequest(req.PortId, req.ChannelId); err != nil { + return nil, err + } + + if !q.HasChannel(ctx, req.PortId, req.ChannelId) { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", req.PortId, req.ChannelId).Error(), + ) + } + var commitments []*types.PacketState + store := prefix.NewStore(runtime.KVStoreAdapter(q.storeService.OpenKVStore(ctx)), host.PacketCommitmentPrefixKey(req.PortId, req.ChannelId)) + + pageRes, err := query.Paginate(store, req.Pagination, func(key, value []byte) error { + keySplit := strings.Split(string(key), "/") + + sequence, err := strconv.ParseUint(keySplit[len(keySplit)-1], 10, 64) + if err != nil { + return err + } + + commitment := types.NewPacketState(req.PortId, req.ChannelId, sequence, value) + commitments = append(commitments, &commitment) + return nil + }) + if err != nil { + return nil, err + } + + selfHeight := clienttypes.GetSelfHeight(ctx) + return &types.QueryPacketCommitmentsResponse{ + Commitments: commitments, + Pagination: pageRes, + Height: selfHeight, + }, nil +} + +// PacketReceipt implements the Query/PacketReceipt gRPC method +func (q *queryServer) PacketReceipt(goCtx context.Context, req *types.QueryPacketReceiptRequest) (*types.QueryPacketReceiptResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := validate.GRPCRequest(req.PortId, req.ChannelId); err != nil { + return nil, err + } + + if req.Sequence == 0 { + return nil, status.Error(codes.InvalidArgument, "packet sequence cannot be 0") + } + + if !q.HasChannel(ctx, req.PortId, req.ChannelId) { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", req.PortId, req.ChannelId).Error(), + ) + } + _, recvd := q.GetPacketReceipt(ctx, req.PortId, req.ChannelId, req.Sequence) + + selfHeight := clienttypes.GetSelfHeight(ctx) + return types.NewQueryPacketReceiptResponse(recvd, nil, selfHeight), nil +} + +// PacketAcknowledgement implements the Query/PacketAcknowledgement gRPC method +func (q *queryServer) PacketAcknowledgement(goCtx context.Context, req *types.QueryPacketAcknowledgementRequest) (*types.QueryPacketAcknowledgementResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := validate.GRPCRequest(req.PortId, req.ChannelId); err != nil { + return nil, err + } + + if req.Sequence == 0 { + return nil, status.Error(codes.InvalidArgument, "packet sequence cannot be 0") + } + + if !q.HasChannel(ctx, req.PortId, req.ChannelId) { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", req.PortId, req.ChannelId).Error(), + ) + } + acknowledgementBz, found := q.GetPacketAcknowledgement(ctx, req.PortId, req.ChannelId, req.Sequence) + if !found || len(acknowledgementBz) == 0 { + return nil, status.Error(codes.NotFound, "packet acknowledgement hash not found") + } + + selfHeight := clienttypes.GetSelfHeight(ctx) + return types.NewQueryPacketAcknowledgementResponse(acknowledgementBz, nil, selfHeight), nil +} + +// PacketAcknowledgements implements the Query/PacketAcknowledgements gRPC method +func (q *queryServer) PacketAcknowledgements(goCtx context.Context, req *types.QueryPacketAcknowledgementsRequest) (*types.QueryPacketAcknowledgementsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := validate.GRPCRequest(req.PortId, req.ChannelId); err != nil { + return nil, err + } + + if !q.HasChannel(ctx, req.PortId, req.ChannelId) { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", req.PortId, req.ChannelId).Error(), + ) + } + var acks []*types.PacketState + store := prefix.NewStore(runtime.KVStoreAdapter(q.storeService.OpenKVStore(ctx)), host.PacketAcknowledgementPrefixKey(req.PortId, req.ChannelId)) + + // if a list of packet sequences is provided then query for each specific ack and return a list <= len(req.PacketCommitmentSequences) + // otherwise, maintain previous behaviour and perform paginated query + for _, seq := range req.PacketCommitmentSequences { + acknowledgementBz, found := q.GetPacketAcknowledgement(ctx, req.PortId, req.ChannelId, seq) + if !found || len(acknowledgementBz) == 0 { + continue + } + + ack := types.NewPacketState(req.PortId, req.ChannelId, seq, acknowledgementBz) + acks = append(acks, &ack) + } + + if len(req.PacketCommitmentSequences) > 0 { + selfHeight := clienttypes.GetSelfHeight(ctx) + return &types.QueryPacketAcknowledgementsResponse{ + Acknowledgements: acks, + Pagination: nil, + Height: selfHeight, + }, nil + } + + pageRes, err := query.Paginate(store, req.Pagination, func(key, value []byte) error { + keySplit := strings.Split(string(key), "/") + + sequence, err := strconv.ParseUint(keySplit[len(keySplit)-1], 10, 64) + if err != nil { + return err + } + + ack := types.NewPacketState(req.PortId, req.ChannelId, sequence, value) + acks = append(acks, &ack) + + return nil + }) + if err != nil { + return nil, err + } + + selfHeight := clienttypes.GetSelfHeight(ctx) + return &types.QueryPacketAcknowledgementsResponse{ + Acknowledgements: acks, + Pagination: pageRes, + Height: selfHeight, + }, nil +} + +// UnreceivedPackets implements the Query/UnreceivedPackets gRPC method. Given +// a list of counterparty packet commitments, the querier checks if the packet +// has already been received by checking if a receipt exists on this +// chain for the packet sequence. All packets that haven't been received yet +// are returned in the response +// Usage: To use this method correctly, first query all packet commitments on +// the sending chain using the Query/PacketCommitments gRPC method. +// Then input the returned sequences into the QueryUnreceivedPacketsRequest +// and send the request to this Query/UnreceivedPackets on the **receiving** +// chain. This gRPC method will then return the list of packet sequences that +// are yet to be received on the receiving chain. +// +// NOTE: The querier makes the assumption that the provided list of packet +// commitments is correct and will not function properly if the list +// is not up to date. Ideally the query height should equal the latest height +// on the counterparty's client which represents this chain. +func (q *queryServer) UnreceivedPackets(goCtx context.Context, req *types.QueryUnreceivedPacketsRequest) (*types.QueryUnreceivedPacketsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := validate.GRPCRequest(req.PortId, req.ChannelId); err != nil { + return nil, err + } + + channel, found := q.GetChannel(ctx, req.PortId, req.ChannelId) + if !found { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrChannelNotFound, "port-id: %s, channel-id %s", req.PortId, req.ChannelId).Error(), + ) + } + + var unreceivedSequences []uint64 + switch channel.Ordering { + case types.UNORDERED: + for i, seq := range req.PacketCommitmentSequences { + // filter for invalid sequences to ensure they are not included in the response value. + if seq == 0 { + return nil, status.Errorf(codes.InvalidArgument, "packet sequence %d cannot be 0", i) + } + + // if the packet receipt does not exist, then it is unreceived + if _, found := q.GetPacketReceipt(ctx, req.PortId, req.ChannelId, seq); !found { + unreceivedSequences = append(unreceivedSequences, seq) + } + } + case types.ORDERED: + nextSequenceRecv, found := q.GetNextSequenceRecv(ctx, req.PortId, req.ChannelId) + if !found { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrapf( + types.ErrSequenceReceiveNotFound, + "destination port: %s, destination channel: %s", req.PortId, req.ChannelId, + ).Error(), + ) + } + + for i, seq := range req.PacketCommitmentSequences { + // filter for invalid sequences to ensure they are not included in the response value. + if seq == 0 { + return nil, status.Errorf(codes.InvalidArgument, "packet sequence %d cannot be 0", i) + } + + // Any sequence greater than or equal to the next sequence to be received is not received. + if seq >= nextSequenceRecv { + unreceivedSequences = append(unreceivedSequences, seq) + } + } + default: + return nil, status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(types.ErrInvalidChannelOrdering, "channel order %s is not supported", channel.Ordering).Error()) + } + + selfHeight := clienttypes.GetSelfHeight(ctx) + return &types.QueryUnreceivedPacketsResponse{ + Sequences: unreceivedSequences, + Height: selfHeight, + }, nil +} + +// UnreceivedAcks implements the Query/UnreceivedAcks gRPC method. Given +// a list of counterparty packet acknowledgements, the querier checks if the packet +// has already been received by checking if the packet commitment still exists on this +// chain (original sender) for the packet sequence. +// All acknowledgmeents that haven't been received yet are returned in the response. +// Usage: To use this method correctly, first query all packet acknowledgements on +// the original receiving chain (ie the chain that wrote the acks) using the Query/PacketAcknowledgements gRPC method. +// Then input the returned sequences into the QueryUnreceivedAcksRequest +// and send the request to this Query/UnreceivedAcks on the **original sending** +// chain. This gRPC method will then return the list of packet sequences whose +// acknowledgements are already written on the receiving chain but haven't yet +// been received back to the sending chain. +// +// NOTE: The querier makes the assumption that the provided list of packet +// acknowledgements is correct and will not function properly if the list +// is not up to date. Ideally the query height should equal the latest height +// on the counterparty's client which represents this chain. +func (q *queryServer) UnreceivedAcks(goCtx context.Context, req *types.QueryUnreceivedAcksRequest) (*types.QueryUnreceivedAcksResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := validate.GRPCRequest(req.PortId, req.ChannelId); err != nil { + return nil, err + } + + if !q.HasChannel(ctx, req.PortId, req.ChannelId) { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", req.PortId, req.ChannelId).Error(), + ) + } + var unreceivedSequences []uint64 + + for i, seq := range req.PacketAckSequences { + if seq == 0 { + return nil, status.Errorf(codes.InvalidArgument, "packet sequence %d cannot be 0", i) + } + + // if packet commitment still exists on the original sending chain, then packet ack has not been received + // since processing the ack will delete the packet commitment + if commitment := q.GetPacketCommitment(ctx, req.PortId, req.ChannelId, seq); len(commitment) != 0 { + unreceivedSequences = append(unreceivedSequences, seq) + } + + } + + selfHeight := clienttypes.GetSelfHeight(ctx) + return &types.QueryUnreceivedAcksResponse{ + Sequences: unreceivedSequences, + Height: selfHeight, + }, nil +} + +// NextSequenceReceive implements the Query/NextSequenceReceive gRPC method +func (q *queryServer) NextSequenceReceive(goCtx context.Context, req *types.QueryNextSequenceReceiveRequest) (*types.QueryNextSequenceReceiveResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := validate.GRPCRequest(req.PortId, req.ChannelId); err != nil { + return nil, err + } + + channel, found := q.GetChannel(ctx, req.PortId, req.ChannelId) + if !found { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrChannelNotFound, "port-id: %s, channel-id %s", req.PortId, req.ChannelId).Error(), + ) + } + + // Return the next sequence received for ordered channels. Unordered channels + // do not make use of the next sequence receive. + var sequence uint64 + if channel.Ordering != types.UNORDERED { + sequence, found = q.GetNextSequenceRecv(ctx, req.PortId, req.ChannelId) + if !found { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrSequenceReceiveNotFound, "port-id: %s, channel-id %s", req.PortId, req.ChannelId).Error(), + ) + } + } + selfHeight := clienttypes.GetSelfHeight(ctx) + return types.NewQueryNextSequenceReceiveResponse(sequence, nil, selfHeight), nil +} + +// NextSequenceSend implements the Query/NextSequenceSend gRPC method +func (q *queryServer) NextSequenceSend(goCtx context.Context, req *types.QueryNextSequenceSendRequest) (*types.QueryNextSequenceSendResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := validate.GRPCRequest(req.PortId, req.ChannelId); err != nil { + return nil, err + } + + sequence, found := q.GetNextSequenceSend(ctx, req.PortId, req.ChannelId) + if !found { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrSequenceSendNotFound, "port-id: %s, channel-id %s", req.PortId, req.ChannelId).Error(), + ) + } + selfHeight := clienttypes.GetSelfHeight(ctx) + return types.NewQueryNextSequenceSendResponse(sequence, nil, selfHeight), nil +} diff --git a/modules/core/04-channel/keeper/grpc_query_test.go b/modules/core/04-channel/keeper/grpc_query_test.go new file mode 100644 index 0000000..d1fa8fd --- /dev/null +++ b/modules/core/04-channel/keeper/grpc_query_test.go @@ -0,0 +1,1903 @@ +package keeper_test + +import ( + "errors" + "fmt" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/cosmos-sdk/types/query" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/keeper" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +const doesnotexist = "doesnotexist" + +func (suite *KeeperTestSuite) TestQueryChannel() { + var ( + req *types.QueryChannelRequest + expChannel types.Channel + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid port ID", + func() { + req = &types.QueryChannelRequest{ + PortId: "", + ChannelId: "test-channel-id", + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "invalid channel ID", + func() { + req = &types.QueryChannelRequest{ + PortId: "test-port-id", + ChannelId: "", + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "channel not found", + func() { + req = &types.QueryChannelRequest{ + PortId: "test-port-id", + ChannelId: "test-channel-id", + } + }, + status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrChannelNotFound, "port-id: test-port-id, channel-id test-channel-id").Error(), + ), + }, + { + "success", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupConnections() + path.SetChannelOrdered() + + // init channel + err := path.EndpointA.ChanOpenInit() + suite.Require().NoError(err) + + expChannel = path.EndpointA.GetChannel() + + req = &types.QueryChannelRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ChannelKeeper) + res, err := queryServer.Channel(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(&expChannel, res.Channel) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryChannels() { + var ( + req *types.QueryChannelsRequest + expChannels = []*types.IdentifiedChannel(nil) + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "empty pagination", + func() { + req = &types.QueryChannelsRequest{} + }, + nil, + }, + { + "success", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + // channel0 on first connection on chainA + counterparty0 := types.Counterparty{ + PortId: path.EndpointB.ChannelConfig.PortID, + ChannelId: path.EndpointB.ChannelID, + } + + // path1 creates a second channel on first connection on chainA + path1 := ibctesting.NewPath(suite.chainA, suite.chainB) + path1.SetChannelOrdered() + path1.EndpointA.ClientID = path.EndpointA.ClientID + path1.EndpointB.ClientID = path.EndpointB.ClientID + path1.EndpointA.ConnectionID = path.EndpointA.ConnectionID + path1.EndpointB.ConnectionID = path.EndpointB.ConnectionID + + suite.coordinator.CreateMockChannels(path1) + counterparty1 := types.Counterparty{ + PortId: path1.EndpointB.ChannelConfig.PortID, + ChannelId: path1.EndpointB.ChannelID, + } + + channel0 := types.NewChannel( + types.OPEN, types.UNORDERED, + counterparty0, []string{path.EndpointA.ConnectionID}, path.EndpointA.ChannelConfig.Version, + ) + channel1 := types.NewChannel( + types.OPEN, types.ORDERED, + counterparty1, []string{path.EndpointA.ConnectionID}, path1.EndpointA.ChannelConfig.Version, + ) + + idCh0 := types.NewIdentifiedChannel(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, channel0) + idCh1 := types.NewIdentifiedChannel(path1.EndpointA.ChannelConfig.PortID, path1.EndpointA.ChannelID, channel1) + + expChannels = []*types.IdentifiedChannel{&idCh0, &idCh1} + + req = &types.QueryChannelsRequest{ + Pagination: &query.PageRequest{ + Key: nil, + Limit: 2, + CountTotal: true, + }, + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ChannelKeeper) + res, err := queryServer.Channels(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(len(expChannels), int(res.Pagination.Total)) + suite.Require().ElementsMatch(expChannels, res.Channels) // order of channels is not guaranteed, due to lexicographical ordering + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryConnectionChannels() { + var ( + req *types.QueryConnectionChannelsRequest + expChannels = []*types.IdentifiedChannel{} + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid connection ID", + func() { + req = &types.QueryConnectionChannelsRequest{ + Connection: "", + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "success", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + // channel0 on first connection on chainA + counterparty0 := types.Counterparty{ + PortId: path.EndpointB.ChannelConfig.PortID, + ChannelId: path.EndpointB.ChannelID, + } + + // path1 creates a second channel on first connection on chainA + path1 := ibctesting.NewPath(suite.chainA, suite.chainB) + path1.SetChannelOrdered() + path1.EndpointA.ClientID = path.EndpointA.ClientID + path1.EndpointB.ClientID = path.EndpointB.ClientID + path1.EndpointA.ConnectionID = path.EndpointA.ConnectionID + path1.EndpointB.ConnectionID = path.EndpointB.ConnectionID + + suite.coordinator.CreateMockChannels(path1) + counterparty1 := types.Counterparty{ + PortId: path1.EndpointB.ChannelConfig.PortID, + ChannelId: path1.EndpointB.ChannelID, + } + + channel0 := types.NewChannel( + types.OPEN, types.UNORDERED, + counterparty0, []string{path.EndpointA.ConnectionID}, path.EndpointA.ChannelConfig.Version, + ) + channel1 := types.NewChannel( + types.OPEN, types.ORDERED, + counterparty1, []string{path.EndpointA.ConnectionID}, path.EndpointA.ChannelConfig.Version, + ) + + idCh0 := types.NewIdentifiedChannel(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, channel0) + idCh1 := types.NewIdentifiedChannel(path1.EndpointA.ChannelConfig.PortID, path1.EndpointA.ChannelID, channel1) + + expChannels = []*types.IdentifiedChannel{&idCh0, &idCh1} + + req = &types.QueryConnectionChannelsRequest{ + Connection: path.EndpointA.ConnectionID, + Pagination: &query.PageRequest{ + Key: nil, + Limit: 2, + CountTotal: true, + }, + } + }, + nil, + }, + { + "success, empty response", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + expChannels = []*types.IdentifiedChannel(nil) + req = &types.QueryConnectionChannelsRequest{ + Connection: "externalConnID", + Pagination: &query.PageRequest{ + Key: nil, + Limit: 2, + CountTotal: false, + }, + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ChannelKeeper) + res, err := queryServer.ConnectionChannels(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expChannels, res.Channels) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryChannelClientState() { + var ( + req *types.QueryChannelClientStateRequest + expIdentifiedClientState clienttypes.IdentifiedClientState + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid port ID", + func() { + req = &types.QueryChannelClientStateRequest{ + PortId: "", + ChannelId: "test-channel-id", + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "invalid channel ID", + func() { + req = &types.QueryChannelClientStateRequest{ + PortId: "test-port-id", + ChannelId: "", + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "channel not found", + func() { + req = &types.QueryChannelClientStateRequest{ + PortId: "test-port-id", + ChannelId: "test-channel-id", + } + }, + status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrChannelNotFound, "port-id: test-port-id, channel-id: test-channel-id").Error(), + ), + }, + { + "connection not found", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + channel := path.EndpointA.GetChannel() + // update channel to reference a connection that does not exist + channel.ConnectionHops[0] = doesnotexist + + // set connection hops to wrong connection ID + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, channel) + + req = &types.QueryChannelClientStateRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + } + }, status.Error( + codes.NotFound, + errorsmod.Wrapf(connectiontypes.ErrConnectionNotFound, "connection-id: doesnotexist").Error(), + ), + }, + { + "client state for channel's connection not found", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + // set connection to empty so clientID is empty + suite.chainA.App.GetIBCKeeper().ConnectionKeeper.SetConnection(suite.chainA.GetContext(), path.EndpointA.ConnectionID, connectiontypes.ConnectionEnd{}) + + req = &types.QueryChannelClientStateRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + } + }, status.Error( + codes.NotFound, + errorsmod.Wrapf(clienttypes.ErrClientNotFound, "client-id: ").Error(), + ), + }, + { + "success", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupConnections() + path.SetChannelOrdered() + + // init channel + err := path.EndpointA.ChanOpenInit() + suite.Require().NoError(err) + + expClientState := suite.chainA.GetClientState(path.EndpointA.ClientID) + expIdentifiedClientState = clienttypes.NewIdentifiedClientState(path.EndpointA.ClientID, expClientState) + + req = &types.QueryChannelClientStateRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ChannelKeeper) + res, err := queryServer.ChannelClientState(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(&expIdentifiedClientState, res.IdentifiedClientState) + + // ensure UnpackInterfaces is defined + cachedValue := res.IdentifiedClientState.ClientState.GetCachedValue() + suite.Require().NotNil(cachedValue) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryChannelConsensusState() { + var ( + req *types.QueryChannelConsensusStateRequest + expConsensusState exported.ConsensusState + expClientID string + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid port ID", + func() { + req = &types.QueryChannelConsensusStateRequest{ + PortId: "", + ChannelId: "test-channel-id", + RevisionNumber: 0, + RevisionHeight: 1, + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "invalid channel ID", + func() { + req = &types.QueryChannelConsensusStateRequest{ + PortId: "test-port-id", + ChannelId: "", + RevisionNumber: 0, + RevisionHeight: 1, + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "channel not found", + func() { + req = &types.QueryChannelConsensusStateRequest{ + PortId: "test-port-id", + ChannelId: "test-channel-id", + RevisionNumber: 0, + RevisionHeight: 1, + } + }, + status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrChannelNotFound, "port-id: test-port-id, channel-id test-channel-id").Error(), + ), + }, + { + "connection not found", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + channel := path.EndpointA.GetChannel() + // update channel to reference a connection that does not exist + channel.ConnectionHops[0] = doesnotexist + + // set connection hops to wrong connection ID + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, channel) + + req = &types.QueryChannelConsensusStateRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + RevisionNumber: 0, + RevisionHeight: 1, + } + }, status.Error( + codes.NotFound, + errorsmod.Wrapf(connectiontypes.ErrConnectionNotFound, "connection-id: doesnotexist").Error(), + ), + }, + { + "consensus state for channel's connection not found", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + req = &types.QueryChannelConsensusStateRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + RevisionNumber: 0, + RevisionHeight: uint64(suite.chainA.GetContext().BlockHeight()), // use current height + } + }, status.Error( + codes.NotFound, + errorsmod.Wrapf(clienttypes.ErrConsensusStateNotFound, "client-id: 07-tendermint-0").Error(), + ), + }, + { + "success", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupConnections() + path.SetChannelOrdered() + + // init channel + err := path.EndpointA.ChanOpenInit() + suite.Require().NoError(err) + + expConsensusState, _ = suite.chainA.GetConsensusState(path.EndpointA.ClientID, path.EndpointA.GetClientLatestHeight()) + suite.Require().NotNil(expConsensusState) + expClientID = path.EndpointA.ClientID + + req = &types.QueryChannelConsensusStateRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + RevisionNumber: path.EndpointA.GetClientLatestHeight().GetRevisionNumber(), + RevisionHeight: path.EndpointA.GetClientLatestHeight().GetRevisionHeight(), + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ChannelKeeper) + res, err := queryServer.ChannelConsensusState(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + consensusState, err := clienttypes.UnpackConsensusState(res.ConsensusState) + suite.Require().NoError(err) + suite.Require().Equal(expConsensusState, consensusState) + suite.Require().Equal(expClientID, res.ClientId) + + // ensure UnpackInterfaces is defined + cachedValue := res.ConsensusState.GetCachedValue() + suite.Require().NotNil(cachedValue) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryPacketCommitment() { + var ( + req *types.QueryPacketCommitmentRequest + expCommitment []byte + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid port ID", + func() { + req = &types.QueryPacketCommitmentRequest{ + PortId: "", + ChannelId: "test-channel-id", + Sequence: 0, + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "invalid channel ID", + func() { + req = &types.QueryPacketCommitmentRequest{ + PortId: "test-port-id", + ChannelId: "", + Sequence: 0, + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "invalid sequence", + func() { + req = &types.QueryPacketCommitmentRequest{ + PortId: "test-port-id", + ChannelId: "test-channel-id", + Sequence: 0, + } + }, + status.Error( + codes.InvalidArgument, + errors.New("packet sequence cannot be 0").Error(), + ), + }, + { + "channel not found", + func() { + req = &types.QueryPacketCommitmentRequest{ + PortId: "test-port-id", + ChannelId: "test-channel-id", + Sequence: 1, + } + }, + status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrChannelNotFound, "port ID (test-port-id) channel ID (test-channel-id)").Error(), + ), + }, + { + "commitment not found", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + expCommitment = []byte("hash") + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketCommitment(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, 1, expCommitment) + req = &types.QueryPacketCommitmentRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + Sequence: 2, + } + }, + status.Error( + codes.NotFound, + errors.New("packet commitment hash not found").Error(), + ), + }, + { + "invalid ID", + func() { + req = &types.QueryPacketCommitmentRequest{ + PortId: "", + ChannelId: "test-channel-id", + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "success", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + expCommitment = []byte("hash") + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketCommitment(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, 1, expCommitment) + + req = &types.QueryPacketCommitmentRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + Sequence: 1, + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ChannelKeeper) + res, err := queryServer.PacketCommitment(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expCommitment, res.Commitment) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryPacketCommitments() { + var ( + req *types.QueryPacketCommitmentsRequest + expCommitments = []*types.PacketState{} + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid ID", + func() { + req = &types.QueryPacketCommitmentsRequest{ + PortId: "", + ChannelId: "test-channel-id", + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "channel not found", + func() { + req = &types.QueryPacketCommitmentsRequest{ + PortId: "test-port-id", + ChannelId: "test-channel-id", + } + }, + status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrChannelNotFound, "port ID (test-port-id) channel ID (test-channel-id)").Error(), + ), + }, + { + "success", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + expCommitments = make([]*types.PacketState, 9) + + for i := uint64(0); i < 9; i++ { + commitment := types.NewPacketState(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, i, fmt.Appendf(nil, "hash_%d", i)) + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketCommitment(suite.chainA.GetContext(), commitment.PortId, commitment.ChannelId, commitment.Sequence, commitment.Data) + expCommitments[i] = &commitment + } + + req = &types.QueryPacketCommitmentsRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + Pagination: &query.PageRequest{ + Key: nil, + Limit: 11, + CountTotal: true, + }, + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ChannelKeeper) + res, err := queryServer.PacketCommitments(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expCommitments, res.Commitments) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryPacketReceipt() { + var ( + req *types.QueryPacketReceiptRequest + expReceived bool + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid port ID", + func() { + req = &types.QueryPacketReceiptRequest{ + PortId: "", + ChannelId: "test-channel-id", + Sequence: 1, + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "invalid channel ID", + func() { + req = &types.QueryPacketReceiptRequest{ + PortId: "test-port-id", + ChannelId: "", + Sequence: 1, + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "invalid sequence", + func() { + req = &types.QueryPacketReceiptRequest{ + PortId: "test-port-id", + ChannelId: "test-channel-id", + Sequence: 0, + } + }, + status.Error( + codes.InvalidArgument, + errors.New("packet sequence cannot be 0").Error(), + ), + }, + { + "channel not found", + func() { + req = &types.QueryPacketReceiptRequest{ + PortId: "test-port-id", + ChannelId: "test-channel-id", + Sequence: 1, + } + }, + status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrChannelNotFound, "port ID (test-port-id) channel ID (test-channel-id)").Error(), + ), + }, + { + "success: receipt not found", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketReceipt(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, 1) + + req = &types.QueryPacketReceiptRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + Sequence: 3, + } + expReceived = false + }, + nil, + }, + { + "success: receipt found", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketReceipt(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, 1) + + req = &types.QueryPacketReceiptRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + Sequence: 1, + } + expReceived = true + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ChannelKeeper) + res, err := queryServer.PacketReceipt(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expReceived, res.Received) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryPacketAcknowledgement() { + var ( + req *types.QueryPacketAcknowledgementRequest + expAck []byte + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid port ID", + func() { + req = &types.QueryPacketAcknowledgementRequest{ + PortId: "", + ChannelId: "test-channel-id", + Sequence: 0, + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "invalid channel ID", + func() { + req = &types.QueryPacketAcknowledgementRequest{ + PortId: "test-port-id", + ChannelId: "", + Sequence: 0, + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "invalid sequence", + func() { + req = &types.QueryPacketAcknowledgementRequest{ + PortId: "test-port-id", + ChannelId: "test-channel-id", + Sequence: 0, + } + }, + status.Error( + codes.InvalidArgument, + errors.New("packet sequence cannot be 0").Error(), + ), + }, + { + "ack not found", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + expAck = []byte("hash") + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketAcknowledgement(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, 1, expAck) + + req = &types.QueryPacketAcknowledgementRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + Sequence: 2, + } + }, + status.Error( + codes.NotFound, + errors.New("packet acknowledgement hash not found").Error(), + ), + }, + { + "channel not found", + func() { + req = &types.QueryPacketAcknowledgementRequest{ + PortId: "test-port-id", + ChannelId: "test-channel-id", + Sequence: 1, + } + }, + status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrChannelNotFound, "port ID (test-port-id) channel ID (test-channel-id)").Error(), + ), + }, + { + "success", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + expAck = []byte("hash") + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketAcknowledgement(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, 1, expAck) + + req = &types.QueryPacketAcknowledgementRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + Sequence: 1, + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ChannelKeeper) + res, err := queryServer.PacketAcknowledgement(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expAck, res.Acknowledgement) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryPacketAcknowledgements() { + var ( + req *types.QueryPacketAcknowledgementsRequest + expAcknowledgements = []*types.PacketState{} + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid ID", + func() { + req = &types.QueryPacketAcknowledgementsRequest{ + PortId: "", + ChannelId: "test-channel-id", + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "channel not found", + func() { + req = &types.QueryPacketAcknowledgementsRequest{ + PortId: "test-port-id", + ChannelId: "test-channel-id", + } + }, + status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrChannelNotFound, "port ID (test-port-id) channel ID (test-channel-id)").Error(), + ), + }, + { + "success, filtered res", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + var commitments []uint64 + + for i := uint64(0); i < 100; i++ { + ack := types.NewPacketState(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, i, fmt.Appendf(nil, "hash_%d", i)) + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketAcknowledgement(suite.chainA.GetContext(), ack.PortId, ack.ChannelId, ack.Sequence, ack.Data) + + if i < 10 { // populate the store with 100 and query for 10 specific acks + expAcknowledgements = append(expAcknowledgements, &ack) + commitments = append(commitments, ack.Sequence) + } + } + + req = &types.QueryPacketAcknowledgementsRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + PacketCommitmentSequences: commitments, + Pagination: nil, + } + }, + nil, + }, + { + "success", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + expAcknowledgements = make([]*types.PacketState, 9) + + for i := uint64(0); i < 9; i++ { + ack := types.NewPacketState(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, i, fmt.Appendf(nil, "hash_%d", i)) + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketAcknowledgement(suite.chainA.GetContext(), ack.PortId, ack.ChannelId, ack.Sequence, ack.Data) + expAcknowledgements[i] = &ack + } + + req = &types.QueryPacketAcknowledgementsRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + Pagination: &query.PageRequest{ + Key: nil, + Limit: 11, + CountTotal: true, + }, + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ChannelKeeper) + res, err := queryServer.PacketAcknowledgements(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expAcknowledgements, res.Acknowledgements) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryUnreceivedPackets() { + var ( + req *types.QueryUnreceivedPacketsRequest + expSeq = []uint64(nil) + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid port ID", + func() { + req = &types.QueryUnreceivedPacketsRequest{ + PortId: "", + ChannelId: "test-channel-id", + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "invalid channel ID", + func() { + req = &types.QueryUnreceivedPacketsRequest{ + PortId: "test-port-id", + ChannelId: "", + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "invalid seq", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + req = &types.QueryUnreceivedPacketsRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + PacketCommitmentSequences: []uint64{0}, + } + }, + status.Error( + codes.InvalidArgument, + errors.New("packet sequence 0 cannot be 0").Error(), + ), + }, + { + "invalid seq, ordered channel", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetChannelOrdered() + path.Setup() + + req = &types.QueryUnreceivedPacketsRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + PacketCommitmentSequences: []uint64{0}, + } + }, + status.Error( + codes.InvalidArgument, + errors.New("packet sequence 0 cannot be 0").Error(), + ), + }, + { + "channel not found", + func() { + req = &types.QueryUnreceivedPacketsRequest{ + PortId: "invalid-port-id", //nolint:goconst + ChannelId: "invalid-channel-id", + } + }, + status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrChannelNotFound, "port-id: invalid-port-id, channel-id invalid-channel-id").Error(), + ), + }, + { + "basic success empty packet commitments", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + expSeq = []uint64(nil) + req = &types.QueryUnreceivedPacketsRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + PacketCommitmentSequences: []uint64{}, + } + }, + nil, + }, + { + "basic success unreceived packet commitments", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + // no ack exists + + expSeq = []uint64{1} + req = &types.QueryUnreceivedPacketsRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + PacketCommitmentSequences: []uint64{1}, + } + }, + nil, + }, + { + "basic success unreceived packet commitments, nothing to relay", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketReceipt(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, 1) + + expSeq = []uint64(nil) + req = &types.QueryUnreceivedPacketsRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + PacketCommitmentSequences: []uint64{1}, + } + }, + nil, + }, + { + "success multiple unreceived packet commitments", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + expSeq = []uint64(nil) // reset + packetCommitments := []uint64{} + + // set packet receipt for every other sequence + for seq := uint64(1); seq < 10; seq++ { + packetCommitments = append(packetCommitments, seq) + + if seq%2 == 0 { + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketReceipt(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, seq) + } else { + expSeq = append(expSeq, seq) + } + } + + req = &types.QueryUnreceivedPacketsRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + PacketCommitmentSequences: packetCommitments, + } + }, + nil, + }, + { + "basic success empty packet commitments, ordered channel", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetChannelOrdered() + path.Setup() + + expSeq = []uint64(nil) + req = &types.QueryUnreceivedPacketsRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + PacketCommitmentSequences: []uint64{}, + } + }, + nil, + }, + { + "basic success unreceived packet commitments, ordered channel", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetChannelOrdered() + path.Setup() + + // Note: NextSequenceRecv is set to 1 on channel creation. + expSeq = []uint64{1} + req = &types.QueryUnreceivedPacketsRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + PacketCommitmentSequences: []uint64{1}, + } + }, + nil, + }, + { + "basic success multiple unreceived packet commitments, ordered channel", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetChannelOrdered() + path.Setup() + + // Exercise scenario from issue #1532. NextSequenceRecv is 5, packet commitments provided are 2, 7, 9, 10. + // Packet sequence 2 is already received so only sequences 7, 9, 10 should be considered unreceived. + expSeq = []uint64{7, 9, 10} + packetCommitments := []uint64{2, 7, 9, 10} + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetNextSequenceRecv(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, 5) + + req = &types.QueryUnreceivedPacketsRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + PacketCommitmentSequences: packetCommitments, + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ChannelKeeper) + res, err := queryServer.UnreceivedPackets(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expSeq, res.Sequences) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryUnreceivedAcks() { + var ( + req *types.QueryUnreceivedAcksRequest + expSeq = []uint64{} + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid port ID", + func() { + req = &types.QueryUnreceivedAcksRequest{ + PortId: "", + ChannelId: "test-channel-id", + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "invalid channel ID", + func() { + req = &types.QueryUnreceivedAcksRequest{ + PortId: "test-port-id", + ChannelId: "", + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "channel not found", + func() { + req = &types.QueryUnreceivedAcksRequest{ + PortId: "test-port-id", + ChannelId: "test-channel-id", + } + }, + status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrChannelNotFound, "port ID (test-port-id) channel ID (test-channel-id)").Error(), + ), + }, + { + "invalid seq", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + req = &types.QueryUnreceivedAcksRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + PacketAckSequences: []uint64{0}, + } + }, + status.Error( + codes.InvalidArgument, + errors.New("packet sequence 0 cannot be 0").Error(), + ), + }, + { + "basic success unreceived packet acks", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketCommitment(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, 1, []byte("commitment")) + + expSeq = []uint64{1} + req = &types.QueryUnreceivedAcksRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + PacketAckSequences: []uint64{1}, + } + }, + nil, + }, + { + "basic success unreceived packet acknowledgements, nothing to relay", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + expSeq = []uint64(nil) + req = &types.QueryUnreceivedAcksRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + PacketAckSequences: []uint64{1}, + } + }, + nil, + }, + { + "success multiple unreceived packet acknowledgements", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + expSeq = []uint64{} // reset + packetAcks := []uint64{} + + // set packet commitment for every other sequence + for seq := uint64(1); seq < 10; seq++ { + packetAcks = append(packetAcks, seq) + + if seq%2 == 0 { + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketCommitment(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, seq, []byte("commitement")) + expSeq = append(expSeq, seq) + } + } + + req = &types.QueryUnreceivedAcksRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + PacketAckSequences: packetAcks, + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ChannelKeeper) + res, err := queryServer.UnreceivedAcks(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expSeq, res.Sequences) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryNextSequenceReceive() { + var ( + req *types.QueryNextSequenceReceiveRequest + expSeq uint64 + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid port ID", + func() { + req = &types.QueryNextSequenceReceiveRequest{ + PortId: "", + ChannelId: "test-channel-id", + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "invalid channel ID", + func() { + req = &types.QueryNextSequenceReceiveRequest{ + PortId: "test-port-id", + ChannelId: "", + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "channel not found", + func() { + req = &types.QueryNextSequenceReceiveRequest{ + PortId: "test-port-id", + ChannelId: "test-channel-id", + } + }, + status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrChannelNotFound, "port-id: test-port-id, channel-id test-channel-id").Error(), + ), + }, + { + "basic success on unordered channel returns zero", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + expSeq = 0 + req = &types.QueryNextSequenceReceiveRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + } + }, + nil, + }, + { + "basic success on ordered channel returns the set receive sequence", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetChannelOrdered() + path.Setup() + + expSeq = 3 + seq := uint64(3) + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetNextSequenceRecv(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, seq) + + req = &types.QueryNextSequenceReceiveRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ChannelKeeper) + res, err := queryServer.NextSequenceReceive(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expSeq, res.NextSequenceReceive) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryNextSequenceSend() { + var ( + req *types.QueryNextSequenceSendRequest + expSeq uint64 + ) + + testCases := []struct { + msg string + malleate func() + expErr error + }{ + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid port ID", + func() { + req = &types.QueryNextSequenceSendRequest{ + PortId: "", + ChannelId: "test-channel-id", + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "invalid channel ID", + func() { + req = &types.QueryNextSequenceSendRequest{ + PortId: "test-port-id", + ChannelId: "", + } + }, + status.Error( + codes.InvalidArgument, + errorsmod.Wrapf(host.ErrInvalidID, "identifier cannot be blank").Error(), + ), + }, + { + "channel not found", + func() { + req = &types.QueryNextSequenceSendRequest{ + PortId: "test-port-id", + ChannelId: "test-channel-id", + } + }, + status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrSequenceSendNotFound, "port-id: test-port-id, channel-id test-channel-id").Error(), + ), + }, + { + "basic success on unordered channel returns the set send sequence", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + expSeq = 42 + seq := uint64(42) + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetNextSequenceSend(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, seq) + req = &types.QueryNextSequenceSendRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + } + }, + nil, + }, + { + "basic success on ordered channel returns the set send sequence", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetChannelOrdered() + path.Setup() + + expSeq = 3 + seq := uint64(3) + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetNextSequenceSend(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, seq) + + req = &types.QueryNextSequenceSendRequest{ + PortId: path.EndpointA.ChannelConfig.PortID, + ChannelId: path.EndpointA.ChannelID, + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ChannelKeeper) + res, err := queryServer.NextSequenceSend(ctx, req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expSeq, res.NextSequenceSend) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} diff --git a/modules/core/04-channel/keeper/handshake.go b/modules/core/04-channel/keeper/handshake.go new file mode 100644 index 0000000..205bc94 --- /dev/null +++ b/modules/core/04-channel/keeper/handshake.go @@ -0,0 +1,418 @@ +package keeper + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/cosmos-sdk/telemetry" + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// ChanOpenInit is called by a module to initiate a channel opening handshake with +// a module on another chain. The counterparty channel identifier is validated to be +// empty in msg validation. +func (k *Keeper) ChanOpenInit( + ctx sdk.Context, + order types.Order, + connectionHops []string, + portID string, + counterparty types.Counterparty, + version string, +) (string, error) { + // connection hop length checked on msg.ValidateBasic() + connectionEnd, found := k.connectionKeeper.GetConnection(ctx, connectionHops[0]) + if !found { + return "", errorsmod.Wrap(connectiontypes.ErrConnectionNotFound, connectionHops[0]) + } + + getVersions := connectionEnd.Versions + if len(getVersions) != 1 { + return "", errorsmod.Wrapf( + connectiontypes.ErrInvalidVersion, + "single version must be negotiated on connection before opening channel, got: %v", + getVersions, + ) + } + + if !connectiontypes.VerifySupportedFeature(getVersions[0], order.String()) { + return "", errorsmod.Wrapf( + connectiontypes.ErrInvalidVersion, + "connection version %s does not support channel ordering: %s", + getVersions[0], order.String(), + ) + } + + if status := k.clientKeeper.GetClientStatus(ctx, connectionEnd.ClientId); status != exported.Active { + return "", errorsmod.Wrapf(clienttypes.ErrClientNotActive, "client (%s) status is %s", connectionEnd.ClientId, status) + } + + channelID := k.GenerateChannelIdentifier(ctx) + + return channelID, nil +} + +// WriteOpenInitChannel writes a channel which has successfully passed the OpenInit handshake step. +// The channel is set in state and all the associated Send and Recv sequences are set to 1. +// An event is emitted for the handshake step. +func (k *Keeper) WriteOpenInitChannel( + ctx sdk.Context, + portID, + channelID string, + order types.Order, + connectionHops []string, + counterparty types.Counterparty, + version string, +) { + channel := types.NewChannel(types.INIT, order, counterparty, connectionHops, version) + k.SetChannel(ctx, portID, channelID, channel) + + k.SetNextSequenceSend(ctx, portID, channelID, 1) + k.SetNextSequenceRecv(ctx, portID, channelID, 1) + k.SetNextSequenceAck(ctx, portID, channelID, 1) + + k.Logger(ctx).Info("channel state updated", "port-id", portID, "channel-id", channelID, "previous-state", types.UNINITIALIZED, "new-state", types.INIT) + + defer telemetry.IncrCounter(1, "ibc", "channel", "open-init") + + emitChannelOpenInitEvent(ctx, portID, channelID, channel) +} + +// ChanOpenTry is called by a module to accept the first step of a channel opening +// handshake initiated by a module on another chain. +func (k *Keeper) ChanOpenTry( + ctx sdk.Context, + order types.Order, + connectionHops []string, + portID string, + counterparty types.Counterparty, + counterpartyVersion string, + initProof []byte, + proofHeight exported.Height, +) (string, error) { + // connection hops only supports a single connection + if len(connectionHops) != 1 { + return "", errorsmod.Wrapf(types.ErrTooManyConnectionHops, "expected 1, got %d", len(connectionHops)) + } + + // generate a new channel + channelID := k.GenerateChannelIdentifier(ctx) + + connectionEnd, found := k.connectionKeeper.GetConnection(ctx, connectionHops[0]) + if !found { + return "", errorsmod.Wrap(connectiontypes.ErrConnectionNotFound, connectionHops[0]) + } + + if connectionEnd.State != connectiontypes.OPEN { + return "", errorsmod.Wrapf(connectiontypes.ErrInvalidConnectionState, "connection state is not OPEN (got %s)", connectionEnd.State) + } + + getVersions := connectionEnd.Versions + if len(getVersions) != 1 { + return "", errorsmod.Wrapf( + connectiontypes.ErrInvalidVersion, + "single version must be negotiated on connection before opening channel, got: %v", + getVersions, + ) + } + + if !connectiontypes.VerifySupportedFeature(getVersions[0], order.String()) { + return "", errorsmod.Wrapf( + connectiontypes.ErrInvalidVersion, + "connection version %s does not support channel ordering: %s", + getVersions[0], order.String(), + ) + } + + counterpartyHops := []string{connectionEnd.Counterparty.ConnectionId} + + // expectedCounterpaty is the counterparty of the counterparty's channel end + // (i.e self) + expectedCounterparty := types.NewCounterparty(portID, "") + expectedChannel := types.NewChannel( + types.INIT, order, expectedCounterparty, + counterpartyHops, counterpartyVersion, + ) + + if err := k.connectionKeeper.VerifyChannelState( + ctx, connectionEnd, proofHeight, initProof, + counterparty.PortId, counterparty.ChannelId, expectedChannel, + ); err != nil { + return "", err + } + + return channelID, nil +} + +// WriteOpenTryChannel writes a channel which has successfully passed the OpenTry handshake step. +// The channel is set in state. If a previous channel state did not exist, all the Send and Recv +// sequences are set to 1. An event is emitted for the handshake step. +func (k *Keeper) WriteOpenTryChannel( + ctx sdk.Context, + portID, + channelID string, + order types.Order, + connectionHops []string, + counterparty types.Counterparty, + version string, +) { + k.SetNextSequenceSend(ctx, portID, channelID, 1) + k.SetNextSequenceRecv(ctx, portID, channelID, 1) + k.SetNextSequenceAck(ctx, portID, channelID, 1) + + channel := types.NewChannel(types.TRYOPEN, order, counterparty, connectionHops, version) + + k.SetChannel(ctx, portID, channelID, channel) + + k.Logger(ctx).Info("channel state updated", "port-id", portID, "channel-id", channelID, "previous-state", types.UNINITIALIZED, "new-state", types.TRYOPEN) + + defer telemetry.IncrCounter(1, "ibc", "channel", "open-try") + + emitChannelOpenTryEvent(ctx, portID, channelID, channel) +} + +// ChanOpenAck is called by the handshake-originating module to acknowledge the +// acceptance of the initial request by the counterparty module on the other chain. +func (k *Keeper) ChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyVersion, + counterpartyChannelID string, + tryProof []byte, + proofHeight exported.Height, +) error { + channel, found := k.GetChannel(ctx, portID, channelID) + if !found { + return errorsmod.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID) + } + + if channel.State != types.INIT { + return errorsmod.Wrapf(types.ErrInvalidChannelState, "channel state should be INIT (got %s)", channel.State) + } + + connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + if !found { + return errorsmod.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0]) + } + + if connectionEnd.State != connectiontypes.OPEN { + return errorsmod.Wrapf(connectiontypes.ErrInvalidConnectionState, "connection state is not OPEN (got %s)", connectionEnd.State) + } + + counterpartyHops := []string{connectionEnd.Counterparty.ConnectionId} + + // counterparty of the counterparty channel end (i.e self) + expectedCounterparty := types.NewCounterparty(portID, channelID) + expectedChannel := types.NewChannel( + types.TRYOPEN, channel.Ordering, expectedCounterparty, + counterpartyHops, counterpartyVersion, + ) + + return k.connectionKeeper.VerifyChannelState( + ctx, connectionEnd, proofHeight, tryProof, + channel.Counterparty.PortId, counterpartyChannelID, + expectedChannel) +} + +// WriteOpenAckChannel writes an updated channel state for the successful OpenAck handshake step. +// An event is emitted for the handshake step. +func (k *Keeper) WriteOpenAckChannel( + ctx sdk.Context, + portID, + channelID, + counterpartyVersion, + counterpartyChannelID string, +) { + channel, found := k.GetChannel(ctx, portID, channelID) + if !found { + panic(fmt.Errorf("could not find existing channel when updating channel state in successful ChanOpenAck step, channelID: %s, portID: %s", channelID, portID)) + } + + channel.State = types.OPEN + channel.Version = counterpartyVersion + channel.Counterparty.ChannelId = counterpartyChannelID + k.SetChannel(ctx, portID, channelID, channel) + + k.Logger(ctx).Info("channel state updated", "port-id", portID, "channel-id", channelID, "previous-state", types.INIT, "new-state", types.OPEN) + + defer telemetry.IncrCounter(1, "ibc", "channel", "open-ack") + + emitChannelOpenAckEvent(ctx, portID, channelID, channel) +} + +// ChanOpenConfirm is called by the handshake-accepting module to confirm the acknowledgement +// of the handshake-originating module on the other chain and finish the channel opening handshake. +func (k *Keeper) ChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, + ackProof []byte, + proofHeight exported.Height, +) error { + channel, found := k.GetChannel(ctx, portID, channelID) + if !found { + return errorsmod.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID) + } + + if channel.State != types.TRYOPEN { + return errorsmod.Wrapf( + types.ErrInvalidChannelState, + "channel state is not TRYOPEN (got %s)", channel.State, + ) + } + + connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + if !found { + return errorsmod.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0]) + } + + if connectionEnd.State != connectiontypes.OPEN { + return errorsmod.Wrapf(connectiontypes.ErrInvalidConnectionState, "connection state is not OPEN (got %s)", connectionEnd.State) + } + + counterpartyHops := []string{connectionEnd.Counterparty.ConnectionId} + + counterparty := types.NewCounterparty(portID, channelID) + expectedChannel := types.NewChannel( + types.OPEN, channel.Ordering, counterparty, + counterpartyHops, channel.Version, + ) + + // NOTE: If the counterparty has initialized an upgrade in the same block as performing the + // ACK handshake step, this channel end will be incapable of opening. + return k.connectionKeeper.VerifyChannelState( + ctx, connectionEnd, proofHeight, ackProof, + channel.Counterparty.PortId, channel.Counterparty.ChannelId, + expectedChannel) +} + +// WriteOpenConfirmChannel writes an updated channel state for the successful OpenConfirm handshake step. +// An event is emitted for the handshake step. +func (k *Keeper) WriteOpenConfirmChannel( + ctx sdk.Context, + portID, + channelID string, +) { + channel, found := k.GetChannel(ctx, portID, channelID) + if !found { + panic(fmt.Errorf("could not find existing channel when updating channel state in successful ChanOpenConfirm step, channelID: %s, portID: %s", channelID, portID)) + } + + channel.State = types.OPEN + k.SetChannel(ctx, portID, channelID, channel) + k.Logger(ctx).Info("channel state updated", "port-id", portID, "channel-id", channelID, "previous-state", types.TRYOPEN, "new-state", types.OPEN) + + defer telemetry.IncrCounter(1, "ibc", "channel", "open-confirm") + + emitChannelOpenConfirmEvent(ctx, portID, channelID, channel) +} + +// Closing Handshake +// +// This section defines the set of functions required to close a channel handshake +// as defined in https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#closing-handshake +// +// ChanCloseInit is called by either module to close their end of the channel. Once +// closed, channels cannot be reopened. +func (k *Keeper) ChanCloseInit( + ctx sdk.Context, + portID, + channelID string, +) error { + channel, found := k.GetChannel(ctx, portID, channelID) + if !found { + return errorsmod.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID) + } + + if channel.State == types.CLOSED { + return errorsmod.Wrap(types.ErrInvalidChannelState, "channel is already CLOSED") + } + + connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + if !found { + return errorsmod.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0]) + } + + if status := k.clientKeeper.GetClientStatus(ctx, connectionEnd.ClientId); status != exported.Active { + return errorsmod.Wrapf(clienttypes.ErrClientNotActive, "client (%s) status is %s", connectionEnd.ClientId, status) + } + + if connectionEnd.State != connectiontypes.OPEN { + return errorsmod.Wrapf(connectiontypes.ErrInvalidConnectionState, "connection state is not OPEN (got %s)", connectionEnd.State) + } + + k.Logger(ctx).Info("channel state updated", "port-id", portID, "channel-id", channelID, "previous-state", channel.State, "new-state", types.CLOSED) + + defer telemetry.IncrCounter(1, "ibc", "channel", "close-init") + + channel.State = types.CLOSED + k.SetChannel(ctx, portID, channelID, channel) + + emitChannelCloseInitEvent(ctx, portID, channelID, channel) + + return nil +} + +// ChanCloseConfirm is called by the counterparty module to close their end of the +// channel, since the other end has been closed. +func (k *Keeper) ChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, + initProof []byte, + proofHeight exported.Height, +) error { + channel, found := k.GetChannel(ctx, portID, channelID) + if !found { + return errorsmod.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", portID, channelID) + } + + if channel.State == types.CLOSED { + return errorsmod.Wrap(types.ErrInvalidChannelState, "channel is already CLOSED") + } + + connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + if !found { + return errorsmod.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0]) + } + + if connectionEnd.State != connectiontypes.OPEN { + return errorsmod.Wrapf(connectiontypes.ErrInvalidConnectionState, "connection state is not OPEN (got %s)", connectionEnd.State) + } + + counterpartyHops := []string{connectionEnd.Counterparty.ConnectionId} + + counterparty := types.NewCounterparty(portID, channelID) + expectedChannel := types.Channel{ + State: types.CLOSED, + Ordering: channel.Ordering, + Counterparty: counterparty, + ConnectionHops: counterpartyHops, + Version: channel.Version, + } + + if err := k.connectionKeeper.VerifyChannelState( + ctx, connectionEnd, proofHeight, initProof, + channel.Counterparty.PortId, channel.Counterparty.ChannelId, + expectedChannel, + ); err != nil { + return err + } + + k.Logger(ctx).Info("channel state updated", "port-id", portID, "channel-id", channelID, "previous-state", channel.State, "new-state", types.CLOSED) + + defer telemetry.IncrCounter(1, "ibc", "channel", "close-confirm") + + channel.State = types.CLOSED + k.SetChannel(ctx, portID, channelID, channel) + + emitChannelCloseConfirmEvent(ctx, portID, channelID, channel) + + return nil +} diff --git a/modules/core/04-channel/keeper/handshake_test.go b/modules/core/04-channel/keeper/handshake_test.go new file mode 100644 index 0000000..543efdc --- /dev/null +++ b/modules/core/04-channel/keeper/handshake_test.go @@ -0,0 +1,644 @@ +package keeper_test + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +type testCase = struct { + msg string + malleate func() + expErr error +} + +// TestChanOpenInit tests the OpenInit handshake call for channels. It uses message passing +// to enter into the appropriate state and then calls ChanOpenInit directly. The channel is +// being created on chainA. +func (suite *KeeperTestSuite) TestChanOpenInit() { + var ( + path *ibctesting.Path + features []string + expErrorMsgSubstring string + ) + + testCases := []testCase{ + {"success", func() { + path.SetupConnections() + features = []string{"ORDER_ORDERED", "ORDER_UNORDERED"} + }, nil}, + {"connection doesn't exist", func() { + // any non-empty values + path.EndpointA.ConnectionID = "connection-0" + path.EndpointB.ConnectionID = "connection-0" + }, connectiontypes.ErrConnectionNotFound}, + {"connection version not negotiated", func() { + path.SetupConnections() + + // modify connA versions + path.EndpointA.UpdateConnection(func(c *connectiontypes.ConnectionEnd) { + c.Versions = append(c.Versions, connectiontypes.NewVersion("2", []string{"ORDER_ORDERED", "ORDER_UNORDERED"})) + }) + + features = []string{"ORDER_ORDERED", "ORDER_UNORDERED"} + }, connectiontypes.ErrInvalidVersion}, + {"connection does not support ORDERED channels", func() { + path.SetupConnections() + + // modify connA versions to only support UNORDERED channels + path.EndpointA.UpdateConnection(func(c *connectiontypes.ConnectionEnd) { + c.Versions = []*connectiontypes.Version{connectiontypes.NewVersion("1", []string{"ORDER_UNORDERED"})} + }) + + // NOTE: Opening UNORDERED channels is still expected to pass but ORDERED channels should fail + features = []string{"ORDER_UNORDERED"} + }, nil}, + { + msg: "unauthorized client", + expErr: clienttypes.ErrClientNotActive, + malleate: func() { + expErrorMsgSubstring = "status is Unauthorized" + path.SetupConnections() + + // remove client from allowed list + params := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetParams(suite.chainA.GetContext()) + params.AllowedClients = []string{} + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetParams(suite.chainA.GetContext(), params) + }, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + // run test for all types of ordering + for _, order := range []types.Order{types.UNORDERED, types.ORDERED} { + suite.SetupTest() // reset + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.EndpointA.ChannelConfig.Order = order + path.EndpointB.ChannelConfig.Order = order + expErrorMsgSubstring = "" + + tc.malleate() + + counterparty := types.NewCounterparty(ibctesting.MockPort, ibctesting.FirstChannelID) + + channelID, err := suite.chainA.App.GetIBCKeeper().ChannelKeeper.ChanOpenInit( + suite.chainA.GetContext(), path.EndpointA.ChannelConfig.Order, []string{path.EndpointA.ConnectionID}, + path.EndpointA.ChannelConfig.PortID, counterparty, path.EndpointA.ChannelConfig.Version, + ) + + // check if order is supported by channel to determine expected behaviour + orderSupported := false + for _, f := range features { + if f == order.String() { + orderSupported = true + } + } + + // Testcase must have expectedPass = true AND channel order supported before + // asserting the channel handshake initiation succeeded + if (tc.expErr == nil) && orderSupported { + suite.Require().NoError(err) + suite.Require().Equal(types.FormatChannelIdentifier(0), channelID) + } else { + suite.Require().Error(err) + suite.Require().Contains(err.Error(), expErrorMsgSubstring) + suite.Require().Equal("", channelID) + } + } + }) + } +} + +// TestChanOpenTry tests the OpenTry handshake call for channels. It uses message passing +// to enter into the appropriate state and then calls ChanOpenTry directly. The channel +// is being created on chainB. +func (suite *KeeperTestSuite) TestChanOpenTry() { + var ( + path *ibctesting.Path + heightDiff uint64 + ) + + testCases := []testCase{ + {"success", func() { + path.SetupConnections() + path.SetChannelOrdered() + err := path.EndpointA.ChanOpenInit() + suite.Require().NoError(err) + }, nil}, + {"connection doesn't exist", func() { + path.EndpointA.ConnectionID = ibctesting.FirstConnectionID + path.EndpointB.ConnectionID = ibctesting.FirstConnectionID + }, connectiontypes.ErrConnectionNotFound}, + {"connection is not OPEN", func() { + path.SetupClients() + + err := path.EndpointB.ConnOpenInit() + suite.Require().NoError(err) + }, connectiontypes.ErrInvalidConnectionState}, + {"consensus state not found", func() { + path.SetupConnections() + path.SetChannelOrdered() + err := path.EndpointA.ChanOpenInit() + suite.Require().NoError(err) + + heightDiff = 3 // consensus state doesn't exist at this height + }, errorsmod.Wrap(ibcerrors.ErrInvalidHeight, "")}, + {"channel verification failed", func() { + // not creating a channel on chainA will result in an invalid proof of existence + path.SetupConnections() + }, commitmenttypes.ErrInvalidProof}, + {"connection version not negotiated", func() { + path.SetupConnections() + path.SetChannelOrdered() + err := path.EndpointA.ChanOpenInit() + suite.Require().NoError(err) + + // modify connB versions + path.EndpointB.UpdateConnection(func(c *connectiontypes.ConnectionEnd) { + c.Versions = append(c.Versions, connectiontypes.NewVersion("2", []string{"ORDER_ORDERED", "ORDER_UNORDERED"})) + }) + }, connectiontypes.ErrInvalidVersion}, + {"connection does not support ORDERED channels", func() { + path.SetupConnections() + path.SetChannelOrdered() + err := path.EndpointA.ChanOpenInit() + suite.Require().NoError(err) + + // modify connB versions to only support UNORDERED channels + path.EndpointB.UpdateConnection(func(c *connectiontypes.ConnectionEnd) { + c.Versions = []*connectiontypes.Version{connectiontypes.NewVersion("1", []string{"ORDER_UNORDERED"})} + }) + }, connectiontypes.ErrInvalidVersion}, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + heightDiff = 0 // must be explicitly changed in malleate + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + tc.malleate() + + if path.EndpointB.ClientID != "" { + // ensure client is up to date + err := path.EndpointB.UpdateClient() + suite.Require().NoError(err) + } + + counterparty := types.NewCounterparty(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + + channelKey := host.ChannelKey(counterparty.PortId, counterparty.ChannelId) + proof, proofHeight := suite.chainA.QueryProof(channelKey) + + channelID, err := suite.chainB.App.GetIBCKeeper().ChannelKeeper.ChanOpenTry( + suite.chainB.GetContext(), types.ORDERED, []string{path.EndpointB.ConnectionID}, + path.EndpointB.ChannelConfig.PortID, counterparty, path.EndpointA.ChannelConfig.Version, + proof, malleateHeight(proofHeight, heightDiff), + ) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotEmpty(channelID) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +// TestChanOpenAck tests the OpenAck handshake call for channels. It uses message passing +// to enter into the appropriate state and then calls ChanOpenAck directly. The handshake +// call is occurring on chainA. +func (suite *KeeperTestSuite) TestChanOpenAck() { + var ( + path *ibctesting.Path + counterpartyChannelID string + heightDiff uint64 + ) + + testCases := []testCase{ + {"success", func() { + path.SetupConnections() + path.SetChannelOrdered() + err := path.EndpointA.ChanOpenInit() + suite.Require().NoError(err) + + err = path.EndpointB.ChanOpenTry() + suite.Require().NoError(err) + }, nil}, + {"success with empty stored counterparty channel ID", func() { + path.SetupConnections() + path.SetChannelOrdered() + + err := path.EndpointA.ChanOpenInit() + suite.Require().NoError(err) + + err = path.EndpointB.ChanOpenTry() + suite.Require().NoError(err) + + // set the channel's counterparty channel identifier to empty string + channel := path.EndpointA.GetChannel() + channel.Counterparty.ChannelId = "" + + // use a different channel identifier + counterpartyChannelID = path.EndpointB.ChannelID + + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, channel) + }, nil}, + {"channel doesn't exist", func() {}, errorsmod.Wrap(types.ErrChannelNotFound, "")}, + {"channel state is not INIT", func() { + // create fully open channels on both chains + path.Setup() + }, types.ErrInvalidChannelState}, + {"connection not found", func() { + path.SetupConnections() + path.SetChannelOrdered() + err := path.EndpointA.ChanOpenInit() + suite.Require().NoError(err) + + err = path.EndpointB.ChanOpenTry() + suite.Require().NoError(err) + + // set the channel's connection hops to wrong connection ID + channel := path.EndpointA.GetChannel() + channel.ConnectionHops[0] = doesnotexist + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, channel) + }, errorsmod.Wrap(connectiontypes.ErrConnectionNotFound, "")}, + {"connection is not OPEN", func() { + path.SetupClients() + + err := path.EndpointA.ConnOpenInit() + suite.Require().NoError(err) + + // create channel in init + path.SetChannelOrdered() + + err = path.EndpointA.ChanOpenInit() + suite.Require().NoError(err) + }, connectiontypes.ErrInvalidConnectionState}, + {"consensus state not found", func() { + path.SetupConnections() + path.SetChannelOrdered() + + err := path.EndpointA.ChanOpenInit() + suite.Require().NoError(err) + + err = path.EndpointB.ChanOpenTry() + suite.Require().NoError(err) + + heightDiff = 3 // consensus state doesn't exist at this height + }, ibcerrors.ErrInvalidHeight}, + {"invalid counterparty channel identifier", func() { + path.SetupConnections() + path.SetChannelOrdered() + + err := path.EndpointA.ChanOpenInit() + suite.Require().NoError(err) + + err = path.EndpointB.ChanOpenTry() + suite.Require().NoError(err) + + counterpartyChannelID = "otheridentifier" + }, commitmenttypes.ErrInvalidProof}, + {"channel verification failed", func() { + // chainB is INIT, chainA in TRYOPEN + path.SetupConnections() + path.SetChannelOrdered() + + err := path.EndpointB.ChanOpenInit() + suite.Require().NoError(err) + + err = path.EndpointA.ChanOpenTry() + suite.Require().NoError(err) + }, types.ErrInvalidChannelState}, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + counterpartyChannelID = "" // must be explicitly changed in malleate + heightDiff = 0 // must be explicitly changed + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + tc.malleate() + + if counterpartyChannelID == "" { + counterpartyChannelID = path.EndpointB.ChannelID + } + + if path.EndpointA.ClientID != "" { + // ensure client is up to date + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + } + + channelKey := host.ChannelKey(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + proof, proofHeight := suite.chainB.QueryProof(channelKey) + + err := suite.chainA.App.GetIBCKeeper().ChannelKeeper.ChanOpenAck( + suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.Version, counterpartyChannelID, + proof, malleateHeight(proofHeight, heightDiff), + ) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +// TestChanOpenConfirm tests the OpenAck handshake call for channels. It uses message passing +// to enter into the appropriate state and then calls ChanOpenConfirm directly. The handshake +// call is occurring on chainB. +func (suite *KeeperTestSuite) TestChanOpenConfirm() { + var ( + path *ibctesting.Path + heightDiff uint64 + ) + testCases := []testCase{ + {"success", func() { + path.SetupConnections() + path.SetChannelOrdered() + + err := path.EndpointA.ChanOpenInit() + suite.Require().NoError(err) + + err = path.EndpointB.ChanOpenTry() + suite.Require().NoError(err) + + err = path.EndpointA.ChanOpenAck() + suite.Require().NoError(err) + }, nil}, + {"channel doesn't exist", func() {}, types.ErrChannelNotFound}, + {"channel state is not TRYOPEN", func() { + // create fully open channels on both chains + path.Setup() + }, types.ErrInvalidChannelState}, + {"connection not found", func() { + path.SetupConnections() + path.SetChannelOrdered() + + err := path.EndpointA.ChanOpenInit() + suite.Require().NoError(err) + + err = path.EndpointB.ChanOpenTry() + suite.Require().NoError(err) + + err = path.EndpointA.ChanOpenAck() + suite.Require().NoError(err) + + // set the channel's connection hops to wrong connection ID + channel := path.EndpointB.GetChannel() + channel.ConnectionHops[0] = doesnotexist + suite.chainB.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, channel) + }, errorsmod.Wrap(connectiontypes.ErrConnectionNotFound, "")}, + {"connection is not OPEN", func() { + path.SetupClients() + + err := path.EndpointB.ConnOpenInit() + suite.Require().NoError(err) + }, types.ErrChannelNotFound}, + {"consensus state not found", func() { + path.SetupConnections() + path.SetChannelOrdered() + + err := path.EndpointA.ChanOpenInit() + suite.Require().NoError(err) + + err = path.EndpointB.ChanOpenTry() + suite.Require().NoError(err) + + err = path.EndpointA.ChanOpenAck() + suite.Require().NoError(err) + + heightDiff = 3 + }, ibcerrors.ErrInvalidHeight}, + {"channel verification failed", func() { + // chainA is INIT, chainB in TRYOPEN + path.SetupConnections() + path.SetChannelOrdered() + + err := path.EndpointA.ChanOpenInit() + suite.Require().NoError(err) + + err = path.EndpointB.ChanOpenTry() + suite.Require().NoError(err) + }, commitmenttypes.ErrInvalidProof}, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + heightDiff = 0 // must be explicitly changed + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + tc.malleate() + + if path.EndpointB.ClientID != "" { + // ensure client is up to date + err := path.EndpointB.UpdateClient() + suite.Require().NoError(err) + + } + + channelKey := host.ChannelKey(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + proof, proofHeight := suite.chainA.QueryProof(channelKey) + + err := suite.chainB.App.GetIBCKeeper().ChannelKeeper.ChanOpenConfirm( + suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, + proof, malleateHeight(proofHeight, heightDiff), + ) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +// TestChanCloseInit tests the initial closing of a handshake on chainA by calling +// ChanCloseInit. Both chains will use message passing to setup OPEN channels. +func (suite *KeeperTestSuite) TestChanCloseInit() { + var ( + path *ibctesting.Path + expErrorMsgSubstring string + ) + + testCases := []testCase{ + {"success", func() { + path.Setup() + }, nil}, + {"channel doesn't exist", func() { + // any non-nil values work for connections + path.EndpointA.ConnectionID = ibctesting.FirstConnectionID + path.EndpointB.ConnectionID = ibctesting.FirstConnectionID + + path.EndpointA.ChannelID = ibctesting.FirstChannelID + path.EndpointB.ChannelID = ibctesting.FirstChannelID + }, types.ErrChannelNotFound}, + {"channel state is CLOSED", func() { + path.Setup() + + // close channel + path.EndpointA.UpdateChannel(func(channel *types.Channel) { channel.State = types.CLOSED }) + }, types.ErrInvalidChannelState}, + {"connection not found", func() { + path.Setup() + + // set the channel's connection hops to wrong connection ID + channel := path.EndpointA.GetChannel() + channel.ConnectionHops[0] = doesnotexist + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, channel) + }, errorsmod.Wrap(connectiontypes.ErrConnectionNotFound, "")}, + {"connection is not OPEN", func() { + path.SetupClients() + + err := path.EndpointA.ConnOpenInit() + suite.Require().NoError(err) + + // create channel in init + path.SetChannelOrdered() + err = path.EndpointA.ChanOpenInit() + suite.Require().NoError(err) + }, connectiontypes.ErrInvalidConnectionState}, + { + msg: "unauthorized client", + expErr: clienttypes.ErrClientNotActive, + malleate: func() { + path.Setup() + + // remove client from allowed list + params := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetParams(suite.chainA.GetContext()) + params.AllowedClients = []string{} + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetParams(suite.chainA.GetContext(), params) + expErrorMsgSubstring = "status is Unauthorized" + }, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + path = ibctesting.NewPath(suite.chainA, suite.chainB) + expErrorMsgSubstring = "" + + tc.malleate() + + err := suite.chainA.App.GetIBCKeeper().ChannelKeeper.ChanCloseInit( + suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, + ) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().Contains(err.Error(), expErrorMsgSubstring) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +// TestChanCloseConfirm tests the confirming closing channel ends by calling ChanCloseConfirm +// on chainB. Both chains will use message passing to setup OPEN channels. ChanCloseInit is +// bypassed on chainA by setting the channel state in the ChannelKeeper. +func (suite *KeeperTestSuite) TestChanCloseConfirm() { + var ( + path *ibctesting.Path + heightDiff uint64 + ) + + testCases := []testCase{ + {"success", func() { + path.Setup() + path.EndpointA.UpdateChannel(func(channel *types.Channel) { channel.State = types.CLOSED }) + }, nil}, + {"channel doesn't exist", func() { + // any non-nil values work for connections + path.EndpointA.ChannelID = ibctesting.FirstChannelID + path.EndpointB.ChannelID = ibctesting.FirstChannelID + }, errorsmod.Wrap(types.ErrChannelNotFound, "")}, + {"channel state is CLOSED", func() { + path.Setup() + + path.EndpointB.UpdateChannel(func(channel *types.Channel) { channel.State = types.CLOSED }) + }, types.ErrInvalidChannelState}, + {"connection not found", func() { + path.Setup() + + // set the channel's connection hops to wrong connection ID + channel := path.EndpointB.GetChannel() + channel.ConnectionHops[0] = doesnotexist + suite.chainB.App.GetIBCKeeper().ChannelKeeper.SetChannel(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, channel) + }, errorsmod.Wrap(connectiontypes.ErrConnectionNotFound, "")}, + {"connection is not OPEN", func() { + path.SetupClients() + + err := path.EndpointB.ConnOpenInit() + suite.Require().NoError(err) + + // create channel in init + path.SetChannelOrdered() + err = path.EndpointB.ChanOpenInit() + suite.Require().NoError(err) + }, connectiontypes.ErrInvalidConnectionState}, + {"consensus state not found", func() { + path.Setup() + + path.EndpointA.UpdateChannel(func(channel *types.Channel) { channel.State = types.CLOSED }) + + heightDiff = 3 + }, ibcerrors.ErrInvalidHeight}, + {"channel verification failed", func() { + // channel not closed + path.Setup() + }, ibcerrors.ErrInvalidHeight}, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + heightDiff = 0 // must explicitly be changed + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + tc.malleate() + + channelKey := host.ChannelKey(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + proof, proofHeight := suite.chainA.QueryProof(channelKey) + + ctx := suite.chainB.GetContext() + err := suite.chainB.App.GetIBCKeeper().ChannelKeeper.ChanCloseConfirm( + ctx, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, + proof, malleateHeight(proofHeight, heightDiff), + ) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func malleateHeight(height exported.Height, diff uint64) exported.Height { + return clienttypes.NewHeight(height.GetRevisionNumber(), height.GetRevisionHeight()+diff) +} diff --git a/modules/core/04-channel/keeper/keeper.go b/modules/core/04-channel/keeper/keeper.go new file mode 100644 index 0000000..8b582c7 --- /dev/null +++ b/modules/core/04-channel/keeper/keeper.go @@ -0,0 +1,586 @@ +package keeper + +import ( + "errors" + "strconv" + "strings" + + db "github.com/cosmos/cosmos-db" + + corestore "cosmossdk.io/core/store" + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/log" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var _ porttypes.ICS4Wrapper = (*Keeper)(nil) + +// Keeper defines the IBC channel keeper +type Keeper struct { + // implements gRPC QueryServer interface + types.QueryServer + + storeService corestore.KVStoreService + cdc codec.BinaryCodec + clientKeeper types.ClientKeeper + connectionKeeper types.ConnectionKeeper +} + +// NewKeeper creates a new IBC channel Keeper instance +func NewKeeper( + cdc codec.BinaryCodec, + storeService corestore.KVStoreService, + clientKeeper types.ClientKeeper, + connectionKeeper types.ConnectionKeeper, +) *Keeper { + return &Keeper{ + storeService: storeService, + cdc: cdc, + clientKeeper: clientKeeper, + connectionKeeper: connectionKeeper, + } +} + +// Logger returns a module-specific logger. +func (Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", "x/"+exported.ModuleName+"/"+types.SubModuleName) +} + +// GenerateChannelIdentifier returns the next channel identifier. +func (k *Keeper) GenerateChannelIdentifier(ctx sdk.Context) string { + nextChannelSeq := k.GetNextChannelSequence(ctx) + channelID := types.FormatChannelIdentifier(nextChannelSeq) + + nextChannelSeq++ + k.SetNextChannelSequence(ctx, nextChannelSeq) + return channelID +} + +// HasChannel true if the channel with the given identifiers exists in state. +func (k *Keeper) HasChannel(ctx sdk.Context, portID, channelID string) bool { + store := k.storeService.OpenKVStore(ctx) + has, err := store.Has(host.ChannelKey(portID, channelID)) + if err != nil { + panic(err) + } + return has +} + +// GetChannel returns a channel with a particular identifier binded to a specific port +func (k *Keeper) GetChannel(ctx sdk.Context, portID, channelID string) (types.Channel, bool) { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(host.ChannelKey(portID, channelID)) + if err != nil { + panic(err) + } + if len(bz) == 0 { + return types.Channel{}, false + } + + var channel types.Channel + k.cdc.MustUnmarshal(bz, &channel) + return channel, true +} + +// SetChannel sets a channel to the store +func (k *Keeper) SetChannel(ctx sdk.Context, portID, channelID string, channel types.Channel) { + store := k.storeService.OpenKVStore(ctx) + bz := k.cdc.MustMarshal(&channel) + if err := store.Set(host.ChannelKey(portID, channelID), bz); err != nil { + panic(err) + } +} + +// GetAppVersion gets the version for the specified channel. +func (k *Keeper) GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) { + channel, found := k.GetChannel(ctx, portID, channelID) + if !found { + return "", false + } + + return channel.Version, true +} + +// GetNextChannelSequence gets the next channel sequence from the store. +func (k *Keeper) GetNextChannelSequence(ctx sdk.Context) uint64 { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get([]byte(types.KeyNextChannelSequence)) + if err != nil { + panic(err) + } + if len(bz) == 0 { + panic(errors.New("next channel sequence is nil")) + } + + return sdk.BigEndianToUint64(bz) +} + +// SetNextChannelSequence sets the next channel sequence to the store. +func (k *Keeper) SetNextChannelSequence(ctx sdk.Context, sequence uint64) { + store := k.storeService.OpenKVStore(ctx) + bz := sdk.Uint64ToBigEndian(sequence) + if err := store.Set([]byte(types.KeyNextChannelSequence), bz); err != nil { + panic(err) + } +} + +// GetNextSequenceSend gets a channel's next send sequence from the store +func (k *Keeper) GetNextSequenceSend(ctx sdk.Context, portID, channelID string) (uint64, bool) { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(host.NextSequenceSendKey(portID, channelID)) + if err != nil { + panic(err) + } + if len(bz) == 0 { + return 0, false + } + + return sdk.BigEndianToUint64(bz), true +} + +// SetNextSequenceSend sets a channel's next send sequence to the store +func (k *Keeper) SetNextSequenceSend(ctx sdk.Context, portID, channelID string, sequence uint64) { + store := k.storeService.OpenKVStore(ctx) + bz := sdk.Uint64ToBigEndian(sequence) + if err := store.Set(host.NextSequenceSendKey(portID, channelID), bz); err != nil { + panic(err) + } +} + +// GetNextSequenceRecv gets a channel's next receive sequence from the store +func (k *Keeper) GetNextSequenceRecv(ctx sdk.Context, portID, channelID string) (uint64, bool) { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(host.NextSequenceRecvKey(portID, channelID)) + if err != nil { + panic(err) + } + if len(bz) == 0 { + return 0, false + } + + return sdk.BigEndianToUint64(bz), true +} + +// SetNextSequenceRecv sets a channel's next receive sequence to the store +func (k *Keeper) SetNextSequenceRecv(ctx sdk.Context, portID, channelID string, sequence uint64) { + store := k.storeService.OpenKVStore(ctx) + bz := sdk.Uint64ToBigEndian(sequence) + if err := store.Set(host.NextSequenceRecvKey(portID, channelID), bz); err != nil { + panic(err) + } +} + +// GetNextSequenceAck gets a channel's next ack sequence from the store +func (k *Keeper) GetNextSequenceAck(ctx sdk.Context, portID, channelID string) (uint64, bool) { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(host.NextSequenceAckKey(portID, channelID)) + if err != nil { + panic(err) + } + + if len(bz) == 0 { + return 0, false + } + + return sdk.BigEndianToUint64(bz), true +} + +// SetNextSequenceAck sets a channel's next ack sequence to the store +func (k *Keeper) SetNextSequenceAck(ctx sdk.Context, portID, channelID string, sequence uint64) { + store := k.storeService.OpenKVStore(ctx) + bz := sdk.Uint64ToBigEndian(sequence) + if err := store.Set(host.NextSequenceAckKey(portID, channelID), bz); err != nil { + panic(err) + } +} + +// GetPacketReceipt gets a packet receipt from the store +func (k *Keeper) GetPacketReceipt(ctx sdk.Context, portID, channelID string, sequence uint64) (string, bool) { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(host.PacketReceiptKey(portID, channelID, sequence)) + if err != nil { + panic(err) + } + + if len(bz) == 0 { + return "", false + } + + return string(bz), true +} + +// SetPacketReceipt sets an empty packet receipt to the store +func (k *Keeper) SetPacketReceipt(ctx sdk.Context, portID, channelID string, sequence uint64) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Set(host.PacketReceiptKey(portID, channelID, sequence), []byte{byte(1)}); err != nil { + panic(err) + } +} + +// GetPacketCommitment gets the packet commitment hash from the store +func (k *Keeper) GetPacketCommitment(ctx sdk.Context, portID, channelID string, sequence uint64) []byte { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(host.PacketCommitmentKey(portID, channelID, sequence)) + if err != nil { + panic(err) + } + + return bz +} + +// HasPacketCommitment returns true if the packet commitment exists +func (k *Keeper) HasPacketCommitment(ctx sdk.Context, portID, channelID string, sequence uint64) bool { + store := k.storeService.OpenKVStore(ctx) + has, err := store.Has(host.PacketCommitmentKey(portID, channelID, sequence)) + if err != nil { + panic(err) + } + return has +} + +// SetPacketCommitment sets the packet commitment hash to the store +func (k *Keeper) SetPacketCommitment(ctx sdk.Context, portID, channelID string, sequence uint64, commitmentHash []byte) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Set(host.PacketCommitmentKey(portID, channelID, sequence), commitmentHash); err != nil { + panic(err) + } +} + +func (k *Keeper) deletePacketCommitment(ctx sdk.Context, portID, channelID string, sequence uint64) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Delete(host.PacketCommitmentKey(portID, channelID, sequence)); err != nil { + panic(err) + } +} + +// SetPacketAcknowledgement sets the packet ack hash to the store +func (k *Keeper) SetPacketAcknowledgement(ctx sdk.Context, portID, channelID string, sequence uint64, ackHash []byte) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Set(host.PacketAcknowledgementKey(portID, channelID, sequence), ackHash); err != nil { + panic(err) + } +} + +// GetPacketAcknowledgement gets the packet ack hash from the store +func (k *Keeper) GetPacketAcknowledgement(ctx sdk.Context, portID, channelID string, sequence uint64) ([]byte, bool) { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(host.PacketAcknowledgementKey(portID, channelID, sequence)) + if err != nil { + panic(err) + } + + if len(bz) == 0 { + return nil, false + } + + return bz, true +} + +// HasPacketAcknowledgement check if the packet ack hash is already on the store +func (k *Keeper) HasPacketAcknowledgement(ctx sdk.Context, portID, channelID string, sequence uint64) bool { + store := k.storeService.OpenKVStore(ctx) + has, err := store.Has(host.PacketAcknowledgementKey(portID, channelID, sequence)) + if err != nil { + panic(err) + } + return has +} + +// IteratePacketSequence provides an iterator over all send, receive or ack sequences. +// For each sequence, cb will be called. If the cb returns true, the iterator +// will close and stop. +func (k *Keeper) IteratePacketSequence(ctx sdk.Context, iterator db.Iterator, cb func(portID, channelID string, sequence uint64) bool) { + defer sdk.LogDeferred(k.Logger(ctx), func() error { return iterator.Close() }) + for ; iterator.Valid(); iterator.Next() { + portID, channelID, err := host.ParseChannelPath(string(iterator.Key())) + if err != nil { + // return if the key is not a channel key + return + } + + sequence := sdk.BigEndianToUint64(iterator.Value()) + + if cb(portID, channelID, sequence) { + break + } + } +} + +// GetAllPacketSendSeqs returns all stored next send sequences. +func (k *Keeper) GetAllPacketSendSeqs(ctx sdk.Context) (seqs []types.PacketSequence) { + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + iterator := storetypes.KVStorePrefixIterator(store, []byte(host.KeyNextSeqSendPrefix)) + k.IteratePacketSequence(ctx, iterator, func(portID, channelID string, nextSendSeq uint64) bool { + ps := types.NewPacketSequence(portID, channelID, nextSendSeq) + seqs = append(seqs, ps) + return false + }) + return seqs +} + +// GetAllPacketRecvSeqs returns all stored next recv sequences. +func (k *Keeper) GetAllPacketRecvSeqs(ctx sdk.Context) (seqs []types.PacketSequence) { + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + iterator := storetypes.KVStorePrefixIterator(store, []byte(host.KeyNextSeqRecvPrefix)) + k.IteratePacketSequence(ctx, iterator, func(portID, channelID string, nextRecvSeq uint64) bool { + ps := types.NewPacketSequence(portID, channelID, nextRecvSeq) + seqs = append(seqs, ps) + return false + }) + return seqs +} + +// GetAllPacketAckSeqs returns all stored next acknowledgements sequences. +func (k *Keeper) GetAllPacketAckSeqs(ctx sdk.Context) (seqs []types.PacketSequence) { + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + iterator := storetypes.KVStorePrefixIterator(store, []byte(host.KeyNextSeqAckPrefix)) + k.IteratePacketSequence(ctx, iterator, func(portID, channelID string, nextAckSeq uint64) bool { + ps := types.NewPacketSequence(portID, channelID, nextAckSeq) + seqs = append(seqs, ps) + return false + }) + return seqs +} + +// IteratePacketCommitment provides an iterator over all PacketCommitment objects. For each +// packet commitment, cb will be called. If the cb returns true, the iterator will close +// and stop. +func (k *Keeper) IteratePacketCommitment(ctx sdk.Context, cb func(portID, channelID string, sequence uint64, hash []byte) bool) { + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + iterator := storetypes.KVStorePrefixIterator(store, []byte(host.KeyPacketCommitmentPrefix)) + k.iterateHashes(ctx, iterator, cb) +} + +// GetAllPacketCommitments returns all stored PacketCommitments objects. +func (k *Keeper) GetAllPacketCommitments(ctx sdk.Context) (commitments []types.PacketState) { + k.IteratePacketCommitment(ctx, func(portID, channelID string, sequence uint64, hash []byte) bool { + pc := types.NewPacketState(portID, channelID, sequence, hash) + commitments = append(commitments, pc) + return false + }) + return commitments +} + +// IteratePacketCommitmentAtChannel provides an iterator over all PacketCommitment objects +// at a specified channel. For each packet commitment, cb will be called. If the cb returns +// true, the iterator will close and stop. +func (k *Keeper) IteratePacketCommitmentAtChannel(ctx sdk.Context, portID, channelID string, cb func(_, _ string, sequence uint64, hash []byte) bool) { + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + iterator := storetypes.KVStorePrefixIterator(store, host.PacketCommitmentPrefixKey(portID, channelID)) + k.iterateHashes(ctx, iterator, cb) +} + +// GetAllPacketCommitmentsAtChannel returns all stored PacketCommitments objects for a specified +// port ID and channel ID. +func (k *Keeper) GetAllPacketCommitmentsAtChannel(ctx sdk.Context, portID, channelID string) (commitments []types.PacketState) { + k.IteratePacketCommitmentAtChannel(ctx, portID, channelID, func(_, _ string, sequence uint64, hash []byte) bool { + pc := types.NewPacketState(portID, channelID, sequence, hash) + commitments = append(commitments, pc) + return false + }) + return commitments +} + +// IteratePacketReceipt provides an iterator over all PacketReceipt objects. For each +// receipt, cb will be called. If the cb returns true, the iterator will close +// and stop. +func (k *Keeper) IteratePacketReceipt(ctx sdk.Context, cb func(portID, channelID string, sequence uint64, receipt []byte) bool) { + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + iterator := storetypes.KVStorePrefixIterator(store, []byte(host.KeyPacketReceiptPrefix)) + k.iterateHashes(ctx, iterator, cb) +} + +// GetAllPacketReceipts returns all stored PacketReceipt objects. +func (k *Keeper) GetAllPacketReceipts(ctx sdk.Context) (receipts []types.PacketState) { + k.IteratePacketReceipt(ctx, func(portID, channelID string, sequence uint64, receipt []byte) bool { + packetReceipt := types.NewPacketState(portID, channelID, sequence, receipt) + receipts = append(receipts, packetReceipt) + return false + }) + return receipts +} + +// IteratePacketAcknowledgement provides an iterator over all PacketAcknowledgement objects. For each +// acknowledgement, cb will be called. If the cb returns true, the iterator will close +// and stop. +func (k *Keeper) IteratePacketAcknowledgement(ctx sdk.Context, cb func(portID, channelID string, sequence uint64, hash []byte) bool) { + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + iterator := storetypes.KVStorePrefixIterator(store, []byte(host.KeyPacketAckPrefix)) + k.iterateHashes(ctx, iterator, cb) +} + +// GetAllPacketAcks returns all stored PacketAcknowledgements objects. +func (k *Keeper) GetAllPacketAcks(ctx sdk.Context) (acks []types.PacketState) { + k.IteratePacketAcknowledgement(ctx, func(portID, channelID string, sequence uint64, ack []byte) bool { + packetAck := types.NewPacketState(portID, channelID, sequence, ack) + acks = append(acks, packetAck) + return false + }) + return acks +} + +// IterateChannels provides an iterator over all Channel objects. For each +// Channel, cb will be called. If the cb returns true, the iterator will close +// and stop. +func (k *Keeper) IterateChannels(ctx sdk.Context, cb func(types.IdentifiedChannel) bool) { + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + iterator := storetypes.KVStorePrefixIterator(store, []byte(host.KeyChannelEndPrefix)) + + defer sdk.LogDeferred(k.Logger(ctx), func() error { return iterator.Close() }) + for ; iterator.Valid(); iterator.Next() { + var channel types.Channel + k.cdc.MustUnmarshal(iterator.Value(), &channel) + + portID, channelID := host.MustParseChannelPath(string(iterator.Key())) + identifiedChannel := types.NewIdentifiedChannel(portID, channelID, channel) + if cb(identifiedChannel) { + break + } + } +} + +// GetAllChannelsWithPortPrefix returns all channels with the specified port prefix. If an empty prefix is provided +// all channels will be returned. +func (k *Keeper) GetAllChannelsWithPortPrefix(ctx sdk.Context, portPrefix string) []types.IdentifiedChannel { + if strings.TrimSpace(portPrefix) == "" { + return k.GetAllChannels(ctx) + } + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + iterator := storetypes.KVStorePrefixIterator(store, types.FilteredPortPrefix(portPrefix)) + defer sdk.LogDeferred(k.Logger(ctx), func() error { return iterator.Close() }) + + var filteredChannels []types.IdentifiedChannel + for ; iterator.Valid(); iterator.Next() { + var channel types.Channel + k.cdc.MustUnmarshal(iterator.Value(), &channel) + + portID, channelID := host.MustParseChannelPath(string(iterator.Key())) + identifiedChannel := types.NewIdentifiedChannel(portID, channelID, channel) + filteredChannels = append(filteredChannels, identifiedChannel) + } + return filteredChannels +} + +// GetAllChannels returns all stored Channel objects. +func (k *Keeper) GetAllChannels(ctx sdk.Context) (channels []types.IdentifiedChannel) { + k.IterateChannels(ctx, func(channel types.IdentifiedChannel) bool { + channels = append(channels, channel) + return false + }) + return channels +} + +// GetChannelClientState returns the associated client state with its ID, from a port and channel identifier. +func (k *Keeper) GetChannelClientState(ctx sdk.Context, portID, channelID string) (string, exported.ClientState, error) { + channel, found := k.GetChannel(ctx, portID, channelID) + if !found { + return "", nil, errorsmod.Wrapf(types.ErrChannelNotFound, "port-id: %s, channel-id: %s", portID, channelID) + } + + connection, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + if !found { + return "", nil, errorsmod.Wrapf(connectiontypes.ErrConnectionNotFound, "connection-id: %s", channel.ConnectionHops[0]) + } + + clientState, found := k.clientKeeper.GetClientState(ctx, connection.ClientId) + if !found { + return "", nil, errorsmod.Wrapf(clienttypes.ErrClientNotFound, "client-id: %s", connection.ClientId) + } + + return connection.ClientId, clientState, nil +} + +// GetConnection wraps the connection keeper's GetConnection function. +func (k *Keeper) GetConnection(ctx sdk.Context, connectionID string) (connectiontypes.ConnectionEnd, error) { + connection, found := k.connectionKeeper.GetConnection(ctx, connectionID) + if !found { + return connectiontypes.ConnectionEnd{}, errorsmod.Wrapf(connectiontypes.ErrConnectionNotFound, "connection-id: %s", connectionID) + } + + return connection, nil +} + +// GetChannelConnection returns the connection ID and state associated with the given port and channel identifier. +func (k *Keeper) GetChannelConnection(ctx sdk.Context, portID, channelID string) (string, connectiontypes.ConnectionEnd, error) { + channel, found := k.GetChannel(ctx, portID, channelID) + if !found { + return "", connectiontypes.ConnectionEnd{}, errorsmod.Wrapf(types.ErrChannelNotFound, "port-id: %s, channel-id: %s", portID, channelID) + } + + connectionID := channel.ConnectionHops[0] + + connection, found := k.connectionKeeper.GetConnection(ctx, connectionID) + if !found { + return "", connectiontypes.ConnectionEnd{}, errorsmod.Wrapf(connectiontypes.ErrConnectionNotFound, "connection-id: %s", connectionID) + } + + return connectionID, connection, nil +} + +// common functionality for IteratePacketCommitment and IteratePacketAcknowledgement +func (k *Keeper) iterateHashes(ctx sdk.Context, iterator db.Iterator, cb func(portID, channelID string, sequence uint64, hash []byte) bool) { + defer sdk.LogDeferred(k.Logger(ctx), func() error { return iterator.Close() }) + + for ; iterator.Valid(); iterator.Next() { + keySplit := strings.Split(string(iterator.Key()), "/") + portID := keySplit[2] + channelID := keySplit[4] + + sequence, err := strconv.ParseUint(keySplit[len(keySplit)-1], 10, 64) + if err != nil { + panic(err) + } + + if cb(portID, channelID, sequence, iterator.Value()) { + break + } + } +} + +// HasInflightPackets returns true if there are packet commitments stored at the specified +// port and channel, and false otherwise. +func (k *Keeper) HasInflightPackets(ctx sdk.Context, portID, channelID string) bool { + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + iterator := storetypes.KVStorePrefixIterator(store, host.PacketCommitmentPrefixKey(portID, channelID)) + defer sdk.LogDeferred(k.Logger(ctx), func() error { return iterator.Close() }) + + return iterator.Valid() +} + +// setRecvStartSequence sets the channel's recv start sequence to the store. +func (k *Keeper) setRecvStartSequence(ctx sdk.Context, portID, channelID string, sequence uint64) { + store := k.storeService.OpenKVStore(ctx) + bz := sdk.Uint64ToBigEndian(sequence) + if err := store.Set(host.RecvStartSequenceKey(portID, channelID), bz); err != nil { + panic(err) + } +} + +// GetRecvStartSequence gets a channel's recv start sequence from the store. +// The recv start sequence will be set to the counterparty's next sequence send +// upon a successful channel upgrade. It will be used for replay protection of +// historical packets and as the upper bound for pruning stale packet receives. +func (k *Keeper) GetRecvStartSequence(ctx sdk.Context, portID, channelID string) (uint64, bool) { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(host.RecvStartSequenceKey(portID, channelID)) + if err != nil { + panic(err) + } + + if len(bz) == 0 { + return 0, false + } + + return sdk.BigEndianToUint64(bz), true +} diff --git a/modules/core/04-channel/keeper/keeper_test.go b/modules/core/04-channel/keeper/keeper_test.go new file mode 100644 index 0000000..c3602c1 --- /dev/null +++ b/modules/core/04-channel/keeper/keeper_test.go @@ -0,0 +1,469 @@ +package keeper_test + +import ( + "reflect" + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + ibcmock "github.com/cosmos/ibc-go/v10/testing/mock" +) + +// KeeperTestSuite is a testing suite to test keeper functions. +type KeeperTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains used for convenience and readability + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain + chainC *ibctesting.TestChain +} + +// TestKeeperTestSuite runs all the tests within this package. +func TestKeeperTestSuite(t *testing.T) { + testifysuite.Run(t, new(KeeperTestSuite)) +} + +// SetupTest creates a coordinator with 2 test chains. +func (suite *KeeperTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 3) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) + suite.chainC = suite.coordinator.GetChain(ibctesting.GetChainID(3)) + // commit some blocks so that QueryProof returns valid proof (cannot return valid query if height <= 1) + suite.coordinator.CommitNBlocks(suite.chainA, 2) + suite.coordinator.CommitNBlocks(suite.chainB, 2) + suite.coordinator.CommitNBlocks(suite.chainC, 2) +} + +// TestSetChannel create clients and connections on both chains. It tests for the non-existence +// and existence of a channel in INIT on chainA. +func (suite *KeeperTestSuite) TestSetChannel() { + // create client and connections on both chains + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupConnections() + + // check for channel to be created on chainA + found := suite.chainA.App.GetIBCKeeper().ChannelKeeper.HasChannel(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + suite.False(found) + + path.SetChannelOrdered() + + // init channel + err := path.EndpointA.ChanOpenInit() + suite.NoError(err) + + storedChannel, found := suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetChannel(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + // counterparty channel id is empty after open init + expectedCounterparty := types.NewCounterparty(path.EndpointB.ChannelConfig.PortID, "") + + suite.True(found) + suite.Equal(types.INIT, storedChannel.State) + suite.Equal(types.ORDERED, storedChannel.Ordering) + suite.Equal(expectedCounterparty, storedChannel.Counterparty) +} + +func (suite *KeeperTestSuite) TestGetAppVersion() { + // create client and connections on both chains + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupConnections() + + version, found := suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetAppVersion(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + suite.Require().False(found) + suite.Require().Empty(version) + + // init channel + err := path.EndpointA.ChanOpenInit() + suite.NoError(err) + + channelVersion, found := suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetAppVersion(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + suite.Require().True(found) + suite.Require().Equal(ibcmock.Version, channelVersion) +} + +// TestGetAllChannelsWithPortPrefix verifies ports are filtered correctly using a port prefix. +func (suite *KeeperTestSuite) TestGetAllChannelsWithPortPrefix() { + const ( + secondChannelID = "channel-1" + differentChannelPortID = "different-portid" + ) + + allChannels := []types.IdentifiedChannel{ + types.NewIdentifiedChannel(transfertypes.PortID, ibctesting.FirstChannelID, types.Channel{}), + types.NewIdentifiedChannel(differentChannelPortID, secondChannelID, types.Channel{}), + } + + tests := []struct { + name string + prefix string + allChannels []types.IdentifiedChannel + expectedChannels []types.IdentifiedChannel + }{ + { + name: "transfer channel is retrieved with prefix", + prefix: "tra", + allChannels: allChannels, + expectedChannels: []types.IdentifiedChannel{types.NewIdentifiedChannel(transfertypes.PortID, ibctesting.FirstChannelID, types.Channel{})}, + }, + { + name: "matches port with full name as prefix", + prefix: transfertypes.PortID, + allChannels: allChannels, + expectedChannels: []types.IdentifiedChannel{types.NewIdentifiedChannel(transfertypes.PortID, ibctesting.FirstChannelID, types.Channel{})}, + }, + { + name: "no ports match prefix", + prefix: "wont-match-anything", + allChannels: allChannels, + expectedChannels: nil, + }, + { + name: "empty prefix matches everything", + prefix: "", + allChannels: allChannels, + expectedChannels: allChannels, + }, + } + + for _, tc := range tests { + suite.Run(tc.name, func() { + suite.SetupTest() + + for _, ch := range tc.allChannels { + suite.chainA.GetSimApp().GetIBCKeeper().ChannelKeeper.SetChannel(suite.chainA.GetContext(), ch.PortId, ch.ChannelId, types.Channel{}) + } + + ctxA := suite.chainA.GetContext() + + actualChannels := suite.chainA.GetSimApp().GetIBCKeeper().ChannelKeeper.GetAllChannelsWithPortPrefix(ctxA, tc.prefix) + + suite.Require().True(containsAll(tc.expectedChannels, actualChannels)) + }) + } +} + +// containsAll verifies if all elements in the expected slice exist in the actual slice +// independent of order. +func containsAll(expected, actual []types.IdentifiedChannel) bool { + for _, expectedChannel := range expected { + foundMatch := false + for _, actualChannel := range actual { + if reflect.DeepEqual(actualChannel, expectedChannel) { + foundMatch = true + break + } + } + if !foundMatch { + return false + } + } + return true +} + +// TestGetAllChannels creates multiple channels on chain A through various connections +// and tests their retrieval. 2 channels are on connA0 and 1 channel is on connA1 +func (suite *KeeperTestSuite) TestGetAllChannels() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + // channel0 on first connection on chainA + counterparty0 := types.Counterparty{ + PortId: path.EndpointB.ChannelConfig.PortID, + ChannelId: path.EndpointB.ChannelID, + } + + // path1 creates a second channel on first connection on chainA + path1 := ibctesting.NewPath(suite.chainA, suite.chainB) + path1.SetChannelOrdered() + path1.EndpointA.ClientID = path.EndpointA.ClientID + path1.EndpointB.ClientID = path.EndpointB.ClientID + path1.EndpointA.ConnectionID = path.EndpointA.ConnectionID + path1.EndpointB.ConnectionID = path.EndpointB.ConnectionID + + suite.coordinator.CreateMockChannels(path1) + counterparty1 := types.Counterparty{ + PortId: path1.EndpointB.ChannelConfig.PortID, + ChannelId: path1.EndpointB.ChannelID, + } + + path2 := ibctesting.NewPath(suite.chainA, suite.chainB) + path2.SetupConnections() + + // path2 creates a second channel on chainA + err := path2.EndpointA.ChanOpenInit() + suite.Require().NoError(err) + + // counterparty channel id is empty after open init + counterparty2 := types.Counterparty{ + PortId: path2.EndpointB.ChannelConfig.PortID, + ChannelId: "", + } + + channel0 := types.NewChannel( + types.OPEN, types.UNORDERED, + counterparty0, []string{path.EndpointA.ConnectionID}, path.EndpointA.ChannelConfig.Version, + ) + channel1 := types.NewChannel( + types.OPEN, types.ORDERED, + counterparty1, []string{path1.EndpointA.ConnectionID}, path1.EndpointA.ChannelConfig.Version, + ) + channel2 := types.NewChannel( + types.INIT, types.UNORDERED, + counterparty2, []string{path2.EndpointA.ConnectionID}, path2.EndpointA.ChannelConfig.Version, + ) + + expChannels := []types.IdentifiedChannel{ + types.NewIdentifiedChannel(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, channel0), + types.NewIdentifiedChannel(path1.EndpointA.ChannelConfig.PortID, path1.EndpointA.ChannelID, channel1), + types.NewIdentifiedChannel(path2.EndpointA.ChannelConfig.PortID, path2.EndpointA.ChannelID, channel2), + } + + ctxA := suite.chainA.GetContext() + + channels := suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetAllChannels(ctxA) + suite.Require().Len(channels, len(expChannels)) + suite.Require().Equal(expChannels, channels) +} + +// TestGetAllSequences sets all packet sequences for two different channels on chain A and +// tests their retrieval. +func (suite *KeeperTestSuite) TestGetAllSequences() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + path1 := ibctesting.NewPath(suite.chainA, suite.chainB) + path1.SetChannelOrdered() + path1.EndpointA.ClientID = path.EndpointA.ClientID + path1.EndpointB.ClientID = path.EndpointB.ClientID + path1.EndpointA.ConnectionID = path.EndpointA.ConnectionID + path1.EndpointB.ConnectionID = path.EndpointB.ConnectionID + + suite.coordinator.CreateMockChannels(path1) + + seq1 := types.NewPacketSequence(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, 1) + seq2 := types.NewPacketSequence(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, 2) + seq3 := types.NewPacketSequence(path1.EndpointA.ChannelConfig.PortID, path1.EndpointA.ChannelID, 3) + + // seq1 should be overwritten by seq2 + expSeqs := []types.PacketSequence{seq2, seq3} + + ctxA := suite.chainA.GetContext() + + for _, seq := range []types.PacketSequence{seq1, seq2, seq3} { + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetNextSequenceSend(ctxA, seq.PortId, seq.ChannelId, seq.Sequence) + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetNextSequenceRecv(ctxA, seq.PortId, seq.ChannelId, seq.Sequence) + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetNextSequenceAck(ctxA, seq.PortId, seq.ChannelId, seq.Sequence) + } + + sendSeqs := suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetAllPacketSendSeqs(ctxA) + recvSeqs := suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetAllPacketRecvSeqs(ctxA) + ackSeqs := suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetAllPacketAckSeqs(ctxA) + suite.Len(sendSeqs, 2) + suite.Len(recvSeqs, 2) + suite.Len(ackSeqs, 2) + + suite.Equal(expSeqs, sendSeqs) + suite.Equal(expSeqs, recvSeqs) + suite.Equal(expSeqs, ackSeqs) +} + +// TestGetAllPacketState creates a set of acks, packet commitments, and receipts on two different +// channels on chain A and tests their retrieval. +func (suite *KeeperTestSuite) TestGetAllPacketState() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + path1 := ibctesting.NewPath(suite.chainA, suite.chainB) + path1.EndpointA.ClientID = path.EndpointA.ClientID + path1.EndpointB.ClientID = path.EndpointB.ClientID + path1.EndpointA.ConnectionID = path.EndpointA.ConnectionID + path1.EndpointB.ConnectionID = path.EndpointB.ConnectionID + + suite.coordinator.CreateMockChannels(path1) + + // channel 0 acks + ack1 := types.NewPacketState(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, 1, []byte("ack")) + ack2 := types.NewPacketState(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, 2, []byte("ack")) + + // duplicate ack + ack2dup := types.NewPacketState(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, 2, []byte("ack")) + + // channel 1 acks + ack3 := types.NewPacketState(path1.EndpointA.ChannelConfig.PortID, path1.EndpointA.ChannelID, 1, []byte("ack")) + + // create channel 0 receipts + receipt := string([]byte{byte(1)}) + rec1 := types.NewPacketState(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, 1, []byte(receipt)) + rec2 := types.NewPacketState(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, 2, []byte(receipt)) + + // channel 1 receipts + rec3 := types.NewPacketState(path1.EndpointA.ChannelConfig.PortID, path1.EndpointA.ChannelID, 1, []byte(receipt)) + rec4 := types.NewPacketState(path1.EndpointA.ChannelConfig.PortID, path1.EndpointA.ChannelID, 2, []byte(receipt)) + + // channel 0 packet commitments + comm1 := types.NewPacketState(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, 1, []byte("hash")) + comm2 := types.NewPacketState(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, 2, []byte("hash")) + + // channel 1 packet commitments + comm3 := types.NewPacketState(path1.EndpointA.ChannelConfig.PortID, path1.EndpointA.ChannelID, 1, []byte("hash")) + comm4 := types.NewPacketState(path1.EndpointA.ChannelConfig.PortID, path1.EndpointA.ChannelID, 2, []byte("hash")) + + expAcks := []types.PacketState{ack1, ack2, ack3} + expReceipts := []types.PacketState{rec1, rec2, rec3, rec4} + expCommitments := []types.PacketState{comm1, comm2, comm3, comm4} + + ctxA := suite.chainA.GetContext() + + // set acknowledgements + for _, ack := range []types.PacketState{ack1, ack2, ack2dup, ack3} { + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketAcknowledgement(ctxA, ack.PortId, ack.ChannelId, ack.Sequence, ack.Data) + } + + // set packet receipts + for _, rec := range expReceipts { + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketReceipt(ctxA, rec.PortId, rec.ChannelId, rec.Sequence) + } + + // set packet commitments + for _, comm := range expCommitments { + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketCommitment(ctxA, comm.PortId, comm.ChannelId, comm.Sequence, comm.Data) + } + + acks := suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetAllPacketAcks(ctxA) + receipts := suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetAllPacketReceipts(ctxA) + commitments := suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetAllPacketCommitments(ctxA) + + suite.Require().Len(acks, len(expAcks)) + suite.Require().Len(commitments, len(expCommitments)) + suite.Require().Len(receipts, len(expReceipts)) + + suite.Require().Equal(expAcks, acks) + suite.Require().Equal(expReceipts, receipts) + suite.Require().Equal(expCommitments, commitments) +} + +// TestSetSequence verifies that the keeper correctly sets the sequence counters. +func (suite *KeeperTestSuite) TestSetSequence() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + ctxA := suite.chainA.GetContext() + one := uint64(1) + + // initialized channel has next send seq of 1 + seq, found := suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetNextSequenceSend(ctxA, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + suite.True(found) + suite.Equal(one, seq) + + // initialized channel has next seq recv of 1 + seq, found = suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetNextSequenceRecv(ctxA, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + suite.True(found) + suite.Equal(one, seq) + + // initialized channel has next seq ack of + seq, found = suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetNextSequenceAck(ctxA, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + suite.True(found) + suite.Equal(one, seq) + + nextSeqSend, nextSeqRecv, nextSeqAck := uint64(10), uint64(10), uint64(10) + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetNextSequenceSend(ctxA, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, nextSeqSend) + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetNextSequenceRecv(ctxA, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, nextSeqRecv) + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetNextSequenceAck(ctxA, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, nextSeqAck) + + storedNextSeqSend, found := suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetNextSequenceSend(ctxA, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + suite.True(found) + suite.Equal(nextSeqSend, storedNextSeqSend) + + storedNextSeqRecv, found := suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetNextSequenceSend(ctxA, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + suite.True(found) + suite.Equal(nextSeqRecv, storedNextSeqRecv) + + storedNextSeqAck, found := suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetNextSequenceAck(ctxA, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + suite.True(found) + suite.Equal(nextSeqAck, storedNextSeqAck) +} + +// TestGetAllPacketCommitmentsAtChannel verifies that the keeper returns all stored packet +// commitments for a specific channel. The test will store consecutive commitments up to the +// value of "seq" and then add non-consecutive up to the value of "maxSeq". A final commitment +// with the value maxSeq + 1 is set on a different channel. +func (suite *KeeperTestSuite) TestGetAllPacketCommitmentsAtChannel() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + // create second channel + path1 := ibctesting.NewPath(suite.chainA, suite.chainB) + path1.SetChannelOrdered() + path1.EndpointA.ClientID = path.EndpointA.ClientID + path1.EndpointB.ClientID = path.EndpointB.ClientID + path1.EndpointA.ConnectionID = path.EndpointA.ConnectionID + path1.EndpointB.ConnectionID = path.EndpointB.ConnectionID + + suite.coordinator.CreateMockChannels(path1) + + ctxA := suite.chainA.GetContext() + expectedSeqs := make(map[uint64]bool) + hash := []byte("commitment") + + seq := uint64(15) + maxSeq := uint64(25) + suite.Require().Greater(maxSeq, seq) + + // create consecutive commitments + for i := uint64(1); i < seq; i++ { + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketCommitment(ctxA, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, i, hash) + expectedSeqs[i] = true + } + + // add non-consecutive commitments + for i := seq; i < maxSeq; i += 2 { + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketCommitment(ctxA, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, i, hash) + expectedSeqs[i] = true + } + + // add sequence on different channel/port + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketCommitment(ctxA, path1.EndpointA.ChannelConfig.PortID, path1.EndpointA.ChannelID, maxSeq+1, hash) + + commitments := suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetAllPacketCommitmentsAtChannel(ctxA, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID) + + suite.Equal(len(expectedSeqs), len(commitments)) + // ensure above for loops occurred + suite.NotEqual(0, len(commitments)) + + // verify that all the packet commitments were stored + for _, packet := range commitments { + suite.True(expectedSeqs[packet.Sequence]) + suite.Equal(path.EndpointA.ChannelConfig.PortID, packet.PortId) + suite.Equal(path.EndpointA.ChannelID, packet.ChannelId) + suite.Equal(hash, packet.Data) + + // prevent duplicates from passing checks + expectedSeqs[packet.Sequence] = false + } +} + +// TestSetPacketAcknowledgement verifies that packet acknowledgements are correctly +// set in the keeper. +func (suite *KeeperTestSuite) TestSetPacketAcknowledgement() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + ctxA := suite.chainA.GetContext() + seq := uint64(10) + + storedAckHash, found := suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetPacketAcknowledgement(ctxA, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, seq) + suite.Require().False(found) + suite.Require().Nil(storedAckHash) + + ackHash := []byte("ackhash") + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketAcknowledgement(ctxA, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, seq, ackHash) + + storedAckHash, found = suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetPacketAcknowledgement(ctxA, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, seq) + suite.Require().True(found) + suite.Require().Equal(ackHash, storedAckHash) + suite.Require().True(suite.chainA.App.GetIBCKeeper().ChannelKeeper.HasPacketAcknowledgement(ctxA, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, seq)) +} diff --git a/modules/core/04-channel/keeper/migrations.go b/modules/core/04-channel/keeper/migrations.go new file mode 100644 index 0000000..56a6d16 --- /dev/null +++ b/modules/core/04-channel/keeper/migrations.go @@ -0,0 +1,27 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + v10 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/migrations/v10" +) + +// Migrator is a struct for handling in-place store migrations. +type Migrator struct { + keeper *Keeper +} + +// NewMigrator returns a new Migrator. +func NewMigrator(keeper *Keeper) Migrator { + return Migrator{keeper: keeper} +} + +// Migrate7To8 migrates the channel store from module version 7 to 8 by: +// - Removing channel upgrade sequences +// - Removing any channel upgrade info (i.e. upgrades, counterparty upgrades, upgrade errors) +// - Removing channel params +// - Removing pruning sequences +// NOTE: This migration will fail if any channels are in the FLUSHING or FLUSHCOMPLETE state. +func (m *Migrator) Migrate7To8(ctx sdk.Context) error { + return v10.MigrateStore(ctx, m.keeper.storeService, m.keeper.cdc, m.keeper) +} diff --git a/modules/core/04-channel/keeper/packet.go b/modules/core/04-channel/keeper/packet.go new file mode 100644 index 0000000..1403f3c --- /dev/null +++ b/modules/core/04-channel/keeper/packet.go @@ -0,0 +1,449 @@ +package keeper + +import ( + "bytes" + "strconv" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// SendPacket is called by a module in order to send an IBC packet on a channel. +// The packet sequence generated for the packet to be sent is returned. An error +// is returned if one occurs. +func (k *Keeper) SendPacket( + ctx sdk.Context, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + data []byte, +) (uint64, error) { + channel, found := k.GetChannel(ctx, sourcePort, sourceChannel) + if !found { + return 0, errorsmod.Wrap(types.ErrChannelNotFound, sourceChannel) + } + + if channel.State != types.OPEN { + return 0, errorsmod.Wrapf(types.ErrInvalidChannelState, "channel is not OPEN (got %s)", channel.State) + } + + sequence, found := k.GetNextSequenceSend(ctx, sourcePort, sourceChannel) + if !found { + return 0, errorsmod.Wrapf( + types.ErrSequenceSendNotFound, + "source port: %s, source channel: %s", sourcePort, sourceChannel, + ) + } + + // construct packet from given fields and channel state + packet := types.NewPacket(data, sequence, sourcePort, sourceChannel, + channel.Counterparty.PortId, channel.Counterparty.ChannelId, timeoutHeight, timeoutTimestamp) + + if err := packet.ValidateBasic(); err != nil { + return 0, errorsmod.Wrap(err, "constructed packet failed basic validation") + } + + connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + if !found { + return 0, errorsmod.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0]) + } + + // prevent accidental sends with clients that cannot be updated + if status := k.clientKeeper.GetClientStatus(ctx, connectionEnd.ClientId); status != exported.Active { + return 0, errorsmod.Wrapf(clienttypes.ErrClientNotActive, "cannot send packet using client (%s) with status %s", connectionEnd.ClientId, status) + } + + latestHeight := k.clientKeeper.GetClientLatestHeight(ctx, connectionEnd.ClientId) + if latestHeight.IsZero() { + return 0, errorsmod.Wrapf(clienttypes.ErrInvalidHeight, "cannot send packet using client (%s) with zero height", connectionEnd.ClientId) + } + + latestTimestamp, err := k.clientKeeper.GetClientTimestampAtHeight(ctx, connectionEnd.ClientId, latestHeight) + if err != nil { + return 0, err + } + + // check if packet is timed out on the receiving chain + timeout := types.NewTimeout(packet.GetTimeoutHeight().(clienttypes.Height), packet.GetTimeoutTimestamp()) + if timeout.Elapsed(latestHeight, latestTimestamp) { + return 0, errorsmod.Wrap(timeout.ErrTimeoutElapsed(latestHeight, latestTimestamp), "invalid packet timeout") + } + + commitment := types.CommitPacket(packet) + + k.SetNextSequenceSend(ctx, sourcePort, sourceChannel, sequence+1) + k.SetPacketCommitment(ctx, sourcePort, sourceChannel, packet.GetSequence(), commitment) + + emitSendPacketEvent(ctx, packet, channel, timeoutHeight) + + k.Logger(ctx).Info( + "packet sent", + "sequence", strconv.FormatUint(packet.GetSequence(), 10), + "src_port", sourcePort, + "src_channel", sourceChannel, + "dst_port", packet.GetDestPort(), + "dst_channel", packet.GetDestChannel(), + ) + + return packet.GetSequence(), nil +} + +// RecvPacket is called by a module in order to receive & process an IBC packet +// sent on the corresponding channel end on the counterparty chain. +func (k *Keeper) RecvPacket( + ctx sdk.Context, + packet types.Packet, + proof []byte, + proofHeight exported.Height, +) (string, error) { + channel, found := k.GetChannel(ctx, packet.GetDestPort(), packet.GetDestChannel()) + if !found { + return "", errorsmod.Wrap(types.ErrChannelNotFound, packet.GetDestChannel()) + } + + if channel.State != types.OPEN { + return "", errorsmod.Wrapf(types.ErrInvalidChannelState, "channel is not OPEN (got %s)", channel.State) + } + + // packet must come from the channel's counterparty + if packet.GetSourcePort() != channel.Counterparty.PortId { + return "", errorsmod.Wrapf( + types.ErrInvalidPacket, + "packet source port doesn't match the counterparty's port (%s ≠ %s)", packet.GetSourcePort(), channel.Counterparty.PortId, + ) + } + + if packet.GetSourceChannel() != channel.Counterparty.ChannelId { + return "", errorsmod.Wrapf( + types.ErrInvalidPacket, + "packet source channel doesn't match the counterparty's channel (%s ≠ %s)", packet.GetSourceChannel(), channel.Counterparty.ChannelId, + ) + } + + // Connection must be OPEN to receive a packet. It is possible for connection to not yet be open if packet was + // sent optimistically before connection and channel handshake completed. However, to receive a packet, + // connection and channel must both be open + connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + if !found { + return "", errorsmod.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0]) + } + + if connectionEnd.State != connectiontypes.OPEN { + return "", errorsmod.Wrapf(connectiontypes.ErrInvalidConnectionState, "connection state is not OPEN (got %s)", connectionEnd.State) + } + + // check if packet timed out by comparing it with the latest height of the chain + selfHeight, selfTimestamp := clienttypes.GetSelfHeight(ctx), uint64(ctx.BlockTime().UnixNano()) + timeout := types.NewTimeout(packet.GetTimeoutHeight().(clienttypes.Height), packet.GetTimeoutTimestamp()) + if timeout.Elapsed(selfHeight, selfTimestamp) { + return "", errorsmod.Wrap(timeout.ErrTimeoutElapsed(selfHeight, selfTimestamp), "packet timeout elapsed") + } + + commitment := types.CommitPacket(packet) + + // verify that the counterparty did commit to sending this packet + if err := k.connectionKeeper.VerifyPacketCommitment( + ctx, connectionEnd, proofHeight, proof, + packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(), + commitment, + ); err != nil { + return "", errorsmod.Wrap(err, "couldn't verify counterparty packet commitment") + } + + if err := k.applyReplayProtection(ctx, packet, channel); err != nil { + return "", err + } + + // log that a packet has been received & executed + k.Logger(ctx).Info( + "packet received", + "sequence", strconv.FormatUint(packet.GetSequence(), 10), + "src_port", packet.GetSourcePort(), + "src_channel", packet.GetSourceChannel(), + "dst_port", packet.GetDestPort(), + "dst_channel", packet.GetDestChannel(), + ) + + // emit an event that the relayer can query for + emitRecvPacketEvent(ctx, packet, channel) + + return channel.Version, nil +} + +// applyReplayProtection ensures a packet has not already been received +// and performs the necessary state changes to ensure it cannot be received again. +func (k *Keeper) applyReplayProtection(ctx sdk.Context, packet types.Packet, channel types.Channel) error { + // REPLAY PROTECTION: The recvStartSequence will prevent historical proofs from allowing replay + // attacks on packets processed in previous lifecycles of a channel. After a successful channel + // upgrade all packets under the recvStartSequence will have been processed and thus should be + // rejected. + recvStartSequence, _ := k.GetRecvStartSequence(ctx, packet.GetDestPort(), packet.GetDestChannel()) + if packet.GetSequence() < recvStartSequence { + return errorsmod.Wrap(types.ErrPacketReceived, "packet already processed in previous channel upgrade") + } + + switch channel.Ordering { + case types.UNORDERED: + // REPLAY PROTECTION: Packet receipts will indicate that a packet has already been received + // on unordered channels. Packet receipts must not be pruned, unless it has been marked stale + // by the increase of the recvStartSequence. + _, found := k.GetPacketReceipt(ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + if found { + emitRecvPacketEvent(ctx, packet, channel) + // This error indicates that the packet has already been relayed. Core IBC will + // treat this error as a no-op in order to prevent an entire relay transaction + // from failing and consuming unnecessary fees. + return types.ErrNoOpMsg + } + + // All verification complete, update state + // For unordered channels we must set the receipt so it can be verified on the other side. + // This receipt does not contain any data, since the packet has not yet been processed, + // it's just a single store key set to a single byte to indicate that the packet has been received + k.SetPacketReceipt(ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + + case types.ORDERED: + // check if the packet is being received in order + nextSequenceRecv, found := k.GetNextSequenceRecv(ctx, packet.GetDestPort(), packet.GetDestChannel()) + if !found { + return errorsmod.Wrapf( + types.ErrSequenceReceiveNotFound, + "destination port: %s, destination channel: %s", packet.GetDestPort(), packet.GetDestChannel(), + ) + } + + if packet.GetSequence() < nextSequenceRecv { + emitRecvPacketEvent(ctx, packet, channel) + // This error indicates that the packet has already been relayed. Core IBC will + // treat this error as a no-op in order to prevent an entire relay transaction + // from failing and consuming unnecessary fees. + return types.ErrNoOpMsg + } + + // REPLAY PROTECTION: Ordered channels require packets to be received in a strict order. + // Any out of order or previously received packets are rejected. + if packet.GetSequence() != nextSequenceRecv { + return errorsmod.Wrapf( + types.ErrPacketSequenceOutOfOrder, + "packet sequence ≠ next receive sequence (%d ≠ %d)", packet.GetSequence(), nextSequenceRecv, + ) + } + + // All verification complete, update state + // In ordered case, we must increment nextSequenceRecv + nextSequenceRecv++ + + // incrementing nextSequenceRecv and storing under this chain's channelEnd identifiers + // Since this is the receiving chain, our channelEnd is packet's destination port and channel + k.SetNextSequenceRecv(ctx, packet.GetDestPort(), packet.GetDestChannel(), nextSequenceRecv) + } + + return nil +} + +// WriteAcknowledgement writes the packet execution acknowledgement to the state, +// which will be verified by the counterparty chain using AcknowledgePacket. +// +// CONTRACT: +// +// 1) For synchronous execution, this function is be called in the IBC handler . +// For async handling, it needs to be called directly by the module which originally +// processed the packet. +// +// 2) Assumes that packet receipt has been written (unordered), or nextSeqRecv was incremented (ordered) +// previously by RecvPacket. +func (k *Keeper) WriteAcknowledgement( + ctx sdk.Context, + packet exported.PacketI, + acknowledgement exported.Acknowledgement, +) error { + channel, found := k.GetChannel(ctx, packet.GetDestPort(), packet.GetDestChannel()) + if !found { + return errorsmod.Wrap(types.ErrChannelNotFound, packet.GetDestChannel()) + } + + if channel.State != types.OPEN { + return errorsmod.Wrapf(types.ErrInvalidChannelState, "channel is not OPEN (got %s)", channel.State) + } + + // REPLAY PROTECTION: The recvStartSequence will prevent historical proofs from allowing replay + // attacks on packets processed in previous lifecycles of a channel. After a successful channel + // upgrade all packets under the recvStartSequence will have been processed and thus should be + // rejected. Any asynchronous acknowledgement writes from packets processed in a previous lifecycle of a channel + // will also be rejected. + recvStartSequence, _ := k.GetRecvStartSequence(ctx, packet.GetDestPort(), packet.GetDestChannel()) + if packet.GetSequence() < recvStartSequence { + return errorsmod.Wrap(types.ErrPacketReceived, "packet already processed in previous channel upgrade") + } + + // NOTE: IBC app modules might have written the acknowledgement synchronously on + // the OnRecvPacket callback so we need to check if the acknowledgement is already + // set on the store and return an error if so. + if k.HasPacketAcknowledgement(ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) { + return types.ErrAcknowledgementExists + } + + if acknowledgement == nil { + return errorsmod.Wrap(types.ErrInvalidAcknowledgement, "acknowledgement cannot be nil") + } + + bz := acknowledgement.Acknowledgement() + if len(bz) == 0 { + return errorsmod.Wrap(types.ErrInvalidAcknowledgement, "acknowledgement cannot be empty") + } + + // set the acknowledgement so that it can be verified on the other side + k.SetPacketAcknowledgement( + ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), + types.CommitAcknowledgement(bz), + ) + + // log that a packet acknowledgement has been written + k.Logger(ctx).Info( + "acknowledgement written", + "sequence", strconv.FormatUint(packet.GetSequence(), 10), + "src_port", packet.GetSourcePort(), + "src_channel", packet.GetSourceChannel(), + "dst_port", packet.GetDestPort(), + "dst_channel", packet.GetDestChannel(), + ) + + emitWriteAcknowledgementEvent(ctx, packet.(types.Packet), channel, bz) + + return nil +} + +// AcknowledgePacket is called by a module to process the acknowledgement of a +// packet previously sent by the calling module on a channel to a counterparty +// module on the counterparty chain. Its intended usage is within the ante +// handler. AcknowledgePacket will clean up the packet commitment, +// which is no longer necessary since the packet has been received and acted upon. +// It will also increment NextSequenceAck in case of ORDERED channels. +func (k *Keeper) AcknowledgePacket( + ctx sdk.Context, + packet types.Packet, + acknowledgement []byte, + proof []byte, + proofHeight exported.Height, +) (string, error) { + channel, found := k.GetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel()) + if !found { + return "", errorsmod.Wrapf( + types.ErrChannelNotFound, + "port ID (%s) channel ID (%s)", packet.GetSourcePort(), packet.GetSourceChannel(), + ) + } + + if channel.State != types.OPEN { + return "", errorsmod.Wrapf(types.ErrInvalidChannelState, "channel is not OPEN (got %s)", channel.State) + } + + // packet must have been sent to the channel's counterparty + if packet.GetDestPort() != channel.Counterparty.PortId { + return "", errorsmod.Wrapf( + types.ErrInvalidPacket, + "packet destination port doesn't match the counterparty's port (%s ≠ %s)", packet.GetDestPort(), channel.Counterparty.PortId, + ) + } + + if packet.GetDestChannel() != channel.Counterparty.ChannelId { + return "", errorsmod.Wrapf( + types.ErrInvalidPacket, + "packet destination channel doesn't match the counterparty's channel (%s ≠ %s)", packet.GetDestChannel(), channel.Counterparty.ChannelId, + ) + } + + connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + if !found { + return "", errorsmod.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0]) + } + + if connectionEnd.State != connectiontypes.OPEN { + return "", errorsmod.Wrapf(connectiontypes.ErrInvalidConnectionState, "connection state is not OPEN (got %s)", connectionEnd.State) + } + + commitment := k.GetPacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + + if len(commitment) == 0 { + emitAcknowledgePacketEvent(ctx, packet, channel) + // This error indicates that the acknowledgement has already been relayed + // or there is a misconfigured relayer attempting to prove an acknowledgement + // for a packet never sent. Core IBC will treat this error as a no-op in order to + // prevent an entire relay transaction from failing and consuming unnecessary fees. + return "", types.ErrNoOpMsg + } + + packetCommitment := types.CommitPacket(packet) + + var ack types.Acknowledgement + err := types.SubModuleCdc.UnmarshalJSON(acknowledgement, &ack) + if err == nil { + ackBz := ack.Acknowledgement() + if !bytes.Equal(ackBz, acknowledgement) { + return "", errorsmod.Wrap(types.ErrInvalidAcknowledgement, "acknowledgement marshalling error") + } + } + + // verify we sent the packet and haven't cleared it out yet + if !bytes.Equal(commitment, packetCommitment) { + return "", errorsmod.Wrapf(types.ErrInvalidPacket, "commitment bytes are not equal: got (%v), expected (%v)", packetCommitment, commitment) + } + + if err := k.connectionKeeper.VerifyPacketAcknowledgement( + ctx, connectionEnd, proofHeight, proof, packet.GetDestPort(), packet.GetDestChannel(), + packet.GetSequence(), acknowledgement, + ); err != nil { + return "", err + } + + // assert packets acknowledged in order + if channel.Ordering == types.ORDERED { + nextSequenceAck, found := k.GetNextSequenceAck(ctx, packet.GetSourcePort(), packet.GetSourceChannel()) + if !found { + return "", errorsmod.Wrapf( + types.ErrSequenceAckNotFound, + "source port: %s, source channel: %s", packet.GetSourcePort(), packet.GetSourceChannel(), + ) + } + + if packet.GetSequence() != nextSequenceAck { + return "", errorsmod.Wrapf( + types.ErrPacketSequenceOutOfOrder, + "packet sequence ≠ next ack sequence (%d ≠ %d)", packet.GetSequence(), nextSequenceAck, + ) + } + + // All verification complete, in the case of ORDERED channels we must increment nextSequenceAck + nextSequenceAck++ + + // incrementing NextSequenceAck and storing under this chain's channelEnd identifiers + // Since this is the original sending chain, our channelEnd is packet's source port and channel + k.SetNextSequenceAck(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), nextSequenceAck) + + } + + // Delete packet commitment, since the packet has been acknowledged, the commitement is no longer necessary + k.deletePacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + + // log that a packet has been acknowledged + k.Logger(ctx).Info( + "packet acknowledged", + "sequence", strconv.FormatUint(packet.GetSequence(), 10), + "src_port", packet.GetSourcePort(), + "src_channel", packet.GetSourceChannel(), + "dst_port", packet.GetDestPort(), + "dst_channel", packet.GetDestChannel(), + ) + + // emit an event marking that we have processed the acknowledgement + emitAcknowledgePacketEvent(ctx, packet, channel) + + return channel.Version, nil +} diff --git a/modules/core/04-channel/keeper/packet_test.go b/modules/core/04-channel/keeper/packet_test.go new file mode 100644 index 0000000..0394d3b --- /dev/null +++ b/modules/core/04-channel/keeper/packet_test.go @@ -0,0 +1,1010 @@ +package keeper_test + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + + abci "github.com/cometbft/cometbft/abci/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + ibcmock "github.com/cosmos/ibc-go/v10/testing/mock" +) + +var ( + disabledTimeoutTimestamp = uint64(0) + disabledTimeoutHeight = clienttypes.ZeroHeight() + defaultTimeoutHeight = clienttypes.NewHeight(1, 100) + + // for when the testing package cannot be used + connIDA = "connA" + connIDB = "connB" +) + +// TestSendPacket tests SendPacket from chainA to chainB +func (suite *KeeperTestSuite) TestSendPacket() { + var ( + path *ibctesting.Path + sourcePort string + sourceChannel string + packetData []byte + timeoutHeight clienttypes.Height + timeoutTimestamp uint64 + ) + + testCases := []testCase{ + {"success: UNORDERED channel", func() { + path.Setup() + sourceChannel = path.EndpointA.ChannelID + }, nil}, + {"success: ORDERED channel", func() { + path.SetChannelOrdered() + path.Setup() + sourceChannel = path.EndpointA.ChannelID + }, nil}, + {"success with solomachine: UNORDERED channel", func() { + path.Setup() + sourceChannel = path.EndpointA.ChannelID + + // swap client with solo machine + solomachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachinesingle", "testing", 1) + path.EndpointA.ClientID = clienttypes.FormatClientIdentifier(exported.Solomachine, 10) + path.EndpointA.SetClientState(solomachine.ClientState()) + path.EndpointA.UpdateConnection(func(c *connectiontypes.ConnectionEnd) { c.ClientId = path.EndpointA.ClientID }) + }, nil}, + {"success with solomachine: ORDERED channel", func() { + path.SetChannelOrdered() + path.Setup() + sourceChannel = path.EndpointA.ChannelID + + // swap client with solomachine + solomachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachinesingle", "testing", 1) + path.EndpointA.ClientID = clienttypes.FormatClientIdentifier(exported.Solomachine, 10) + path.EndpointA.SetClientState(solomachine.ClientState()) + + path.EndpointA.UpdateConnection(func(c *connectiontypes.ConnectionEnd) { c.ClientId = path.EndpointA.ClientID }) + }, nil}, + {"packet basic validation failed, empty packet data", func() { + path.Setup() + sourceChannel = path.EndpointA.ChannelID + + packetData = []byte{} + }, types.ErrInvalidPacket}, + {"channel not found", func() { + // use wrong channel naming + path.Setup() + sourceChannel = ibctesting.InvalidID + }, types.ErrChannelNotFound}, + {"channel is in CLOSED state", func() { + path.Setup() + sourceChannel = path.EndpointA.ChannelID + + path.EndpointA.UpdateChannel(func(channel *types.Channel) { channel.State = types.CLOSED }) + }, types.ErrInvalidChannelState}, + {"channel is in INIT state", func() { + path.Setup() + sourceChannel = path.EndpointA.ChannelID + + path.EndpointA.UpdateChannel(func(channel *types.Channel) { channel.State = types.INIT }) + }, types.ErrInvalidChannelState}, + {"channel is in TRYOPEN stage", func() { + path.Setup() + sourceChannel = path.EndpointA.ChannelID + + path.EndpointA.UpdateChannel(func(channel *types.Channel) { channel.State = types.TRYOPEN }) + }, types.ErrInvalidChannelState}, + {"connection not found", func() { + // pass channel check + path.Setup() + sourceChannel = path.EndpointA.ChannelID + + path.EndpointA.UpdateChannel(func(channel *types.Channel) { channel.ConnectionHops[0] = "invalid-connection" }) + }, errorsmod.Wrap(connectiontypes.ErrConnectionNotFound, "")}, + {"client state not found", func() { + path.Setup() + sourceChannel = path.EndpointA.ChannelID + + // change connection client ID + path.EndpointA.UpdateConnection(func(c *connectiontypes.ConnectionEnd) { c.ClientId = ibctesting.InvalidID }) + }, clienttypes.ErrClientNotActive}, + {"client state is frozen", func() { + path.Setup() + sourceChannel = path.EndpointA.ChannelID + + connection := path.EndpointA.GetConnection() + clientState := path.EndpointA.GetClientState() + cs, ok := clientState.(*ibctm.ClientState) + suite.Require().True(ok) + + // freeze client + cs.FrozenHeight = clienttypes.NewHeight(0, 1) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), connection.ClientId, cs) + }, clienttypes.ErrClientNotActive}, + {"client state zero height", func() { + path.Setup() + sourceChannel = path.EndpointA.ChannelID + + connection := path.EndpointA.GetConnection() + clientState := path.EndpointA.GetClientState() + cs, ok := clientState.(*ibctm.ClientState) + suite.Require().True(ok) + + // force a consensus state into the store at height zero to allow client status check to pass. + consensusState := path.EndpointA.GetConsensusState(cs.LatestHeight) + path.EndpointA.SetConsensusState(consensusState, clienttypes.ZeroHeight()) + + cs.LatestHeight = clienttypes.ZeroHeight() + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), connection.ClientId, cs) + }, clienttypes.ErrInvalidHeight}, + {"timeout height passed", func() { + path.Setup() + sourceChannel = path.EndpointA.ChannelID + + var ok bool + timeoutHeight, ok = path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + }, types.ErrTimeoutElapsed}, + {"timeout timestamp passed", func() { + path.Setup() + sourceChannel = path.EndpointA.ChannelID + + connection := path.EndpointA.GetConnection() + timestamp, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientTimestampAtHeight(suite.chainA.GetContext(), connection.ClientId, path.EndpointA.GetClientLatestHeight()) + suite.Require().NoError(err) + + timeoutHeight = disabledTimeoutHeight + timeoutTimestamp = timestamp + }, types.ErrTimeoutElapsed}, + {"timeout timestamp passed with solomachine", func() { + path.Setup() + // swap client with solomachine + solomachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachinesingle", "testing", 1) + path.EndpointA.ClientID = clienttypes.FormatClientIdentifier(exported.Solomachine, 10) + path.EndpointA.SetClientState(solomachine.ClientState()) + + path.EndpointA.UpdateConnection(func(c *connectiontypes.ConnectionEnd) { c.ClientId = path.EndpointA.ClientID }) + + connection := path.EndpointA.GetConnection() + timestamp, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientTimestampAtHeight(suite.chainA.GetContext(), connection.ClientId, path.EndpointA.GetClientLatestHeight()) + suite.Require().NoError(err) + + sourceChannel = path.EndpointA.ChannelID + timeoutHeight = disabledTimeoutHeight + timeoutTimestamp = timestamp + }, types.ErrTimeoutElapsed}, + {"next sequence send not found", func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + sourceChannel = path.EndpointA.ChannelID + + path.SetupConnections() + // manually creating channel prevents next sequence from being set + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetChannel( + suite.chainA.GetContext(), + path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, + types.NewChannel(types.OPEN, types.ORDERED, types.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID), []string{path.EndpointA.ConnectionID}, path.EndpointA.ChannelConfig.Version), + ) + }, errorsmod.Wrap(types.ErrSequenceSendNotFound, "")}, + } + + for i, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s, %d/%d tests", tc.msg, i, len(testCases)), func() { + suite.SetupTest() // reset + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + // set default send packet arguments + // sourceChannel is set after path is setup + sourcePort = path.EndpointA.ChannelConfig.PortID + timeoutHeight = defaultTimeoutHeight + timeoutTimestamp = disabledTimeoutTimestamp + packetData = ibctesting.MockPacketData + + // malleate may modify send packet arguments above + tc.malleate() + + // only check if nextSequenceSend exists in no error case since it is a tested error case above. + expectedSequence, ok := suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetNextSequenceSend(suite.chainA.GetContext(), sourcePort, sourceChannel) + + sequence, err := suite.chainA.App.GetIBCKeeper().ChannelKeeper.SendPacket(suite.chainA.GetContext(), + sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, packetData) + + if tc.expErr == nil { + suite.Require().NoError(err) + // verify that the returned sequence matches expected value + suite.Require().True(ok) + suite.Require().Equal(expectedSequence, sequence, "send packet did not return the expected sequence of the outgoing packet") + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +// TestRecvPacket test RecvPacket on chainB. Since packet commitment verification will always +// occur last (resource instensive), only tests expected to succeed and packet commitment +// verification tests need to simulate sending a packet from chainA to chainB. +func (suite *KeeperTestSuite) TestRecvPacket() { + var ( + path *ibctesting.Path + packet types.Packet + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success: ORDERED channel", + func() { + path.SetChannelOrdered() + path.Setup() + + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + }, + nil, + }, + { + "success UNORDERED channel", + func() { + // setup uses an UNORDERED channel + path.Setup() + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + }, + nil, + }, + { + "success with out of order packet: UNORDERED channel", + func() { + // setup uses an UNORDERED channel + path.Setup() + // send 2 packets + _, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + // attempts to receive packet 2 without receiving packet 1 + }, + nil, + }, + { + "packet already relayed ORDERED channel (no-op)", + func() { + path.SetChannelOrdered() + path.Setup() + + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + }, + types.ErrNoOpMsg, + }, + { + "packet already relayed UNORDERED channel (no-op)", + func() { + // setup uses an UNORDERED channel + path.Setup() + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + }, + types.ErrNoOpMsg, + }, + { + "out of order packet failure with ORDERED channel", + func() { + path.SetChannelOrdered() + path.Setup() + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + + // send 2 packets + _, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + // attempts to receive packet 2 without receiving packet 1 + }, + types.ErrPacketSequenceOutOfOrder, + }, + { + "channel not found", + func() { + // use wrong channel naming + path.Setup() + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, ibctesting.InvalidID, ibctesting.InvalidID, defaultTimeoutHeight, disabledTimeoutTimestamp) + }, + types.ErrChannelNotFound, + }, + { + "channel not open", + func() { + path.Setup() + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + + path.EndpointB.UpdateChannel(func(channel *types.Channel) { channel.State = types.CLOSED }) + }, + types.ErrInvalidChannelState, + }, + { + "packet source port ≠ channel counterparty port", + func() { + path.Setup() + + // use wrong port for dest + packet = types.NewPacket(ibctesting.MockPacketData, 1, ibctesting.InvalidID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + }, + types.ErrInvalidPacket, + }, + { + "packet source channel ID ≠ channel counterparty channel ID", + func() { + path.Setup() + + // use wrong port for dest + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, ibctesting.InvalidID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + }, + types.ErrInvalidPacket, + }, + { + "connection not found", + func() { + path.Setup() + + // pass channel check + suite.chainB.App.GetIBCKeeper().ChannelKeeper.SetChannel( + suite.chainB.GetContext(), + path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, + types.NewChannel(types.OPEN, types.ORDERED, types.NewCounterparty(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID), []string{connIDB}, path.EndpointB.ChannelConfig.Version), + ) + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + }, + connectiontypes.ErrConnectionNotFound, + }, + { + "connection not OPEN", + func() { + path.SetupClients() + + // connection on chainB is in INIT + err := path.EndpointB.ConnOpenInit() + suite.Require().NoError(err) + + // pass channel check + suite.chainB.App.GetIBCKeeper().ChannelKeeper.SetChannel( + suite.chainB.GetContext(), + path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, + types.NewChannel(types.OPEN, types.ORDERED, types.NewCounterparty(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID), []string{path.EndpointB.ConnectionID}, path.EndpointB.ChannelConfig.Version), + ) + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + }, + connectiontypes.ErrInvalidConnectionState, + }, + { + "timeout height passed", + func() { + path.Setup() + + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.GetSelfHeight(suite.chainB.GetContext()), disabledTimeoutTimestamp) + }, + types.ErrTimeoutElapsed, + }, + { + "timeout timestamp passed", + func() { + path.Setup() + + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, disabledTimeoutHeight, uint64(suite.chainB.GetContext().BlockTime().UnixNano())) + }, + types.ErrTimeoutElapsed, + }, + { + "next receive sequence is not found", + func() { + path.SetupConnections() + + path.EndpointA.ChannelID = ibctesting.FirstChannelID + path.EndpointB.ChannelID = ibctesting.FirstChannelID + + // manually creating channel prevents next recv sequence from being set + suite.chainB.App.GetIBCKeeper().ChannelKeeper.SetChannel( + suite.chainB.GetContext(), + path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, + types.NewChannel(types.OPEN, types.ORDERED, types.NewCounterparty(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID), []string{path.EndpointB.ConnectionID}, path.EndpointB.ChannelConfig.Version), + ) + + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + + // manually set packet commitment + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetPacketCommitment(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, packet.GetSequence(), types.CommitPacket(packet)) + + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + err = path.EndpointB.UpdateClient() + suite.Require().NoError(err) + }, + types.ErrSequenceReceiveNotFound, + }, + { + "packet already received", + func() { + path.Setup() + + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + + // set recv seq start to indicate packet was processed in previous upgrade + suite.chainB.App.GetIBCKeeper().ChannelKeeper.SetRecvStartSequence(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, sequence+1) + }, + types.ErrPacketReceived, + }, + { + "receipt already stored", + func() { + path.Setup() + + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + suite.chainB.App.GetIBCKeeper().ChannelKeeper.SetPacketReceipt(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, sequence) + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + }, + types.ErrNoOpMsg, + }, + { + "validation failed", + func() { + // packet commitment not set resulting in invalid proof + path.Setup() + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + }, + commitmenttypes.ErrInvalidProof, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + tc.malleate() + + // get proof of packet commitment from chainA + packetKey := host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + proof, proofHeight := path.EndpointA.QueryProof(packetKey) + + channelVersion, err := suite.chainB.App.GetIBCKeeper().ChannelKeeper.RecvPacket(suite.chainB.GetContext(), packet, proof, proofHeight) + + if tc.expError == nil { + suite.Require().NoError(err) + suite.Require().Equal(path.EndpointA.GetChannel().Version, channelVersion, "channel version is incorrect") + + channelB, _ := suite.chainB.App.GetIBCKeeper().ChannelKeeper.GetChannel(suite.chainB.GetContext(), packet.GetDestPort(), packet.GetDestChannel()) + nextSeqRecv, found := suite.chainB.App.GetIBCKeeper().ChannelKeeper.GetNextSequenceRecv(suite.chainB.GetContext(), packet.GetDestPort(), packet.GetDestChannel()) + suite.Require().True(found) + receipt, receiptStored := suite.chainB.App.GetIBCKeeper().ChannelKeeper.GetPacketReceipt(suite.chainB.GetContext(), packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + + if channelB.Ordering == types.ORDERED { + suite.Require().Equal(packet.GetSequence()+1, nextSeqRecv, "sequence not incremented in ordered channel") + suite.Require().False(receiptStored, "packet receipt stored on ORDERED channel") + } else { + suite.Require().Equal(uint64(1), nextSeqRecv, "sequence incremented for UNORDERED channel") + suite.Require().True(receiptStored, "packet receipt not stored after RecvPacket in UNORDERED channel") + suite.Require().Equal(string([]byte{byte(1)}), receipt, "packet receipt is not empty string") + } + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expError) + suite.Require().Equal("", channelVersion) + } + }) + } +} + +func (suite *KeeperTestSuite) TestWriteAcknowledgement() { + var ( + path *ibctesting.Path + ack exported.Acknowledgement + packet exported.PacketI + ) + + testCases := []testCase{ + { + "success", + func() { + path.Setup() + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + ack = ibcmock.MockAcknowledgement + }, + nil, + }, + {"channel not found", func() { + // use wrong channel naming + path.Setup() + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, ibctesting.InvalidID, ibctesting.InvalidID, defaultTimeoutHeight, disabledTimeoutTimestamp) + ack = ibcmock.MockAcknowledgement + }, types.ErrChannelNotFound}, + {"channel not open", func() { + path.Setup() + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + ack = ibcmock.MockAcknowledgement + + path.EndpointB.UpdateChannel(func(channel *types.Channel) { channel.State = types.CLOSED }) + }, types.ErrInvalidChannelState}, + { + "no-op, already acked", + func() { + path.Setup() + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + ack = ibcmock.MockAcknowledgement + suite.chainB.App.GetIBCKeeper().ChannelKeeper.SetPacketAcknowledgement(suite.chainB.GetContext(), packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), ack.Acknowledgement()) + }, + types.ErrAcknowledgementExists, + }, + { + "empty acknowledgement", + func() { + path.Setup() + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + ack = ibcmock.NewEmptyAcknowledgement() + }, + errorsmod.Wrap(types.ErrInvalidAcknowledgement, ""), + }, + { + "acknowledgement is nil", + func() { + path.Setup() + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + ack = nil + }, + errorsmod.Wrap(types.ErrInvalidAcknowledgement, ""), + }, + { + "packet already received", + func() { + path.Setup() + + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + ack = ibcmock.MockAcknowledgement + + // set recv seq start to indicate packet was processed in previous upgrade + suite.chainB.App.GetIBCKeeper().ChannelKeeper.SetRecvStartSequence(suite.chainB.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, sequence+1) + }, + errorsmod.Wrap(types.ErrPacketReceived, ""), + }, + } + for i, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s, %d/%d tests", tc.msg, i, len(testCases)), func() { + suite.SetupTest() // reset + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + tc.malleate() + + err := suite.chainB.App.GetIBCKeeper().ChannelKeeper.WriteAcknowledgement(suite.chainB.GetContext(), packet, ack) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +// TestAcknowledgePacket tests the call AcknowledgePacket on chainA. +func (suite *KeeperTestSuite) TestAcknowledgePacket() { + var ( + path *ibctesting.Path + packet types.Packet + ack []byte + ) + + assertErr := func(errType *errorsmod.Error) func(commitment []byte, channelVersion string, err error) { + return func(commitment []byte, channelVersion string, err error) { + suite.Require().Error(err) + suite.Require().ErrorIs(err, errType) + suite.Require().NotNil(commitment) + suite.Require().Equal("", channelVersion) + } + } + + assertNoOp := func(commitment []byte, channelVersion string, err error) { + suite.Require().Error(err) + suite.Require().ErrorIs(err, types.ErrNoOpMsg) + suite.Require().Nil(commitment) + suite.Require().Equal("", channelVersion) + } + + assertSuccess := func(seq func() uint64, msg string) func(commitment []byte, channelVersion string, err error) { + return func(commitment []byte, channelVersion string, err error) { + suite.Require().NoError(err) + suite.Require().Nil(commitment) + suite.Require().Equal(path.EndpointA.GetChannel().Version, channelVersion) + + nextSequenceAck, found := suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetNextSequenceAck(suite.chainA.GetContext(), packet.GetSourcePort(), packet.GetSourceChannel()) + + suite.Require().True(found) + suite.Require().Equal(seq(), nextSequenceAck, msg) + } + } + + testCases := []struct { + name string + malleate func() + expResult func(commitment []byte, channelVersion string, err error) + expEvents func(path *ibctesting.Path) []abci.Event + }{ + { + name: "success on ordered channel", + malleate: func() { + path.SetChannelOrdered() + path.Setup() + + // create packet commitment + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + + // create packet receipt and acknowledgement + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + }, + expResult: assertSuccess(func() uint64 { return packet.GetSequence() + 1 }, "sequence not incremented in ordered channel"), + }, + { + name: "success on unordered channel", + malleate: func() { + // setup uses an UNORDERED channel + path.Setup() + + // create packet commitment + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + + // create packet receipt and acknowledgement + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + }, + expResult: assertSuccess(func() uint64 { return uint64(1) }, "sequence incremented for UNORDERED channel"), + }, + { + name: "packet already acknowledged ordered channel (no-op)", + malleate: func() { + path.SetChannelOrdered() + path.Setup() + + // create packet commitment + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + + // create packet receipt and acknowledgement + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + + err = path.EndpointA.AcknowledgePacket(packet, ack) + suite.Require().NoError(err) + }, + expResult: assertNoOp, + }, + { + name: "packet already acknowledged unordered channel (no-op)", + malleate: func() { + // setup uses an UNORDERED channel + path.Setup() + + // create packet commitment + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + + // create packet receipt and acknowledgement + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + + err = path.EndpointA.AcknowledgePacket(packet, ack) + suite.Require().NoError(err) + }, + expResult: assertNoOp, + }, + { + name: "fake acknowledgement", + malleate: func() { + path.Setup() + + // create packet commitment + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + + // create packet + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + + // write packet acknowledgement directly + // Create a valid acknowledgement using deterministic serialization. + ack = types.NewResultAcknowledgement([]byte{byte(1)}).Acknowledgement() + // Introduce non-determinism: insert an extra space after the first character '{' + // This will deserialize correctly but fail to re-serialize to the expected bytes. + if len(ack) > 0 && ack[0] == '{' { + ack = []byte("{ " + string(ack[1:])) + } + path.EndpointB.Chain.Coordinator.UpdateTimeForChain(path.EndpointB.Chain) + + path.EndpointB.Chain.App.GetIBCKeeper().ChannelKeeper.SetPacketAcknowledgement(path.EndpointB.Chain.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, sequence, types.CommitAcknowledgement(ack)) + + path.EndpointB.Chain.NextBlock() + suite.Require().NoError(path.EndpointA.UpdateClient()) + }, + expResult: func(commitment []byte, channelVersion string, err error) { + suite.Require().Error(err) + suite.Require().ErrorIs(err, types.ErrInvalidAcknowledgement) + suite.Require().Equal("", channelVersion) + suite.Require().NotNil(commitment) + }, + }, + { + name: "non-standard acknowledgement", + malleate: func() { + // setup uses an UNORDERED channel + suite.coordinator.Setup(path) + + // create packet commitment + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + + // create packet + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + + // write packet acknowledgement directly + ack = []byte(`{"somethingelse":"anything"}`) + path.EndpointB.Chain.Coordinator.UpdateTimeForChain(path.EndpointB.Chain) + + path.EndpointB.Chain.App.GetIBCKeeper().ChannelKeeper.SetPacketAcknowledgement(path.EndpointB.Chain.GetContext(), path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, sequence, types.CommitAcknowledgement(ack)) + + path.EndpointB.Chain.NextBlock() + suite.Require().NoError(path.EndpointA.UpdateClient()) + }, + expResult: func(commitment []byte, channelVersion string, err error) { + suite.Require().NoError(err) + channel := path.EndpointA.GetChannel() + suite.Require().Equal(channel.Version, channelVersion) + suite.Require().Nil(commitment) + }, + }, + { + name: "channel not found", + malleate: func() { + // use wrong channel naming + path.Setup() + + // create packet commitment + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = types.NewPacket(ibctesting.MockPacketData, sequence, ibctesting.InvalidID, ibctesting.InvalidID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + }, + expResult: assertErr(types.ErrChannelNotFound), + }, + { + name: "channel not open", + malleate: func() { + path.Setup() + + // create packet commitment + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + + path.EndpointA.UpdateChannel(func(channel *types.Channel) { channel.State = types.CLOSED }) + }, + expResult: assertErr(types.ErrInvalidChannelState), + }, + { + name: "packet destination port ≠ channel counterparty port", + malleate: func() { + path.Setup() + + // create packet commitment + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + + // use wrong port for dest + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, ibctesting.InvalidID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + }, + expResult: assertErr(types.ErrInvalidPacket), + }, + { + name: "packet destination channel ID ≠ channel counterparty channel ID", + malleate: func() { + path.Setup() + + // create packet commitment + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + + // use wrong channel for dest + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, ibctesting.InvalidID, defaultTimeoutHeight, disabledTimeoutTimestamp) + }, + expResult: assertErr(types.ErrInvalidPacket), + }, + { + name: "connection not found", + malleate: func() { + path.Setup() + + // create packet commitment + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + + // pass channel check + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetChannel( + suite.chainA.GetContext(), + path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, + types.NewChannel(types.OPEN, types.ORDERED, types.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID), []string{"connection-1000"}, path.EndpointA.GetChannel().Version), + ) + }, + expResult: assertErr(connectiontypes.ErrConnectionNotFound), + }, + { + name: "connection not OPEN", + malleate: func() { + path.Setup() + + // create packet commitment + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + + // connection on chainA is in INIT + err = path.EndpointA.ConnOpenInit() + suite.Require().NoError(err) + + // pass channel check + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetChannel( + suite.chainA.GetContext(), + path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, + types.NewChannel(types.OPEN, types.ORDERED, types.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID), []string{path.EndpointA.ConnectionID}, path.EndpointA.GetChannel().Version), + ) + }, + expResult: assertErr(connectiontypes.ErrInvalidConnectionState), + }, + { + name: "packet hasn't been sent", + malleate: func() { + // packet commitment never written + path.Setup() + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + }, + expResult: assertNoOp, // NOTE: ibc core does not distinguish between unsent and already relayed packets. + }, + { + name: "packet ack verification failed", + malleate: func() { + // skip error code check since error occurs in light-clients + + // ack never written + path.Setup() + + // create packet commitment + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + }, + expResult: assertErr(commitmenttypes.ErrInvalidProof), + }, + { + name: "packet commitment bytes do not match", + malleate: func() { + // setup uses an UNORDERED channel + path.Setup() + + // create packet commitment + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + + // create packet receipt and acknowledgement + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + + packet.Data = []byte("invalid packet commitment") + }, + expResult: assertErr(types.ErrInvalidPacket), + }, + { + name: "next ack sequence not found", + malleate: func() { + path.SetChannelOrdered() + path.Setup() + + // create packet commitment + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + + // manually delete the next sequence ack in the ibc store + storeKey := suite.chainA.GetSimApp().GetKey(exported.ModuleName) + ibcStore := suite.chainA.GetContext().KVStore(storeKey) + + ibcStore.Delete(host.NextSequenceAckKey(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)) + }, + expResult: assertErr(types.ErrSequenceAckNotFound), + }, + { + name: "next ack sequence mismatch ORDERED", + malleate: func() { + path.SetChannelOrdered() + path.Setup() + + // create packet commitment + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + + // create packet acknowledgement + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + + // set next sequence ack wrong + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetNextSequenceAck(suite.chainA.GetContext(), path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, 10) + }, + expResult: assertErr(types.ErrPacketSequenceOutOfOrder), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + // reset ack + ack = ibcmock.MockAcknowledgement.Acknowledgement() + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + ctx := suite.chainA.GetContext() + + tc.malleate() + + packetKey := host.PacketAcknowledgementKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + proof, proofHeight := path.EndpointB.QueryProof(packetKey) + + channelVersion, err := suite.chainA.App.GetIBCKeeper().ChannelKeeper.AcknowledgePacket(ctx, packet, ack, proof, proofHeight) + + commitment := suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetPacketCommitment(ctx, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, packet.GetSequence()) + tc.expResult(commitment, channelVersion, err) + if tc.expEvents != nil { + events := ctx.EventManager().ABCIEvents() + + expEvents := tc.expEvents(path) + + ibctesting.AssertEvents(&suite.Suite, expEvents, events) + } + }) + } +} diff --git a/modules/core/04-channel/keeper/timeout.go b/modules/core/04-channel/keeper/timeout.go new file mode 100644 index 0000000..57febb8 --- /dev/null +++ b/modules/core/04-channel/keeper/timeout.go @@ -0,0 +1,261 @@ +package keeper + +import ( + "bytes" + "strconv" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// TimeoutPacket is called by a module which originally attempted to send a +// packet to a counterparty module, where the timeout height has passed on the +// counterparty chain without the packet being committed, to prove that the +// packet can no longer be executed and to allow the calling module to safely +// perform appropriate state transitions. Its intended usage is within the +// ante handler. +func (k *Keeper) TimeoutPacket( + ctx sdk.Context, + packet types.Packet, + proof []byte, + proofHeight exported.Height, + nextSequenceRecv uint64, +) (string, error) { + channel, found := k.GetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel()) + if !found { + return "", errorsmod.Wrapf( + types.ErrChannelNotFound, + "port ID (%s) channel ID (%s)", packet.GetSourcePort(), packet.GetSourceChannel(), + ) + } + + if packet.GetDestPort() != channel.Counterparty.PortId { + return "", errorsmod.Wrapf( + types.ErrInvalidPacket, + "packet destination port doesn't match the counterparty's port (%s ≠ %s)", packet.GetDestPort(), channel.Counterparty.PortId, + ) + } + + if packet.GetDestChannel() != channel.Counterparty.ChannelId { + return "", errorsmod.Wrapf( + types.ErrInvalidPacket, + "packet destination channel doesn't match the counterparty's channel (%s ≠ %s)", packet.GetDestChannel(), channel.Counterparty.ChannelId, + ) + } + + connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + if !found { + return "", errorsmod.Wrap( + connectiontypes.ErrConnectionNotFound, + channel.ConnectionHops[0], + ) + } + + // check that timeout height or timeout timestamp has passed on the other end + proofTimestamp, err := k.clientKeeper.GetClientTimestampAtHeight(ctx, connectionEnd.ClientId, proofHeight) + if err != nil { + return "", err + } + + timeout := types.NewTimeout(packet.GetTimeoutHeight().(clienttypes.Height), packet.GetTimeoutTimestamp()) + if !timeout.Elapsed(proofHeight.(clienttypes.Height), proofTimestamp) { + return "", errorsmod.Wrap(timeout.ErrTimeoutNotReached(proofHeight.(clienttypes.Height), proofTimestamp), "packet timeout not reached") + } + + commitment := k.GetPacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + + if len(commitment) == 0 { + emitTimeoutPacketEvent(ctx, packet, channel) + // This error indicates that the timeout has already been relayed + // or there is a misconfigured relayer attempting to prove a timeout + // for a packet never sent. Core IBC will treat this error as a no-op in order to + // prevent an entire relay transaction from failing and consuming unnecessary fees. + return "", types.ErrNoOpMsg + } + + packetCommitment := types.CommitPacket(packet) + + // verify we sent the packet and haven't cleared it out yet + if !bytes.Equal(commitment, packetCommitment) { + return "", errorsmod.Wrapf(types.ErrInvalidPacket, "packet commitment bytes are not equal: got (%v), expected (%v)", commitment, packetCommitment) + } + + switch channel.Ordering { + case types.ORDERED: + // check that packet has not been received + if nextSequenceRecv > packet.GetSequence() { + return "", errorsmod.Wrapf( + types.ErrPacketReceived, + "packet already received, next sequence receive > packet sequence (%d > %d)", nextSequenceRecv, packet.GetSequence(), + ) + } + + // check that the recv sequence is as claimed + err = k.connectionKeeper.VerifyNextSequenceRecv( + ctx, connectionEnd, proofHeight, proof, + packet.GetDestPort(), packet.GetDestChannel(), nextSequenceRecv, + ) + case types.UNORDERED: + err = k.connectionKeeper.VerifyPacketReceiptAbsence( + ctx, connectionEnd, proofHeight, proof, + packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), + ) + default: + panic(errorsmod.Wrap(types.ErrInvalidChannelOrdering, channel.Ordering.String())) + } + + if err != nil { + return "", err + } + + if err = k.timeoutExecuted(ctx, channel, packet); err != nil { + return "", err + } + + return channel.Version, nil +} + +// timeoutExecuted deletes the commitment send from this chain after it verifies timeout. +// If the timed-out packet came from an ORDERED channel then this channel will be closed. +// +// CONTRACT: this function must be called in the IBC handler +func (k *Keeper) timeoutExecuted( + ctx sdk.Context, + channel types.Channel, + packet types.Packet, +) error { + k.deletePacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + + if channel.Ordering == types.ORDERED { + // Close the channel since the packet timed-out and the channel is ORDERED + channel.State = types.CLOSED + k.SetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), channel) + emitChannelClosedEvent(ctx, packet, channel) + } + + k.Logger(ctx).Info( + "packet timed-out", + "sequence", strconv.FormatUint(packet.GetSequence(), 10), + "src_port", packet.GetSourcePort(), + "src_channel", packet.GetSourceChannel(), + "dst_port", packet.GetDestPort(), + "dst_channel", packet.GetDestChannel(), + ) + + // emit an event marking that we have processed the timeout + emitTimeoutPacketEvent(ctx, packet, channel) + + return nil +} + +// TimeoutOnClose is called by a module in order to prove that the channel to +// which an unreceived packet was addressed has been closed, so the packet will +// never be received (even if the timeoutHeight has not yet been reached). +func (k *Keeper) TimeoutOnClose( + ctx sdk.Context, + packet types.Packet, + proof, + closedProof []byte, + proofHeight exported.Height, + nextSequenceRecv uint64, +) (string, error) { + channel, found := k.GetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel()) + if !found { + return "", errorsmod.Wrapf(types.ErrChannelNotFound, "port ID (%s) channel ID (%s)", packet.GetSourcePort(), packet.GetSourceChannel()) + } + + if packet.GetDestPort() != channel.Counterparty.PortId { + return "", errorsmod.Wrapf( + types.ErrInvalidPacket, + "packet destination port doesn't match the counterparty's port (%s ≠ %s)", packet.GetDestPort(), channel.Counterparty.PortId, + ) + } + + if packet.GetDestChannel() != channel.Counterparty.ChannelId { + return "", errorsmod.Wrapf( + types.ErrInvalidPacket, + "packet destination channel doesn't match the counterparty's channel (%s ≠ %s)", packet.GetDestChannel(), channel.Counterparty.ChannelId, + ) + } + + connectionEnd, found := k.connectionKeeper.GetConnection(ctx, channel.ConnectionHops[0]) + if !found { + return "", errorsmod.Wrap(connectiontypes.ErrConnectionNotFound, channel.ConnectionHops[0]) + } + + commitment := k.GetPacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + + if len(commitment) == 0 { + emitTimeoutPacketEvent(ctx, packet, channel) + // This error indicates that the timeout has already been relayed + // or there is a misconfigured relayer attempting to prove a timeout + // for a packet never sent. Core IBC will treat this error as a no-op in order to + // prevent an entire relay transaction from failing and consuming unnecessary fees. + return "", types.ErrNoOpMsg + } + + packetCommitment := types.CommitPacket(packet) + + // verify we sent the packet and haven't cleared it out yet + if !bytes.Equal(commitment, packetCommitment) { + return "", errorsmod.Wrapf(types.ErrInvalidPacket, "packet commitment bytes are not equal: got (%v), expected (%v)", commitment, packetCommitment) + } + + counterpartyHops := []string{connectionEnd.Counterparty.ConnectionId} + + counterparty := types.NewCounterparty(packet.GetSourcePort(), packet.GetSourceChannel()) + expectedChannel := types.Channel{ + State: types.CLOSED, + Ordering: channel.Ordering, + Counterparty: counterparty, + ConnectionHops: counterpartyHops, + Version: channel.Version, + } + + // check that the opposing channel end has closed + if err := k.connectionKeeper.VerifyChannelState( + ctx, connectionEnd, proofHeight, closedProof, + channel.Counterparty.PortId, channel.Counterparty.ChannelId, + expectedChannel, + ); err != nil { + return "", err + } + + var err error + switch channel.Ordering { + case types.ORDERED: + // check that packet has not been received + if nextSequenceRecv > packet.GetSequence() { + return "", errorsmod.Wrapf(types.ErrInvalidPacket, "packet already received, next sequence receive > packet sequence (%d > %d", nextSequenceRecv, packet.GetSequence()) + } + + // check that the recv sequence is as claimed + err = k.connectionKeeper.VerifyNextSequenceRecv( + ctx, connectionEnd, proofHeight, proof, + packet.GetDestPort(), packet.GetDestChannel(), nextSequenceRecv, + ) + case types.UNORDERED: + err = k.connectionKeeper.VerifyPacketReceiptAbsence( + ctx, connectionEnd, proofHeight, proof, + packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), + ) + default: + panic(errorsmod.Wrap(types.ErrInvalidChannelOrdering, channel.Ordering.String())) + } + + if err != nil { + return "", err + } + + if err = k.timeoutExecuted(ctx, channel, packet); err != nil { + return "", err + } + + return channel.Version, nil +} diff --git a/modules/core/04-channel/keeper/timeout_test.go b/modules/core/04-channel/keeper/timeout_test.go new file mode 100644 index 0000000..36e4812 --- /dev/null +++ b/modules/core/04-channel/keeper/timeout_test.go @@ -0,0 +1,481 @@ +package keeper_test + +import ( + "errors" + "fmt" + + errorsmod "cosmossdk.io/errors" + + abci "github.com/cometbft/cometbft/abci/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +// TestTimeoutPacket test the TimeoutPacket call on chainA by ensuring the timeout has passed +// on chainB, but that no ack has been written yet. Test cases expected to reach proof +// verification must specify which proof to use using the ordered bool. +func (suite *KeeperTestSuite) TestTimeoutPacket() { + var ( + path *ibctesting.Path + packet types.Packet + nextSeqRecv uint64 + ordered bool + expError *errorsmod.Error + ) + + testCases := []testCase{ + {"success: ORDERED", func() { + ordered = true + path.SetChannelOrdered() + path.Setup() + + timeoutHeight := clienttypes.GetSelfHeight(suite.chainB.GetContext()) + timeoutTimestamp := uint64(suite.chainB.GetContext().BlockTime().UnixNano()) + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, timeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, timeoutTimestamp) + // need to update chainA's client representing chainB to prove missing ack + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + }, nil}, + {"success: UNORDERED", func() { + ordered = false + path.Setup() + + timeoutHeight := clienttypes.GetSelfHeight(suite.chainB.GetContext()) + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) + // need to update chainA's client representing chainB to prove missing ack + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + }, nil}, + {"packet already timed out: ORDERED", func() { + expError = types.ErrNoOpMsg + ordered = true + path.SetChannelOrdered() + path.Setup() + + timeoutHeight := clienttypes.GetSelfHeight(suite.chainB.GetContext()) + timeoutTimestamp := uint64(suite.chainB.GetContext().BlockTime().UnixNano()) + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, timeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + // need to update chainA's client representing chainB to prove missing ack + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, timeoutTimestamp) + err = path.EndpointA.TimeoutPacket(packet) + suite.Require().NoError(err) + }, errorsmod.Wrap(types.ErrNoOpMsg, "")}, + {"packet already timed out: UNORDERED", func() { + expError = types.ErrNoOpMsg + ordered = false + path.Setup() + + timeoutHeight := clienttypes.GetSelfHeight(suite.chainB.GetContext()) + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + // need to update chainA's client representing chainB to prove missing ack + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) + err = path.EndpointA.TimeoutPacket(packet) + suite.Require().NoError(err) + }, errorsmod.Wrap(types.ErrNoOpMsg, "")}, + {"channel not found", func() { + expError = types.ErrChannelNotFound + // use wrong channel naming + path.Setup() + packet = types.NewPacket(ibctesting.MockPacketData, 1, ibctesting.InvalidID, ibctesting.InvalidID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + }, types.ErrChannelNotFound}, + {"packet destination port ≠ channel counterparty port", func() { + expError = types.ErrInvalidPacket + path.Setup() + // use wrong port for dest + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, ibctesting.InvalidID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + }, errorsmod.Wrap(types.ErrInvalidPacket, "")}, + {"packet destination channel ID ≠ channel counterparty channel ID", func() { + expError = types.ErrInvalidPacket + path.Setup() + // use wrong channel for dest + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, ibctesting.InvalidID, defaultTimeoutHeight, disabledTimeoutTimestamp) + }, errorsmod.Wrap(types.ErrInvalidPacket, "")}, + {"connection not found", func() { + expError = connectiontypes.ErrConnectionNotFound + // pass channel check + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetChannel( + suite.chainA.GetContext(), + path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, + types.NewChannel(types.OPEN, types.ORDERED, types.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID), []string{connIDA}, path.EndpointA.ChannelConfig.Version), + ) + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + }, errorsmod.Wrap(connectiontypes.ErrConnectionNotFound, "")}, + {"timeout", func() { + expError = types.ErrTimeoutNotReached + path.Setup() + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + }, errorsmod.Wrap(types.ErrTimeoutNotReached, "")}, + {"packet already received ", func() { + expError = types.ErrPacketReceived + ordered = true + path.SetChannelOrdered() + path.Setup() + + nextSeqRecv = 2 + timeoutTimestamp := uint64(suite.chainB.GetContext().BlockTime().UnixNano()) + + sequence, err := path.EndpointA.SendPacket(defaultTimeoutHeight, timeoutTimestamp, ibctesting.MockPacketData) + + suite.Require().NoError(err) + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, timeoutTimestamp) + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + }, errorsmod.Wrap(types.ErrPacketReceived, "")}, + {"packet hasn't been sent", func() { + expError = types.ErrNoOpMsg + ordered = true + path.SetChannelOrdered() + + path.Setup() + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, uint64(suite.chainB.GetContext().BlockTime().UnixNano())) + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + }, errorsmod.Wrap(types.ErrNoOpMsg, "")}, + {"next seq receive verification failed", func() { + // skip error check, error occurs in light-clients + + // set ordered to false resulting in wrong proof provided + ordered = false + + path.SetChannelOrdered() + path.Setup() + + timeoutHeight := clienttypes.GetSelfHeight(suite.chainB.GetContext()) + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + }, errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "")}, + {"packet ack verification failed", func() { + // skip error check, error occurs in light-clients + + // set ordered to true resulting in wrong proof provided + ordered = true + + path.Setup() + + timeoutHeight := clienttypes.GetSelfHeight(suite.chainB.GetContext()) + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + }, errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "")}, + } + + for _, tc := range testCases { + suite.Run(tc.msg, func() { + var ( + proof []byte + proofHeight exported.Height + ) + + suite.SetupTest() // reset + expError = nil // must be expliticly changed by failed cases + nextSeqRecv = 1 // must be explicitly changed + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + tc.malleate() + + orderedPacketKey := host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) + unorderedPacketKey := host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + + if path.EndpointB.ConnectionID != "" { + if ordered { + proof, proofHeight = path.EndpointB.QueryProof(orderedPacketKey) + } else { + proof, proofHeight = path.EndpointB.QueryProof(unorderedPacketKey) + } + } + + channelVersion, err := suite.chainA.App.GetIBCKeeper().ChannelKeeper.TimeoutPacket(suite.chainA.GetContext(), packet, proof, proofHeight, nextSeqRecv) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().Equal(path.EndpointA.GetChannel().Version, channelVersion) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + suite.Require().Equal("", channelVersion) + // only check if expError is set, since not all error codes can be known + if expError != nil { + suite.Require().True(errors.Is(err, expError)) + } + } + }) + } +} + +// TestTimeoutExecuted verifies that packet commitments are deleted. +// In addition, the test verifies that the channel state +// after a timeout is updated accordingly. +func (suite *KeeperTestSuite) TestTimeoutExecuted() { + var ( + path *ibctesting.Path + packet types.Packet + ) + + testCases := []struct { + msg string + malleate func() + expResult func(packetCommitment []byte, err error) + expEvents func(path *ibctesting.Path) []abci.Event + }{ + { + "success ORDERED", + func() { + path.SetChannelOrdered() + path.Setup() + + timeoutHeight := clienttypes.GetSelfHeight(suite.chainB.GetContext()) + timeoutTimestamp := uint64(suite.chainB.GetContext().BlockTime().UnixNano()) + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, timeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, timeoutTimestamp) + }, + func(packetCommitment []byte, err error) { + suite.Require().NoError(err) + suite.Require().Nil(packetCommitment) + + // Check channel has been closed + channel := path.EndpointA.GetChannel() + suite.Require().Equal(channel.State, types.CLOSED) + }, + nil, + }, + } + + for i, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s, %d/%d tests", tc.msg, i, len(testCases)), func() { + suite.SetupTest() // reset + path = ibctesting.NewPath(suite.chainA, suite.chainB) + ctx := suite.chainA.GetContext() + + tc.malleate() + + err := suite.chainA.App.GetIBCKeeper().ChannelKeeper.TimeoutExecuted(ctx, path.EndpointA.GetChannel(), packet) + pc := suite.chainA.App.GetIBCKeeper().ChannelKeeper.GetPacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + + tc.expResult(pc, err) + if tc.expEvents != nil { + events := ctx.EventManager().ABCIEvents() + + expEvents := tc.expEvents(path) + + ibctesting.AssertEvents(&suite.Suite, expEvents, events) + } + }) + } +} + +// TestTimeoutOnClose tests the call TimeoutOnClose on chainA by closing the corresponding +// channel on chainB after the packet commitment has been created. +func (suite *KeeperTestSuite) TestTimeoutOnClose() { + var ( + path *ibctesting.Path + packet types.Packet + nextSeqRecv uint64 + ordered bool + ) + + testCases := []testCase{ + {"success: ORDERED", func() { + ordered = true + path.SetChannelOrdered() + path.Setup() + + timeoutHeight := clienttypes.GetSelfHeight(suite.chainB.GetContext()) + timeoutTimestamp := uint64(suite.chainB.GetContext().BlockTime().UnixNano()) + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, timeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + path.EndpointB.UpdateChannel(func(channel *types.Channel) { channel.State = types.CLOSED }) + // need to update chainA's client representing chainB to prove missing ack + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, timeoutTimestamp) + }, nil}, + {"success: UNORDERED", func() { + ordered = false + path.Setup() + + timeoutHeight := clienttypes.GetSelfHeight(suite.chainB.GetContext()) + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + path.EndpointB.UpdateChannel(func(channel *types.Channel) { channel.State = types.CLOSED }) + // need to update chainA's client representing chainB to prove missing ack + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) + }, nil}, + {"channel not found", func() { + // use wrong channel naming + path.Setup() + packet = types.NewPacket(ibctesting.MockPacketData, 1, ibctesting.InvalidID, ibctesting.InvalidID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + }, types.ErrChannelNotFound}, + {"packet dest port ≠ channel counterparty port", func() { + path.Setup() + // use wrong port for dest + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, ibctesting.InvalidID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + }, errorsmod.Wrap(types.ErrInvalidPacket, "")}, + {"packet dest channel ID ≠ channel counterparty channel ID", func() { + path.Setup() + // use wrong channel for dest + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, ibctesting.InvalidID, defaultTimeoutHeight, disabledTimeoutTimestamp) + }, errorsmod.Wrap(types.ErrInvalidPacket, "")}, + {"connection not found", func() { + // pass channel check + suite.chainA.App.GetIBCKeeper().ChannelKeeper.SetChannel( + suite.chainA.GetContext(), + path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, + types.NewChannel(types.OPEN, types.ORDERED, types.NewCounterparty(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID), []string{connIDA}, path.EndpointA.ChannelConfig.Version), + ) + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, defaultTimeoutHeight, disabledTimeoutTimestamp) + }, errorsmod.Wrap(connectiontypes.ErrConnectionNotFound, "")}, + {"packet hasn't been sent ORDERED", func() { + path.SetChannelOrdered() + path.Setup() + + packet = types.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.GetSelfHeight(suite.chainB.GetContext()), uint64(suite.chainB.GetContext().BlockTime().UnixNano())) + }, errorsmod.Wrap(types.ErrNoOpMsg, "")}, + {"packet already received ORDERED", func() { + path.SetChannelOrdered() + nextSeqRecv = 2 + ordered = true + path.Setup() + + timeoutHeight := clienttypes.GetSelfHeight(suite.chainB.GetContext()) + timeoutTimestamp := uint64(suite.chainB.GetContext().BlockTime().UnixNano()) + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, timeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + path.EndpointB.UpdateChannel(func(channel *types.Channel) { channel.State = types.CLOSED }) + // need to update chainA's client representing chainB to prove missing ack + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, timeoutTimestamp) + }, errorsmod.Wrap(types.ErrInvalidPacket, "")}, + {"channel verification failed ORDERED", func() { + ordered = true + path.SetChannelOrdered() + path.Setup() + + timeoutHeight := clienttypes.GetSelfHeight(suite.chainB.GetContext()) + timeoutTimestamp := uint64(suite.chainB.GetContext().BlockTime().UnixNano()) + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, timeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, timeoutTimestamp) + }, ibcerrors.ErrInvalidHeight}, + {"next seq receive verification failed ORDERED", func() { + // set ordered to false providing the wrong proof for ORDERED case + ordered = false + path.SetChannelOrdered() + path.Setup() + + timeoutHeight := clienttypes.GetSelfHeight(suite.chainB.GetContext()) + timeoutTimestamp := uint64(suite.chainB.GetContext().BlockTime().UnixNano()) + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, timeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + path.EndpointB.UpdateChannel(func(channel *types.Channel) { channel.State = types.CLOSED }) + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.GetSelfHeight(suite.chainB.GetContext()), uint64(suite.chainB.GetContext().BlockTime().UnixNano())) + }, errorsmod.Wrap(types.ErrInvalidPacket, "")}, + {"packet ack verification failed", func() { + // set ordered to true providing the wrong proof for UNORDERED case + ordered = true + path.Setup() + + timeoutHeight := clienttypes.GetSelfHeight(suite.chainB.GetContext()) + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, disabledTimeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + path.EndpointB.UpdateChannel(func(channel *types.Channel) { channel.State = types.CLOSED }) + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + packet = types.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, disabledTimeoutTimestamp) + }, errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "")}, + } + + for i, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s, %d/%d tests", tc.msg, i, len(testCases)), func() { + var proof []byte + + suite.SetupTest() // reset + nextSeqRecv = 1 // must be explicitly changed + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + tc.malleate() + + channelKey := host.ChannelKey(packet.GetDestPort(), packet.GetDestChannel()) + unorderedPacketKey := host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + orderedPacketKey := host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) + + closedProof, proofHeight := suite.chainB.QueryProof(channelKey) + + if ordered { + proof, _ = suite.chainB.QueryProof(orderedPacketKey) + } else { + proof, _ = suite.chainB.QueryProof(unorderedPacketKey) + } + + channelVersion, err := suite.chainA.App.GetIBCKeeper().ChannelKeeper.TimeoutOnClose( + suite.chainA.GetContext(), + packet, + proof, + closedProof, + proofHeight, + nextSeqRecv, + ) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().Equal(path.EndpointA.GetChannel().Version, channelVersion) + } else { + suite.Require().Error(err) + suite.Require().Equal("", channelVersion) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} diff --git a/modules/core/04-channel/migrations/v10/channel.pb.go b/modules/core/04-channel/migrations/v10/channel.pb.go new file mode 100644 index 0000000..fd7d861 --- /dev/null +++ b/modules/core/04-channel/migrations/v10/channel.pb.go @@ -0,0 +1,2964 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc.core.channel.v0/channel.proto + +package v10 + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + types "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// State defines if a channel is in one of the following states: +// CLOSED, INIT, TRYOPEN, OPEN, FLUSHING, FLUSHCOMPLETE or UNINITIALIZED. +type State int32 + +const ( + // Default State + UNINITIALIZED State = 0 + // A channel has just started the opening handshake. + INIT State = 1 + // A channel has acknowledged the handshake step on the counterparty chain. + TRYOPEN State = 2 + // A channel has completed the handshake. Open channels are + // ready to send and receive packets. + OPEN State = 3 + // A channel has been closed and can no longer be used to send or receive + // packets. + CLOSED State = 4 + // A channel has just accepted the upgrade handshake attempt and is flushing in-flight packets. + FLUSHING State = 5 + // A channel has just completed flushing any in-flight packets. + FLUSHCOMPLETE State = 6 +) + +var State_name = map[int32]string{ + 0: "STATE_UNINITIALIZED_UNSPECIFIED", + 1: "STATE_INIT", + 2: "STATE_TRYOPEN", + 3: "STATE_OPEN", + 4: "STATE_CLOSED", + 5: "STATE_FLUSHING", + 6: "STATE_FLUSHCOMPLETE", +} + +var State_value = map[string]int32{ + "STATE_UNINITIALIZED_UNSPECIFIED": 0, + "STATE_INIT": 1, + "STATE_TRYOPEN": 2, + "STATE_OPEN": 3, + "STATE_CLOSED": 4, + "STATE_FLUSHING": 5, + "STATE_FLUSHCOMPLETE": 6, +} + +func (x State) String() string { + return proto.EnumName(State_name, int32(x)) +} + +func (State) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_c3a07336710636a0, []int{0} +} + +// Order defines if a channel is ORDERED or UNORDERED +type Order int32 + +const ( + // zero-value for channel ordering + NONE Order = 0 + // packets can be delivered in any order, which may differ from the order in + // which they were sent. + UNORDERED Order = 1 + // packets are delivered exactly in the order which they were sent + ORDERED Order = 2 +) + +var Order_name = map[int32]string{ + 0: "ORDER_NONE_UNSPECIFIED", + 1: "ORDER_UNORDERED", + 2: "ORDER_ORDERED", +} + +var Order_value = map[string]int32{ + "ORDER_NONE_UNSPECIFIED": 0, + "ORDER_UNORDERED": 1, + "ORDER_ORDERED": 2, +} + +func (x Order) String() string { + return proto.EnumName(Order_name, int32(x)) +} + +func (Order) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_c3a07336710636a0, []int{1} +} + +// Channel defines pipeline for exactly-once packet delivery between specific +// modules on separate blockchains, which has at least one end capable of +// sending packets and one end capable of receiving packets. +type Channel struct { + // current state of the channel end + State State `protobuf:"varint,1,opt,name=state,proto3,enum=ibc.core.channel.v0.State" json:"state,omitempty"` + // whether the channel is ordered or unordered + Ordering Order `protobuf:"varint,2,opt,name=ordering,proto3,enum=ibc.core.channel.v0.Order" json:"ordering,omitempty"` + // counterparty channel end + Counterparty Counterparty `protobuf:"bytes,3,opt,name=counterparty,proto3" json:"counterparty"` + // list of connection identifiers, in order, along which packets sent on + // this channel will travel + ConnectionHops []string `protobuf:"bytes,4,rep,name=connection_hops,json=connectionHops,proto3" json:"connection_hops,omitempty"` + // opaque channel version, which is agreed upon during the handshake + Version string `protobuf:"bytes,5,opt,name=version,proto3" json:"version,omitempty"` + // upgrade sequence indicates the latest upgrade attempt performed by this channel + // the value of 0 indicates the channel has never been upgraded + UpgradeSequence uint64 `protobuf:"varint,6,opt,name=upgrade_sequence,json=upgradeSequence,proto3" json:"upgrade_sequence,omitempty"` +} + +func (m *Channel) Reset() { *m = Channel{} } +func (m *Channel) String() string { return proto.CompactTextString(m) } +func (*Channel) ProtoMessage() {} +func (*Channel) Descriptor() ([]byte, []int) { + return fileDescriptor_c3a07336710636a0, []int{0} +} +func (m *Channel) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Channel) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Channel.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Channel) XXX_Merge(src proto.Message) { + xxx_messageInfo_Channel.Merge(m, src) +} +func (m *Channel) XXX_Size() int { + return m.Size() +} +func (m *Channel) XXX_DiscardUnknown() { + xxx_messageInfo_Channel.DiscardUnknown(m) +} + +var xxx_messageInfo_Channel proto.InternalMessageInfo + +// IdentifiedChannel defines a channel with additional port and channel +// identifier fields. +type IdentifiedChannel struct { + // current state of the channel end + State State `protobuf:"varint,1,opt,name=state,proto3,enum=ibc.core.channel.v0.State" json:"state,omitempty"` + // whether the channel is ordered or unordered + Ordering Order `protobuf:"varint,2,opt,name=ordering,proto3,enum=ibc.core.channel.v0.Order" json:"ordering,omitempty"` + // counterparty channel end + Counterparty Counterparty `protobuf:"bytes,3,opt,name=counterparty,proto3" json:"counterparty"` + // list of connection identifiers, in order, along which packets sent on + // this channel will travel + ConnectionHops []string `protobuf:"bytes,4,rep,name=connection_hops,json=connectionHops,proto3" json:"connection_hops,omitempty"` + // opaque channel version, which is agreed upon during the handshake + Version string `protobuf:"bytes,5,opt,name=version,proto3" json:"version,omitempty"` + // port identifier + PortId string `protobuf:"bytes,6,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + // channel identifier + ChannelId string `protobuf:"bytes,7,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + // upgrade sequence indicates the latest upgrade attempt performed by this channel + // the value of 0 indicates the channel has never been upgraded + UpgradeSequence uint64 `protobuf:"varint,8,opt,name=upgrade_sequence,json=upgradeSequence,proto3" json:"upgrade_sequence,omitempty"` +} + +func (m *IdentifiedChannel) Reset() { *m = IdentifiedChannel{} } +func (m *IdentifiedChannel) String() string { return proto.CompactTextString(m) } +func (*IdentifiedChannel) ProtoMessage() {} +func (*IdentifiedChannel) Descriptor() ([]byte, []int) { + return fileDescriptor_c3a07336710636a0, []int{1} +} +func (m *IdentifiedChannel) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *IdentifiedChannel) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_IdentifiedChannel.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *IdentifiedChannel) XXX_Merge(src proto.Message) { + xxx_messageInfo_IdentifiedChannel.Merge(m, src) +} +func (m *IdentifiedChannel) XXX_Size() int { + return m.Size() +} +func (m *IdentifiedChannel) XXX_DiscardUnknown() { + xxx_messageInfo_IdentifiedChannel.DiscardUnknown(m) +} + +var xxx_messageInfo_IdentifiedChannel proto.InternalMessageInfo + +// Counterparty defines a channel end counterparty +type Counterparty struct { + // port on the counterparty chain which owns the other end of the channel. + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + // channel end on the counterparty chain + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` +} + +func (m *Counterparty) Reset() { *m = Counterparty{} } +func (m *Counterparty) String() string { return proto.CompactTextString(m) } +func (*Counterparty) ProtoMessage() {} +func (*Counterparty) Descriptor() ([]byte, []int) { + return fileDescriptor_c3a07336710636a0, []int{2} +} +func (m *Counterparty) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Counterparty) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Counterparty.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Counterparty) XXX_Merge(src proto.Message) { + xxx_messageInfo_Counterparty.Merge(m, src) +} +func (m *Counterparty) XXX_Size() int { + return m.Size() +} +func (m *Counterparty) XXX_DiscardUnknown() { + xxx_messageInfo_Counterparty.DiscardUnknown(m) +} + +var xxx_messageInfo_Counterparty proto.InternalMessageInfo + +// Packet defines a type that carries data across different chains through IBC +type Packet struct { + // number corresponds to the order of sends and receives, where a Packet + // with an earlier sequence number must be sent and received before a Packet + // with a later sequence number. + Sequence uint64 `protobuf:"varint,1,opt,name=sequence,proto3" json:"sequence,omitempty"` + // identifies the port on the sending chain. + SourcePort string `protobuf:"bytes,2,opt,name=source_port,json=sourcePort,proto3" json:"source_port,omitempty"` + // identifies the channel end on the sending chain. + SourceChannel string `protobuf:"bytes,3,opt,name=source_channel,json=sourceChannel,proto3" json:"source_channel,omitempty"` + // identifies the port on the receiving chain. + DestinationPort string `protobuf:"bytes,4,opt,name=destination_port,json=destinationPort,proto3" json:"destination_port,omitempty"` + // identifies the channel end on the receiving chain. + DestinationChannel string `protobuf:"bytes,5,opt,name=destination_channel,json=destinationChannel,proto3" json:"destination_channel,omitempty"` + // actual opaque bytes transferred directly to the application module + Data []byte `protobuf:"bytes,6,opt,name=data,proto3" json:"data,omitempty"` + // block height after which the packet times out + TimeoutHeight types.Height `protobuf:"bytes,7,opt,name=timeout_height,json=timeoutHeight,proto3" json:"timeout_height"` + // block timestamp (in nanoseconds) after which the packet times out + TimeoutTimestamp uint64 `protobuf:"varint,8,opt,name=timeout_timestamp,json=timeoutTimestamp,proto3" json:"timeout_timestamp,omitempty"` +} + +func (m *Packet) Reset() { *m = Packet{} } +func (m *Packet) String() string { return proto.CompactTextString(m) } +func (*Packet) ProtoMessage() {} +func (*Packet) Descriptor() ([]byte, []int) { + return fileDescriptor_c3a07336710636a0, []int{3} +} +func (m *Packet) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Packet) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Packet.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Packet) XXX_Merge(src proto.Message) { + xxx_messageInfo_Packet.Merge(m, src) +} +func (m *Packet) XXX_Size() int { + return m.Size() +} +func (m *Packet) XXX_DiscardUnknown() { + xxx_messageInfo_Packet.DiscardUnknown(m) +} + +var xxx_messageInfo_Packet proto.InternalMessageInfo + +// PacketState defines the generic type necessary to retrieve and store +// packet commitments, acknowledgements, and receipts. +// Caller is responsible for knowing the context necessary to interpret this +// state as a commitment, acknowledgement, or a receipt. +type PacketState struct { + // channel port identifier. + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + // channel unique identifier. + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + // packet sequence. + Sequence uint64 `protobuf:"varint,3,opt,name=sequence,proto3" json:"sequence,omitempty"` + // embedded data that represents packet state. + Data []byte `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"` +} + +func (m *PacketState) Reset() { *m = PacketState{} } +func (m *PacketState) String() string { return proto.CompactTextString(m) } +func (*PacketState) ProtoMessage() {} +func (*PacketState) Descriptor() ([]byte, []int) { + return fileDescriptor_c3a07336710636a0, []int{4} +} +func (m *PacketState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PacketState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PacketState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *PacketState) XXX_Merge(src proto.Message) { + xxx_messageInfo_PacketState.Merge(m, src) +} +func (m *PacketState) XXX_Size() int { + return m.Size() +} +func (m *PacketState) XXX_DiscardUnknown() { + xxx_messageInfo_PacketState.DiscardUnknown(m) +} + +var xxx_messageInfo_PacketState proto.InternalMessageInfo + +// PacketId is an identifier for a unique Packet +// Source chains refer to packets by source port/channel +// Destination chains refer to packets by destination port/channel +type PacketId struct { + // channel port identifier + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + // channel unique identifier + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + // packet sequence + Sequence uint64 `protobuf:"varint,3,opt,name=sequence,proto3" json:"sequence,omitempty"` +} + +func (m *PacketId) Reset() { *m = PacketId{} } +func (m *PacketId) String() string { return proto.CompactTextString(m) } +func (*PacketId) ProtoMessage() {} +func (*PacketId) Descriptor() ([]byte, []int) { + return fileDescriptor_c3a07336710636a0, []int{5} +} +func (m *PacketId) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PacketId) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PacketId.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *PacketId) XXX_Merge(src proto.Message) { + xxx_messageInfo_PacketId.Merge(m, src) +} +func (m *PacketId) XXX_Size() int { + return m.Size() +} +func (m *PacketId) XXX_DiscardUnknown() { + xxx_messageInfo_PacketId.DiscardUnknown(m) +} + +var xxx_messageInfo_PacketId proto.InternalMessageInfo + +// Acknowledgement is the recommended acknowledgement format to be used by +// app-specific protocols. +// NOTE: The field numbers 21 and 22 were explicitly chosen to avoid accidental +// conflicts with other protobuf message formats used for acknowledgements. +// The first byte of any message with this format will be the non-ASCII values +// `0xaa` (result) or `0xb2` (error). Implemented as defined by ICS: +// https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope +type Acknowledgement struct { + // response contains either a result or an error and must be non-empty + // + // Types that are valid to be assigned to Response: + // *Acknowledgement_Result + // *Acknowledgement_Error + Response isAcknowledgement_Response `protobuf_oneof:"response"` +} + +func (m *Acknowledgement) Reset() { *m = Acknowledgement{} } +func (m *Acknowledgement) String() string { return proto.CompactTextString(m) } +func (*Acknowledgement) ProtoMessage() {} +func (*Acknowledgement) Descriptor() ([]byte, []int) { + return fileDescriptor_c3a07336710636a0, []int{6} +} +func (m *Acknowledgement) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Acknowledgement) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Acknowledgement.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Acknowledgement) XXX_Merge(src proto.Message) { + xxx_messageInfo_Acknowledgement.Merge(m, src) +} +func (m *Acknowledgement) XXX_Size() int { + return m.Size() +} +func (m *Acknowledgement) XXX_DiscardUnknown() { + xxx_messageInfo_Acknowledgement.DiscardUnknown(m) +} + +var xxx_messageInfo_Acknowledgement proto.InternalMessageInfo + +type isAcknowledgement_Response interface { + isAcknowledgement_Response() + MarshalTo([]byte) (int, error) + Size() int +} + +type Acknowledgement_Result struct { + Result []byte `protobuf:"bytes,21,opt,name=result,proto3,oneof" json:"result,omitempty"` +} +type Acknowledgement_Error struct { + Error string `protobuf:"bytes,22,opt,name=error,proto3,oneof" json:"error,omitempty"` +} + +func (*Acknowledgement_Result) isAcknowledgement_Response() {} +func (*Acknowledgement_Error) isAcknowledgement_Response() {} + +func (m *Acknowledgement) GetResponse() isAcknowledgement_Response { + if m != nil { + return m.Response + } + return nil +} + +func (m *Acknowledgement) GetResult() []byte { + if x, ok := m.GetResponse().(*Acknowledgement_Result); ok { + return x.Result + } + return nil +} + +func (m *Acknowledgement) GetError() string { + if x, ok := m.GetResponse().(*Acknowledgement_Error); ok { + return x.Error + } + return "" +} + +// XXX_OneofWrappers is for the internal use of the proto package. +func (*Acknowledgement) XXX_OneofWrappers() []interface{} { + return []interface{}{ + (*Acknowledgement_Result)(nil), + (*Acknowledgement_Error)(nil), + } +} + +// Timeout defines an execution deadline structure for 04-channel handlers. +// This includes packet lifecycle handlers as well as the upgrade handshake handlers. +// A valid Timeout contains either one or both of a timestamp and block height (sequence). +type Timeout struct { + // block height after which the packet or upgrade times out + Height types.Height `protobuf:"bytes,1,opt,name=height,proto3" json:"height"` + // block timestamp (in nanoseconds) after which the packet or upgrade times out + Timestamp uint64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` +} + +func (m *Timeout) Reset() { *m = Timeout{} } +func (m *Timeout) String() string { return proto.CompactTextString(m) } +func (*Timeout) ProtoMessage() {} +func (*Timeout) Descriptor() ([]byte, []int) { + return fileDescriptor_c3a07336710636a0, []int{7} +} +func (m *Timeout) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Timeout) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Timeout.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Timeout) XXX_Merge(src proto.Message) { + xxx_messageInfo_Timeout.Merge(m, src) +} +func (m *Timeout) XXX_Size() int { + return m.Size() +} +func (m *Timeout) XXX_DiscardUnknown() { + xxx_messageInfo_Timeout.DiscardUnknown(m) +} + +var xxx_messageInfo_Timeout proto.InternalMessageInfo + +func (m *Timeout) GetHeight() types.Height { + if m != nil { + return m.Height + } + return types.Height{} +} + +func (m *Timeout) GetTimestamp() uint64 { + if m != nil { + return m.Timestamp + } + return 0 +} + +// Params defines the set of IBC channel parameters. +type Params struct { + // the relative timeout after which channel upgrades will time out. + UpgradeTimeout Timeout `protobuf:"bytes,1,opt,name=upgrade_timeout,json=upgradeTimeout,proto3" json:"upgrade_timeout"` +} + +func (m *Params) Reset() { *m = Params{} } +func (m *Params) String() string { return proto.CompactTextString(m) } +func (*Params) ProtoMessage() {} +func (*Params) Descriptor() ([]byte, []int) { + return fileDescriptor_c3a07336710636a0, []int{8} +} +func (m *Params) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Params) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params.Merge(m, src) +} +func (m *Params) XXX_Size() int { + return m.Size() +} +func (m *Params) XXX_DiscardUnknown() { + xxx_messageInfo_Params.DiscardUnknown(m) +} + +var xxx_messageInfo_Params proto.InternalMessageInfo + +func (m *Params) GetUpgradeTimeout() Timeout { + if m != nil { + return m.UpgradeTimeout + } + return Timeout{} +} + +func init() { + proto.RegisterEnum("ibc.core.channel.v0.State", State_name, State_value) + proto.RegisterEnum("ibc.core.channel.v0.Order", Order_name, Order_value) + proto.RegisterType((*Channel)(nil), "ibc.core.channel.v0.Channel") + proto.RegisterType((*IdentifiedChannel)(nil), "ibc.core.channel.v0.IdentifiedChannel") + proto.RegisterType((*Counterparty)(nil), "ibc.core.channel.v0.Counterparty") + proto.RegisterType((*Packet)(nil), "ibc.core.channel.v0.Packet") + proto.RegisterType((*PacketState)(nil), "ibc.core.channel.v0.PacketState") + proto.RegisterType((*PacketId)(nil), "ibc.core.channel.v0.PacketId") + proto.RegisterType((*Acknowledgement)(nil), "ibc.core.channel.v0.Acknowledgement") + proto.RegisterType((*Timeout)(nil), "ibc.core.channel.v0.Timeout") + proto.RegisterType((*Params)(nil), "ibc.core.channel.v0.Params") +} + +func init() { proto.RegisterFile("ibc.core.channel.v0/channel.proto", fileDescriptor_c3a07336710636a0) } + +var fileDescriptor_c3a07336710636a0 = []byte{ + // 936 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x56, 0x4f, 0x6f, 0xe2, 0x46, + 0x14, 0xc7, 0xc4, 0xfc, 0x7b, 0x49, 0xc0, 0x3b, 0x69, 0x53, 0xcb, 0x4a, 0xc1, 0x8b, 0x5a, 0x95, + 0x4d, 0xb5, 0x90, 0x6c, 0xab, 0xaa, 0xda, 0x5b, 0x02, 0xde, 0xc5, 0x5a, 0x0a, 0xc8, 0xc0, 0xa1, + 0x7b, 0x41, 0xc6, 0x9e, 0x82, 0xb5, 0xe0, 0xa1, 0xf6, 0xc0, 0x6a, 0xd5, 0x73, 0xa5, 0x15, 0xa7, + 0x7e, 0x01, 0xa4, 0x4a, 0xfd, 0x0a, 0xfd, 0x10, 0x7b, 0xdc, 0xe3, 0x9e, 0xaa, 0x2a, 0xf9, 0x0e, + 0x3d, 0x57, 0x9e, 0x19, 0x07, 0x88, 0xa2, 0xa8, 0xaa, 0xd4, 0x5b, 0x4f, 0xcc, 0xfb, 0xbd, 0xdf, + 0x7b, 0xbf, 0xf7, 0x67, 0x18, 0x19, 0x1e, 0x7a, 0x23, 0xa7, 0xe6, 0x90, 0x00, 0xd7, 0x9c, 0x89, + 0xed, 0xfb, 0x78, 0x5a, 0x5b, 0x9e, 0xc7, 0xc7, 0xea, 0x3c, 0x20, 0x94, 0xa0, 0x23, 0x6f, 0xe4, + 0x54, 0x23, 0x4a, 0x35, 0xc6, 0x97, 0xe7, 0xda, 0x47, 0x63, 0x32, 0x26, 0xcc, 0x5f, 0x8b, 0x4e, + 0x9c, 0xaa, 0x95, 0x36, 0xd9, 0xa6, 0x1e, 0xf6, 0x29, 0x4b, 0xc6, 0x4e, 0x9c, 0x50, 0xfe, 0x3d, + 0x09, 0x99, 0x3a, 0xcf, 0x82, 0xce, 0x20, 0x15, 0x52, 0x9b, 0x62, 0x55, 0xd2, 0xa5, 0x4a, 0xfe, + 0x89, 0x56, 0xbd, 0x43, 0xa7, 0xda, 0x8b, 0x18, 0x16, 0x27, 0xa2, 0x6f, 0x20, 0x4b, 0x02, 0x17, + 0x07, 0x9e, 0x3f, 0x56, 0x93, 0xf7, 0x04, 0x75, 0x22, 0x92, 0x75, 0xc3, 0x45, 0x2f, 0xe0, 0xc0, + 0x21, 0x0b, 0x9f, 0xe2, 0x60, 0x6e, 0x07, 0xf4, 0x8d, 0xba, 0xa7, 0x4b, 0x95, 0xfd, 0x27, 0x0f, + 0xef, 0x8c, 0xad, 0x6f, 0x11, 0x2f, 0xe5, 0x77, 0x7f, 0x94, 0x12, 0xd6, 0x4e, 0x30, 0xfa, 0x02, + 0x0a, 0x0e, 0xf1, 0x7d, 0xec, 0x50, 0x8f, 0xf8, 0xc3, 0x09, 0x99, 0x87, 0xaa, 0xac, 0xef, 0x55, + 0x72, 0x56, 0x7e, 0x03, 0x37, 0xc9, 0x3c, 0x44, 0x2a, 0x64, 0x96, 0x38, 0x08, 0x3d, 0xe2, 0xab, + 0x29, 0x5d, 0xaa, 0xe4, 0xac, 0xd8, 0x44, 0x8f, 0x40, 0x59, 0xcc, 0xc7, 0x81, 0xed, 0xe2, 0x61, + 0x88, 0x7f, 0x5c, 0x60, 0xdf, 0xc1, 0x6a, 0x5a, 0x97, 0x2a, 0xb2, 0x55, 0x10, 0x78, 0x4f, 0xc0, + 0x4f, 0xe5, 0xb7, 0xbf, 0x96, 0x12, 0xe5, 0xbf, 0x92, 0xf0, 0xc0, 0x74, 0xb1, 0x4f, 0xbd, 0x1f, + 0x3c, 0xec, 0xfe, 0x3f, 0xc0, 0x4f, 0x20, 0x33, 0x27, 0x01, 0x1d, 0x7a, 0x2e, 0x9b, 0x5b, 0xce, + 0x4a, 0x47, 0xa6, 0xe9, 0xa2, 0x4f, 0x01, 0x44, 0x29, 0x91, 0x2f, 0xc3, 0x7c, 0x39, 0x81, 0x98, + 0xee, 0x9d, 0x83, 0xcf, 0xde, 0x37, 0xf8, 0x16, 0x1c, 0x6c, 0xf7, 0xb3, 0x2d, 0x2c, 0xdd, 0x23, + 0x9c, 0xbc, 0x25, 0x2c, 0xb2, 0x7d, 0x48, 0x42, 0xba, 0x6b, 0x3b, 0xaf, 0x30, 0x45, 0x1a, 0x64, + 0x6f, 0x2a, 0x90, 0x58, 0x05, 0x37, 0x36, 0x2a, 0xc1, 0x7e, 0x48, 0x16, 0x81, 0x83, 0x87, 0x51, + 0x72, 0x91, 0x0c, 0x38, 0xd4, 0x25, 0x01, 0x45, 0x9f, 0x43, 0x5e, 0x10, 0x84, 0x02, 0x5b, 0x48, + 0xce, 0x3a, 0xe4, 0x68, 0x7c, 0x3f, 0x1e, 0x81, 0xe2, 0xe2, 0x90, 0x7a, 0xbe, 0xcd, 0x26, 0xcd, + 0x92, 0xc9, 0x8c, 0x58, 0xd8, 0xc2, 0x59, 0xc6, 0x1a, 0x1c, 0x6d, 0x53, 0xe3, 0xb4, 0x7c, 0xec, + 0x68, 0xcb, 0x15, 0xe7, 0x46, 0x20, 0xbb, 0x36, 0xb5, 0xd9, 0xf8, 0x0f, 0x2c, 0x76, 0x46, 0xcf, + 0x21, 0x4f, 0xbd, 0x19, 0x26, 0x0b, 0x3a, 0x9c, 0x60, 0x6f, 0x3c, 0xa1, 0x6c, 0x01, 0xfb, 0x3b, + 0x77, 0x8c, 0x3f, 0x06, 0xcb, 0xf3, 0x6a, 0x93, 0x31, 0xc4, 0x05, 0x39, 0x14, 0x71, 0x1c, 0x44, + 0x5f, 0xc2, 0x83, 0x38, 0x51, 0xf4, 0x1b, 0x52, 0x7b, 0x36, 0x17, 0x7b, 0x52, 0x84, 0xa3, 0x1f, + 0xe3, 0x62, 0xb4, 0x3f, 0xc1, 0x3e, 0x9f, 0x2c, 0xbb, 0xef, 0xff, 0x76, 0x4f, 0x3b, 0x6b, 0xd9, + 0xbb, 0xb5, 0x96, 0xb8, 0x65, 0x79, 0xd3, 0xb2, 0x10, 0x77, 0x21, 0xcb, 0xc5, 0x4d, 0xf7, 0xbf, + 0x50, 0x16, 0x2a, 0x1d, 0x28, 0x5c, 0x38, 0xaf, 0x7c, 0xf2, 0x7a, 0x8a, 0xdd, 0x31, 0x9e, 0x61, + 0x9f, 0x22, 0x15, 0xd2, 0x01, 0x0e, 0x17, 0x53, 0xaa, 0x7e, 0x1c, 0x15, 0xd5, 0x4c, 0x58, 0xc2, + 0x46, 0xc7, 0x90, 0xc2, 0x41, 0x40, 0x02, 0xf5, 0x38, 0x12, 0x6a, 0x26, 0x2c, 0x6e, 0x5e, 0x02, + 0x64, 0x03, 0x1c, 0xce, 0x89, 0x1f, 0xe2, 0xb2, 0x0d, 0x99, 0x3e, 0x9f, 0x26, 0xfa, 0x16, 0xd2, + 0x62, 0x65, 0xd2, 0x3f, 0x5c, 0x99, 0xe0, 0xa3, 0x13, 0xc8, 0x6d, 0x76, 0x94, 0x64, 0x85, 0x6f, + 0x80, 0xf2, 0x20, 0xba, 0xf0, 0x81, 0x3d, 0x0b, 0xd1, 0x0b, 0x88, 0xff, 0x62, 0x43, 0xb1, 0x42, + 0x21, 0x75, 0x72, 0xe7, 0x2b, 0x22, 0x0a, 0x13, 0x62, 0x79, 0x11, 0x2a, 0xd0, 0xd3, 0x9f, 0x93, + 0x90, 0xea, 0x89, 0x17, 0xad, 0xd4, 0xeb, 0x5f, 0xf4, 0x8d, 0xe1, 0xa0, 0x6d, 0xb6, 0xcd, 0xbe, + 0x79, 0xd1, 0x32, 0x5f, 0x1a, 0x8d, 0xe1, 0xa0, 0xdd, 0xeb, 0x1a, 0x75, 0xf3, 0x99, 0x69, 0x34, + 0x94, 0x84, 0xf6, 0x60, 0xb5, 0xd6, 0x0f, 0x77, 0x08, 0x48, 0x05, 0xe0, 0x71, 0x11, 0xa8, 0x48, + 0x5a, 0x76, 0xb5, 0xd6, 0xe5, 0xe8, 0x8c, 0x8a, 0x70, 0xc8, 0x3d, 0x7d, 0xeb, 0xfb, 0x4e, 0xd7, + 0x68, 0x2b, 0x49, 0x6d, 0x7f, 0xb5, 0xd6, 0x33, 0xc2, 0xdc, 0x44, 0x32, 0xe7, 0x1e, 0x8f, 0x64, + 0x9e, 0x13, 0x38, 0xe0, 0x9e, 0x7a, 0xab, 0xd3, 0x33, 0x1a, 0x8a, 0xac, 0xc1, 0x6a, 0xad, 0xa7, + 0xb9, 0x85, 0x74, 0xc8, 0x73, 0xef, 0xb3, 0xd6, 0xa0, 0xd7, 0x34, 0xdb, 0xcf, 0x95, 0x94, 0x76, + 0xb0, 0x5a, 0xeb, 0xd9, 0xd8, 0x46, 0xa7, 0x70, 0xb4, 0xc5, 0xa8, 0x77, 0xbe, 0xeb, 0xb6, 0x8c, + 0xbe, 0xa1, 0xa4, 0x79, 0xfd, 0x3b, 0xa0, 0x26, 0xbf, 0xfd, 0xad, 0x98, 0x38, 0x7d, 0x0d, 0x29, + 0xf6, 0x54, 0xa3, 0xcf, 0xe0, 0xb8, 0x63, 0x35, 0x0c, 0x6b, 0xd8, 0xee, 0xb4, 0x8d, 0x5b, 0xdd, + 0xb3, 0x02, 0x23, 0x1c, 0x95, 0xa1, 0xc0, 0x59, 0x83, 0x36, 0xfb, 0x35, 0x1a, 0x8a, 0xa4, 0x1d, + 0xae, 0xd6, 0x7a, 0xee, 0x06, 0x88, 0xda, 0xe7, 0x9c, 0x98, 0x21, 0xda, 0x17, 0x26, 0x17, 0xbe, + 0xec, 0xbf, 0xbb, 0x2a, 0x4a, 0xef, 0xaf, 0x8a, 0xd2, 0x9f, 0x57, 0x45, 0xe9, 0x97, 0xeb, 0x62, + 0xe2, 0xfd, 0x75, 0x31, 0xf1, 0xe1, 0xba, 0x98, 0x78, 0xf9, 0x74, 0xec, 0xd1, 0xc9, 0x62, 0x54, + 0x75, 0xc8, 0xac, 0xe6, 0x90, 0x70, 0x46, 0xc2, 0x9a, 0x37, 0x72, 0x1e, 0x8f, 0x49, 0x6d, 0x79, + 0x7e, 0x56, 0x9b, 0x11, 0x77, 0x31, 0xc5, 0x21, 0xff, 0x46, 0x38, 0xfb, 0xfa, 0x71, 0xfc, 0xd1, + 0x41, 0xdf, 0xcc, 0x71, 0x38, 0x4a, 0xb3, 0x8f, 0x84, 0xaf, 0xfe, 0x0e, 0x00, 0x00, 0xff, 0xff, + 0xaf, 0x78, 0x54, 0x46, 0x95, 0x08, 0x00, 0x00, +} + +func (m *Channel) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Channel) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Channel) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.UpgradeSequence != 0 { + i = encodeVarintChannel(dAtA, i, uint64(m.UpgradeSequence)) + i-- + dAtA[i] = 0x30 + } + if len(m.Version) > 0 { + i -= len(m.Version) + copy(dAtA[i:], m.Version) + i = encodeVarintChannel(dAtA, i, uint64(len(m.Version))) + i-- + dAtA[i] = 0x2a + } + if len(m.ConnectionHops) > 0 { + for iNdEx := len(m.ConnectionHops) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.ConnectionHops[iNdEx]) + copy(dAtA[i:], m.ConnectionHops[iNdEx]) + i = encodeVarintChannel(dAtA, i, uint64(len(m.ConnectionHops[iNdEx]))) + i-- + dAtA[i] = 0x22 + } + } + { + size, err := m.Counterparty.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintChannel(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if m.Ordering != 0 { + i = encodeVarintChannel(dAtA, i, uint64(m.Ordering)) + i-- + dAtA[i] = 0x10 + } + if m.State != 0 { + i = encodeVarintChannel(dAtA, i, uint64(m.State)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *IdentifiedChannel) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *IdentifiedChannel) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *IdentifiedChannel) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.UpgradeSequence != 0 { + i = encodeVarintChannel(dAtA, i, uint64(m.UpgradeSequence)) + i-- + dAtA[i] = 0x40 + } + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintChannel(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x3a + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintChannel(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0x32 + } + if len(m.Version) > 0 { + i -= len(m.Version) + copy(dAtA[i:], m.Version) + i = encodeVarintChannel(dAtA, i, uint64(len(m.Version))) + i-- + dAtA[i] = 0x2a + } + if len(m.ConnectionHops) > 0 { + for iNdEx := len(m.ConnectionHops) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.ConnectionHops[iNdEx]) + copy(dAtA[i:], m.ConnectionHops[iNdEx]) + i = encodeVarintChannel(dAtA, i, uint64(len(m.ConnectionHops[iNdEx]))) + i-- + dAtA[i] = 0x22 + } + } + { + size, err := m.Counterparty.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintChannel(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if m.Ordering != 0 { + i = encodeVarintChannel(dAtA, i, uint64(m.Ordering)) + i-- + dAtA[i] = 0x10 + } + if m.State != 0 { + i = encodeVarintChannel(dAtA, i, uint64(m.State)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *Counterparty) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Counterparty) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Counterparty) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintChannel(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintChannel(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Packet) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Packet) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Packet) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.TimeoutTimestamp != 0 { + i = encodeVarintChannel(dAtA, i, uint64(m.TimeoutTimestamp)) + i-- + dAtA[i] = 0x40 + } + { + size, err := m.TimeoutHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintChannel(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintChannel(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0x32 + } + if len(m.DestinationChannel) > 0 { + i -= len(m.DestinationChannel) + copy(dAtA[i:], m.DestinationChannel) + i = encodeVarintChannel(dAtA, i, uint64(len(m.DestinationChannel))) + i-- + dAtA[i] = 0x2a + } + if len(m.DestinationPort) > 0 { + i -= len(m.DestinationPort) + copy(dAtA[i:], m.DestinationPort) + i = encodeVarintChannel(dAtA, i, uint64(len(m.DestinationPort))) + i-- + dAtA[i] = 0x22 + } + if len(m.SourceChannel) > 0 { + i -= len(m.SourceChannel) + copy(dAtA[i:], m.SourceChannel) + i = encodeVarintChannel(dAtA, i, uint64(len(m.SourceChannel))) + i-- + dAtA[i] = 0x1a + } + if len(m.SourcePort) > 0 { + i -= len(m.SourcePort) + copy(dAtA[i:], m.SourcePort) + i = encodeVarintChannel(dAtA, i, uint64(len(m.SourcePort))) + i-- + dAtA[i] = 0x12 + } + if m.Sequence != 0 { + i = encodeVarintChannel(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *PacketState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PacketState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PacketState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintChannel(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0x22 + } + if m.Sequence != 0 { + i = encodeVarintChannel(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x18 + } + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintChannel(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintChannel(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *PacketId) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PacketId) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PacketId) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Sequence != 0 { + i = encodeVarintChannel(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x18 + } + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintChannel(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintChannel(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Acknowledgement) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Acknowledgement) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Acknowledgement) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Response != nil { + { + size := m.Response.Size() + i -= size + if _, err := m.Response.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + } + } + return len(dAtA) - i, nil +} + +func (m *Acknowledgement_Result) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Acknowledgement_Result) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.Result != nil { + i -= len(m.Result) + copy(dAtA[i:], m.Result) + i = encodeVarintChannel(dAtA, i, uint64(len(m.Result))) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xaa + } + return len(dAtA) - i, nil +} +func (m *Acknowledgement_Error) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Acknowledgement_Error) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + i -= len(m.Error) + copy(dAtA[i:], m.Error) + i = encodeVarintChannel(dAtA, i, uint64(len(m.Error))) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xb2 + return len(dAtA) - i, nil +} +func (m *Timeout) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Timeout) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Timeout) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Timestamp != 0 { + i = encodeVarintChannel(dAtA, i, uint64(m.Timestamp)) + i-- + dAtA[i] = 0x10 + } + { + size, err := m.Height.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintChannel(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *Params) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Params) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.UpgradeTimeout.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintChannel(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintChannel(dAtA []byte, offset int, v uint64) int { + offset -= sovChannel(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Channel) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.State != 0 { + n += 1 + sovChannel(uint64(m.State)) + } + if m.Ordering != 0 { + n += 1 + sovChannel(uint64(m.Ordering)) + } + l = m.Counterparty.Size() + n += 1 + l + sovChannel(uint64(l)) + if len(m.ConnectionHops) > 0 { + for _, s := range m.ConnectionHops { + l = len(s) + n += 1 + l + sovChannel(uint64(l)) + } + } + l = len(m.Version) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + if m.UpgradeSequence != 0 { + n += 1 + sovChannel(uint64(m.UpgradeSequence)) + } + return n +} + +func (m *IdentifiedChannel) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.State != 0 { + n += 1 + sovChannel(uint64(m.State)) + } + if m.Ordering != 0 { + n += 1 + sovChannel(uint64(m.Ordering)) + } + l = m.Counterparty.Size() + n += 1 + l + sovChannel(uint64(l)) + if len(m.ConnectionHops) > 0 { + for _, s := range m.ConnectionHops { + l = len(s) + n += 1 + l + sovChannel(uint64(l)) + } + } + l = len(m.Version) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + if m.UpgradeSequence != 0 { + n += 1 + sovChannel(uint64(m.UpgradeSequence)) + } + return n +} + +func (m *Counterparty) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + return n +} + +func (m *Packet) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Sequence != 0 { + n += 1 + sovChannel(uint64(m.Sequence)) + } + l = len(m.SourcePort) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + l = len(m.SourceChannel) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + l = len(m.DestinationPort) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + l = len(m.DestinationChannel) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + l = len(m.Data) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + l = m.TimeoutHeight.Size() + n += 1 + l + sovChannel(uint64(l)) + if m.TimeoutTimestamp != 0 { + n += 1 + sovChannel(uint64(m.TimeoutTimestamp)) + } + return n +} + +func (m *PacketState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + if m.Sequence != 0 { + n += 1 + sovChannel(uint64(m.Sequence)) + } + l = len(m.Data) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + return n +} + +func (m *PacketId) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + if m.Sequence != 0 { + n += 1 + sovChannel(uint64(m.Sequence)) + } + return n +} + +func (m *Acknowledgement) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Response != nil { + n += m.Response.Size() + } + return n +} + +func (m *Acknowledgement_Result) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Result != nil { + l = len(m.Result) + n += 2 + l + sovChannel(uint64(l)) + } + return n +} +func (m *Acknowledgement_Error) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Error) + n += 2 + l + sovChannel(uint64(l)) + return n +} +func (m *Timeout) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Height.Size() + n += 1 + l + sovChannel(uint64(l)) + if m.Timestamp != 0 { + n += 1 + sovChannel(uint64(m.Timestamp)) + } + return n +} + +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.UpgradeTimeout.Size() + n += 1 + l + sovChannel(uint64(l)) + return n +} + +func sovChannel(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozChannel(x uint64) (n int) { + return sovChannel(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Channel) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Channel: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Channel: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field State", wireType) + } + m.State = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.State |= State(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Ordering", wireType) + } + m.Ordering = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Ordering |= Order(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Counterparty", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Counterparty.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectionHops", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ConnectionHops = append(m.ConnectionHops, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Version = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field UpgradeSequence", wireType) + } + m.UpgradeSequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.UpgradeSequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipChannel(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthChannel + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *IdentifiedChannel) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: IdentifiedChannel: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: IdentifiedChannel: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field State", wireType) + } + m.State = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.State |= State(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Ordering", wireType) + } + m.Ordering = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Ordering |= Order(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Counterparty", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Counterparty.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectionHops", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ConnectionHops = append(m.ConnectionHops, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Version = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field UpgradeSequence", wireType) + } + m.UpgradeSequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.UpgradeSequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipChannel(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthChannel + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Counterparty) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Counterparty: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Counterparty: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipChannel(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthChannel + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Packet) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Packet: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Packet: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SourcePort", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SourcePort = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SourceChannel", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SourceChannel = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DestinationPort", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DestinationPort = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DestinationChannel", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DestinationChannel = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeoutHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.TimeoutHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeoutTimestamp", wireType) + } + m.TimeoutTimestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TimeoutTimestamp |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipChannel(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthChannel + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PacketState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PacketState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PacketState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipChannel(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthChannel + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PacketId) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PacketId: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PacketId: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipChannel(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthChannel + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Acknowledgement) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Acknowledgement: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Acknowledgement: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 21: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Result", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := make([]byte, postIndex-iNdEx) + copy(v, dAtA[iNdEx:postIndex]) + m.Response = &Acknowledgement_Result{v} + iNdEx = postIndex + case 22: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Error", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Response = &Acknowledgement_Error{string(dAtA[iNdEx:postIndex])} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipChannel(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthChannel + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Timeout) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Timeout: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Timeout: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Height.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Timestamp |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipChannel(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthChannel + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Params) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Params: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UpgradeTimeout", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.UpgradeTimeout.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipChannel(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthChannel + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipChannel(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowChannel + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowChannel + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowChannel + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthChannel + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupChannel + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthChannel + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthChannel = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowChannel = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupChannel = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/04-channel/migrations/v10/expected_keepers.go b/modules/core/04-channel/migrations/v10/expected_keepers.go new file mode 100644 index 0000000..339d39e --- /dev/null +++ b/modules/core/04-channel/migrations/v10/expected_keepers.go @@ -0,0 +1,14 @@ +package v10 + +import ( + "cosmossdk.io/log" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" +) + +type ChannelKeeper interface { + Logger(ctx sdk.Context) log.Logger + SetChannel(ctx sdk.Context, portID, channelID string, channel types.Channel) +} diff --git a/modules/core/04-channel/migrations/v10/store.go b/modules/core/04-channel/migrations/v10/store.go new file mode 100644 index 0000000..d795333 --- /dev/null +++ b/modules/core/04-channel/migrations/v10/store.go @@ -0,0 +1,124 @@ +package v10 + +import ( + "errors" + fmt "fmt" + + corestore "cosmossdk.io/core/store" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +const ( + // ParamsKey defines the key to store the params in the keeper. + ParamsKey = "channelParams" + KeyPruningSequenceStart = "pruningSequenceStart" + + KeyChannelUpgradePrefix = "channelUpgrades" + KeyUpgradePrefix = "upgrades" + KeyUpgradeErrorPrefix = "upgradeError" + KeyCounterpartyUpgrade = "counterpartyUpgrade" +) + +// PruningSequenceStartKey returns the store key for the pruning sequence start of a particular channel +func PruningSequenceStartKey(portID, channelID string) []byte { + return fmt.Appendf(nil, "%s/%s", KeyPruningSequenceStart, channelPath(portID, channelID)) +} + +func ChannelUpgradeKey(portID, channelID string) []byte { + return fmt.Appendf(nil, "%s/%s/%s", KeyChannelUpgradePrefix, KeyUpgradePrefix, channelPath(portID, channelID)) +} + +func ChannelUpgradeErrorKey(portID, channelID string) []byte { + return fmt.Appendf(nil, "%s/%s/%s", KeyChannelUpgradePrefix, KeyUpgradeErrorPrefix, channelPath(portID, channelID)) +} + +func ChannelCounterpartyUpgradeKey(portID, channelID string) []byte { + return fmt.Appendf(nil, "%s/%s/%s", KeyChannelUpgradePrefix, KeyCounterpartyUpgrade, channelPath(portID, channelID)) +} + +func channelPath(portID, channelID string) string { + return fmt.Sprintf("%s/%s/%s/%s", host.KeyPortPrefix, portID, host.KeyChannelPrefix, channelID) +} + +// MigrateStore migrates the channel store to the ibc-go v10 store by: +// - Removing channel upgrade sequences +// - Removing any channel upgrade info (i.e. upgrades, counterparty upgrades, upgrade errors) +// - Removing channel params +// - Removing pruning sequences +// NOTE: This migration will fail if any channels are in the FLUSHING or FLUSHCOMPLETE state. +func MigrateStore(ctx sdk.Context, storeService corestore.KVStoreService, cdc codec.BinaryCodec, channelKeeper ChannelKeeper) error { + store := storeService.OpenKVStore(ctx) + + if err := handleChannelMigration(ctx, store, cdc, channelKeeper); err != nil { + return err + } + if err := deleteChannelUpgrades(store); err != nil { + return err + } + if err := deleteParams(store); err != nil { + return err + } + if err := deletePruneSequences(store); err != nil { + return err + } + + // TODO: See if there is more to migrate/delete from store + + return nil +} + +func handleChannelMigration(ctx sdk.Context, store corestore.KVStore, cdc codec.BinaryCodec, channelKeeper ChannelKeeper) error { + // Remove channel upgrade sequences and set in-upgrade channels back to open + iterator := storetypes.KVStorePrefixIterator(runtime.KVStoreAdapter(store), []byte(host.KeyChannelEndPrefix)) + + defer sdk.LogDeferred(channelKeeper.Logger(ctx), func() error { return iterator.Close() }) + for ; iterator.Valid(); iterator.Next() { + var channel Channel + cdc.MustUnmarshal(iterator.Value(), &channel) + + if channel.State == FLUSHING || channel.State == FLUSHCOMPLETE { + return errors.New("channel in state FLUSHING or FLUSHCOMPLETE found, to proceed with migration, please ensure no channels are currently upgrading") + } + + newChannel := types.Channel{ + State: types.State(channel.State), + Ordering: types.Order(channel.Ordering), + Counterparty: types.Counterparty{ + PortId: channel.Counterparty.PortId, + ChannelId: channel.Counterparty.ChannelId, + }, + ConnectionHops: channel.ConnectionHops, + Version: channel.Version, + } + // Any pitfalls of doing this? + if err := newChannel.ValidateBasic(); err != nil { + return err + } + portID, channelID := host.MustParseChannelPath(string(iterator.Key())) + channelKeeper.SetChannel(ctx, portID, channelID, newChannel) + } + + return nil +} + +func deleteChannelUpgrades(store corestore.KVStore) error { + // Delete channel upgrades (i.e. upgrades, counterparty upgrades, upgrade errors, which are stored in the channelUpgrades prefix) + return store.Delete([]byte(KeyChannelUpgradePrefix)) +} + +func deleteParams(store corestore.KVStore) error { + // Delete channel params + return store.Delete([]byte(ParamsKey)) +} + +func deletePruneSequences(store corestore.KVStore) error { + // Delete all pruning sequences + return store.Delete([]byte(KeyPruningSequenceStart)) +} diff --git a/modules/core/04-channel/migrations/v10/store_test.go b/modules/core/04-channel/migrations/v10/store_test.go new file mode 100644 index 0000000..1547f02 --- /dev/null +++ b/modules/core/04-channel/migrations/v10/store_test.go @@ -0,0 +1,232 @@ +package v10_test + +import ( + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + corestore "cosmossdk.io/core/store" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/keeper" + v10 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/migrations/v10" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +type MigrationsV10TestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain +} + +func (suite *MigrationsV10TestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) +} + +func TestMigrationsV10TestSuite(t *testing.T) { + testifysuite.Run(t, new(MigrationsV10TestSuite)) +} + +// set up channels that are still in upgrade state, and assert that the upgrade fails. +// migrate the store, and assert that the channels have been upgraded and state removed as expected +func (suite *MigrationsV10TestSuite) TestMigrateStoreWithUpgradingChannels() { + ctx := suite.chainA.GetContext() + cdc := suite.chainA.App.AppCodec() + channelKeeper := suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper + storeService := runtime.NewKVStoreService(suite.chainA.GetSimApp().GetKey(ibcexported.StoreKey)) + + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + preMigrationChannels := suite.getPreMigrationTypeChannels(ctx, cdc, storeService) + suite.Require().Len(preMigrationChannels, 2) + + // Set up some channels with old state + flushingChannel := preMigrationChannels[0] + flushingChannel.State = v10.FLUSHING + suite.setPreMigrationChannel(ctx, cdc, storeService, flushingChannel) + + flushCompleteChannel := preMigrationChannels[1] + flushCompleteChannel.State = v10.FLUSHCOMPLETE + suite.setPreMigrationChannel(ctx, cdc, storeService, flushCompleteChannel) + + err := v10.MigrateStore(ctx, storeService, cdc, channelKeeper) + suite.Require().Errorf(err, "channel in state FLUSHING or FLUSHCOMPLETE found, to proceed with migration, please ensure no channels are currently upgrading") +} + +// set up channels, upgrades, params, and prune sequences in the store, +// migrate the store, and assert that the channels have been upgraded and state removed as expected +func (suite *MigrationsV10TestSuite) TestMigrateStore() { + ctx := suite.chainA.GetContext() + cdc := suite.chainA.App.AppCodec() + channelKeeper := suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper + storeService := runtime.NewKVStoreService(suite.chainA.GetSimApp().GetKey(ibcexported.StoreKey)) + store := storeService.OpenKVStore(ctx) + numberOfChannels := 100 + + for range numberOfChannels { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + } + + preMigrationChannels := suite.getPreMigrationTypeChannels(ctx, cdc, storeService) + suite.Require().Len(preMigrationChannels, numberOfChannels) + + // Set up some channels with old state + testChannel1 := preMigrationChannels[0] + testChannel2 := preMigrationChannels[1] + + // Set some upgrades + upgrade := v10.Upgrade{ + Fields: v10.UpgradeFields{ + Ordering: v10.ORDERED, + ConnectionHops: []string{"connection-0"}, + Version: testChannel1.Version, + }, + Timeout: v10.Timeout{}, + NextSequenceSend: 2, + } + err := store.Set(v10.ChannelUpgradeKey(testChannel1.PortId, testChannel1.ChannelId), cdc.MustMarshal(&upgrade)) + suite.Require().NoError(err) + upgrade = v10.Upgrade{ + Fields: v10.UpgradeFields{ + Ordering: v10.ORDERED, + ConnectionHops: []string{"connection-0"}, + Version: testChannel2.Version, + }, + Timeout: v10.Timeout{}, + NextSequenceSend: 20, + } + err = store.Set(v10.ChannelUpgradeKey(testChannel2.PortId, testChannel2.ChannelId), cdc.MustMarshal(&upgrade)) + suite.Require().NoError(err) + + counterpartyUpgrade := v10.Upgrade{ + Fields: v10.UpgradeFields{ + Ordering: v10.ORDERED, + ConnectionHops: []string{"connection-0"}, + Version: testChannel2.Version, + }, + Timeout: v10.Timeout{}, + NextSequenceSend: 20, + } + err = store.Set(v10.ChannelCounterpartyUpgradeKey(testChannel2.PortId, testChannel2.ChannelId), cdc.MustMarshal(&counterpartyUpgrade)) + suite.Require().NoError(err) + + errorReceipt := v10.ErrorReceipt{ + Sequence: 3, + Message: "🤷", + } + err = store.Set(v10.ChannelUpgradeErrorKey(testChannel1.PortId, testChannel1.ChannelId), cdc.MustMarshal(&errorReceipt)) + suite.Require().NoError(err) + + // Set some params + err = store.Set([]byte(v10.ParamsKey), cdc.MustMarshal(&v10.Params{UpgradeTimeout: v10.Timeout{ + Timestamp: 1000, + }})) + suite.Require().NoError(err) + + // Set some prune sequences + err = store.Set(v10.PruningSequenceStartKey(testChannel1.PortId, testChannel1.ChannelId), sdk.Uint64ToBigEndian(0)) + suite.Require().NoError(err) + err = store.Set(v10.PruningSequenceStartKey(testChannel2.PortId, testChannel2.ChannelId), sdk.Uint64ToBigEndian(42)) + suite.Require().NoError(err) + + err = v10.MigrateStore(ctx, storeService, cdc, channelKeeper) + suite.Require().NoError(err) + + suite.assertChannelsUpgraded(ctx, suite.chainA.App.AppCodec(), storeService, channelKeeper, preMigrationChannels) + suite.assertNoUpgrades(ctx, storeService) + suite.assertNoParms(ctx, storeService) + suite.assertNoPruneSequences(ctx, storeService) +} + +func (suite *MigrationsV10TestSuite) setPreMigrationChannel(ctx sdk.Context, cdc codec.Codec, storeService corestore.KVStoreService, channel v10.IdentifiedChannel) { + store := storeService.OpenKVStore(ctx) + channelKey := host.ChannelKey(channel.PortId, channel.ChannelId) + err := store.Set(channelKey, cdc.MustMarshal(&v10.Channel{ + State: channel.State, + Ordering: channel.Ordering, + Counterparty: channel.Counterparty, + ConnectionHops: channel.ConnectionHops, + Version: channel.Version, + UpgradeSequence: channel.UpgradeSequence, + })) + suite.Require().NoError(err) +} + +func (suite *MigrationsV10TestSuite) getPreMigrationTypeChannels(ctx sdk.Context, cdc codec.Codec, storeService corestore.KVStoreService) []v10.IdentifiedChannel { + var channels []v10.IdentifiedChannel + + iterator := storetypes.KVStorePrefixIterator(runtime.KVStoreAdapter(storeService.OpenKVStore(ctx)), []byte(host.KeyChannelEndPrefix)) + for ; iterator.Valid(); iterator.Next() { + var channel v10.Channel + err := cdc.Unmarshal(iterator.Value(), &channel) + suite.Require().NoError(err) + + portID, channelID, err := host.ParseChannelPath(string(iterator.Key())) + identifiedChannel := v10.IdentifiedChannel{ + State: channel.State, + Ordering: channel.Ordering, + Counterparty: channel.Counterparty, + ConnectionHops: channel.ConnectionHops, + Version: channel.Version, + PortId: portID, + ChannelId: channelID, + UpgradeSequence: channel.UpgradeSequence, + } + suite.Require().NoError(err) + channels = append(channels, identifiedChannel) + + } + iterator.Close() + + return channels +} + +func (suite *MigrationsV10TestSuite) assertChannelsUpgraded(ctx sdk.Context, cdc codec.Codec, storeService corestore.KVStoreService, channelKeeper *keeper.Keeper, preMigrationChannels []v10.IdentifiedChannel) { + // First check that all channels have gotten the old state pruned + newChannelsWithPreMigrationType := suite.getPreMigrationTypeChannels(ctx, cdc, storeService) + for _, channel := range newChannelsWithPreMigrationType { + suite.Require().NotEqual(v10.FLUSHING, channel.State) + suite.Require().NotEqual(v10.FLUSHCOMPLETE, channel.State) + suite.Require().Equal(uint64(0), channel.UpgradeSequence) + } + + // Then check that we can still receive all the channels + newChannelsWithPostMigrationType := channelKeeper.GetAllChannels(ctx) + for _, channel := range newChannelsWithPostMigrationType { + suite.Require().NoError(channel.ValidateBasic()) + } + + suite.Require().Equal(len(newChannelsWithPreMigrationType), len(newChannelsWithPostMigrationType)) + suite.Require().Equal(len(newChannelsWithPostMigrationType), len(preMigrationChannels)) +} + +func (suite *MigrationsV10TestSuite) assertNoUpgrades(ctx sdk.Context, storeService corestore.KVStoreService) { + store := storeService.OpenKVStore(ctx) + suite.Require().False(store.Has([]byte(v10.KeyChannelUpgradePrefix))) +} + +func (suite *MigrationsV10TestSuite) assertNoParms(ctx sdk.Context, storeService corestore.KVStoreService) { + store := storeService.OpenKVStore(ctx) + suite.Require().False(store.Has([]byte(v10.ParamsKey))) +} + +func (suite *MigrationsV10TestSuite) assertNoPruneSequences(ctx sdk.Context, storeService corestore.KVStoreService) { + store := storeService.OpenKVStore(ctx) + suite.Require().False(store.Has([]byte(v10.KeyPruningSequenceStart))) +} diff --git a/modules/core/04-channel/migrations/v10/upgrade.pb.go b/modules/core/04-channel/migrations/v10/upgrade.pb.go new file mode 100644 index 0000000..63e86e1 --- /dev/null +++ b/modules/core/04-channel/migrations/v10/upgrade.pb.go @@ -0,0 +1,842 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/channel/v1/upgrade.proto + +package v10 + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Upgrade is a verifiable type which contains the relevant information +// for an attempted upgrade. It provides the proposed changes to the channel +// end, the timeout for this upgrade attempt and the next packet sequence +// which allows the counterparty to efficiently know the highest sequence it has received. +// The next sequence send is used for pruning and upgrading from unordered to ordered channels. +type Upgrade struct { + Fields UpgradeFields `protobuf:"bytes,1,opt,name=fields,proto3" json:"fields"` + Timeout Timeout `protobuf:"bytes,2,opt,name=timeout,proto3" json:"timeout"` + NextSequenceSend uint64 `protobuf:"varint,3,opt,name=next_sequence_send,json=nextSequenceSend,proto3" json:"next_sequence_send,omitempty"` +} + +func (m *Upgrade) Reset() { *m = Upgrade{} } +func (m *Upgrade) String() string { return proto.CompactTextString(m) } +func (*Upgrade) ProtoMessage() {} +func (*Upgrade) Descriptor() ([]byte, []int) { + return fileDescriptor_fb1cef68588848b2, []int{0} +} +func (m *Upgrade) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Upgrade) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Upgrade.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Upgrade) XXX_Merge(src proto.Message) { + xxx_messageInfo_Upgrade.Merge(m, src) +} +func (m *Upgrade) XXX_Size() int { + return m.Size() +} +func (m *Upgrade) XXX_DiscardUnknown() { + xxx_messageInfo_Upgrade.DiscardUnknown(m) +} + +var xxx_messageInfo_Upgrade proto.InternalMessageInfo + +// UpgradeFields are the fields in a channel end which may be changed +// during a channel upgrade. +type UpgradeFields struct { + Ordering Order `protobuf:"varint,1,opt,name=ordering,proto3,enum=ibc.core.channel.v1.Order" json:"ordering,omitempty"` + ConnectionHops []string `protobuf:"bytes,2,rep,name=connection_hops,json=connectionHops,proto3" json:"connection_hops,omitempty"` + Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` +} + +func (m *UpgradeFields) Reset() { *m = UpgradeFields{} } +func (m *UpgradeFields) String() string { return proto.CompactTextString(m) } +func (*UpgradeFields) ProtoMessage() {} +func (*UpgradeFields) Descriptor() ([]byte, []int) { + return fileDescriptor_fb1cef68588848b2, []int{1} +} +func (m *UpgradeFields) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *UpgradeFields) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_UpgradeFields.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *UpgradeFields) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpgradeFields.Merge(m, src) +} +func (m *UpgradeFields) XXX_Size() int { + return m.Size() +} +func (m *UpgradeFields) XXX_DiscardUnknown() { + xxx_messageInfo_UpgradeFields.DiscardUnknown(m) +} + +var xxx_messageInfo_UpgradeFields proto.InternalMessageInfo + +// ErrorReceipt defines a type which encapsulates the upgrade sequence and error associated with the +// upgrade handshake failure. When a channel upgrade handshake is aborted both chains are expected to increment to the +// next sequence. +type ErrorReceipt struct { + // the channel upgrade sequence + Sequence uint64 `protobuf:"varint,1,opt,name=sequence,proto3" json:"sequence,omitempty"` + // the error message detailing the cause of failure + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` +} + +func (m *ErrorReceipt) Reset() { *m = ErrorReceipt{} } +func (m *ErrorReceipt) String() string { return proto.CompactTextString(m) } +func (*ErrorReceipt) ProtoMessage() {} +func (*ErrorReceipt) Descriptor() ([]byte, []int) { + return fileDescriptor_fb1cef68588848b2, []int{2} +} +func (m *ErrorReceipt) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ErrorReceipt) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ErrorReceipt.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ErrorReceipt) XXX_Merge(src proto.Message) { + xxx_messageInfo_ErrorReceipt.Merge(m, src) +} +func (m *ErrorReceipt) XXX_Size() int { + return m.Size() +} +func (m *ErrorReceipt) XXX_DiscardUnknown() { + xxx_messageInfo_ErrorReceipt.DiscardUnknown(m) +} + +var xxx_messageInfo_ErrorReceipt proto.InternalMessageInfo + +func init() { + proto.RegisterType((*Upgrade)(nil), "ibc.core.channel.v1.Upgrade") + proto.RegisterType((*UpgradeFields)(nil), "ibc.core.channel.v1.UpgradeFields") + proto.RegisterType((*ErrorReceipt)(nil), "ibc.core.channel.v1.ErrorReceipt") +} + +func init() { proto.RegisterFile("ibc/core/channel/v1/upgrade.proto", fileDescriptor_fb1cef68588848b2) } + +var fileDescriptor_fb1cef68588848b2 = []byte{ + // 407 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x91, 0x41, 0x6f, 0xd3, 0x30, + 0x14, 0xc7, 0xe3, 0xad, 0x5a, 0x57, 0x03, 0x03, 0x19, 0x0e, 0x51, 0x85, 0xb2, 0xd2, 0x0b, 0x3d, + 0xb0, 0x78, 0x1d, 0x88, 0xc3, 0xc4, 0x01, 0x4d, 0x02, 0x21, 0x2e, 0x48, 0xde, 0xb8, 0x70, 0xa9, + 0x1a, 0xe7, 0x91, 0x5a, 0x6a, 0xfc, 0x82, 0xed, 0x44, 0xf0, 0x0d, 0x38, 0xee, 0x23, 0xf0, 0x45, + 0xb8, 0xef, 0xb8, 0x23, 0x27, 0x84, 0xda, 0x2f, 0x82, 0xe2, 0x24, 0x45, 0x48, 0xb9, 0xf9, 0xf9, + 0xfd, 0xfe, 0x7f, 0xff, 0x9f, 0x1f, 0x7d, 0xa2, 0x12, 0xc9, 0x25, 0x1a, 0xe0, 0x72, 0xb5, 0xd4, + 0x1a, 0xd6, 0xbc, 0x9a, 0xf3, 0xb2, 0xc8, 0xcc, 0x32, 0x85, 0xb8, 0x30, 0xe8, 0x90, 0x3d, 0x54, + 0x89, 0x8c, 0x6b, 0x24, 0x6e, 0x91, 0xb8, 0x9a, 0x8f, 0x1f, 0x65, 0x98, 0xa1, 0xef, 0xf3, 0xfa, + 0xd4, 0xa0, 0xe3, 0x5e, 0xb7, 0x4e, 0xe5, 0x91, 0xe9, 0x4f, 0x42, 0x87, 0x1f, 0x1b, 0x7f, 0xf6, + 0x9a, 0x1e, 0x7c, 0x56, 0xb0, 0x4e, 0x6d, 0x48, 0x26, 0x64, 0x76, 0xe7, 0x6c, 0x1a, 0xf7, 0x3c, + 0x15, 0xb7, 0xf4, 0x5b, 0x4f, 0x5e, 0x0c, 0x6e, 0x7e, 0x1f, 0x07, 0xa2, 0xd5, 0xb1, 0x57, 0x74, + 0xe8, 0x54, 0x0e, 0x58, 0xba, 0x70, 0xcf, 0x5b, 0x3c, 0xee, 0xb5, 0xb8, 0x6a, 0x98, 0x56, 0xdc, + 0x49, 0xd8, 0x33, 0xca, 0x34, 0x7c, 0x75, 0x0b, 0x0b, 0x5f, 0x4a, 0xd0, 0x12, 0x16, 0x16, 0x74, + 0x1a, 0xee, 0x4f, 0xc8, 0x6c, 0x20, 0x1e, 0xd4, 0x9d, 0xcb, 0xb6, 0x71, 0x09, 0x3a, 0x3d, 0x1f, + 0x7c, 0xff, 0x71, 0x1c, 0x4c, 0xaf, 0x09, 0xbd, 0xf7, 0x5f, 0x22, 0xf6, 0x92, 0x1e, 0xa2, 0x49, + 0xc1, 0x28, 0x9d, 0xf9, 0x39, 0x8e, 0xce, 0xc6, 0xbd, 0x21, 0x3e, 0xd4, 0x90, 0xd8, 0xb1, 0xec, + 0x29, 0xbd, 0x2f, 0x51, 0x6b, 0x90, 0x4e, 0xa1, 0x5e, 0xac, 0xb0, 0xb0, 0xe1, 0xde, 0x64, 0x7f, + 0x36, 0x12, 0x47, 0xff, 0xae, 0xdf, 0x61, 0x61, 0x59, 0x48, 0x87, 0x15, 0x18, 0xab, 0x50, 0xfb, + 0x6c, 0x23, 0xd1, 0x95, 0x6d, 0xa4, 0xf7, 0xf4, 0xee, 0x1b, 0x63, 0xd0, 0x08, 0x90, 0xa0, 0x0a, + 0xc7, 0xc6, 0xf4, 0xb0, 0x9b, 0xc8, 0x07, 0x1a, 0x88, 0x5d, 0x5d, 0x7b, 0xe5, 0x60, 0xed, 0x32, + 0x03, 0xff, 0x61, 0x23, 0xd1, 0x95, 0x8d, 0xd7, 0xc5, 0xd5, 0xcd, 0x26, 0x22, 0xb7, 0x9b, 0x88, + 0xfc, 0xd9, 0x44, 0xe4, 0x7a, 0x1b, 0x05, 0xb7, 0xdb, 0x28, 0xf8, 0xb5, 0x8d, 0x82, 0x4f, 0xe7, + 0x99, 0x72, 0xab, 0x32, 0x89, 0x25, 0xe6, 0x5c, 0xa2, 0xcd, 0xd1, 0x72, 0x95, 0xc8, 0x93, 0x0c, + 0x79, 0x35, 0x3f, 0xe5, 0x39, 0xa6, 0xe5, 0x1a, 0x6c, 0xb3, 0xfc, 0xd3, 0x17, 0x27, 0xdd, 0xfe, + 0xdd, 0xb7, 0x02, 0x6c, 0x72, 0xe0, 0x77, 0xff, 0xfc, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x90, + 0x11, 0x85, 0x24, 0x6e, 0x02, 0x00, 0x00, +} + +func (m *Upgrade) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Upgrade) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Upgrade) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.NextSequenceSend != 0 { + i = encodeVarintUpgrade(dAtA, i, uint64(m.NextSequenceSend)) + i-- + dAtA[i] = 0x18 + } + { + size, err := m.Timeout.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintUpgrade(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.Fields.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintUpgrade(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *UpgradeFields) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *UpgradeFields) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *UpgradeFields) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Version) > 0 { + i -= len(m.Version) + copy(dAtA[i:], m.Version) + i = encodeVarintUpgrade(dAtA, i, uint64(len(m.Version))) + i-- + dAtA[i] = 0x1a + } + if len(m.ConnectionHops) > 0 { + for iNdEx := len(m.ConnectionHops) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.ConnectionHops[iNdEx]) + copy(dAtA[i:], m.ConnectionHops[iNdEx]) + i = encodeVarintUpgrade(dAtA, i, uint64(len(m.ConnectionHops[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + if m.Ordering != 0 { + i = encodeVarintUpgrade(dAtA, i, uint64(m.Ordering)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *ErrorReceipt) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ErrorReceipt) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ErrorReceipt) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Message) > 0 { + i -= len(m.Message) + copy(dAtA[i:], m.Message) + i = encodeVarintUpgrade(dAtA, i, uint64(len(m.Message))) + i-- + dAtA[i] = 0x12 + } + if m.Sequence != 0 { + i = encodeVarintUpgrade(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintUpgrade(dAtA []byte, offset int, v uint64) int { + offset -= sovUpgrade(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Upgrade) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Fields.Size() + n += 1 + l + sovUpgrade(uint64(l)) + l = m.Timeout.Size() + n += 1 + l + sovUpgrade(uint64(l)) + if m.NextSequenceSend != 0 { + n += 1 + sovUpgrade(uint64(m.NextSequenceSend)) + } + return n +} + +func (m *UpgradeFields) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Ordering != 0 { + n += 1 + sovUpgrade(uint64(m.Ordering)) + } + if len(m.ConnectionHops) > 0 { + for _, s := range m.ConnectionHops { + l = len(s) + n += 1 + l + sovUpgrade(uint64(l)) + } + } + l = len(m.Version) + if l > 0 { + n += 1 + l + sovUpgrade(uint64(l)) + } + return n +} + +func (m *ErrorReceipt) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Sequence != 0 { + n += 1 + sovUpgrade(uint64(m.Sequence)) + } + l = len(m.Message) + if l > 0 { + n += 1 + l + sovUpgrade(uint64(l)) + } + return n +} + +func sovUpgrade(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozUpgrade(x uint64) (n int) { + return sovUpgrade(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Upgrade) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowUpgrade + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Upgrade: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Upgrade: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Fields", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowUpgrade + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthUpgrade + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthUpgrade + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Fields.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Timeout", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowUpgrade + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthUpgrade + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthUpgrade + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Timeout.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NextSequenceSend", wireType) + } + m.NextSequenceSend = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowUpgrade + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.NextSequenceSend |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipUpgrade(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthUpgrade + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *UpgradeFields) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowUpgrade + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: UpgradeFields: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: UpgradeFields: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Ordering", wireType) + } + m.Ordering = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowUpgrade + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Ordering |= Order(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectionHops", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowUpgrade + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthUpgrade + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthUpgrade + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ConnectionHops = append(m.ConnectionHops, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowUpgrade + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthUpgrade + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthUpgrade + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Version = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipUpgrade(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthUpgrade + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ErrorReceipt) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowUpgrade + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ErrorReceipt: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ErrorReceipt: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowUpgrade + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowUpgrade + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthUpgrade + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthUpgrade + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Message = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipUpgrade(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthUpgrade + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipUpgrade(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowUpgrade + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowUpgrade + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowUpgrade + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthUpgrade + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupUpgrade + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthUpgrade + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthUpgrade = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowUpgrade = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupUpgrade = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/04-channel/module.go b/modules/core/04-channel/module.go new file mode 100644 index 0000000..416cf01 --- /dev/null +++ b/modules/core/04-channel/module.go @@ -0,0 +1,18 @@ +package channel + +import ( + "github.com/spf13/cobra" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/client/cli" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" +) + +// Name returns the IBC channel ICS name. +func Name() string { + return types.SubModuleName +} + +// GetQueryCmd returns the root query command for IBC channels. +func GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd() +} diff --git a/modules/core/04-channel/simulation/decoder.go b/modules/core/04-channel/simulation/decoder.go new file mode 100644 index 0000000..11ab606 --- /dev/null +++ b/modules/core/04-channel/simulation/decoder.go @@ -0,0 +1,49 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's +// Value to the corresponding channel type. +func NewDecodeStore(cdc codec.BinaryCodec, kvA, kvB kv.Pair) (string, bool) { + switch { + case bytes.HasPrefix(kvA.Key, []byte(host.KeyChannelEndPrefix)): + var channelA, channelB types.Channel + cdc.MustUnmarshal(kvA.Value, &channelA) + cdc.MustUnmarshal(kvB.Value, &channelB) + return fmt.Sprintf("Channel A: %v\nChannel B: %v", channelA, channelB), true + + case bytes.HasPrefix(kvA.Key, []byte(host.KeyNextSeqSendPrefix)): + seqA := sdk.BigEndianToUint64(kvA.Value) + seqB := sdk.BigEndianToUint64(kvB.Value) + return fmt.Sprintf("NextSeqSend A: %d\nNextSeqSend B: %d", seqA, seqB), true + + case bytes.HasPrefix(kvA.Key, []byte(host.KeyNextSeqRecvPrefix)): + seqA := sdk.BigEndianToUint64(kvA.Value) + seqB := sdk.BigEndianToUint64(kvB.Value) + return fmt.Sprintf("NextSeqRecv A: %d\nNextSeqRecv B: %d", seqA, seqB), true + + case bytes.HasPrefix(kvA.Key, []byte(host.KeyNextSeqAckPrefix)): + seqA := sdk.BigEndianToUint64(kvA.Value) + seqB := sdk.BigEndianToUint64(kvB.Value) + return fmt.Sprintf("NextSeqAck A: %d\nNextSeqAck B: %d", seqA, seqB), true + + case bytes.HasPrefix(kvA.Key, []byte(host.KeyPacketCommitmentPrefix)): + return fmt.Sprintf("CommitmentHash A: %X\nCommitmentHash B: %X", kvA.Value, kvB.Value), true + + case bytes.HasPrefix(kvA.Key, []byte(host.KeyPacketAckPrefix)): + return fmt.Sprintf("AckHash A: %X\nAckHash B: %X", kvA.Value, kvB.Value), true + + default: + return "", false + } +} diff --git a/modules/core/04-channel/simulation/decoder_test.go b/modules/core/04-channel/simulation/decoder_test.go new file mode 100644 index 0000000..ff53df5 --- /dev/null +++ b/modules/core/04-channel/simulation/decoder_test.go @@ -0,0 +1,89 @@ +package simulation_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/kv" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/simulation" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/testing/simapp" +) + +func TestDecodeStore(t *testing.T) { + app := simapp.Setup(t, false) + cdc := app.AppCodec() + + channelID := "channelidone" + portID := "portidone" + + channel := types.Channel{ + State: types.OPEN, + Version: "1.0", + } + + bz := []byte{0x1, 0x2, 0x3} + + kvPairs := kv.Pairs{ + Pairs: []kv.Pair{ + { + Key: host.ChannelKey(portID, channelID), + Value: cdc.MustMarshal(&channel), + }, + { + Key: host.NextSequenceSendKey(portID, channelID), + Value: sdk.Uint64ToBigEndian(1), + }, + { + Key: host.NextSequenceRecvKey(portID, channelID), + Value: sdk.Uint64ToBigEndian(1), + }, + { + Key: host.NextSequenceAckKey(portID, channelID), + Value: sdk.Uint64ToBigEndian(1), + }, + { + Key: host.PacketCommitmentKey(portID, channelID, 1), + Value: bz, + }, + { + Key: host.PacketAcknowledgementKey(portID, channelID, 1), + Value: bz, + }, + { + Key: []byte{0x99}, + Value: []byte{0x99}, + }, + }, + } + tests := []struct { + name string + expectedLog string + }{ + {"Channel", fmt.Sprintf("Channel A: %v\nChannel B: %v", channel, channel)}, + {"NextSeqSend", "NextSeqSend A: 1\nNextSeqSend B: 1"}, + {"NextSeqRecv", "NextSeqRecv A: 1\nNextSeqRecv B: 1"}, + {"NextSeqAck", "NextSeqAck A: 1\nNextSeqAck B: 1"}, + {"CommitmentHash", fmt.Sprintf("CommitmentHash A: %X\nCommitmentHash B: %X", bz, bz)}, + {"AckHash", fmt.Sprintf("AckHash A: %X\nAckHash B: %X", bz, bz)}, + {"other", ""}, + } + + for i, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + res, found := simulation.NewDecodeStore(cdc, kvPairs.Pairs[i], kvPairs.Pairs[i]) + if i == len(tests)-1 { + require.False(t, found, string(kvPairs.Pairs[i].Key)) + require.Empty(t, res, string(kvPairs.Pairs[i].Key)) + } else { + require.True(t, found, string(kvPairs.Pairs[i].Key)) + require.Equal(t, tt.expectedLog, res, string(kvPairs.Pairs[i].Key)) + } + }) + } +} diff --git a/modules/core/04-channel/simulation/genesis.go b/modules/core/04-channel/simulation/genesis.go new file mode 100644 index 0000000..024cd57 --- /dev/null +++ b/modules/core/04-channel/simulation/genesis.go @@ -0,0 +1,14 @@ +package simulation + +import ( + "math/rand" + + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" +) + +// GenChannelGenesis returns the default channel genesis state. +func GenChannelGenesis(_ *rand.Rand, _ []simtypes.Account) types.GenesisState { + return types.DefaultGenesisState() +} diff --git a/modules/core/04-channel/types/acknowledgement.go b/modules/core/04-channel/types/acknowledgement.go new file mode 100644 index 0000000..1478ec4 --- /dev/null +++ b/modules/core/04-channel/types/acknowledgement.go @@ -0,0 +1,92 @@ +package types + +import ( + "fmt" + "reflect" + "strings" + + errorsmod "cosmossdk.io/errors" +) + +const ( + // ackErrorString defines a string constant included in error acknowledgements + // NOTE: Changing this const is state machine breaking as acknowledgements are written into state. + ackErrorString = "error handling packet: see events for details" +) + +// NewResultAcknowledgement returns a new instance of Acknowledgement using an Acknowledgement_Result +// type in the Response field. +func NewResultAcknowledgement(result []byte) Acknowledgement { + return Acknowledgement{ + Response: &Acknowledgement_Result{ + Result: result, + }, + } +} + +// NewErrorAcknowledgementWithCodespace returns a new instance of Acknowledgement using an Acknowledgement_Error +// type in the Response field. +// NOTE: The error includes the ABCI codespace and code in the error string to provide more information about the module +// that generated the error. This is useful for debugging but can potentially introduce non-determinism if care is +// not taken to ensure the codespace doesn't change in non state-machine breaking versions. +func NewErrorAcknowledgementWithCodespace(err error) Acknowledgement { + // The ABCI code is included in the abcitypes.ResponseDeliverTx hash + // constructed in Tendermint and is therefore deterministic. + // However, a code without codespace is incomplete information (e.g. sdk/5 and wasm/5 are + // different errors). We add this codespace here, in order to provide a meaningful error + // identifier which means changing the codespace of an error becomes a consensus breaking change. + codespace, code, _ := errorsmod.ABCIInfo(err, false) + + return Acknowledgement{ + Response: &Acknowledgement_Error{ + Error: fmt.Sprintf("ABCI error: %s/%d: %s", codespace, code, ackErrorString), + }, + } +} + +// NewErrorAcknowledgement returns a new instance of Acknowledgement using an Acknowledgement_Error +// type in the Response field. +// NOTE: Acknowledgements are written into state and thus, changes made to error strings included in packet acknowledgements +// risk an app hash divergence when nodes in a network are running different patch versions of software. +func NewErrorAcknowledgement(err error) Acknowledgement { + // the ABCI code is included in the abcitypes.ResponseDeliverTx hash + // constructed in Tendermint and is therefore deterministic + _, code, _ := errorsmod.ABCIInfo(err, false) // discard non-deterministic codespace and log values + + return Acknowledgement{ + Response: &Acknowledgement_Error{ + Error: fmt.Sprintf("ABCI code: %d: %s", code, ackErrorString), + }, + } +} + +// ValidateBasic performs a basic validation of the acknowledgement +func (ack Acknowledgement) ValidateBasic() error { + switch resp := ack.Response.(type) { + case *Acknowledgement_Result: + if len(resp.Result) == 0 { + return errorsmod.Wrap(ErrInvalidAcknowledgement, "acknowledgement result cannot be empty") + } + case *Acknowledgement_Error: + if strings.TrimSpace(resp.Error) == "" { + return errorsmod.Wrap(ErrInvalidAcknowledgement, "acknowledgement error cannot be empty") + } + + default: + return errorsmod.Wrapf(ErrInvalidAcknowledgement, "unsupported acknowledgement response field type %T", resp) + } + return nil +} + +// Success implements the Acknowledgement interface. The acknowledgement is +// considered successful if it is a ResultAcknowledgement. Otherwise it is +// considered a failed acknowledgement. +func (ack Acknowledgement) Success() bool { + return reflect.TypeOf(ack.Response) == reflect.TypeOf(((*Acknowledgement_Result)(nil))) +} + +// Acknowledgement implements the Acknowledgement interface. It returns the +// acknowledgement serialised using JSON. +func (ack Acknowledgement) Acknowledgement() []byte { + return SubModuleCdc.MustMarshalJSON(&ack) +} diff --git a/modules/core/04-channel/types/acknowledgement_test.go b/modules/core/04-channel/types/acknowledgement_test.go new file mode 100644 index 0000000..13f5f6b --- /dev/null +++ b/modules/core/04-channel/types/acknowledgement_test.go @@ -0,0 +1,171 @@ +package types_test + +import ( + "errors" + + errorsmod "cosmossdk.io/errors" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + abcitypes "github.com/cometbft/cometbft/abci/types" + cmtstate "github.com/cometbft/cometbft/state" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +const ( + gasUsed = uint64(100) + gasWanted = uint64(100) +) + +// tests acknowledgement.ValidateBasic and acknowledgement.Acknowledgement +func (suite TypesTestSuite) TestAcknowledgement() { //nolint:govet // this is a test, we are okay with copying locks + testCases := []struct { + name string + ack types.Acknowledgement + expValidates bool + expBytes []byte + expSuccess bool // indicate if this is a success or failed ack + }{ + { + "valid successful ack", + types.NewResultAcknowledgement([]byte("success")), + true, + []byte(`{"result":"c3VjY2Vzcw=="}`), + true, + }, + { + "valid failed ack", + types.NewErrorAcknowledgement(errors.New("error")), + true, + []byte(`{"error":"ABCI code: 1: error handling packet: see events for details"}`), + false, + }, + { + "empty successful ack", + types.NewResultAcknowledgement([]byte{}), + false, + nil, + true, + }, + { + "empty failed ack", + types.NewErrorAcknowledgement(errors.New(" ")), + true, + []byte(`{"error":"ABCI code: 1: error handling packet: see events for details"}`), + false, + }, + { + "nil response", + types.Acknowledgement{ + Response: nil, + }, + false, + nil, + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + err := tc.ack.ValidateBasic() + + if tc.expValidates { + suite.Require().NoError(err) + + // expect all valid acks to be able to be marshaled + suite.NotPanics(func() { + bz := tc.ack.Acknowledgement() + suite.Require().NotNil(bz) + suite.Require().Equal(tc.expBytes, bz) + }) + } else { + suite.Require().Error(err) + } + + suite.Require().Equal(tc.expSuccess, tc.ack.Success()) + }) + } +} + +// The safety of including ABCI error codes in the acknowledgement rests +// on the inclusion of these ABCI error codes in the abcitypes.ResponseDeliverTx +// hash. If the ABCI codes get removed from consensus they must no longer be used +// in the packet acknowledgement. +// +// This test acts as an indicator that the ABCI error codes may no longer be deterministic. +func (suite *TypesTestSuite) TestABCICodeDeterminism() { + // same ABCI error code used + err := errorsmod.Wrap(ibcerrors.ErrOutOfGas, "error string 1") + errSameABCICode := errorsmod.Wrap(ibcerrors.ErrOutOfGas, "error string 2") + + // different ABCI error code used + errDifferentABCICode := ibcerrors.ErrNotFound + + deliverTx := sdkerrors.ResponseExecTxResultWithEvents(err, gasUsed, gasWanted, []abcitypes.Event{}, false) + execTxResults := []*abcitypes.ExecTxResult{deliverTx} + + deliverTxSameABCICode := sdkerrors.ResponseExecTxResultWithEvents(errSameABCICode, gasUsed, gasWanted, []abcitypes.Event{}, false) + resultsSameABCICode := []*abcitypes.ExecTxResult{deliverTxSameABCICode} + + deliverTxDifferentABCICode := sdkerrors.ResponseExecTxResultWithEvents(errDifferentABCICode, gasUsed, gasWanted, []abcitypes.Event{}, false) + resultsDifferentABCICode := []*abcitypes.ExecTxResult{deliverTxDifferentABCICode} + + hash := cmtstate.TxResultsHash(execTxResults) + hashSameABCICode := cmtstate.TxResultsHash(resultsSameABCICode) + hashDifferentABCICode := cmtstate.TxResultsHash(resultsDifferentABCICode) + + suite.Require().Equal(hash, hashSameABCICode) + suite.Require().NotEqual(hash, hashDifferentABCICode) +} + +// TestAcknowledgementError will verify that only a constant string and +// ABCI error code are used in constructing the acknowledgement error string +func (suite *TypesTestSuite) TestAcknowledgementError() { + // same ABCI error code used + err := errorsmod.Wrap(ibcerrors.ErrOutOfGas, "error string 1") + errSameABCICode := errorsmod.Wrap(ibcerrors.ErrOutOfGas, "error string 2") + + // different ABCI error code used + errDifferentABCICode := ibcerrors.ErrNotFound + + ack := types.NewErrorAcknowledgement(err) + ackSameABCICode := types.NewErrorAcknowledgement(errSameABCICode) + ackDifferentABCICode := types.NewErrorAcknowledgement(errDifferentABCICode) + + suite.Require().Equal(ack, ackSameABCICode) + suite.Require().NotEqual(ack, ackDifferentABCICode) +} + +func (suite TypesTestSuite) TestAcknowledgementWithCodespace() { //nolint:govet // this is a test, we are okay with copying locks + testCases := []struct { + name string + ack types.Acknowledgement + expBytes []byte + }{ + { + "valid failed ack", + types.NewErrorAcknowledgementWithCodespace(ibcerrors.ErrInsufficientFunds), + []byte(`{"error":"ABCI error: ibc/3: error handling packet: see events for details"}`), + }, + { + "unknown error", + types.NewErrorAcknowledgementWithCodespace(errors.New("unknown error")), + []byte(`{"error":"ABCI error: undefined/1: error handling packet: see events for details"}`), + }, + { + "nil error", + types.NewErrorAcknowledgementWithCodespace(nil), + []byte(`{"error":"ABCI error: /0: error handling packet: see events for details"}`), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.Require().Equal(tc.expBytes, tc.ack.Acknowledgement()) + }) + } +} diff --git a/modules/core/04-channel/types/channel.go b/modules/core/04-channel/types/channel.go new file mode 100644 index 0000000..ff1212d --- /dev/null +++ b/modules/core/04-channel/types/channel.go @@ -0,0 +1,89 @@ +package types + +import ( + "slices" + + errorsmod "cosmossdk.io/errors" + + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +// NewChannel creates a new Channel instance +func NewChannel( + state State, ordering Order, counterparty Counterparty, + hops []string, version string, +) Channel { + return Channel{ + State: state, + Ordering: ordering, + Counterparty: counterparty, + ConnectionHops: hops, + Version: version, + } +} + +// ValidateBasic performs a basic validation of the channel fields +func (ch Channel) ValidateBasic() error { + if ch.State == UNINITIALIZED { + return ErrInvalidChannelState + } + if !slices.Contains([]Order{ORDERED, UNORDERED}, ch.Ordering) { + return errorsmod.Wrap(ErrInvalidChannelOrdering, ch.Ordering.String()) + } + if len(ch.ConnectionHops) != 1 { + return errorsmod.Wrap( + ErrTooManyConnectionHops, + "current IBC version only supports one connection hop", + ) + } + if err := host.ConnectionIdentifierValidator(ch.ConnectionHops[0]); err != nil { + return errorsmod.Wrap(err, "invalid connection hop ID") + } + return ch.Counterparty.ValidateBasic() +} + +// NewCounterparty returns a new Counterparty instance +func NewCounterparty(portID, channelID string) Counterparty { + return Counterparty{ + PortId: portID, + ChannelId: channelID, + } +} + +// ValidateBasic performs a basic validation check of the identifiers +func (c Counterparty) ValidateBasic() error { + if err := host.PortIdentifierValidator(c.PortId); err != nil { + return errorsmod.Wrap(err, "invalid counterparty port ID") + } + if c.ChannelId != "" { + if err := host.ChannelIdentifierValidator(c.ChannelId); err != nil { + return errorsmod.Wrap(err, "invalid counterparty channel ID") + } + } + return nil +} + +// NewIdentifiedChannel creates a new IdentifiedChannel instance +func NewIdentifiedChannel(portID, channelID string, ch Channel) IdentifiedChannel { + return IdentifiedChannel{ + State: ch.State, + Ordering: ch.Ordering, + Counterparty: ch.Counterparty, + ConnectionHops: ch.ConnectionHops, + Version: ch.Version, + PortId: portID, + ChannelId: channelID, + } +} + +// ValidateBasic performs a basic validation of the identifiers and channel fields. +func (ic IdentifiedChannel) ValidateBasic() error { + if err := host.ChannelIdentifierValidator(ic.ChannelId); err != nil { + return errorsmod.Wrap(err, "invalid channel ID") + } + if err := host.PortIdentifierValidator(ic.PortId); err != nil { + return errorsmod.Wrap(err, "invalid port ID") + } + channel := NewChannel(ic.State, ic.Ordering, ic.Counterparty, ic.ConnectionHops, ic.Version) + return channel.ValidateBasic() +} diff --git a/modules/core/04-channel/types/channel.pb.go b/modules/core/04-channel/types/channel.pb.go new file mode 100644 index 0000000..8012bc2 --- /dev/null +++ b/modules/core/04-channel/types/channel.pb.go @@ -0,0 +1,2716 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/channel/v1/channel.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + types "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// State defines if a channel is in one of the following states: +// CLOSED, INIT, TRYOPEN, OPEN, or UNINITIALIZED. +type State int32 + +const ( + // Default State + UNINITIALIZED State = 0 + // A channel has just started the opening handshake. + INIT State = 1 + // A channel has acknowledged the handshake step on the counterparty chain. + TRYOPEN State = 2 + // A channel has completed the handshake. Open channels are + // ready to send and receive packets. + OPEN State = 3 + // A channel has been closed and can no longer be used to send or receive + // packets. + CLOSED State = 4 +) + +var State_name = map[int32]string{ + 0: "STATE_UNINITIALIZED_UNSPECIFIED", + 1: "STATE_INIT", + 2: "STATE_TRYOPEN", + 3: "STATE_OPEN", + 4: "STATE_CLOSED", +} + +var State_value = map[string]int32{ + "STATE_UNINITIALIZED_UNSPECIFIED": 0, + "STATE_INIT": 1, + "STATE_TRYOPEN": 2, + "STATE_OPEN": 3, + "STATE_CLOSED": 4, +} + +func (x State) String() string { + return proto.EnumName(State_name, int32(x)) +} + +func (State) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_c3a07336710636a0, []int{0} +} + +// Order defines if a channel is ORDERED or UNORDERED +type Order int32 + +const ( + // zero-value for channel ordering + NONE Order = 0 + // packets can be delivered in any order, which may differ from the order in + // which they were sent. + UNORDERED Order = 1 + // packets are delivered exactly in the order which they were sent + ORDERED Order = 2 +) + +var Order_name = map[int32]string{ + 0: "ORDER_NONE_UNSPECIFIED", + 1: "ORDER_UNORDERED", + 2: "ORDER_ORDERED", +} + +var Order_value = map[string]int32{ + "ORDER_NONE_UNSPECIFIED": 0, + "ORDER_UNORDERED": 1, + "ORDER_ORDERED": 2, +} + +func (x Order) String() string { + return proto.EnumName(Order_name, int32(x)) +} + +func (Order) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_c3a07336710636a0, []int{1} +} + +// Channel defines pipeline for exactly-once packet delivery between specific +// modules on separate blockchains, which has at least one end capable of +// sending packets and one end capable of receiving packets. +type Channel struct { + // current state of the channel end + State State `protobuf:"varint,1,opt,name=state,proto3,enum=ibc.core.channel.v1.State" json:"state,omitempty"` + // whether the channel is ordered or unordered + Ordering Order `protobuf:"varint,2,opt,name=ordering,proto3,enum=ibc.core.channel.v1.Order" json:"ordering,omitempty"` + // counterparty channel end + Counterparty Counterparty `protobuf:"bytes,3,opt,name=counterparty,proto3" json:"counterparty"` + // list of connection identifiers, in order, along which packets sent on + // this channel will travel + ConnectionHops []string `protobuf:"bytes,4,rep,name=connection_hops,json=connectionHops,proto3" json:"connection_hops,omitempty"` + // opaque channel version, which is agreed upon during the handshake + Version string `protobuf:"bytes,5,opt,name=version,proto3" json:"version,omitempty"` +} + +func (m *Channel) Reset() { *m = Channel{} } +func (m *Channel) String() string { return proto.CompactTextString(m) } +func (*Channel) ProtoMessage() {} +func (*Channel) Descriptor() ([]byte, []int) { + return fileDescriptor_c3a07336710636a0, []int{0} +} +func (m *Channel) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Channel) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Channel.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Channel) XXX_Merge(src proto.Message) { + xxx_messageInfo_Channel.Merge(m, src) +} +func (m *Channel) XXX_Size() int { + return m.Size() +} +func (m *Channel) XXX_DiscardUnknown() { + xxx_messageInfo_Channel.DiscardUnknown(m) +} + +var xxx_messageInfo_Channel proto.InternalMessageInfo + +// IdentifiedChannel defines a channel with additional port and channel +// identifier fields. +type IdentifiedChannel struct { + // current state of the channel end + State State `protobuf:"varint,1,opt,name=state,proto3,enum=ibc.core.channel.v1.State" json:"state,omitempty"` + // whether the channel is ordered or unordered + Ordering Order `protobuf:"varint,2,opt,name=ordering,proto3,enum=ibc.core.channel.v1.Order" json:"ordering,omitempty"` + // counterparty channel end + Counterparty Counterparty `protobuf:"bytes,3,opt,name=counterparty,proto3" json:"counterparty"` + // list of connection identifiers, in order, along which packets sent on + // this channel will travel + ConnectionHops []string `protobuf:"bytes,4,rep,name=connection_hops,json=connectionHops,proto3" json:"connection_hops,omitempty"` + // opaque channel version, which is agreed upon during the handshake + Version string `protobuf:"bytes,5,opt,name=version,proto3" json:"version,omitempty"` + // port identifier + PortId string `protobuf:"bytes,6,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + // channel identifier + ChannelId string `protobuf:"bytes,7,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` +} + +func (m *IdentifiedChannel) Reset() { *m = IdentifiedChannel{} } +func (m *IdentifiedChannel) String() string { return proto.CompactTextString(m) } +func (*IdentifiedChannel) ProtoMessage() {} +func (*IdentifiedChannel) Descriptor() ([]byte, []int) { + return fileDescriptor_c3a07336710636a0, []int{1} +} +func (m *IdentifiedChannel) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *IdentifiedChannel) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_IdentifiedChannel.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *IdentifiedChannel) XXX_Merge(src proto.Message) { + xxx_messageInfo_IdentifiedChannel.Merge(m, src) +} +func (m *IdentifiedChannel) XXX_Size() int { + return m.Size() +} +func (m *IdentifiedChannel) XXX_DiscardUnknown() { + xxx_messageInfo_IdentifiedChannel.DiscardUnknown(m) +} + +var xxx_messageInfo_IdentifiedChannel proto.InternalMessageInfo + +// Counterparty defines a channel end counterparty +type Counterparty struct { + // port on the counterparty chain which owns the other end of the channel. + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + // channel end on the counterparty chain + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` +} + +func (m *Counterparty) Reset() { *m = Counterparty{} } +func (m *Counterparty) String() string { return proto.CompactTextString(m) } +func (*Counterparty) ProtoMessage() {} +func (*Counterparty) Descriptor() ([]byte, []int) { + return fileDescriptor_c3a07336710636a0, []int{2} +} +func (m *Counterparty) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Counterparty) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Counterparty.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Counterparty) XXX_Merge(src proto.Message) { + xxx_messageInfo_Counterparty.Merge(m, src) +} +func (m *Counterparty) XXX_Size() int { + return m.Size() +} +func (m *Counterparty) XXX_DiscardUnknown() { + xxx_messageInfo_Counterparty.DiscardUnknown(m) +} + +var xxx_messageInfo_Counterparty proto.InternalMessageInfo + +// Packet defines a type that carries data across different chains through IBC +type Packet struct { + // number corresponds to the order of sends and receives, where a Packet + // with an earlier sequence number must be sent and received before a Packet + // with a later sequence number. + Sequence uint64 `protobuf:"varint,1,opt,name=sequence,proto3" json:"sequence,omitempty"` + // identifies the port on the sending chain. + SourcePort string `protobuf:"bytes,2,opt,name=source_port,json=sourcePort,proto3" json:"source_port,omitempty"` + // identifies the channel end on the sending chain. + SourceChannel string `protobuf:"bytes,3,opt,name=source_channel,json=sourceChannel,proto3" json:"source_channel,omitempty"` + // identifies the port on the receiving chain. + DestinationPort string `protobuf:"bytes,4,opt,name=destination_port,json=destinationPort,proto3" json:"destination_port,omitempty"` + // identifies the channel end on the receiving chain. + DestinationChannel string `protobuf:"bytes,5,opt,name=destination_channel,json=destinationChannel,proto3" json:"destination_channel,omitempty"` + // actual opaque bytes transferred directly to the application module + Data []byte `protobuf:"bytes,6,opt,name=data,proto3" json:"data,omitempty"` + // block height after which the packet times out + TimeoutHeight types.Height `protobuf:"bytes,7,opt,name=timeout_height,json=timeoutHeight,proto3" json:"timeout_height"` + // block timestamp (in nanoseconds) after which the packet times out + TimeoutTimestamp uint64 `protobuf:"varint,8,opt,name=timeout_timestamp,json=timeoutTimestamp,proto3" json:"timeout_timestamp,omitempty"` +} + +func (m *Packet) Reset() { *m = Packet{} } +func (m *Packet) String() string { return proto.CompactTextString(m) } +func (*Packet) ProtoMessage() {} +func (*Packet) Descriptor() ([]byte, []int) { + return fileDescriptor_c3a07336710636a0, []int{3} +} +func (m *Packet) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Packet) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Packet.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Packet) XXX_Merge(src proto.Message) { + xxx_messageInfo_Packet.Merge(m, src) +} +func (m *Packet) XXX_Size() int { + return m.Size() +} +func (m *Packet) XXX_DiscardUnknown() { + xxx_messageInfo_Packet.DiscardUnknown(m) +} + +var xxx_messageInfo_Packet proto.InternalMessageInfo + +// PacketState defines the generic type necessary to retrieve and store +// packet commitments, acknowledgements, and receipts. +// Caller is responsible for knowing the context necessary to interpret this +// state as a commitment, acknowledgement, or a receipt. +type PacketState struct { + // channel port identifier. + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + // channel unique identifier. + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + // packet sequence. + Sequence uint64 `protobuf:"varint,3,opt,name=sequence,proto3" json:"sequence,omitempty"` + // embedded data that represents packet state. + Data []byte `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"` +} + +func (m *PacketState) Reset() { *m = PacketState{} } +func (m *PacketState) String() string { return proto.CompactTextString(m) } +func (*PacketState) ProtoMessage() {} +func (*PacketState) Descriptor() ([]byte, []int) { + return fileDescriptor_c3a07336710636a0, []int{4} +} +func (m *PacketState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PacketState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PacketState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *PacketState) XXX_Merge(src proto.Message) { + xxx_messageInfo_PacketState.Merge(m, src) +} +func (m *PacketState) XXX_Size() int { + return m.Size() +} +func (m *PacketState) XXX_DiscardUnknown() { + xxx_messageInfo_PacketState.DiscardUnknown(m) +} + +var xxx_messageInfo_PacketState proto.InternalMessageInfo + +// PacketId is an identifier for a unique Packet +// Source chains refer to packets by source port/channel +// Destination chains refer to packets by destination port/channel +type PacketId struct { + // channel port identifier + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + // channel unique identifier + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + // packet sequence + Sequence uint64 `protobuf:"varint,3,opt,name=sequence,proto3" json:"sequence,omitempty"` +} + +func (m *PacketId) Reset() { *m = PacketId{} } +func (m *PacketId) String() string { return proto.CompactTextString(m) } +func (*PacketId) ProtoMessage() {} +func (*PacketId) Descriptor() ([]byte, []int) { + return fileDescriptor_c3a07336710636a0, []int{5} +} +func (m *PacketId) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PacketId) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PacketId.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *PacketId) XXX_Merge(src proto.Message) { + xxx_messageInfo_PacketId.Merge(m, src) +} +func (m *PacketId) XXX_Size() int { + return m.Size() +} +func (m *PacketId) XXX_DiscardUnknown() { + xxx_messageInfo_PacketId.DiscardUnknown(m) +} + +var xxx_messageInfo_PacketId proto.InternalMessageInfo + +// Acknowledgement is the recommended acknowledgement format to be used by +// app-specific protocols. +// NOTE: The field numbers 21 and 22 were explicitly chosen to avoid accidental +// conflicts with other protobuf message formats used for acknowledgements. +// The first byte of any message with this format will be the non-ASCII values +// `0xaa` (result) or `0xb2` (error). Implemented as defined by ICS: +// https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope +type Acknowledgement struct { + // response contains either a result or an error and must be non-empty + // + // Types that are valid to be assigned to Response: + // *Acknowledgement_Result + // *Acknowledgement_Error + Response isAcknowledgement_Response `protobuf_oneof:"response"` +} + +func (m *Acknowledgement) Reset() { *m = Acknowledgement{} } +func (m *Acknowledgement) String() string { return proto.CompactTextString(m) } +func (*Acknowledgement) ProtoMessage() {} +func (*Acknowledgement) Descriptor() ([]byte, []int) { + return fileDescriptor_c3a07336710636a0, []int{6} +} +func (m *Acknowledgement) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Acknowledgement) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Acknowledgement.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Acknowledgement) XXX_Merge(src proto.Message) { + xxx_messageInfo_Acknowledgement.Merge(m, src) +} +func (m *Acknowledgement) XXX_Size() int { + return m.Size() +} +func (m *Acknowledgement) XXX_DiscardUnknown() { + xxx_messageInfo_Acknowledgement.DiscardUnknown(m) +} + +var xxx_messageInfo_Acknowledgement proto.InternalMessageInfo + +type isAcknowledgement_Response interface { + isAcknowledgement_Response() + MarshalTo([]byte) (int, error) + Size() int +} + +type Acknowledgement_Result struct { + Result []byte `protobuf:"bytes,21,opt,name=result,proto3,oneof" json:"result,omitempty"` +} +type Acknowledgement_Error struct { + Error string `protobuf:"bytes,22,opt,name=error,proto3,oneof" json:"error,omitempty"` +} + +func (*Acknowledgement_Result) isAcknowledgement_Response() {} +func (*Acknowledgement_Error) isAcknowledgement_Response() {} + +func (m *Acknowledgement) GetResponse() isAcknowledgement_Response { + if m != nil { + return m.Response + } + return nil +} + +func (m *Acknowledgement) GetResult() []byte { + if x, ok := m.GetResponse().(*Acknowledgement_Result); ok { + return x.Result + } + return nil +} + +func (m *Acknowledgement) GetError() string { + if x, ok := m.GetResponse().(*Acknowledgement_Error); ok { + return x.Error + } + return "" +} + +// XXX_OneofWrappers is for the internal use of the proto package. +func (*Acknowledgement) XXX_OneofWrappers() []interface{} { + return []interface{}{ + (*Acknowledgement_Result)(nil), + (*Acknowledgement_Error)(nil), + } +} + +// Timeout defines an execution deadline structure for 04-channel handlers. +// This includes packet lifecycle handlers. +// A valid Timeout contains either one or both of a timestamp and block height (sequence). +type Timeout struct { + // block height after which the packet times out + Height types.Height `protobuf:"bytes,1,opt,name=height,proto3" json:"height"` + // block timestamp (in nanoseconds) after which the packet times out + Timestamp uint64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` +} + +func (m *Timeout) Reset() { *m = Timeout{} } +func (m *Timeout) String() string { return proto.CompactTextString(m) } +func (*Timeout) ProtoMessage() {} +func (*Timeout) Descriptor() ([]byte, []int) { + return fileDescriptor_c3a07336710636a0, []int{7} +} +func (m *Timeout) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Timeout) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Timeout.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Timeout) XXX_Merge(src proto.Message) { + xxx_messageInfo_Timeout.Merge(m, src) +} +func (m *Timeout) XXX_Size() int { + return m.Size() +} +func (m *Timeout) XXX_DiscardUnknown() { + xxx_messageInfo_Timeout.DiscardUnknown(m) +} + +var xxx_messageInfo_Timeout proto.InternalMessageInfo + +func (m *Timeout) GetHeight() types.Height { + if m != nil { + return m.Height + } + return types.Height{} +} + +func (m *Timeout) GetTimestamp() uint64 { + if m != nil { + return m.Timestamp + } + return 0 +} + +func init() { + proto.RegisterEnum("ibc.core.channel.v1.State", State_name, State_value) + proto.RegisterEnum("ibc.core.channel.v1.Order", Order_name, Order_value) + proto.RegisterType((*Channel)(nil), "ibc.core.channel.v1.Channel") + proto.RegisterType((*IdentifiedChannel)(nil), "ibc.core.channel.v1.IdentifiedChannel") + proto.RegisterType((*Counterparty)(nil), "ibc.core.channel.v1.Counterparty") + proto.RegisterType((*Packet)(nil), "ibc.core.channel.v1.Packet") + proto.RegisterType((*PacketState)(nil), "ibc.core.channel.v1.PacketState") + proto.RegisterType((*PacketId)(nil), "ibc.core.channel.v1.PacketId") + proto.RegisterType((*Acknowledgement)(nil), "ibc.core.channel.v1.Acknowledgement") + proto.RegisterType((*Timeout)(nil), "ibc.core.channel.v1.Timeout") +} + +func init() { proto.RegisterFile("ibc/core/channel/v1/channel.proto", fileDescriptor_c3a07336710636a0) } + +var fileDescriptor_c3a07336710636a0 = []byte{ + // 843 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x55, 0xcd, 0x6e, 0xdb, 0x46, + 0x10, 0x26, 0x65, 0xea, 0x6f, 0x64, 0xd9, 0xf2, 0xa6, 0x75, 0x09, 0x22, 0xa5, 0x18, 0xa3, 0x45, + 0xdd, 0x14, 0x11, 0xad, 0xb4, 0x28, 0x8a, 0xdc, 0x6c, 0x89, 0xad, 0x88, 0x1a, 0x92, 0x40, 0xc9, + 0x87, 0xe6, 0x22, 0x50, 0xe4, 0x56, 0x22, 0x22, 0x71, 0x59, 0x72, 0xa5, 0x20, 0xe8, 0x0b, 0xa4, + 0x3a, 0xf5, 0x05, 0x04, 0x14, 0xe8, 0x43, 0xf4, 0x15, 0x02, 0xf4, 0x92, 0x63, 0x4e, 0x45, 0x61, + 0xbf, 0x48, 0xc1, 0xdd, 0xa5, 0x25, 0x05, 0x46, 0x50, 0x14, 0xe8, 0x29, 0x27, 0xed, 0x7c, 0xf3, + 0xcd, 0x37, 0xc3, 0x6f, 0x28, 0x2e, 0x3c, 0x08, 0xc6, 0x9e, 0xe9, 0x91, 0x18, 0x9b, 0xde, 0xd4, + 0x0d, 0x43, 0x3c, 0x33, 0x97, 0xcd, 0xec, 0xd8, 0x88, 0x62, 0x42, 0x09, 0xba, 0x17, 0x8c, 0xbd, + 0x46, 0x4a, 0x69, 0x64, 0xf8, 0xb2, 0xa9, 0x7d, 0x30, 0x21, 0x13, 0xc2, 0xf2, 0x66, 0x7a, 0xe2, + 0x54, 0xad, 0xbe, 0x51, 0x9b, 0x05, 0x38, 0xa4, 0x4c, 0x8c, 0x9d, 0x38, 0xe1, 0xe4, 0x97, 0x1c, + 0x14, 0x5b, 0x5c, 0x05, 0x9d, 0x41, 0x3e, 0xa1, 0x2e, 0xc5, 0xaa, 0x6c, 0xc8, 0xa7, 0x07, 0x8f, + 0xb5, 0xc6, 0x1d, 0x7d, 0x1a, 0x83, 0x94, 0xe1, 0x70, 0x22, 0xfa, 0x1a, 0x4a, 0x24, 0xf6, 0x71, + 0x1c, 0x84, 0x13, 0x35, 0xf7, 0x8e, 0xa2, 0x5e, 0x4a, 0x72, 0x6e, 0xb9, 0xe8, 0x7b, 0xd8, 0xf7, + 0xc8, 0x22, 0xa4, 0x38, 0x8e, 0xdc, 0x98, 0xbe, 0x50, 0xf7, 0x0c, 0xf9, 0xb4, 0xf2, 0xf8, 0xc1, + 0x9d, 0xb5, 0xad, 0x2d, 0xe2, 0x85, 0xf2, 0xea, 0xaf, 0xba, 0xe4, 0xec, 0x14, 0xa3, 0xcf, 0xe0, + 0xd0, 0x23, 0x61, 0x88, 0x3d, 0x1a, 0x90, 0x70, 0x34, 0x25, 0x51, 0xa2, 0x2a, 0xc6, 0xde, 0x69, + 0xd9, 0x39, 0xd8, 0xc0, 0x1d, 0x12, 0x25, 0x48, 0x85, 0xe2, 0x12, 0xc7, 0x49, 0x40, 0x42, 0x35, + 0x6f, 0xc8, 0xa7, 0x65, 0x27, 0x0b, 0x9f, 0x28, 0x2f, 0x7f, 0xab, 0x4b, 0x27, 0x7f, 0xe6, 0xe0, + 0xc8, 0xf6, 0x71, 0x48, 0x83, 0x1f, 0x03, 0xec, 0xbf, 0xf7, 0xae, 0xa0, 0x8f, 0xa0, 0x18, 0x91, + 0x98, 0x8e, 0x02, 0x5f, 0x2d, 0xb0, 0x4c, 0x21, 0x0d, 0x6d, 0x1f, 0x7d, 0x0c, 0x20, 0x46, 0x49, + 0x73, 0x45, 0x96, 0x2b, 0x0b, 0xc4, 0xf6, 0x85, 0x9b, 0x97, 0xb0, 0xbf, 0x3d, 0xe4, 0xb6, 0x9a, + 0xfc, 0x0e, 0xb5, 0xdc, 0xdd, 0x6a, 0x6f, 0x72, 0x50, 0xe8, 0xbb, 0xde, 0x33, 0x4c, 0x91, 0x06, + 0xa5, 0x04, 0xff, 0xb4, 0xc0, 0xa1, 0xc7, 0x77, 0xa2, 0x38, 0xb7, 0x31, 0xaa, 0x43, 0x25, 0x21, + 0x8b, 0xd8, 0xc3, 0xa3, 0x54, 0x5c, 0x88, 0x01, 0x87, 0xfa, 0x24, 0xa6, 0xe8, 0x53, 0x38, 0x10, + 0x04, 0xd1, 0x81, 0xb9, 0x5c, 0x76, 0xaa, 0x1c, 0xcd, 0x96, 0xfe, 0x39, 0xd4, 0x7c, 0x9c, 0xd0, + 0x20, 0x74, 0x99, 0x7d, 0x4c, 0x4c, 0x61, 0xc4, 0xc3, 0x2d, 0x9c, 0x29, 0x9a, 0x70, 0x6f, 0x9b, + 0x9a, 0xc9, 0x72, 0x2f, 0xd1, 0x56, 0x2a, 0xd3, 0x46, 0xa0, 0xf8, 0x2e, 0x75, 0x99, 0xa7, 0xfb, + 0x0e, 0x3b, 0xa3, 0xef, 0xe0, 0x80, 0x06, 0x73, 0x4c, 0x16, 0x74, 0x34, 0xc5, 0xc1, 0x64, 0x4a, + 0x99, 0xab, 0x95, 0x9d, 0x17, 0x87, 0xff, 0x6d, 0x97, 0xcd, 0x46, 0x87, 0x31, 0xc4, 0xd6, 0xab, + 0xa2, 0x8e, 0x83, 0xe8, 0x0b, 0x38, 0xca, 0x84, 0xd2, 0xdf, 0x84, 0xba, 0xf3, 0x48, 0x2d, 0x31, + 0x97, 0x6a, 0x22, 0x31, 0xcc, 0x70, 0x61, 0xed, 0xcf, 0x50, 0xe1, 0xce, 0xb2, 0x97, 0xf8, 0xbf, + 0xee, 0x69, 0x67, 0x2d, 0x7b, 0x6f, 0xad, 0x25, 0x7b, 0x64, 0x65, 0xf3, 0xc8, 0xa2, 0xb9, 0x0f, + 0x25, 0xde, 0xdc, 0xf6, 0xff, 0x8f, 0xce, 0xa2, 0x4b, 0x0f, 0x0e, 0xcf, 0xbd, 0x67, 0x21, 0x79, + 0x3e, 0xc3, 0xfe, 0x04, 0xcf, 0x71, 0x48, 0x91, 0x0a, 0x85, 0x18, 0x27, 0x8b, 0x19, 0x55, 0x3f, + 0x4c, 0x87, 0xea, 0x48, 0x8e, 0x88, 0xd1, 0x31, 0xe4, 0x71, 0x1c, 0x93, 0x58, 0x3d, 0x4e, 0x1b, + 0x75, 0x24, 0x87, 0x87, 0x17, 0x00, 0xa5, 0x18, 0x27, 0x11, 0x09, 0x13, 0x7c, 0xe2, 0x42, 0x71, + 0xc8, 0xdd, 0x44, 0xdf, 0x40, 0x41, 0xac, 0x4c, 0xfe, 0x97, 0x2b, 0x13, 0x7c, 0x74, 0x1f, 0xca, + 0x9b, 0x1d, 0xe5, 0xd8, 0xe0, 0x1b, 0xe0, 0xe1, 0x1f, 0x32, 0xe4, 0x07, 0xe2, 0x7b, 0x52, 0x1f, + 0x0c, 0xcf, 0x87, 0xd6, 0xe8, 0xaa, 0x6b, 0x77, 0xed, 0xa1, 0x7d, 0x7e, 0x69, 0x3f, 0xb5, 0xda, + 0xa3, 0xab, 0xee, 0xa0, 0x6f, 0xb5, 0xec, 0x6f, 0x6d, 0xab, 0x5d, 0x93, 0xb4, 0xa3, 0xd5, 0xda, + 0xa8, 0xee, 0x10, 0x90, 0x0a, 0xc0, 0xeb, 0x52, 0xb0, 0x26, 0x6b, 0xa5, 0xd5, 0xda, 0x50, 0xd2, + 0x33, 0xd2, 0xa1, 0xca, 0x33, 0x43, 0xe7, 0x87, 0x5e, 0xdf, 0xea, 0xd6, 0x72, 0x5a, 0x65, 0xb5, + 0x36, 0x8a, 0x22, 0xdc, 0x54, 0xb2, 0xe4, 0x1e, 0xaf, 0x64, 0x99, 0xfb, 0xb0, 0xcf, 0x33, 0xad, + 0xcb, 0xde, 0xc0, 0x6a, 0xd7, 0x14, 0x0d, 0x56, 0x6b, 0xa3, 0xc0, 0x23, 0x4d, 0x79, 0xf9, 0xbb, + 0x2e, 0x3d, 0x7c, 0x0e, 0x79, 0xf6, 0x69, 0x43, 0x9f, 0xc0, 0x71, 0xcf, 0x69, 0x5b, 0xce, 0xa8, + 0xdb, 0xeb, 0x5a, 0x6f, 0xcd, 0xcb, 0x24, 0x53, 0x1c, 0x9d, 0xc0, 0x21, 0x67, 0x5d, 0x75, 0xd9, + 0xaf, 0xd5, 0xae, 0xc9, 0x5a, 0x75, 0xb5, 0x36, 0xca, 0xb7, 0x40, 0x3a, 0x30, 0xe7, 0x64, 0x0c, + 0x31, 0xb0, 0x08, 0x79, 0xe3, 0x8b, 0xe1, 0xab, 0x6b, 0x5d, 0x7e, 0x7d, 0xad, 0xcb, 0x7f, 0x5f, + 0xeb, 0xf2, 0xaf, 0x37, 0xba, 0xf4, 0xfa, 0x46, 0x97, 0xde, 0xdc, 0xe8, 0xd2, 0xd3, 0x27, 0x93, + 0x80, 0x4e, 0x17, 0xe3, 0x86, 0x47, 0xe6, 0xa6, 0x47, 0x92, 0x39, 0x49, 0xcc, 0x60, 0xec, 0x3d, + 0x9a, 0x10, 0x73, 0xd9, 0x3c, 0x33, 0xe7, 0xc4, 0x5f, 0xcc, 0x70, 0xc2, 0x2f, 0xca, 0xb3, 0xaf, + 0x1e, 0x65, 0x37, 0x2f, 0x7d, 0x11, 0xe1, 0x64, 0x5c, 0x60, 0x37, 0xe5, 0x97, 0xff, 0x04, 0x00, + 0x00, 0xff, 0xff, 0xf1, 0xb2, 0x33, 0xdb, 0x9a, 0x07, 0x00, 0x00, +} + +func (m *Channel) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Channel) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Channel) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Version) > 0 { + i -= len(m.Version) + copy(dAtA[i:], m.Version) + i = encodeVarintChannel(dAtA, i, uint64(len(m.Version))) + i-- + dAtA[i] = 0x2a + } + if len(m.ConnectionHops) > 0 { + for iNdEx := len(m.ConnectionHops) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.ConnectionHops[iNdEx]) + copy(dAtA[i:], m.ConnectionHops[iNdEx]) + i = encodeVarintChannel(dAtA, i, uint64(len(m.ConnectionHops[iNdEx]))) + i-- + dAtA[i] = 0x22 + } + } + { + size, err := m.Counterparty.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintChannel(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if m.Ordering != 0 { + i = encodeVarintChannel(dAtA, i, uint64(m.Ordering)) + i-- + dAtA[i] = 0x10 + } + if m.State != 0 { + i = encodeVarintChannel(dAtA, i, uint64(m.State)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *IdentifiedChannel) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *IdentifiedChannel) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *IdentifiedChannel) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintChannel(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x3a + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintChannel(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0x32 + } + if len(m.Version) > 0 { + i -= len(m.Version) + copy(dAtA[i:], m.Version) + i = encodeVarintChannel(dAtA, i, uint64(len(m.Version))) + i-- + dAtA[i] = 0x2a + } + if len(m.ConnectionHops) > 0 { + for iNdEx := len(m.ConnectionHops) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.ConnectionHops[iNdEx]) + copy(dAtA[i:], m.ConnectionHops[iNdEx]) + i = encodeVarintChannel(dAtA, i, uint64(len(m.ConnectionHops[iNdEx]))) + i-- + dAtA[i] = 0x22 + } + } + { + size, err := m.Counterparty.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintChannel(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if m.Ordering != 0 { + i = encodeVarintChannel(dAtA, i, uint64(m.Ordering)) + i-- + dAtA[i] = 0x10 + } + if m.State != 0 { + i = encodeVarintChannel(dAtA, i, uint64(m.State)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *Counterparty) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Counterparty) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Counterparty) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintChannel(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintChannel(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Packet) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Packet) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Packet) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.TimeoutTimestamp != 0 { + i = encodeVarintChannel(dAtA, i, uint64(m.TimeoutTimestamp)) + i-- + dAtA[i] = 0x40 + } + { + size, err := m.TimeoutHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintChannel(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintChannel(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0x32 + } + if len(m.DestinationChannel) > 0 { + i -= len(m.DestinationChannel) + copy(dAtA[i:], m.DestinationChannel) + i = encodeVarintChannel(dAtA, i, uint64(len(m.DestinationChannel))) + i-- + dAtA[i] = 0x2a + } + if len(m.DestinationPort) > 0 { + i -= len(m.DestinationPort) + copy(dAtA[i:], m.DestinationPort) + i = encodeVarintChannel(dAtA, i, uint64(len(m.DestinationPort))) + i-- + dAtA[i] = 0x22 + } + if len(m.SourceChannel) > 0 { + i -= len(m.SourceChannel) + copy(dAtA[i:], m.SourceChannel) + i = encodeVarintChannel(dAtA, i, uint64(len(m.SourceChannel))) + i-- + dAtA[i] = 0x1a + } + if len(m.SourcePort) > 0 { + i -= len(m.SourcePort) + copy(dAtA[i:], m.SourcePort) + i = encodeVarintChannel(dAtA, i, uint64(len(m.SourcePort))) + i-- + dAtA[i] = 0x12 + } + if m.Sequence != 0 { + i = encodeVarintChannel(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *PacketState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PacketState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PacketState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintChannel(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0x22 + } + if m.Sequence != 0 { + i = encodeVarintChannel(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x18 + } + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintChannel(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintChannel(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *PacketId) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PacketId) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PacketId) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Sequence != 0 { + i = encodeVarintChannel(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x18 + } + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintChannel(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintChannel(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Acknowledgement) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Acknowledgement) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Acknowledgement) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Response != nil { + { + size := m.Response.Size() + i -= size + if _, err := m.Response.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + } + } + return len(dAtA) - i, nil +} + +func (m *Acknowledgement_Result) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Acknowledgement_Result) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.Result != nil { + i -= len(m.Result) + copy(dAtA[i:], m.Result) + i = encodeVarintChannel(dAtA, i, uint64(len(m.Result))) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xaa + } + return len(dAtA) - i, nil +} +func (m *Acknowledgement_Error) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Acknowledgement_Error) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + i -= len(m.Error) + copy(dAtA[i:], m.Error) + i = encodeVarintChannel(dAtA, i, uint64(len(m.Error))) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xb2 + return len(dAtA) - i, nil +} +func (m *Timeout) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Timeout) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Timeout) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Timestamp != 0 { + i = encodeVarintChannel(dAtA, i, uint64(m.Timestamp)) + i-- + dAtA[i] = 0x10 + } + { + size, err := m.Height.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintChannel(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintChannel(dAtA []byte, offset int, v uint64) int { + offset -= sovChannel(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Channel) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.State != 0 { + n += 1 + sovChannel(uint64(m.State)) + } + if m.Ordering != 0 { + n += 1 + sovChannel(uint64(m.Ordering)) + } + l = m.Counterparty.Size() + n += 1 + l + sovChannel(uint64(l)) + if len(m.ConnectionHops) > 0 { + for _, s := range m.ConnectionHops { + l = len(s) + n += 1 + l + sovChannel(uint64(l)) + } + } + l = len(m.Version) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + return n +} + +func (m *IdentifiedChannel) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.State != 0 { + n += 1 + sovChannel(uint64(m.State)) + } + if m.Ordering != 0 { + n += 1 + sovChannel(uint64(m.Ordering)) + } + l = m.Counterparty.Size() + n += 1 + l + sovChannel(uint64(l)) + if len(m.ConnectionHops) > 0 { + for _, s := range m.ConnectionHops { + l = len(s) + n += 1 + l + sovChannel(uint64(l)) + } + } + l = len(m.Version) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + return n +} + +func (m *Counterparty) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + return n +} + +func (m *Packet) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Sequence != 0 { + n += 1 + sovChannel(uint64(m.Sequence)) + } + l = len(m.SourcePort) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + l = len(m.SourceChannel) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + l = len(m.DestinationPort) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + l = len(m.DestinationChannel) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + l = len(m.Data) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + l = m.TimeoutHeight.Size() + n += 1 + l + sovChannel(uint64(l)) + if m.TimeoutTimestamp != 0 { + n += 1 + sovChannel(uint64(m.TimeoutTimestamp)) + } + return n +} + +func (m *PacketState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + if m.Sequence != 0 { + n += 1 + sovChannel(uint64(m.Sequence)) + } + l = len(m.Data) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + return n +} + +func (m *PacketId) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovChannel(uint64(l)) + } + if m.Sequence != 0 { + n += 1 + sovChannel(uint64(m.Sequence)) + } + return n +} + +func (m *Acknowledgement) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Response != nil { + n += m.Response.Size() + } + return n +} + +func (m *Acknowledgement_Result) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Result != nil { + l = len(m.Result) + n += 2 + l + sovChannel(uint64(l)) + } + return n +} +func (m *Acknowledgement_Error) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Error) + n += 2 + l + sovChannel(uint64(l)) + return n +} +func (m *Timeout) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Height.Size() + n += 1 + l + sovChannel(uint64(l)) + if m.Timestamp != 0 { + n += 1 + sovChannel(uint64(m.Timestamp)) + } + return n +} + +func sovChannel(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozChannel(x uint64) (n int) { + return sovChannel(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Channel) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Channel: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Channel: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field State", wireType) + } + m.State = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.State |= State(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Ordering", wireType) + } + m.Ordering = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Ordering |= Order(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Counterparty", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Counterparty.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectionHops", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ConnectionHops = append(m.ConnectionHops, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Version = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipChannel(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthChannel + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *IdentifiedChannel) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: IdentifiedChannel: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: IdentifiedChannel: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field State", wireType) + } + m.State = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.State |= State(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Ordering", wireType) + } + m.Ordering = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Ordering |= Order(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Counterparty", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Counterparty.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectionHops", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ConnectionHops = append(m.ConnectionHops, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Version = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipChannel(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthChannel + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Counterparty) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Counterparty: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Counterparty: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipChannel(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthChannel + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Packet) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Packet: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Packet: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SourcePort", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SourcePort = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SourceChannel", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SourceChannel = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DestinationPort", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DestinationPort = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DestinationChannel", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DestinationChannel = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeoutHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.TimeoutHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeoutTimestamp", wireType) + } + m.TimeoutTimestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TimeoutTimestamp |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipChannel(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthChannel + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PacketState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PacketState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PacketState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipChannel(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthChannel + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PacketId) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PacketId: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PacketId: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipChannel(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthChannel + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Acknowledgement) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Acknowledgement: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Acknowledgement: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 21: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Result", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := make([]byte, postIndex-iNdEx) + copy(v, dAtA[iNdEx:postIndex]) + m.Response = &Acknowledgement_Result{v} + iNdEx = postIndex + case 22: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Error", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Response = &Acknowledgement_Error{string(dAtA[iNdEx:postIndex])} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipChannel(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthChannel + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Timeout) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Timeout: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Timeout: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthChannel + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthChannel + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Height.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowChannel + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Timestamp |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipChannel(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthChannel + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipChannel(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowChannel + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowChannel + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowChannel + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthChannel + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupChannel + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthChannel + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthChannel = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowChannel = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupChannel = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/04-channel/types/channel_test.go b/modules/core/04-channel/types/channel_test.go new file mode 100644 index 0000000..cb6cbc7 --- /dev/null +++ b/modules/core/04-channel/types/channel_test.go @@ -0,0 +1,93 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +func TestChannelValidateBasic(t *testing.T) { + counterparty := types.Counterparty{"portidone", "channelidone"} + testCases := []struct { + name string + channel types.Channel + expErr error + }{ + {"valid channel", types.NewChannel(types.TRYOPEN, types.ORDERED, counterparty, connHops, version), nil}, + {"invalid state", types.NewChannel(types.UNINITIALIZED, types.ORDERED, counterparty, connHops, version), types.ErrInvalidChannelState}, + {"invalid order", types.NewChannel(types.TRYOPEN, types.NONE, counterparty, connHops, version), types.ErrInvalidChannelOrdering}, + {"more than 1 connection hop", types.NewChannel(types.TRYOPEN, types.ORDERED, counterparty, []string{"connection1", "connection2"}, version), types.ErrTooManyConnectionHops}, + {"invalid connection hop identifier", types.NewChannel(types.TRYOPEN, types.ORDERED, counterparty, []string{"(invalid)"}, version), host.ErrInvalidID}, + {"invalid counterparty", types.NewChannel(types.TRYOPEN, types.ORDERED, types.NewCounterparty("(invalidport)", "channelidone"), connHops, version), host.ErrInvalidID}, + } + + for i, tc := range testCases { + + err := tc.channel.ValidateBasic() + if tc.expErr == nil { + require.NoError(t, err, "valid test case %d failed: %s", i, tc.name) + } else { + require.Error(t, err, "invalid test case %d passed: %s", i, tc.name) + require.ErrorIs(t, err, tc.expErr) + } + } +} + +func TestCounterpartyValidateBasic(t *testing.T) { + testCases := []struct { + name string + counterparty types.Counterparty + expErr error + }{ + {"valid counterparty", types.Counterparty{"portidone", "channelidone"}, nil}, + {"invalid port id", types.Counterparty{"(InvalidPort)", "channelidone"}, host.ErrInvalidID}, + {"invalid channel id", types.Counterparty{"portidone", "(InvalidChannel)"}, host.ErrInvalidID}, + } + + for i, tc := range testCases { + + err := tc.counterparty.ValidateBasic() + if tc.expErr == nil { + require.NoError(t, err, "valid test case %d failed: %s", i, tc.name) + } else { + require.Error(t, err, "invalid test case %d passed: %s", i, tc.name) + require.ErrorIs(t, err, tc.expErr) + } + } +} + +// TestIdentifiedChannelValidateBasic tests ValidateBasic for IdentifiedChannel. +func TestIdentifiedChannelValidateBasic(t *testing.T) { + channel := types.NewChannel(types.TRYOPEN, types.ORDERED, types.Counterparty{"portidone", "channelidone"}, connHops, version) + + testCases := []struct { + name string + identifiedChannel types.IdentifiedChannel + expErr error + }{ + { + "valid identified channel", + types.NewIdentifiedChannel("portidone", "channelidone", channel), + nil, + }, + { + "invalid portID", + types.NewIdentifiedChannel("(InvalidPort)", "channelidone", channel), + host.ErrInvalidID, + }, + { + "invalid channelID", + types.NewIdentifiedChannel("portidone", "(InvalidChannel)", channel), + host.ErrInvalidID, + }, + } + + for _, tc := range testCases { + + err := tc.identifiedChannel.ValidateBasic() + require.ErrorIs(t, err, tc.expErr) + } +} diff --git a/modules/core/04-channel/types/codec.go b/modules/core/04-channel/types/codec.go new file mode 100644 index 0000000..4cc8bd4 --- /dev/null +++ b/modules/core/04-channel/types/codec.go @@ -0,0 +1,42 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/msgservice" + + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// RegisterInterfaces register the ibc channel submodule interfaces to protobuf +// Any. +func RegisterInterfaces(registry codectypes.InterfaceRegistry) { + registry.RegisterInterface( + "ibc.core.channel.v1.PacketI", + (*exported.PacketI)(nil), + &Packet{}, + ) + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgChannelOpenInit{}, + &MsgChannelOpenTry{}, + &MsgChannelOpenAck{}, + &MsgChannelOpenConfirm{}, + &MsgChannelCloseInit{}, + &MsgChannelCloseConfirm{}, + &MsgRecvPacket{}, + &MsgAcknowledgement{}, + &MsgTimeout{}, + &MsgTimeoutOnClose{}, + ) + + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} + +// SubModuleCdc references the global x/ibc/core/04-channel module codec. Note, the codec should +// ONLY be used in certain instances of tests and for JSON encoding. +// +// The actual codec used for serialization should be provided to x/ibc/core/04-channel and +// defined at the application level. +var SubModuleCdc = codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) diff --git a/modules/core/04-channel/types/codec_test.go b/modules/core/04-channel/types/codec_test.go new file mode 100644 index 0000000..940d7dd --- /dev/null +++ b/modules/core/04-channel/types/codec_test.go @@ -0,0 +1,98 @@ +package types_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + + ibc "github.com/cosmos/ibc-go/v10/modules/core" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" +) + +func TestCodecTypeRegistration(t *testing.T) { + testCases := []struct { + name string + typeURL string + expError error + }{ + { + "success: Packet", + sdk.MsgTypeURL(&types.Packet{}), + nil, + }, + { + "success: MsgChannelOpenInit", + sdk.MsgTypeURL(&types.MsgChannelOpenInit{}), + nil, + }, + { + "success: MsgChannelOpenTry", + sdk.MsgTypeURL(&types.MsgChannelOpenTry{}), + nil, + }, + { + "success: MsgChannelOpenAck", + sdk.MsgTypeURL(&types.MsgChannelOpenAck{}), + nil, + }, + { + "success: MsgChannelOpenConfirm", + sdk.MsgTypeURL(&types.MsgChannelOpenConfirm{}), + nil, + }, + { + "success: MsgChannelCloseInit", + sdk.MsgTypeURL(&types.MsgChannelCloseInit{}), + nil, + }, + { + "success: MsgChannelCloseConfirm", + sdk.MsgTypeURL(&types.MsgChannelCloseConfirm{}), + nil, + }, + { + "success: MsgRecvPacket", + sdk.MsgTypeURL(&types.MsgRecvPacket{}), + nil, + }, + { + "success: MsgAcknowledgement", + sdk.MsgTypeURL(&types.MsgAcknowledgement{}), + nil, + }, + { + "success: MsgTimeout", + sdk.MsgTypeURL(&types.MsgTimeout{}), + nil, + }, + { + "success: MsgTimeoutOnClose", + sdk.MsgTypeURL(&types.MsgTimeoutOnClose{}), + nil, + }, + { + "type not registered on codec", + "ibc.invalid.MsgTypeURL", + errors.New("unable to resolve type URL ibc.invalid.MsgTypeURL"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + encodingCfg := moduletestutil.MakeTestEncodingConfig(ibc.AppModuleBasic{}) + msg, err := encodingCfg.Codec.InterfaceRegistry().Resolve(tc.typeURL) + + if tc.expError == nil { + require.NotNil(t, msg) + require.NoError(t, err) + } else { + require.Nil(t, msg) + require.ErrorContains(t, err, tc.expError.Error()) + } + }) + } +} diff --git a/modules/core/04-channel/types/errors.go b/modules/core/04-channel/types/errors.go new file mode 100644 index 0000000..f848afd --- /dev/null +++ b/modules/core/04-channel/types/errors.go @@ -0,0 +1,62 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" +) + +// IBC channel sentinel errors +var ( + ErrChannelExists = errorsmod.Register(SubModuleName, 2, "channel already exists") + ErrChannelNotFound = errorsmod.Register(SubModuleName, 3, "channel not found") + ErrInvalidChannel = errorsmod.Register(SubModuleName, 4, "invalid channel") + ErrInvalidChannelState = errorsmod.Register(SubModuleName, 5, "invalid channel state") + ErrInvalidChannelOrdering = errorsmod.Register(SubModuleName, 6, "invalid channel ordering") + ErrInvalidCounterparty = errorsmod.Register(SubModuleName, 7, "invalid counterparty channel") + ErrSequenceSendNotFound = errorsmod.Register(SubModuleName, 10, "sequence send not found") + ErrSequenceReceiveNotFound = errorsmod.Register(SubModuleName, 11, "sequence receive not found") + ErrSequenceAckNotFound = errorsmod.Register(SubModuleName, 12, "sequence acknowledgement not found") + ErrInvalidPacket = errorsmod.Register(SubModuleName, 13, "invalid packet") + + // Deprecated: ErrPacketTimeout is deprecated and will be removed in a future release. + // Please use ErrTimeoutElapsed instead. + ErrPacketTimeout = errorsmod.Register(SubModuleName, 14, "packet timeout") + + ErrTooManyConnectionHops = errorsmod.Register(SubModuleName, 15, "too many connection hops") + ErrInvalidAcknowledgement = errorsmod.Register(SubModuleName, 16, "invalid acknowledgement") + ErrAcknowledgementExists = errorsmod.Register(SubModuleName, 17, "acknowledgement for packet already exists") + ErrInvalidChannelIdentifier = errorsmod.Register(SubModuleName, 18, "invalid channel identifier") + + // packets already relayed errors + ErrPacketReceived = errorsmod.Register(SubModuleName, 19, "packet already received") + ErrPacketCommitmentNotFound = errorsmod.Register(SubModuleName, 20, "packet commitment not found") // may occur for already received acknowledgements or timeouts and in rare cases for packets never sent + + // ORDERED channel error + ErrPacketSequenceOutOfOrder = errorsmod.Register(SubModuleName, 21, "packet sequence is out of order") + + // Antehandler error + ErrRedundantTx = errorsmod.Register(SubModuleName, 22, "packet messages are redundant") + + // Perform a no-op on the current Msg + ErrNoOpMsg = errorsmod.Register(SubModuleName, 23, "message is redundant, no-op will be performed") + + ErrInvalidChannelVersion = errorsmod.Register(SubModuleName, 24, "invalid channel version") + ErrPacketNotSent = errorsmod.Register(SubModuleName, 25, "packet has not been sent") + ErrInvalidTimeout = errorsmod.Register(SubModuleName, 26, "invalid packet timeout") + ErrUpgradeErrorNotFound = errorsmod.Register(SubModuleName, 27, "upgrade error receipt not found") + ErrInvalidUpgrade = errorsmod.Register(SubModuleName, 28, "invalid upgrade") + ErrInvalidUpgradeSequence = errorsmod.Register(SubModuleName, 29, "invalid upgrade sequence") + ErrUpgradeNotFound = errorsmod.Register(SubModuleName, 30, "upgrade not found") + ErrIncompatibleCounterpartyUpgrade = errorsmod.Register(SubModuleName, 31, "incompatible counterparty upgrade") + ErrInvalidUpgradeError = errorsmod.Register(SubModuleName, 32, "invalid upgrade error") + ErrUpgradeRestoreFailed = errorsmod.Register(SubModuleName, 33, "restore failed") + ErrUpgradeTimeout = errorsmod.Register(SubModuleName, 34, "upgrade timed-out") + ErrInvalidUpgradeTimeout = errorsmod.Register(SubModuleName, 35, "upgrade timeout is invalid") + ErrPendingInflightPackets = errorsmod.Register(SubModuleName, 36, "pending inflight packets exist") + ErrUpgradeTimeoutFailed = errorsmod.Register(SubModuleName, 37, "upgrade timeout failed") + ErrInvalidPruningLimit = errorsmod.Register(SubModuleName, 38, "invalid pruning limit") + ErrTimeoutNotReached = errorsmod.Register(SubModuleName, 39, "timeout not reached") + ErrTimeoutElapsed = errorsmod.Register(SubModuleName, 40, "timeout elapsed") + ErrPruningSequenceStartNotFound = errorsmod.Register(SubModuleName, 41, "pruning sequence start not found") + ErrRecvStartSequenceNotFound = errorsmod.Register(SubModuleName, 42, "recv start sequence not found") + ErrInvalidCommitment = errorsmod.Register(SubModuleName, 43, "invalid commitment") +) diff --git a/modules/core/04-channel/types/events.go b/modules/core/04-channel/types/events.go new file mode 100644 index 0000000..e3ed427 --- /dev/null +++ b/modules/core/04-channel/types/events.go @@ -0,0 +1,52 @@ +package types + +import ( + "fmt" + + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// IBC channel events +const ( + AttributeKeyConnectionID = "connection_id" + AttributeKeyPortID = "port_id" + AttributeKeyChannelID = "channel_id" + AttributeKeyChannelState = "channel_state" + AttributeKeyVersion = "version" + AttributeKeyConnectionHops = "connection_hops" + AttributeKeyOrdering = "ordering" + + AttributeCounterpartyPortID = "counterparty_port_id" + AttributeCounterpartyChannelID = "counterparty_channel_id" + + EventTypeSendPacket = "send_packet" + EventTypeRecvPacket = "recv_packet" + EventTypeWriteAck = "write_acknowledgement" + EventTypeAcknowledgePacket = "acknowledge_packet" + EventTypeTimeoutPacket = "timeout_packet" + + AttributeKeyDataHex = "packet_data_hex" + AttributeKeyAckHex = "packet_ack_hex" + AttributeKeyTimeoutHeight = "packet_timeout_height" + AttributeKeyTimeoutTimestamp = "packet_timeout_timestamp" + AttributeKeySequence = "packet_sequence" + AttributeKeySrcPort = "packet_src_port" + AttributeKeySrcChannel = "packet_src_channel" + AttributeKeyDstPort = "packet_dst_port" + AttributeKeyDstChannel = "packet_dst_channel" + AttributeKeyChannelOrdering = "packet_channel_ordering" + AttributeKeyConnection = "packet_connection" +) + +// IBC channel events vars +var ( + EventTypeChannelOpenInit = "channel_open_init" + EventTypeChannelOpenTry = "channel_open_try" + EventTypeChannelOpenAck = "channel_open_ack" + EventTypeChannelOpenConfirm = "channel_open_confirm" + EventTypeChannelCloseInit = "channel_close_init" + EventTypeChannelCloseConfirm = "channel_close_confirm" + EventTypeChannelClosed = "channel_close" + + AttributeValueCategory = fmt.Sprintf("%s_%s", ibcexported.ModuleName, SubModuleName) +) diff --git a/modules/core/04-channel/types/expected_keepers.go b/modules/core/04-channel/types/expected_keepers.go new file mode 100644 index 0000000..09dddbd --- /dev/null +++ b/modules/core/04-channel/types/expected_keepers.go @@ -0,0 +1,70 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// ClientKeeper expected account IBC client keeper +type ClientKeeper interface { + GetClientStatus(ctx sdk.Context, clientID string) exported.Status + GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool) + GetClientConsensusState(ctx sdk.Context, clientID string, height exported.Height) (exported.ConsensusState, bool) + GetClientLatestHeight(ctx sdk.Context, clientID string) clienttypes.Height + GetClientTimestampAtHeight(ctx sdk.Context, clientID string, height exported.Height) (uint64, error) +} + +// ConnectionKeeper expected account IBC connection keeper +type ConnectionKeeper interface { + GetConnection(ctx sdk.Context, connectionID string) (connectiontypes.ConnectionEnd, bool) + VerifyChannelState( + ctx sdk.Context, + connection connectiontypes.ConnectionEnd, + height exported.Height, + proof []byte, + portID, + channelID string, + channel Channel, + ) error + VerifyPacketCommitment( + ctx sdk.Context, + connection connectiontypes.ConnectionEnd, + height exported.Height, + proof []byte, + portID, + channelID string, + sequence uint64, + commitmentBytes []byte, + ) error + VerifyPacketAcknowledgement( + ctx sdk.Context, + connection connectiontypes.ConnectionEnd, + height exported.Height, + proof []byte, + portID, + channelID string, + sequence uint64, + acknowledgement []byte, + ) error + VerifyPacketReceiptAbsence( + ctx sdk.Context, + connection connectiontypes.ConnectionEnd, + height exported.Height, + proof []byte, + portID, + channelID string, + sequence uint64, + ) error + VerifyNextSequenceRecv( + ctx sdk.Context, + connection connectiontypes.ConnectionEnd, + height exported.Height, + proof []byte, + portID, + channelID string, + nextSequenceRecv uint64, + ) error +} diff --git a/modules/core/04-channel/types/genesis.go b/modules/core/04-channel/types/genesis.go new file mode 100644 index 0000000..80bbf75 --- /dev/null +++ b/modules/core/04-channel/types/genesis.go @@ -0,0 +1,160 @@ +package types + +import ( + "errors" + "fmt" + + errorsmod "cosmossdk.io/errors" + + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +// NewPacketState creates a new PacketState instance. +func NewPacketState(portID, channelID string, seq uint64, data []byte) PacketState { + return PacketState{ + PortId: portID, + ChannelId: channelID, + Sequence: seq, + Data: data, + } +} + +// Validate performs basic validation of fields returning an error upon any +// failure. +func (pa PacketState) Validate() error { + if pa.Data == nil { + return errors.New("data bytes cannot be nil") + } + return validateGenFields(pa.PortId, pa.ChannelId, pa.Sequence) +} + +// NewPacketSequence creates a new PacketSequences instance. +func NewPacketSequence(portID, channelID string, seq uint64) PacketSequence { + return PacketSequence{ + PortId: portID, + ChannelId: channelID, + Sequence: seq, + } +} + +// Validate performs basic validation of fields returning an error upon any +// failure. +func (ps PacketSequence) Validate() error { + return validateGenFields(ps.PortId, ps.ChannelId, ps.Sequence) +} + +// NewGenesisState creates a GenesisState instance. +func NewGenesisState( + channels []IdentifiedChannel, acks, receipts, commitments []PacketState, + sendSeqs, recvSeqs, ackSeqs []PacketSequence, nextChannelSequence uint64, +) GenesisState { + return GenesisState{ + Channels: channels, + Acknowledgements: acks, + Receipts: receipts, + Commitments: commitments, + SendSequences: sendSeqs, + RecvSequences: recvSeqs, + AckSequences: ackSeqs, + NextChannelSequence: nextChannelSequence, + } +} + +// DefaultGenesisState returns the ibc channel submodule's default genesis state. +func DefaultGenesisState() GenesisState { + return GenesisState{ + Channels: []IdentifiedChannel{}, + Acknowledgements: []PacketState{}, + Receipts: []PacketState{}, + Commitments: []PacketState{}, + SendSequences: []PacketSequence{}, + RecvSequences: []PacketSequence{}, + AckSequences: []PacketSequence{}, + NextChannelSequence: 0, + } +} + +// Validate performs basic genesis state validation returning an error upon any +// failure. +func (gs GenesisState) Validate() error { + // keep track of the max sequence to ensure it is less than + // the next sequence used in creating connection identifiers. + var maxSequence uint64 + + for i, channel := range gs.Channels { + sequence, err := ParseChannelSequence(channel.ChannelId) + if err != nil { + return err + } + + if sequence > maxSequence { + maxSequence = sequence + } + + if err := channel.ValidateBasic(); err != nil { + return fmt.Errorf("invalid channel %v channel index %d: %w", channel, i, err) + } + } + + if maxSequence != 0 && maxSequence >= gs.NextChannelSequence { + return errorsmod.Wrapf(ibcerrors.ErrInvalidSequence, "next channel sequence %d must be greater than maximum sequence used in channel identifier %d", gs.NextChannelSequence, maxSequence) + } + + for i, ack := range gs.Acknowledgements { + if err := ack.Validate(); err != nil { + return errorsmod.Wrapf(ErrInvalidAcknowledgement, "%v ack index %d: %s", ack, i, err.Error()) + } + if len(ack.Data) == 0 { + return errorsmod.Wrapf(ErrInvalidAcknowledgement, "%v ack index %d: data bytes cannot be empty", ack, i) + } + } + + for i, receipt := range gs.Receipts { + if err := receipt.Validate(); err != nil { + return errorsmod.Wrapf(ErrInvalidAcknowledgement, "%v ack index %d: %s", receipt, i, err.Error()) + } + } + + for i, commitment := range gs.Commitments { + if err := commitment.Validate(); err != nil { + return errorsmod.Wrapf(ErrInvalidCommitment, "%v index %d: %s", commitment, i, err.Error()) + } + if len(commitment.Data) == 0 { + return errorsmod.Wrapf(ErrInvalidAcknowledgement, "%v ack index %d: data bytes cannot be empty", commitment, i) + } + } + + for i, ss := range gs.SendSequences { + if err := ss.Validate(); err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidSequence, "invalid send sequence %v index %d: %s", ss, i, err.Error()) + } + } + + for i, rs := range gs.RecvSequences { + if err := rs.Validate(); err != nil { + return fmt.Errorf("invalid receive sequence %v index %d: %w", rs, i, err) + } + } + + for i, as := range gs.AckSequences { + if err := as.Validate(); err != nil { + return errorsmod.Wrapf(ErrInvalidAcknowledgement, "sequence %v index %d: %s", as, i, err.Error()) + } + } + + return nil +} + +func validateGenFields(portID, channelID string, sequence uint64) error { + if err := host.PortIdentifierValidator(portID); err != nil { + return fmt.Errorf("invalid port Id: %w", err) + } + if err := host.ChannelIdentifierValidator(channelID); err != nil { + return fmt.Errorf("invalid channel Id: %w", err) + } + if sequence == 0 { + return errors.New("sequence cannot be 0") + } + return nil +} diff --git a/modules/core/04-channel/types/genesis.pb.go b/modules/core/04-channel/types/genesis.pb.go new file mode 100644 index 0000000..dd7910c --- /dev/null +++ b/modules/core/04-channel/types/genesis.pb.go @@ -0,0 +1,1011 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/channel/v1/genesis.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// GenesisState defines the ibc channel submodule's genesis state. +type GenesisState struct { + Channels []IdentifiedChannel `protobuf:"bytes,1,rep,name=channels,proto3,casttype=IdentifiedChannel" json:"channels"` + Acknowledgements []PacketState `protobuf:"bytes,2,rep,name=acknowledgements,proto3" json:"acknowledgements"` + Commitments []PacketState `protobuf:"bytes,3,rep,name=commitments,proto3" json:"commitments"` + Receipts []PacketState `protobuf:"bytes,4,rep,name=receipts,proto3" json:"receipts"` + SendSequences []PacketSequence `protobuf:"bytes,5,rep,name=send_sequences,json=sendSequences,proto3" json:"send_sequences"` + RecvSequences []PacketSequence `protobuf:"bytes,6,rep,name=recv_sequences,json=recvSequences,proto3" json:"recv_sequences"` + AckSequences []PacketSequence `protobuf:"bytes,7,rep,name=ack_sequences,json=ackSequences,proto3" json:"ack_sequences"` + // the sequence for the next generated channel identifier + NextChannelSequence uint64 `protobuf:"varint,8,opt,name=next_channel_sequence,json=nextChannelSequence,proto3" json:"next_channel_sequence,omitempty"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_cb06ec201f452595, []int{0} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetChannels() []IdentifiedChannel { + if m != nil { + return m.Channels + } + return nil +} + +func (m *GenesisState) GetAcknowledgements() []PacketState { + if m != nil { + return m.Acknowledgements + } + return nil +} + +func (m *GenesisState) GetCommitments() []PacketState { + if m != nil { + return m.Commitments + } + return nil +} + +func (m *GenesisState) GetReceipts() []PacketState { + if m != nil { + return m.Receipts + } + return nil +} + +func (m *GenesisState) GetSendSequences() []PacketSequence { + if m != nil { + return m.SendSequences + } + return nil +} + +func (m *GenesisState) GetRecvSequences() []PacketSequence { + if m != nil { + return m.RecvSequences + } + return nil +} + +func (m *GenesisState) GetAckSequences() []PacketSequence { + if m != nil { + return m.AckSequences + } + return nil +} + +func (m *GenesisState) GetNextChannelSequence() uint64 { + if m != nil { + return m.NextChannelSequence + } + return 0 +} + +// PacketSequence defines the genesis type necessary to retrieve and store +// next send and receive sequences. +type PacketSequence struct { + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + Sequence uint64 `protobuf:"varint,3,opt,name=sequence,proto3" json:"sequence,omitempty"` +} + +func (m *PacketSequence) Reset() { *m = PacketSequence{} } +func (m *PacketSequence) String() string { return proto.CompactTextString(m) } +func (*PacketSequence) ProtoMessage() {} +func (*PacketSequence) Descriptor() ([]byte, []int) { + return fileDescriptor_cb06ec201f452595, []int{1} +} +func (m *PacketSequence) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PacketSequence) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PacketSequence.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *PacketSequence) XXX_Merge(src proto.Message) { + xxx_messageInfo_PacketSequence.Merge(m, src) +} +func (m *PacketSequence) XXX_Size() int { + return m.Size() +} +func (m *PacketSequence) XXX_DiscardUnknown() { + xxx_messageInfo_PacketSequence.DiscardUnknown(m) +} + +var xxx_messageInfo_PacketSequence proto.InternalMessageInfo + +func (m *PacketSequence) GetPortId() string { + if m != nil { + return m.PortId + } + return "" +} + +func (m *PacketSequence) GetChannelId() string { + if m != nil { + return m.ChannelId + } + return "" +} + +func (m *PacketSequence) GetSequence() uint64 { + if m != nil { + return m.Sequence + } + return 0 +} + +func init() { + proto.RegisterType((*GenesisState)(nil), "ibc.core.channel.v1.GenesisState") + proto.RegisterType((*PacketSequence)(nil), "ibc.core.channel.v1.PacketSequence") +} + +func init() { proto.RegisterFile("ibc/core/channel/v1/genesis.proto", fileDescriptor_cb06ec201f452595) } + +var fileDescriptor_cb06ec201f452595 = []byte{ + // 447 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0xd3, 0x4d, 0x6f, 0xd3, 0x30, + 0x18, 0x07, 0xf0, 0x66, 0x2d, 0x5d, 0xe7, 0xbd, 0x08, 0x3c, 0x10, 0xa1, 0x12, 0x59, 0x19, 0x12, + 0xea, 0x65, 0xf1, 0x3a, 0x38, 0x71, 0x0c, 0x07, 0xe8, 0x05, 0x4d, 0x81, 0x13, 0x12, 0xaa, 0x12, + 0xfb, 0x21, 0xb3, 0xd2, 0xd8, 0x21, 0x76, 0x03, 0x7c, 0x0b, 0x3e, 0xd6, 0x8e, 0x3b, 0x72, 0x9a, + 0x50, 0xfb, 0x21, 0x90, 0x38, 0xa1, 0x38, 0x2f, 0x2b, 0x5a, 0x35, 0x29, 0xb7, 0xda, 0xcf, 0xf3, + 0xff, 0xfd, 0x7b, 0x88, 0xd1, 0x33, 0x1e, 0x52, 0x42, 0x65, 0x06, 0x84, 0x5e, 0x04, 0x42, 0xc0, + 0x9c, 0xe4, 0x13, 0x12, 0x81, 0x00, 0xc5, 0x95, 0x9b, 0x66, 0x52, 0x4b, 0x7c, 0xc8, 0x43, 0xea, + 0x16, 0x2b, 0x6e, 0xb5, 0xe2, 0xe6, 0x93, 0xe1, 0xc3, 0x48, 0x46, 0xd2, 0xcc, 0x49, 0xf1, 0xab, + 0x5c, 0x1d, 0x6e, 0xd4, 0xea, 0x94, 0x59, 0x39, 0xfe, 0xd3, 0x43, 0x7b, 0x6f, 0x4b, 0xff, 0x83, + 0x0e, 0x34, 0xe0, 0xcf, 0x68, 0x50, 0x6d, 0x28, 0xdb, 0x1a, 0x75, 0xc7, 0xbb, 0x67, 0x2f, 0xdc, + 0x0d, 0x8d, 0xee, 0x94, 0x81, 0xd0, 0xfc, 0x0b, 0x07, 0xf6, 0xa6, 0xbc, 0xf4, 0x9e, 0x5c, 0x5e, + 0x1f, 0x75, 0xfe, 0x5e, 0x1f, 0x3d, 0xb8, 0x35, 0xf2, 0x1b, 0x12, 0xfb, 0xe8, 0x7e, 0x40, 0x63, + 0x21, 0xbf, 0xcd, 0x81, 0x45, 0x90, 0x80, 0xd0, 0xca, 0xde, 0x32, 0x35, 0xa3, 0x8d, 0x35, 0xe7, + 0x01, 0x8d, 0x41, 0x9b, 0xbf, 0xe6, 0xf5, 0x8a, 0x02, 0xff, 0x56, 0x1e, 0xbf, 0x43, 0xbb, 0x54, + 0x26, 0x09, 0xd7, 0x25, 0xd7, 0x6d, 0xc5, 0xad, 0x47, 0xb1, 0x87, 0x06, 0x19, 0x50, 0xe0, 0xa9, + 0x56, 0x76, 0xaf, 0x15, 0xd3, 0xe4, 0xf0, 0x39, 0x3a, 0x50, 0x20, 0xd8, 0x4c, 0xc1, 0xd7, 0x05, + 0x08, 0x0a, 0xca, 0xbe, 0x67, 0xa4, 0xe7, 0x77, 0x49, 0xd5, 0x6e, 0x85, 0xed, 0x17, 0x40, 0x7d, + 0x67, 0xc4, 0x0c, 0x68, 0xbe, 0x26, 0xf6, 0x5b, 0x8b, 0x05, 0x70, 0x23, 0xbe, 0x47, 0xfb, 0x01, + 0x8d, 0xd7, 0xc0, 0xed, 0xb6, 0xe0, 0x5e, 0x40, 0xe3, 0x1b, 0xef, 0x0c, 0x3d, 0x12, 0xf0, 0x5d, + 0xcf, 0xaa, 0x54, 0x03, 0xdb, 0x83, 0x91, 0x35, 0xee, 0xf9, 0x87, 0xc5, 0xb0, 0xfa, 0x16, 0xea, + 0xd0, 0x31, 0x43, 0x07, 0xff, 0xcb, 0xf8, 0x31, 0xda, 0x4e, 0x65, 0xa6, 0x67, 0x9c, 0xd9, 0xd6, + 0xc8, 0x1a, 0xef, 0xf8, 0xfd, 0xe2, 0x38, 0x65, 0xf8, 0x29, 0x42, 0xb5, 0xcc, 0x99, 0xbd, 0x65, + 0x66, 0x3b, 0xd5, 0xcd, 0x94, 0xe1, 0x21, 0x1a, 0x34, 0x85, 0x5d, 0x53, 0xd8, 0x9c, 0xbd, 0x8f, + 0x97, 0x4b, 0xc7, 0xba, 0x5a, 0x3a, 0xd6, 0xef, 0xa5, 0x63, 0xfd, 0x5c, 0x39, 0x9d, 0xab, 0x95, + 0xd3, 0xf9, 0xb5, 0x72, 0x3a, 0x9f, 0x5e, 0x47, 0x5c, 0x5f, 0x2c, 0x42, 0x97, 0xca, 0x84, 0x50, + 0xa9, 0x12, 0xa9, 0x08, 0x0f, 0xe9, 0x49, 0x24, 0x49, 0x3e, 0x39, 0x25, 0x89, 0x64, 0x8b, 0x39, + 0xa8, 0xf2, 0xf5, 0x9c, 0xbe, 0x3a, 0xa9, 0x1f, 0x90, 0xfe, 0x91, 0x82, 0x0a, 0xfb, 0xe6, 0xf1, + 0xbc, 0xfc, 0x17, 0x00, 0x00, 0xff, 0xff, 0xd5, 0x69, 0xaa, 0xca, 0xaf, 0x03, 0x00, 0x00, +} + +func (m *GenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.NextChannelSequence != 0 { + i = encodeVarintGenesis(dAtA, i, uint64(m.NextChannelSequence)) + i-- + dAtA[i] = 0x40 + } + if len(m.AckSequences) > 0 { + for iNdEx := len(m.AckSequences) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.AckSequences[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + } + } + if len(m.RecvSequences) > 0 { + for iNdEx := len(m.RecvSequences) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.RecvSequences[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + } + if len(m.SendSequences) > 0 { + for iNdEx := len(m.SendSequences) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.SendSequences[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + } + if len(m.Receipts) > 0 { + for iNdEx := len(m.Receipts) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Receipts[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + } + if len(m.Commitments) > 0 { + for iNdEx := len(m.Commitments) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Commitments[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + if len(m.Acknowledgements) > 0 { + for iNdEx := len(m.Acknowledgements) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Acknowledgements[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.Channels) > 0 { + for iNdEx := len(m.Channels) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Channels[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *PacketSequence) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PacketSequence) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PacketSequence) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Sequence != 0 { + i = encodeVarintGenesis(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x18 + } + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Channels) > 0 { + for _, e := range m.Channels { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.Acknowledgements) > 0 { + for _, e := range m.Acknowledgements { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.Commitments) > 0 { + for _, e := range m.Commitments { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.Receipts) > 0 { + for _, e := range m.Receipts { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.SendSequences) > 0 { + for _, e := range m.SendSequences { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.RecvSequences) > 0 { + for _, e := range m.RecvSequences { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.AckSequences) > 0 { + for _, e := range m.AckSequences { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if m.NextChannelSequence != 0 { + n += 1 + sovGenesis(uint64(m.NextChannelSequence)) + } + return n +} + +func (m *PacketSequence) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + if m.Sequence != 0 { + n += 1 + sovGenesis(uint64(m.Sequence)) + } + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Channels", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Channels = append(m.Channels, IdentifiedChannel{}) + if err := m.Channels[len(m.Channels)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Acknowledgements", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Acknowledgements = append(m.Acknowledgements, PacketState{}) + if err := m.Acknowledgements[len(m.Acknowledgements)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Commitments", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Commitments = append(m.Commitments, PacketState{}) + if err := m.Commitments[len(m.Commitments)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Receipts", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Receipts = append(m.Receipts, PacketState{}) + if err := m.Receipts[len(m.Receipts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SendSequences", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SendSequences = append(m.SendSequences, PacketSequence{}) + if err := m.SendSequences[len(m.SendSequences)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RecvSequences", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RecvSequences = append(m.RecvSequences, PacketSequence{}) + if err := m.RecvSequences[len(m.RecvSequences)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AckSequences", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AckSequences = append(m.AckSequences, PacketSequence{}) + if err := m.AckSequences[len(m.AckSequences)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NextChannelSequence", wireType) + } + m.NextChannelSequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.NextChannelSequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PacketSequence) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PacketSequence: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PacketSequence: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/04-channel/types/genesis_test.go b/modules/core/04-channel/types/genesis_test.go new file mode 100644 index 0000000..e233372 --- /dev/null +++ b/modules/core/04-channel/types/genesis_test.go @@ -0,0 +1,228 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +const ( + testPort1 = "firstport" + testPort2 = "secondport" + testConnectionIDA = "connectionidatob" + + testChannel1 = "channel-0" + testChannel2 = "channel-1" + + testChannelOrder = types.ORDERED + testChannelVersion = "1.0" +) + +func TestValidateGenesis(t *testing.T) { + counterparty1 := types.NewCounterparty(testPort1, testChannel1) + counterparty2 := types.NewCounterparty(testPort2, testChannel2) + testCases := []struct { + name string + genState types.GenesisState + expErr error + }{ + { + name: "default", + genState: types.DefaultGenesisState(), + expErr: nil, + }, + { + name: "valid genesis", + genState: types.NewGenesisState( + []types.IdentifiedChannel{ + types.NewIdentifiedChannel( + testPort1, testChannel1, types.NewChannel( + types.INIT, testChannelOrder, counterparty2, []string{testConnectionIDA}, testChannelVersion, + ), + ), + types.NewIdentifiedChannel( + testPort2, testChannel2, types.NewChannel( + types.INIT, testChannelOrder, counterparty1, []string{testConnectionIDA}, testChannelVersion, + ), + ), + }, + []types.PacketState{ + types.NewPacketState(testPort2, testChannel2, 1, []byte("ack")), + }, + []types.PacketState{ + types.NewPacketState(testPort2, testChannel2, 1, []byte("")), + }, + []types.PacketState{ + types.NewPacketState(testPort1, testChannel1, 1, []byte("commit_hash")), + }, + []types.PacketSequence{ + types.NewPacketSequence(testPort1, testChannel1, 1), + }, + []types.PacketSequence{ + types.NewPacketSequence(testPort2, testChannel2, 1), + }, + []types.PacketSequence{ + types.NewPacketSequence(testPort2, testChannel2, 1), + }, + 2, + ), + expErr: nil, + }, + { + name: "invalid channel", + genState: types.GenesisState{ + Channels: []types.IdentifiedChannel{ + types.NewIdentifiedChannel( + testPort1, "(testChannel1)", types.NewChannel( + types.INIT, testChannelOrder, counterparty2, []string{testConnectionIDA}, testChannelVersion, + ), + ), + }, + }, + expErr: host.ErrInvalidID, + }, + { + name: "invalid ack", + genState: types.GenesisState{ + Acknowledgements: []types.PacketState{ + types.NewPacketState(testPort2, testChannel2, 1, nil), + }, + }, + expErr: types.ErrInvalidAcknowledgement, + }, + { + name: "invalid commitment", + genState: types.GenesisState{ + Commitments: []types.PacketState{ + types.NewPacketState(testPort1, testChannel1, 1, nil), + }, + }, + expErr: types.ErrInvalidCommitment, + }, + { + name: "invalid send seq", + genState: types.GenesisState{ + SendSequences: []types.PacketSequence{ + types.NewPacketSequence(testPort1, testChannel1, 0), + }, + }, + expErr: ibcerrors.ErrInvalidSequence, + }, + { + name: "invalid recv seq", + genState: types.GenesisState{ + RecvSequences: []types.PacketSequence{ + types.NewPacketSequence(testPort1, "(testChannel1)", 1), + }, + }, + expErr: host.ErrInvalidID, + }, + { + name: "invalid recv seq 2", + genState: types.GenesisState{ + RecvSequences: []types.PacketSequence{ + types.NewPacketSequence("(testPort1)", testChannel1, 1), + }, + }, + expErr: host.ErrInvalidID, + }, + { + name: "invalid ack seq", + genState: types.GenesisState{ + AckSequences: []types.PacketSequence{ + types.NewPacketSequence(testPort1, "(testChannel1)", 1), + }, + }, + expErr: types.ErrInvalidAcknowledgement, + }, + { + name: "invalid channel identifier", + genState: types.NewGenesisState( + []types.IdentifiedChannel{ + types.NewIdentifiedChannel( + testPort1, "chan-0", types.NewChannel( + types.INIT, testChannelOrder, counterparty2, []string{testConnectionIDA}, testChannelVersion, + ), + ), + types.NewIdentifiedChannel( + testPort2, testChannel2, types.NewChannel( + types.INIT, testChannelOrder, counterparty1, []string{testConnectionIDA}, testChannelVersion, + ), + ), + }, + []types.PacketState{ + types.NewPacketState(testPort2, testChannel2, 1, []byte("ack")), + }, + []types.PacketState{ + types.NewPacketState(testPort2, testChannel2, 1, []byte("")), + }, + []types.PacketState{ + types.NewPacketState(testPort1, testChannel1, 1, []byte("commit_hash")), + }, + []types.PacketSequence{ + types.NewPacketSequence(testPort1, testChannel1, 1), + }, + []types.PacketSequence{ + types.NewPacketSequence(testPort2, testChannel2, 1), + }, + []types.PacketSequence{ + types.NewPacketSequence(testPort2, testChannel2, 1), + }, + 0, + ), + expErr: host.ErrInvalidID, + }, + { + name: "next channel sequence is less than maximum channel identifier sequence used", + genState: types.NewGenesisState( + []types.IdentifiedChannel{ + types.NewIdentifiedChannel( + testPort1, "channel-10", types.NewChannel( + types.INIT, testChannelOrder, counterparty2, []string{testConnectionIDA}, testChannelVersion, + ), + ), + types.NewIdentifiedChannel( + testPort2, testChannel2, types.NewChannel( + types.INIT, testChannelOrder, counterparty1, []string{testConnectionIDA}, testChannelVersion, + ), + ), + }, + []types.PacketState{ + types.NewPacketState(testPort2, testChannel2, 1, []byte("ack")), + }, + []types.PacketState{ + types.NewPacketState(testPort2, testChannel2, 1, []byte("")), + }, + []types.PacketState{ + types.NewPacketState(testPort1, testChannel1, 1, []byte("commit_hash")), + }, + []types.PacketSequence{ + types.NewPacketSequence(testPort1, testChannel1, 1), + }, + []types.PacketSequence{ + types.NewPacketSequence(testPort2, testChannel2, 1), + }, + []types.PacketSequence{ + types.NewPacketSequence(testPort2, testChannel2, 1), + }, + 0, + ), + expErr: ibcerrors.ErrInvalidSequence, + }, + } + + for _, tc := range testCases { + + err := tc.genState.Validate() + if tc.expErr == nil { + require.NoError(t, err, tc.name) + } else { + require.Error(t, err, tc.name) + require.ErrorIs(t, err, tc.expErr) + } + } +} diff --git a/modules/core/04-channel/types/keys.go b/modules/core/04-channel/types/keys.go new file mode 100644 index 0000000..5f9f0f3 --- /dev/null +++ b/modules/core/04-channel/types/keys.go @@ -0,0 +1,67 @@ +package types + +import ( + "fmt" + "regexp" + + errorsmod "cosmossdk.io/errors" + + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +const ( + // SubModuleName defines the IBC channels name + SubModuleName = "channel" + + // StoreKey is the store key string for IBC channels + StoreKey = SubModuleName + + // RouterKey is the message route for IBC channels + RouterKey = SubModuleName + + // QuerierRoute is the querier route for IBC channels + QuerierRoute = SubModuleName + + // KeyNextChannelSequence is the key used to store the next channel sequence in + // the keeper. + KeyNextChannelSequence = "nextChannelSequence" + + // ChannelPrefix is the prefix used when creating a channel identifier + ChannelPrefix = "channel-" +) + +// FormatChannelIdentifier returns the channel identifier with the sequence appended. +// This is an SDK specific format not enforced by IBC protocol. +func FormatChannelIdentifier(sequence uint64) string { + return fmt.Sprintf("%s%d", ChannelPrefix, sequence) +} + +// IsChannelIDFormat checks if a channelID is in the format required on the SDK for +// parsing channel identifiers. The channel identifier must be in the form: `channel-{N} +var IsChannelIDFormat = regexp.MustCompile(`^channel-[0-9]{1,20}$`).MatchString + +// IsValidChannelID checks if a channelID is valid and can be parsed to the channel +// identifier format. +func IsValidChannelID(channelID string) bool { + _, err := ParseChannelSequence(channelID) + return err == nil +} + +// ParseChannelSequence parses the channel sequence from the channel identifier. +func ParseChannelSequence(channelID string) (uint64, error) { + if !IsChannelIDFormat(channelID) { + return 0, errorsmod.Wrap(host.ErrInvalidID, "channel identifier is not in the format: `channel-{N}`") + } + + sequence, err := host.ParseIdentifier(channelID, ChannelPrefix) + if err != nil { + return 0, errorsmod.Wrap(err, "invalid channel identifier") + } + + return sequence, nil +} + +// FilteredPortPrefix returns the prefix key for the given port prefix. +func FilteredPortPrefix(portPrefix string) []byte { + return fmt.Appendf(nil, "%s/%s/%s", host.KeyChannelEndPrefix, host.KeyPortPrefix, portPrefix) +} diff --git a/modules/core/04-channel/types/keys_test.go b/modules/core/04-channel/types/keys_test.go new file mode 100644 index 0000000..3f7239e --- /dev/null +++ b/modules/core/04-channel/types/keys_test.go @@ -0,0 +1,50 @@ +package types_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +// tests ParseChannelSequence and IsValidChannelID +func TestParseChannelSequence(t *testing.T) { + testCases := []struct { + name string + channelID string + expSeq uint64 + expErr error + }{ + {"valid 0", "channel-0", 0, nil}, + {"valid 1", "channel-1", 1, nil}, + {"valid large sequence", "channel-234568219356718293", 234568219356718293, nil}, + // one above uint64 max + {"invalid uint64", "channel-18446744073709551616", 0, errors.New("invalid channel identifier: failed to parse identifier sequence")}, + // uint64 == 20 characters + {"invalid large sequence", "channel-2345682193567182931243", 0, host.ErrInvalidID}, + {"capital prefix", "Channel-0", 0, host.ErrInvalidID}, + {"missing dash", "channel0", 0, host.ErrInvalidID}, + {"blank id", " ", 0, host.ErrInvalidID}, + {"empty id", "", 0, host.ErrInvalidID}, + {"negative sequence", "channel--1", 0, host.ErrInvalidID}, + } + + for _, tc := range testCases { + + seq, err := types.ParseChannelSequence(tc.channelID) + valid := types.IsValidChannelID(tc.channelID) + require.Equal(t, tc.expSeq, seq) + + if tc.expErr == nil { + require.NoError(t, err, tc.name) + require.True(t, valid) + } else { + require.Error(t, err, tc.name) + require.False(t, valid) + require.ErrorContains(t, err, tc.expErr.Error()) + } + } +} diff --git a/modules/core/04-channel/types/msgs.go b/modules/core/04-channel/types/msgs.go new file mode 100644 index 0000000..14db5aa --- /dev/null +++ b/modules/core/04-channel/types/msgs.go @@ -0,0 +1,376 @@ +package types + +import ( + "encoding/base64" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +var ( + _ sdk.Msg = (*MsgChannelOpenInit)(nil) + _ sdk.Msg = (*MsgChannelOpenTry)(nil) + _ sdk.Msg = (*MsgChannelOpenAck)(nil) + _ sdk.Msg = (*MsgChannelOpenConfirm)(nil) + _ sdk.Msg = (*MsgChannelCloseInit)(nil) + _ sdk.Msg = (*MsgChannelCloseConfirm)(nil) + _ sdk.Msg = (*MsgRecvPacket)(nil) + _ sdk.Msg = (*MsgAcknowledgement)(nil) + _ sdk.Msg = (*MsgTimeout)(nil) + _ sdk.Msg = (*MsgTimeoutOnClose)(nil) + + _ sdk.HasValidateBasic = (*MsgChannelOpenInit)(nil) + _ sdk.HasValidateBasic = (*MsgChannelOpenTry)(nil) + _ sdk.HasValidateBasic = (*MsgChannelOpenAck)(nil) + _ sdk.HasValidateBasic = (*MsgChannelOpenConfirm)(nil) + _ sdk.HasValidateBasic = (*MsgChannelCloseInit)(nil) + _ sdk.HasValidateBasic = (*MsgChannelCloseConfirm)(nil) + _ sdk.HasValidateBasic = (*MsgRecvPacket)(nil) + _ sdk.HasValidateBasic = (*MsgAcknowledgement)(nil) + _ sdk.HasValidateBasic = (*MsgTimeout)(nil) + _ sdk.HasValidateBasic = (*MsgTimeoutOnClose)(nil) +) + +// NewMsgChannelOpenInit creates a new MsgChannelOpenInit. It sets the counterparty channel +// identifier to be empty. +func NewMsgChannelOpenInit( + portID, version string, channelOrder Order, connectionHops []string, + counterpartyPortID string, signer string, +) *MsgChannelOpenInit { + counterparty := NewCounterparty(counterpartyPortID, "") + channel := NewChannel(INIT, channelOrder, counterparty, connectionHops, version) + return &MsgChannelOpenInit{ + PortId: portID, + Channel: channel, + Signer: signer, + } +} + +// ValidateBasic implements sdk.Msg +func (msg MsgChannelOpenInit) ValidateBasic() error { + if err := host.PortIdentifierValidator(msg.PortId); err != nil { + return errorsmod.Wrap(err, "invalid port ID") + } + if msg.Channel.State != INIT { + return errorsmod.Wrapf(ErrInvalidChannelState, + "channel state must be INIT in MsgChannelOpenInit. expected: %s, got: %s", + INIT, msg.Channel.State, + ) + } + if msg.Channel.Counterparty.ChannelId != "" { + return errorsmod.Wrap(ErrInvalidCounterparty, "counterparty channel identifier must be empty") + } + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + return msg.Channel.ValidateBasic() +} + +// NewMsgChannelOpenTry creates a new MsgChannelOpenTry instance +// The version string is deprecated and will be ignored by core IBC. +// It is left as an argument for go API backwards compatibility. +func NewMsgChannelOpenTry( + portID, version string, channelOrder Order, connectionHops []string, + counterpartyPortID, counterpartyChannelID, counterpartyVersion string, + initProof []byte, proofHeight clienttypes.Height, signer string, +) *MsgChannelOpenTry { + counterparty := NewCounterparty(counterpartyPortID, counterpartyChannelID) + channel := NewChannel(TRYOPEN, channelOrder, counterparty, connectionHops, version) + return &MsgChannelOpenTry{ + PortId: portID, + Channel: channel, + CounterpartyVersion: counterpartyVersion, + ProofInit: initProof, + ProofHeight: proofHeight, + Signer: signer, + } +} + +// ValidateBasic implements sdk.Msg +func (msg MsgChannelOpenTry) ValidateBasic() error { + if err := host.PortIdentifierValidator(msg.PortId); err != nil { + return errorsmod.Wrap(err, "invalid port ID") + } + if msg.PreviousChannelId != "" { + return errorsmod.Wrap(ErrInvalidChannelIdentifier, "previous channel identifier must be empty, this field has been deprecated as crossing hellos are no longer supported") + } + if len(msg.ProofInit) == 0 { + return errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty init proof") + } + if msg.Channel.State != TRYOPEN { + return errorsmod.Wrapf(ErrInvalidChannelState, + "channel state must be TRYOPEN in MsgChannelOpenTry. expected: %s, got: %s", + TRYOPEN, msg.Channel.State, + ) + } + // counterparty validate basic allows empty counterparty channel identifiers + if err := host.ChannelIdentifierValidator(msg.Channel.Counterparty.ChannelId); err != nil { + return errorsmod.Wrap(err, "invalid counterparty channel ID") + } + + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + return msg.Channel.ValidateBasic() +} + +// NewMsgChannelOpenAck creates a new MsgChannelOpenAck instance +func NewMsgChannelOpenAck( + portID, channelID, counterpartyChannelID string, cpv string, tryProof []byte, proofHeight clienttypes.Height, + signer string, +) *MsgChannelOpenAck { + return &MsgChannelOpenAck{ + PortId: portID, + ChannelId: channelID, + CounterpartyChannelId: counterpartyChannelID, + CounterpartyVersion: cpv, + ProofTry: tryProof, + ProofHeight: proofHeight, + Signer: signer, + } +} + +// ValidateBasic implements sdk.Msg +func (msg MsgChannelOpenAck) ValidateBasic() error { + if err := host.PortIdentifierValidator(msg.PortId); err != nil { + return errorsmod.Wrap(err, "invalid port ID") + } + if !IsValidChannelID(msg.ChannelId) { + return ErrInvalidChannelIdentifier + } + if err := host.ChannelIdentifierValidator(msg.CounterpartyChannelId); err != nil { + return errorsmod.Wrap(err, "invalid counterparty channel ID") + } + if len(msg.ProofTry) == 0 { + return errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty try proof") + } + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + return nil +} + +// NewMsgChannelOpenConfirm creates a new MsgChannelOpenConfirm instance +func NewMsgChannelOpenConfirm( + portID, channelID string, ackProof []byte, proofHeight clienttypes.Height, + signer string, +) *MsgChannelOpenConfirm { + return &MsgChannelOpenConfirm{ + PortId: portID, + ChannelId: channelID, + ProofAck: ackProof, + ProofHeight: proofHeight, + Signer: signer, + } +} + +// ValidateBasic implements sdk.Msg +func (msg MsgChannelOpenConfirm) ValidateBasic() error { + if err := host.PortIdentifierValidator(msg.PortId); err != nil { + return errorsmod.Wrap(err, "invalid port ID") + } + if !IsValidChannelID(msg.ChannelId) { + return ErrInvalidChannelIdentifier + } + if len(msg.ProofAck) == 0 { + return errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty acknowledgement proof") + } + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + return nil +} + +// NewMsgChannelCloseInit creates a new MsgChannelCloseInit instance +func NewMsgChannelCloseInit( + portID string, channelID string, signer string, +) *MsgChannelCloseInit { + return &MsgChannelCloseInit{ + PortId: portID, + ChannelId: channelID, + Signer: signer, + } +} + +// ValidateBasic implements sdk.Msg +func (msg MsgChannelCloseInit) ValidateBasic() error { + if err := host.PortIdentifierValidator(msg.PortId); err != nil { + return errorsmod.Wrap(err, "invalid port ID") + } + if !IsValidChannelID(msg.ChannelId) { + return ErrInvalidChannelIdentifier + } + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + return nil +} + +// NewMsgChannelCloseConfirm creates a new MsgChannelCloseConfirm instance +func NewMsgChannelCloseConfirm( + portID, channelID string, initProof []byte, proofHeight clienttypes.Height, + signer string, +) *MsgChannelCloseConfirm { + return &MsgChannelCloseConfirm{ + PortId: portID, + ChannelId: channelID, + ProofInit: initProof, + ProofHeight: proofHeight, + Signer: signer, + } +} + +// ValidateBasic implements sdk.Msg +func (msg MsgChannelCloseConfirm) ValidateBasic() error { + if err := host.PortIdentifierValidator(msg.PortId); err != nil { + return errorsmod.Wrap(err, "invalid port ID") + } + if !IsValidChannelID(msg.ChannelId) { + return ErrInvalidChannelIdentifier + } + if len(msg.ProofInit) == 0 { + return errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty init proof") + } + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + return nil +} + +// NewMsgRecvPacket constructs new MsgRecvPacket +func NewMsgRecvPacket( + packet Packet, commitmentProof []byte, proofHeight clienttypes.Height, + signer string, +) *MsgRecvPacket { + return &MsgRecvPacket{ + Packet: packet, + ProofCommitment: commitmentProof, + ProofHeight: proofHeight, + Signer: signer, + } +} + +// ValidateBasic implements sdk.Msg +func (msg MsgRecvPacket) ValidateBasic() error { + if len(msg.ProofCommitment) == 0 { + return errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty commitment proof") + } + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + return msg.Packet.ValidateBasic() +} + +// GetDataSignBytes returns the base64-encoded bytes used for the +// data field when signing the packet. +func (msg MsgRecvPacket) GetDataSignBytes() []byte { + s := "\"" + base64.StdEncoding.EncodeToString(msg.Packet.Data) + "\"" + return []byte(s) +} + +// NewMsgTimeout constructs new MsgTimeout +func NewMsgTimeout( + packet Packet, nextSequenceRecv uint64, unreceivedProof []byte, + proofHeight clienttypes.Height, signer string, +) *MsgTimeout { + return &MsgTimeout{ + Packet: packet, + NextSequenceRecv: nextSequenceRecv, + ProofUnreceived: unreceivedProof, + ProofHeight: proofHeight, + Signer: signer, + } +} + +// ValidateBasic implements sdk.Msg +func (msg MsgTimeout) ValidateBasic() error { + if len(msg.ProofUnreceived) == 0 { + return errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty unreceived proof") + } + if msg.NextSequenceRecv == 0 { + return errorsmod.Wrap(ibcerrors.ErrInvalidSequence, "next sequence receive cannot be 0") + } + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + return msg.Packet.ValidateBasic() +} + +// NewMsgTimeoutOnClose constructs new MsgTimeoutOnClose +func NewMsgTimeoutOnClose( + packet Packet, nextSequenceRecv uint64, + unreceivedProof, closeProof []byte, + proofHeight clienttypes.Height, signer string, +) *MsgTimeoutOnClose { + return &MsgTimeoutOnClose{ + Packet: packet, + NextSequenceRecv: nextSequenceRecv, + ProofUnreceived: unreceivedProof, + ProofClose: closeProof, + ProofHeight: proofHeight, + Signer: signer, + } +} + +// ValidateBasic implements sdk.Msg +func (msg MsgTimeoutOnClose) ValidateBasic() error { + if msg.NextSequenceRecv == 0 { + return errorsmod.Wrap(ibcerrors.ErrInvalidSequence, "next sequence receive cannot be 0") + } + if len(msg.ProofUnreceived) == 0 { + return errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty unreceived proof") + } + if len(msg.ProofClose) == 0 { + return errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty proof of closed counterparty channel end") + } + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + return msg.Packet.ValidateBasic() +} + +// NewMsgAcknowledgement constructs a new MsgAcknowledgement +func NewMsgAcknowledgement( + packet Packet, + ack, ackedProof []byte, + proofHeight clienttypes.Height, + signer string, +) *MsgAcknowledgement { + return &MsgAcknowledgement{ + Packet: packet, + Acknowledgement: ack, + ProofAcked: ackedProof, + ProofHeight: proofHeight, + Signer: signer, + } +} + +// ValidateBasic implements sdk.Msg +func (msg MsgAcknowledgement) ValidateBasic() error { + if len(msg.ProofAcked) == 0 { + return errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty acknowledgement proof") + } + if len(msg.Acknowledgement) == 0 { + return errorsmod.Wrap(ErrInvalidAcknowledgement, "ack bytes cannot be empty") + } + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + return msg.Packet.ValidateBasic() +} diff --git a/modules/core/04-channel/types/msgs_test.go b/modules/core/04-channel/types/msgs_test.go new file mode 100644 index 0000000..3751200 --- /dev/null +++ b/modules/core/04-channel/types/msgs_test.go @@ -0,0 +1,1058 @@ +package types_test + +import ( + "errors" + "fmt" + "testing" + + dbm "github.com/cosmos/cosmos-db" + testifysuite "github.com/stretchr/testify/suite" + + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/log" + "cosmossdk.io/store/iavl" + "cosmossdk.io/store/metrics" + "cosmossdk.io/store/rootmulti" + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + + ibc "github.com/cosmos/ibc-go/v10/modules/core" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/testing/simapp" +) + +const ( + // valid constants used for testing + portid = "testportid" + chanid = "channel-0" + cpportid = "testcpport" + cpchanid = "testcpchannel" + + version = "1.0" + + // invalid constants used for testing + invalidPort = "(invalidport1)" + invalidShortPort = "p" + // 195 characters + invalidLongPort = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis eros neque, ultricies vel ligula ac, convallis porttitor elit. Maecenas tincidunt turpis elit, vel faucibus nisl pellentesque sodales" + + invalidChannel = "(invalidchannel1)" + invalidShortChannel = "invalid" + invalidLongChannel = "invalidlongchannelinvalidlongchannelinvalidlongchannelinvalidlongchannel" + + invalidConnection = "(invalidconnection1)" + invalidShortConnection = "invalidcn" + invalidLongConnection = "invalidlongconnectioninvalidlongconnectioninvalidlongconnectioninvalid" +) + +// define variables used for testing +var ( + height = clienttypes.NewHeight(0, 1) + timeoutHeight = clienttypes.NewHeight(0, 100) + timeoutTimestamp = uint64(100) + disabledTimeout = clienttypes.ZeroHeight() + validPacketData = []byte("testdata") + unknownPacketData = []byte("unknown") + + packet = types.NewPacket(validPacketData, 1, portid, chanid, cpportid, cpchanid, timeoutHeight, timeoutTimestamp) + invalidPacket = types.NewPacket(unknownPacketData, 0, portid, chanid, cpportid, cpchanid, timeoutHeight, timeoutTimestamp) + + emptyProof = []byte{} + + addr = sdk.AccAddress("testaddr111111111111").String() + emptyAddr string + + connHops = []string{"testconnection"} + invalidConnHops = []string{"testconnection", "testconnection"} + invalidShortConnHops = []string{invalidShortConnection} + invalidLongConnHops = []string{invalidLongConnection} +) + +type TypesTestSuite struct { + testifysuite.Suite + + proof []byte +} + +func (suite *TypesTestSuite) SetupTest() { + app := simapp.Setup(suite.T(), false) + db := dbm.NewMemDB() + store := rootmulti.NewStore(db, log.NewNopLogger(), metrics.NewNoOpMetrics()) + storeKey := storetypes.NewKVStoreKey("iavlStoreKey") + + store.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, nil) + err := store.LoadVersion(0) + suite.Require().NoError(err) + iavlStore, ok := store.GetCommitStore(storeKey).(*iavl.Store) + suite.Require().True(ok) + + iavlStore.Set([]byte("KEY"), []byte("VALUE")) + _ = store.Commit() + + res, err := store.Query(&storetypes.RequestQuery{ + Data: []byte("KEY"), + Path: fmt.Sprintf("/%s/key", storeKey.Name()), // required path to get key/value+proof + Prove: true, + }) + suite.Require().NoError(err) + + merkleProof, err := commitmenttypes.ConvertProofs(res.ProofOps) + suite.Require().NoError(err) + proof, err := app.AppCodec().Marshal(&merkleProof) + suite.Require().NoError(err) + + suite.proof = proof +} + +func TestTypesTestSuite(t *testing.T) { + testifysuite.Run(t, new(TypesTestSuite)) +} + +func (suite *TypesTestSuite) TestMsgChannelOpenInitValidateBasic() { + counterparty := types.NewCounterparty(cpportid, cpchanid) + tryOpenChannel := types.NewChannel(types.TRYOPEN, types.ORDERED, counterparty, connHops, version) + + testCases := []struct { + name string + msg *types.MsgChannelOpenInit + expErr error + }{ + { + "success", + types.NewMsgChannelOpenInit(portid, version, types.ORDERED, connHops, cpportid, addr), + nil, + }, + { + "success: empty version", + types.NewMsgChannelOpenInit(portid, "", types.UNORDERED, connHops, cpportid, addr), + nil, + }, + { + "too short port id", + types.NewMsgChannelOpenInit(invalidShortPort, version, types.ORDERED, connHops, cpportid, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s has invalid length: %d, must be between %d-%d characters", + invalidShortPort, + len(invalidShortPort), + 2, + host.DefaultMaxPortCharacterLength, + ), "invalid port ID"), + }, + { + "too long port id", + types.NewMsgChannelOpenInit(invalidLongPort, version, types.ORDERED, connHops, cpportid, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s has invalid length: %d, must be between %d-%d characters", + invalidLongPort, + len(invalidLongPort), + 2, + host.DefaultMaxPortCharacterLength, + ), "invalid port ID"), + }, + { + "port id contains non-alpha", + types.NewMsgChannelOpenInit(invalidPort, version, types.ORDERED, connHops, cpportid, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s must contain only alphanumeric or the following characters: '.', '_', '+', '-', '#', '[', ']', '<', '>'", + invalidPort, + ), "invalid port ID"), + }, + { + "invalid channel order", + types.NewMsgChannelOpenInit(portid, version, types.Order(3), + connHops, cpportid, addr), + errorsmod.Wrap(types.ErrInvalidChannelOrdering, types.Order(3).String()), + }, + { + "connection hops more than 1 ", + types.NewMsgChannelOpenInit(portid, version, types.ORDERED, invalidConnHops, cpportid, addr), + errorsmod.Wrap( + types.ErrTooManyConnectionHops, + "current IBC version only supports one connection hop", + ), + }, + { + "too short connection id", + types.NewMsgChannelOpenInit(portid, version, types.UNORDERED, invalidShortConnHops, cpportid, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s has invalid length: %d, must be between %d-%d characters", + invalidShortConnection, len(invalidShortConnection), 10, host.DefaultMaxCharacterLength), + "invalid connection hop ID", + ), + }, + { + "too long connection id", + types.NewMsgChannelOpenInit(portid, version, types.UNORDERED, invalidLongConnHops, cpportid, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s has invalid length: %d, must be between %d-%d characters", + invalidLongConnection, len(invalidLongConnection), 10, host.DefaultMaxCharacterLength), + "invalid connection hop ID", + ), + }, + { + "connection id contains non-alpha", + types.NewMsgChannelOpenInit(portid, version, types.UNORDERED, []string{invalidConnection}, cpportid, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s must contain only alphanumeric or the following characters: '.', '_', '+', '-', '#', '[', ']', '<', '>'", + invalidConnection, + ), "invalid connection hop ID", + ), + }, + { + "invalid counterparty port id", + types.NewMsgChannelOpenInit(portid, version, types.UNORDERED, connHops, invalidPort, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s must contain only alphanumeric or the following characters: '.', '_', '+', '-', '#', '[', ']', '<', '>'", + invalidPort, + ), "invalid counterparty port ID", + ), + }, + { + "channel not in INIT state", + &types.MsgChannelOpenInit{portid, tryOpenChannel, addr}, + errorsmod.Wrapf(types.ErrInvalidChannelState, + "channel state must be INIT in MsgChannelOpenInit. expected: %s, got: %s", + types.INIT, tryOpenChannel.State, + ), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := tc.msg.ValidateBasic() + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().Equal(err.Error(), tc.expErr.Error()) + } + }) + } +} + +func (suite *TypesTestSuite) TestMsgChannelOpenInitGetSigners() { + expSigner, err := sdk.AccAddressFromBech32(addr) + suite.Require().NoError(err) + msg := types.NewMsgChannelOpenInit(portid, version, types.ORDERED, connHops, cpportid, addr) + + encodingCfg := moduletestutil.MakeTestEncodingConfig(ibc.AppModuleBasic{}) + signers, _, err := encodingCfg.Codec.GetMsgV1Signers(msg) + + suite.Require().NoError(err) + suite.Require().Equal(expSigner.Bytes(), signers[0]) +} + +func (suite *TypesTestSuite) TestMsgChannelOpenTryValidateBasic() { + counterparty := types.NewCounterparty(cpportid, cpchanid) + initChannel := types.NewChannel(types.INIT, types.ORDERED, counterparty, connHops, version) + + testCases := []struct { + name string + msg *types.MsgChannelOpenTry + expErr error + }{ + { + "success", + types.NewMsgChannelOpenTry(portid, version, types.ORDERED, connHops, cpportid, cpchanid, version, suite.proof, height, addr), + nil, + }, + { + "success with empty channel version", + types.NewMsgChannelOpenTry(portid, "", types.UNORDERED, connHops, cpportid, cpchanid, version, suite.proof, height, addr), + nil, + }, + { + "success with empty counterparty version", + types.NewMsgChannelOpenTry(portid, version, types.ORDERED, connHops, cpportid, cpchanid, "", suite.proof, height, addr), + nil, + }, + { + "too short port id", + types.NewMsgChannelOpenTry(invalidShortPort, version, types.ORDERED, connHops, cpportid, cpchanid, version, suite.proof, height, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s has invalid length: %d, must be between %d-%d characters", + invalidShortPort, len(invalidShortPort), 2, host.DefaultMaxPortCharacterLength), + "invalid port ID", + ), + }, + { + "too long port id", + types.NewMsgChannelOpenTry(invalidLongPort, version, types.ORDERED, connHops, cpportid, cpchanid, version, suite.proof, height, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s has invalid length: %d, must be between %d-%d characters", + invalidLongPort, len(invalidLongPort), 2, host.DefaultMaxPortCharacterLength), + "invalid port ID", + ), + }, + { + "port id contains non-alpha", + types.NewMsgChannelOpenTry(invalidPort, version, types.ORDERED, connHops, cpportid, cpchanid, version, suite.proof, height, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s must contain only alphanumeric or the following characters: '.', '_', '+', '-', '#', '[', ']', '<', '>'", + invalidPort, + ), "invalid port ID", + ), + }, + { + "invalid channel order", + types.NewMsgChannelOpenTry(portid, version, types.Order(4), connHops, cpportid, cpchanid, version, suite.proof, height, addr), + errorsmod.Wrap(types.ErrInvalidChannelOrdering, types.Order(4).String()), + }, + { + "connection hops more than 1 ", + types.NewMsgChannelOpenTry(portid, version, types.UNORDERED, invalidConnHops, cpportid, cpchanid, version, suite.proof, height, addr), + errorsmod.Wrap( + types.ErrTooManyConnectionHops, + "current IBC version only supports one connection hop", + ), + }, + { + "too short connection id", + types.NewMsgChannelOpenTry(portid, version, types.UNORDERED, invalidShortConnHops, cpportid, cpchanid, version, suite.proof, height, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s has invalid length: %d, must be between %d-%d characters", invalidShortConnection, len(invalidShortConnection), 10, host.DefaultMaxCharacterLength), + "invalid connection hop ID", + ), + }, + { + "too long connection id", + types.NewMsgChannelOpenTry(portid, version, types.UNORDERED, invalidLongConnHops, cpportid, cpchanid, version, suite.proof, height, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s has invalid length: %d, must be between %d-%d characters", invalidLongConnection, len(invalidLongConnection), 10, host.DefaultMaxCharacterLength), + "invalid connection hop ID", + ), + }, + { + "connection id contains non-alpha", + types.NewMsgChannelOpenTry(portid, version, types.UNORDERED, []string{invalidConnection}, cpportid, cpchanid, version, suite.proof, height, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s must contain only alphanumeric or the following characters: '.', '_', '+', '-', '#', '[', ']', '<', '>'", + invalidConnection, + ), "invalid connection hop ID", + ), + }, + { + "invalid counterparty port id", + types.NewMsgChannelOpenTry(portid, version, types.UNORDERED, connHops, invalidPort, cpchanid, version, suite.proof, height, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s must contain only alphanumeric or the following characters: '.', '_', '+', '-', '#', '[', ']', '<', '>'", + invalidPort, + ), "invalid counterparty port ID", + ), + }, + { + "invalid counterparty channel id", + types.NewMsgChannelOpenTry(portid, version, types.UNORDERED, connHops, cpportid, invalidChannel, version, suite.proof, height, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s must contain only alphanumeric or the following characters: '.', '_', '+', '-', '#', '[', ']', '<', '>'", + invalidChannel, + ), "invalid counterparty channel ID", + ), + }, + { + "empty proof", + types.NewMsgChannelOpenTry(portid, version, types.UNORDERED, connHops, cpportid, cpchanid, version, emptyProof, height, addr), + errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty init proof"), + }, + { + "channel not in TRYOPEN state", + &types.MsgChannelOpenTry{portid, "", initChannel, version, suite.proof, height, addr}, + errorsmod.Wrapf(types.ErrInvalidChannelState, + "channel state must be TRYOPEN in MsgChannelOpenTry. expected: %s, got: %s", + types.TRYOPEN, initChannel.State, + ), + }, + { + "previous channel id is not empty", + &types.MsgChannelOpenTry{portid, chanid, initChannel, version, suite.proof, height, addr}, + errorsmod.Wrap(types.ErrInvalidChannelIdentifier, "previous channel identifier must be empty, this field has been deprecated as crossing hellos are no longer supported"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := tc.msg.ValidateBasic() + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().Equal(err.Error(), tc.expErr.Error()) + } + }) + } +} + +func (suite *TypesTestSuite) TestMsgChannelOpenTryGetSigners() { + expSigner, err := sdk.AccAddressFromBech32(addr) + suite.Require().NoError(err) + msg := types.NewMsgChannelOpenTry(portid, version, types.ORDERED, connHops, cpportid, cpchanid, version, suite.proof, height, addr) + + encodingCfg := moduletestutil.MakeTestEncodingConfig(ibc.AppModuleBasic{}) + signers, _, err := encodingCfg.Codec.GetMsgV1Signers(msg) + + suite.Require().NoError(err) + suite.Require().Equal(expSigner.Bytes(), signers[0]) +} + +func (suite *TypesTestSuite) TestMsgChannelOpenAckValidateBasic() { + testCases := []struct { + name string + msg *types.MsgChannelOpenAck + expErr error + }{ + { + "success", + types.NewMsgChannelOpenAck(portid, chanid, chanid, version, suite.proof, height, addr), + nil, + }, + { + "success empty cpv", + types.NewMsgChannelOpenAck(portid, chanid, chanid, "", suite.proof, height, addr), + nil, + }, + { + "too short port id", + types.NewMsgChannelOpenAck(invalidShortPort, chanid, chanid, version, suite.proof, height, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s has invalid length: %d, must be between %d-%d characters", + invalidShortPort, len(invalidShortPort), 2, host.DefaultMaxPortCharacterLength), + "invalid port ID", + ), + }, + { + "too long port id", + types.NewMsgChannelOpenAck(invalidLongPort, chanid, chanid, version, suite.proof, height, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s has invalid length: %d, must be between %d-%d characters", + invalidLongPort, len(invalidLongPort), 2, host.DefaultMaxPortCharacterLength), + "invalid port ID", + ), + }, + { + "port id contains non-alpha", + types.NewMsgChannelOpenAck(invalidPort, chanid, chanid, version, suite.proof, height, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s must contain only alphanumeric or the following characters: '.', '_', '+', '-', '#', '[', ']', '<', '>'", + invalidPort, + ), "invalid port ID", + ), + }, + { + "too short channel id", + types.NewMsgChannelOpenAck(portid, invalidShortChannel, chanid, version, suite.proof, height, addr), + types.ErrInvalidChannelIdentifier, + }, + { + "too long channel id", + types.NewMsgChannelOpenAck(portid, invalidLongChannel, chanid, version, suite.proof, height, addr), + types.ErrInvalidChannelIdentifier, + }, + { + "channel id contains non-alpha", + types.NewMsgChannelOpenAck(portid, invalidChannel, chanid, version, suite.proof, height, addr), + types.ErrInvalidChannelIdentifier, + }, + { + "empty proof", + types.NewMsgChannelOpenAck(portid, chanid, chanid, version, emptyProof, height, addr), + errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty try proof"), + }, + { + "invalid counterparty channel id", + types.NewMsgChannelOpenAck(portid, chanid, invalidShortChannel, version, suite.proof, height, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s has invalid length: %d, must be between %d-%d characters", + invalidShortChannel, len(invalidShortChannel), 8, host.DefaultMaxCharacterLength), + "invalid counterparty channel ID", + ), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := tc.msg.ValidateBasic() + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().Equal(err.Error(), tc.expErr.Error()) + } + }) + } +} + +func (suite *TypesTestSuite) TestMsgChannelOpenAckGetSigners() { + expSigner, err := sdk.AccAddressFromBech32(addr) + suite.Require().NoError(err) + msg := types.NewMsgChannelOpenAck(portid, chanid, chanid, version, suite.proof, height, addr) + + encodingCfg := moduletestutil.MakeTestEncodingConfig(ibc.AppModuleBasic{}) + signers, _, err := encodingCfg.Codec.GetMsgV1Signers(msg) + + suite.Require().NoError(err) + suite.Require().Equal(expSigner.Bytes(), signers[0]) +} + +func (suite *TypesTestSuite) TestMsgChannelOpenConfirmValidateBasic() { + testCases := []struct { + name string + msg *types.MsgChannelOpenConfirm + expErr error + }{ + { + "success", + types.NewMsgChannelOpenConfirm(portid, chanid, suite.proof, height, addr), + nil, + }, + { + "too short port id", + types.NewMsgChannelOpenConfirm(invalidShortPort, chanid, suite.proof, height, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s has invalid length: %d, must be between %d-%d characters", + invalidShortPort, len(invalidShortPort), 2, host.DefaultMaxPortCharacterLength), + "invalid port ID", + ), + }, + { + "too long port id", + types.NewMsgChannelOpenConfirm(invalidLongPort, chanid, suite.proof, height, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s has invalid length: %d, must be between %d-%d characters", + invalidLongPort, len(invalidLongPort), 2, host.DefaultMaxPortCharacterLength), + "invalid port ID", + ), + }, + { + "port id contains non-alpha", + types.NewMsgChannelOpenConfirm(invalidPort, chanid, suite.proof, height, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s must contain only alphanumeric or the following characters: '.', '_', '+', '-', '#', '[', ']', '<', '>'", + invalidPort, + ), "invalid port ID", + ), + }, + { + "too short channel id", + types.NewMsgChannelOpenConfirm(portid, invalidShortChannel, suite.proof, height, addr), + types.ErrInvalidChannelIdentifier, + }, + { + "too long channel id", + types.NewMsgChannelOpenConfirm(portid, invalidLongChannel, suite.proof, height, addr), + types.ErrInvalidChannelIdentifier, + }, + { + "channel id contains non-alpha", + types.NewMsgChannelOpenConfirm(portid, invalidChannel, suite.proof, height, addr), + types.ErrInvalidChannelIdentifier, + }, + { + "empty proof", + types.NewMsgChannelOpenConfirm(portid, chanid, emptyProof, height, addr), + errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty acknowledgement proof"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := tc.msg.ValidateBasic() + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().Equal(err.Error(), tc.expErr.Error()) + } + }) + } +} + +func (suite *TypesTestSuite) TestMsgChannelOpenConfirmGetSigners() { + expSigner, err := sdk.AccAddressFromBech32(addr) + suite.Require().NoError(err) + msg := types.NewMsgChannelOpenConfirm(portid, chanid, suite.proof, height, addr) + + encodingCfg := moduletestutil.MakeTestEncodingConfig(ibc.AppModuleBasic{}) + signers, _, err := encodingCfg.Codec.GetMsgV1Signers(msg) + + suite.Require().NoError(err) + suite.Require().Equal(expSigner.Bytes(), signers[0]) +} + +func (suite *TypesTestSuite) TestMsgChannelCloseInitValidateBasic() { + testCases := []struct { + name string + msg *types.MsgChannelCloseInit + expErr error + }{ + { + "success", + types.NewMsgChannelCloseInit(portid, chanid, addr), + nil, + }, + { + "too short port id", + types.NewMsgChannelCloseInit(invalidShortPort, chanid, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s has invalid length: %d, must be between %d-%d characters", + invalidShortPort, len(invalidShortPort), 2, host.DefaultMaxPortCharacterLength), + "invalid port ID", + ), + }, + { + "too long port id", + types.NewMsgChannelCloseInit(invalidLongPort, chanid, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s has invalid length: %d, must be between %d-%d characters", + invalidLongPort, len(invalidLongPort), 2, host.DefaultMaxPortCharacterLength), + "invalid port ID", + ), + }, + { + "port id contains non-alpha", + types.NewMsgChannelCloseInit(invalidPort, chanid, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s must contain only alphanumeric or the following characters: '.', '_', '+', '-', '#', '[', ']', '<', '>'", + invalidPort, + ), "invalid port ID", + ), + }, + { + "too short channel id", + types.NewMsgChannelCloseInit(portid, invalidShortChannel, addr), + types.ErrInvalidChannelIdentifier, + }, + { + "too long channel id", + types.NewMsgChannelCloseInit(portid, invalidLongChannel, addr), + types.ErrInvalidChannelIdentifier, + }, + { + "channel id contains non-alpha", + types.NewMsgChannelCloseInit(portid, invalidChannel, addr), + types.ErrInvalidChannelIdentifier, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := tc.msg.ValidateBasic() + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().Equal(err.Error(), tc.expErr.Error()) + } + }) + } +} + +func (suite *TypesTestSuite) TestMsgChannelCloseInitGetSigners() { + expSigner, err := sdk.AccAddressFromBech32(addr) + suite.Require().NoError(err) + msg := types.NewMsgChannelCloseInit(portid, chanid, addr) + + encodingCfg := moduletestutil.MakeTestEncodingConfig(ibc.AppModuleBasic{}) + signers, _, err := encodingCfg.Codec.GetMsgV1Signers(msg) + + suite.Require().NoError(err) + suite.Require().Equal(expSigner.Bytes(), signers[0]) +} + +func (suite *TypesTestSuite) TestMsgChannelCloseConfirmValidateBasic() { + testCases := []struct { + name string + msg *types.MsgChannelCloseConfirm + expErr error + }{ + { + "success", + types.NewMsgChannelCloseConfirm(portid, chanid, suite.proof, height, addr), + nil, + }, + { + "success, positive counterparty upgrade sequence", + types.NewMsgChannelCloseConfirm(portid, chanid, suite.proof, height, addr), + nil, + }, + { + "too short port id", + types.NewMsgChannelCloseConfirm(invalidShortPort, chanid, suite.proof, height, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s has invalid length: %d, must be between %d-%d characters", + invalidShortPort, len(invalidShortPort), 2, host.DefaultMaxPortCharacterLength), + "invalid port ID", + ), + }, + { + "too long port id", + types.NewMsgChannelCloseConfirm(invalidLongPort, chanid, suite.proof, height, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s has invalid length: %d, must be between %d-%d characters", + invalidLongPort, len(invalidLongPort), 2, host.DefaultMaxPortCharacterLength), + "invalid port ID", + ), + }, + { + "port id contains non-alpha", + types.NewMsgChannelCloseConfirm(invalidPort, chanid, suite.proof, height, addr), + errorsmod.Wrap( + errorsmod.Wrapf( + host.ErrInvalidID, + "identifier %s must contain only alphanumeric or the following characters: '.', '_', '+', '-', '#', '[', ']', '<', '>'", + invalidPort, + ), "invalid port ID", + ), + }, + { + "too short channel id", + types.NewMsgChannelCloseConfirm(portid, invalidShortChannel, suite.proof, height, addr), + types.ErrInvalidChannelIdentifier, + }, + { + "too long channel id", + types.NewMsgChannelCloseConfirm(portid, invalidLongChannel, suite.proof, height, addr), + types.ErrInvalidChannelIdentifier, + }, + { + "channel id contains non-alpha", + types.NewMsgChannelCloseConfirm(portid, invalidChannel, suite.proof, height, addr), + types.ErrInvalidChannelIdentifier, + }, + { + "empty proof", + types.NewMsgChannelCloseConfirm(portid, chanid, emptyProof, height, addr), + errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty init proof"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := tc.msg.ValidateBasic() + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().Equal(err.Error(), tc.expErr.Error()) + } + }) + } +} + +func (suite *TypesTestSuite) TestMsgChannelCloseConfirmGetSigners() { + expSigner, err := sdk.AccAddressFromBech32(addr) + suite.Require().NoError(err) + msg := types.NewMsgChannelCloseConfirm(portid, chanid, suite.proof, height, addr) + + encodingCfg := moduletestutil.MakeTestEncodingConfig(ibc.AppModuleBasic{}) + signers, _, err := encodingCfg.Codec.GetMsgV1Signers(msg) + + suite.Require().NoError(err) + suite.Require().Equal(expSigner.Bytes(), signers[0]) +} + +func (suite *TypesTestSuite) TestMsgRecvPacketValidateBasic() { + testCases := []struct { + name string + msg *types.MsgRecvPacket + expErr error + }{ + { + "success", + types.NewMsgRecvPacket(packet, suite.proof, height, addr), + nil, + }, + { + "missing signer address", + types.NewMsgRecvPacket(packet, suite.proof, height, emptyAddr), + errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", errors.New("empty address string is not allowed")), + }, + { + "proof contain empty proof", + types.NewMsgRecvPacket(packet, emptyProof, height, addr), + errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty commitment proof"), + }, + { + "invalid packet", + types.NewMsgRecvPacket(invalidPacket, suite.proof, height, addr), + errorsmod.Wrap(types.ErrInvalidPacket, "packet sequence cannot be 0"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := tc.msg.ValidateBasic() + + if tc.expErr == nil { + suite.NoError(err) + } else { + suite.Error(err) + suite.Require().Equal(err.Error(), tc.expErr.Error()) + } + }) + } +} + +func (suite *TypesTestSuite) TestMsgRecvPacketGetSigners() { + expSigner, err := sdk.AccAddressFromBech32(addr) + suite.Require().NoError(err) + msg := types.NewMsgRecvPacket(packet, suite.proof, height, addr) + + encodingCfg := moduletestutil.MakeTestEncodingConfig(ibc.AppModuleBasic{}) + signers, _, err := encodingCfg.Codec.GetMsgV1Signers(msg) + + suite.Require().NoError(err) + suite.Require().Equal(expSigner.Bytes(), signers[0]) +} + +func (suite *TypesTestSuite) TestMsgTimeoutValidateBasic() { + testCases := []struct { + name string + msg *types.MsgTimeout + expErr error + }{ + { + "success", + types.NewMsgTimeout(packet, 1, suite.proof, height, addr), + nil, + }, + { + "seq 0", + types.NewMsgTimeout(packet, 0, suite.proof, height, addr), + errorsmod.Wrap(ibcerrors.ErrInvalidSequence, "next sequence receive cannot be 0"), + }, + { + "missing signer address", + types.NewMsgTimeout(packet, 1, suite.proof, height, emptyAddr), + errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", errors.New("empty address string is not allowed")), + }, + { + "cannot submit an empty proof", + types.NewMsgTimeout(packet, 1, emptyProof, height, addr), + errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty unreceived proof"), + }, + { + "invalid packet", + types.NewMsgTimeout(invalidPacket, 1, suite.proof, height, addr), + errorsmod.Wrap(types.ErrInvalidPacket, "packet sequence cannot be 0"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := tc.msg.ValidateBasic() + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().Equal(err.Error(), tc.expErr.Error()) + } + }) + } +} + +func (suite *TypesTestSuite) TestMsgTimeoutGetSigners() { + expSigner, err := sdk.AccAddressFromBech32(addr) + suite.Require().NoError(err) + msg := types.NewMsgTimeout(packet, 1, suite.proof, height, addr) + + encodingCfg := moduletestutil.MakeTestEncodingConfig(ibc.AppModuleBasic{}) + signers, _, err := encodingCfg.Codec.GetMsgV1Signers(msg) + + suite.Require().NoError(err) + suite.Require().Equal(expSigner.Bytes(), signers[0]) +} + +func (suite *TypesTestSuite) TestMsgTimeoutOnCloseValidateBasic() { + testCases := []struct { + name string + msg *types.MsgTimeoutOnClose + expErr error + }{ + { + "success", + types.NewMsgTimeoutOnClose(packet, 1, suite.proof, suite.proof, height, addr), + nil, + }, + { + "success, positive counterparty upgrade sequence", + types.NewMsgTimeoutOnClose(packet, 1, suite.proof, suite.proof, height, addr), + nil, + }, + { + "seq 0", + types.NewMsgTimeoutOnClose(packet, 0, suite.proof, suite.proof, height, addr), + errorsmod.Wrap(ibcerrors.ErrInvalidSequence, "next sequence receive cannot be 0"), + }, + { + "signer address is empty", + types.NewMsgTimeoutOnClose(packet, 1, suite.proof, suite.proof, height, emptyAddr), + errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", errors.New("empty address string is not allowed")), + }, + { + "empty proof", + types.NewMsgTimeoutOnClose(packet, 1, emptyProof, suite.proof, height, addr), + errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty unreceived proof"), + }, + { + "empty proof close", + types.NewMsgTimeoutOnClose(packet, 1, suite.proof, emptyProof, height, addr), + errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty proof of closed counterparty channel end"), + }, + { + "invalid packet", + types.NewMsgTimeoutOnClose(invalidPacket, 1, suite.proof, suite.proof, height, addr), + errorsmod.Wrap(types.ErrInvalidPacket, "packet sequence cannot be 0"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := tc.msg.ValidateBasic() + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().Equal(err.Error(), tc.expErr.Error()) + } + }) + } +} + +func (suite *TypesTestSuite) TestMsgTimeoutOnCloseGetSigners() { + expSigner, err := sdk.AccAddressFromBech32(addr) + suite.Require().NoError(err) + msg := types.NewMsgTimeoutOnClose(packet, 1, suite.proof, suite.proof, height, addr) + + encodingCfg := moduletestutil.MakeTestEncodingConfig(ibc.AppModuleBasic{}) + signers, _, err := encodingCfg.Codec.GetMsgV1Signers(msg) + + suite.Require().NoError(err) + suite.Require().Equal(expSigner.Bytes(), signers[0]) +} + +func (suite *TypesTestSuite) TestMsgAcknowledgementValidateBasic() { + testCases := []struct { + name string + msg *types.MsgAcknowledgement + expErr error + }{ + { + "success", + types.NewMsgAcknowledgement(packet, packet.GetData(), suite.proof, height, addr), + nil, + }, + { + "empty ack", + types.NewMsgAcknowledgement(packet, nil, suite.proof, height, addr), + errorsmod.Wrap(types.ErrInvalidAcknowledgement, "ack bytes cannot be empty"), + }, + { + "missing signer address", + types.NewMsgAcknowledgement(packet, packet.GetData(), suite.proof, height, emptyAddr), + errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", errors.New("empty address string is not allowed")), + }, + { + "cannot submit an empty proof", + types.NewMsgAcknowledgement(packet, packet.GetData(), emptyProof, height, addr), + errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "cannot submit an empty acknowledgement proof"), + }, + { + "invalid packet", + types.NewMsgAcknowledgement(invalidPacket, packet.GetData(), suite.proof, height, addr), + errorsmod.Wrap(types.ErrInvalidPacket, "packet sequence cannot be 0"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := tc.msg.ValidateBasic() + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().Equal(err.Error(), tc.expErr.Error()) + } + }) + } +} + +func (suite *TypesTestSuite) TestMsgAcknowledgementGetSigners() { + expSigner, err := sdk.AccAddressFromBech32(addr) + suite.Require().NoError(err) + msg := types.NewMsgAcknowledgement(packet, packet.GetData(), suite.proof, height, addr) + + encodingCfg := moduletestutil.MakeTestEncodingConfig(ibc.AppModuleBasic{}) + signers, _, err := encodingCfg.Codec.GetMsgV1Signers(msg) + + suite.Require().NoError(err) + suite.Require().Equal(expSigner.Bytes(), signers[0]) +} diff --git a/modules/core/04-channel/types/packet.go b/modules/core/04-channel/types/packet.go new file mode 100644 index 0000000..bcda5ad --- /dev/null +++ b/modules/core/04-channel/types/packet.go @@ -0,0 +1,142 @@ +package types + +import ( + "crypto/sha256" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +const MaximumPayloadsSize = 262144 // 256 KiB. This is the maximum size of all payloads combined + +// CommitPacket returns the packet commitment bytes. The commitment consists of: +// sha256_hash(timeout_timestamp + timeout_height.RevisionNumber + timeout_height.RevisionHeight + sha256_hash(data)) +// from a given packet. This results in a fixed length preimage. +// NOTE: A fixed length preimage is ESSENTIAL to prevent relayers from being able +// to malleate the packet fields and create a commitment hash that matches the original packet. +// NOTE: sdk.Uint64ToBigEndian sets the uint64 to a slice of length 8. +func CommitPacket(packet Packet) []byte { + timeoutHeight := packet.GetTimeoutHeight() + + buf := sdk.Uint64ToBigEndian(packet.GetTimeoutTimestamp()) + + revisionNumber := sdk.Uint64ToBigEndian(timeoutHeight.GetRevisionNumber()) + buf = append(buf, revisionNumber...) + + revisionHeight := sdk.Uint64ToBigEndian(timeoutHeight.GetRevisionHeight()) + buf = append(buf, revisionHeight...) + + dataHash := sha256.Sum256(packet.GetData()) + buf = append(buf, dataHash[:]...) + + hash := sha256.Sum256(buf) + return hash[:] +} + +// CommitAcknowledgement returns the hash of commitment bytes +func CommitAcknowledgement(data []byte) []byte { + hash := sha256.Sum256(data) + return hash[:] +} + +// NewPacket creates a new Packet instance. It panics if the provided +// packet data interface is not registered. +func NewPacket( + data []byte, + sequence uint64, sourcePort, sourceChannel, + destinationPort, destinationChannel string, + timeoutHeight clienttypes.Height, timeoutTimestamp uint64, +) Packet { + return Packet{ + Data: data, + Sequence: sequence, + SourcePort: sourcePort, + SourceChannel: sourceChannel, + DestinationPort: destinationPort, + DestinationChannel: destinationChannel, + TimeoutHeight: timeoutHeight, + TimeoutTimestamp: timeoutTimestamp, + } +} + +// GetSequence implements PacketI interface +func (p Packet) GetSequence() uint64 { return p.Sequence } + +// GetSourcePort implements PacketI interface +func (p Packet) GetSourcePort() string { return p.SourcePort } + +// GetSourceChannel implements PacketI interface +func (p Packet) GetSourceChannel() string { return p.SourceChannel } + +// GetDestPort implements PacketI interface +func (p Packet) GetDestPort() string { return p.DestinationPort } + +// GetDestChannel implements PacketI interface +func (p Packet) GetDestChannel() string { return p.DestinationChannel } + +// GetData implements PacketI interface +func (p Packet) GetData() []byte { return p.Data } + +// GetTimeoutHeight implements PacketI interface +func (p Packet) GetTimeoutHeight() exported.Height { return p.TimeoutHeight } + +// GetTimeoutTimestamp implements PacketI interface +func (p Packet) GetTimeoutTimestamp() uint64 { return p.TimeoutTimestamp } + +// ValidateBasic implements PacketI interface +func (p Packet) ValidateBasic() error { + if err := host.PortIdentifierValidator(p.SourcePort); err != nil { + return errorsmod.Wrap(err, "invalid source port ID") + } + if err := host.PortIdentifierValidator(p.DestinationPort); err != nil { + return errorsmod.Wrap(err, "invalid destination port ID") + } + if err := host.ChannelIdentifierValidator(p.SourceChannel); err != nil { + return errorsmod.Wrap(err, "invalid source channel ID") + } + if err := host.ChannelIdentifierValidator(p.DestinationChannel); err != nil { + return errorsmod.Wrap(err, "invalid destination channel ID") + } + if p.Sequence == 0 { + return errorsmod.Wrap(ErrInvalidPacket, "packet sequence cannot be 0") + } + if p.TimeoutHeight.IsZero() && p.TimeoutTimestamp == 0 { + return errorsmod.Wrap(ErrInvalidPacket, "packet timeout height and packet timeout timestamp cannot both be 0") + } + if len(p.Data) == 0 { + return errorsmod.Wrap(ErrInvalidPacket, "packet data bytes cannot be empty") + } + + if len(p.Data) > MaximumPayloadsSize { + return errorsmod.Wrapf(ErrInvalidPacket, "packet data bytes cannot exceed %d bytes", MaximumPayloadsSize) + } + + return nil +} + +// Validates a PacketId +func (p PacketId) Validate() error { + if err := host.PortIdentifierValidator(p.PortId); err != nil { + return errorsmod.Wrap(err, "invalid source port ID") + } + + if err := host.ChannelIdentifierValidator(p.ChannelId); err != nil { + return errorsmod.Wrap(err, "invalid source channel ID") + } + + if p.Sequence == 0 { + return errorsmod.Wrap(ErrInvalidPacket, "packet sequence cannot be 0") + } + + return nil +} + +// NewPacketID returns a new instance of PacketId +func NewPacketID(portID, channelID string, seq uint64) PacketId { + return PacketId{PortId: portID, ChannelId: channelID, Sequence: seq} +} diff --git a/modules/core/04-channel/types/packet_test.go b/modules/core/04-channel/types/packet_test.go new file mode 100644 index 0000000..c270abf --- /dev/null +++ b/modules/core/04-channel/types/packet_test.go @@ -0,0 +1,74 @@ +package types_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" +) + +func TestCommitPacket(t *testing.T) { + packet := types.NewPacket(validPacketData, 1, portid, chanid, cpportid, cpchanid, timeoutHeight, timeoutTimestamp) + commitment := types.CommitPacket(packet) + require.NotNil(t, commitment) + + testCases := []struct { + name string + packet types.Packet + }{ + { + name: "diff data", + packet: types.NewPacket(unknownPacketData, 1, portid, chanid, cpportid, cpchanid, timeoutHeight, timeoutTimestamp), + }, + { + name: "diff timeout revision number", + packet: types.NewPacket(validPacketData, 1, portid, chanid, cpportid, cpchanid, clienttypes.NewHeight(timeoutHeight.RevisionNumber+1, timeoutHeight.RevisionHeight), timeoutTimestamp), + }, + { + name: "diff timeout revision height", + packet: types.NewPacket(validPacketData, 1, portid, chanid, cpportid, cpchanid, clienttypes.NewHeight(timeoutHeight.RevisionNumber, timeoutHeight.RevisionHeight+1), timeoutTimestamp), + }, + { + name: "diff timeout timestamp", + packet: types.NewPacket(validPacketData, 1, portid, chanid, cpportid, cpchanid, timeoutHeight, uint64(1)), + }, + } + + for _, tc := range testCases { + testCommitment := types.CommitPacket(tc.packet) + require.NotNil(t, testCommitment) + + require.NotEqual(t, commitment, testCommitment) + } +} + +func TestPacketValidateBasic(t *testing.T) { + testCases := []struct { + packet types.Packet + expError error + }{ + {types.NewPacket(validPacketData, 1, portid, chanid, cpportid, cpchanid, timeoutHeight, timeoutTimestamp), nil}, + {types.NewPacket(validPacketData, 1, portid, chanid, cpportid, cpchanid, disabledTimeout, timeoutTimestamp), nil}, + {types.NewPacket(validPacketData, 1, portid, chanid, cpportid, cpchanid, timeoutHeight, 0), nil}, + {types.NewPacket(unknownPacketData, 1, portid, chanid, cpportid, cpchanid, timeoutHeight, timeoutTimestamp), nil}, + {types.NewPacket(validPacketData, 0, portid, chanid, cpportid, cpchanid, timeoutHeight, timeoutTimestamp), errors.New("packet sequence cannot be 0: invalid packet")}, + {types.NewPacket(validPacketData, 1, invalidPort, chanid, cpportid, cpchanid, timeoutHeight, timeoutTimestamp), errors.New("invalid source port")}, + {types.NewPacket(validPacketData, 1, portid, invalidChannel, cpportid, cpchanid, timeoutHeight, timeoutTimestamp), errors.New("invalid source channel")}, + {types.NewPacket(validPacketData, 1, portid, chanid, invalidPort, cpchanid, timeoutHeight, timeoutTimestamp), errors.New("invalid destination port")}, + {types.NewPacket(validPacketData, 1, portid, chanid, cpportid, invalidChannel, timeoutHeight, timeoutTimestamp), errors.New("invalid destination channel")}, + {types.NewPacket(validPacketData, 1, portid, chanid, cpportid, cpchanid, disabledTimeout, 0), errors.New("packet timeout height and packet timeout timestamp cannot both be 0: invalid packet")}, + {types.NewPacket(make([]byte, types.MaximumPayloadsSize+1), 1, portid, chanid, cpportid, cpchanid, timeoutHeight, timeoutTimestamp), errors.New("packet data bytes cannot exceed 262144 bytes: invalid packet")}, + } + + for _, tc := range testCases { + err := tc.packet.ValidateBasic() + if tc.expError == nil { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, tc.expError.Error()) + } + } +} diff --git a/modules/core/04-channel/types/query.go b/modules/core/04-channel/types/query.go new file mode 100644 index 0000000..984a73f --- /dev/null +++ b/modules/core/04-channel/types/query.go @@ -0,0 +1,106 @@ +package types + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var ( + _ codectypes.UnpackInterfacesMessage = (*QueryChannelClientStateResponse)(nil) + _ codectypes.UnpackInterfacesMessage = (*QueryChannelConsensusStateResponse)(nil) +) + +// NewQueryChannelResponse creates a new QueryChannelResponse instance +func NewQueryChannelResponse(channel Channel, proof []byte, height clienttypes.Height) *QueryChannelResponse { + return &QueryChannelResponse{ + Channel: &channel, + Proof: proof, + ProofHeight: height, + } +} + +// NewQueryChannelClientStateResponse creates a newQueryChannelClientStateResponse instance +func NewQueryChannelClientStateResponse(identifiedClientState clienttypes.IdentifiedClientState, proof []byte, height clienttypes.Height) *QueryChannelClientStateResponse { + return &QueryChannelClientStateResponse{ + IdentifiedClientState: &identifiedClientState, + Proof: proof, + ProofHeight: height, + } +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (qccsr QueryChannelClientStateResponse) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + return qccsr.IdentifiedClientState.UnpackInterfaces(unpacker) +} + +// NewQueryChannelConsensusStateResponse creates a newQueryChannelConsensusStateResponse instance +func NewQueryChannelConsensusStateResponse(clientID string, anyConsensusState *codectypes.Any, consensusStateHeight exported.Height, proof []byte, height clienttypes.Height) *QueryChannelConsensusStateResponse { + return &QueryChannelConsensusStateResponse{ + ConsensusState: anyConsensusState, + ClientId: clientID, + Proof: proof, + ProofHeight: height, + } +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (qccsr QueryChannelConsensusStateResponse) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + return unpacker.UnpackAny(qccsr.ConsensusState, new(exported.ConsensusState)) +} + +// NewQueryPacketCommitmentResponse creates a new QueryPacketCommitmentResponse instance +func NewQueryPacketCommitmentResponse( + commitment []byte, proof []byte, height clienttypes.Height, +) *QueryPacketCommitmentResponse { + return &QueryPacketCommitmentResponse{ + Commitment: commitment, + Proof: proof, + ProofHeight: height, + } +} + +// NewQueryPacketReceiptResponse creates a new QueryPacketReceiptResponse instance +func NewQueryPacketReceiptResponse( + recvd bool, proof []byte, height clienttypes.Height, +) *QueryPacketReceiptResponse { + return &QueryPacketReceiptResponse{ + Received: recvd, + Proof: proof, + ProofHeight: height, + } +} + +// NewQueryPacketAcknowledgementResponse creates a new QueryPacketAcknowledgementResponse instance +func NewQueryPacketAcknowledgementResponse( + acknowledgement []byte, proof []byte, height clienttypes.Height, +) *QueryPacketAcknowledgementResponse { + return &QueryPacketAcknowledgementResponse{ + Acknowledgement: acknowledgement, + Proof: proof, + ProofHeight: height, + } +} + +// NewQueryNextSequenceReceiveResponse creates a new QueryNextSequenceReceiveResponse instance +func NewQueryNextSequenceReceiveResponse( + sequence uint64, proof []byte, height clienttypes.Height, +) *QueryNextSequenceReceiveResponse { + return &QueryNextSequenceReceiveResponse{ + NextSequenceReceive: sequence, + Proof: proof, + ProofHeight: height, + } +} + +// NewQueryNextSequenceSendResponse creates a new QueryNextSequenceSendResponse instance +func NewQueryNextSequenceSendResponse( + sequence uint64, proof []byte, height clienttypes.Height, +) *QueryNextSequenceSendResponse { + return &QueryNextSequenceSendResponse{ + NextSequenceSend: sequence, + Proof: proof, + ProofHeight: height, + } +} diff --git a/modules/core/04-channel/types/query.pb.go b/modules/core/04-channel/types/query.pb.go new file mode 100644 index 0000000..8893c9e --- /dev/null +++ b/modules/core/04-channel/types/query.pb.go @@ -0,0 +1,8635 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/channel/v1/query.proto + +package types + +import ( + context "context" + fmt "fmt" + types1 "github.com/cosmos/cosmos-sdk/codec/types" + query "github.com/cosmos/cosmos-sdk/types/query" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + types "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// QueryChannelRequest is the request type for the Query/Channel RPC method +type QueryChannelRequest struct { + // port unique identifier + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + // channel unique identifier + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` +} + +func (m *QueryChannelRequest) Reset() { *m = QueryChannelRequest{} } +func (m *QueryChannelRequest) String() string { return proto.CompactTextString(m) } +func (*QueryChannelRequest) ProtoMessage() {} +func (*QueryChannelRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{0} +} +func (m *QueryChannelRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryChannelRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryChannelRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryChannelRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryChannelRequest.Merge(m, src) +} +func (m *QueryChannelRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryChannelRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryChannelRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryChannelRequest proto.InternalMessageInfo + +func (m *QueryChannelRequest) GetPortId() string { + if m != nil { + return m.PortId + } + return "" +} + +func (m *QueryChannelRequest) GetChannelId() string { + if m != nil { + return m.ChannelId + } + return "" +} + +// QueryChannelResponse is the response type for the Query/Channel RPC method. +// Besides the Channel end, it includes a proof and the height from which the +// proof was retrieved. +type QueryChannelResponse struct { + // channel associated with the request identifiers + Channel *Channel `protobuf:"bytes,1,opt,name=channel,proto3" json:"channel,omitempty"` + // merkle proof of existence + Proof []byte `protobuf:"bytes,2,opt,name=proof,proto3" json:"proof,omitempty"` + // height at which the proof was retrieved + ProofHeight types.Height `protobuf:"bytes,3,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` +} + +func (m *QueryChannelResponse) Reset() { *m = QueryChannelResponse{} } +func (m *QueryChannelResponse) String() string { return proto.CompactTextString(m) } +func (*QueryChannelResponse) ProtoMessage() {} +func (*QueryChannelResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{1} +} +func (m *QueryChannelResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryChannelResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryChannelResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryChannelResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryChannelResponse.Merge(m, src) +} +func (m *QueryChannelResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryChannelResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryChannelResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryChannelResponse proto.InternalMessageInfo + +func (m *QueryChannelResponse) GetChannel() *Channel { + if m != nil { + return m.Channel + } + return nil +} + +func (m *QueryChannelResponse) GetProof() []byte { + if m != nil { + return m.Proof + } + return nil +} + +func (m *QueryChannelResponse) GetProofHeight() types.Height { + if m != nil { + return m.ProofHeight + } + return types.Height{} +} + +// QueryChannelsRequest is the request type for the Query/Channels RPC method +type QueryChannelsRequest struct { + // pagination request + Pagination *query.PageRequest `protobuf:"bytes,1,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryChannelsRequest) Reset() { *m = QueryChannelsRequest{} } +func (m *QueryChannelsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryChannelsRequest) ProtoMessage() {} +func (*QueryChannelsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{2} +} +func (m *QueryChannelsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryChannelsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryChannelsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryChannelsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryChannelsRequest.Merge(m, src) +} +func (m *QueryChannelsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryChannelsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryChannelsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryChannelsRequest proto.InternalMessageInfo + +func (m *QueryChannelsRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryChannelsResponse is the response type for the Query/Channels RPC method. +type QueryChannelsResponse struct { + // list of stored channels of the chain. + Channels []*IdentifiedChannel `protobuf:"bytes,1,rep,name=channels,proto3" json:"channels,omitempty"` + // pagination response + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` + // query block height + Height types.Height `protobuf:"bytes,3,opt,name=height,proto3" json:"height"` +} + +func (m *QueryChannelsResponse) Reset() { *m = QueryChannelsResponse{} } +func (m *QueryChannelsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryChannelsResponse) ProtoMessage() {} +func (*QueryChannelsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{3} +} +func (m *QueryChannelsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryChannelsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryChannelsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryChannelsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryChannelsResponse.Merge(m, src) +} +func (m *QueryChannelsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryChannelsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryChannelsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryChannelsResponse proto.InternalMessageInfo + +func (m *QueryChannelsResponse) GetChannels() []*IdentifiedChannel { + if m != nil { + return m.Channels + } + return nil +} + +func (m *QueryChannelsResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + +func (m *QueryChannelsResponse) GetHeight() types.Height { + if m != nil { + return m.Height + } + return types.Height{} +} + +// QueryConnectionChannelsRequest is the request type for the +// Query/QueryConnectionChannels RPC method +type QueryConnectionChannelsRequest struct { + // connection unique identifier + Connection string `protobuf:"bytes,1,opt,name=connection,proto3" json:"connection,omitempty"` + // pagination request + Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryConnectionChannelsRequest) Reset() { *m = QueryConnectionChannelsRequest{} } +func (m *QueryConnectionChannelsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryConnectionChannelsRequest) ProtoMessage() {} +func (*QueryConnectionChannelsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{4} +} +func (m *QueryConnectionChannelsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryConnectionChannelsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryConnectionChannelsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryConnectionChannelsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryConnectionChannelsRequest.Merge(m, src) +} +func (m *QueryConnectionChannelsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryConnectionChannelsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryConnectionChannelsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryConnectionChannelsRequest proto.InternalMessageInfo + +func (m *QueryConnectionChannelsRequest) GetConnection() string { + if m != nil { + return m.Connection + } + return "" +} + +func (m *QueryConnectionChannelsRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryConnectionChannelsResponse is the Response type for the +// Query/QueryConnectionChannels RPC method +type QueryConnectionChannelsResponse struct { + // list of channels associated with a connection. + Channels []*IdentifiedChannel `protobuf:"bytes,1,rep,name=channels,proto3" json:"channels,omitempty"` + // pagination response + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` + // query block height + Height types.Height `protobuf:"bytes,3,opt,name=height,proto3" json:"height"` +} + +func (m *QueryConnectionChannelsResponse) Reset() { *m = QueryConnectionChannelsResponse{} } +func (m *QueryConnectionChannelsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryConnectionChannelsResponse) ProtoMessage() {} +func (*QueryConnectionChannelsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{5} +} +func (m *QueryConnectionChannelsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryConnectionChannelsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryConnectionChannelsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryConnectionChannelsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryConnectionChannelsResponse.Merge(m, src) +} +func (m *QueryConnectionChannelsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryConnectionChannelsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryConnectionChannelsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryConnectionChannelsResponse proto.InternalMessageInfo + +func (m *QueryConnectionChannelsResponse) GetChannels() []*IdentifiedChannel { + if m != nil { + return m.Channels + } + return nil +} + +func (m *QueryConnectionChannelsResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + +func (m *QueryConnectionChannelsResponse) GetHeight() types.Height { + if m != nil { + return m.Height + } + return types.Height{} +} + +// QueryChannelClientStateRequest is the request type for the Query/ClientState +// RPC method +type QueryChannelClientStateRequest struct { + // port unique identifier + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + // channel unique identifier + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` +} + +func (m *QueryChannelClientStateRequest) Reset() { *m = QueryChannelClientStateRequest{} } +func (m *QueryChannelClientStateRequest) String() string { return proto.CompactTextString(m) } +func (*QueryChannelClientStateRequest) ProtoMessage() {} +func (*QueryChannelClientStateRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{6} +} +func (m *QueryChannelClientStateRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryChannelClientStateRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryChannelClientStateRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryChannelClientStateRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryChannelClientStateRequest.Merge(m, src) +} +func (m *QueryChannelClientStateRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryChannelClientStateRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryChannelClientStateRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryChannelClientStateRequest proto.InternalMessageInfo + +func (m *QueryChannelClientStateRequest) GetPortId() string { + if m != nil { + return m.PortId + } + return "" +} + +func (m *QueryChannelClientStateRequest) GetChannelId() string { + if m != nil { + return m.ChannelId + } + return "" +} + +// QueryChannelClientStateResponse is the Response type for the +// Query/QueryChannelClientState RPC method +type QueryChannelClientStateResponse struct { + // client state associated with the channel + IdentifiedClientState *types.IdentifiedClientState `protobuf:"bytes,1,opt,name=identified_client_state,json=identifiedClientState,proto3" json:"identified_client_state,omitempty"` + // merkle proof of existence + Proof []byte `protobuf:"bytes,2,opt,name=proof,proto3" json:"proof,omitempty"` + // height at which the proof was retrieved + ProofHeight types.Height `protobuf:"bytes,3,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` +} + +func (m *QueryChannelClientStateResponse) Reset() { *m = QueryChannelClientStateResponse{} } +func (m *QueryChannelClientStateResponse) String() string { return proto.CompactTextString(m) } +func (*QueryChannelClientStateResponse) ProtoMessage() {} +func (*QueryChannelClientStateResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{7} +} +func (m *QueryChannelClientStateResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryChannelClientStateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryChannelClientStateResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryChannelClientStateResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryChannelClientStateResponse.Merge(m, src) +} +func (m *QueryChannelClientStateResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryChannelClientStateResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryChannelClientStateResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryChannelClientStateResponse proto.InternalMessageInfo + +func (m *QueryChannelClientStateResponse) GetIdentifiedClientState() *types.IdentifiedClientState { + if m != nil { + return m.IdentifiedClientState + } + return nil +} + +func (m *QueryChannelClientStateResponse) GetProof() []byte { + if m != nil { + return m.Proof + } + return nil +} + +func (m *QueryChannelClientStateResponse) GetProofHeight() types.Height { + if m != nil { + return m.ProofHeight + } + return types.Height{} +} + +// QueryChannelConsensusStateRequest is the request type for the +// Query/ConsensusState RPC method +type QueryChannelConsensusStateRequest struct { + // port unique identifier + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + // channel unique identifier + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + // revision number of the consensus state + RevisionNumber uint64 `protobuf:"varint,3,opt,name=revision_number,json=revisionNumber,proto3" json:"revision_number,omitempty"` + // revision height of the consensus state + RevisionHeight uint64 `protobuf:"varint,4,opt,name=revision_height,json=revisionHeight,proto3" json:"revision_height,omitempty"` +} + +func (m *QueryChannelConsensusStateRequest) Reset() { *m = QueryChannelConsensusStateRequest{} } +func (m *QueryChannelConsensusStateRequest) String() string { return proto.CompactTextString(m) } +func (*QueryChannelConsensusStateRequest) ProtoMessage() {} +func (*QueryChannelConsensusStateRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{8} +} +func (m *QueryChannelConsensusStateRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryChannelConsensusStateRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryChannelConsensusStateRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryChannelConsensusStateRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryChannelConsensusStateRequest.Merge(m, src) +} +func (m *QueryChannelConsensusStateRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryChannelConsensusStateRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryChannelConsensusStateRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryChannelConsensusStateRequest proto.InternalMessageInfo + +func (m *QueryChannelConsensusStateRequest) GetPortId() string { + if m != nil { + return m.PortId + } + return "" +} + +func (m *QueryChannelConsensusStateRequest) GetChannelId() string { + if m != nil { + return m.ChannelId + } + return "" +} + +func (m *QueryChannelConsensusStateRequest) GetRevisionNumber() uint64 { + if m != nil { + return m.RevisionNumber + } + return 0 +} + +func (m *QueryChannelConsensusStateRequest) GetRevisionHeight() uint64 { + if m != nil { + return m.RevisionHeight + } + return 0 +} + +// QueryChannelClientStateResponse is the Response type for the +// Query/QueryChannelClientState RPC method +type QueryChannelConsensusStateResponse struct { + // consensus state associated with the channel + ConsensusState *types1.Any `protobuf:"bytes,1,opt,name=consensus_state,json=consensusState,proto3" json:"consensus_state,omitempty"` + // client ID associated with the consensus state + ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // merkle proof of existence + Proof []byte `protobuf:"bytes,3,opt,name=proof,proto3" json:"proof,omitempty"` + // height at which the proof was retrieved + ProofHeight types.Height `protobuf:"bytes,4,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` +} + +func (m *QueryChannelConsensusStateResponse) Reset() { *m = QueryChannelConsensusStateResponse{} } +func (m *QueryChannelConsensusStateResponse) String() string { return proto.CompactTextString(m) } +func (*QueryChannelConsensusStateResponse) ProtoMessage() {} +func (*QueryChannelConsensusStateResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{9} +} +func (m *QueryChannelConsensusStateResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryChannelConsensusStateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryChannelConsensusStateResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryChannelConsensusStateResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryChannelConsensusStateResponse.Merge(m, src) +} +func (m *QueryChannelConsensusStateResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryChannelConsensusStateResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryChannelConsensusStateResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryChannelConsensusStateResponse proto.InternalMessageInfo + +func (m *QueryChannelConsensusStateResponse) GetConsensusState() *types1.Any { + if m != nil { + return m.ConsensusState + } + return nil +} + +func (m *QueryChannelConsensusStateResponse) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +func (m *QueryChannelConsensusStateResponse) GetProof() []byte { + if m != nil { + return m.Proof + } + return nil +} + +func (m *QueryChannelConsensusStateResponse) GetProofHeight() types.Height { + if m != nil { + return m.ProofHeight + } + return types.Height{} +} + +// QueryPacketCommitmentRequest is the request type for the +// Query/PacketCommitment RPC method +type QueryPacketCommitmentRequest struct { + // port unique identifier + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + // channel unique identifier + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + // packet sequence + Sequence uint64 `protobuf:"varint,3,opt,name=sequence,proto3" json:"sequence,omitempty"` +} + +func (m *QueryPacketCommitmentRequest) Reset() { *m = QueryPacketCommitmentRequest{} } +func (m *QueryPacketCommitmentRequest) String() string { return proto.CompactTextString(m) } +func (*QueryPacketCommitmentRequest) ProtoMessage() {} +func (*QueryPacketCommitmentRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{10} +} +func (m *QueryPacketCommitmentRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPacketCommitmentRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPacketCommitmentRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPacketCommitmentRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPacketCommitmentRequest.Merge(m, src) +} +func (m *QueryPacketCommitmentRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryPacketCommitmentRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPacketCommitmentRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPacketCommitmentRequest proto.InternalMessageInfo + +func (m *QueryPacketCommitmentRequest) GetPortId() string { + if m != nil { + return m.PortId + } + return "" +} + +func (m *QueryPacketCommitmentRequest) GetChannelId() string { + if m != nil { + return m.ChannelId + } + return "" +} + +func (m *QueryPacketCommitmentRequest) GetSequence() uint64 { + if m != nil { + return m.Sequence + } + return 0 +} + +// QueryPacketCommitmentResponse defines the client query response for a packet +// which also includes a proof and the height from which the proof was +// retrieved +type QueryPacketCommitmentResponse struct { + // packet associated with the request fields + Commitment []byte `protobuf:"bytes,1,opt,name=commitment,proto3" json:"commitment,omitempty"` + // merkle proof of existence + Proof []byte `protobuf:"bytes,2,opt,name=proof,proto3" json:"proof,omitempty"` + // height at which the proof was retrieved + ProofHeight types.Height `protobuf:"bytes,3,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` +} + +func (m *QueryPacketCommitmentResponse) Reset() { *m = QueryPacketCommitmentResponse{} } +func (m *QueryPacketCommitmentResponse) String() string { return proto.CompactTextString(m) } +func (*QueryPacketCommitmentResponse) ProtoMessage() {} +func (*QueryPacketCommitmentResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{11} +} +func (m *QueryPacketCommitmentResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPacketCommitmentResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPacketCommitmentResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPacketCommitmentResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPacketCommitmentResponse.Merge(m, src) +} +func (m *QueryPacketCommitmentResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryPacketCommitmentResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPacketCommitmentResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPacketCommitmentResponse proto.InternalMessageInfo + +func (m *QueryPacketCommitmentResponse) GetCommitment() []byte { + if m != nil { + return m.Commitment + } + return nil +} + +func (m *QueryPacketCommitmentResponse) GetProof() []byte { + if m != nil { + return m.Proof + } + return nil +} + +func (m *QueryPacketCommitmentResponse) GetProofHeight() types.Height { + if m != nil { + return m.ProofHeight + } + return types.Height{} +} + +// QueryPacketCommitmentsRequest is the request type for the +// Query/QueryPacketCommitments RPC method +type QueryPacketCommitmentsRequest struct { + // port unique identifier + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + // channel unique identifier + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + // pagination request + Pagination *query.PageRequest `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryPacketCommitmentsRequest) Reset() { *m = QueryPacketCommitmentsRequest{} } +func (m *QueryPacketCommitmentsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryPacketCommitmentsRequest) ProtoMessage() {} +func (*QueryPacketCommitmentsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{12} +} +func (m *QueryPacketCommitmentsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPacketCommitmentsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPacketCommitmentsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPacketCommitmentsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPacketCommitmentsRequest.Merge(m, src) +} +func (m *QueryPacketCommitmentsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryPacketCommitmentsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPacketCommitmentsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPacketCommitmentsRequest proto.InternalMessageInfo + +func (m *QueryPacketCommitmentsRequest) GetPortId() string { + if m != nil { + return m.PortId + } + return "" +} + +func (m *QueryPacketCommitmentsRequest) GetChannelId() string { + if m != nil { + return m.ChannelId + } + return "" +} + +func (m *QueryPacketCommitmentsRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryPacketCommitmentsResponse is the request type for the +// Query/QueryPacketCommitments RPC method +type QueryPacketCommitmentsResponse struct { + Commitments []*PacketState `protobuf:"bytes,1,rep,name=commitments,proto3" json:"commitments,omitempty"` + // pagination response + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` + // query block height + Height types.Height `protobuf:"bytes,3,opt,name=height,proto3" json:"height"` +} + +func (m *QueryPacketCommitmentsResponse) Reset() { *m = QueryPacketCommitmentsResponse{} } +func (m *QueryPacketCommitmentsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryPacketCommitmentsResponse) ProtoMessage() {} +func (*QueryPacketCommitmentsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{13} +} +func (m *QueryPacketCommitmentsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPacketCommitmentsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPacketCommitmentsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPacketCommitmentsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPacketCommitmentsResponse.Merge(m, src) +} +func (m *QueryPacketCommitmentsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryPacketCommitmentsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPacketCommitmentsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPacketCommitmentsResponse proto.InternalMessageInfo + +func (m *QueryPacketCommitmentsResponse) GetCommitments() []*PacketState { + if m != nil { + return m.Commitments + } + return nil +} + +func (m *QueryPacketCommitmentsResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + +func (m *QueryPacketCommitmentsResponse) GetHeight() types.Height { + if m != nil { + return m.Height + } + return types.Height{} +} + +// QueryPacketReceiptRequest is the request type for the +// Query/PacketReceipt RPC method +type QueryPacketReceiptRequest struct { + // port unique identifier + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + // channel unique identifier + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + // packet sequence + Sequence uint64 `protobuf:"varint,3,opt,name=sequence,proto3" json:"sequence,omitempty"` +} + +func (m *QueryPacketReceiptRequest) Reset() { *m = QueryPacketReceiptRequest{} } +func (m *QueryPacketReceiptRequest) String() string { return proto.CompactTextString(m) } +func (*QueryPacketReceiptRequest) ProtoMessage() {} +func (*QueryPacketReceiptRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{14} +} +func (m *QueryPacketReceiptRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPacketReceiptRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPacketReceiptRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPacketReceiptRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPacketReceiptRequest.Merge(m, src) +} +func (m *QueryPacketReceiptRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryPacketReceiptRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPacketReceiptRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPacketReceiptRequest proto.InternalMessageInfo + +func (m *QueryPacketReceiptRequest) GetPortId() string { + if m != nil { + return m.PortId + } + return "" +} + +func (m *QueryPacketReceiptRequest) GetChannelId() string { + if m != nil { + return m.ChannelId + } + return "" +} + +func (m *QueryPacketReceiptRequest) GetSequence() uint64 { + if m != nil { + return m.Sequence + } + return 0 +} + +// QueryPacketReceiptResponse defines the client query response for a packet +// receipt which also includes a proof, and the height from which the proof was +// retrieved +type QueryPacketReceiptResponse struct { + // success flag for if receipt exists + Received bool `protobuf:"varint,2,opt,name=received,proto3" json:"received,omitempty"` + // merkle proof of existence + Proof []byte `protobuf:"bytes,3,opt,name=proof,proto3" json:"proof,omitempty"` + // height at which the proof was retrieved + ProofHeight types.Height `protobuf:"bytes,4,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` +} + +func (m *QueryPacketReceiptResponse) Reset() { *m = QueryPacketReceiptResponse{} } +func (m *QueryPacketReceiptResponse) String() string { return proto.CompactTextString(m) } +func (*QueryPacketReceiptResponse) ProtoMessage() {} +func (*QueryPacketReceiptResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{15} +} +func (m *QueryPacketReceiptResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPacketReceiptResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPacketReceiptResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPacketReceiptResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPacketReceiptResponse.Merge(m, src) +} +func (m *QueryPacketReceiptResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryPacketReceiptResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPacketReceiptResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPacketReceiptResponse proto.InternalMessageInfo + +func (m *QueryPacketReceiptResponse) GetReceived() bool { + if m != nil { + return m.Received + } + return false +} + +func (m *QueryPacketReceiptResponse) GetProof() []byte { + if m != nil { + return m.Proof + } + return nil +} + +func (m *QueryPacketReceiptResponse) GetProofHeight() types.Height { + if m != nil { + return m.ProofHeight + } + return types.Height{} +} + +// QueryPacketAcknowledgementRequest is the request type for the +// Query/PacketAcknowledgement RPC method +type QueryPacketAcknowledgementRequest struct { + // port unique identifier + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + // channel unique identifier + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + // packet sequence + Sequence uint64 `protobuf:"varint,3,opt,name=sequence,proto3" json:"sequence,omitempty"` +} + +func (m *QueryPacketAcknowledgementRequest) Reset() { *m = QueryPacketAcknowledgementRequest{} } +func (m *QueryPacketAcknowledgementRequest) String() string { return proto.CompactTextString(m) } +func (*QueryPacketAcknowledgementRequest) ProtoMessage() {} +func (*QueryPacketAcknowledgementRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{16} +} +func (m *QueryPacketAcknowledgementRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPacketAcknowledgementRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPacketAcknowledgementRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPacketAcknowledgementRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPacketAcknowledgementRequest.Merge(m, src) +} +func (m *QueryPacketAcknowledgementRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryPacketAcknowledgementRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPacketAcknowledgementRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPacketAcknowledgementRequest proto.InternalMessageInfo + +func (m *QueryPacketAcknowledgementRequest) GetPortId() string { + if m != nil { + return m.PortId + } + return "" +} + +func (m *QueryPacketAcknowledgementRequest) GetChannelId() string { + if m != nil { + return m.ChannelId + } + return "" +} + +func (m *QueryPacketAcknowledgementRequest) GetSequence() uint64 { + if m != nil { + return m.Sequence + } + return 0 +} + +// QueryPacketAcknowledgementResponse defines the client query response for a +// packet which also includes a proof and the height from which the +// proof was retrieved +type QueryPacketAcknowledgementResponse struct { + // packet associated with the request fields + Acknowledgement []byte `protobuf:"bytes,1,opt,name=acknowledgement,proto3" json:"acknowledgement,omitempty"` + // merkle proof of existence + Proof []byte `protobuf:"bytes,2,opt,name=proof,proto3" json:"proof,omitempty"` + // height at which the proof was retrieved + ProofHeight types.Height `protobuf:"bytes,3,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` +} + +func (m *QueryPacketAcknowledgementResponse) Reset() { *m = QueryPacketAcknowledgementResponse{} } +func (m *QueryPacketAcknowledgementResponse) String() string { return proto.CompactTextString(m) } +func (*QueryPacketAcknowledgementResponse) ProtoMessage() {} +func (*QueryPacketAcknowledgementResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{17} +} +func (m *QueryPacketAcknowledgementResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPacketAcknowledgementResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPacketAcknowledgementResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPacketAcknowledgementResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPacketAcknowledgementResponse.Merge(m, src) +} +func (m *QueryPacketAcknowledgementResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryPacketAcknowledgementResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPacketAcknowledgementResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPacketAcknowledgementResponse proto.InternalMessageInfo + +func (m *QueryPacketAcknowledgementResponse) GetAcknowledgement() []byte { + if m != nil { + return m.Acknowledgement + } + return nil +} + +func (m *QueryPacketAcknowledgementResponse) GetProof() []byte { + if m != nil { + return m.Proof + } + return nil +} + +func (m *QueryPacketAcknowledgementResponse) GetProofHeight() types.Height { + if m != nil { + return m.ProofHeight + } + return types.Height{} +} + +// QueryPacketAcknowledgementsRequest is the request type for the +// Query/QueryPacketCommitments RPC method +type QueryPacketAcknowledgementsRequest struct { + // port unique identifier + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + // channel unique identifier + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + // pagination request + Pagination *query.PageRequest `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` + // list of packet sequences + PacketCommitmentSequences []uint64 `protobuf:"varint,4,rep,packed,name=packet_commitment_sequences,json=packetCommitmentSequences,proto3" json:"packet_commitment_sequences,omitempty"` +} + +func (m *QueryPacketAcknowledgementsRequest) Reset() { *m = QueryPacketAcknowledgementsRequest{} } +func (m *QueryPacketAcknowledgementsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryPacketAcknowledgementsRequest) ProtoMessage() {} +func (*QueryPacketAcknowledgementsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{18} +} +func (m *QueryPacketAcknowledgementsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPacketAcknowledgementsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPacketAcknowledgementsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPacketAcknowledgementsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPacketAcknowledgementsRequest.Merge(m, src) +} +func (m *QueryPacketAcknowledgementsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryPacketAcknowledgementsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPacketAcknowledgementsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPacketAcknowledgementsRequest proto.InternalMessageInfo + +func (m *QueryPacketAcknowledgementsRequest) GetPortId() string { + if m != nil { + return m.PortId + } + return "" +} + +func (m *QueryPacketAcknowledgementsRequest) GetChannelId() string { + if m != nil { + return m.ChannelId + } + return "" +} + +func (m *QueryPacketAcknowledgementsRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +func (m *QueryPacketAcknowledgementsRequest) GetPacketCommitmentSequences() []uint64 { + if m != nil { + return m.PacketCommitmentSequences + } + return nil +} + +// QueryPacketAcknowledgemetsResponse is the request type for the +// Query/QueryPacketAcknowledgements RPC method +type QueryPacketAcknowledgementsResponse struct { + Acknowledgements []*PacketState `protobuf:"bytes,1,rep,name=acknowledgements,proto3" json:"acknowledgements,omitempty"` + // pagination response + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` + // query block height + Height types.Height `protobuf:"bytes,3,opt,name=height,proto3" json:"height"` +} + +func (m *QueryPacketAcknowledgementsResponse) Reset() { *m = QueryPacketAcknowledgementsResponse{} } +func (m *QueryPacketAcknowledgementsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryPacketAcknowledgementsResponse) ProtoMessage() {} +func (*QueryPacketAcknowledgementsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{19} +} +func (m *QueryPacketAcknowledgementsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPacketAcknowledgementsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPacketAcknowledgementsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPacketAcknowledgementsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPacketAcknowledgementsResponse.Merge(m, src) +} +func (m *QueryPacketAcknowledgementsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryPacketAcknowledgementsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPacketAcknowledgementsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPacketAcknowledgementsResponse proto.InternalMessageInfo + +func (m *QueryPacketAcknowledgementsResponse) GetAcknowledgements() []*PacketState { + if m != nil { + return m.Acknowledgements + } + return nil +} + +func (m *QueryPacketAcknowledgementsResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + +func (m *QueryPacketAcknowledgementsResponse) GetHeight() types.Height { + if m != nil { + return m.Height + } + return types.Height{} +} + +// QueryUnreceivedPacketsRequest is the request type for the +// Query/UnreceivedPackets RPC method +type QueryUnreceivedPacketsRequest struct { + // port unique identifier + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + // channel unique identifier + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + // list of packet sequences + PacketCommitmentSequences []uint64 `protobuf:"varint,3,rep,packed,name=packet_commitment_sequences,json=packetCommitmentSequences,proto3" json:"packet_commitment_sequences,omitempty"` +} + +func (m *QueryUnreceivedPacketsRequest) Reset() { *m = QueryUnreceivedPacketsRequest{} } +func (m *QueryUnreceivedPacketsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryUnreceivedPacketsRequest) ProtoMessage() {} +func (*QueryUnreceivedPacketsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{20} +} +func (m *QueryUnreceivedPacketsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryUnreceivedPacketsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryUnreceivedPacketsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryUnreceivedPacketsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryUnreceivedPacketsRequest.Merge(m, src) +} +func (m *QueryUnreceivedPacketsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryUnreceivedPacketsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryUnreceivedPacketsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryUnreceivedPacketsRequest proto.InternalMessageInfo + +func (m *QueryUnreceivedPacketsRequest) GetPortId() string { + if m != nil { + return m.PortId + } + return "" +} + +func (m *QueryUnreceivedPacketsRequest) GetChannelId() string { + if m != nil { + return m.ChannelId + } + return "" +} + +func (m *QueryUnreceivedPacketsRequest) GetPacketCommitmentSequences() []uint64 { + if m != nil { + return m.PacketCommitmentSequences + } + return nil +} + +// QueryUnreceivedPacketsResponse is the response type for the +// Query/UnreceivedPacketCommitments RPC method +type QueryUnreceivedPacketsResponse struct { + // list of unreceived packet sequences + Sequences []uint64 `protobuf:"varint,1,rep,packed,name=sequences,proto3" json:"sequences,omitempty"` + // query block height + Height types.Height `protobuf:"bytes,2,opt,name=height,proto3" json:"height"` +} + +func (m *QueryUnreceivedPacketsResponse) Reset() { *m = QueryUnreceivedPacketsResponse{} } +func (m *QueryUnreceivedPacketsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryUnreceivedPacketsResponse) ProtoMessage() {} +func (*QueryUnreceivedPacketsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{21} +} +func (m *QueryUnreceivedPacketsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryUnreceivedPacketsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryUnreceivedPacketsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryUnreceivedPacketsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryUnreceivedPacketsResponse.Merge(m, src) +} +func (m *QueryUnreceivedPacketsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryUnreceivedPacketsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryUnreceivedPacketsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryUnreceivedPacketsResponse proto.InternalMessageInfo + +func (m *QueryUnreceivedPacketsResponse) GetSequences() []uint64 { + if m != nil { + return m.Sequences + } + return nil +} + +func (m *QueryUnreceivedPacketsResponse) GetHeight() types.Height { + if m != nil { + return m.Height + } + return types.Height{} +} + +// QueryUnreceivedAcks is the request type for the +// Query/UnreceivedAcks RPC method +type QueryUnreceivedAcksRequest struct { + // port unique identifier + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + // channel unique identifier + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + // list of acknowledgement sequences + PacketAckSequences []uint64 `protobuf:"varint,3,rep,packed,name=packet_ack_sequences,json=packetAckSequences,proto3" json:"packet_ack_sequences,omitempty"` +} + +func (m *QueryUnreceivedAcksRequest) Reset() { *m = QueryUnreceivedAcksRequest{} } +func (m *QueryUnreceivedAcksRequest) String() string { return proto.CompactTextString(m) } +func (*QueryUnreceivedAcksRequest) ProtoMessage() {} +func (*QueryUnreceivedAcksRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{22} +} +func (m *QueryUnreceivedAcksRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryUnreceivedAcksRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryUnreceivedAcksRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryUnreceivedAcksRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryUnreceivedAcksRequest.Merge(m, src) +} +func (m *QueryUnreceivedAcksRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryUnreceivedAcksRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryUnreceivedAcksRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryUnreceivedAcksRequest proto.InternalMessageInfo + +func (m *QueryUnreceivedAcksRequest) GetPortId() string { + if m != nil { + return m.PortId + } + return "" +} + +func (m *QueryUnreceivedAcksRequest) GetChannelId() string { + if m != nil { + return m.ChannelId + } + return "" +} + +func (m *QueryUnreceivedAcksRequest) GetPacketAckSequences() []uint64 { + if m != nil { + return m.PacketAckSequences + } + return nil +} + +// QueryUnreceivedAcksResponse is the response type for the +// Query/UnreceivedAcks RPC method +type QueryUnreceivedAcksResponse struct { + // list of unreceived acknowledgement sequences + Sequences []uint64 `protobuf:"varint,1,rep,packed,name=sequences,proto3" json:"sequences,omitempty"` + // query block height + Height types.Height `protobuf:"bytes,2,opt,name=height,proto3" json:"height"` +} + +func (m *QueryUnreceivedAcksResponse) Reset() { *m = QueryUnreceivedAcksResponse{} } +func (m *QueryUnreceivedAcksResponse) String() string { return proto.CompactTextString(m) } +func (*QueryUnreceivedAcksResponse) ProtoMessage() {} +func (*QueryUnreceivedAcksResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{23} +} +func (m *QueryUnreceivedAcksResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryUnreceivedAcksResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryUnreceivedAcksResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryUnreceivedAcksResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryUnreceivedAcksResponse.Merge(m, src) +} +func (m *QueryUnreceivedAcksResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryUnreceivedAcksResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryUnreceivedAcksResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryUnreceivedAcksResponse proto.InternalMessageInfo + +func (m *QueryUnreceivedAcksResponse) GetSequences() []uint64 { + if m != nil { + return m.Sequences + } + return nil +} + +func (m *QueryUnreceivedAcksResponse) GetHeight() types.Height { + if m != nil { + return m.Height + } + return types.Height{} +} + +// QueryNextSequenceReceiveRequest is the request type for the +// Query/QueryNextSequenceReceiveRequest RPC method +type QueryNextSequenceReceiveRequest struct { + // port unique identifier + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + // channel unique identifier + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` +} + +func (m *QueryNextSequenceReceiveRequest) Reset() { *m = QueryNextSequenceReceiveRequest{} } +func (m *QueryNextSequenceReceiveRequest) String() string { return proto.CompactTextString(m) } +func (*QueryNextSequenceReceiveRequest) ProtoMessage() {} +func (*QueryNextSequenceReceiveRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{24} +} +func (m *QueryNextSequenceReceiveRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryNextSequenceReceiveRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryNextSequenceReceiveRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryNextSequenceReceiveRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryNextSequenceReceiveRequest.Merge(m, src) +} +func (m *QueryNextSequenceReceiveRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryNextSequenceReceiveRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryNextSequenceReceiveRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryNextSequenceReceiveRequest proto.InternalMessageInfo + +func (m *QueryNextSequenceReceiveRequest) GetPortId() string { + if m != nil { + return m.PortId + } + return "" +} + +func (m *QueryNextSequenceReceiveRequest) GetChannelId() string { + if m != nil { + return m.ChannelId + } + return "" +} + +// QuerySequenceResponse is the response type for the +// Query/QueryNextSequenceReceiveResponse RPC method +type QueryNextSequenceReceiveResponse struct { + // next sequence receive number + NextSequenceReceive uint64 `protobuf:"varint,1,opt,name=next_sequence_receive,json=nextSequenceReceive,proto3" json:"next_sequence_receive,omitempty"` + // merkle proof of existence + Proof []byte `protobuf:"bytes,2,opt,name=proof,proto3" json:"proof,omitempty"` + // height at which the proof was retrieved + ProofHeight types.Height `protobuf:"bytes,3,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` +} + +func (m *QueryNextSequenceReceiveResponse) Reset() { *m = QueryNextSequenceReceiveResponse{} } +func (m *QueryNextSequenceReceiveResponse) String() string { return proto.CompactTextString(m) } +func (*QueryNextSequenceReceiveResponse) ProtoMessage() {} +func (*QueryNextSequenceReceiveResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{25} +} +func (m *QueryNextSequenceReceiveResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryNextSequenceReceiveResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryNextSequenceReceiveResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryNextSequenceReceiveResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryNextSequenceReceiveResponse.Merge(m, src) +} +func (m *QueryNextSequenceReceiveResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryNextSequenceReceiveResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryNextSequenceReceiveResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryNextSequenceReceiveResponse proto.InternalMessageInfo + +func (m *QueryNextSequenceReceiveResponse) GetNextSequenceReceive() uint64 { + if m != nil { + return m.NextSequenceReceive + } + return 0 +} + +func (m *QueryNextSequenceReceiveResponse) GetProof() []byte { + if m != nil { + return m.Proof + } + return nil +} + +func (m *QueryNextSequenceReceiveResponse) GetProofHeight() types.Height { + if m != nil { + return m.ProofHeight + } + return types.Height{} +} + +// QueryNextSequenceSendRequest is the request type for the +// Query/QueryNextSequenceSend RPC method +type QueryNextSequenceSendRequest struct { + // port unique identifier + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + // channel unique identifier + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` +} + +func (m *QueryNextSequenceSendRequest) Reset() { *m = QueryNextSequenceSendRequest{} } +func (m *QueryNextSequenceSendRequest) String() string { return proto.CompactTextString(m) } +func (*QueryNextSequenceSendRequest) ProtoMessage() {} +func (*QueryNextSequenceSendRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{26} +} +func (m *QueryNextSequenceSendRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryNextSequenceSendRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryNextSequenceSendRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryNextSequenceSendRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryNextSequenceSendRequest.Merge(m, src) +} +func (m *QueryNextSequenceSendRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryNextSequenceSendRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryNextSequenceSendRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryNextSequenceSendRequest proto.InternalMessageInfo + +func (m *QueryNextSequenceSendRequest) GetPortId() string { + if m != nil { + return m.PortId + } + return "" +} + +func (m *QueryNextSequenceSendRequest) GetChannelId() string { + if m != nil { + return m.ChannelId + } + return "" +} + +// QueryNextSequenceSendResponse is the request type for the +// Query/QueryNextSequenceSend RPC method +type QueryNextSequenceSendResponse struct { + // next sequence send number + NextSequenceSend uint64 `protobuf:"varint,1,opt,name=next_sequence_send,json=nextSequenceSend,proto3" json:"next_sequence_send,omitempty"` + // merkle proof of existence + Proof []byte `protobuf:"bytes,2,opt,name=proof,proto3" json:"proof,omitempty"` + // height at which the proof was retrieved + ProofHeight types.Height `protobuf:"bytes,3,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` +} + +func (m *QueryNextSequenceSendResponse) Reset() { *m = QueryNextSequenceSendResponse{} } +func (m *QueryNextSequenceSendResponse) String() string { return proto.CompactTextString(m) } +func (*QueryNextSequenceSendResponse) ProtoMessage() {} +func (*QueryNextSequenceSendResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_1034a1e9abc4cca1, []int{27} +} +func (m *QueryNextSequenceSendResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryNextSequenceSendResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryNextSequenceSendResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryNextSequenceSendResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryNextSequenceSendResponse.Merge(m, src) +} +func (m *QueryNextSequenceSendResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryNextSequenceSendResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryNextSequenceSendResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryNextSequenceSendResponse proto.InternalMessageInfo + +func (m *QueryNextSequenceSendResponse) GetNextSequenceSend() uint64 { + if m != nil { + return m.NextSequenceSend + } + return 0 +} + +func (m *QueryNextSequenceSendResponse) GetProof() []byte { + if m != nil { + return m.Proof + } + return nil +} + +func (m *QueryNextSequenceSendResponse) GetProofHeight() types.Height { + if m != nil { + return m.ProofHeight + } + return types.Height{} +} + +func init() { + proto.RegisterType((*QueryChannelRequest)(nil), "ibc.core.channel.v1.QueryChannelRequest") + proto.RegisterType((*QueryChannelResponse)(nil), "ibc.core.channel.v1.QueryChannelResponse") + proto.RegisterType((*QueryChannelsRequest)(nil), "ibc.core.channel.v1.QueryChannelsRequest") + proto.RegisterType((*QueryChannelsResponse)(nil), "ibc.core.channel.v1.QueryChannelsResponse") + proto.RegisterType((*QueryConnectionChannelsRequest)(nil), "ibc.core.channel.v1.QueryConnectionChannelsRequest") + proto.RegisterType((*QueryConnectionChannelsResponse)(nil), "ibc.core.channel.v1.QueryConnectionChannelsResponse") + proto.RegisterType((*QueryChannelClientStateRequest)(nil), "ibc.core.channel.v1.QueryChannelClientStateRequest") + proto.RegisterType((*QueryChannelClientStateResponse)(nil), "ibc.core.channel.v1.QueryChannelClientStateResponse") + proto.RegisterType((*QueryChannelConsensusStateRequest)(nil), "ibc.core.channel.v1.QueryChannelConsensusStateRequest") + proto.RegisterType((*QueryChannelConsensusStateResponse)(nil), "ibc.core.channel.v1.QueryChannelConsensusStateResponse") + proto.RegisterType((*QueryPacketCommitmentRequest)(nil), "ibc.core.channel.v1.QueryPacketCommitmentRequest") + proto.RegisterType((*QueryPacketCommitmentResponse)(nil), "ibc.core.channel.v1.QueryPacketCommitmentResponse") + proto.RegisterType((*QueryPacketCommitmentsRequest)(nil), "ibc.core.channel.v1.QueryPacketCommitmentsRequest") + proto.RegisterType((*QueryPacketCommitmentsResponse)(nil), "ibc.core.channel.v1.QueryPacketCommitmentsResponse") + proto.RegisterType((*QueryPacketReceiptRequest)(nil), "ibc.core.channel.v1.QueryPacketReceiptRequest") + proto.RegisterType((*QueryPacketReceiptResponse)(nil), "ibc.core.channel.v1.QueryPacketReceiptResponse") + proto.RegisterType((*QueryPacketAcknowledgementRequest)(nil), "ibc.core.channel.v1.QueryPacketAcknowledgementRequest") + proto.RegisterType((*QueryPacketAcknowledgementResponse)(nil), "ibc.core.channel.v1.QueryPacketAcknowledgementResponse") + proto.RegisterType((*QueryPacketAcknowledgementsRequest)(nil), "ibc.core.channel.v1.QueryPacketAcknowledgementsRequest") + proto.RegisterType((*QueryPacketAcknowledgementsResponse)(nil), "ibc.core.channel.v1.QueryPacketAcknowledgementsResponse") + proto.RegisterType((*QueryUnreceivedPacketsRequest)(nil), "ibc.core.channel.v1.QueryUnreceivedPacketsRequest") + proto.RegisterType((*QueryUnreceivedPacketsResponse)(nil), "ibc.core.channel.v1.QueryUnreceivedPacketsResponse") + proto.RegisterType((*QueryUnreceivedAcksRequest)(nil), "ibc.core.channel.v1.QueryUnreceivedAcksRequest") + proto.RegisterType((*QueryUnreceivedAcksResponse)(nil), "ibc.core.channel.v1.QueryUnreceivedAcksResponse") + proto.RegisterType((*QueryNextSequenceReceiveRequest)(nil), "ibc.core.channel.v1.QueryNextSequenceReceiveRequest") + proto.RegisterType((*QueryNextSequenceReceiveResponse)(nil), "ibc.core.channel.v1.QueryNextSequenceReceiveResponse") + proto.RegisterType((*QueryNextSequenceSendRequest)(nil), "ibc.core.channel.v1.QueryNextSequenceSendRequest") + proto.RegisterType((*QueryNextSequenceSendResponse)(nil), "ibc.core.channel.v1.QueryNextSequenceSendResponse") +} + +func init() { proto.RegisterFile("ibc/core/channel/v1/query.proto", fileDescriptor_1034a1e9abc4cca1) } + +var fileDescriptor_1034a1e9abc4cca1 = []byte{ + // 1549 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x59, 0xcf, 0x6f, 0x13, 0xc7, + 0x17, 0xcf, 0x38, 0x06, 0x92, 0x07, 0x5f, 0x08, 0x93, 0xe4, 0x4b, 0x58, 0x82, 0x13, 0x5c, 0xb5, + 0x04, 0x54, 0x76, 0xe2, 0x40, 0x29, 0x42, 0x2d, 0x12, 0x89, 0x54, 0x48, 0x55, 0x7e, 0x6d, 0xa0, + 0x05, 0xa4, 0xd6, 0x5d, 0xaf, 0x07, 0x67, 0x95, 0x78, 0xd7, 0x78, 0xd7, 0x06, 0x94, 0xba, 0xaa, + 0x7a, 0xa0, 0x1c, 0xab, 0x72, 0xa8, 0xd4, 0x4b, 0xa5, 0x9e, 0xca, 0xa1, 0x87, 0xfe, 0x05, 0xbd, + 0x72, 0x2b, 0x12, 0x3d, 0x54, 0x42, 0xa2, 0x15, 0x41, 0xa2, 0xd7, 0x5e, 0x7a, 0xae, 0x76, 0x7e, + 0xac, 0x77, 0xed, 0xdd, 0x4d, 0x1c, 0xc7, 0x52, 0xd4, 0x9b, 0x77, 0x66, 0xde, 0x9b, 0xcf, 0xe7, + 0xf3, 0xe6, 0xbd, 0x9d, 0xb7, 0x86, 0x09, 0xb3, 0x60, 0x10, 0xc3, 0xae, 0x52, 0x62, 0x2c, 0xea, + 0x96, 0x45, 0x97, 0x49, 0x3d, 0x47, 0x6e, 0xd7, 0x68, 0xf5, 0x9e, 0x5a, 0xa9, 0xda, 0xae, 0x8d, + 0x87, 0xcd, 0x82, 0xa1, 0x7a, 0x0b, 0x54, 0xb1, 0x40, 0xad, 0xe7, 0x94, 0x80, 0xd5, 0xb2, 0x49, + 0x2d, 0xd7, 0x33, 0xe2, 0xbf, 0xb8, 0x95, 0x72, 0xd4, 0xb0, 0x9d, 0xb2, 0xed, 0x90, 0x82, 0xee, + 0x50, 0xee, 0x8e, 0xd4, 0x73, 0x05, 0xea, 0xea, 0x39, 0x52, 0xd1, 0x4b, 0xa6, 0xa5, 0xbb, 0xa6, + 0x6d, 0x89, 0xb5, 0x87, 0xa2, 0x20, 0xc8, 0xcd, 0xf8, 0x92, 0xf1, 0x92, 0x6d, 0x97, 0x96, 0x29, + 0xd1, 0x2b, 0x26, 0xd1, 0x2d, 0xcb, 0x76, 0x99, 0xbd, 0x23, 0x66, 0xf7, 0x8b, 0x59, 0xf6, 0x54, + 0xa8, 0xdd, 0x22, 0xba, 0x25, 0xd0, 0x2b, 0x23, 0x25, 0xbb, 0x64, 0xb3, 0x9f, 0xc4, 0xfb, 0xc5, + 0x47, 0xb3, 0x17, 0x60, 0xf8, 0x8a, 0x87, 0x69, 0x8e, 0x6f, 0xa2, 0xd1, 0xdb, 0x35, 0xea, 0xb8, + 0x78, 0x1f, 0xec, 0xa8, 0xd8, 0x55, 0x37, 0x6f, 0x16, 0xc7, 0xd0, 0x24, 0x9a, 0x1a, 0xd4, 0xb6, + 0x7b, 0x8f, 0xf3, 0x45, 0x7c, 0x10, 0x40, 0xe0, 0xf1, 0xe6, 0x52, 0x6c, 0x6e, 0x50, 0x8c, 0xcc, + 0x17, 0xb3, 0x8f, 0x10, 0x8c, 0x84, 0xfd, 0x39, 0x15, 0xdb, 0x72, 0x28, 0x3e, 0x09, 0x3b, 0xc4, + 0x2a, 0xe6, 0x70, 0xe7, 0xcc, 0xb8, 0x1a, 0xa1, 0xa6, 0x2a, 0xcd, 0xe4, 0x62, 0x3c, 0x02, 0xdb, + 0x2a, 0x55, 0xdb, 0xbe, 0xc5, 0xb6, 0xda, 0xa5, 0xf1, 0x07, 0x3c, 0x07, 0xbb, 0xd8, 0x8f, 0xfc, + 0x22, 0x35, 0x4b, 0x8b, 0xee, 0x58, 0x3f, 0x73, 0xa9, 0x04, 0x5c, 0xf2, 0x08, 0xd4, 0x73, 0xea, + 0x79, 0xb6, 0x62, 0x36, 0xfd, 0xf8, 0xf9, 0x44, 0x9f, 0xb6, 0x93, 0x59, 0xf1, 0xa1, 0xec, 0x27, + 0x61, 0xa8, 0x8e, 0xe4, 0xfe, 0x1e, 0x40, 0x33, 0x30, 0x02, 0xed, 0x1b, 0x2a, 0x8f, 0xa2, 0xea, + 0x45, 0x51, 0xe5, 0x87, 0x42, 0x44, 0x51, 0xbd, 0xac, 0x97, 0xa8, 0xb0, 0xd5, 0x02, 0x96, 0xd9, + 0xe7, 0x08, 0x46, 0x5b, 0x36, 0x10, 0x62, 0xcc, 0xc2, 0x80, 0xe0, 0xe7, 0x8c, 0xa1, 0xc9, 0x7e, + 0xe6, 0x3f, 0x4a, 0x8d, 0xf9, 0x22, 0xb5, 0x5c, 0xf3, 0x96, 0x49, 0x8b, 0x52, 0x17, 0xdf, 0x0e, + 0x9f, 0x0b, 0xa1, 0x4c, 0x31, 0x94, 0x87, 0xd7, 0x44, 0xc9, 0x01, 0x04, 0x61, 0xe2, 0x53, 0xb0, + 0xbd, 0x43, 0x15, 0xc5, 0xfa, 0xec, 0x03, 0x04, 0x19, 0x4e, 0xd0, 0xb6, 0x2c, 0x6a, 0x78, 0xde, + 0x5a, 0xb5, 0xcc, 0x00, 0x18, 0xfe, 0xa4, 0x38, 0x4a, 0x81, 0x91, 0x16, 0xad, 0x53, 0x1b, 0xd6, + 0xfa, 0x2f, 0x04, 0x13, 0xb1, 0x50, 0xfe, 0x5b, 0xaa, 0x5f, 0x97, 0xa2, 0x73, 0x4c, 0x73, 0x6c, + 0xf5, 0x82, 0xab, 0xbb, 0xb4, 0xdb, 0xe4, 0xfd, 0xc3, 0x17, 0x31, 0xc2, 0xb5, 0x10, 0x51, 0x87, + 0x7d, 0xa6, 0xaf, 0x4f, 0x9e, 0x43, 0xcd, 0x3b, 0xde, 0x12, 0x91, 0x29, 0x47, 0xa2, 0x88, 0x04, + 0x24, 0x0d, 0xf8, 0x1c, 0x35, 0xa3, 0x86, 0x7b, 0x99, 0xf2, 0x3f, 0x21, 0x38, 0x14, 0x62, 0xe8, + 0x71, 0xb2, 0x9c, 0x9a, 0xb3, 0x19, 0xfa, 0xe1, 0xc3, 0xb0, 0xa7, 0x4a, 0xeb, 0xa6, 0x63, 0xda, + 0x56, 0xde, 0xaa, 0x95, 0x0b, 0xb4, 0xca, 0x50, 0xa6, 0xb5, 0xdd, 0x72, 0xf8, 0x22, 0x1b, 0x0d, + 0x2d, 0x14, 0x74, 0xd2, 0xe1, 0x85, 0x02, 0xef, 0x33, 0x04, 0xd9, 0x24, 0xbc, 0x22, 0x28, 0xef, + 0xc2, 0x1e, 0x43, 0xce, 0x84, 0x82, 0x31, 0xa2, 0xf2, 0xf7, 0x81, 0x2a, 0xdf, 0x07, 0xea, 0x59, + 0xeb, 0x9e, 0xb6, 0xdb, 0x08, 0xb9, 0xc1, 0x07, 0x60, 0x50, 0x04, 0xd2, 0x67, 0x35, 0xc0, 0x07, + 0xe6, 0x8b, 0xcd, 0x68, 0xf4, 0x27, 0x45, 0x23, 0xbd, 0x91, 0x68, 0x54, 0x61, 0x9c, 0x91, 0xbb, + 0xac, 0x1b, 0x4b, 0xd4, 0x9d, 0xb3, 0xcb, 0x65, 0xd3, 0x2d, 0x53, 0xcb, 0xed, 0x36, 0x0e, 0x0a, + 0x0c, 0x38, 0x9e, 0x0b, 0xcb, 0xa0, 0x22, 0x00, 0xfe, 0x73, 0xf6, 0x3b, 0x04, 0x07, 0x63, 0x36, + 0x15, 0x62, 0xb2, 0x92, 0x25, 0x47, 0xd9, 0xc6, 0xbb, 0xb4, 0xc0, 0x48, 0x2f, 0x8f, 0xe7, 0xf7, + 0x71, 0xe0, 0x9c, 0x6e, 0x25, 0x09, 0xd7, 0xd9, 0xfe, 0x0d, 0xd7, 0xd9, 0x57, 0xb2, 0xe4, 0x47, + 0x20, 0xf4, 0xcb, 0xec, 0xce, 0xa6, 0x5a, 0xb2, 0xd2, 0x4e, 0x46, 0x56, 0x5a, 0xee, 0x84, 0x9f, + 0xe5, 0xa0, 0xd1, 0x56, 0x28, 0xb3, 0x36, 0xec, 0x0f, 0x10, 0xd5, 0xa8, 0x41, 0xcd, 0x4a, 0x4f, + 0x4f, 0xe6, 0x43, 0x04, 0x4a, 0xd4, 0x8e, 0x42, 0x56, 0x05, 0x06, 0xaa, 0xde, 0x50, 0x9d, 0x72, + 0xbf, 0x03, 0x9a, 0xff, 0xdc, 0xcb, 0x1c, 0xbd, 0x23, 0x0a, 0x26, 0x07, 0x75, 0xd6, 0x58, 0xb2, + 0xec, 0x3b, 0xcb, 0xb4, 0x58, 0xa2, 0xbd, 0x4e, 0xd4, 0x47, 0xb2, 0xf4, 0xc5, 0xec, 0x2c, 0x64, + 0x99, 0x82, 0x3d, 0x7a, 0x78, 0x4a, 0xa4, 0x6c, 0xeb, 0x70, 0x2f, 0xf3, 0xf6, 0x65, 0x22, 0xd6, + 0xad, 0x92, 0xbc, 0xf8, 0x0c, 0x1c, 0xa8, 0x30, 0x80, 0xf9, 0x66, 0xae, 0xe5, 0xa5, 0xe0, 0xce, + 0x58, 0x7a, 0xb2, 0x7f, 0x2a, 0xad, 0xed, 0xaf, 0xb4, 0x64, 0xf6, 0x82, 0x5c, 0x90, 0xfd, 0x07, + 0xc1, 0x6b, 0x89, 0x34, 0x45, 0x4c, 0x3e, 0x80, 0xa1, 0x16, 0xf1, 0xd7, 0x5f, 0x06, 0xda, 0x2c, + 0xb7, 0x42, 0x2d, 0xf8, 0x56, 0xd6, 0xe5, 0x6b, 0x96, 0xcc, 0x39, 0x8e, 0xb9, 0xeb, 0xd0, 0xae, + 0x11, 0x92, 0xfe, 0xb5, 0x42, 0x72, 0x57, 0x94, 0xe3, 0x08, 0x60, 0x22, 0x18, 0xe3, 0x30, 0xd8, + 0xf4, 0x87, 0x98, 0xbf, 0xe6, 0x40, 0x40, 0x93, 0x54, 0x87, 0x9a, 0xdc, 0x97, 0xe5, 0xaa, 0xb9, + 0xf5, 0x59, 0x63, 0xa9, 0x6b, 0x41, 0xa6, 0x61, 0x44, 0x08, 0xa2, 0x1b, 0x4b, 0x6d, 0x4a, 0xe0, + 0x8a, 0x3c, 0x79, 0x4d, 0x09, 0x6a, 0x70, 0x20, 0x12, 0x47, 0x8f, 0xf9, 0xdf, 0x10, 0x77, 0xe5, + 0x8b, 0xf4, 0xae, 0x1f, 0x0f, 0x8d, 0x03, 0xe8, 0xf6, 0x1e, 0xfe, 0x33, 0x82, 0xc9, 0x78, 0xdf, + 0x82, 0xd7, 0x0c, 0x8c, 0x5a, 0xf4, 0x6e, 0xf3, 0xb0, 0xe4, 0x05, 0x7b, 0xb6, 0x55, 0x5a, 0x1b, + 0xb6, 0xda, 0x6d, 0x7b, 0x59, 0x02, 0x3f, 0x14, 0x77, 0xb9, 0x20, 0xe4, 0x05, 0x6a, 0x15, 0xbb, + 0xd5, 0xe2, 0x47, 0x99, 0x7a, 0xed, 0x8e, 0x85, 0x10, 0x6f, 0x02, 0x0e, 0x0b, 0xe1, 0x50, 0xab, + 0x28, 0x54, 0x18, 0xb2, 0x5a, 0xac, 0x7a, 0x28, 0xc1, 0xcc, 0xf3, 0x7d, 0xb0, 0x8d, 0x41, 0xc5, + 0x3f, 0x20, 0xd8, 0x21, 0x6e, 0xec, 0x78, 0x2a, 0xb2, 0xe4, 0x45, 0x7c, 0x73, 0x51, 0x8e, 0xac, + 0x63, 0x25, 0xe7, 0x9c, 0x9d, 0xfd, 0xf2, 0xe9, 0xcb, 0x87, 0xa9, 0x77, 0xf0, 0x69, 0x92, 0xf0, + 0xc1, 0xc8, 0x21, 0x2b, 0x4d, 0x65, 0x1b, 0xc4, 0xd3, 0xdb, 0x21, 0x2b, 0x22, 0x0a, 0x0d, 0xfc, + 0x00, 0xc1, 0x80, 0xec, 0x91, 0xf1, 0xda, 0x7b, 0xcb, 0xcc, 0x56, 0x8e, 0xae, 0x67, 0xa9, 0xc0, + 0xf9, 0x3a, 0xc3, 0x39, 0x81, 0x0f, 0x26, 0xe2, 0xc4, 0xbf, 0x20, 0xc0, 0xed, 0x8d, 0x3b, 0x3e, + 0x9e, 0xb0, 0x53, 0xdc, 0x17, 0x07, 0xe5, 0x44, 0x67, 0x46, 0x02, 0xe8, 0x19, 0x06, 0xf4, 0x14, + 0x3e, 0x19, 0x0d, 0xd4, 0x37, 0xf4, 0x34, 0xf5, 0x1f, 0x1a, 0x4d, 0x06, 0x4f, 0x3c, 0x06, 0x6d, + 0x5d, 0x73, 0x22, 0x83, 0xb8, 0xf6, 0x3d, 0x91, 0x41, 0x6c, 0x63, 0x9e, 0xbd, 0xc4, 0x18, 0xcc, + 0xe3, 0x73, 0x1b, 0x3f, 0x12, 0x24, 0xd8, 0xce, 0xe3, 0x6f, 0x52, 0x30, 0x1a, 0xd9, 0x76, 0xe2, + 0x93, 0x6b, 0x03, 0x8c, 0xea, 0xab, 0x95, 0xb7, 0x3b, 0xb6, 0x13, 0xdc, 0xbe, 0x42, 0x8c, 0xdc, + 0x17, 0x08, 0x7f, 0xde, 0x0d, 0xbb, 0x70, 0x8b, 0x4c, 0x64, 0xaf, 0x4d, 0x56, 0x5a, 0xba, 0xf6, + 0x06, 0xe1, 0x65, 0x20, 0x30, 0xc1, 0x07, 0x1a, 0xf8, 0x19, 0x82, 0xa1, 0xd6, 0xd6, 0x07, 0xe7, + 0xe2, 0x79, 0xc5, 0xb4, 0xb6, 0xca, 0x4c, 0x27, 0x26, 0x42, 0x85, 0x4f, 0x99, 0x08, 0x37, 0xf1, + 0xf5, 0x2e, 0x34, 0x68, 0xbb, 0x6c, 0x38, 0x64, 0x45, 0x16, 0xce, 0x06, 0x7e, 0x8a, 0x60, 0x6f, + 0x5b, 0x63, 0x87, 0x3b, 0xc0, 0xea, 0x67, 0xe1, 0xf1, 0x8e, 0x6c, 0x04, 0xc1, 0x6b, 0x8c, 0xe0, + 0x25, 0x7c, 0x61, 0x53, 0x09, 0xe2, 0x5f, 0x11, 0xfc, 0x2f, 0xd4, 0x53, 0x61, 0x75, 0x2d, 0x74, + 0xe1, 0x76, 0x4f, 0x21, 0xeb, 0x5e, 0x2f, 0x98, 0x7c, 0xcc, 0x98, 0x7c, 0x84, 0xaf, 0x75, 0xcf, + 0xa4, 0xca, 0x5d, 0x87, 0xe2, 0xb4, 0x8a, 0x60, 0x34, 0xf2, 0x0e, 0x9e, 0x94, 0x9a, 0x49, 0x1d, + 0x5c, 0x52, 0x6a, 0x26, 0xf6, 0x5f, 0xd9, 0x1b, 0x8c, 0xe9, 0x02, 0xbe, 0xd2, 0x3d, 0x53, 0xdd, + 0x58, 0x0a, 0xb1, 0x7c, 0x85, 0xe0, 0xff, 0xd1, 0x9d, 0x06, 0xee, 0x14, 0xae, 0x7f, 0x2e, 0x4f, + 0x75, 0x6e, 0x28, 0x88, 0xde, 0x64, 0x44, 0xaf, 0x62, 0x6d, 0x53, 0x88, 0x86, 0xe9, 0xdc, 0x4f, + 0xc1, 0xde, 0xb6, 0x1b, 0x7c, 0x52, 0xde, 0xc5, 0xf5, 0x21, 0x49, 0x79, 0x17, 0xdb, 0x22, 0x6c, + 0x52, 0x79, 0x8d, 0x2a, 0x2d, 0x09, 0xbd, 0x4d, 0x83, 0xd4, 0x7c, 0x40, 0xf9, 0x8a, 0xa0, 0xfc, + 0x37, 0x82, 0xdd, 0xe1, 0x7b, 0x3c, 0x26, 0xeb, 0x61, 0x14, 0xe8, 0x3c, 0x94, 0xe9, 0xf5, 0x1b, + 0x08, 0xfe, 0x9f, 0x31, 0xfa, 0x75, 0xec, 0xf6, 0x86, 0x7d, 0xa8, 0x91, 0x09, 0xd1, 0xf6, 0x4e, + 0x3c, 0xfe, 0x0d, 0xc1, 0x70, 0xc4, 0x45, 0x1f, 0x27, 0x5c, 0x03, 0xe2, 0x7b, 0x0e, 0xe5, 0xad, + 0x0e, 0xad, 0x84, 0x04, 0x97, 0x99, 0x04, 0xef, 0xe3, 0xf3, 0x5d, 0x48, 0x10, 0xba, 0x85, 0x7b, + 0x37, 0xa2, 0xa1, 0xd6, 0x3b, 0x7b, 0xd2, 0x9b, 0x32, 0xa6, 0x71, 0x48, 0x7a, 0x53, 0xc6, 0xb5, + 0x04, 0x9b, 0xf2, 0x22, 0x69, 0xef, 0x29, 0x66, 0xaf, 0x3e, 0x7e, 0x91, 0x41, 0x4f, 0x5e, 0x64, + 0xd0, 0x9f, 0x2f, 0x32, 0xe8, 0xeb, 0xd5, 0x4c, 0xdf, 0x93, 0xd5, 0x4c, 0xdf, 0xef, 0xab, 0x99, + 0xbe, 0x9b, 0xa7, 0x4b, 0xa6, 0xbb, 0x58, 0x2b, 0xa8, 0x86, 0x5d, 0x26, 0xe2, 0xef, 0x5e, 0xb3, + 0x60, 0x1c, 0x2b, 0xd9, 0xa4, 0x9e, 0x9b, 0x26, 0x65, 0xbb, 0x58, 0x5b, 0xa6, 0x0e, 0x07, 0x32, + 0x7d, 0xe2, 0x98, 0xc4, 0xe2, 0xde, 0xab, 0x50, 0xa7, 0xb0, 0x9d, 0x7d, 0x9b, 0x3f, 0xfe, 0x6f, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x59, 0x88, 0xd9, 0xeb, 0x7f, 0x1e, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // Channel queries an IBC Channel. + Channel(ctx context.Context, in *QueryChannelRequest, opts ...grpc.CallOption) (*QueryChannelResponse, error) + // Channels queries all the IBC channels of a chain. + Channels(ctx context.Context, in *QueryChannelsRequest, opts ...grpc.CallOption) (*QueryChannelsResponse, error) + // ConnectionChannels queries all the channels associated with a connection + // end. + ConnectionChannels(ctx context.Context, in *QueryConnectionChannelsRequest, opts ...grpc.CallOption) (*QueryConnectionChannelsResponse, error) + // ChannelClientState queries for the client state for the channel associated + // with the provided channel identifiers. + ChannelClientState(ctx context.Context, in *QueryChannelClientStateRequest, opts ...grpc.CallOption) (*QueryChannelClientStateResponse, error) + // ChannelConsensusState queries for the consensus state for the channel + // associated with the provided channel identifiers. + ChannelConsensusState(ctx context.Context, in *QueryChannelConsensusStateRequest, opts ...grpc.CallOption) (*QueryChannelConsensusStateResponse, error) + // PacketCommitment queries a stored packet commitment hash. + PacketCommitment(ctx context.Context, in *QueryPacketCommitmentRequest, opts ...grpc.CallOption) (*QueryPacketCommitmentResponse, error) + // PacketCommitments returns all the packet commitments hashes associated + // with a channel. + PacketCommitments(ctx context.Context, in *QueryPacketCommitmentsRequest, opts ...grpc.CallOption) (*QueryPacketCommitmentsResponse, error) + // PacketReceipt queries if a given packet sequence has been received on the + // queried chain + PacketReceipt(ctx context.Context, in *QueryPacketReceiptRequest, opts ...grpc.CallOption) (*QueryPacketReceiptResponse, error) + // PacketAcknowledgement queries a stored packet acknowledgement hash. + PacketAcknowledgement(ctx context.Context, in *QueryPacketAcknowledgementRequest, opts ...grpc.CallOption) (*QueryPacketAcknowledgementResponse, error) + // PacketAcknowledgements returns all the packet acknowledgements associated + // with a channel. + PacketAcknowledgements(ctx context.Context, in *QueryPacketAcknowledgementsRequest, opts ...grpc.CallOption) (*QueryPacketAcknowledgementsResponse, error) + // UnreceivedPackets returns all the unreceived IBC packets associated with a + // channel and sequences. + UnreceivedPackets(ctx context.Context, in *QueryUnreceivedPacketsRequest, opts ...grpc.CallOption) (*QueryUnreceivedPacketsResponse, error) + // UnreceivedAcks returns all the unreceived IBC acknowledgements associated + // with a channel and sequences. + UnreceivedAcks(ctx context.Context, in *QueryUnreceivedAcksRequest, opts ...grpc.CallOption) (*QueryUnreceivedAcksResponse, error) + // NextSequenceReceive returns the next receive sequence for a given channel. + NextSequenceReceive(ctx context.Context, in *QueryNextSequenceReceiveRequest, opts ...grpc.CallOption) (*QueryNextSequenceReceiveResponse, error) + // NextSequenceSend returns the next send sequence for a given channel. + NextSequenceSend(ctx context.Context, in *QueryNextSequenceSendRequest, opts ...grpc.CallOption) (*QueryNextSequenceSendResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) Channel(ctx context.Context, in *QueryChannelRequest, opts ...grpc.CallOption) (*QueryChannelResponse, error) { + out := new(QueryChannelResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Query/Channel", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) Channels(ctx context.Context, in *QueryChannelsRequest, opts ...grpc.CallOption) (*QueryChannelsResponse, error) { + out := new(QueryChannelsResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Query/Channels", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) ConnectionChannels(ctx context.Context, in *QueryConnectionChannelsRequest, opts ...grpc.CallOption) (*QueryConnectionChannelsResponse, error) { + out := new(QueryConnectionChannelsResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Query/ConnectionChannels", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) ChannelClientState(ctx context.Context, in *QueryChannelClientStateRequest, opts ...grpc.CallOption) (*QueryChannelClientStateResponse, error) { + out := new(QueryChannelClientStateResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Query/ChannelClientState", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) ChannelConsensusState(ctx context.Context, in *QueryChannelConsensusStateRequest, opts ...grpc.CallOption) (*QueryChannelConsensusStateResponse, error) { + out := new(QueryChannelConsensusStateResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Query/ChannelConsensusState", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) PacketCommitment(ctx context.Context, in *QueryPacketCommitmentRequest, opts ...grpc.CallOption) (*QueryPacketCommitmentResponse, error) { + out := new(QueryPacketCommitmentResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Query/PacketCommitment", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) PacketCommitments(ctx context.Context, in *QueryPacketCommitmentsRequest, opts ...grpc.CallOption) (*QueryPacketCommitmentsResponse, error) { + out := new(QueryPacketCommitmentsResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Query/PacketCommitments", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) PacketReceipt(ctx context.Context, in *QueryPacketReceiptRequest, opts ...grpc.CallOption) (*QueryPacketReceiptResponse, error) { + out := new(QueryPacketReceiptResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Query/PacketReceipt", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) PacketAcknowledgement(ctx context.Context, in *QueryPacketAcknowledgementRequest, opts ...grpc.CallOption) (*QueryPacketAcknowledgementResponse, error) { + out := new(QueryPacketAcknowledgementResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Query/PacketAcknowledgement", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) PacketAcknowledgements(ctx context.Context, in *QueryPacketAcknowledgementsRequest, opts ...grpc.CallOption) (*QueryPacketAcknowledgementsResponse, error) { + out := new(QueryPacketAcknowledgementsResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Query/PacketAcknowledgements", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) UnreceivedPackets(ctx context.Context, in *QueryUnreceivedPacketsRequest, opts ...grpc.CallOption) (*QueryUnreceivedPacketsResponse, error) { + out := new(QueryUnreceivedPacketsResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Query/UnreceivedPackets", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) UnreceivedAcks(ctx context.Context, in *QueryUnreceivedAcksRequest, opts ...grpc.CallOption) (*QueryUnreceivedAcksResponse, error) { + out := new(QueryUnreceivedAcksResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Query/UnreceivedAcks", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) NextSequenceReceive(ctx context.Context, in *QueryNextSequenceReceiveRequest, opts ...grpc.CallOption) (*QueryNextSequenceReceiveResponse, error) { + out := new(QueryNextSequenceReceiveResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Query/NextSequenceReceive", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) NextSequenceSend(ctx context.Context, in *QueryNextSequenceSendRequest, opts ...grpc.CallOption) (*QueryNextSequenceSendResponse, error) { + out := new(QueryNextSequenceSendResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Query/NextSequenceSend", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // Channel queries an IBC Channel. + Channel(context.Context, *QueryChannelRequest) (*QueryChannelResponse, error) + // Channels queries all the IBC channels of a chain. + Channels(context.Context, *QueryChannelsRequest) (*QueryChannelsResponse, error) + // ConnectionChannels queries all the channels associated with a connection + // end. + ConnectionChannels(context.Context, *QueryConnectionChannelsRequest) (*QueryConnectionChannelsResponse, error) + // ChannelClientState queries for the client state for the channel associated + // with the provided channel identifiers. + ChannelClientState(context.Context, *QueryChannelClientStateRequest) (*QueryChannelClientStateResponse, error) + // ChannelConsensusState queries for the consensus state for the channel + // associated with the provided channel identifiers. + ChannelConsensusState(context.Context, *QueryChannelConsensusStateRequest) (*QueryChannelConsensusStateResponse, error) + // PacketCommitment queries a stored packet commitment hash. + PacketCommitment(context.Context, *QueryPacketCommitmentRequest) (*QueryPacketCommitmentResponse, error) + // PacketCommitments returns all the packet commitments hashes associated + // with a channel. + PacketCommitments(context.Context, *QueryPacketCommitmentsRequest) (*QueryPacketCommitmentsResponse, error) + // PacketReceipt queries if a given packet sequence has been received on the + // queried chain + PacketReceipt(context.Context, *QueryPacketReceiptRequest) (*QueryPacketReceiptResponse, error) + // PacketAcknowledgement queries a stored packet acknowledgement hash. + PacketAcknowledgement(context.Context, *QueryPacketAcknowledgementRequest) (*QueryPacketAcknowledgementResponse, error) + // PacketAcknowledgements returns all the packet acknowledgements associated + // with a channel. + PacketAcknowledgements(context.Context, *QueryPacketAcknowledgementsRequest) (*QueryPacketAcknowledgementsResponse, error) + // UnreceivedPackets returns all the unreceived IBC packets associated with a + // channel and sequences. + UnreceivedPackets(context.Context, *QueryUnreceivedPacketsRequest) (*QueryUnreceivedPacketsResponse, error) + // UnreceivedAcks returns all the unreceived IBC acknowledgements associated + // with a channel and sequences. + UnreceivedAcks(context.Context, *QueryUnreceivedAcksRequest) (*QueryUnreceivedAcksResponse, error) + // NextSequenceReceive returns the next receive sequence for a given channel. + NextSequenceReceive(context.Context, *QueryNextSequenceReceiveRequest) (*QueryNextSequenceReceiveResponse, error) + // NextSequenceSend returns the next send sequence for a given channel. + NextSequenceSend(context.Context, *QueryNextSequenceSendRequest) (*QueryNextSequenceSendResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) Channel(ctx context.Context, req *QueryChannelRequest) (*QueryChannelResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Channel not implemented") +} +func (*UnimplementedQueryServer) Channels(ctx context.Context, req *QueryChannelsRequest) (*QueryChannelsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Channels not implemented") +} +func (*UnimplementedQueryServer) ConnectionChannels(ctx context.Context, req *QueryConnectionChannelsRequest) (*QueryConnectionChannelsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ConnectionChannels not implemented") +} +func (*UnimplementedQueryServer) ChannelClientState(ctx context.Context, req *QueryChannelClientStateRequest) (*QueryChannelClientStateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ChannelClientState not implemented") +} +func (*UnimplementedQueryServer) ChannelConsensusState(ctx context.Context, req *QueryChannelConsensusStateRequest) (*QueryChannelConsensusStateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ChannelConsensusState not implemented") +} +func (*UnimplementedQueryServer) PacketCommitment(ctx context.Context, req *QueryPacketCommitmentRequest) (*QueryPacketCommitmentResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PacketCommitment not implemented") +} +func (*UnimplementedQueryServer) PacketCommitments(ctx context.Context, req *QueryPacketCommitmentsRequest) (*QueryPacketCommitmentsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PacketCommitments not implemented") +} +func (*UnimplementedQueryServer) PacketReceipt(ctx context.Context, req *QueryPacketReceiptRequest) (*QueryPacketReceiptResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PacketReceipt not implemented") +} +func (*UnimplementedQueryServer) PacketAcknowledgement(ctx context.Context, req *QueryPacketAcknowledgementRequest) (*QueryPacketAcknowledgementResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PacketAcknowledgement not implemented") +} +func (*UnimplementedQueryServer) PacketAcknowledgements(ctx context.Context, req *QueryPacketAcknowledgementsRequest) (*QueryPacketAcknowledgementsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PacketAcknowledgements not implemented") +} +func (*UnimplementedQueryServer) UnreceivedPackets(ctx context.Context, req *QueryUnreceivedPacketsRequest) (*QueryUnreceivedPacketsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UnreceivedPackets not implemented") +} +func (*UnimplementedQueryServer) UnreceivedAcks(ctx context.Context, req *QueryUnreceivedAcksRequest) (*QueryUnreceivedAcksResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UnreceivedAcks not implemented") +} +func (*UnimplementedQueryServer) NextSequenceReceive(ctx context.Context, req *QueryNextSequenceReceiveRequest) (*QueryNextSequenceReceiveResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method NextSequenceReceive not implemented") +} +func (*UnimplementedQueryServer) NextSequenceSend(ctx context.Context, req *QueryNextSequenceSendRequest) (*QueryNextSequenceSendResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method NextSequenceSend not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_Channel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryChannelRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Channel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Query/Channel", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Channel(ctx, req.(*QueryChannelRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_Channels_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryChannelsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Channels(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Query/Channels", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Channels(ctx, req.(*QueryChannelsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_ConnectionChannels_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryConnectionChannelsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).ConnectionChannels(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Query/ConnectionChannels", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).ConnectionChannels(ctx, req.(*QueryConnectionChannelsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_ChannelClientState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryChannelClientStateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).ChannelClientState(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Query/ChannelClientState", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).ChannelClientState(ctx, req.(*QueryChannelClientStateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_ChannelConsensusState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryChannelConsensusStateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).ChannelConsensusState(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Query/ChannelConsensusState", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).ChannelConsensusState(ctx, req.(*QueryChannelConsensusStateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_PacketCommitment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryPacketCommitmentRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).PacketCommitment(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Query/PacketCommitment", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).PacketCommitment(ctx, req.(*QueryPacketCommitmentRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_PacketCommitments_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryPacketCommitmentsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).PacketCommitments(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Query/PacketCommitments", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).PacketCommitments(ctx, req.(*QueryPacketCommitmentsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_PacketReceipt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryPacketReceiptRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).PacketReceipt(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Query/PacketReceipt", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).PacketReceipt(ctx, req.(*QueryPacketReceiptRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_PacketAcknowledgement_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryPacketAcknowledgementRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).PacketAcknowledgement(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Query/PacketAcknowledgement", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).PacketAcknowledgement(ctx, req.(*QueryPacketAcknowledgementRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_PacketAcknowledgements_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryPacketAcknowledgementsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).PacketAcknowledgements(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Query/PacketAcknowledgements", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).PacketAcknowledgements(ctx, req.(*QueryPacketAcknowledgementsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_UnreceivedPackets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryUnreceivedPacketsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).UnreceivedPackets(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Query/UnreceivedPackets", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).UnreceivedPackets(ctx, req.(*QueryUnreceivedPacketsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_UnreceivedAcks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryUnreceivedAcksRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).UnreceivedAcks(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Query/UnreceivedAcks", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).UnreceivedAcks(ctx, req.(*QueryUnreceivedAcksRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_NextSequenceReceive_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryNextSequenceReceiveRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).NextSequenceReceive(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Query/NextSequenceReceive", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).NextSequenceReceive(ctx, req.(*QueryNextSequenceReceiveRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_NextSequenceSend_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryNextSequenceSendRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).NextSequenceSend(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Query/NextSequenceSend", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).NextSequenceSend(ctx, req.(*QueryNextSequenceSendRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ibc.core.channel.v1.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Channel", + Handler: _Query_Channel_Handler, + }, + { + MethodName: "Channels", + Handler: _Query_Channels_Handler, + }, + { + MethodName: "ConnectionChannels", + Handler: _Query_ConnectionChannels_Handler, + }, + { + MethodName: "ChannelClientState", + Handler: _Query_ChannelClientState_Handler, + }, + { + MethodName: "ChannelConsensusState", + Handler: _Query_ChannelConsensusState_Handler, + }, + { + MethodName: "PacketCommitment", + Handler: _Query_PacketCommitment_Handler, + }, + { + MethodName: "PacketCommitments", + Handler: _Query_PacketCommitments_Handler, + }, + { + MethodName: "PacketReceipt", + Handler: _Query_PacketReceipt_Handler, + }, + { + MethodName: "PacketAcknowledgement", + Handler: _Query_PacketAcknowledgement_Handler, + }, + { + MethodName: "PacketAcknowledgements", + Handler: _Query_PacketAcknowledgements_Handler, + }, + { + MethodName: "UnreceivedPackets", + Handler: _Query_UnreceivedPackets_Handler, + }, + { + MethodName: "UnreceivedAcks", + Handler: _Query_UnreceivedAcks_Handler, + }, + { + MethodName: "NextSequenceReceive", + Handler: _Query_NextSequenceReceive_Handler, + }, + { + MethodName: "NextSequenceSend", + Handler: _Query_NextSequenceSend_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "ibc/core/channel/v1/query.proto", +} + +func (m *QueryChannelRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryChannelRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryChannelRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryChannelResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryChannelResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryChannelResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.Proof) > 0 { + i -= len(m.Proof) + copy(dAtA[i:], m.Proof) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Proof))) + i-- + dAtA[i] = 0x12 + } + if m.Channel != nil { + { + size, err := m.Channel.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryChannelsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryChannelsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryChannelsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryChannelsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryChannelsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryChannelsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Height.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Channels) > 0 { + for iNdEx := len(m.Channels) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Channels[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *QueryConnectionChannelsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryConnectionChannelsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryConnectionChannelsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Connection) > 0 { + i -= len(m.Connection) + copy(dAtA[i:], m.Connection) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Connection))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryConnectionChannelsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryConnectionChannelsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryConnectionChannelsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Height.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Channels) > 0 { + for iNdEx := len(m.Channels) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Channels[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *QueryChannelClientStateRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryChannelClientStateRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryChannelClientStateRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryChannelClientStateResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryChannelClientStateResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryChannelClientStateResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.Proof) > 0 { + i -= len(m.Proof) + copy(dAtA[i:], m.Proof) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Proof))) + i-- + dAtA[i] = 0x12 + } + if m.IdentifiedClientState != nil { + { + size, err := m.IdentifiedClientState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryChannelConsensusStateRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryChannelConsensusStateRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryChannelConsensusStateRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.RevisionHeight != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.RevisionHeight)) + i-- + dAtA[i] = 0x20 + } + if m.RevisionNumber != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.RevisionNumber)) + i-- + dAtA[i] = 0x18 + } + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryChannelConsensusStateResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryChannelConsensusStateResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryChannelConsensusStateResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + if len(m.Proof) > 0 { + i -= len(m.Proof) + copy(dAtA[i:], m.Proof) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Proof))) + i-- + dAtA[i] = 0x1a + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0x12 + } + if m.ConsensusState != nil { + { + size, err := m.ConsensusState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryPacketCommitmentRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPacketCommitmentRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPacketCommitmentRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Sequence != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x18 + } + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryPacketCommitmentResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPacketCommitmentResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPacketCommitmentResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.Proof) > 0 { + i -= len(m.Proof) + copy(dAtA[i:], m.Proof) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Proof))) + i-- + dAtA[i] = 0x12 + } + if len(m.Commitment) > 0 { + i -= len(m.Commitment) + copy(dAtA[i:], m.Commitment) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Commitment))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryPacketCommitmentsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPacketCommitmentsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPacketCommitmentsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryPacketCommitmentsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPacketCommitmentsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPacketCommitmentsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Height.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Commitments) > 0 { + for iNdEx := len(m.Commitments) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Commitments[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *QueryPacketReceiptRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPacketReceiptRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPacketReceiptRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Sequence != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x18 + } + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryPacketReceiptResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPacketReceiptResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPacketReceiptResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + if len(m.Proof) > 0 { + i -= len(m.Proof) + copy(dAtA[i:], m.Proof) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Proof))) + i-- + dAtA[i] = 0x1a + } + if m.Received { + i-- + if m.Received { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x10 + } + return len(dAtA) - i, nil +} + +func (m *QueryPacketAcknowledgementRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPacketAcknowledgementRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPacketAcknowledgementRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Sequence != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x18 + } + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryPacketAcknowledgementResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPacketAcknowledgementResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPacketAcknowledgementResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.Proof) > 0 { + i -= len(m.Proof) + copy(dAtA[i:], m.Proof) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Proof))) + i-- + dAtA[i] = 0x12 + } + if len(m.Acknowledgement) > 0 { + i -= len(m.Acknowledgement) + copy(dAtA[i:], m.Acknowledgement) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Acknowledgement))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryPacketAcknowledgementsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPacketAcknowledgementsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPacketAcknowledgementsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.PacketCommitmentSequences) > 0 { + dAtA20 := make([]byte, len(m.PacketCommitmentSequences)*10) + var j19 int + for _, num := range m.PacketCommitmentSequences { + for num >= 1<<7 { + dAtA20[j19] = uint8(uint64(num)&0x7f | 0x80) + num >>= 7 + j19++ + } + dAtA20[j19] = uint8(num) + j19++ + } + i -= j19 + copy(dAtA[i:], dAtA20[:j19]) + i = encodeVarintQuery(dAtA, i, uint64(j19)) + i-- + dAtA[i] = 0x22 + } + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryPacketAcknowledgementsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPacketAcknowledgementsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPacketAcknowledgementsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Height.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Acknowledgements) > 0 { + for iNdEx := len(m.Acknowledgements) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Acknowledgements[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *QueryUnreceivedPacketsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryUnreceivedPacketsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryUnreceivedPacketsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.PacketCommitmentSequences) > 0 { + dAtA25 := make([]byte, len(m.PacketCommitmentSequences)*10) + var j24 int + for _, num := range m.PacketCommitmentSequences { + for num >= 1<<7 { + dAtA25[j24] = uint8(uint64(num)&0x7f | 0x80) + num >>= 7 + j24++ + } + dAtA25[j24] = uint8(num) + j24++ + } + i -= j24 + copy(dAtA[i:], dAtA25[:j24]) + i = encodeVarintQuery(dAtA, i, uint64(j24)) + i-- + dAtA[i] = 0x1a + } + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryUnreceivedPacketsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryUnreceivedPacketsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryUnreceivedPacketsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Height.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Sequences) > 0 { + dAtA28 := make([]byte, len(m.Sequences)*10) + var j27 int + for _, num := range m.Sequences { + for num >= 1<<7 { + dAtA28[j27] = uint8(uint64(num)&0x7f | 0x80) + num >>= 7 + j27++ + } + dAtA28[j27] = uint8(num) + j27++ + } + i -= j27 + copy(dAtA[i:], dAtA28[:j27]) + i = encodeVarintQuery(dAtA, i, uint64(j27)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryUnreceivedAcksRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryUnreceivedAcksRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryUnreceivedAcksRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.PacketAckSequences) > 0 { + dAtA30 := make([]byte, len(m.PacketAckSequences)*10) + var j29 int + for _, num := range m.PacketAckSequences { + for num >= 1<<7 { + dAtA30[j29] = uint8(uint64(num)&0x7f | 0x80) + num >>= 7 + j29++ + } + dAtA30[j29] = uint8(num) + j29++ + } + i -= j29 + copy(dAtA[i:], dAtA30[:j29]) + i = encodeVarintQuery(dAtA, i, uint64(j29)) + i-- + dAtA[i] = 0x1a + } + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryUnreceivedAcksResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryUnreceivedAcksResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryUnreceivedAcksResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Height.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Sequences) > 0 { + dAtA33 := make([]byte, len(m.Sequences)*10) + var j32 int + for _, num := range m.Sequences { + for num >= 1<<7 { + dAtA33[j32] = uint8(uint64(num)&0x7f | 0x80) + num >>= 7 + j32++ + } + dAtA33[j32] = uint8(num) + j32++ + } + i -= j32 + copy(dAtA[i:], dAtA33[:j32]) + i = encodeVarintQuery(dAtA, i, uint64(j32)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryNextSequenceReceiveRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryNextSequenceReceiveRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryNextSequenceReceiveRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryNextSequenceReceiveResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryNextSequenceReceiveResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryNextSequenceReceiveResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.Proof) > 0 { + i -= len(m.Proof) + copy(dAtA[i:], m.Proof) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Proof))) + i-- + dAtA[i] = 0x12 + } + if m.NextSequenceReceive != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.NextSequenceReceive)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *QueryNextSequenceSendRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryNextSequenceSendRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryNextSequenceSendRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryNextSequenceSendResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryNextSequenceSendResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryNextSequenceSendResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.Proof) > 0 { + i -= len(m.Proof) + copy(dAtA[i:], m.Proof) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Proof))) + i-- + dAtA[i] = 0x12 + } + if m.NextSequenceSend != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.NextSequenceSend)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryChannelRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryChannelResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Channel != nil { + l = m.Channel.Size() + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.Proof) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryChannelsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryChannelsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Channels) > 0 { + for _, e := range m.Channels { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + l = m.Height.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryConnectionChannelsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Connection) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryConnectionChannelsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Channels) > 0 { + for _, e := range m.Channels { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + l = m.Height.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryChannelClientStateRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryChannelClientStateResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.IdentifiedClientState != nil { + l = m.IdentifiedClientState.Size() + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.Proof) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryChannelConsensusStateRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.RevisionNumber != 0 { + n += 1 + sovQuery(uint64(m.RevisionNumber)) + } + if m.RevisionHeight != 0 { + n += 1 + sovQuery(uint64(m.RevisionHeight)) + } + return n +} + +func (m *QueryChannelConsensusStateResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ConsensusState != nil { + l = m.ConsensusState.Size() + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.Proof) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryPacketCommitmentRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.Sequence != 0 { + n += 1 + sovQuery(uint64(m.Sequence)) + } + return n +} + +func (m *QueryPacketCommitmentResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Commitment) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.Proof) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryPacketCommitmentsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryPacketCommitmentsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Commitments) > 0 { + for _, e := range m.Commitments { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + l = m.Height.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryPacketReceiptRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.Sequence != 0 { + n += 1 + sovQuery(uint64(m.Sequence)) + } + return n +} + +func (m *QueryPacketReceiptResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Received { + n += 2 + } + l = len(m.Proof) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryPacketAcknowledgementRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.Sequence != 0 { + n += 1 + sovQuery(uint64(m.Sequence)) + } + return n +} + +func (m *QueryPacketAcknowledgementResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Acknowledgement) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.Proof) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryPacketAcknowledgementsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + if len(m.PacketCommitmentSequences) > 0 { + l = 0 + for _, e := range m.PacketCommitmentSequences { + l += sovQuery(uint64(e)) + } + n += 1 + sovQuery(uint64(l)) + l + } + return n +} + +func (m *QueryPacketAcknowledgementsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Acknowledgements) > 0 { + for _, e := range m.Acknowledgements { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + l = m.Height.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryUnreceivedPacketsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if len(m.PacketCommitmentSequences) > 0 { + l = 0 + for _, e := range m.PacketCommitmentSequences { + l += sovQuery(uint64(e)) + } + n += 1 + sovQuery(uint64(l)) + l + } + return n +} + +func (m *QueryUnreceivedPacketsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Sequences) > 0 { + l = 0 + for _, e := range m.Sequences { + l += sovQuery(uint64(e)) + } + n += 1 + sovQuery(uint64(l)) + l + } + l = m.Height.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryUnreceivedAcksRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if len(m.PacketAckSequences) > 0 { + l = 0 + for _, e := range m.PacketAckSequences { + l += sovQuery(uint64(e)) + } + n += 1 + sovQuery(uint64(l)) + l + } + return n +} + +func (m *QueryUnreceivedAcksResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Sequences) > 0 { + l = 0 + for _, e := range m.Sequences { + l += sovQuery(uint64(e)) + } + n += 1 + sovQuery(uint64(l)) + l + } + l = m.Height.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryNextSequenceReceiveRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryNextSequenceReceiveResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.NextSequenceReceive != 0 { + n += 1 + sovQuery(uint64(m.NextSequenceReceive)) + } + l = len(m.Proof) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryNextSequenceSendRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryNextSequenceSendResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.NextSequenceSend != 0 { + n += 1 + sovQuery(uint64(m.NextSequenceSend)) + } + l = len(m.Proof) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryChannelRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryChannelRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryChannelRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryChannelResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryChannelResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryChannelResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Channel", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Channel == nil { + m.Channel = &Channel{} + } + if err := m.Channel.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proof", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Proof = append(m.Proof[:0], dAtA[iNdEx:postIndex]...) + if m.Proof == nil { + m.Proof = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryChannelsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryChannelsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryChannelsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryChannelsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryChannelsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryChannelsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Channels", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Channels = append(m.Channels, &IdentifiedChannel{}) + if err := m.Channels[len(m.Channels)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Height.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryConnectionChannelsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryConnectionChannelsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryConnectionChannelsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Connection", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Connection = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryConnectionChannelsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryConnectionChannelsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryConnectionChannelsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Channels", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Channels = append(m.Channels, &IdentifiedChannel{}) + if err := m.Channels[len(m.Channels)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Height.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryChannelClientStateRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryChannelClientStateRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryChannelClientStateRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryChannelClientStateResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryChannelClientStateResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryChannelClientStateResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field IdentifiedClientState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.IdentifiedClientState == nil { + m.IdentifiedClientState = &types.IdentifiedClientState{} + } + if err := m.IdentifiedClientState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proof", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Proof = append(m.Proof[:0], dAtA[iNdEx:postIndex]...) + if m.Proof == nil { + m.Proof = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryChannelConsensusStateRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryChannelConsensusStateRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryChannelConsensusStateRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RevisionNumber", wireType) + } + m.RevisionNumber = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.RevisionNumber |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RevisionHeight", wireType) + } + m.RevisionHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.RevisionHeight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryChannelConsensusStateResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryChannelConsensusStateResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryChannelConsensusStateResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConsensusState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ConsensusState == nil { + m.ConsensusState = &types1.Any{} + } + if err := m.ConsensusState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proof", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Proof = append(m.Proof[:0], dAtA[iNdEx:postIndex]...) + if m.Proof == nil { + m.Proof = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPacketCommitmentRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPacketCommitmentRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPacketCommitmentRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPacketCommitmentResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPacketCommitmentResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPacketCommitmentResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Commitment", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Commitment = append(m.Commitment[:0], dAtA[iNdEx:postIndex]...) + if m.Commitment == nil { + m.Commitment = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proof", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Proof = append(m.Proof[:0], dAtA[iNdEx:postIndex]...) + if m.Proof == nil { + m.Proof = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPacketCommitmentsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPacketCommitmentsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPacketCommitmentsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPacketCommitmentsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPacketCommitmentsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPacketCommitmentsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Commitments", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Commitments = append(m.Commitments, &PacketState{}) + if err := m.Commitments[len(m.Commitments)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Height.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPacketReceiptRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPacketReceiptRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPacketReceiptRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPacketReceiptResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPacketReceiptResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPacketReceiptResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Received", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Received = bool(v != 0) + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proof", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Proof = append(m.Proof[:0], dAtA[iNdEx:postIndex]...) + if m.Proof == nil { + m.Proof = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPacketAcknowledgementRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPacketAcknowledgementRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPacketAcknowledgementRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPacketAcknowledgementResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPacketAcknowledgementResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPacketAcknowledgementResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Acknowledgement", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Acknowledgement = append(m.Acknowledgement[:0], dAtA[iNdEx:postIndex]...) + if m.Acknowledgement == nil { + m.Acknowledgement = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proof", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Proof = append(m.Proof[:0], dAtA[iNdEx:postIndex]...) + if m.Proof == nil { + m.Proof = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPacketAcknowledgementsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPacketAcknowledgementsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPacketAcknowledgementsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType == 0 { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.PacketCommitmentSequences = append(m.PacketCommitmentSequences, v) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + var count int + for _, integer := range dAtA[iNdEx:postIndex] { + if integer < 128 { + count++ + } + } + elementCount = count + if elementCount != 0 && len(m.PacketCommitmentSequences) == 0 { + m.PacketCommitmentSequences = make([]uint64, 0, elementCount) + } + for iNdEx < postIndex { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.PacketCommitmentSequences = append(m.PacketCommitmentSequences, v) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field PacketCommitmentSequences", wireType) + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPacketAcknowledgementsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPacketAcknowledgementsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPacketAcknowledgementsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Acknowledgements", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Acknowledgements = append(m.Acknowledgements, &PacketState{}) + if err := m.Acknowledgements[len(m.Acknowledgements)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Height.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryUnreceivedPacketsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryUnreceivedPacketsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryUnreceivedPacketsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType == 0 { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.PacketCommitmentSequences = append(m.PacketCommitmentSequences, v) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + var count int + for _, integer := range dAtA[iNdEx:postIndex] { + if integer < 128 { + count++ + } + } + elementCount = count + if elementCount != 0 && len(m.PacketCommitmentSequences) == 0 { + m.PacketCommitmentSequences = make([]uint64, 0, elementCount) + } + for iNdEx < postIndex { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.PacketCommitmentSequences = append(m.PacketCommitmentSequences, v) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field PacketCommitmentSequences", wireType) + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryUnreceivedPacketsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryUnreceivedPacketsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryUnreceivedPacketsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType == 0 { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Sequences = append(m.Sequences, v) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + var count int + for _, integer := range dAtA[iNdEx:postIndex] { + if integer < 128 { + count++ + } + } + elementCount = count + if elementCount != 0 && len(m.Sequences) == 0 { + m.Sequences = make([]uint64, 0, elementCount) + } + for iNdEx < postIndex { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Sequences = append(m.Sequences, v) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field Sequences", wireType) + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Height.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryUnreceivedAcksRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryUnreceivedAcksRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryUnreceivedAcksRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType == 0 { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.PacketAckSequences = append(m.PacketAckSequences, v) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + var count int + for _, integer := range dAtA[iNdEx:postIndex] { + if integer < 128 { + count++ + } + } + elementCount = count + if elementCount != 0 && len(m.PacketAckSequences) == 0 { + m.PacketAckSequences = make([]uint64, 0, elementCount) + } + for iNdEx < postIndex { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.PacketAckSequences = append(m.PacketAckSequences, v) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field PacketAckSequences", wireType) + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryUnreceivedAcksResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryUnreceivedAcksResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryUnreceivedAcksResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType == 0 { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Sequences = append(m.Sequences, v) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + var count int + for _, integer := range dAtA[iNdEx:postIndex] { + if integer < 128 { + count++ + } + } + elementCount = count + if elementCount != 0 && len(m.Sequences) == 0 { + m.Sequences = make([]uint64, 0, elementCount) + } + for iNdEx < postIndex { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Sequences = append(m.Sequences, v) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field Sequences", wireType) + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Height.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryNextSequenceReceiveRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryNextSequenceReceiveRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryNextSequenceReceiveRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryNextSequenceReceiveResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryNextSequenceReceiveResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryNextSequenceReceiveResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NextSequenceReceive", wireType) + } + m.NextSequenceReceive = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.NextSequenceReceive |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proof", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Proof = append(m.Proof[:0], dAtA[iNdEx:postIndex]...) + if m.Proof == nil { + m.Proof = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryNextSequenceSendRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryNextSequenceSendRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryNextSequenceSendRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryNextSequenceSendResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryNextSequenceSendResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryNextSequenceSendResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NextSequenceSend", wireType) + } + m.NextSequenceSend = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.NextSequenceSend |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proof", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Proof = append(m.Proof[:0], dAtA[iNdEx:postIndex]...) + if m.Proof == nil { + m.Proof = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/04-channel/types/query.pb.gw.go b/modules/core/04-channel/types/query.pb.gw.go new file mode 100644 index 0000000..84efac9 --- /dev/null +++ b/modules/core/04-channel/types/query.pb.gw.go @@ -0,0 +1,1956 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: ibc/core/channel/v1/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +func request_Query_Channel_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryChannelRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + msg, err := client.Channel(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Channel_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryChannelRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + msg, err := server.Channel(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Query_Channels_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Query_Channels_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryChannelsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Channels_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Channels(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Channels_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryChannelsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Channels_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Channels(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Query_ConnectionChannels_0 = &utilities.DoubleArray{Encoding: map[string]int{"connection": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} +) + +func request_Query_ConnectionChannels_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryConnectionChannelsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["connection"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "connection") + } + + protoReq.Connection, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "connection", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ConnectionChannels_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ConnectionChannels(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_ConnectionChannels_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryConnectionChannelsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["connection"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "connection") + } + + protoReq.Connection, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "connection", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ConnectionChannels_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ConnectionChannels(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_ChannelClientState_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryChannelClientStateRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + msg, err := client.ChannelClientState(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_ChannelClientState_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryChannelClientStateRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + msg, err := server.ChannelClientState(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_ChannelConsensusState_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryChannelConsensusStateRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + val, ok = pathParams["revision_number"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "revision_number") + } + + protoReq.RevisionNumber, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "revision_number", err) + } + + val, ok = pathParams["revision_height"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "revision_height") + } + + protoReq.RevisionHeight, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "revision_height", err) + } + + msg, err := client.ChannelConsensusState(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_ChannelConsensusState_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryChannelConsensusStateRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + val, ok = pathParams["revision_number"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "revision_number") + } + + protoReq.RevisionNumber, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "revision_number", err) + } + + val, ok = pathParams["revision_height"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "revision_height") + } + + protoReq.RevisionHeight, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "revision_height", err) + } + + msg, err := server.ChannelConsensusState(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_PacketCommitment_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPacketCommitmentRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + val, ok = pathParams["sequence"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "sequence") + } + + protoReq.Sequence, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "sequence", err) + } + + msg, err := client.PacketCommitment(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_PacketCommitment_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPacketCommitmentRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + val, ok = pathParams["sequence"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "sequence") + } + + protoReq.Sequence, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "sequence", err) + } + + msg, err := server.PacketCommitment(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Query_PacketCommitments_0 = &utilities.DoubleArray{Encoding: map[string]int{"channel_id": 0, "port_id": 1}, Base: []int{1, 1, 2, 0, 0}, Check: []int{0, 1, 1, 2, 3}} +) + +func request_Query_PacketCommitments_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPacketCommitmentsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_PacketCommitments_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.PacketCommitments(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_PacketCommitments_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPacketCommitmentsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_PacketCommitments_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.PacketCommitments(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_PacketReceipt_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPacketReceiptRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + val, ok = pathParams["sequence"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "sequence") + } + + protoReq.Sequence, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "sequence", err) + } + + msg, err := client.PacketReceipt(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_PacketReceipt_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPacketReceiptRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + val, ok = pathParams["sequence"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "sequence") + } + + protoReq.Sequence, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "sequence", err) + } + + msg, err := server.PacketReceipt(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_PacketAcknowledgement_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPacketAcknowledgementRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + val, ok = pathParams["sequence"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "sequence") + } + + protoReq.Sequence, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "sequence", err) + } + + msg, err := client.PacketAcknowledgement(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_PacketAcknowledgement_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPacketAcknowledgementRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + val, ok = pathParams["sequence"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "sequence") + } + + protoReq.Sequence, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "sequence", err) + } + + msg, err := server.PacketAcknowledgement(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Query_PacketAcknowledgements_0 = &utilities.DoubleArray{Encoding: map[string]int{"channel_id": 0, "port_id": 1}, Base: []int{1, 1, 2, 0, 0}, Check: []int{0, 1, 1, 2, 3}} +) + +func request_Query_PacketAcknowledgements_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPacketAcknowledgementsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_PacketAcknowledgements_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.PacketAcknowledgements(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_PacketAcknowledgements_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPacketAcknowledgementsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_PacketAcknowledgements_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.PacketAcknowledgements(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_UnreceivedPackets_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryUnreceivedPacketsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + val, ok = pathParams["packet_commitment_sequences"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "packet_commitment_sequences") + } + + protoReq.PacketCommitmentSequences, err = runtime.Uint64Slice(val, ",") + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "packet_commitment_sequences", err) + } + + msg, err := client.UnreceivedPackets(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_UnreceivedPackets_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryUnreceivedPacketsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + val, ok = pathParams["packet_commitment_sequences"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "packet_commitment_sequences") + } + + protoReq.PacketCommitmentSequences, err = runtime.Uint64Slice(val, ",") + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "packet_commitment_sequences", err) + } + + msg, err := server.UnreceivedPackets(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_UnreceivedAcks_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryUnreceivedAcksRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + val, ok = pathParams["packet_ack_sequences"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "packet_ack_sequences") + } + + protoReq.PacketAckSequences, err = runtime.Uint64Slice(val, ",") + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "packet_ack_sequences", err) + } + + msg, err := client.UnreceivedAcks(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_UnreceivedAcks_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryUnreceivedAcksRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + val, ok = pathParams["packet_ack_sequences"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "packet_ack_sequences") + } + + protoReq.PacketAckSequences, err = runtime.Uint64Slice(val, ",") + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "packet_ack_sequences", err) + } + + msg, err := server.UnreceivedAcks(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_NextSequenceReceive_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryNextSequenceReceiveRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + msg, err := client.NextSequenceReceive(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_NextSequenceReceive_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryNextSequenceReceiveRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + msg, err := server.NextSequenceReceive(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_NextSequenceSend_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryNextSequenceSendRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + msg, err := client.NextSequenceSend(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_NextSequenceSend_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryNextSequenceSendRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["channel_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "channel_id") + } + + protoReq.ChannelId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "channel_id", err) + } + + val, ok = pathParams["port_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port_id") + } + + protoReq.PortId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port_id", err) + } + + msg, err := server.NextSequenceSend(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_Channel_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Channel_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Channel_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Channels_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Channels_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Channels_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ConnectionChannels_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_ConnectionChannels_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ConnectionChannels_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ChannelClientState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_ChannelClientState_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ChannelClientState_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ChannelConsensusState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_ChannelConsensusState_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ChannelConsensusState_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PacketCommitment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_PacketCommitment_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PacketCommitment_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PacketCommitments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_PacketCommitments_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PacketCommitments_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PacketReceipt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_PacketReceipt_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PacketReceipt_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PacketAcknowledgement_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_PacketAcknowledgement_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PacketAcknowledgement_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PacketAcknowledgements_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_PacketAcknowledgements_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PacketAcknowledgements_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_UnreceivedPackets_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_UnreceivedPackets_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_UnreceivedPackets_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_UnreceivedAcks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_UnreceivedAcks_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_UnreceivedAcks_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_NextSequenceReceive_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_NextSequenceReceive_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_NextSequenceReceive_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_NextSequenceSend_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_NextSequenceSend_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_NextSequenceSend_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_Channel_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Channel_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Channel_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Channels_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Channels_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Channels_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ConnectionChannels_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_ConnectionChannels_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ConnectionChannels_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ChannelClientState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_ChannelClientState_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ChannelClientState_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_ChannelConsensusState_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_ChannelConsensusState_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ChannelConsensusState_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PacketCommitment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_PacketCommitment_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PacketCommitment_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PacketCommitments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_PacketCommitments_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PacketCommitments_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PacketReceipt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_PacketReceipt_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PacketReceipt_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PacketAcknowledgement_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_PacketAcknowledgement_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PacketAcknowledgement_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PacketAcknowledgements_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_PacketAcknowledgements_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PacketAcknowledgements_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_UnreceivedPackets_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_UnreceivedPackets_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_UnreceivedPackets_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_UnreceivedAcks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_UnreceivedAcks_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_UnreceivedAcks_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_NextSequenceReceive_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_NextSequenceReceive_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_NextSequenceReceive_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_NextSequenceSend_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_NextSequenceSend_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_NextSequenceSend_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_Channel_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 1, 0, 4, 1, 5, 7}, []string{"ibc", "core", "channel", "v1", "channels", "channel_id", "ports", "port_id"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_Channels_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"ibc", "core", "channel", "v1", "channels"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_ConnectionChannels_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6}, []string{"ibc", "core", "channel", "v1", "connections", "connection", "channels"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_ChannelClientState_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 1, 0, 4, 1, 5, 7, 2, 8}, []string{"ibc", "core", "channel", "v1", "channels", "channel_id", "ports", "port_id", "client_state"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_ChannelConsensusState_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 1, 0, 4, 1, 5, 7, 2, 8, 2, 9, 1, 0, 4, 1, 5, 10, 2, 11, 1, 0, 4, 1, 5, 12}, []string{"ibc", "core", "channel", "v1", "channels", "channel_id", "ports", "port_id", "consensus_state", "revision", "revision_number", "height", "revision_height"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_PacketCommitment_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 1, 0, 4, 1, 5, 7, 2, 8, 1, 0, 4, 1, 5, 9}, []string{"ibc", "core", "channel", "v1", "channels", "channel_id", "ports", "port_id", "packet_commitments", "sequence"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_PacketCommitments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 1, 0, 4, 1, 5, 7, 2, 8}, []string{"ibc", "core", "channel", "v1", "channels", "channel_id", "ports", "port_id", "packet_commitments"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_PacketReceipt_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 1, 0, 4, 1, 5, 7, 2, 8, 1, 0, 4, 1, 5, 9}, []string{"ibc", "core", "channel", "v1", "channels", "channel_id", "ports", "port_id", "packet_receipts", "sequence"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_PacketAcknowledgement_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 1, 0, 4, 1, 5, 7, 2, 8, 1, 0, 4, 1, 5, 9}, []string{"ibc", "core", "channel", "v1", "channels", "channel_id", "ports", "port_id", "packet_acks", "sequence"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_PacketAcknowledgements_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 1, 0, 4, 1, 5, 7, 2, 8}, []string{"ibc", "core", "channel", "v1", "channels", "channel_id", "ports", "port_id", "packet_acknowledgements"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_UnreceivedPackets_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 1, 0, 4, 1, 5, 7, 2, 8, 1, 0, 4, 1, 5, 9, 2, 10}, []string{"ibc", "core", "channel", "v1", "channels", "channel_id", "ports", "port_id", "packet_commitments", "packet_commitment_sequences", "unreceived_packets"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_UnreceivedAcks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 1, 0, 4, 1, 5, 7, 2, 8, 1, 0, 4, 1, 5, 9, 2, 10}, []string{"ibc", "core", "channel", "v1", "channels", "channel_id", "ports", "port_id", "packet_commitments", "packet_ack_sequences", "unreceived_acks"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_NextSequenceReceive_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 1, 0, 4, 1, 5, 7, 2, 8}, []string{"ibc", "core", "channel", "v1", "channels", "channel_id", "ports", "port_id", "next_sequence"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_NextSequenceSend_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 1, 0, 4, 1, 5, 7, 2, 8}, []string{"ibc", "core", "channel", "v1", "channels", "channel_id", "ports", "port_id", "next_sequence_send"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Query_Channel_0 = runtime.ForwardResponseMessage + + forward_Query_Channels_0 = runtime.ForwardResponseMessage + + forward_Query_ConnectionChannels_0 = runtime.ForwardResponseMessage + + forward_Query_ChannelClientState_0 = runtime.ForwardResponseMessage + + forward_Query_ChannelConsensusState_0 = runtime.ForwardResponseMessage + + forward_Query_PacketCommitment_0 = runtime.ForwardResponseMessage + + forward_Query_PacketCommitments_0 = runtime.ForwardResponseMessage + + forward_Query_PacketReceipt_0 = runtime.ForwardResponseMessage + + forward_Query_PacketAcknowledgement_0 = runtime.ForwardResponseMessage + + forward_Query_PacketAcknowledgements_0 = runtime.ForwardResponseMessage + + forward_Query_UnreceivedPackets_0 = runtime.ForwardResponseMessage + + forward_Query_UnreceivedAcks_0 = runtime.ForwardResponseMessage + + forward_Query_NextSequenceReceive_0 = runtime.ForwardResponseMessage + + forward_Query_NextSequenceSend_0 = runtime.ForwardResponseMessage +) diff --git a/modules/core/04-channel/types/timeout.go b/modules/core/04-channel/types/timeout.go new file mode 100644 index 0000000..ed5064f --- /dev/null +++ b/modules/core/04-channel/types/timeout.go @@ -0,0 +1,73 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" +) + +// NewTimeout returns a new Timeout instance. +func NewTimeout(height clienttypes.Height, timestamp uint64) Timeout { + return Timeout{ + Height: height, + Timestamp: timestamp, + } +} + +// NewTimeoutWithTimestamp creates a new Timeout with only the timestamp set. +func NewTimeoutWithTimestamp(timestamp uint64) Timeout { + return Timeout{ + Height: clienttypes.ZeroHeight(), + Timestamp: timestamp, + } +} + +// IsValid returns true if either the height or timestamp is non-zero. +func (t Timeout) IsValid() bool { + return !t.Height.IsZero() || t.Timestamp != 0 +} + +// Elapsed returns true if either the provided height or timestamp is past the +// respective absolute timeout values. +func (t Timeout) Elapsed(height clienttypes.Height, timestamp uint64) bool { + return t.heightElapsed(height) || t.timestampElapsed(timestamp) +} + +// TimestampElapsed returns true if the provided timestamp is past the timeout timestamp. +func (t Timeout) TimestampElapsed(timestamp uint64) bool { + return t.timestampElapsed(timestamp) +} + +// ErrTimeoutElapsed returns a timeout elapsed error indicating which timeout value +// has elapsed. +func (t Timeout) ErrTimeoutElapsed(height clienttypes.Height, timestamp uint64) error { + if t.heightElapsed(height) { + return errorsmod.Wrapf(ErrTimeoutElapsed, "current height: %s, timeout height %s", height, t.Height) + } + + return errorsmod.Wrapf(ErrTimeoutElapsed, "current timestamp: %d, timeout timestamp %d", timestamp, t.Timestamp) +} + +// ErrTimeoutNotReached returns a timeout not reached error indicating which timeout value +// has not been reached. +func (t Timeout) ErrTimeoutNotReached(height clienttypes.Height, timestamp uint64) error { + // only return height information if the height is set + // t.heightElapsed() will return false when it is empty + if !t.Height.IsZero() && !t.heightElapsed(height) { + return errorsmod.Wrapf(ErrTimeoutNotReached, "current height: %s, timeout height %s", height, t.Height) + } + + return errorsmod.Wrapf(ErrTimeoutNotReached, "current timestamp: %d, timeout timestamp %d", timestamp, t.Timestamp) +} + +// heightElapsed returns true if the timeout height is non empty +// and the timeout height is greater than or equal to the relative height. +func (t Timeout) heightElapsed(height clienttypes.Height) bool { + return !t.Height.IsZero() && height.GTE(t.Height) +} + +// timestampElapsed returns true if the timeout timestamp is non empty +// and the timeout timestamp is greater than or equal to the relative timestamp. +func (t Timeout) timestampElapsed(timestamp uint64) bool { + return t.Timestamp != 0 && timestamp >= t.Timestamp +} diff --git a/modules/core/04-channel/types/timeout_test.go b/modules/core/04-channel/types/timeout_test.go new file mode 100644 index 0000000..9027046 --- /dev/null +++ b/modules/core/04-channel/types/timeout_test.go @@ -0,0 +1,230 @@ +package types_test + +import ( + errorsmod "cosmossdk.io/errors" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" +) + +func (suite *TypesTestSuite) TestIsValid() { + var timeout types.Timeout + + testCases := []struct { + name string + malleate func() + isValid bool + }{ + { + "success: valid timeout with height and timestamp", + func() { + timeout = types.NewTimeout(clienttypes.NewHeight(1, 100), 100) + }, + true, + }, + { + "success: valid timeout with height and zero timestamp", + func() { + timeout = types.NewTimeout(clienttypes.NewHeight(1, 100), 0) + }, + true, + }, + { + "success: valid timeout with timestamp and zero height", + func() { + timeout = types.NewTimeout(clienttypes.ZeroHeight(), 100) + }, + true, + }, + { + "invalid timeout with zero height and zero timestamp", + func() { + timeout = types.NewTimeout(clienttypes.ZeroHeight(), 0) + }, + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + tc.malleate() + + isValid := timeout.IsValid() + suite.Require().Equal(tc.isValid, isValid) + }) + } +} + +func (suite *TypesTestSuite) TestElapsed() { + // elapsed is expected to be true when either timeout height or timestamp + // is greater than or equal to 2 + var ( + height = clienttypes.NewHeight(0, 2) + timestamp = uint64(2) + ) + + testCases := []struct { + name string + timeout types.Timeout + expElapsed bool + }{ + { + "elapsed: both timeout with height and timestamp", + types.NewTimeout(height, timestamp), + true, + }, + { + "elapsed: timeout with height and zero timestamp", + types.NewTimeout(height, 0), + true, + }, + { + "elapsed: timeout with timestamp and zero height", + types.NewTimeout(clienttypes.ZeroHeight(), timestamp), + true, + }, + { + "elapsed: height elapsed, timestamp did not", + types.NewTimeout(height, timestamp+1), + true, + }, + { + "elapsed: timestamp elapsed, height did not", + types.NewTimeout(height.Increment().(clienttypes.Height), timestamp), + true, + }, + { + "elapsed: timestamp elapsed when less than current timestamp", + types.NewTimeout(clienttypes.ZeroHeight(), timestamp-1), + true, + }, + { + "elapsed: height elapsed when less than current height", + types.NewTimeout(clienttypes.NewHeight(0, 1), 0), + true, + }, + { + "not elapsed: invalid timeout", + types.NewTimeout(clienttypes.ZeroHeight(), 0), + false, + }, + { + "not elapsed: neither height nor timeout elapsed", + types.NewTimeout(height.Increment().(clienttypes.Height), timestamp+1), + false, + }, + { + "not elapsed: timeout not reached with height and zero timestamp", + types.NewTimeout(height.Increment().(clienttypes.Height), 0), + false, + }, + { + "elapsed: timeout not reached with timestamp and zero height", + types.NewTimeout(clienttypes.ZeroHeight(), timestamp+1), + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + elapsed := tc.timeout.Elapsed(height, timestamp) + suite.Require().Equal(tc.expElapsed, elapsed) + }) + } +} + +func (suite *TypesTestSuite) TestErrTimeoutElapsed() { + // elapsed is expected to be true when either timeout height or timestamp + // is greater than or equal to 2 + var ( + height = clienttypes.NewHeight(0, 2) + timestamp = uint64(2) + ) + + testCases := []struct { + name string + timeout types.Timeout + expError error + }{ + { + "both timeout with height and timestamp", + types.NewTimeout(height, timestamp), + errorsmod.Wrapf(types.ErrTimeoutElapsed, "current height: %s, timeout height %s", height, height), + }, + { + "timeout with height and zero timestamp", + types.NewTimeout(height, 0), + errorsmod.Wrapf(types.ErrTimeoutElapsed, "current height: %s, timeout height %s", height, height), + }, + { + "timeout with timestamp and zero height", + types.NewTimeout(clienttypes.ZeroHeight(), timestamp), + errorsmod.Wrapf(types.ErrTimeoutElapsed, "current timestamp: %d, timeout timestamp %d", timestamp, timestamp), + }, + { + "height elapsed, timestamp did not", + types.NewTimeout(height, timestamp+1), + errorsmod.Wrapf(types.ErrTimeoutElapsed, "current height: %s, timeout height %s", height, height), + }, + { + "timestamp elapsed, height did not", + types.NewTimeout(height.Increment().(clienttypes.Height), timestamp), + errorsmod.Wrapf(types.ErrTimeoutElapsed, "current timestamp: %d, timeout timestamp %d", timestamp, timestamp), + }, + { + "height elapsed when less than current height", + types.NewTimeout(clienttypes.NewHeight(0, 1), 0), + errorsmod.Wrapf(types.ErrTimeoutElapsed, "current height: %s, timeout height %s", height, clienttypes.NewHeight(0, 1)), + }, + { + "timestamp elapsed when less than current timestamp", + types.NewTimeout(clienttypes.ZeroHeight(), timestamp-1), + errorsmod.Wrapf(types.ErrTimeoutElapsed, "current timestamp: %d, timeout timestamp %d", timestamp, timestamp-1), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := tc.timeout.ErrTimeoutElapsed(height, timestamp) + suite.Require().Equal(tc.expError.Error(), err.Error()) + }) + } +} + +func (suite *TypesTestSuite) TestErrTimeoutNotReached() { + // elapsed is expected to be true when either timeout height or timestamp + // is greater than or equal to 2 + var ( + height = clienttypes.NewHeight(0, 2) + timestamp = uint64(2) + ) + + testCases := []struct { + name string + timeout types.Timeout + expError error + }{ + { + "neither timeout reached with height and timestamp", + types.NewTimeout(height.Increment().(clienttypes.Height), timestamp+1), + errorsmod.Wrapf(types.ErrTimeoutNotReached, "current height: %s, timeout height %s", height, height.Increment().(clienttypes.Height)), + }, + { + "timeout not reached with height and zero timestamp", + types.NewTimeout(height.Increment().(clienttypes.Height), 0), + errorsmod.Wrapf(types.ErrTimeoutNotReached, "current height: %s, timeout height %s", height, height.Increment().(clienttypes.Height)), + }, + { + "timeout not reached with timestamp and zero height", + types.NewTimeout(clienttypes.ZeroHeight(), timestamp+1), + errorsmod.Wrapf(types.ErrTimeoutNotReached, "current timestamp: %d, timeout timestamp %d", timestamp, timestamp+1), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := tc.timeout.ErrTimeoutNotReached(height, timestamp) + suite.Require().Equal(tc.expError.Error(), err.Error()) + }) + } +} diff --git a/modules/core/04-channel/types/tx.pb.go b/modules/core/04-channel/types/tx.pb.go new file mode 100644 index 0000000..51d2874 --- /dev/null +++ b/modules/core/04-channel/types/tx.pb.go @@ -0,0 +1,5596 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/channel/v1/tx.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/cosmos/cosmos-sdk/types/msgservice" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + types "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// ResponseResultType defines the possible outcomes of the execution of a message +type ResponseResultType int32 + +const ( + // Default zero value enumeration + UNSPECIFIED ResponseResultType = 0 + // The message did not call the IBC application callbacks (because, for example, the packet had already been relayed) + NOOP ResponseResultType = 1 + // The message was executed successfully + SUCCESS ResponseResultType = 2 + // The message was executed unsuccessfully + FAILURE ResponseResultType = 3 +) + +var ResponseResultType_name = map[int32]string{ + 0: "RESPONSE_RESULT_TYPE_UNSPECIFIED", + 1: "RESPONSE_RESULT_TYPE_NOOP", + 2: "RESPONSE_RESULT_TYPE_SUCCESS", + 3: "RESPONSE_RESULT_TYPE_FAILURE", +} + +var ResponseResultType_value = map[string]int32{ + "RESPONSE_RESULT_TYPE_UNSPECIFIED": 0, + "RESPONSE_RESULT_TYPE_NOOP": 1, + "RESPONSE_RESULT_TYPE_SUCCESS": 2, + "RESPONSE_RESULT_TYPE_FAILURE": 3, +} + +func (x ResponseResultType) String() string { + return proto.EnumName(ResponseResultType_name, int32(x)) +} + +func (ResponseResultType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_bc4637e0ac3fc7b7, []int{0} +} + +// MsgChannelOpenInit defines an sdk.Msg to initialize a channel handshake. It +// is called by a relayer on Chain A. +type MsgChannelOpenInit struct { + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + Channel Channel `protobuf:"bytes,2,opt,name=channel,proto3" json:"channel"` + Signer string `protobuf:"bytes,3,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgChannelOpenInit) Reset() { *m = MsgChannelOpenInit{} } +func (m *MsgChannelOpenInit) String() string { return proto.CompactTextString(m) } +func (*MsgChannelOpenInit) ProtoMessage() {} +func (*MsgChannelOpenInit) Descriptor() ([]byte, []int) { + return fileDescriptor_bc4637e0ac3fc7b7, []int{0} +} +func (m *MsgChannelOpenInit) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgChannelOpenInit) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgChannelOpenInit.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgChannelOpenInit) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgChannelOpenInit.Merge(m, src) +} +func (m *MsgChannelOpenInit) XXX_Size() int { + return m.Size() +} +func (m *MsgChannelOpenInit) XXX_DiscardUnknown() { + xxx_messageInfo_MsgChannelOpenInit.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgChannelOpenInit proto.InternalMessageInfo + +// MsgChannelOpenInitResponse defines the Msg/ChannelOpenInit response type. +type MsgChannelOpenInitResponse struct { + ChannelId string `protobuf:"bytes,1,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` +} + +func (m *MsgChannelOpenInitResponse) Reset() { *m = MsgChannelOpenInitResponse{} } +func (m *MsgChannelOpenInitResponse) String() string { return proto.CompactTextString(m) } +func (*MsgChannelOpenInitResponse) ProtoMessage() {} +func (*MsgChannelOpenInitResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_bc4637e0ac3fc7b7, []int{1} +} +func (m *MsgChannelOpenInitResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgChannelOpenInitResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgChannelOpenInitResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgChannelOpenInitResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgChannelOpenInitResponse.Merge(m, src) +} +func (m *MsgChannelOpenInitResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgChannelOpenInitResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgChannelOpenInitResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgChannelOpenInitResponse proto.InternalMessageInfo + +// MsgChannelOpenInit defines a msg sent by a Relayer to try to open a channel +// on Chain B. The version field within the Channel field has been deprecated. Its +// value will be ignored by core IBC. +type MsgChannelOpenTry struct { + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + // Deprecated: this field is unused. Crossing hello's are no longer supported in core IBC. + PreviousChannelId string `protobuf:"bytes,2,opt,name=previous_channel_id,json=previousChannelId,proto3" json:"previous_channel_id,omitempty"` // Deprecated: Do not use. + // NOTE: the version field within the channel has been deprecated. Its value will be ignored by core IBC. + Channel Channel `protobuf:"bytes,3,opt,name=channel,proto3" json:"channel"` + CounterpartyVersion string `protobuf:"bytes,4,opt,name=counterparty_version,json=counterpartyVersion,proto3" json:"counterparty_version,omitempty"` + ProofInit []byte `protobuf:"bytes,5,opt,name=proof_init,json=proofInit,proto3" json:"proof_init,omitempty"` + ProofHeight types.Height `protobuf:"bytes,6,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` + Signer string `protobuf:"bytes,7,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgChannelOpenTry) Reset() { *m = MsgChannelOpenTry{} } +func (m *MsgChannelOpenTry) String() string { return proto.CompactTextString(m) } +func (*MsgChannelOpenTry) ProtoMessage() {} +func (*MsgChannelOpenTry) Descriptor() ([]byte, []int) { + return fileDescriptor_bc4637e0ac3fc7b7, []int{2} +} +func (m *MsgChannelOpenTry) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgChannelOpenTry) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgChannelOpenTry.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgChannelOpenTry) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgChannelOpenTry.Merge(m, src) +} +func (m *MsgChannelOpenTry) XXX_Size() int { + return m.Size() +} +func (m *MsgChannelOpenTry) XXX_DiscardUnknown() { + xxx_messageInfo_MsgChannelOpenTry.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgChannelOpenTry proto.InternalMessageInfo + +// MsgChannelOpenTryResponse defines the Msg/ChannelOpenTry response type. +type MsgChannelOpenTryResponse struct { + Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` +} + +func (m *MsgChannelOpenTryResponse) Reset() { *m = MsgChannelOpenTryResponse{} } +func (m *MsgChannelOpenTryResponse) String() string { return proto.CompactTextString(m) } +func (*MsgChannelOpenTryResponse) ProtoMessage() {} +func (*MsgChannelOpenTryResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_bc4637e0ac3fc7b7, []int{3} +} +func (m *MsgChannelOpenTryResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgChannelOpenTryResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgChannelOpenTryResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgChannelOpenTryResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgChannelOpenTryResponse.Merge(m, src) +} +func (m *MsgChannelOpenTryResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgChannelOpenTryResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgChannelOpenTryResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgChannelOpenTryResponse proto.InternalMessageInfo + +// MsgChannelOpenAck defines a msg sent by a Relayer to Chain A to acknowledge +// the change of channel state to TRYOPEN on Chain B. +type MsgChannelOpenAck struct { + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + CounterpartyChannelId string `protobuf:"bytes,3,opt,name=counterparty_channel_id,json=counterpartyChannelId,proto3" json:"counterparty_channel_id,omitempty"` + CounterpartyVersion string `protobuf:"bytes,4,opt,name=counterparty_version,json=counterpartyVersion,proto3" json:"counterparty_version,omitempty"` + ProofTry []byte `protobuf:"bytes,5,opt,name=proof_try,json=proofTry,proto3" json:"proof_try,omitempty"` + ProofHeight types.Height `protobuf:"bytes,6,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` + Signer string `protobuf:"bytes,7,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgChannelOpenAck) Reset() { *m = MsgChannelOpenAck{} } +func (m *MsgChannelOpenAck) String() string { return proto.CompactTextString(m) } +func (*MsgChannelOpenAck) ProtoMessage() {} +func (*MsgChannelOpenAck) Descriptor() ([]byte, []int) { + return fileDescriptor_bc4637e0ac3fc7b7, []int{4} +} +func (m *MsgChannelOpenAck) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgChannelOpenAck) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgChannelOpenAck.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgChannelOpenAck) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgChannelOpenAck.Merge(m, src) +} +func (m *MsgChannelOpenAck) XXX_Size() int { + return m.Size() +} +func (m *MsgChannelOpenAck) XXX_DiscardUnknown() { + xxx_messageInfo_MsgChannelOpenAck.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgChannelOpenAck proto.InternalMessageInfo + +// MsgChannelOpenAckResponse defines the Msg/ChannelOpenAck response type. +type MsgChannelOpenAckResponse struct { +} + +func (m *MsgChannelOpenAckResponse) Reset() { *m = MsgChannelOpenAckResponse{} } +func (m *MsgChannelOpenAckResponse) String() string { return proto.CompactTextString(m) } +func (*MsgChannelOpenAckResponse) ProtoMessage() {} +func (*MsgChannelOpenAckResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_bc4637e0ac3fc7b7, []int{5} +} +func (m *MsgChannelOpenAckResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgChannelOpenAckResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgChannelOpenAckResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgChannelOpenAckResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgChannelOpenAckResponse.Merge(m, src) +} +func (m *MsgChannelOpenAckResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgChannelOpenAckResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgChannelOpenAckResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgChannelOpenAckResponse proto.InternalMessageInfo + +// MsgChannelOpenConfirm defines a msg sent by a Relayer to Chain B to +// acknowledge the change of channel state to OPEN on Chain A. +type MsgChannelOpenConfirm struct { + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + ProofAck []byte `protobuf:"bytes,3,opt,name=proof_ack,json=proofAck,proto3" json:"proof_ack,omitempty"` + ProofHeight types.Height `protobuf:"bytes,4,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` + Signer string `protobuf:"bytes,5,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgChannelOpenConfirm) Reset() { *m = MsgChannelOpenConfirm{} } +func (m *MsgChannelOpenConfirm) String() string { return proto.CompactTextString(m) } +func (*MsgChannelOpenConfirm) ProtoMessage() {} +func (*MsgChannelOpenConfirm) Descriptor() ([]byte, []int) { + return fileDescriptor_bc4637e0ac3fc7b7, []int{6} +} +func (m *MsgChannelOpenConfirm) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgChannelOpenConfirm) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgChannelOpenConfirm.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgChannelOpenConfirm) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgChannelOpenConfirm.Merge(m, src) +} +func (m *MsgChannelOpenConfirm) XXX_Size() int { + return m.Size() +} +func (m *MsgChannelOpenConfirm) XXX_DiscardUnknown() { + xxx_messageInfo_MsgChannelOpenConfirm.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgChannelOpenConfirm proto.InternalMessageInfo + +// MsgChannelOpenConfirmResponse defines the Msg/ChannelOpenConfirm response +// type. +type MsgChannelOpenConfirmResponse struct { +} + +func (m *MsgChannelOpenConfirmResponse) Reset() { *m = MsgChannelOpenConfirmResponse{} } +func (m *MsgChannelOpenConfirmResponse) String() string { return proto.CompactTextString(m) } +func (*MsgChannelOpenConfirmResponse) ProtoMessage() {} +func (*MsgChannelOpenConfirmResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_bc4637e0ac3fc7b7, []int{7} +} +func (m *MsgChannelOpenConfirmResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgChannelOpenConfirmResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgChannelOpenConfirmResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgChannelOpenConfirmResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgChannelOpenConfirmResponse.Merge(m, src) +} +func (m *MsgChannelOpenConfirmResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgChannelOpenConfirmResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgChannelOpenConfirmResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgChannelOpenConfirmResponse proto.InternalMessageInfo + +// MsgChannelCloseInit defines a msg sent by a Relayer to Chain A +// to close a channel with Chain B. +type MsgChannelCloseInit struct { + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + Signer string `protobuf:"bytes,3,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgChannelCloseInit) Reset() { *m = MsgChannelCloseInit{} } +func (m *MsgChannelCloseInit) String() string { return proto.CompactTextString(m) } +func (*MsgChannelCloseInit) ProtoMessage() {} +func (*MsgChannelCloseInit) Descriptor() ([]byte, []int) { + return fileDescriptor_bc4637e0ac3fc7b7, []int{8} +} +func (m *MsgChannelCloseInit) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgChannelCloseInit) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgChannelCloseInit.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgChannelCloseInit) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgChannelCloseInit.Merge(m, src) +} +func (m *MsgChannelCloseInit) XXX_Size() int { + return m.Size() +} +func (m *MsgChannelCloseInit) XXX_DiscardUnknown() { + xxx_messageInfo_MsgChannelCloseInit.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgChannelCloseInit proto.InternalMessageInfo + +// MsgChannelCloseInitResponse defines the Msg/ChannelCloseInit response type. +type MsgChannelCloseInitResponse struct { +} + +func (m *MsgChannelCloseInitResponse) Reset() { *m = MsgChannelCloseInitResponse{} } +func (m *MsgChannelCloseInitResponse) String() string { return proto.CompactTextString(m) } +func (*MsgChannelCloseInitResponse) ProtoMessage() {} +func (*MsgChannelCloseInitResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_bc4637e0ac3fc7b7, []int{9} +} +func (m *MsgChannelCloseInitResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgChannelCloseInitResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgChannelCloseInitResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgChannelCloseInitResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgChannelCloseInitResponse.Merge(m, src) +} +func (m *MsgChannelCloseInitResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgChannelCloseInitResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgChannelCloseInitResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgChannelCloseInitResponse proto.InternalMessageInfo + +// MsgChannelCloseConfirm defines a msg sent by a Relayer to Chain B +// to acknowledge the change of channel state to CLOSED on Chain A. +type MsgChannelCloseConfirm struct { + PortId string `protobuf:"bytes,1,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` + ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + ProofInit []byte `protobuf:"bytes,3,opt,name=proof_init,json=proofInit,proto3" json:"proof_init,omitempty"` + ProofHeight types.Height `protobuf:"bytes,4,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` + Signer string `protobuf:"bytes,5,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgChannelCloseConfirm) Reset() { *m = MsgChannelCloseConfirm{} } +func (m *MsgChannelCloseConfirm) String() string { return proto.CompactTextString(m) } +func (*MsgChannelCloseConfirm) ProtoMessage() {} +func (*MsgChannelCloseConfirm) Descriptor() ([]byte, []int) { + return fileDescriptor_bc4637e0ac3fc7b7, []int{10} +} +func (m *MsgChannelCloseConfirm) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgChannelCloseConfirm) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgChannelCloseConfirm.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgChannelCloseConfirm) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgChannelCloseConfirm.Merge(m, src) +} +func (m *MsgChannelCloseConfirm) XXX_Size() int { + return m.Size() +} +func (m *MsgChannelCloseConfirm) XXX_DiscardUnknown() { + xxx_messageInfo_MsgChannelCloseConfirm.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgChannelCloseConfirm proto.InternalMessageInfo + +// MsgChannelCloseConfirmResponse defines the Msg/ChannelCloseConfirm response +// type. +type MsgChannelCloseConfirmResponse struct { +} + +func (m *MsgChannelCloseConfirmResponse) Reset() { *m = MsgChannelCloseConfirmResponse{} } +func (m *MsgChannelCloseConfirmResponse) String() string { return proto.CompactTextString(m) } +func (*MsgChannelCloseConfirmResponse) ProtoMessage() {} +func (*MsgChannelCloseConfirmResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_bc4637e0ac3fc7b7, []int{11} +} +func (m *MsgChannelCloseConfirmResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgChannelCloseConfirmResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgChannelCloseConfirmResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgChannelCloseConfirmResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgChannelCloseConfirmResponse.Merge(m, src) +} +func (m *MsgChannelCloseConfirmResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgChannelCloseConfirmResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgChannelCloseConfirmResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgChannelCloseConfirmResponse proto.InternalMessageInfo + +// MsgRecvPacket receives incoming IBC packet +type MsgRecvPacket struct { + Packet Packet `protobuf:"bytes,1,opt,name=packet,proto3" json:"packet"` + ProofCommitment []byte `protobuf:"bytes,2,opt,name=proof_commitment,json=proofCommitment,proto3" json:"proof_commitment,omitempty"` + ProofHeight types.Height `protobuf:"bytes,3,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` + Signer string `protobuf:"bytes,4,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgRecvPacket) Reset() { *m = MsgRecvPacket{} } +func (m *MsgRecvPacket) String() string { return proto.CompactTextString(m) } +func (*MsgRecvPacket) ProtoMessage() {} +func (*MsgRecvPacket) Descriptor() ([]byte, []int) { + return fileDescriptor_bc4637e0ac3fc7b7, []int{12} +} +func (m *MsgRecvPacket) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgRecvPacket) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgRecvPacket.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgRecvPacket) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgRecvPacket.Merge(m, src) +} +func (m *MsgRecvPacket) XXX_Size() int { + return m.Size() +} +func (m *MsgRecvPacket) XXX_DiscardUnknown() { + xxx_messageInfo_MsgRecvPacket.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgRecvPacket proto.InternalMessageInfo + +// MsgRecvPacketResponse defines the Msg/RecvPacket response type. +type MsgRecvPacketResponse struct { + Result ResponseResultType `protobuf:"varint,1,opt,name=result,proto3,enum=ibc.core.channel.v1.ResponseResultType" json:"result,omitempty"` +} + +func (m *MsgRecvPacketResponse) Reset() { *m = MsgRecvPacketResponse{} } +func (m *MsgRecvPacketResponse) String() string { return proto.CompactTextString(m) } +func (*MsgRecvPacketResponse) ProtoMessage() {} +func (*MsgRecvPacketResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_bc4637e0ac3fc7b7, []int{13} +} +func (m *MsgRecvPacketResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgRecvPacketResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgRecvPacketResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgRecvPacketResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgRecvPacketResponse.Merge(m, src) +} +func (m *MsgRecvPacketResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgRecvPacketResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgRecvPacketResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgRecvPacketResponse proto.InternalMessageInfo + +// MsgTimeout receives timed-out packet +type MsgTimeout struct { + Packet Packet `protobuf:"bytes,1,opt,name=packet,proto3" json:"packet"` + ProofUnreceived []byte `protobuf:"bytes,2,opt,name=proof_unreceived,json=proofUnreceived,proto3" json:"proof_unreceived,omitempty"` + ProofHeight types.Height `protobuf:"bytes,3,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` + NextSequenceRecv uint64 `protobuf:"varint,4,opt,name=next_sequence_recv,json=nextSequenceRecv,proto3" json:"next_sequence_recv,omitempty"` + Signer string `protobuf:"bytes,5,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgTimeout) Reset() { *m = MsgTimeout{} } +func (m *MsgTimeout) String() string { return proto.CompactTextString(m) } +func (*MsgTimeout) ProtoMessage() {} +func (*MsgTimeout) Descriptor() ([]byte, []int) { + return fileDescriptor_bc4637e0ac3fc7b7, []int{14} +} +func (m *MsgTimeout) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgTimeout) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgTimeout.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgTimeout) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgTimeout.Merge(m, src) +} +func (m *MsgTimeout) XXX_Size() int { + return m.Size() +} +func (m *MsgTimeout) XXX_DiscardUnknown() { + xxx_messageInfo_MsgTimeout.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgTimeout proto.InternalMessageInfo + +// MsgTimeoutResponse defines the Msg/Timeout response type. +type MsgTimeoutResponse struct { + Result ResponseResultType `protobuf:"varint,1,opt,name=result,proto3,enum=ibc.core.channel.v1.ResponseResultType" json:"result,omitempty"` +} + +func (m *MsgTimeoutResponse) Reset() { *m = MsgTimeoutResponse{} } +func (m *MsgTimeoutResponse) String() string { return proto.CompactTextString(m) } +func (*MsgTimeoutResponse) ProtoMessage() {} +func (*MsgTimeoutResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_bc4637e0ac3fc7b7, []int{15} +} +func (m *MsgTimeoutResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgTimeoutResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgTimeoutResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgTimeoutResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgTimeoutResponse.Merge(m, src) +} +func (m *MsgTimeoutResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgTimeoutResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgTimeoutResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgTimeoutResponse proto.InternalMessageInfo + +// MsgTimeoutOnClose timed-out packet upon counterparty channel closure. +type MsgTimeoutOnClose struct { + Packet Packet `protobuf:"bytes,1,opt,name=packet,proto3" json:"packet"` + ProofUnreceived []byte `protobuf:"bytes,2,opt,name=proof_unreceived,json=proofUnreceived,proto3" json:"proof_unreceived,omitempty"` + ProofClose []byte `protobuf:"bytes,3,opt,name=proof_close,json=proofClose,proto3" json:"proof_close,omitempty"` + ProofHeight types.Height `protobuf:"bytes,4,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` + NextSequenceRecv uint64 `protobuf:"varint,5,opt,name=next_sequence_recv,json=nextSequenceRecv,proto3" json:"next_sequence_recv,omitempty"` + Signer string `protobuf:"bytes,6,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgTimeoutOnClose) Reset() { *m = MsgTimeoutOnClose{} } +func (m *MsgTimeoutOnClose) String() string { return proto.CompactTextString(m) } +func (*MsgTimeoutOnClose) ProtoMessage() {} +func (*MsgTimeoutOnClose) Descriptor() ([]byte, []int) { + return fileDescriptor_bc4637e0ac3fc7b7, []int{16} +} +func (m *MsgTimeoutOnClose) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgTimeoutOnClose) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgTimeoutOnClose.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgTimeoutOnClose) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgTimeoutOnClose.Merge(m, src) +} +func (m *MsgTimeoutOnClose) XXX_Size() int { + return m.Size() +} +func (m *MsgTimeoutOnClose) XXX_DiscardUnknown() { + xxx_messageInfo_MsgTimeoutOnClose.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgTimeoutOnClose proto.InternalMessageInfo + +// MsgTimeoutOnCloseResponse defines the Msg/TimeoutOnClose response type. +type MsgTimeoutOnCloseResponse struct { + Result ResponseResultType `protobuf:"varint,1,opt,name=result,proto3,enum=ibc.core.channel.v1.ResponseResultType" json:"result,omitempty"` +} + +func (m *MsgTimeoutOnCloseResponse) Reset() { *m = MsgTimeoutOnCloseResponse{} } +func (m *MsgTimeoutOnCloseResponse) String() string { return proto.CompactTextString(m) } +func (*MsgTimeoutOnCloseResponse) ProtoMessage() {} +func (*MsgTimeoutOnCloseResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_bc4637e0ac3fc7b7, []int{17} +} +func (m *MsgTimeoutOnCloseResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgTimeoutOnCloseResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgTimeoutOnCloseResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgTimeoutOnCloseResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgTimeoutOnCloseResponse.Merge(m, src) +} +func (m *MsgTimeoutOnCloseResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgTimeoutOnCloseResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgTimeoutOnCloseResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgTimeoutOnCloseResponse proto.InternalMessageInfo + +// MsgAcknowledgement receives incoming IBC acknowledgement +type MsgAcknowledgement struct { + Packet Packet `protobuf:"bytes,1,opt,name=packet,proto3" json:"packet"` + Acknowledgement []byte `protobuf:"bytes,2,opt,name=acknowledgement,proto3" json:"acknowledgement,omitempty"` + ProofAcked []byte `protobuf:"bytes,3,opt,name=proof_acked,json=proofAcked,proto3" json:"proof_acked,omitempty"` + ProofHeight types.Height `protobuf:"bytes,4,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` + Signer string `protobuf:"bytes,5,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgAcknowledgement) Reset() { *m = MsgAcknowledgement{} } +func (m *MsgAcknowledgement) String() string { return proto.CompactTextString(m) } +func (*MsgAcknowledgement) ProtoMessage() {} +func (*MsgAcknowledgement) Descriptor() ([]byte, []int) { + return fileDescriptor_bc4637e0ac3fc7b7, []int{18} +} +func (m *MsgAcknowledgement) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgAcknowledgement) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgAcknowledgement.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgAcknowledgement) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAcknowledgement.Merge(m, src) +} +func (m *MsgAcknowledgement) XXX_Size() int { + return m.Size() +} +func (m *MsgAcknowledgement) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAcknowledgement.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgAcknowledgement proto.InternalMessageInfo + +// MsgAcknowledgementResponse defines the Msg/Acknowledgement response type. +type MsgAcknowledgementResponse struct { + Result ResponseResultType `protobuf:"varint,1,opt,name=result,proto3,enum=ibc.core.channel.v1.ResponseResultType" json:"result,omitempty"` +} + +func (m *MsgAcknowledgementResponse) Reset() { *m = MsgAcknowledgementResponse{} } +func (m *MsgAcknowledgementResponse) String() string { return proto.CompactTextString(m) } +func (*MsgAcknowledgementResponse) ProtoMessage() {} +func (*MsgAcknowledgementResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_bc4637e0ac3fc7b7, []int{19} +} +func (m *MsgAcknowledgementResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgAcknowledgementResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgAcknowledgementResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgAcknowledgementResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAcknowledgementResponse.Merge(m, src) +} +func (m *MsgAcknowledgementResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgAcknowledgementResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAcknowledgementResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgAcknowledgementResponse proto.InternalMessageInfo + +func init() { + proto.RegisterEnum("ibc.core.channel.v1.ResponseResultType", ResponseResultType_name, ResponseResultType_value) + proto.RegisterType((*MsgChannelOpenInit)(nil), "ibc.core.channel.v1.MsgChannelOpenInit") + proto.RegisterType((*MsgChannelOpenInitResponse)(nil), "ibc.core.channel.v1.MsgChannelOpenInitResponse") + proto.RegisterType((*MsgChannelOpenTry)(nil), "ibc.core.channel.v1.MsgChannelOpenTry") + proto.RegisterType((*MsgChannelOpenTryResponse)(nil), "ibc.core.channel.v1.MsgChannelOpenTryResponse") + proto.RegisterType((*MsgChannelOpenAck)(nil), "ibc.core.channel.v1.MsgChannelOpenAck") + proto.RegisterType((*MsgChannelOpenAckResponse)(nil), "ibc.core.channel.v1.MsgChannelOpenAckResponse") + proto.RegisterType((*MsgChannelOpenConfirm)(nil), "ibc.core.channel.v1.MsgChannelOpenConfirm") + proto.RegisterType((*MsgChannelOpenConfirmResponse)(nil), "ibc.core.channel.v1.MsgChannelOpenConfirmResponse") + proto.RegisterType((*MsgChannelCloseInit)(nil), "ibc.core.channel.v1.MsgChannelCloseInit") + proto.RegisterType((*MsgChannelCloseInitResponse)(nil), "ibc.core.channel.v1.MsgChannelCloseInitResponse") + proto.RegisterType((*MsgChannelCloseConfirm)(nil), "ibc.core.channel.v1.MsgChannelCloseConfirm") + proto.RegisterType((*MsgChannelCloseConfirmResponse)(nil), "ibc.core.channel.v1.MsgChannelCloseConfirmResponse") + proto.RegisterType((*MsgRecvPacket)(nil), "ibc.core.channel.v1.MsgRecvPacket") + proto.RegisterType((*MsgRecvPacketResponse)(nil), "ibc.core.channel.v1.MsgRecvPacketResponse") + proto.RegisterType((*MsgTimeout)(nil), "ibc.core.channel.v1.MsgTimeout") + proto.RegisterType((*MsgTimeoutResponse)(nil), "ibc.core.channel.v1.MsgTimeoutResponse") + proto.RegisterType((*MsgTimeoutOnClose)(nil), "ibc.core.channel.v1.MsgTimeoutOnClose") + proto.RegisterType((*MsgTimeoutOnCloseResponse)(nil), "ibc.core.channel.v1.MsgTimeoutOnCloseResponse") + proto.RegisterType((*MsgAcknowledgement)(nil), "ibc.core.channel.v1.MsgAcknowledgement") + proto.RegisterType((*MsgAcknowledgementResponse)(nil), "ibc.core.channel.v1.MsgAcknowledgementResponse") +} + +func init() { proto.RegisterFile("ibc/core/channel/v1/tx.proto", fileDescriptor_bc4637e0ac3fc7b7) } + +var fileDescriptor_bc4637e0ac3fc7b7 = []byte{ + // 1202 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0xcd, 0x6f, 0xda, 0x66, + 0x18, 0xc7, 0x40, 0x20, 0x79, 0xc8, 0x0a, 0x75, 0xda, 0x86, 0x3a, 0x09, 0xb0, 0x1c, 0x9a, 0x2c, + 0x5b, 0x20, 0xa1, 0xdb, 0xa4, 0x45, 0x93, 0x26, 0xc2, 0xa8, 0x86, 0xd4, 0x7c, 0xc8, 0x90, 0x49, + 0x6b, 0xa7, 0xa1, 0x60, 0xde, 0x3a, 0x16, 0xe0, 0x97, 0xd9, 0x86, 0x96, 0xdb, 0xb4, 0x53, 0x94, + 0xc3, 0xb4, 0xc3, 0xae, 0x91, 0x26, 0xed, 0x1f, 0xe8, 0xa9, 0xf7, 0xdd, 0x7a, 0xec, 0xb1, 0x9a, + 0xb4, 0x6a, 0x4a, 0x0e, 0xfd, 0x37, 0x26, 0xbf, 0x7e, 0x6d, 0x8c, 0xb1, 0xc1, 0x6d, 0x68, 0x6e, + 0xf6, 0xf3, 0xfc, 0xde, 0xe7, 0xe3, 0xf7, 0x7b, 0xbf, 0x6c, 0x58, 0x96, 0xea, 0x42, 0x4e, 0xc0, + 0x0a, 0xca, 0x09, 0x27, 0xc7, 0xb2, 0x8c, 0x5a, 0xb9, 0xde, 0x76, 0x4e, 0x7b, 0x96, 0xed, 0x28, + 0x58, 0xc3, 0xec, 0x82, 0x54, 0x17, 0xb2, 0xba, 0x37, 0x4b, 0xbd, 0xd9, 0xde, 0x36, 0x77, 0x4b, + 0xc4, 0x22, 0x26, 0xfe, 0x9c, 0xfe, 0x64, 0x40, 0xb9, 0x45, 0x01, 0xab, 0x6d, 0xac, 0xe6, 0xda, + 0xaa, 0xa8, 0x87, 0x68, 0xab, 0x22, 0x75, 0xa4, 0x07, 0x19, 0x5a, 0x12, 0x92, 0x35, 0xdd, 0x6b, + 0x3c, 0x51, 0xc0, 0xc7, 0x6e, 0x25, 0x98, 0xf9, 0x08, 0x64, 0xf5, 0x0f, 0x06, 0xd8, 0x3d, 0x55, + 0x2c, 0x1a, 0xc6, 0x83, 0x0e, 0x92, 0xcb, 0xb2, 0xa4, 0xb1, 0x8b, 0x10, 0xed, 0x60, 0x45, 0xab, + 0x49, 0x8d, 0x24, 0x93, 0x61, 0xd6, 0xe7, 0xf8, 0x88, 0xfe, 0x5a, 0x6e, 0xb0, 0x5f, 0x43, 0x94, + 0x06, 0x48, 0x06, 0x33, 0xcc, 0x7a, 0x2c, 0xbf, 0x9c, 0x75, 0xe9, 0x24, 0x4b, 0xe3, 0xed, 0x86, + 0x5f, 0xbe, 0x49, 0x07, 0x78, 0x73, 0x08, 0x7b, 0x07, 0x22, 0xaa, 0x24, 0xca, 0x48, 0x49, 0x86, + 0x8c, 0xa8, 0xc6, 0xdb, 0x4e, 0xfc, 0xf4, 0xcf, 0x74, 0xe0, 0xd7, 0xb7, 0xcf, 0x37, 0xa8, 0x61, + 0xf5, 0x31, 0x70, 0xa3, 0x55, 0xf1, 0x48, 0xed, 0x60, 0x59, 0x45, 0xec, 0x0a, 0x00, 0x8d, 0x38, + 0x28, 0x70, 0x8e, 0x5a, 0xca, 0x0d, 0x36, 0x09, 0xd1, 0x1e, 0x52, 0x54, 0x09, 0xcb, 0xa4, 0xc6, + 0x39, 0xde, 0x7c, 0xdd, 0x09, 0xeb, 0x79, 0x56, 0xdf, 0x04, 0xe1, 0xe6, 0x70, 0xf4, 0xaa, 0xd2, + 0xf7, 0x6e, 0x39, 0x0f, 0x0b, 0x1d, 0x05, 0xf5, 0x24, 0xdc, 0x55, 0x6b, 0xb6, 0xb4, 0x24, 0xf4, + 0x6e, 0x30, 0xc9, 0xf0, 0x37, 0x4d, 0x77, 0xd1, 0x2a, 0xc1, 0x46, 0x53, 0xe8, 0xdd, 0x69, 0xda, + 0x86, 0x5b, 0x02, 0xee, 0xca, 0x1a, 0x52, 0x3a, 0xc7, 0x8a, 0xd6, 0xaf, 0x99, 0xdd, 0x84, 0x49, + 0x5d, 0x0b, 0x76, 0xdf, 0xf7, 0x86, 0x4b, 0xa7, 0xa4, 0xa3, 0x60, 0xfc, 0xa4, 0x26, 0xc9, 0x92, + 0x96, 0x9c, 0xc9, 0x30, 0xeb, 0xf3, 0xfc, 0x1c, 0xb1, 0x10, 0x3d, 0x8b, 0x30, 0x6f, 0xb8, 0x4f, + 0x90, 0x24, 0x9e, 0x68, 0xc9, 0x08, 0x29, 0x8a, 0xb3, 0x15, 0x65, 0xcc, 0x9b, 0xde, 0x76, 0xf6, + 0x3b, 0x82, 0xa0, 0x25, 0xc5, 0xc8, 0x28, 0xc3, 0x64, 0x53, 0x2f, 0x3a, 0x5e, 0xbd, 0x47, 0x70, + 0x77, 0x84, 0x5f, 0x4b, 0x3c, 0x9b, 0x3a, 0xcc, 0x90, 0x3a, 0x0e, 0x59, 0x83, 0x0e, 0x59, 0xa9, + 0x78, 0x7f, 0x8f, 0x88, 0x57, 0x10, 0x9a, 0xde, 0xe2, 0x8d, 0x8f, 0xc9, 0x7e, 0x09, 0x8b, 0x43, + 0x4c, 0xdb, 0xb0, 0xc6, 0x0c, 0xbd, 0x6d, 0x77, 0x0f, 0xf4, 0x7d, 0x0f, 0x85, 0x96, 0xc0, 0xd0, + 0xa3, 0xa6, 0x29, 0x7d, 0x2a, 0xd0, 0x2c, 0x31, 0xe8, 0x93, 0xef, 0x7a, 0xf5, 0x59, 0x72, 0xea, + 0x53, 0x10, 0x9a, 0xa6, 0x3e, 0xab, 0xff, 0x30, 0x70, 0x7b, 0xd8, 0x5b, 0xc4, 0xf2, 0x13, 0x49, + 0x69, 0xbf, 0x37, 0xc9, 0x56, 0xe7, 0xc7, 0x42, 0x93, 0xd0, 0x6a, 0x76, 0xae, 0x2b, 0xe7, 0xec, + 0x3c, 0x7c, 0xb5, 0xce, 0x67, 0xc6, 0x77, 0x9e, 0x86, 0x15, 0xd7, 0xde, 0xac, 0xee, 0x7b, 0xb0, + 0x30, 0x00, 0x14, 0x5b, 0x58, 0x45, 0xe3, 0xf7, 0xc3, 0x09, 0xad, 0xfb, 0xde, 0xf0, 0x56, 0x60, + 0xc9, 0x25, 0xaf, 0x55, 0xd6, 0xbf, 0x0c, 0xdc, 0x71, 0xf8, 0xaf, 0xaa, 0xca, 0xf0, 0x8e, 0x11, + 0x9a, 0xb4, 0x63, 0x7c, 0x58, 0x5d, 0x32, 0x90, 0x72, 0x6f, 0xcf, 0x62, 0xe0, 0x92, 0x81, 0x8f, + 0xf6, 0x54, 0x91, 0x47, 0x42, 0xef, 0xf0, 0x58, 0x68, 0x22, 0x8d, 0xfd, 0x0a, 0x22, 0x1d, 0xf2, + 0x44, 0xfa, 0x8e, 0xe5, 0x97, 0x5c, 0xb7, 0x58, 0x03, 0x4c, 0x8b, 0xa3, 0x03, 0xd8, 0x4f, 0x20, + 0x61, 0x34, 0x27, 0xe0, 0x76, 0x5b, 0xd2, 0xda, 0x48, 0xd6, 0x08, 0x41, 0xf3, 0x7c, 0x9c, 0xd8, + 0x8b, 0x96, 0x79, 0x84, 0x87, 0xd0, 0xd5, 0x78, 0x08, 0x8f, 0xe7, 0xe1, 0x27, 0xb2, 0xf6, 0x06, + 0x4d, 0x5a, 0xbb, 0xe6, 0x37, 0x10, 0x51, 0x90, 0xda, 0x6d, 0x19, 0xcd, 0xde, 0xc8, 0xaf, 0xb9, + 0x36, 0x6b, 0xc2, 0x79, 0x02, 0xad, 0xf6, 0x3b, 0x88, 0xa7, 0xc3, 0xe8, 0xee, 0xf9, 0x5b, 0x10, + 0x60, 0x4f, 0x15, 0xab, 0x52, 0x1b, 0xe1, 0xee, 0x74, 0x28, 0xec, 0xca, 0x0a, 0x12, 0x90, 0xd4, + 0x43, 0x8d, 0x21, 0x0a, 0x8f, 0x2c, 0xf3, 0x74, 0x28, 0xfc, 0x0c, 0x58, 0x19, 0x3d, 0xd3, 0x6a, + 0x2a, 0xfa, 0xb9, 0x8b, 0x64, 0x01, 0xd5, 0x14, 0x24, 0xf4, 0x08, 0x9d, 0x61, 0x3e, 0xa1, 0x7b, + 0x2a, 0xd4, 0xa1, 0x93, 0xe7, 0x7f, 0xe2, 0x3d, 0x26, 0xd7, 0x1f, 0xca, 0xc7, 0xb4, 0xd9, 0x7e, + 0x61, 0x9c, 0x55, 0x34, 0xfa, 0x81, 0x4c, 0x26, 0xf6, 0x35, 0x91, 0x9e, 0x86, 0x18, 0x9d, 0xe2, + 0x7a, 0x52, 0xba, 0xbe, 0x8d, 0x15, 0x6f, 0x94, 0x31, 0x95, 0x05, 0xee, 0xae, 0xca, 0xcc, 0x44, + 0x55, 0x22, 0xe3, 0x55, 0xa9, 0x93, 0x03, 0x6a, 0x98, 0xb7, 0x69, 0x8b, 0x73, 0x1a, 0x24, 0xd2, + 0x17, 0x84, 0xa6, 0x8c, 0x9f, 0xb6, 0x50, 0x43, 0x44, 0x64, 0xbd, 0x5f, 0x41, 0x9d, 0x75, 0x88, + 0x1f, 0x0f, 0x47, 0x33, 0xc5, 0x71, 0x98, 0x07, 0xe2, 0xe8, 0x03, 0x1b, 0x43, 0xe2, 0x14, 0x74, + 0xcb, 0x35, 0xef, 0xbe, 0x02, 0xb9, 0x6d, 0x3b, 0x98, 0x98, 0x32, 0xdf, 0x1b, 0xaf, 0x19, 0x60, + 0x47, 0x41, 0xec, 0x17, 0x90, 0xe1, 0x4b, 0x95, 0xc3, 0x83, 0xfd, 0x4a, 0xa9, 0xc6, 0x97, 0x2a, + 0x47, 0x0f, 0xab, 0xb5, 0xea, 0x0f, 0x87, 0xa5, 0xda, 0xd1, 0x7e, 0xe5, 0xb0, 0x54, 0x2c, 0x3f, + 0x28, 0x97, 0xbe, 0x4d, 0x04, 0xb8, 0xf8, 0xd9, 0x79, 0x26, 0x66, 0x33, 0xb1, 0x6b, 0x70, 0xd7, + 0x75, 0xd8, 0xfe, 0xc1, 0xc1, 0x61, 0x82, 0xe1, 0x66, 0xcf, 0xce, 0x33, 0x61, 0xfd, 0x99, 0xdd, + 0x84, 0x65, 0x57, 0x60, 0xe5, 0xa8, 0x58, 0x2c, 0x55, 0x2a, 0x89, 0x20, 0x17, 0x3b, 0x3b, 0xcf, + 0x44, 0xe9, 0xab, 0x27, 0xfc, 0x41, 0xa1, 0xfc, 0xf0, 0x88, 0x2f, 0x25, 0x42, 0x06, 0x9c, 0xbe, + 0x72, 0xe1, 0xd3, 0xbf, 0x52, 0x81, 0xfc, 0x8b, 0x59, 0x08, 0xed, 0xa9, 0x22, 0xdb, 0x84, 0xb8, + 0xf3, 0x43, 0xca, 0x9d, 0xac, 0xd1, 0x6f, 0x1b, 0x2e, 0xe7, 0x13, 0x68, 0xc9, 0x72, 0x02, 0x37, + 0x1c, 0x5f, 0x30, 0xf7, 0x7c, 0x84, 0xa8, 0x2a, 0x7d, 0x2e, 0xeb, 0x0f, 0xe7, 0x91, 0x49, 0xbf, + 0xb4, 0xf9, 0xc9, 0x54, 0x10, 0x9a, 0xbe, 0x32, 0xd9, 0xee, 0x9e, 0xac, 0x06, 0xac, 0xcb, 0xbd, + 0x73, 0xc3, 0x47, 0x14, 0x8a, 0xe5, 0xf2, 0xfe, 0xb1, 0x56, 0x56, 0x19, 0x12, 0x23, 0x17, 0xbe, + 0xf5, 0x09, 0x71, 0x2c, 0x24, 0xb7, 0xe5, 0x17, 0x69, 0xe5, 0x7b, 0x0a, 0x0b, 0x6e, 0x17, 0xb9, + 0x4f, 0xfd, 0x04, 0x32, 0xfb, 0xbc, 0xff, 0x0e, 0x60, 0x2b, 0xf1, 0x8f, 0x00, 0xb6, 0xfb, 0xd3, + 0xaa, 0x57, 0x88, 0x01, 0x86, 0xdb, 0x98, 0x8c, 0xb1, 0xa2, 0x57, 0x20, 0x6a, 0xde, 0x2b, 0xd2, + 0x5e, 0xc3, 0x28, 0x80, 0x5b, 0x9b, 0x00, 0xb0, 0xcf, 0x3d, 0xc7, 0xf1, 0x79, 0x6f, 0xc2, 0x50, + 0x8a, 0xf3, 0x9e, 0x7b, 0x1e, 0xc7, 0x4a, 0x13, 0xe2, 0xce, 0xb3, 0xc0, 0xb3, 0x4a, 0x07, 0xd0, + 0x7b, 0xf1, 0x7a, 0xec, 0xa9, 0xdc, 0xcc, 0x2f, 0x6f, 0x9f, 0x6f, 0x30, 0xbb, 0xd5, 0x97, 0x17, + 0x29, 0xe6, 0xd5, 0x45, 0x8a, 0xf9, 0xef, 0x22, 0xc5, 0xfc, 0x7e, 0x99, 0x0a, 0xbc, 0xba, 0x4c, + 0x05, 0x5e, 0x5f, 0xa6, 0x02, 0x8f, 0x76, 0x44, 0x49, 0x3b, 0xe9, 0xd6, 0xb3, 0x02, 0x6e, 0xe7, + 0xe8, 0xff, 0x1f, 0xa9, 0x2e, 0x6c, 0x8a, 0x38, 0xd7, 0xdb, 0xde, 0xca, 0xb5, 0x71, 0xa3, 0xdb, + 0x42, 0xaa, 0xf1, 0x6f, 0x67, 0xeb, 0xf3, 0x4d, 0xf3, 0xf7, 0x8e, 0xd6, 0xef, 0x20, 0xb5, 0x1e, + 0x21, 0xbf, 0x76, 0xee, 0xff, 0x1f, 0x00, 0x00, 0xff, 0xff, 0x98, 0x44, 0xff, 0x9f, 0x82, 0x12, + 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MsgClient is the client API for Msg service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MsgClient interface { + // ChannelOpenInit defines a rpc handler method for MsgChannelOpenInit. + ChannelOpenInit(ctx context.Context, in *MsgChannelOpenInit, opts ...grpc.CallOption) (*MsgChannelOpenInitResponse, error) + // ChannelOpenTry defines a rpc handler method for MsgChannelOpenTry. + ChannelOpenTry(ctx context.Context, in *MsgChannelOpenTry, opts ...grpc.CallOption) (*MsgChannelOpenTryResponse, error) + // ChannelOpenAck defines a rpc handler method for MsgChannelOpenAck. + ChannelOpenAck(ctx context.Context, in *MsgChannelOpenAck, opts ...grpc.CallOption) (*MsgChannelOpenAckResponse, error) + // ChannelOpenConfirm defines a rpc handler method for MsgChannelOpenConfirm. + ChannelOpenConfirm(ctx context.Context, in *MsgChannelOpenConfirm, opts ...grpc.CallOption) (*MsgChannelOpenConfirmResponse, error) + // ChannelCloseInit defines a rpc handler method for MsgChannelCloseInit. + ChannelCloseInit(ctx context.Context, in *MsgChannelCloseInit, opts ...grpc.CallOption) (*MsgChannelCloseInitResponse, error) + // ChannelCloseConfirm defines a rpc handler method for + // MsgChannelCloseConfirm. + ChannelCloseConfirm(ctx context.Context, in *MsgChannelCloseConfirm, opts ...grpc.CallOption) (*MsgChannelCloseConfirmResponse, error) + // RecvPacket defines a rpc handler method for MsgRecvPacket. + RecvPacket(ctx context.Context, in *MsgRecvPacket, opts ...grpc.CallOption) (*MsgRecvPacketResponse, error) + // Timeout defines a rpc handler method for MsgTimeout. + Timeout(ctx context.Context, in *MsgTimeout, opts ...grpc.CallOption) (*MsgTimeoutResponse, error) + // TimeoutOnClose defines a rpc handler method for MsgTimeoutOnClose. + TimeoutOnClose(ctx context.Context, in *MsgTimeoutOnClose, opts ...grpc.CallOption) (*MsgTimeoutOnCloseResponse, error) + // Acknowledgement defines a rpc handler method for MsgAcknowledgement. + Acknowledgement(ctx context.Context, in *MsgAcknowledgement, opts ...grpc.CallOption) (*MsgAcknowledgementResponse, error) +} + +type msgClient struct { + cc grpc1.ClientConn +} + +func NewMsgClient(cc grpc1.ClientConn) MsgClient { + return &msgClient{cc} +} + +func (c *msgClient) ChannelOpenInit(ctx context.Context, in *MsgChannelOpenInit, opts ...grpc.CallOption) (*MsgChannelOpenInitResponse, error) { + out := new(MsgChannelOpenInitResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Msg/ChannelOpenInit", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) ChannelOpenTry(ctx context.Context, in *MsgChannelOpenTry, opts ...grpc.CallOption) (*MsgChannelOpenTryResponse, error) { + out := new(MsgChannelOpenTryResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Msg/ChannelOpenTry", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) ChannelOpenAck(ctx context.Context, in *MsgChannelOpenAck, opts ...grpc.CallOption) (*MsgChannelOpenAckResponse, error) { + out := new(MsgChannelOpenAckResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Msg/ChannelOpenAck", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) ChannelOpenConfirm(ctx context.Context, in *MsgChannelOpenConfirm, opts ...grpc.CallOption) (*MsgChannelOpenConfirmResponse, error) { + out := new(MsgChannelOpenConfirmResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Msg/ChannelOpenConfirm", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) ChannelCloseInit(ctx context.Context, in *MsgChannelCloseInit, opts ...grpc.CallOption) (*MsgChannelCloseInitResponse, error) { + out := new(MsgChannelCloseInitResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Msg/ChannelCloseInit", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) ChannelCloseConfirm(ctx context.Context, in *MsgChannelCloseConfirm, opts ...grpc.CallOption) (*MsgChannelCloseConfirmResponse, error) { + out := new(MsgChannelCloseConfirmResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Msg/ChannelCloseConfirm", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) RecvPacket(ctx context.Context, in *MsgRecvPacket, opts ...grpc.CallOption) (*MsgRecvPacketResponse, error) { + out := new(MsgRecvPacketResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Msg/RecvPacket", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) Timeout(ctx context.Context, in *MsgTimeout, opts ...grpc.CallOption) (*MsgTimeoutResponse, error) { + out := new(MsgTimeoutResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Msg/Timeout", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) TimeoutOnClose(ctx context.Context, in *MsgTimeoutOnClose, opts ...grpc.CallOption) (*MsgTimeoutOnCloseResponse, error) { + out := new(MsgTimeoutOnCloseResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Msg/TimeoutOnClose", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) Acknowledgement(ctx context.Context, in *MsgAcknowledgement, opts ...grpc.CallOption) (*MsgAcknowledgementResponse, error) { + out := new(MsgAcknowledgementResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v1.Msg/Acknowledgement", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MsgServer is the server API for Msg service. +type MsgServer interface { + // ChannelOpenInit defines a rpc handler method for MsgChannelOpenInit. + ChannelOpenInit(context.Context, *MsgChannelOpenInit) (*MsgChannelOpenInitResponse, error) + // ChannelOpenTry defines a rpc handler method for MsgChannelOpenTry. + ChannelOpenTry(context.Context, *MsgChannelOpenTry) (*MsgChannelOpenTryResponse, error) + // ChannelOpenAck defines a rpc handler method for MsgChannelOpenAck. + ChannelOpenAck(context.Context, *MsgChannelOpenAck) (*MsgChannelOpenAckResponse, error) + // ChannelOpenConfirm defines a rpc handler method for MsgChannelOpenConfirm. + ChannelOpenConfirm(context.Context, *MsgChannelOpenConfirm) (*MsgChannelOpenConfirmResponse, error) + // ChannelCloseInit defines a rpc handler method for MsgChannelCloseInit. + ChannelCloseInit(context.Context, *MsgChannelCloseInit) (*MsgChannelCloseInitResponse, error) + // ChannelCloseConfirm defines a rpc handler method for + // MsgChannelCloseConfirm. + ChannelCloseConfirm(context.Context, *MsgChannelCloseConfirm) (*MsgChannelCloseConfirmResponse, error) + // RecvPacket defines a rpc handler method for MsgRecvPacket. + RecvPacket(context.Context, *MsgRecvPacket) (*MsgRecvPacketResponse, error) + // Timeout defines a rpc handler method for MsgTimeout. + Timeout(context.Context, *MsgTimeout) (*MsgTimeoutResponse, error) + // TimeoutOnClose defines a rpc handler method for MsgTimeoutOnClose. + TimeoutOnClose(context.Context, *MsgTimeoutOnClose) (*MsgTimeoutOnCloseResponse, error) + // Acknowledgement defines a rpc handler method for MsgAcknowledgement. + Acknowledgement(context.Context, *MsgAcknowledgement) (*MsgAcknowledgementResponse, error) +} + +// UnimplementedMsgServer can be embedded to have forward compatible implementations. +type UnimplementedMsgServer struct { +} + +func (*UnimplementedMsgServer) ChannelOpenInit(ctx context.Context, req *MsgChannelOpenInit) (*MsgChannelOpenInitResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ChannelOpenInit not implemented") +} +func (*UnimplementedMsgServer) ChannelOpenTry(ctx context.Context, req *MsgChannelOpenTry) (*MsgChannelOpenTryResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ChannelOpenTry not implemented") +} +func (*UnimplementedMsgServer) ChannelOpenAck(ctx context.Context, req *MsgChannelOpenAck) (*MsgChannelOpenAckResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ChannelOpenAck not implemented") +} +func (*UnimplementedMsgServer) ChannelOpenConfirm(ctx context.Context, req *MsgChannelOpenConfirm) (*MsgChannelOpenConfirmResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ChannelOpenConfirm not implemented") +} +func (*UnimplementedMsgServer) ChannelCloseInit(ctx context.Context, req *MsgChannelCloseInit) (*MsgChannelCloseInitResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ChannelCloseInit not implemented") +} +func (*UnimplementedMsgServer) ChannelCloseConfirm(ctx context.Context, req *MsgChannelCloseConfirm) (*MsgChannelCloseConfirmResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ChannelCloseConfirm not implemented") +} +func (*UnimplementedMsgServer) RecvPacket(ctx context.Context, req *MsgRecvPacket) (*MsgRecvPacketResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RecvPacket not implemented") +} +func (*UnimplementedMsgServer) Timeout(ctx context.Context, req *MsgTimeout) (*MsgTimeoutResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Timeout not implemented") +} +func (*UnimplementedMsgServer) TimeoutOnClose(ctx context.Context, req *MsgTimeoutOnClose) (*MsgTimeoutOnCloseResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method TimeoutOnClose not implemented") +} +func (*UnimplementedMsgServer) Acknowledgement(ctx context.Context, req *MsgAcknowledgement) (*MsgAcknowledgementResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Acknowledgement not implemented") +} + +func RegisterMsgServer(s grpc1.Server, srv MsgServer) { + s.RegisterService(&_Msg_serviceDesc, srv) +} + +func _Msg_ChannelOpenInit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgChannelOpenInit) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).ChannelOpenInit(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Msg/ChannelOpenInit", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).ChannelOpenInit(ctx, req.(*MsgChannelOpenInit)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_ChannelOpenTry_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgChannelOpenTry) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).ChannelOpenTry(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Msg/ChannelOpenTry", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).ChannelOpenTry(ctx, req.(*MsgChannelOpenTry)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_ChannelOpenAck_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgChannelOpenAck) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).ChannelOpenAck(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Msg/ChannelOpenAck", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).ChannelOpenAck(ctx, req.(*MsgChannelOpenAck)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_ChannelOpenConfirm_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgChannelOpenConfirm) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).ChannelOpenConfirm(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Msg/ChannelOpenConfirm", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).ChannelOpenConfirm(ctx, req.(*MsgChannelOpenConfirm)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_ChannelCloseInit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgChannelCloseInit) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).ChannelCloseInit(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Msg/ChannelCloseInit", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).ChannelCloseInit(ctx, req.(*MsgChannelCloseInit)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_ChannelCloseConfirm_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgChannelCloseConfirm) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).ChannelCloseConfirm(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Msg/ChannelCloseConfirm", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).ChannelCloseConfirm(ctx, req.(*MsgChannelCloseConfirm)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_RecvPacket_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgRecvPacket) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).RecvPacket(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Msg/RecvPacket", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).RecvPacket(ctx, req.(*MsgRecvPacket)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_Timeout_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgTimeout) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).Timeout(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Msg/Timeout", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).Timeout(ctx, req.(*MsgTimeout)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_TimeoutOnClose_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgTimeoutOnClose) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).TimeoutOnClose(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Msg/TimeoutOnClose", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).TimeoutOnClose(ctx, req.(*MsgTimeoutOnClose)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_Acknowledgement_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgAcknowledgement) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).Acknowledgement(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v1.Msg/Acknowledgement", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).Acknowledgement(ctx, req.(*MsgAcknowledgement)) + } + return interceptor(ctx, in, info, handler) +} + +var _Msg_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ibc.core.channel.v1.Msg", + HandlerType: (*MsgServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ChannelOpenInit", + Handler: _Msg_ChannelOpenInit_Handler, + }, + { + MethodName: "ChannelOpenTry", + Handler: _Msg_ChannelOpenTry_Handler, + }, + { + MethodName: "ChannelOpenAck", + Handler: _Msg_ChannelOpenAck_Handler, + }, + { + MethodName: "ChannelOpenConfirm", + Handler: _Msg_ChannelOpenConfirm_Handler, + }, + { + MethodName: "ChannelCloseInit", + Handler: _Msg_ChannelCloseInit_Handler, + }, + { + MethodName: "ChannelCloseConfirm", + Handler: _Msg_ChannelCloseConfirm_Handler, + }, + { + MethodName: "RecvPacket", + Handler: _Msg_RecvPacket_Handler, + }, + { + MethodName: "Timeout", + Handler: _Msg_Timeout_Handler, + }, + { + MethodName: "TimeoutOnClose", + Handler: _Msg_TimeoutOnClose_Handler, + }, + { + MethodName: "Acknowledgement", + Handler: _Msg_Acknowledgement_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "ibc/core/channel/v1/tx.proto", +} + +func (m *MsgChannelOpenInit) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgChannelOpenInit) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgChannelOpenInit) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x1a + } + { + size, err := m.Channel.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintTx(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgChannelOpenInitResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgChannelOpenInitResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgChannelOpenInitResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Version) > 0 { + i -= len(m.Version) + copy(dAtA[i:], m.Version) + i = encodeVarintTx(dAtA, i, uint64(len(m.Version))) + i-- + dAtA[i] = 0x12 + } + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgChannelOpenTry) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgChannelOpenTry) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgChannelOpenTry) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x3a + } + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + if len(m.ProofInit) > 0 { + i -= len(m.ProofInit) + copy(dAtA[i:], m.ProofInit) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProofInit))) + i-- + dAtA[i] = 0x2a + } + if len(m.CounterpartyVersion) > 0 { + i -= len(m.CounterpartyVersion) + copy(dAtA[i:], m.CounterpartyVersion) + i = encodeVarintTx(dAtA, i, uint64(len(m.CounterpartyVersion))) + i-- + dAtA[i] = 0x22 + } + { + size, err := m.Channel.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.PreviousChannelId) > 0 { + i -= len(m.PreviousChannelId) + copy(dAtA[i:], m.PreviousChannelId) + i = encodeVarintTx(dAtA, i, uint64(len(m.PreviousChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintTx(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgChannelOpenTryResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgChannelOpenTryResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgChannelOpenTryResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.Version) > 0 { + i -= len(m.Version) + copy(dAtA[i:], m.Version) + i = encodeVarintTx(dAtA, i, uint64(len(m.Version))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgChannelOpenAck) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgChannelOpenAck) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgChannelOpenAck) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x3a + } + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + if len(m.ProofTry) > 0 { + i -= len(m.ProofTry) + copy(dAtA[i:], m.ProofTry) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProofTry))) + i-- + dAtA[i] = 0x2a + } + if len(m.CounterpartyVersion) > 0 { + i -= len(m.CounterpartyVersion) + copy(dAtA[i:], m.CounterpartyVersion) + i = encodeVarintTx(dAtA, i, uint64(len(m.CounterpartyVersion))) + i-- + dAtA[i] = 0x22 + } + if len(m.CounterpartyChannelId) > 0 { + i -= len(m.CounterpartyChannelId) + copy(dAtA[i:], m.CounterpartyChannelId) + i = encodeVarintTx(dAtA, i, uint64(len(m.CounterpartyChannelId))) + i-- + dAtA[i] = 0x1a + } + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintTx(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgChannelOpenAckResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgChannelOpenAckResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgChannelOpenAckResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgChannelOpenConfirm) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgChannelOpenConfirm) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgChannelOpenConfirm) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x2a + } + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + if len(m.ProofAck) > 0 { + i -= len(m.ProofAck) + copy(dAtA[i:], m.ProofAck) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProofAck))) + i-- + dAtA[i] = 0x1a + } + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintTx(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgChannelOpenConfirmResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgChannelOpenConfirmResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgChannelOpenConfirmResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgChannelCloseInit) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgChannelCloseInit) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgChannelCloseInit) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x1a + } + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintTx(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgChannelCloseInitResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgChannelCloseInitResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgChannelCloseInitResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgChannelCloseConfirm) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgChannelCloseConfirm) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgChannelCloseConfirm) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x2a + } + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + if len(m.ProofInit) > 0 { + i -= len(m.ProofInit) + copy(dAtA[i:], m.ProofInit) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProofInit))) + i-- + dAtA[i] = 0x1a + } + if len(m.ChannelId) > 0 { + i -= len(m.ChannelId) + copy(dAtA[i:], m.ChannelId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ChannelId))) + i-- + dAtA[i] = 0x12 + } + if len(m.PortId) > 0 { + i -= len(m.PortId) + copy(dAtA[i:], m.PortId) + i = encodeVarintTx(dAtA, i, uint64(len(m.PortId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgChannelCloseConfirmResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgChannelCloseConfirmResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgChannelCloseConfirmResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgRecvPacket) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgRecvPacket) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgRecvPacket) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x22 + } + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.ProofCommitment) > 0 { + i -= len(m.ProofCommitment) + copy(dAtA[i:], m.ProofCommitment) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProofCommitment))) + i-- + dAtA[i] = 0x12 + } + { + size, err := m.Packet.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *MsgRecvPacketResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgRecvPacketResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgRecvPacketResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Result != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.Result)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *MsgTimeout) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgTimeout) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgTimeout) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x2a + } + if m.NextSequenceRecv != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.NextSequenceRecv)) + i-- + dAtA[i] = 0x20 + } + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.ProofUnreceived) > 0 { + i -= len(m.ProofUnreceived) + copy(dAtA[i:], m.ProofUnreceived) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProofUnreceived))) + i-- + dAtA[i] = 0x12 + } + { + size, err := m.Packet.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *MsgTimeoutResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgTimeoutResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgTimeoutResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Result != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.Result)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *MsgTimeoutOnClose) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgTimeoutOnClose) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgTimeoutOnClose) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x32 + } + if m.NextSequenceRecv != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.NextSequenceRecv)) + i-- + dAtA[i] = 0x28 + } + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + if len(m.ProofClose) > 0 { + i -= len(m.ProofClose) + copy(dAtA[i:], m.ProofClose) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProofClose))) + i-- + dAtA[i] = 0x1a + } + if len(m.ProofUnreceived) > 0 { + i -= len(m.ProofUnreceived) + copy(dAtA[i:], m.ProofUnreceived) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProofUnreceived))) + i-- + dAtA[i] = 0x12 + } + { + size, err := m.Packet.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *MsgTimeoutOnCloseResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgTimeoutOnCloseResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgTimeoutOnCloseResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Result != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.Result)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *MsgAcknowledgement) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgAcknowledgement) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgAcknowledgement) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x2a + } + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + if len(m.ProofAcked) > 0 { + i -= len(m.ProofAcked) + copy(dAtA[i:], m.ProofAcked) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProofAcked))) + i-- + dAtA[i] = 0x1a + } + if len(m.Acknowledgement) > 0 { + i -= len(m.Acknowledgement) + copy(dAtA[i:], m.Acknowledgement) + i = encodeVarintTx(dAtA, i, uint64(len(m.Acknowledgement))) + i-- + dAtA[i] = 0x12 + } + { + size, err := m.Packet.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *MsgAcknowledgementResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgAcknowledgementResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgAcknowledgementResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Result != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.Result)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgChannelOpenInit) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Channel.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgChannelOpenInitResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Version) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgChannelOpenTry) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.PreviousChannelId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Channel.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.CounterpartyVersion) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ProofInit) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgChannelOpenTryResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Version) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgChannelOpenAck) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.CounterpartyChannelId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.CounterpartyVersion) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ProofTry) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgChannelOpenAckResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgChannelOpenConfirm) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ProofAck) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgChannelOpenConfirmResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgChannelCloseInit) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgChannelCloseInitResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgChannelCloseConfirm) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.PortId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ChannelId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ProofInit) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgChannelCloseConfirmResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgRecvPacket) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Packet.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.ProofCommitment) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgRecvPacketResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Result != 0 { + n += 1 + sovTx(uint64(m.Result)) + } + return n +} + +func (m *MsgTimeout) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Packet.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.ProofUnreceived) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovTx(uint64(l)) + if m.NextSequenceRecv != 0 { + n += 1 + sovTx(uint64(m.NextSequenceRecv)) + } + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgTimeoutResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Result != 0 { + n += 1 + sovTx(uint64(m.Result)) + } + return n +} + +func (m *MsgTimeoutOnClose) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Packet.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.ProofUnreceived) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ProofClose) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovTx(uint64(l)) + if m.NextSequenceRecv != 0 { + n += 1 + sovTx(uint64(m.NextSequenceRecv)) + } + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgTimeoutOnCloseResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Result != 0 { + n += 1 + sovTx(uint64(m.Result)) + } + return n +} + +func (m *MsgAcknowledgement) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Packet.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.Acknowledgement) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ProofAcked) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgAcknowledgementResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Result != 0 { + n += 1 + sovTx(uint64(m.Result)) + } + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgChannelOpenInit) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgChannelOpenInit: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgChannelOpenInit: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Channel", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Channel.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgChannelOpenInitResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgChannelOpenInitResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgChannelOpenInitResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Version = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgChannelOpenTry) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgChannelOpenTry: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgChannelOpenTry: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PreviousChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PreviousChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Channel", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Channel.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CounterpartyVersion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CounterpartyVersion = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofInit", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProofInit = append(m.ProofInit[:0], dAtA[iNdEx:postIndex]...) + if m.ProofInit == nil { + m.ProofInit = []byte{} + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgChannelOpenTryResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgChannelOpenTryResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgChannelOpenTryResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Version = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgChannelOpenAck) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgChannelOpenAck: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgChannelOpenAck: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CounterpartyChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CounterpartyChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CounterpartyVersion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CounterpartyVersion = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofTry", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProofTry = append(m.ProofTry[:0], dAtA[iNdEx:postIndex]...) + if m.ProofTry == nil { + m.ProofTry = []byte{} + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgChannelOpenAckResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgChannelOpenAckResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgChannelOpenAckResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgChannelOpenConfirm) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgChannelOpenConfirm: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgChannelOpenConfirm: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofAck", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProofAck = append(m.ProofAck[:0], dAtA[iNdEx:postIndex]...) + if m.ProofAck == nil { + m.ProofAck = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgChannelOpenConfirmResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgChannelOpenConfirmResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgChannelOpenConfirmResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgChannelCloseInit) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgChannelCloseInit: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgChannelCloseInit: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgChannelCloseInitResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgChannelCloseInitResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgChannelCloseInitResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgChannelCloseConfirm) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgChannelCloseConfirm: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgChannelCloseConfirm: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PortId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PortId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChannelId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofInit", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProofInit = append(m.ProofInit[:0], dAtA[iNdEx:postIndex]...) + if m.ProofInit == nil { + m.ProofInit = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgChannelCloseConfirmResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgChannelCloseConfirmResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgChannelCloseConfirmResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgRecvPacket) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgRecvPacket: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgRecvPacket: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Packet", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Packet.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofCommitment", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProofCommitment = append(m.ProofCommitment[:0], dAtA[iNdEx:postIndex]...) + if m.ProofCommitment == nil { + m.ProofCommitment = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgRecvPacketResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgRecvPacketResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgRecvPacketResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Result", wireType) + } + m.Result = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Result |= ResponseResultType(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgTimeout) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgTimeout: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgTimeout: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Packet", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Packet.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofUnreceived", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProofUnreceived = append(m.ProofUnreceived[:0], dAtA[iNdEx:postIndex]...) + if m.ProofUnreceived == nil { + m.ProofUnreceived = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NextSequenceRecv", wireType) + } + m.NextSequenceRecv = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.NextSequenceRecv |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgTimeoutResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgTimeoutResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgTimeoutResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Result", wireType) + } + m.Result = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Result |= ResponseResultType(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgTimeoutOnClose) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgTimeoutOnClose: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgTimeoutOnClose: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Packet", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Packet.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofUnreceived", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProofUnreceived = append(m.ProofUnreceived[:0], dAtA[iNdEx:postIndex]...) + if m.ProofUnreceived == nil { + m.ProofUnreceived = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofClose", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProofClose = append(m.ProofClose[:0], dAtA[iNdEx:postIndex]...) + if m.ProofClose == nil { + m.ProofClose = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NextSequenceRecv", wireType) + } + m.NextSequenceRecv = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.NextSequenceRecv |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgTimeoutOnCloseResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgTimeoutOnCloseResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgTimeoutOnCloseResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Result", wireType) + } + m.Result = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Result |= ResponseResultType(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgAcknowledgement) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgAcknowledgement: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgAcknowledgement: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Packet", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Packet.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Acknowledgement", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Acknowledgement = append(m.Acknowledgement[:0], dAtA[iNdEx:postIndex]...) + if m.Acknowledgement == nil { + m.Acknowledgement = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofAcked", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProofAcked = append(m.ProofAcked[:0], dAtA[iNdEx:postIndex]...) + if m.ProofAcked == nil { + m.ProofAcked = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgAcknowledgementResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgAcknowledgementResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgAcknowledgementResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Result", wireType) + } + m.Result = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Result |= ResponseResultType(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTx(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/04-channel/v2/client/cli/abci.go b/modules/core/04-channel/v2/client/cli/abci.go new file mode 100644 index 0000000..5e491ce --- /dev/null +++ b/modules/core/04-channel/v2/client/cli/abci.go @@ -0,0 +1,71 @@ +package cli + +import ( + "encoding/binary" + + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/cosmos-sdk/client" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + hostv2 "github.com/cosmos/ibc-go/v10/modules/core/24-host/v2" + ibcclient "github.com/cosmos/ibc-go/v10/modules/core/client" +) + +func queryNextSequenceSendABCI(clientCtx client.Context, channelID string) (*types.QueryNextSequenceSendResponse, error) { + key := hostv2.NextSequenceSendKey(channelID) + value, proofBz, proofHeight, err := ibcclient.QueryTendermintProof(clientCtx, key) + if err != nil { + return nil, err + } + + // check if next sequence send exists + if len(value) == 0 { + return nil, errorsmod.Wrapf(types.ErrSequenceSendNotFound, "channelID (%s)", channelID) + } + + sequence := binary.BigEndian.Uint64(value) + + return types.NewQueryNextSequenceSendResponse(sequence, proofBz, proofHeight), nil +} + +func queryPacketCommitmentABCI(clientCtx client.Context, channelID string, sequence uint64) (*types.QueryPacketCommitmentResponse, error) { + key := hostv2.PacketCommitmentKey(channelID, sequence) + value, proofBz, proofHeight, err := ibcclient.QueryTendermintProof(clientCtx, key) + if err != nil { + return nil, err + } + + // check if packet commitment exists + if len(value) == 0 { + return nil, errorsmod.Wrapf(types.ErrPacketCommitmentNotFound, "channelID (%s), sequence (%d)", channelID, sequence) + } + + return types.NewQueryPacketCommitmentResponse(value, proofBz, proofHeight), nil +} + +func queryPacketAcknowledgementABCI(clientCtx client.Context, channelID string, sequence uint64) (*types.QueryPacketAcknowledgementResponse, error) { + key := hostv2.PacketAcknowledgementKey(channelID, sequence) + value, proofBz, proofHeight, err := ibcclient.QueryTendermintProof(clientCtx, key) + if err != nil { + return nil, err + } + + // check if packet commitment exists + if len(value) == 0 { + return nil, errorsmod.Wrapf(types.ErrAcknowledgementNotFound, "channelID (%s), sequence (%d)", channelID, sequence) + } + + return types.NewQueryPacketAcknowledgementResponse(value, proofBz, proofHeight), nil +} + +func queryPacketReceiptABCI(clientCtx client.Context, channelID string, sequence uint64) (*types.QueryPacketReceiptResponse, error) { + key := hostv2.PacketReceiptKey(channelID, sequence) + + value, proofBz, proofHeight, err := ibcclient.QueryTendermintProof(clientCtx, key) + if err != nil { + return nil, err + } + + return types.NewQueryPacketReceiptResponse(value != nil, proofBz, proofHeight), nil +} diff --git a/modules/core/04-channel/v2/client/cli/cli.go b/modules/core/04-channel/v2/client/cli/cli.go new file mode 100644 index 0000000..00e3e4b --- /dev/null +++ b/modules/core/04-channel/v2/client/cli/cli.go @@ -0,0 +1,48 @@ +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" +) + +// GetQueryCmd returns the query commands for the IBC channel/v2. +func GetQueryCmd() *cobra.Command { + queryCmd := &cobra.Command{ + Use: types.SubModuleName, + Short: "IBC channel/v2 query subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + queryCmd.AddCommand( + getCmdQueryNextSequenceSend(), + getCmdQueryPacketCommitment(), + getCmdQueryPacketCommitments(), + getCmdQueryPacketAcknowledgement(), + getCmdQueryPacketReceipt(), + getCmdQueryUnreceivedPackets(), + getCmdQueryUnreceivedAcks(), + ) + + return queryCmd +} + +// NewTxCmd returns the command to submit transactions defined for IBC channel/v2. +func NewTxCmd() *cobra.Command { + txCmd := &cobra.Command{ + Use: types.SubModuleName, + Short: "IBC channel/v2 transaction subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + // TODO: Add v2 packet commands: https://github.com/cosmos/ibc-go/issues/7853 + txCmd.AddCommand() + + return txCmd +} diff --git a/modules/core/04-channel/v2/client/cli/query.go b/modules/core/04-channel/v2/client/cli/query.go new file mode 100644 index 0000000..461ec91 --- /dev/null +++ b/modules/core/04-channel/v2/client/cli/query.go @@ -0,0 +1,350 @@ +package cli + +import ( + "fmt" + "strconv" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/version" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +const ( + flagSequences = "sequences" +) + +// getCmdQueryNextSequenceSend defines the command to query a next send sequence for a given client +func getCmdQueryNextSequenceSend() *cobra.Command { + cmd := &cobra.Command{ + Use: "next-sequence-send [client-id]", + Short: "Query a next send sequence", + Long: "Query the next sequence send for a given client", + Example: fmt.Sprintf( + "%s query %s %s next-sequence-send [client-id]", version.AppName, exported.ModuleName, types.SubModuleName, + ), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + clientID := args[0] + prove, err := cmd.Flags().GetBool(flags.FlagProve) + if err != nil { + return err + } + + if prove { + res, err := queryNextSequenceSendABCI(clientCtx, clientID) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + } + + queryClient := types.NewQueryClient(clientCtx) + res, err := queryClient.NextSequenceSend(cmd.Context(), types.NewQueryNextSequenceSendRequest(clientID)) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + cmd.Flags().Bool(flags.FlagProve, true, "show proofs for the query results") + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +func getCmdQueryPacketCommitment() *cobra.Command { + cmd := &cobra.Command{ + Use: "packet-commitment [client-id] [sequence]", + Short: "Query a channel/v2 packet commitment", + Long: "Query a channel/v2 packet commitment by client-id and sequence", + Example: fmt.Sprintf( + "%s query %s %s packet-commitment [client-id] [sequence]", version.AppName, exported.ModuleName, types.SubModuleName, + ), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + clientID := args[0] + seq, err := strconv.ParseUint(args[1], 10, 64) + if err != nil { + return err + } + + prove, err := cmd.Flags().GetBool(flags.FlagProve) + if err != nil { + return err + } + + if prove { + res, err := queryPacketCommitmentABCI(clientCtx, clientID, seq) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + } + + queryClient := types.NewQueryClient(clientCtx) + res, err := queryClient.PacketCommitment(cmd.Context(), types.NewQueryPacketCommitmentRequest(clientID, seq)) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + cmd.Flags().Bool(flags.FlagProve, true, "show proofs for the query results") + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +func getCmdQueryPacketCommitments() *cobra.Command { + cmd := &cobra.Command{ + Use: "packet-commitments [client-id]", + Short: "Query all packet commitments associated with a client", + Long: "Query all packet commitments associated with a client", + Example: fmt.Sprintf("%s query %s %s packet-commitments [client-id]", version.AppName, exported.ModuleName, types.SubModuleName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + req := &types.QueryPacketCommitmentsRequest{ + ClientId: args[0], + Pagination: pageReq, + } + + res, err := queryClient.PacketCommitments(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "packet commitments associated with a client") + + return cmd +} + +func getCmdQueryPacketAcknowledgement() *cobra.Command { + cmd := &cobra.Command{ + Use: "packet-acknowledgement [client-id] [sequence]", + Short: "Query a channel/v2 packet acknowledgement", + Long: "Query a channel/v2 packet acknowledgement by client-id and sequence", + Example: fmt.Sprintf( + "%s query %s %s packet-acknowledgement [client-id] [sequence]", version.AppName, exported.ModuleName, types.SubModuleName, + ), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + clientID := args[0] + seq, err := strconv.ParseUint(args[1], 10, 64) + if err != nil { + return err + } + + prove, err := cmd.Flags().GetBool(flags.FlagProve) + if err != nil { + return err + } + + if prove { + res, err := queryPacketAcknowledgementABCI(clientCtx, clientID, seq) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + } + + queryClient := types.NewQueryClient(clientCtx) + res, err := queryClient.PacketAcknowledgement(cmd.Context(), types.NewQueryPacketAcknowledgementRequest(clientID, seq)) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + cmd.Flags().Bool(flags.FlagProve, true, "show proofs for the query results") + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +func getCmdQueryPacketReceipt() *cobra.Command { + cmd := &cobra.Command{ + Use: "packet-receipt [client-id] [sequence]", + Short: "Query a channel/v2 packet receipt", + Long: "Query a channel/v2 packet receipt by client-id and sequence", + Example: fmt.Sprintf( + "%s query %s %s packet-receipt [client-id] [sequence]", version.AppName, exported.ModuleName, types.SubModuleName, + ), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + clientID := args[0] + seq, err := strconv.ParseUint(args[1], 10, 64) + if err != nil { + return err + } + + prove, err := cmd.Flags().GetBool(flags.FlagProve) + if err != nil { + return err + } + + if prove { + res, err := queryPacketReceiptABCI(clientCtx, clientID, seq) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + } + + queryClient := types.NewQueryClient(clientCtx) + res, err := queryClient.PacketReceipt(cmd.Context(), types.NewQueryPacketReceiptRequest(clientID, seq)) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + cmd.Flags().Bool(flags.FlagProve, true, "show proofs for the query results") + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// getCmdQueryUnreceivedPackets defines the command to query all the unreceived +// packets on the receiving chain +func getCmdQueryUnreceivedPackets() *cobra.Command { + cmd := &cobra.Command{ + Use: "unreceived-packets [client-id]", + Short: "Query a channel/v2 unreceived-packets", + Long: "Query a channel/v2 unreceived-packets by client-id and sequences", + Example: fmt.Sprintf( + "%s query %s %s unreceived-packet [client-id] --sequences=1,2,3", version.AppName, exported.ModuleName, types.SubModuleName, + ), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + clientID := args[0] + seqSlice, err := cmd.Flags().GetInt64Slice(flagSequences) + if err != nil { + return err + } + + seqs := make([]uint64, len(seqSlice)) + for i := range seqSlice { + seqs[i] = uint64(seqSlice[i]) + } + + queryClient := types.NewQueryClient(clientCtx) + res, err := queryClient.UnreceivedPackets(cmd.Context(), types.NewQueryUnreceivedPacketsRequest(clientID, seqs)) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + cmd.Flags().Int64Slice(flagSequences, []int64{}, "comma separated list of packet sequence numbers") + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// getCmdQueryUnreceivedAcks defines the command to query all the unreceived acks on the original sending chain +func getCmdQueryUnreceivedAcks() *cobra.Command { + cmd := &cobra.Command{ + Use: "unreceived-acks [client-id]", + Short: "Query all the unreceived acks associated with a client", + Long: `Given a list of acknowledgement sequences from counterparty, determine if an ack on the counterparty chain has been received on the executing chain. + +The return value represents: +- Unreceived packet acknowledgement: packet commitment exists on original sending (executing) chain and ack exists on receiving chain. +`, + Example: fmt.Sprintf("%s query %s %s unreceived-acks [client-id] --sequences=1,2,3", version.AppName, exported.ModuleName, types.SubModuleName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + seqSlice, err := cmd.Flags().GetInt64Slice(flagSequences) + if err != nil { + return err + } + + seqs := make([]uint64, len(seqSlice)) + for i := range seqSlice { + seqs[i] = uint64(seqSlice[i]) + } + + req := &types.QueryUnreceivedAcksRequest{ + ClientId: args[0], + PacketAckSequences: seqs, + } + + res, err := queryClient.UnreceivedAcks(cmd.Context(), req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + cmd.Flags().Int64Slice(flagSequences, []int64{}, "comma separated list of packet sequence numbers") + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/modules/core/04-channel/v2/genesis.go b/modules/core/04-channel/v2/genesis.go new file mode 100644 index 0000000..f84f8f4 --- /dev/null +++ b/modules/core/04-channel/v2/genesis.go @@ -0,0 +1,73 @@ +package channelv2 + +import ( + "github.com/cosmos/gogoproto/proto" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/keeper" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" +) + +func InitGenesis(ctx sdk.Context, k *keeper.Keeper, gs types.GenesisState) { + // set acks + for _, ack := range gs.Acknowledgements { + k.SetPacketAcknowledgement(ctx, ack.ClientId, ack.Sequence, ack.Data) + } + + // set commits + for _, commitment := range gs.Commitments { + k.SetPacketCommitment(ctx, commitment.ClientId, commitment.Sequence, commitment.Data) + } + + // set receipts + for _, receipt := range gs.Receipts { + k.SetPacketReceipt(ctx, receipt.ClientId, receipt.Sequence) + } + + // set async packets + for _, gs := range gs.AsyncPackets { + var packet types.Packet + err := proto.Unmarshal(gs.Data, &packet) + if err != nil { + panic(err) + } + k.SetAsyncPacket(ctx, gs.ClientId, gs.Sequence, packet) + } + + // set send sequences + for _, seq := range gs.SendSequences { + k.SetNextSequenceSend(ctx, seq.ClientId, seq.Sequence) + } +} + +func ExportGenesis(ctx sdk.Context, k *keeper.Keeper) types.GenesisState { + clientStates := k.ClientKeeper.GetAllGenesisClients(ctx) + gs := types.GenesisState{ + Acknowledgements: make([]types.PacketState, 0), + Commitments: make([]types.PacketState, 0), + Receipts: make([]types.PacketState, 0), + AsyncPackets: make([]types.PacketState, 0), + SendSequences: make([]types.PacketSequence, 0), + } + for _, clientState := range clientStates { + acks := k.GetAllPacketAcknowledgementsForClient(ctx, clientState.ClientId) + gs.Acknowledgements = append(gs.Acknowledgements, acks...) + + comms := k.GetAllPacketCommitmentsForClient(ctx, clientState.ClientId) + gs.Commitments = append(gs.Commitments, comms...) + + receipts := k.GetAllPacketReceiptsForClient(ctx, clientState.ClientId) + gs.Receipts = append(gs.Receipts, receipts...) + + asyncPackets := k.GetAllAsyncPacketsForClient(ctx, clientState.ClientId) + gs.AsyncPackets = append(gs.AsyncPackets, asyncPackets...) + + seq, ok := k.GetNextSequenceSend(ctx, clientState.ClientId) + if ok { + gs.SendSequences = append(gs.SendSequences, types.NewPacketSequence(clientState.ClientId, seq)) + } + } + + return gs +} diff --git a/modules/core/04-channel/v2/genesis_test.go b/modules/core/04-channel/v2/genesis_test.go new file mode 100644 index 0000000..010f8a1 --- /dev/null +++ b/modules/core/04-channel/v2/genesis_test.go @@ -0,0 +1,76 @@ +package channelv2_test + +import ( + proto "github.com/cosmos/gogoproto/proto" + + channelv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + mockv2 "github.com/cosmos/ibc-go/v10/testing/mock/v2" +) + +// TestInitExportGenesis tests the import and export flow for the channel v2 keeper. +func (suite *ModuleTestSuite) TestInitExportGenesis() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + path2 := ibctesting.NewPath(suite.chainA, suite.chainC) + path2.SetupV2() + + app := suite.chainA.App + + emptyGenesis := types.DefaultGenesisState() + + // create a valid genesis state that uses the client keepers existing client IDs + clientStates := app.GetIBCKeeper().ClientKeeper.GetAllGenesisClients(suite.chainA.GetContext()) + validGs := types.DefaultGenesisState() + for i, clientState := range clientStates { + ack := types.NewPacketState(clientState.ClientId, uint64(i+1), []byte("ack")) + receipt := types.NewPacketState(clientState.ClientId, uint64(i+1), []byte{byte(0x2)}) + commitment := types.NewPacketState(clientState.ClientId, uint64(i+1), []byte("commit_hash")) + seq := types.NewPacketSequence(clientState.ClientId, uint64(i+1)) + + packet := types.NewPacket( + uint64(i+1), + clientState.ClientId, + clientState.ClientId, + uint64(suite.chainA.GetContext().BlockTime().Unix()), + mockv2.NewMockPayload("src", "dst"), + ) + bz, err := proto.Marshal(&packet) + suite.Require().NoError(err) + asyncPacket := types.NewPacketState(clientState.ClientId, uint64(i+1), bz) + + validGs.Acknowledgements = append(validGs.Acknowledgements, ack) + validGs.Receipts = append(validGs.Receipts, receipt) + validGs.Commitments = append(validGs.Commitments, commitment) + validGs.SendSequences = append(validGs.SendSequences, seq) + validGs.AsyncPackets = append(validGs.AsyncPackets, asyncPacket) + emptyGenesis.SendSequences = append(emptyGenesis.SendSequences, seq) + } + + tests := []struct { + name string + genState types.GenesisState + }{ + { + name: "no modifications genesis", + genState: emptyGenesis, + }, + { + name: "valid", + genState: validGs, + }, + } + + for _, tt := range tests { + suite.Run(tt.name, func() { + channelV2Keeper := app.GetIBCKeeper().ChannelKeeperV2 + + channelv2.InitGenesis(suite.chainA.GetContext(), channelV2Keeper, tt.genState) + + exported := channelv2.ExportGenesis(suite.chainA.GetContext(), channelV2Keeper) + suite.Require().Equal(tt.genState, exported) + }) + } +} diff --git a/modules/core/04-channel/v2/keeper/events.go b/modules/core/04-channel/v2/keeper/events.go new file mode 100644 index 0000000..cfc8818 --- /dev/null +++ b/modules/core/04-channel/v2/keeper/events.go @@ -0,0 +1,133 @@ +package keeper + +import ( + "encoding/hex" + "fmt" + + "github.com/cosmos/gogoproto/proto" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" +) + +// emitSendPacketEvents emits events for the SendPacket handler. +func emitSendPacketEvents(ctx sdk.Context, packet types.Packet) { + encodedPacket, err := proto.Marshal(&packet) + if err != nil { + panic(err) + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeSendPacket, + sdk.NewAttribute(types.AttributeKeySrcClient, packet.SourceClient), + sdk.NewAttribute(types.AttributeKeyDstClient, packet.DestinationClient), + sdk.NewAttribute(types.AttributeKeySequence, fmt.Sprintf("%d", packet.Sequence)), + sdk.NewAttribute(types.AttributeKeyTimeoutTimestamp, fmt.Sprintf("%d", packet.TimeoutTimestamp)), + sdk.NewAttribute(types.AttributeKeyEncodedPacketHex, hex.EncodeToString(encodedPacket)), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitRecvPacketEvents emits events for the RecvPacket handler. +func emitRecvPacketEvents(ctx sdk.Context, packet types.Packet) { + encodedPacket, err := proto.Marshal(&packet) + if err != nil { + panic(err) + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeRecvPacket, + sdk.NewAttribute(types.AttributeKeySrcClient, packet.SourceClient), + sdk.NewAttribute(types.AttributeKeyDstClient, packet.DestinationClient), + sdk.NewAttribute(types.AttributeKeySequence, fmt.Sprintf("%d", packet.Sequence)), + sdk.NewAttribute(types.AttributeKeyTimeoutTimestamp, fmt.Sprintf("%d", packet.TimeoutTimestamp)), + sdk.NewAttribute(types.AttributeKeyEncodedPacketHex, hex.EncodeToString(encodedPacket)), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitWriteAcknowledgementEvents emits events for WriteAcknowledgement. +func emitWriteAcknowledgementEvents(ctx sdk.Context, packet types.Packet, ack types.Acknowledgement) { + encodedPacket, err := proto.Marshal(&packet) + if err != nil { + panic(err) + } + + encodedAck, err := proto.Marshal(&ack) + if err != nil { + panic(err) + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeWriteAck, + sdk.NewAttribute(types.AttributeKeySrcClient, packet.SourceClient), + sdk.NewAttribute(types.AttributeKeyDstClient, packet.DestinationClient), + sdk.NewAttribute(types.AttributeKeySequence, fmt.Sprintf("%d", packet.Sequence)), + sdk.NewAttribute(types.AttributeKeyTimeoutTimestamp, fmt.Sprintf("%d", packet.TimeoutTimestamp)), + sdk.NewAttribute(types.AttributeKeyEncodedPacketHex, hex.EncodeToString(encodedPacket)), + sdk.NewAttribute(types.AttributeKeyEncodedAckHex, hex.EncodeToString(encodedAck)), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitAcknowledgePacketEvents emits events for the AcknowledgePacket handler. +func emitAcknowledgePacketEvents(ctx sdk.Context, packet types.Packet) { + encodedPacket, err := proto.Marshal(&packet) + if err != nil { + panic(err) + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeAcknowledgePacket, + sdk.NewAttribute(types.AttributeKeySrcClient, packet.SourceClient), + sdk.NewAttribute(types.AttributeKeyDstClient, packet.DestinationClient), + sdk.NewAttribute(types.AttributeKeySequence, fmt.Sprintf("%d", packet.Sequence)), + sdk.NewAttribute(types.AttributeKeyTimeoutTimestamp, fmt.Sprintf("%d", packet.TimeoutTimestamp)), + sdk.NewAttribute(types.AttributeKeyEncodedPacketHex, hex.EncodeToString(encodedPacket)), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitTimeoutPacketEvents emits events for the TimeoutPacket handler. +func emitTimeoutPacketEvents(ctx sdk.Context, packet types.Packet) { + encodedPacket, err := proto.Marshal(&packet) + if err != nil { + panic(err) + } + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeTimeoutPacket, + sdk.NewAttribute(types.AttributeKeySrcClient, packet.SourceClient), + sdk.NewAttribute(types.AttributeKeyDstClient, packet.DestinationClient), + sdk.NewAttribute(types.AttributeKeySequence, fmt.Sprintf("%d", packet.Sequence)), + sdk.NewAttribute(types.AttributeKeyTimeoutTimestamp, fmt.Sprintf("%d", packet.TimeoutTimestamp)), + sdk.NewAttribute(types.AttributeKeyEncodedPacketHex, hex.EncodeToString(encodedPacket)), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} diff --git a/modules/core/04-channel/v2/keeper/export_test.go b/modules/core/04-channel/v2/keeper/export_test.go new file mode 100644 index 0000000..de1250d --- /dev/null +++ b/modules/core/04-channel/v2/keeper/export_test.go @@ -0,0 +1,70 @@ +package keeper + +/* + This file is to allow for unexported functions to be accessible to the testing package. +*/ + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +func (k *Keeper) SendPacketTest( + ctx sdk.Context, + sourceChannel string, + timeoutTimestamp uint64, + payloads []types.Payload, +) (uint64, string, error) { + return k.sendPacket( + ctx, + sourceChannel, + timeoutTimestamp, + payloads, + ) +} + +func (k *Keeper) RecvPacketTest( + ctx sdk.Context, + packet types.Packet, + proof []byte, + proofHeight exported.Height, +) error { + return k.recvPacket( + ctx, + packet, + proof, + proofHeight, + ) +} + +func (k *Keeper) AcknowledgePacketTest( + ctx sdk.Context, + packet types.Packet, + acknowledgement types.Acknowledgement, + proof []byte, + proofHeight exported.Height, +) error { + return k.acknowledgePacket( + ctx, + packet, + acknowledgement, + proof, + proofHeight, + ) +} + +func (k *Keeper) TimeoutPacketTest( + ctx sdk.Context, + packet types.Packet, + proof []byte, + proofHeight exported.Height, +) error { + return k.timeoutPacket( + ctx, + packet, + proof, + proofHeight, + ) +} diff --git a/modules/core/04-channel/v2/keeper/grpc_query.go b/modules/core/04-channel/v2/keeper/grpc_query.go new file mode 100644 index 0000000..a36d7b9 --- /dev/null +++ b/modules/core/04-channel/v2/keeper/grpc_query.go @@ -0,0 +1,322 @@ +package keeper + +import ( + "context" + "strings" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/store/prefix" + + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/query" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + hostv2 "github.com/cosmos/ibc-go/v10/modules/core/24-host/v2" +) + +var _ types.QueryServer = (*queryServer)(nil) + +// queryServer implements the channel/v2 types.QueryServer interface. +type queryServer struct { + *Keeper +} + +// NewQueryServer returns a new types.QueryServer implementation. +func NewQueryServer(k *Keeper) types.QueryServer { + return &queryServer{ + Keeper: k, + } +} + +// NextSequenceSend implements the Query/NextSequenceSend gRPC method +func (q *queryServer) NextSequenceSend(goCtx context.Context, req *types.QueryNextSequenceSendRequest) (*types.QueryNextSequenceSendResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := host.ClientIdentifierValidator(req.ClientId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + sequence, found := q.GetNextSequenceSend(ctx, req.ClientId) + if !found { + return nil, status.Error( + codes.NotFound, + errorsmod.Wrapf(types.ErrSequenceSendNotFound, "client-id %s", req.ClientId).Error(), + ) + } + return types.NewQueryNextSequenceSendResponse(sequence, nil, clienttypes.GetSelfHeight(ctx)), nil +} + +// PacketCommitment implements the Query/PacketCommitment gRPC method. +func (q *queryServer) PacketCommitment(goCtx context.Context, req *types.QueryPacketCommitmentRequest) (*types.QueryPacketCommitmentResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := host.ClientIdentifierValidator(req.ClientId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + if req.Sequence == 0 { + return nil, status.Error(codes.InvalidArgument, "packet sequence cannot be 0") + } + + commitment := q.GetPacketCommitment(ctx, req.ClientId, req.Sequence) + if len(commitment) == 0 { + return nil, status.Error(codes.NotFound, "packet commitment hash not found") + } + + return types.NewQueryPacketCommitmentResponse(commitment, nil, clienttypes.GetSelfHeight(ctx)), nil +} + +// PacketCommitments implements the Query/PacketCommitments gRPC method +func (q *queryServer) PacketCommitments(goCtx context.Context, req *types.QueryPacketCommitmentsRequest) (*types.QueryPacketCommitmentsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := host.ClientIdentifierValidator(req.ClientId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + var commitments []*types.PacketState + store := prefix.NewStore(runtime.KVStoreAdapter(q.storeService.OpenKVStore(goCtx)), hostv2.PacketCommitmentPrefixKey(req.ClientId)) + + pageRes, err := query.Paginate(store, req.Pagination, func(key, value []byte) error { + keySplit := strings.Split(string(key), "/") + + sequence := sdk.BigEndianToUint64([]byte(keySplit[len(keySplit)-1])) + if sequence == 0 { + return types.ErrInvalidPacket + } + + commitment := types.NewPacketState(req.ClientId, sequence, value) + commitments = append(commitments, &commitment) + return nil + }) + if err != nil { + return nil, err + } + + selfHeight := clienttypes.GetSelfHeight(ctx) + return &types.QueryPacketCommitmentsResponse{ + Commitments: commitments, + Pagination: pageRes, + Height: selfHeight, + }, nil +} + +// PacketAcknowledgement implements the Query/PacketAcknowledgement gRPC method. +func (q *queryServer) PacketAcknowledgement(goCtx context.Context, req *types.QueryPacketAcknowledgementRequest) (*types.QueryPacketAcknowledgementResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := host.ClientIdentifierValidator(req.ClientId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + if req.Sequence == 0 { + return nil, status.Error(codes.InvalidArgument, "packet sequence cannot be 0") + } + + acknowledgement := q.GetPacketAcknowledgement(ctx, req.ClientId, req.Sequence) + if len(acknowledgement) == 0 { + return nil, status.Error(codes.NotFound, "packet acknowledgement hash not found") + } + + return types.NewQueryPacketAcknowledgementResponse(acknowledgement, nil, clienttypes.GetSelfHeight(ctx)), nil +} + +// PacketAcknowledgements implements the Query/PacketAcknowledgements gRPC method. +func (q *queryServer) PacketAcknowledgements(goCtx context.Context, req *types.QueryPacketAcknowledgementsRequest) (*types.QueryPacketAcknowledgementsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := host.ClientIdentifierValidator(req.ClientId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + var acks []*types.PacketState + store := prefix.NewStore(runtime.KVStoreAdapter(q.storeService.OpenKVStore(goCtx)), hostv2.PacketAcknowledgementPrefixKey(req.ClientId)) + + // if a list of packet sequences is provided then query for each specific ack and return a list <= len(req.PacketCommitmentSequences) + // otherwise, maintain previous behaviour and perform paginated query + for _, seq := range req.PacketCommitmentSequences { + acknowledgement := q.GetPacketAcknowledgement(ctx, req.ClientId, seq) + if len(acknowledgement) == 0 { + continue + } + + ack := types.NewPacketState(req.ClientId, seq, acknowledgement) + acks = append(acks, &ack) + } + + if len(req.PacketCommitmentSequences) > 0 { + selfHeight := clienttypes.GetSelfHeight(ctx) + return &types.QueryPacketAcknowledgementsResponse{ + Acknowledgements: acks, + Pagination: nil, + Height: selfHeight, + }, nil + } + + pageRes, err := query.Paginate(store, req.Pagination, func(key, value []byte) error { + keySplit := strings.Split(string(key), "/") + + sequence := sdk.BigEndianToUint64([]byte(keySplit[len(keySplit)-1])) + if sequence == 0 { + return types.ErrInvalidPacket + } + + ack := types.NewPacketState(req.ClientId, sequence, value) + acks = append(acks, &ack) + + return nil + }) + if err != nil { + return nil, err + } + + return &types.QueryPacketAcknowledgementsResponse{ + Acknowledgements: acks, + Pagination: pageRes, + Height: clienttypes.GetSelfHeight(ctx), + }, nil +} + +// PacketReceipt implements the Query/PacketReceipt gRPC method. +func (q *queryServer) PacketReceipt(goCtx context.Context, req *types.QueryPacketReceiptRequest) (*types.QueryPacketReceiptResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := host.ClientIdentifierValidator(req.ClientId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + if req.Sequence == 0 { + return nil, status.Error(codes.InvalidArgument, "packet sequence cannot be 0") + } + + hasReceipt := q.HasPacketReceipt(ctx, req.ClientId, req.Sequence) + + return types.NewQueryPacketReceiptResponse(hasReceipt, nil, clienttypes.GetSelfHeight(ctx)), nil +} + +// UnreceivedPackets implements the Query/UnreceivedPackets gRPC method. Given +// a list of counterparty packet commitments, the querier checks if the packet +// has already been received by checking if a receipt exists on this +// chain for the packet sequence. All packets that haven't been received yet +// are returned in the response +// Usage: To use this method correctly, first query all packet commitments on +// the sending chain using the Query/PacketCommitments gRPC method. +// Then input the returned sequences into the QueryUnreceivedPacketsRequest +// and send the request to this Query/UnreceivedPackets on the **receiving** +// chain. This gRPC method will then return the list of packet sequences that +// are yet to be received on the receiving chain. +// +// NOTE: The querier makes the assumption that the provided list of packet +// commitments is correct and will not function properly if the list +// is not up to date. Ideally the query height should equal the latest height +// on the counterparty's client which represents this chain. +func (q *queryServer) UnreceivedPackets(goCtx context.Context, req *types.QueryUnreceivedPacketsRequest) (*types.QueryUnreceivedPacketsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := host.ClientIdentifierValidator(req.ClientId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + var unreceivedSequences []uint64 + for i, seq := range req.Sequences { + // filter for invalid sequences to ensure they are not included in the response value. + if seq == 0 { + return nil, status.Errorf(codes.InvalidArgument, "packet sequence %d cannot be 0", i) + } + + // if the packet receipt does not exist, then it is unreceived + if !q.HasPacketReceipt(ctx, req.ClientId, seq) { + unreceivedSequences = append(unreceivedSequences, seq) + } + } + + selfHeight := clienttypes.GetSelfHeight(ctx) + return &types.QueryUnreceivedPacketsResponse{ + Sequences: unreceivedSequences, + Height: selfHeight, + }, nil +} + +// UnreceivedAcks implements the Query/UnreceivedAcks gRPC method. Given +// a list of counterparty packet acknowledgements, the querier checks if the packet +// has already been received by checking if the packet commitment still exists on this +// chain (original sender) for the packet sequence. +// All acknowledgmeents that haven't been received yet are returned in the response. +// Usage: To use this method correctly, first query all packet acknowledgements on +// the original receiving chain (ie the chain that wrote the acks) using the Query/PacketAcknowledgements gRPC method. +// Then input the returned sequences into the QueryUnreceivedAcksRequest +// and send the request to this Query/UnreceivedAcks on the **original sending** +// chain. This gRPC method will then return the list of packet sequences whose +// acknowledgements are already written on the receiving chain but haven't yet +// been received back to the sending chain. +// +// NOTE: The querier makes the assumption that the provided list of packet +// acknowledgements is correct and will not function properly if the list +// is not up to date. Ideally the query height should equal the latest height +// on the counterparty's client which represents this chain. +func (q *queryServer) UnreceivedAcks(goCtx context.Context, req *types.QueryUnreceivedAcksRequest) (*types.QueryUnreceivedAcksResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := host.ClientIdentifierValidator(req.ClientId); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + var unreceivedSequences []uint64 + + for _, seq := range req.PacketAckSequences { + if seq == 0 { + return nil, status.Error(codes.InvalidArgument, "packet sequence cannot be 0") + } + + // if packet commitment still exists on the original sending chain, then packet ack has not been received + // since processing the ack will delete the packet commitment + if commitment := q.GetPacketCommitment(ctx, req.ClientId, seq); len(commitment) != 0 { + unreceivedSequences = append(unreceivedSequences, seq) + } + + } + + selfHeight := clienttypes.GetSelfHeight(ctx) + return &types.QueryUnreceivedAcksResponse{ + Sequences: unreceivedSequences, + Height: selfHeight, + }, nil +} diff --git a/modules/core/04-channel/v2/keeper/grpc_query_test.go b/modules/core/04-channel/v2/keeper/grpc_query_test.go new file mode 100644 index 0000000..a061728 --- /dev/null +++ b/modules/core/04-channel/v2/keeper/grpc_query_test.go @@ -0,0 +1,815 @@ +package keeper_test + +import ( + "fmt" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/cosmos/cosmos-sdk/types/query" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/keeper" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *KeeperTestSuite) TestQueryPacketCommitment() { + var ( + expCommitment []byte + path *ibctesting.Path + req *types.QueryPacketCommitmentRequest + ) + + testCases := []struct { + msg string + malleate func() + expError error + }{ + { + "success", + func() { + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + expCommitment = []byte("commitmentHash") + suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SetPacketCommitment(suite.chainA.GetContext(), path.EndpointA.ClientID, 1, expCommitment) + + req = &types.QueryPacketCommitmentRequest{ + ClientId: path.EndpointA.ClientID, + Sequence: 1, + } + }, + nil, + }, + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid channel ID", + func() { + req = &types.QueryPacketCommitmentRequest{ + ClientId: "", + Sequence: 1, + } + }, + status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"), + }, + { + "invalid sequence", + func() { + req = &types.QueryPacketCommitmentRequest{ + ClientId: ibctesting.FirstClientID, + Sequence: 0, + } + }, + status.Error(codes.InvalidArgument, "packet sequence cannot be 0"), + }, + { + "commitment not found", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + req = &types.QueryPacketCommitmentRequest{ + ClientId: path.EndpointA.ClientID, + Sequence: 1, + } + }, + status.Error(codes.NotFound, "packet commitment hash not found"), + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + + queryServer := keeper.NewQueryServer(suite.chainA.GetSimApp().IBCKeeper.ChannelKeeperV2) + res, err := queryServer.PacketCommitment(suite.chainA.GetContext(), req) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expCommitment, res.Commitment) + } else { + suite.Require().ErrorIs(err, tc.expError) + suite.Require().Nil(res) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryPacketCommitments() { + var ( + req *types.QueryPacketCommitmentsRequest + expCommitments = []*types.PacketState{} + ) + + testCases := []struct { + msg string + malleate func() + expError error + }{ + { + "success", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + expCommitments = make([]*types.PacketState, 0, 10) // reset expected commitments + for i := uint64(1); i <= 10; i++ { + pktStateCommitment := types.NewPacketState(path.EndpointA.ClientID, i, fmt.Appendf(nil, "hash_%d", i)) + suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SetPacketCommitment(suite.chainA.GetContext(), pktStateCommitment.ClientId, pktStateCommitment.Sequence, pktStateCommitment.Data) + expCommitments = append(expCommitments, &pktStateCommitment) + } + + req = &types.QueryPacketCommitmentsRequest{ + ClientId: path.EndpointA.ClientID, + Pagination: &query.PageRequest{ + Key: nil, + Limit: 11, + CountTotal: true, + }, + } + }, + nil, + }, + { + "success: with pagination", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + expCommitments = make([]*types.PacketState, 0, 10) // reset expected commitments + for i := uint64(1); i <= 10; i++ { + pktStateCommitment := types.NewPacketState(path.EndpointA.ClientID, i, fmt.Appendf(nil, "hash_%d", i)) + suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SetPacketCommitment(suite.chainA.GetContext(), pktStateCommitment.ClientId, pktStateCommitment.Sequence, pktStateCommitment.Data) + expCommitments = append(expCommitments, &pktStateCommitment) + } + + limit := uint64(5) + expCommitments = expCommitments[:limit] + + req = &types.QueryPacketCommitmentsRequest{ + ClientId: path.EndpointA.ClientID, + Pagination: &query.PageRequest{ + Key: nil, + Limit: limit, + CountTotal: true, + }, + } + }, + nil, + }, + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid client ID", + func() { + req = &types.QueryPacketCommitmentsRequest{ + ClientId: "", + } + }, + status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"), + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.GetSimApp().IBCKeeper.ChannelKeeperV2) + res, err := queryServer.PacketCommitments(ctx, req) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expCommitments, res.Commitments) + } else { + suite.Require().Error(err) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryPacketAcknowledgement() { + var ( + expAcknowledgement []byte + path *ibctesting.Path + req *types.QueryPacketAcknowledgementRequest + ) + + testCases := []struct { + msg string + malleate func() + expError error + }{ + { + "success", + func() { + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + expAcknowledgement = []byte("acknowledgementHash") + suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SetPacketAcknowledgement(suite.chainA.GetContext(), path.EndpointA.ClientID, 1, expAcknowledgement) + + req = &types.QueryPacketAcknowledgementRequest{ + ClientId: path.EndpointA.ClientID, + Sequence: 1, + } + }, + nil, + }, + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid client ID", + func() { + req = &types.QueryPacketAcknowledgementRequest{ + ClientId: "", + Sequence: 1, + } + }, + status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"), + }, + { + "invalid sequence", + func() { + req = &types.QueryPacketAcknowledgementRequest{ + ClientId: ibctesting.FirstClientID, + Sequence: 0, + } + }, + status.Error(codes.InvalidArgument, "packet sequence cannot be 0"), + }, + { + "acknowledgement not found", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + req = &types.QueryPacketAcknowledgementRequest{ + ClientId: path.EndpointA.ClientID, + Sequence: 1, + } + }, + status.Error(codes.NotFound, "packet acknowledgement hash not found"), + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + + queryServer := keeper.NewQueryServer(suite.chainA.GetSimApp().IBCKeeper.ChannelKeeperV2) + res, err := queryServer.PacketAcknowledgement(suite.chainA.GetContext(), req) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expAcknowledgement, res.Acknowledgement) + } else { + suite.Require().ErrorIs(err, tc.expError) + suite.Require().Nil(res) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryPacketAcknowledgements() { + var ( + req *types.QueryPacketAcknowledgementsRequest + expAcknowledgements = []*types.PacketState{} + ) + + testCases := []struct { + msg string + malleate func() + expError error + }{ + { + "success: with PacketCommitmentSequences", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + var commitments []uint64 + + for i := uint64(0); i < 100; i++ { + ack := types.NewPacketState(path.EndpointA.ClientID, i, fmt.Appendf(nil, "hash_%d", i)) + suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SetPacketAcknowledgement(suite.chainA.GetContext(), ack.ClientId, ack.Sequence, ack.Data) + + if i < 10 { // populate the store with 100 and query for 10 specific acks + expAcknowledgements = append(expAcknowledgements, &ack) + commitments = append(commitments, ack.Sequence) + } + } + + req = &types.QueryPacketAcknowledgementsRequest{ + ClientId: path.EndpointA.ClientID, + PacketCommitmentSequences: commitments, + Pagination: nil, + } + }, + nil, + }, + { + "success: with pagination", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + expAcknowledgements = make([]*types.PacketState, 0, 10) + + for i := uint64(1); i <= 10; i++ { + ack := types.NewPacketState(path.EndpointA.ClientID, i, fmt.Appendf(nil, "hash_%d", i)) + suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SetPacketAcknowledgement(suite.chainA.GetContext(), ack.ClientId, ack.Sequence, ack.Data) + expAcknowledgements = append(expAcknowledgements, &ack) + } + + req = &types.QueryPacketAcknowledgementsRequest{ + ClientId: path.EndpointA.ClientID, + Pagination: &query.PageRequest{ + Key: nil, + Limit: 11, + CountTotal: true, + }, + } + }, + nil, + }, + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid ID", + func() { + req = &types.QueryPacketAcknowledgementsRequest{ + ClientId: "", + } + }, + status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"), + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ChannelKeeperV2) + res, err := queryServer.PacketAcknowledgements(ctx, req) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expAcknowledgements, res.Acknowledgements) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryPacketReceipt() { + var ( + expReceipt bool + path *ibctesting.Path + req *types.QueryPacketReceiptRequest + ) + + testCases := []struct { + msg string + malleate func() + expError error + }{ + { + "success with receipt", + func() { + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SetPacketReceipt(suite.chainA.GetContext(), path.EndpointA.ClientID, 1) + + expReceipt = true + req = &types.QueryPacketReceiptRequest{ + ClientId: path.EndpointA.ClientID, + Sequence: 1, + } + }, + nil, + }, + { + "success with no receipt", + func() { + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + expReceipt = false + req = &types.QueryPacketReceiptRequest{ + ClientId: path.EndpointA.ClientID, + Sequence: 1, + } + }, + nil, + }, + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid client ID", + func() { + req = &types.QueryPacketReceiptRequest{ + ClientId: "", + Sequence: 1, + } + }, + status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"), + }, + { + "invalid sequence", + func() { + req = &types.QueryPacketReceiptRequest{ + ClientId: ibctesting.FirstClientID, + Sequence: 0, + } + }, + status.Error(codes.InvalidArgument, "packet sequence cannot be 0"), + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + + queryServer := keeper.NewQueryServer(suite.chainA.GetSimApp().IBCKeeper.ChannelKeeperV2) + res, err := queryServer.PacketReceipt(suite.chainA.GetContext(), req) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expReceipt, res.Received) + } else { + suite.Require().ErrorIs(err, tc.expError) + suite.Require().Nil(res) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryNextSequenceSend() { + var ( + req *types.QueryNextSequenceSendRequest + expSeq uint64 + ) + + testCases := []struct { + msg string + malleate func() + expError error + }{ + { + "success", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.Setup() + + expSeq = 42 + seq := uint64(42) + suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SetNextSequenceSend(suite.chainA.GetContext(), path.EndpointA.ClientID, seq) + req = types.NewQueryNextSequenceSendRequest(path.EndpointA.ClientID) + }, + nil, + }, + { + "req is nil", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid client ID", + func() { + req = types.NewQueryNextSequenceSendRequest("") + }, + status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"), + }, + { + "sequence send not found", + func() { + req = types.NewQueryNextSequenceSendRequest(ibctesting.FirstClientID) + }, + status.Error(codes.NotFound, fmt.Sprintf("client-id %s: sequence send not found", ibctesting.FirstClientID)), + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ChannelKeeperV2) + res, err := queryServer.NextSequenceSend(ctx, req) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expSeq, res.NextSequenceSend) + } else { + suite.Require().ErrorIs(err, tc.expError) + suite.Require().Nil(res) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryUnreceivedPackets() { + var ( + expSeq []uint64 + path *ibctesting.Path + req *types.QueryUnreceivedPacketsRequest + ) + + testCases := []struct { + msg string + malleate func() + expError error + }{ + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid client ID", + func() { + req = &types.QueryUnreceivedPacketsRequest{ + ClientId: "", + } + }, + status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"), + }, + { + "invalid seq", + func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + req = &types.QueryUnreceivedPacketsRequest{ + ClientId: path.EndpointA.ClientID, + Sequences: []uint64{0}, + } + }, + status.Error(codes.InvalidArgument, "packet sequence 0 cannot be 0"), + }, + { + "basic success empty packet commitments", + func() { + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + expSeq = []uint64(nil) + req = &types.QueryUnreceivedPacketsRequest{ + ClientId: path.EndpointA.ClientID, + Sequences: []uint64{}, + } + }, + nil, + }, + { + "basic success unreceived packet commitments", + func() { + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + // no ack exists + + expSeq = []uint64{1} + req = &types.QueryUnreceivedPacketsRequest{ + ClientId: path.EndpointA.ClientID, + Sequences: []uint64{1}, + } + }, + nil, + }, + { + "basic success unreceived packet commitments, nothing to relay", + func() { + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SetPacketReceipt(suite.chainA.GetContext(), path.EndpointA.ClientID, 1) + + expSeq = []uint64(nil) + req = &types.QueryUnreceivedPacketsRequest{ + ClientId: path.EndpointA.ClientID, + Sequences: []uint64{1}, + } + }, + nil, + }, + { + "success multiple unreceived packet commitments", + func() { + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + expSeq = []uint64(nil) // reset + packetCommitments := []uint64{} + + // set packet receipt for every other sequence + for seq := uint64(1); seq < 10; seq++ { + packetCommitments = append(packetCommitments, seq) + + if seq%2 == 0 { + suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SetPacketReceipt(suite.chainA.GetContext(), path.EndpointA.ClientID, seq) + } else { + expSeq = append(expSeq, seq) + } + } + + req = &types.QueryUnreceivedPacketsRequest{ + ClientId: path.EndpointA.ClientID, + Sequences: packetCommitments, + } + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ChannelKeeperV2) + res, err := queryServer.UnreceivedPackets(ctx, req) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expSeq, res.Sequences) + } else { + suite.Require().ErrorIs(err, tc.expError) + suite.Require().Error(err) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryUnreceivedAcks() { + var ( + path *ibctesting.Path + req *types.QueryUnreceivedAcksRequest + expSeq = []uint64{} + ) + + testCases := []struct { + msg string + malleate func() + expError error + }{ + { + "success", + func() { + expSeq = []uint64(nil) + req = &types.QueryUnreceivedAcksRequest{ + ClientId: path.EndpointA.ClientID, + PacketAckSequences: []uint64{1}, + } + }, + nil, + }, + { + "success: single unreceived packet ack", + func() { + suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SetPacketCommitment(suite.chainA.GetContext(), path.EndpointA.ClientID, 1, []byte("commitment")) + + expSeq = []uint64{1} + req = &types.QueryUnreceivedAcksRequest{ + ClientId: path.EndpointA.ClientID, + PacketAckSequences: []uint64{1}, + } + }, + nil, + }, + { + "success: multiple unreceived packet acknowledgements", + func() { + expSeq = []uint64{} // reset + packetAcks := []uint64{} + + // set packet commitment for every other sequence + for seq := uint64(1); seq < 10; seq++ { + packetAcks = append(packetAcks, seq) + + if seq%2 == 0 { + suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SetPacketCommitment(suite.chainA.GetContext(), path.EndpointA.ClientID, seq, []byte("commitement")) + expSeq = append(expSeq, seq) + } + } + + req = &types.QueryUnreceivedAcksRequest{ + ClientId: path.EndpointA.ClientID, + PacketAckSequences: packetAcks, + } + }, + nil, + }, + { + "empty request", + func() { + req = nil + }, + status.Error(codes.InvalidArgument, "empty request"), + }, + { + "invalid client ID", + func() { + req = &types.QueryUnreceivedAcksRequest{ + ClientId: "", + } + }, + status.Error(codes.InvalidArgument, "identifier cannot be blank: invalid identifier"), + }, + { + "invalid seq", + func() { + req = &types.QueryUnreceivedAcksRequest{ + ClientId: path.EndpointA.ClientID, + PacketAckSequences: []uint64{0}, + } + }, + status.Error(codes.InvalidArgument, "packet sequence cannot be 0"), + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + tc.malleate() + ctx := suite.chainA.GetContext() + + queryServer := keeper.NewQueryServer(suite.chainA.App.GetIBCKeeper().ChannelKeeperV2) + res, err := queryServer.UnreceivedAcks(ctx, req) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(expSeq, res.Sequences) + } else { + suite.Require().ErrorIs(err, tc.expError) + suite.Require().Nil(res) + } + }) + } +} diff --git a/modules/core/04-channel/v2/keeper/keeper.go b/modules/core/04-channel/v2/keeper/keeper.go new file mode 100644 index 0000000..8c4af18 --- /dev/null +++ b/modules/core/04-channel/v2/keeper/keeper.go @@ -0,0 +1,263 @@ +package keeper + +import ( + "bytes" + + corestore "cosmossdk.io/core/store" + "cosmossdk.io/log" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + + clientv2keeper "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/keeper" + connectionkeeper "github.com/cosmos/ibc-go/v10/modules/core/03-connection/keeper" + channelkeeperv1 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/keeper" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + hostv2 "github.com/cosmos/ibc-go/v10/modules/core/24-host/v2" + "github.com/cosmos/ibc-go/v10/modules/core/api" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// Keeper defines the channel keeper v2. +type Keeper struct { + storeService corestore.KVStoreService + cdc codec.BinaryCodec + ClientKeeper types.ClientKeeper + // clientV2Keeper is used for counterparty access. + clientV2Keeper *clientv2keeper.Keeper + + // channelKeeperV1 is used for channel aliasing only. + channelKeeperV1 *channelkeeperv1.Keeper + connectionKeeper *connectionkeeper.Keeper + + // Router is used to route messages to the appropriate module callbacks + // NOTE: it must be explicitly set before usage. + Router *api.Router +} + +// NewKeeper creates a new channel v2 keeper +func NewKeeper( + cdc codec.BinaryCodec, + storeService corestore.KVStoreService, + clientKeeper types.ClientKeeper, + clientV2Keeper *clientv2keeper.Keeper, + channelKeeperV1 *channelkeeperv1.Keeper, + connectionKeeper *connectionkeeper.Keeper, +) *Keeper { + return &Keeper{ + storeService: storeService, + cdc: cdc, + channelKeeperV1: channelKeeperV1, + clientV2Keeper: clientV2Keeper, + connectionKeeper: connectionKeeper, + ClientKeeper: clientKeeper, + } +} + +// Logger returns a module-specific logger. +func (Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", "x/"+exported.ModuleName+"/"+types.SubModuleName) +} + +// GetPacketReceipt returns the packet receipt from the packet receipt path based on the clientID and sequence. +func (k *Keeper) GetPacketReceipt(ctx sdk.Context, clientID string, sequence uint64) ([]byte, bool) { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(hostv2.PacketReceiptKey(clientID, sequence)) + if err != nil { + panic(err) + } + if len(bz) == 0 { + return nil, false + } + return bz, true +} + +// HasPacketReceipt returns true if the packet receipt exists, otherwise false. +func (k *Keeper) HasPacketReceipt(ctx sdk.Context, clientID string, sequence uint64) bool { + store := k.storeService.OpenKVStore(ctx) + has, err := store.Has(hostv2.PacketReceiptKey(clientID, sequence)) + if err != nil { + panic(err) + } + + return has +} + +// SetPacketReceipt writes the packet receipt under the receipt path +// This is a public path that is standardized by the IBC V2 specification. +func (k *Keeper) SetPacketReceipt(ctx sdk.Context, clientID string, sequence uint64) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Set(hostv2.PacketReceiptKey(clientID, sequence), []byte{byte(2)}); err != nil { + panic(err) + } +} + +// GetPacketAcknowledgement fetches the packet acknowledgement from the store. +func (k *Keeper) GetPacketAcknowledgement(ctx sdk.Context, clientID string, sequence uint64) []byte { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(hostv2.PacketAcknowledgementKey(clientID, sequence)) + if err != nil { + panic(err) + } + return bz +} + +// SetPacketAcknowledgement writes the acknowledgement hash under the acknowledgement path +// This is a public path that is standardized by the IBC V2 specification. +func (k *Keeper) SetPacketAcknowledgement(ctx sdk.Context, clientID string, sequence uint64, ackHash []byte) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Set(hostv2.PacketAcknowledgementKey(clientID, sequence), ackHash); err != nil { + panic(err) + } +} + +// HasPacketAcknowledgement checks if the packet ack hash is already on the store. +func (k *Keeper) HasPacketAcknowledgement(ctx sdk.Context, clientID string, sequence uint64) bool { + return len(k.GetPacketAcknowledgement(ctx, clientID, sequence)) > 0 +} + +// GetPacketCommitment returns the packet commitment hash under the commitment path. +func (k *Keeper) GetPacketCommitment(ctx sdk.Context, clientID string, sequence uint64) []byte { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(hostv2.PacketCommitmentKey(clientID, sequence)) + if err != nil { + panic(err) + } + if len(bz) == 0 { + return nil + } + return bz +} + +// SetPacketCommitment writes the commitment hash under the commitment path. +func (k *Keeper) SetPacketCommitment(ctx sdk.Context, clientID string, sequence uint64, commitment []byte) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Set(hostv2.PacketCommitmentKey(clientID, sequence), commitment); err != nil { + panic(err) + } +} + +// DeletePacketCommitment deletes the packet commitment hash under the commitment path. +func (k *Keeper) DeletePacketCommitment(ctx sdk.Context, clientID string, sequence uint64) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Delete(hostv2.PacketCommitmentKey(clientID, sequence)); err != nil { + panic(err) + } +} + +// GetNextSequenceSend returns the next send sequence from the sequence path +func (k *Keeper) GetNextSequenceSend(ctx sdk.Context, clientID string) (uint64, bool) { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(hostv2.NextSequenceSendKey(clientID)) + if err != nil { + panic(err) + } + if len(bz) == 0 { + return 0, false + } + return sdk.BigEndianToUint64(bz), true +} + +// SetNextSequenceSend writes the next send sequence under the sequence path +func (k *Keeper) SetNextSequenceSend(ctx sdk.Context, clientID string, sequence uint64) { + store := k.storeService.OpenKVStore(ctx) + bigEndianBz := sdk.Uint64ToBigEndian(sequence) + if err := store.Set(hostv2.NextSequenceSendKey(clientID), bigEndianBz); err != nil { + panic(err) + } +} + +// SetAsyncPacket writes the packet under the async path +func (k *Keeper) SetAsyncPacket(ctx sdk.Context, clientID string, sequence uint64, packet types.Packet) { + store := k.storeService.OpenKVStore(ctx) + bz := k.cdc.MustMarshal(&packet) + if err := store.Set(types.AsyncPacketKey(clientID, sequence), bz); err != nil { + panic(err) + } +} + +// GetAsyncPacket fetches the packet from the async path +func (k *Keeper) GetAsyncPacket(ctx sdk.Context, clientID string, sequence uint64) (types.Packet, bool) { + store := k.storeService.OpenKVStore(ctx) + bz, err := store.Get(types.AsyncPacketKey(clientID, sequence)) + if err != nil { + panic(err) + } + if len(bz) == 0 { + return types.Packet{}, false + } + var packet types.Packet + k.cdc.MustUnmarshal(bz, &packet) + return packet, true +} + +// DeleteAsyncPacket deletes the packet from the async path +func (k *Keeper) DeleteAsyncPacket(ctx sdk.Context, clientID string, sequence uint64) { + store := k.storeService.OpenKVStore(ctx) + if err := store.Delete(types.AsyncPacketKey(clientID, sequence)); err != nil { + panic(err) + } +} + +// extractSequenceFromKey takes the full store key as well as a packet store prefix and extracts +// the encoded sequence number from the key. +// +// This function panics of the provided key once trimmed is larger than 8 bytes as the expected +// sequence byte length is always 8. +func extractSequenceFromKey(key, storePrefix []byte) uint64 { + sequenceBz := bytes.TrimPrefix(key, storePrefix) + if len(sequenceBz) > 8 { + panic("sequence is too long - expected 8 bytes") + } + return sdk.BigEndianToUint64(sequenceBz) +} + +// GetAllPacketCommitmentsForClient returns all stored PacketCommitments objects for a specified +// client ID. +func (k *Keeper) GetAllPacketCommitmentsForClient(ctx sdk.Context, clientID string) []types.PacketState { + return k.getAllPacketStateForClient(ctx, clientID, hostv2.PacketCommitmentPrefixKey) +} + +// GetAllPacketAcknowledgementsForClient returns all stored PacketAcknowledgements objects for a specified +// client ID. +func (k *Keeper) GetAllPacketAcknowledgementsForClient(ctx sdk.Context, clientID string) []types.PacketState { + return k.getAllPacketStateForClient(ctx, clientID, hostv2.PacketAcknowledgementPrefixKey) +} + +// GetAllPacketReceiptsForClient returns all stored PacketReceipts objects for a specified +// client ID. +func (k *Keeper) GetAllPacketReceiptsForClient(ctx sdk.Context, clientID string) []types.PacketState { + return k.getAllPacketStateForClient(ctx, clientID, hostv2.PacketReceiptPrefixKey) +} + +// GetAllAsyncPacketsForClient returns all stored AsyncPackets objects for a specified +// client ID. +func (k *Keeper) GetAllAsyncPacketsForClient(ctx sdk.Context, clientID string) []types.PacketState { + return k.getAllPacketStateForClient(ctx, clientID, types.AsyncPacketPrefixKey) +} + +// prefixKeyConstructor is a function that constructs a store key for a specific packet store using the provided +// clientID. +type prefixKeyConstructor func(clientID string) []byte + +// getAllPacketStateForClient gets all PacketState objects for the specified clientID using a provided +// function for constructing the key prefix for the store. +// +// For example, to get all PacketReceipts for a clientID the hostv2.PacketReceiptPrefixKey function can be +// passed to get the PacketReceipt store key prefix. +func (k *Keeper) getAllPacketStateForClient(ctx sdk.Context, clientID string, prefixFn prefixKeyConstructor) []types.PacketState { + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + storePrefix := prefixFn(clientID) + iterator := storetypes.KVStorePrefixIterator(store, storePrefix) + + var packets []types.PacketState + for ; iterator.Valid(); iterator.Next() { + sequence := extractSequenceFromKey(iterator.Key(), storePrefix) + state := types.NewPacketState(clientID, sequence, iterator.Value()) + + packets = append(packets, state) + } + return packets +} diff --git a/modules/core/04-channel/v2/keeper/keeper_test.go b/modules/core/04-channel/v2/keeper/keeper_test.go new file mode 100644 index 0000000..ce50ba4 --- /dev/null +++ b/modules/core/04-channel/v2/keeper/keeper_test.go @@ -0,0 +1,31 @@ +package keeper_test + +import ( + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func TestKeeperTestSuite(t *testing.T) { + testifysuite.Run(t, new(KeeperTestSuite)) +} + +type KeeperTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains used for convenience and readability + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain + chainC *ibctesting.TestChain +} + +func (suite *KeeperTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 3) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) + suite.chainC = suite.coordinator.GetChain(ibctesting.GetChainID(3)) +} diff --git a/modules/core/04-channel/v2/keeper/msg_server.go b/modules/core/04-channel/v2/keeper/msg_server.go new file mode 100644 index 0000000..67e9c03 --- /dev/null +++ b/modules/core/04-channel/v2/keeper/msg_server.go @@ -0,0 +1,250 @@ +package keeper + +import ( + "bytes" + "context" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + internalerrors "github.com/cosmos/ibc-go/v10/modules/core/internal/errors" + "github.com/cosmos/ibc-go/v10/modules/core/internal/v2/telemetry" +) + +var _ types.MsgServer = &Keeper{} + +// SendPacket implements the PacketMsgServer SendPacket method. +func (k *Keeper) SendPacket(goCtx context.Context, msg *types.MsgSendPacket) (*types.MsgSendPacketResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + signer, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + ctx.Logger().Error("send packet failed", "error", errorsmod.Wrap(err, "invalid address for msg Signer")) + return nil, errorsmod.Wrap(err, "invalid address for msg Signer") + } + + sequence, destChannel, err := k.sendPacket(ctx, msg.SourceClient, msg.TimeoutTimestamp, msg.Payloads) + if err != nil { + ctx.Logger().Error("send packet failed", "source-client", msg.SourceClient, "error", errorsmod.Wrap(err, "send packet failed")) + return nil, errorsmod.Wrapf(err, "send packet failed for source id: %s", msg.SourceClient) + } + + for _, pd := range msg.Payloads { + cbs := k.Router.Route(pd.SourcePort) + err := cbs.OnSendPacket(ctx, msg.SourceClient, destChannel, sequence, pd, signer) + if err != nil { + return nil, err + } + } + + return &types.MsgSendPacketResponse{Sequence: sequence}, nil +} + +// RecvPacket implements the PacketMsgServer RecvPacket method. +func (k *Keeper) RecvPacket(goCtx context.Context, msg *types.MsgRecvPacket) (*types.MsgRecvPacketResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + signer, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + ctx.Logger().Error("receive packet failed", "error", errorsmod.Wrap(err, "invalid address for msg Signer")) + return nil, errorsmod.Wrap(err, "invalid address for msg Signer") + } + + // check if this client is allowed to update if v2 config are set + config := k.clientV2Keeper.GetConfig(ctx, msg.Packet.DestinationClient) + if !config.IsAllowedRelayer(signer) { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "relayer %s is not authorized to update client %s", msg.Signer, msg.Packet.DestinationClient) + } + + // Perform TAO verification + // + // If the packet was already received, perform a no-op + // Use a cached context to prevent accidental state changes + cacheCtx, writeFn := ctx.CacheContext() + err = k.recvPacket(cacheCtx, msg.Packet, msg.ProofCommitment, msg.ProofHeight) + + switch err { + case nil: + writeFn() + case types.ErrNoOpMsg: + // no-ops do not need event emission as they will be ignored + ctx.Logger().Debug("no-op on redundant relay", "source-client", msg.Packet.SourceClient) + return &types.MsgRecvPacketResponse{Result: types.NOOP}, nil + default: + ctx.Logger().Error("receive packet failed", "source-client", msg.Packet.SourceClient, "error", errorsmod.Wrap(err, "receive packet verification failed")) + return nil, errorsmod.Wrap(err, "receive packet verification failed") + } + + // build up the recv results for each application callback. + ack := types.Acknowledgement{ + AppAcknowledgements: [][]byte{}, + } + + var isAsync bool + isSuccess := true + for _, pd := range msg.Packet.Payloads { + // Cache context so that we may discard state changes from callback if the acknowledgement is unsuccessful. + cacheCtx, writeFn = ctx.CacheContext() + cb := k.Router.Route(pd.DestinationPort) + res := cb.OnRecvPacket(cacheCtx, msg.Packet.SourceClient, msg.Packet.DestinationClient, msg.Packet.Sequence, pd, signer) + + if res.Status != types.PacketStatus_Failure { + // successful app acknowledgement cannot equal sentinel error acknowledgement + if bytes.Equal(res.GetAcknowledgement(), types.ErrorAcknowledgement[:]) { + return nil, errorsmod.Wrapf(types.ErrInvalidAcknowledgement, "application acknowledgement cannot be sentinel error acknowledgement") + } + // write application state changes for asynchronous and successful acknowledgements + writeFn() + // append app acknowledgement to the overall acknowledgement + ack.AppAcknowledgements = append(ack.AppAcknowledgements, res.Acknowledgement) + } else { + isSuccess = false + // construct acknowledgement with single app acknowledgement that is the sentinel error acknowledgement + ack = types.Acknowledgement{ + AppAcknowledgements: [][]byte{types.ErrorAcknowledgement[:]}, + } + // Modify events in cached context to reflect unsuccessful acknowledgement + ctx.EventManager().EmitEvents(internalerrors.ConvertToErrorEvents(cacheCtx.EventManager().Events())) + break + } + + if res.Status == types.PacketStatus_Async { + // Set packet acknowledgement to async if any of the acknowledgements are async. + isAsync = true + // Return error if there is more than 1 payload + // TODO: Handle case where there are multiple payloads + if len(msg.Packet.Payloads) > 1 { + return nil, errorsmod.Wrapf(types.ErrInvalidPacket, "packet with multiple payloads cannot have async acknowledgement") + } + } + } + + if !isAsync { + // If the application callback was successful, the acknowledgement must have the same number of app acknowledgements as the packet payloads. + if isSuccess { + if len(ack.AppAcknowledgements) != len(msg.Packet.Payloads) { + return nil, errorsmod.Wrapf(types.ErrInvalidAcknowledgement, "length of app acknowledgement %d does not match length of app payload %d", len(ack.AppAcknowledgements), len(msg.Packet.Payloads)) + } + } + + // Validate ack before forwarding to WriteAcknowledgement. + if err := ack.Validate(); err != nil { + return nil, err + } + // Set packet acknowledgement only if the acknowledgement is not async. + // NOTE: IBC applications modules may call the WriteAcknowledgement asynchronously if the + // acknowledgement is async. + if err := k.writeAcknowledgement(ctx, msg.Packet, ack); err != nil { + return nil, err + } + } else { + // store the packet temporarily until the application returns an acknowledgement + k.SetAsyncPacket(ctx, msg.Packet.DestinationClient, msg.Packet.Sequence, msg.Packet) + } + + // TODO: store the packet for async applications to access if required. + defer telemetry.ReportRecvPacket(msg.Packet) + + ctx.Logger().Info("receive packet callback succeeded", "source-client", msg.Packet.SourceClient, "dest-client", msg.Packet.DestinationClient, "result", types.SUCCESS.String()) + return &types.MsgRecvPacketResponse{Result: types.SUCCESS}, nil +} + +// Acknowledgement defines an rpc handler method for MsgAcknowledgement. +func (k *Keeper) Acknowledgement(goCtx context.Context, msg *types.MsgAcknowledgement) (*types.MsgAcknowledgementResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + relayer, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + ctx.Logger().Error("acknowledgement failed", "error", errorsmod.Wrap(err, "Invalid address for msg Signer")) + return nil, errorsmod.Wrap(err, "Invalid address for msg Signer") + } + + // check if this client is allowed to update if v2 config are set + config := k.clientV2Keeper.GetConfig(ctx, msg.Packet.SourceClient) + if !config.IsAllowedRelayer(relayer) { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "relayer %s is not authorized to update client %s", msg.Signer, msg.Packet.SourceClient) + } + + cacheCtx, writeFn := ctx.CacheContext() + err = k.acknowledgePacket(cacheCtx, msg.Packet, msg.Acknowledgement, msg.ProofAcked, msg.ProofHeight) + + switch err { + case nil: + writeFn() + case types.ErrNoOpMsg: + ctx.Logger().Debug("no-op on redundant relay", "source-client", msg.Packet.SourceClient) + return &types.MsgAcknowledgementResponse{Result: types.NOOP}, nil + default: + ctx.Logger().Error("acknowledgement failed", "source-client", msg.Packet.SourceClient, "error", errorsmod.Wrap(err, "acknowledge packet verification failed")) + return nil, errorsmod.Wrap(err, "acknowledge packet verification failed") + } + + recvSuccess := !bytes.Equal(msg.Acknowledgement.AppAcknowledgements[0], types.ErrorAcknowledgement[:]) + for i, pd := range msg.Packet.Payloads { + cbs := k.Router.Route(pd.SourcePort) + var ack []byte + // if recv was successful, each payload should have its own acknowledgement so we send each individual acknowledgment to the application + // otherwise, the acknowledgement only contains the sentinel error acknowledgement which we send to the application. The application is responsible + // for knowing that this is an error acknowledgement and executing the appropriate logic. + if recvSuccess { + ack = msg.Acknowledgement.AppAcknowledgements[i] + } else { + ack = types.ErrorAcknowledgement[:] + } + err := cbs.OnAcknowledgementPacket(ctx, msg.Packet.SourceClient, msg.Packet.DestinationClient, + msg.Packet.Sequence, ack, pd, relayer) + if err != nil { + return nil, errorsmod.Wrapf(err, "failed OnAcknowledgementPacket for source port %s, source client %s, destination client %s", pd.SourcePort, msg.Packet.SourceClient, msg.Packet.DestinationClient) + } + } + + defer telemetry.ReportAcknowledgePacket(msg.Packet) + + return &types.MsgAcknowledgementResponse{Result: types.SUCCESS}, nil +} + +// Timeout implements the PacketMsgServer Timeout method. +func (k *Keeper) Timeout(goCtx context.Context, timeout *types.MsgTimeout) (*types.MsgTimeoutResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + signer, err := sdk.AccAddressFromBech32(timeout.Signer) + if err != nil { + ctx.Logger().Error("timeout packet failed", "error", errorsmod.Wrap(err, "invalid address for msg Signer")) + return nil, errorsmod.Wrap(err, "invalid address for msg Signer") + } + + // check if this client is allowed to update if v2 config are set + config := k.clientV2Keeper.GetConfig(ctx, timeout.Packet.SourceClient) + if !config.IsAllowedRelayer(signer) { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "relayer %s is not authorized to update client %s", timeout.Signer, timeout.Packet.SourceClient) + } + + cacheCtx, writeFn := ctx.CacheContext() + err = k.timeoutPacket(cacheCtx, timeout.Packet, timeout.ProofUnreceived, timeout.ProofHeight) + + switch err { + case nil: + writeFn() + case types.ErrNoOpMsg: + ctx.Logger().Debug("no-op on redundant relay", "source-client", timeout.Packet.SourceClient) + return &types.MsgTimeoutResponse{Result: types.NOOP}, nil + default: + ctx.Logger().Error("timeout failed", "source-client", timeout.Packet.SourceClient, "error", errorsmod.Wrap(err, "timeout packet verification failed")) + return nil, errorsmod.Wrap(err, "timeout packet verification failed") + } + + for _, pd := range timeout.Packet.Payloads { + cbs := k.Router.Route(pd.SourcePort) + err := cbs.OnTimeoutPacket(ctx, timeout.Packet.SourceClient, timeout.Packet.DestinationClient, + timeout.Packet.Sequence, pd, signer) + if err != nil { + return nil, errorsmod.Wrapf(err, "failed OnTimeoutPacket for source port %s, source client %s, destination client %s", pd.SourcePort, timeout.Packet.SourceClient, timeout.Packet.DestinationClient) + } + } + + defer telemetry.ReportTimeoutPacket(timeout.Packet) + + return &types.MsgTimeoutResponse{Result: types.SUCCESS}, nil +} diff --git a/modules/core/04-channel/v2/keeper/msg_server_test.go b/modules/core/04-channel/v2/keeper/msg_server_test.go new file mode 100644 index 0000000..d9a0552 --- /dev/null +++ b/modules/core/04-channel/v2/keeper/msg_server_test.go @@ -0,0 +1,553 @@ +package keeper_test + +import ( + "errors" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + clientv2types "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + "github.com/cosmos/ibc-go/v10/testing/mock" + mockv2 "github.com/cosmos/ibc-go/v10/testing/mock/v2" +) + +func (suite *KeeperTestSuite) TestMsgSendPacket() { + var ( + path *ibctesting.Path + expectedPacket types.Packet + timeoutTimestamp uint64 + payload types.Payload + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + name: "success", + malleate: func() {}, + expError: nil, + }, + { + name: "success: valid timeout timestamp", + malleate: func() { + // ensure a message timeout. + timeoutTimestamp = uint64(suite.chainA.GetContext().BlockTime().Add(types.MaxTimeoutDelta - 10*time.Second).Unix()) + expectedPacket = types.NewPacket(1, path.EndpointA.ClientID, path.EndpointB.ClientID, timeoutTimestamp, payload) + }, + expError: nil, + }, + { + name: "failure: timeout elapsed", + malleate: func() { + // ensure a message timeout. + timeoutTimestamp = uint64(1) + }, + expError: types.ErrTimeoutElapsed, + }, + { + name: "failure: timeout timestamp exceeds max allowed input", + malleate: func() { + // ensure message timeout exceeds max allowed input. + timeoutTimestamp = uint64(suite.chainA.GetContext().BlockTime().Add(types.MaxTimeoutDelta + 10*time.Second).Unix()) + }, + expError: types.ErrInvalidTimeout, + }, + { + name: "failure: timeout timestamp less than current block timestamp", + malleate: func() { + // ensure message timeout exceeds max allowed input. + timeoutTimestamp = uint64(suite.chainA.GetContext().BlockTime().Unix()) - 1 + }, + expError: types.ErrTimeoutElapsed, + }, + { + name: "failure: inactive client", + malleate: func() { + path.EndpointA.FreezeClient() + }, + expError: clienttypes.ErrClientNotActive, + }, + { + name: "failure: application callback error", + malleate: func() { + path.EndpointA.Chain.GetSimApp().MockModuleV2A.IBCApp.OnSendPacket = func(ctx sdk.Context, sourceID string, destinationID string, sequence uint64, data types.Payload, signer sdk.AccAddress) error { + return mock.MockApplicationCallbackError + } + }, + expError: mock.MockApplicationCallbackError, + }, + { + name: "failure: client not found", + malleate: func() { + path.EndpointA.ClientID = ibctesting.InvalidID + }, + expError: clientv2types.ErrCounterpartyNotFound, + }, + { + name: "failure: route to non existing app", + malleate: func() { + payload.SourcePort = "foo" + }, + expError: errors.New("no route for foo"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + timeoutTimestamp = suite.chainA.GetTimeoutTimestampSecs() + payload = mockv2.NewMockPayload(mockv2.ModuleNameA, mockv2.ModuleNameB) + + expectedPacket = types.NewPacket(1, path.EndpointA.ClientID, path.EndpointB.ClientID, timeoutTimestamp, payload) + + tc.malleate() + + packet, err := path.EndpointA.MsgSendPacket(timeoutTimestamp, payload) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + suite.Require().NotEmpty(packet) + + ck := path.EndpointA.Chain.GetSimApp().IBCKeeper.ChannelKeeperV2 + + packetCommitment := ck.GetPacketCommitment(path.EndpointA.Chain.GetContext(), path.EndpointA.ClientID, 1) + suite.Require().NotNil(packetCommitment) + suite.Require().Equal(types.CommitPacket(expectedPacket), packetCommitment, "packet commitment is not stored correctly") + + nextSequenceSend, ok := ck.GetNextSequenceSend(path.EndpointA.Chain.GetContext(), path.EndpointA.ClientID) + suite.Require().True(ok) + suite.Require().Equal(uint64(2), nextSequenceSend, "next sequence send was not incremented correctly") + + suite.Require().Equal(expectedPacket, packet) + + } else { + suite.Require().Error(err) + ibctesting.RequireErrorIsOrContains(suite.T(), err, tc.expError) + } + }) + } +} + +func (suite *KeeperTestSuite) TestMsgRecvPacket() { + var ( + path *ibctesting.Path + packet types.Packet + expRecvRes types.RecvPacketResult + ) + + testCases := []struct { + name string + malleate func() + expError error + expAckWritten bool + }{ + { + name: "success", + malleate: func() {}, + expError: nil, + expAckWritten: true, + }, + { + name: "success: failed recv result", + malleate: func() { + expRecvRes = types.RecvPacketResult{ + Status: types.PacketStatus_Failure, + } + }, + expError: nil, + expAckWritten: true, + }, + { + name: "success: async recv result", + malleate: func() { + expRecvRes = types.RecvPacketResult{ + Status: types.PacketStatus_Async, + } + }, + expError: nil, + expAckWritten: false, + }, + { + name: "success: NoOp", + malleate: func() { + suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.SetPacketReceipt(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence) + }, + expError: nil, + expAckWritten: false, + }, + { + name: "success: receive permissioned with msg sender", + malleate: func() { + creator := suite.chainB.SenderAccount.GetAddress() + msg := clientv2types.NewMsgUpdateClientConfig(path.EndpointB.ClientID, creator.String(), clientv2types.NewConfig(suite.chainA.SenderAccount.GetAddress().String(), creator.String())) + _, err := suite.chainB.App.GetIBCKeeper().UpdateClientConfig(suite.chainB.GetContext(), msg) + suite.Require().NoError(err) + }, + expError: nil, + expAckWritten: true, + }, + { + name: "failure: relayer not permissioned", + malleate: func() { + creator := suite.chainB.SenderAccount.GetAddress() + msg := clientv2types.NewMsgUpdateClientConfig(path.EndpointB.ClientID, creator.String(), clientv2types.NewConfig(suite.chainA.SenderAccount.GetAddress().String())) + _, err := suite.chainB.App.GetIBCKeeper().UpdateClientConfig(suite.chainB.GetContext(), msg) + suite.Require().NoError(err) + }, + expError: ibcerrors.ErrUnauthorized, + }, + { + name: "failure: counterparty not found", + malleate: func() { + // change the destination id to a non-existent channel. + packet.DestinationClient = ibctesting.InvalidID + }, + expError: clientv2types.ErrCounterpartyNotFound, + }, + { + name: "failure: invalid proof", + malleate: func() { + // proof verification fails because the packet commitment is different due to a different sequence. + packet.Sequence = 10 + }, + expError: commitmenttypes.ErrInvalidProof, + }, + { + name: "failure: invalid acknowledgement", + malleate: func() { + expRecvRes = types.RecvPacketResult{ + Status: types.PacketStatus_Success, + Acknowledgement: []byte(""), + } + }, + expError: types.ErrInvalidAcknowledgement, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + timeoutTimestamp := suite.chainA.GetTimeoutTimestampSecs() + + var err error + packet, err = path.EndpointA.MsgSendPacket(timeoutTimestamp, mockv2.NewMockPayload(mockv2.ModuleNameA, mockv2.ModuleNameB)) + suite.Require().NoError(err) + + // default expected receive result is a single successful recv result for moduleB. + expRecvRes = mockv2.MockRecvPacketResult + + tc.malleate() + + // expectedAck is derived from the expected recv result. + var expectedAck types.Acknowledgement + if expRecvRes.Status == types.PacketStatus_Success { + expectedAck = types.Acknowledgement{AppAcknowledgements: [][]byte{expRecvRes.Acknowledgement}} + } else { + expectedAck = types.Acknowledgement{AppAcknowledgements: [][]byte{types.ErrorAcknowledgement[:]}} + } + + // modify the callback to return the expected recv result. + path.EndpointB.Chain.GetSimApp().MockModuleV2B.IBCApp.OnRecvPacket = func(ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, data types.Payload, relayer sdk.AccAddress) types.RecvPacketResult { + return expRecvRes + } + + // err is checking under expPass + err = path.EndpointB.MsgRecvPacket(packet) + ck := path.EndpointB.Chain.GetSimApp().IBCKeeper.ChannelKeeperV2 + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + + // packet receipt should be written + _, ok := ck.GetPacketReceipt(path.EndpointB.Chain.GetContext(), packet.DestinationClient, packet.Sequence) + suite.Require().True(ok) + + ackWritten := ck.HasPacketAcknowledgement(path.EndpointB.Chain.GetContext(), packet.DestinationClient, packet.Sequence) + + if !tc.expAckWritten { + // ack should not be written for async app or if the packet receipt was already present. + suite.Require().False(ackWritten) + } else { // successful or failed acknowledgement + // ack should be written for synchronous app (default mock application behaviour). + suite.Require().True(ackWritten) + expectedBz := types.CommitAcknowledgement(expectedAck) + + actualAckBz := ck.GetPacketAcknowledgement(path.EndpointB.Chain.GetContext(), packet.DestinationClient, packet.Sequence) + suite.Require().Equal(expectedBz, actualAckBz) + } + + } else { + ibctesting.RequireErrorIsOrContains(suite.T(), err, tc.expError) + _, ok := ck.GetPacketReceipt(path.EndpointB.Chain.GetContext(), packet.SourceClient, packet.Sequence) + suite.Require().False(ok) + } + }) + } +} + +func (suite *KeeperTestSuite) TestMsgAcknowledgement() { + var ( + path *ibctesting.Path + packet types.Packet + ack types.Acknowledgement + ) + testCases := []struct { + name string + malleate func() + payload types.Payload + expError error + }{ + { + name: "success", + malleate: func() {}, + payload: mockv2.NewMockPayload(mockv2.ModuleNameA, mockv2.ModuleNameB), + }, + { + name: "success: NoOp", + malleate: func() { + suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.DeletePacketCommitment(suite.chainA.GetContext(), packet.SourceClient, packet.Sequence) + + // Modify the callback to return an error. + // This way, we can verify that the callback is not executed in a No-op case. + path.EndpointA.Chain.GetSimApp().MockModuleV2A.IBCApp.OnAcknowledgementPacket = func(sdk.Context, string, string, uint64, types.Payload, []byte, sdk.AccAddress) error { + return mock.MockApplicationCallbackError + } + }, + payload: mockv2.NewMockPayload(mockv2.ModuleNameA, mockv2.ModuleNameB), + }, + { + name: "success: failed ack result", + malleate: func() { + ack.AppAcknowledgements[0] = types.ErrorAcknowledgement[:] + }, + payload: mockv2.NewErrorMockPayload(mockv2.ModuleNameA, mockv2.ModuleNameB), + }, + { + name: "success: relayer permissioned with msg sender", + malleate: func() { + creator := suite.chainA.SenderAccount.GetAddress() + msg := clientv2types.NewMsgUpdateClientConfig(path.EndpointA.ClientID, creator.String(), clientv2types.NewConfig(suite.chainB.SenderAccount.GetAddress().String(), creator.String())) + _, err := suite.chainA.App.GetIBCKeeper().UpdateClientConfig(suite.chainA.GetContext(), msg) + suite.Require().NoError(err) + }, + payload: mockv2.NewMockPayload(mockv2.ModuleNameA, mockv2.ModuleNameB), + }, + { + name: "failure: relayer not permissioned", + malleate: func() { + creator := suite.chainA.SenderAccount.GetAddress() + msg := clientv2types.NewMsgUpdateClientConfig(path.EndpointA.ClientID, creator.String(), clientv2types.NewConfig(suite.chainB.SenderAccount.GetAddress().String())) + _, err := suite.chainA.App.GetIBCKeeper().UpdateClientConfig(suite.chainA.GetContext(), msg) + suite.Require().NoError(err) + }, + payload: mockv2.NewMockPayload(mockv2.ModuleNameA, mockv2.ModuleNameB), + expError: ibcerrors.ErrUnauthorized, + }, + { + name: "failure: callback fails", + malleate: func() { + path.EndpointA.Chain.GetSimApp().MockModuleV2A.IBCApp.OnAcknowledgementPacket = func(sdk.Context, string, string, uint64, types.Payload, []byte, sdk.AccAddress) error { + return mock.MockApplicationCallbackError + } + }, + payload: mockv2.NewMockPayload(mockv2.ModuleNameA, mockv2.ModuleNameB), + expError: mock.MockApplicationCallbackError, + }, + { + name: "failure: counterparty not found", + malleate: func() { + // change the source id to a non-existent channel. + packet.SourceClient = "not-existent-channel" + }, + payload: mockv2.NewMockPayload(mockv2.ModuleNameA, mockv2.ModuleNameB), + expError: clientv2types.ErrCounterpartyNotFound, + }, + { + name: "failure: invalid commitment", + malleate: func() { + suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SetPacketCommitment(suite.chainA.GetContext(), packet.SourceClient, packet.Sequence, []byte("foo")) + }, + payload: mockv2.NewMockPayload(mockv2.ModuleNameA, mockv2.ModuleNameB), + expError: types.ErrInvalidPacket, + }, + { + name: "failure: failed membership verification", + malleate: func() { + ack.AppAcknowledgements[0] = mock.MockFailPacketData + }, + payload: mockv2.NewMockPayload(mockv2.ModuleNameA, mockv2.ModuleNameB), + expError: errors.New("failed packet acknowledgement verification"), + }, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + timeoutTimestamp := suite.chainA.GetTimeoutTimestampSecs() + + var err error + // Send packet from A to B + packet, err = path.EndpointA.MsgSendPacket(timeoutTimestamp, tc.payload) + suite.Require().NoError(err) + + err = path.EndpointB.MsgRecvPacket(packet) + suite.Require().NoError(err) + + // Construct expected acknowledgement + ack = types.Acknowledgement{AppAcknowledgements: [][]byte{mockv2.MockRecvPacketResult.Acknowledgement}} + + tc.malleate() + + // Finally, acknowledge the packet on A + err = path.EndpointA.MsgAcknowledgePacket(packet, ack) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + } else { + ibctesting.RequireErrorIsOrContains(suite.T(), err, tc.expError, "expected error %q, got %q instead", tc.expError, err) + } + }) + } +} + +func (suite *KeeperTestSuite) TestMsgTimeout() { + var ( + path *ibctesting.Path + packet types.Packet + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + name: "success", + malleate: func() { + suite.Require().NoError(path.EndpointA.UpdateClient()) + }, + }, + { + name: "success: no-op", + malleate: func() { + suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.DeletePacketCommitment(suite.chainA.GetContext(), packet.SourceClient, packet.Sequence) + + // Modify the callback to return a different error. + // This way, we can verify that the callback is not executed in a No-op case. + path.EndpointA.Chain.GetSimApp().MockModuleV2A.IBCApp.OnTimeoutPacket = func(sdk.Context, string, string, uint64, types.Payload, sdk.AccAddress) error { + return mock.MockApplicationCallbackError + } + suite.Require().NoError(path.EndpointA.UpdateClient()) + }, + }, + { + name: "success: relayer permissioned with msg sender", + malleate: func() { + creator := suite.chainA.SenderAccount.GetAddress() + msg := clientv2types.NewMsgUpdateClientConfig(path.EndpointA.ClientID, creator.String(), clientv2types.NewConfig(suite.chainB.SenderAccount.GetAddress().String(), creator.String())) + _, err := suite.chainA.App.GetIBCKeeper().UpdateClientConfig(suite.chainA.GetContext(), msg) + suite.Require().NoError(err) + suite.Require().NoError(path.EndpointA.UpdateClient()) + }, + }, + { + name: "failure: relayer not permissioned", + malleate: func() { + // update first before permissioning the relayer in this case + suite.Require().NoError(path.EndpointA.UpdateClient()) + creator := suite.chainA.SenderAccount.GetAddress() + msg := clientv2types.NewMsgUpdateClientConfig(path.EndpointA.ClientID, creator.String(), clientv2types.NewConfig(suite.chainB.SenderAccount.GetAddress().String())) + _, err := suite.chainA.App.GetIBCKeeper().UpdateClientConfig(suite.chainA.GetContext(), msg) + suite.Require().NoError(err) + }, + expError: ibcerrors.ErrUnauthorized, + }, + { + name: "failure: callback fails", + malleate: func() { + path.EndpointA.Chain.GetSimApp().MockModuleV2A.IBCApp.OnTimeoutPacket = func(sdk.Context, string, string, uint64, types.Payload, sdk.AccAddress) error { + return mock.MockApplicationCallbackError + } + suite.Require().NoError(path.EndpointA.UpdateClient()) + }, + expError: mock.MockApplicationCallbackError, + }, + { + name: "failure: client not found", + malleate: func() { + // change the source id to a non-existent client. + packet.SourceClient = "not-existent-client" + suite.Require().NoError(path.EndpointA.UpdateClient()) + }, + expError: clientv2types.ErrCounterpartyNotFound, + }, + { + name: "failure: invalid commitment", + malleate: func() { + suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SetPacketCommitment(suite.chainA.GetContext(), packet.SourceClient, packet.Sequence, []byte("foo")) + suite.Require().NoError(path.EndpointA.UpdateClient()) + }, + expError: types.ErrInvalidPacket, + }, + { + name: "failure: unable to timeout if packet has been received", + malleate: func() { + suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.SetPacketReceipt(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence) + suite.Require().NoError(path.EndpointB.UpdateClient()) + suite.Require().NoError(path.EndpointA.UpdateClient()) + }, + expError: commitmenttypes.ErrInvalidProof, + }, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + // Send packet from A to B + // make timeoutTimestamp 1 second more than sending chain time to ensure it passes SendPacket + // and times out successfully after update + timeoutTimestamp := uint64(suite.chainA.GetContext().BlockTime().Add(time.Second).Unix()) + mockData := mockv2.NewMockPayload(mockv2.ModuleNameA, mockv2.ModuleNameB) + + var err error + packet, err = path.EndpointA.MsgSendPacket(timeoutTimestamp, mockData) + suite.Require().NoError(err) + suite.Require().NotEmpty(packet) + + tc.malleate() + + err = path.EndpointA.MsgTimeoutPacket(packet) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + } else { + ibctesting.RequireErrorIsOrContains(suite.T(), err, tc.expError, "expected error %q, got %q instead", tc.expError, err) + } + }) + } +} diff --git a/modules/core/04-channel/v2/keeper/packet.go b/modules/core/04-channel/v2/keeper/packet.go new file mode 100644 index 0000000..c4da48e --- /dev/null +++ b/modules/core/04-channel/v2/keeper/packet.go @@ -0,0 +1,354 @@ +package keeper + +import ( + "bytes" + "strconv" + "time" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + clientv2types "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + hostv2 "github.com/cosmos/ibc-go/v10/modules/core/24-host/v2" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// sendPacket constructs a packet from the input arguments, writes a packet commitment to state +// in order for the packet to be sent to the counterparty. +func (k *Keeper) sendPacket( + ctx sdk.Context, + sourceClient string, + timeoutTimestamp uint64, + payloads []types.Payload, +) (uint64, string, error) { + // lookup counterparty from client identifiers + counterparty, ok := k.clientV2Keeper.GetClientCounterparty(ctx, sourceClient) + if !ok { + return 0, "", errorsmod.Wrapf(clientv2types.ErrCounterpartyNotFound, "counterparty not found for client: %s", sourceClient) + } + + // Note, the validate basic function in sendPacket does the timeoutTimestamp != 0 check and other stateless checks on the packet. + // timeoutTimestamp must be greater than current block time + timeout := time.Unix(int64(timeoutTimestamp), 0) + if !timeout.After(ctx.BlockTime()) { + return 0, "", errorsmod.Wrapf(types.ErrTimeoutElapsed, "timeout is less than or equal the current block timestamp, %d <= %d", timeoutTimestamp, ctx.BlockTime().Unix()) + } + + // timeoutTimestamp must be less than current block time + MaxTimeoutDelta + if timeout.After(ctx.BlockTime().Add(types.MaxTimeoutDelta)) { + return 0, "", errorsmod.Wrap(types.ErrInvalidTimeout, "timeout exceeds the maximum expected value") + } + + sequence, found := k.GetNextSequenceSend(ctx, sourceClient) + if !found { + return 0, "", errorsmod.Wrapf(types.ErrSequenceSendNotFound, "source client: %s", sourceClient) + } + + // construct packet from given fields and channel state + packet := types.NewPacket(sequence, sourceClient, counterparty.ClientId, timeoutTimestamp, payloads...) + + if err := packet.ValidateBasic(); err != nil { + return 0, "", errorsmod.Wrapf(types.ErrInvalidPacket, "constructed packet failed basic validation: %v", err) + } + + // check that the client of counterparty chain is still active + if status := k.ClientKeeper.GetClientStatus(ctx, sourceClient); status != exported.Active { + return 0, "", errorsmod.Wrapf(clienttypes.ErrClientNotActive, "client (%s) status is %s", sourceClient, status) + } + + // retrieve latest height and timestamp of the client of counterparty chain + latestHeight := k.ClientKeeper.GetClientLatestHeight(ctx, sourceClient) + if latestHeight.IsZero() { + return 0, "", errorsmod.Wrapf(clienttypes.ErrInvalidHeight, "cannot send packet using client (%s) with zero height", sourceClient) + } + + // client timestamps are in nanoseconds while packet timeouts are in seconds + // thus to compare them, we convert the client timestamp to seconds in uint64 + // to be consistent with IBC V2 specified timeout behaviour + latestTimestampNano, err := k.ClientKeeper.GetClientTimestampAtHeight(ctx, sourceClient, latestHeight) + if err != nil { + return 0, "", err + } + latestTimestamp := uint64(time.Unix(0, int64(latestTimestampNano)).Unix()) + + if latestTimestamp >= packet.TimeoutTimestamp { + return 0, "", errorsmod.Wrapf(types.ErrTimeoutElapsed, "latest timestamp: %d, timeout timestamp: %d", latestTimestamp, packet.TimeoutTimestamp) + } + + commitment := types.CommitPacket(packet) + + // bump the sequence and set the packet commitment, so it is provable by the counterparty + k.SetNextSequenceSend(ctx, sourceClient, sequence+1) + k.SetPacketCommitment(ctx, sourceClient, packet.GetSequence(), commitment) + + k.Logger(ctx).Info("packet sent", "sequence", strconv.FormatUint(packet.Sequence, 10), "dst_client_id", + packet.DestinationClient, "src_client_id", packet.SourceClient) + + emitSendPacketEvents(ctx, packet) + + return sequence, counterparty.ClientId, nil +} + +// recvPacket implements the packet receiving logic required by a packet handler. +// The packet is checked for correctness including asserting that the packet was +// sent and received on clients which are counterparties for one another. +// If the packet has already been received a no-op error is returned. +// The packet handler will verify that the packet has not timed out and that the +// counterparty stored a packet commitment. If successful, a packet receipt is stored +// to indicate to the counterparty successful delivery. +func (k *Keeper) recvPacket( + ctx sdk.Context, + packet types.Packet, + proof []byte, + proofHeight exported.Height, +) error { + // lookup counterparty from client identifiers + counterparty, ok := k.clientV2Keeper.GetClientCounterparty(ctx, packet.DestinationClient) + if !ok { + return errorsmod.Wrapf(clientv2types.ErrCounterpartyNotFound, "counterparty not found for client: %s", packet.DestinationClient) + } + + if counterparty.ClientId != packet.SourceClient { + return errorsmod.Wrapf(clientv2types.ErrInvalidCounterparty, "counterparty id (%s) does not match packet source id (%s)", counterparty.ClientId, packet.SourceClient) + } + + currentTimestamp := uint64(ctx.BlockTime().Unix()) + if currentTimestamp >= packet.TimeoutTimestamp { + return errorsmod.Wrapf(types.ErrTimeoutElapsed, "current timestamp: %d, timeout timestamp: %d", currentTimestamp, packet.TimeoutTimestamp) + } + + // REPLAY PROTECTION: Packet receipts will indicate that a packet has already been received + // Packet receipts must not be pruned, unless it has been marked stale + // by the increase of the recvStartSequence. + if k.HasPacketReceipt(ctx, packet.DestinationClient, packet.Sequence) { + // This error indicates that the packet has already been relayed. Core IBC will + // treat this error as a no-op in order to prevent an entire relay transaction + // from failing and consuming unnecessary fees. + return types.ErrNoOpMsg + } + + path := hostv2.PacketCommitmentKey(packet.SourceClient, packet.Sequence) + merklePath := types.BuildMerklePath(counterparty.MerklePrefix, path) + + commitment := types.CommitPacket(packet) + + if err := k.ClientKeeper.VerifyMembership( + ctx, + packet.DestinationClient, + proofHeight, + 0, 0, + proof, + merklePath, + commitment, + ); err != nil { + return errorsmod.Wrapf(err, "failed packet commitment verification for client (%s)", packet.DestinationClient) + } + + // Set Packet Receipt to prevent timeout from occurring on counterparty + k.SetPacketReceipt(ctx, packet.DestinationClient, packet.Sequence) + + k.Logger(ctx).Info("packet received", "sequence", strconv.FormatUint(packet.Sequence, 10), "src_client_id", packet.SourceClient, "dst_client_id", packet.DestinationClient) + + emitRecvPacketEvents(ctx, packet) + + return nil +} + +// writeAcknowledgement writes the acknowledgement to the store and emits the packet and acknowledgement +// for relayers to relay the acknowledgement to the counterparty chain. +func (k Keeper) writeAcknowledgement( + ctx sdk.Context, + packet types.Packet, + ack types.Acknowledgement, +) error { + // lookup counterparty from client identifiers + counterparty, ok := k.clientV2Keeper.GetClientCounterparty(ctx, packet.DestinationClient) + if !ok { + return errorsmod.Wrapf(clientv2types.ErrCounterpartyNotFound, "counterparty not found for client: %s", packet.DestinationClient) + } + + if counterparty.ClientId != packet.SourceClient { + return errorsmod.Wrapf(clientv2types.ErrInvalidCounterparty, "counterparty id (%s) does not match packet source id (%s)", counterparty.ClientId, packet.SourceClient) + } + + // NOTE: IBC app modules might have written the acknowledgement synchronously on + // the OnRecvPacket callback so we need to check if the acknowledgement is already + // set on the store and return an error if so. + if k.HasPacketAcknowledgement(ctx, packet.DestinationClient, packet.Sequence) { + return errorsmod.Wrapf(types.ErrAcknowledgementExists, "acknowledgement for id %s, sequence %d already exists", packet.DestinationClient, packet.Sequence) + } + + if _, found := k.GetPacketReceipt(ctx, packet.DestinationClient, packet.Sequence); !found { + return errorsmod.Wrap(types.ErrInvalidPacket, "receipt not found for packet") + } + + // set the acknowledgement so that it can be verified on the other side + k.SetPacketAcknowledgement( + ctx, packet.DestinationClient, packet.Sequence, + types.CommitAcknowledgement(ack), + ) + + k.Logger(ctx).Info("acknowledgement written", "sequence", strconv.FormatUint(packet.Sequence, 10), "dst_client_id", packet.DestinationClient) + + emitWriteAcknowledgementEvents(ctx, packet, ack) + + return nil +} + +// WriteAcknowledgement writes the acknowledgement and emits events for asynchronous acknowledgements +// this is the method to be called by external apps when they want to write an acknowledgement asyncrhonously +func (k *Keeper) WriteAcknowledgement(ctx sdk.Context, clientID string, sequence uint64, ack types.Acknowledgement) error { + // Validate the acknowledgement + if err := ack.Validate(); err != nil { + ctx.Logger().Error("write acknowledgement failed", "error", errorsmod.Wrap(err, "invalid acknowledgement")) + return errorsmod.Wrap(err, "invalid acknowledgement") + } + + packet, ok := k.GetAsyncPacket(ctx, clientID, sequence) + if !ok { + return errorsmod.Wrapf(types.ErrInvalidAcknowledgement, "packet with clientID (%s) and sequence (%d) not found for async acknowledgement", clientID, sequence) + } + + // Write the acknowledgement to the store + if err := k.writeAcknowledgement(ctx, packet, ack); err != nil { + ctx.Logger().Error("write acknowledgement failed", "error", errorsmod.Wrap(err, "write acknowledgement failed")) + return errorsmod.Wrap(err, "write acknowledgement failed") + } + + // Delete the packet from the async store + k.DeleteAsyncPacket(ctx, clientID, sequence) + + return nil +} + +func (k *Keeper) acknowledgePacket(ctx sdk.Context, packet types.Packet, acknowledgement types.Acknowledgement, proof []byte, proofHeight exported.Height) error { + // lookup counterparty from client identifiers + counterparty, ok := k.clientV2Keeper.GetClientCounterparty(ctx, packet.SourceClient) + if !ok { + return errorsmod.Wrapf(clientv2types.ErrCounterpartyNotFound, "counterparty not found for client: %s", packet.SourceClient) + } + + if counterparty.ClientId != packet.DestinationClient { + return errorsmod.Wrapf(clientv2types.ErrInvalidCounterparty, "counterparty id (%s) does not match packet destination id (%s)", counterparty.ClientId, packet.DestinationClient) + } + + commitment := k.GetPacketCommitment(ctx, packet.SourceClient, packet.Sequence) + if len(commitment) == 0 { + // This error indicates that the acknowledgement has already been relayed + // or there is a misconfigured relayer attempting to prove an acknowledgement + // for a packet never sent. Core IBC will treat this error as a no-op in order to + // prevent an entire relay transaction from failing and consuming unnecessary fees. + return types.ErrNoOpMsg + } + + packetCommitment := types.CommitPacket(packet) + + // verify we sent the packet and haven't cleared it out yet + if !bytes.Equal(commitment, packetCommitment) { + return errorsmod.Wrapf(types.ErrInvalidPacket, "commitment bytes are not equal: got (%v), expected (%v)", packetCommitment, commitment) + } + + path := hostv2.PacketAcknowledgementKey(packet.DestinationClient, packet.Sequence) + merklePath := types.BuildMerklePath(counterparty.MerklePrefix, path) + + if err := k.ClientKeeper.VerifyMembership( + ctx, + packet.SourceClient, + proofHeight, + 0, 0, + proof, + merklePath, + types.CommitAcknowledgement(acknowledgement), + ); err != nil { + return errorsmod.Wrapf(err, "failed packet acknowledgement verification for client (%s)", packet.SourceClient) + } + + k.DeletePacketCommitment(ctx, packet.SourceClient, packet.Sequence) + + k.Logger(ctx).Info("packet acknowledged", "sequence", strconv.FormatUint(packet.GetSequence(), 10), "src_client_id", packet.GetSourceClient(), "dst_client_id", packet.GetDestinationClient()) + + emitAcknowledgePacketEvents(ctx, packet) + + return nil +} + +// timeoutPacket implements the timeout logic required by a packet handler. +// The packet is checked for correctness including asserting that the packet was +// sent and received on clients which are counterparties for one another. +// If no packet commitment exists, a no-op error is returned, otherwise +// an absence proof of the packet receipt is performed to ensure that the packet +// was never delivered to the counterparty. If successful, the packet commitment +// is deleted and the packet has completed its lifecycle. +func (k *Keeper) timeoutPacket( + ctx sdk.Context, + packet types.Packet, + proof []byte, + proofHeight exported.Height, +) error { + // lookup counterparty from client identifiers + counterparty, ok := k.clientV2Keeper.GetClientCounterparty(ctx, packet.SourceClient) + if !ok { + return errorsmod.Wrapf(clientv2types.ErrCounterpartyNotFound, "counterparty not found for client: %s", packet.SourceClient) + } + + if counterparty.ClientId != packet.DestinationClient { + return errorsmod.Wrapf(clientv2types.ErrInvalidCounterparty, "counterparty id (%s) does not match packet destination id (%s)", counterparty.ClientId, packet.DestinationClient) + } + + // check that timeout timestamp has passed on the other end + // client timestamps are in nanoseconds while packet timeouts are in seconds + // so we convert client timestamp to seconds in uint64 to be consistent + // with IBC V2 timeout behaviour + proofTimestampNano, err := k.ClientKeeper.GetClientTimestampAtHeight(ctx, packet.SourceClient, proofHeight) + if err != nil { + return err + } + proofTimestamp := uint64(time.Unix(0, int64(proofTimestampNano)).Unix()) + + if proofTimestamp < packet.TimeoutTimestamp { + return errorsmod.Wrapf(types.ErrTimeoutNotReached, "proof timestamp: %d, timeout timestamp: %d", proofTimestamp, packet.TimeoutTimestamp) + } + + // check that the commitment has not been cleared and that it matches the packet sent by relayer + commitment := k.GetPacketCommitment(ctx, packet.SourceClient, packet.Sequence) + if len(commitment) == 0 { + // This error indicates that the timeout has already been relayed + // or there is a misconfigured relayer attempting to prove a timeout + // for a packet never sent. Core IBC will treat this error as a no-op in order to + // prevent an entire relay transaction from failing and consuming unnecessary fees. + return types.ErrNoOpMsg + } + + packetCommitment := types.CommitPacket(packet) + // verify we sent the packet and haven't cleared it out yet + if !bytes.Equal(commitment, packetCommitment) { + return errorsmod.Wrapf(types.ErrInvalidPacket, "packet commitment bytes are not equal: got (%v), expected (%v)", commitment, packetCommitment) + } + + // verify packet receipt absence + path := hostv2.PacketReceiptKey(packet.DestinationClient, packet.Sequence) + merklePath := types.BuildMerklePath(counterparty.MerklePrefix, path) + + if err := k.ClientKeeper.VerifyNonMembership( + ctx, + packet.SourceClient, + proofHeight, + 0, 0, + proof, + merklePath, + ); err != nil { + return errorsmod.Wrapf(err, "failed packet receipt absence verification for client (%s)", packet.SourceClient) + } + + // delete packet commitment to prevent replay + k.DeletePacketCommitment(ctx, packet.SourceClient, packet.Sequence) + + k.Logger(ctx).Info("packet timed out", "sequence", strconv.FormatUint(packet.Sequence, 10), "src_client_id", packet.SourceClient, "dst_client_id", packet.DestinationClient) + + emitTimeoutPacketEvents(ctx, packet) + + return nil +} diff --git a/modules/core/04-channel/v2/keeper/packet_test.go b/modules/core/04-channel/v2/keeper/packet_test.go new file mode 100644 index 0000000..2c5e8c8 --- /dev/null +++ b/modules/core/04-channel/v2/keeper/packet_test.go @@ -0,0 +1,603 @@ +package keeper_test + +import ( + "fmt" + "time" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + clientv2types "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + hostv2 "github.com/cosmos/ibc-go/v10/modules/core/24-host/v2" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + mockv2 "github.com/cosmos/ibc-go/v10/testing/mock/v2" +) + +var unusedChannel = "channel-5" + +func (suite *KeeperTestSuite) TestSendPacket() { + var ( + path *ibctesting.Path + packet types.Packet + expSequence uint64 + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() {}, + nil, + }, + { + "success with later packet", + func() { + // send the same packet earlier so next packet send should be sequence 2 + _, _, err := suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SendPacketTest(suite.chainA.GetContext(), packet.SourceClient, packet.TimeoutTimestamp, packet.Payloads) + suite.Require().NoError(err) + expSequence = 2 + }, + nil, + }, + { + "client not found", + func() { + packet.SourceClient = ibctesting.InvalidID + }, + clientv2types.ErrCounterpartyNotFound, + }, + { + "packet failed basic validation", + func() { + // invalid data + packet.Payloads[0].SourcePort = "" + }, + types.ErrInvalidPacket, + }, + { + "client status invalid", + func() { + path.EndpointA.FreezeClient() + }, + clienttypes.ErrClientNotActive, + }, + { + "client state zero height", func() { + clientState := path.EndpointA.GetClientState() + cs, ok := clientState.(*ibctm.ClientState) + suite.Require().True(ok) + + // force a consensus state into the store at height zero to allow client status check to pass. + consensusState := path.EndpointA.GetConsensusState(cs.LatestHeight) + path.EndpointA.SetConsensusState(consensusState, clienttypes.ZeroHeight()) + + cs.LatestHeight = clienttypes.ZeroHeight() + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID, cs) + }, + clienttypes.ErrInvalidHeight, + }, + { + "timeout equal to sending chain blocktime", func() { + packet.TimeoutTimestamp = uint64(suite.chainA.GetContext().BlockTime().Unix()) + }, + types.ErrTimeoutElapsed, + }, + { + "timeout elapsed", func() { + packet.TimeoutTimestamp = 1 + }, + types.ErrTimeoutElapsed, + }, + } + + for i, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s, %d/%d tests", tc.name, i, len(testCases)), func() { + suite.SetupTest() // reset + + // create clients and set counterparties on both chains + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + payload := mockv2.NewMockPayload(mockv2.ModuleNameA, mockv2.ModuleNameB) + + timeoutTimestamp := uint64(suite.chainB.GetContext().BlockTime().Add(time.Hour).Unix()) + + // create standard packet that can be malleated + packet = types.NewPacket(1, path.EndpointA.ClientID, path.EndpointB.ClientID, + timeoutTimestamp, payload) + expSequence = 1 + + // malleate the test case + tc.malleate() + + // send packet + seq, destClient, err := suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SendPacketTest(suite.chainA.GetContext(), packet.SourceClient, packet.TimeoutTimestamp, packet.Payloads) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + // verify send packet method instantiated packet with correct sequence and destination channel + suite.Require().Equal(expSequence, seq) + suite.Require().Equal(path.EndpointB.ClientID, destClient) + // verify send packet stored the packet commitment correctly + expCommitment := types.CommitPacket(packet) + suite.Require().Equal(expCommitment, suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.GetPacketCommitment(suite.chainA.GetContext(), packet.SourceClient, seq)) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expError) + suite.Require().Equal(uint64(0), seq) + suite.Require().Nil(suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.GetPacketCommitment(suite.chainA.GetContext(), packet.SourceClient, seq)) + + } + }) + } +} + +func (suite *KeeperTestSuite) TestRecvPacket() { + var ( + path *ibctesting.Path + err error + packet types.Packet + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() {}, + nil, + }, + { + "failure: client not found", + func() { + packet.DestinationClient = ibctesting.InvalidID + }, + clientv2types.ErrCounterpartyNotFound, + }, + { + "failure: client is not active", + func() { + path.EndpointB.FreezeClient() + }, + clienttypes.ErrClientNotActive, + }, + { + "failure: counterparty client identifier different than source client", + func() { + packet.SourceClient = unusedChannel + }, + clientv2types.ErrInvalidCounterparty, + }, + { + "failure: packet has timed out", + func() { + suite.coordinator.IncrementTimeBy(time.Hour * 20) + }, + types.ErrTimeoutElapsed, + }, + { + "failure: packet already received", + func() { + suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.SetPacketReceipt(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence) + }, + types.ErrNoOpMsg, + }, + { + "failure: verify membership failed", + func() { + suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SetPacketCommitment(suite.chainA.GetContext(), packet.SourceClient, packet.Sequence, []byte("")) + suite.coordinator.CommitBlock(path.EndpointA.Chain) + suite.Require().NoError(path.EndpointB.UpdateClient()) + }, + commitmenttypes.ErrInvalidProof, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + payload := mockv2.NewMockPayload(mockv2.ModuleNameA, mockv2.ModuleNameB) + + timeoutTimestamp := uint64(suite.chainB.GetContext().BlockTime().Add(time.Hour).Unix()) + + // send packet + packet, err = path.EndpointA.MsgSendPacket(timeoutTimestamp, payload) + suite.Require().NoError(err) + + tc.malleate() + + // get proof of v2 packet commitment from chainA + packetKey := hostv2.PacketCommitmentKey(packet.GetSourceClient(), packet.GetSequence()) + proof, proofHeight := path.EndpointA.QueryProof(packetKey) + + err = suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.RecvPacketTest(suite.chainB.GetContext(), packet, proof, proofHeight) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + + _, found := suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.GetPacketReceipt(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence) + suite.Require().True(found) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +func (suite *KeeperTestSuite) TestWriteAcknowledgement() { + var ( + packet types.Packet + ack types.Acknowledgement + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() {}, + nil, + }, + { + "failure: client not found", + func() { + packet.DestinationClient = ibctesting.InvalidID + suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.SetPacketReceipt(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence) + suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.SetAsyncPacket(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence, packet) + }, + clientv2types.ErrCounterpartyNotFound, + }, + { + "failure: counterparty client identifier different than source client", + func() { + packet.SourceClient = unusedChannel + suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.SetPacketReceipt(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence) + suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.SetAsyncPacket(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence, packet) + }, + clientv2types.ErrInvalidCounterparty, + }, + { + "failure: ack already exists", + func() { + ackBz := types.CommitAcknowledgement(ack) + suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.SetPacketAcknowledgement(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence, ackBz) + }, + types.ErrAcknowledgementExists, + }, + { + "failure: receipt not found for packet", + func() { + packet.Sequence = 2 + suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.SetAsyncPacket(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence, packet) + }, + types.ErrInvalidPacket, + }, + { + "failure: async packet not found", + func() { + suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.DeleteAsyncPacket(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence) + }, + types.ErrInvalidAcknowledgement, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + payload := mockv2.NewMockPayload(mockv2.ModuleNameA, mockv2.ModuleNameB) + + timeoutTimestamp := uint64(suite.chainB.GetContext().BlockTime().Add(time.Hour).Unix()) + + // create standard packet that can be malleated + packet = types.NewPacket(1, path.EndpointA.ClientID, path.EndpointB.ClientID, + timeoutTimestamp, payload) + + // create standard ack that can be malleated + ack = types.Acknowledgement{ + AppAcknowledgements: [][]byte{mockv2.MockRecvPacketResult.Acknowledgement}, + } + + // mock receive with async acknowledgement + suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.SetPacketReceipt(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence) + suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.SetAsyncPacket(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence, packet) + + tc.malleate() + + err := suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.WriteAcknowledgement(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence, ack) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + + ackCommitment := suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.GetPacketAcknowledgement(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence) + suite.Require().Equal(types.CommitAcknowledgement(ack), ackCommitment) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +func (suite *KeeperTestSuite) TestAcknowledgePacket() { + var ( + packet types.Packet + err error + ack = types.Acknowledgement{ + AppAcknowledgements: [][]byte{mockv2.MockRecvPacketResult.Acknowledgement}, + } + freezeClient bool + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() {}, + nil, + }, + { + "failure: client not found", + func() { + packet.SourceClient = ibctesting.InvalidID + }, + clientv2types.ErrCounterpartyNotFound, + }, + { + "failure: counterparty client identifier different than destination client", + func() { + packet.DestinationClient = unusedChannel + }, + clientv2types.ErrInvalidCounterparty, + }, + { + "failure: packet commitment doesn't exist.", + func() { + suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.DeletePacketCommitment(suite.chainA.GetContext(), packet.SourceClient, packet.Sequence) + }, + types.ErrNoOpMsg, + }, + { + "failure: client status invalid", + func() { + freezeClient = true + }, + clienttypes.ErrClientNotActive, + }, + { + "failure: packet commitment bytes differ", + func() { + // change payload after send to acknowledge different packet + packet.Payloads[0].Value = []byte("different value") + }, + types.ErrInvalidPacket, + }, + { + "failure: verify membership fails", + func() { + ack.AppAcknowledgements[0] = types.ErrorAcknowledgement[:] + }, + commitmenttypes.ErrInvalidProof, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + freezeClient = false + + payload := mockv2.NewMockPayload(mockv2.ModuleNameA, mockv2.ModuleNameB) + + timeoutTimestamp := uint64(suite.chainB.GetContext().BlockTime().Add(time.Hour).Unix()) + + // send packet + packet, err = path.EndpointA.MsgSendPacket(timeoutTimestamp, payload) + suite.Require().NoError(err) + + err = path.EndpointB.MsgRecvPacket(packet) + suite.Require().NoError(err) + + tc.malleate() + + packetKey := hostv2.PacketAcknowledgementKey(packet.DestinationClient, packet.Sequence) + proof, proofHeight := path.EndpointB.QueryProof(packetKey) + + if freezeClient { + path.EndpointA.FreezeClient() + } + + err = suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.AcknowledgePacketTest(suite.chainA.GetContext(), packet, ack, proof, proofHeight) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + + commitment := suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.GetPacketCommitment(suite.chainA.GetContext(), packet.SourceClient, packet.Sequence) + suite.Require().Empty(commitment) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +func (suite *KeeperTestSuite) TestTimeoutPacket() { + var ( + path *ibctesting.Path + packet types.Packet + freezeClient bool + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + // send packet + _, _, err := suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SendPacketTest(suite.chainA.GetContext(), packet.SourceClient, + packet.TimeoutTimestamp, packet.Payloads) + suite.Require().NoError(err, "send packet failed") + }, + nil, + }, + { + "failure: client not found", + func() { + // send packet + _, _, err := suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SendPacketTest(suite.chainA.GetContext(), packet.SourceClient, + packet.TimeoutTimestamp, packet.Payloads) + suite.Require().NoError(err, "send packet failed") + + packet.SourceClient = ibctesting.InvalidID + }, + clientv2types.ErrCounterpartyNotFound, + }, + { + "failure: counterparty client identifier different than destination client", + func() { + // send packet + _, _, err := suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SendPacketTest(suite.chainA.GetContext(), packet.SourceClient, + packet.TimeoutTimestamp, packet.Payloads) + suite.Require().NoError(err, "send packet failed") + + packet.DestinationClient = unusedChannel + }, + clientv2types.ErrInvalidCounterparty, + }, + { + "failure: packet has not timed out yet", + func() { + packet.TimeoutTimestamp = uint64(suite.chainB.GetContext().BlockTime().Add(time.Hour).Unix()) + + // send packet + _, _, err := suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SendPacketTest(suite.chainA.GetContext(), packet.SourceClient, + packet.TimeoutTimestamp, packet.Payloads) + suite.Require().NoError(err, "send packet failed") + }, + types.ErrTimeoutNotReached, + }, + { + "failure: packet already timed out", + func() {}, // equivalent to not sending packet at all + types.ErrNoOpMsg, + }, + { + "failure: packet does not match commitment", + func() { + _, _, err := suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SendPacketTest(suite.chainA.GetContext(), packet.SourceClient, + packet.TimeoutTimestamp, packet.Payloads) + suite.Require().NoError(err, "send packet failed") + + // try to timeout packet with different data + packet.Payloads[0].Value = []byte("different value") + }, + types.ErrInvalidPacket, + }, + { + "failure: client status invalid", + func() { + // send packet + _, _, err := suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SendPacketTest(suite.chainA.GetContext(), packet.SourceClient, + packet.TimeoutTimestamp, packet.Payloads) + suite.Require().NoError(err, "send packet failed") + + freezeClient = true + }, + clienttypes.ErrClientNotActive, + }, + { + "failure: verify non-membership failed", + func() { + // send packet + _, _, err := suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.SendPacketTest(suite.chainA.GetContext(), packet.SourceClient, + packet.TimeoutTimestamp, packet.Payloads) + suite.Require().NoError(err, "send packet failed") + + // set packet receipt to mock a valid past receive + suite.chainB.App.GetIBCKeeper().ChannelKeeperV2.SetPacketReceipt(suite.chainB.GetContext(), packet.DestinationClient, packet.Sequence) + }, + commitmenttypes.ErrInvalidProof, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + // initialize freezeClient to false + freezeClient = false + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupV2() + + // create default packet with a timed out timestamp + payload := mockv2.NewMockPayload(mockv2.ModuleNameA, mockv2.ModuleNameB) + + // make timeoutTimestamp 1 second more than sending chain time to ensure it passes SendPacket + // and times out successfully after update + timeoutTimestamp := uint64(suite.chainB.GetContext().BlockTime().Add(time.Second).Unix()) + + // test cases may mutate timeout values + packet = types.NewPacket(1, path.EndpointA.ClientID, path.EndpointB.ClientID, + timeoutTimestamp, payload) + + tc.malleate() + + // need to update chainA's client representing chainB to prove missing ack + // commit the changes and update the clients + suite.coordinator.CommitBlock(path.EndpointA.Chain) + suite.Require().NoError(path.EndpointB.UpdateClient()) + suite.Require().NoError(path.EndpointA.UpdateClient()) + + // get proof of packet receipt absence from chainB + receiptKey := hostv2.PacketReceiptKey(packet.DestinationClient, packet.Sequence) + proof, proofHeight := path.EndpointB.QueryProof(receiptKey) + + if freezeClient { + path.EndpointA.FreezeClient() + } + + err := suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.TimeoutPacketTest(suite.chainA.GetContext(), packet, proof, proofHeight) + + expPass := tc.expError == nil + if expPass { + suite.Require().NoError(err) + + commitment := suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.GetPacketCommitment(suite.chainA.GetContext(), packet.DestinationClient, packet.Sequence) + suite.Require().Nil(commitment, "packet commitment not deleted") + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} diff --git a/modules/core/04-channel/v2/module.go b/modules/core/04-channel/v2/module.go new file mode 100644 index 0000000..41f0b9a --- /dev/null +++ b/modules/core/04-channel/v2/module.go @@ -0,0 +1,22 @@ +package channelv2 + +import ( + "github.com/spf13/cobra" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/client/cli" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" +) + +func Name() string { + return types.SubModuleName +} + +// GetQueryCmd returns the root query command for IBC channels v2. +func GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd() +} + +// GetTxCmd returns the root tx command for IBC channels v2. +func GetTxCmd() *cobra.Command { + return cli.NewTxCmd() +} diff --git a/modules/core/04-channel/v2/module_test.go b/modules/core/04-channel/v2/module_test.go new file mode 100644 index 0000000..91746a1 --- /dev/null +++ b/modules/core/04-channel/v2/module_test.go @@ -0,0 +1,31 @@ +package channelv2_test + +import ( + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func TestModuleTestSuite(t *testing.T) { + testifysuite.Run(t, new(ModuleTestSuite)) +} + +type ModuleTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains used for convenience and readability + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain + chainC *ibctesting.TestChain +} + +func (suite *ModuleTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 3) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) + suite.chainC = suite.coordinator.GetChain(ibctesting.GetChainID(3)) +} diff --git a/modules/core/04-channel/v2/types/acknowledgement.go b/modules/core/04-channel/v2/types/acknowledgement.go new file mode 100644 index 0000000..1cdbf52 --- /dev/null +++ b/modules/core/04-channel/v2/types/acknowledgement.go @@ -0,0 +1,52 @@ +package types + +import ( + "bytes" + "crypto/sha256" + + proto "github.com/cosmos/gogoproto/proto" + + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var ( + ErrorAcknowledgement = sha256.Sum256([]byte("UNIVERSAL_ERROR_ACKNOWLEDGEMENT")) + _ exported.Acknowledgement = &Acknowledgement{} +) + +// NewAcknowledgement creates a new Acknowledgement containing the provided app acknowledgements. +func NewAcknowledgement(appAcknowledgements ...[]byte) Acknowledgement { + return Acknowledgement{AppAcknowledgements: appAcknowledgements} +} + +// Validate performs a basic validation of the acknowledgement +func (ack Acknowledgement) Validate() error { + if len(ack.AppAcknowledgements) != 1 { + return errorsmod.Wrap(ErrInvalidAcknowledgement, "app acknowledgements must be of length one") + } + + for _, ack := range ack.AppAcknowledgements { + if len(ack) == 0 { + return errorsmod.Wrap(ErrInvalidAcknowledgement, "app acknowledgement cannot be empty") + } + } + + return nil +} + +// Success returns true if the acknowledgement is successful +// it implements the exported.Acknowledgement interface +func (ack Acknowledgement) Success() bool { + return !bytes.Equal(ack.AppAcknowledgements[0], ErrorAcknowledgement[:]) +} + +// Acknowledgement returns the acknowledgement bytes to implement the acknowledgement interface +func (ack Acknowledgement) Acknowledgement() []byte { + bz, err := proto.Marshal(&ack) + if err != nil { + panic(err) + } + return bz +} diff --git a/modules/core/04-channel/v2/types/acknowledgement_test.go b/modules/core/04-channel/v2/types/acknowledgement_test.go new file mode 100644 index 0000000..6d84655 --- /dev/null +++ b/modules/core/04-channel/v2/types/acknowledgement_test.go @@ -0,0 +1,50 @@ +package types_test + +import ( + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" +) + +// Test_ValidateAcknowledgement tests the acknowledgements Validate method +func (s *TypesTestSuite) Test_ValidateAcknowledgement() { + testCases := []struct { + name string + ack types.Acknowledgement + expError error + }{ + { + "success: valid successful ack", + types.NewAcknowledgement([]byte("appAck1")), + nil, + }, + { + "success: valid failed ack", + types.NewAcknowledgement(types.ErrorAcknowledgement[:]), + nil, + }, + { + "failure: more than one app acknowledgements", + types.NewAcknowledgement([]byte("appAck1"), []byte("appAck2")), + types.ErrInvalidAcknowledgement, + }, + { + "failure: app acknowledgement is empty", + types.NewAcknowledgement([]byte("")), + types.ErrInvalidAcknowledgement, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() + + err := tc.ack.Validate() + + expPass := tc.expError == nil + if expPass { + s.Require().NoError(err) + } else { + s.Require().ErrorIs(err, tc.expError) + } + }) + } +} diff --git a/modules/core/04-channel/v2/types/codec.go b/modules/core/04-channel/v2/types/codec.go new file mode 100644 index 0000000..b69ed59 --- /dev/null +++ b/modules/core/04-channel/v2/types/codec.go @@ -0,0 +1,21 @@ +package types + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/msgservice" +) + +// RegisterInterfaces register the ibc channel submodule interfaces to protobuf +// Any. +func RegisterInterfaces(registry codectypes.InterfaceRegistry) { + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgSendPacket{}, + &MsgRecvPacket{}, + &MsgTimeout{}, + &MsgAcknowledgement{}, + ) + + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} diff --git a/modules/core/04-channel/v2/types/commitment.go b/modules/core/04-channel/v2/types/commitment.go new file mode 100644 index 0000000..19b6700 --- /dev/null +++ b/modules/core/04-channel/v2/types/commitment.go @@ -0,0 +1,66 @@ +package types + +import ( + "crypto/sha256" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// CommitPacket returns the V2 packet commitment bytes. The commitment consists of: +// ha256_hash(0x02 + sha256_hash(destinationClient) + sha256_hash(timeout) + sha256_hash(payload)) from a given packet. +// This results in a fixed length preimage of 32 bytes. +// NOTE: A fixed length preimage is ESSENTIAL to prevent relayers from being able +// to malleate the packet fields and create a commitment hash that matches the original packet. +func CommitPacket(packet Packet) []byte { + var buf []byte + destIDHash := sha256.Sum256([]byte(packet.DestinationClient)) + buf = append(buf, destIDHash[:]...) + + timeoutBytes := sdk.Uint64ToBigEndian(packet.GetTimeoutTimestamp()) + timeoutHash := sha256.Sum256(timeoutBytes) + buf = append(buf, timeoutHash[:]...) + + var appBytes []byte + for _, payload := range packet.Payloads { + appBytes = append(appBytes, hashPayload(payload)...) + } + appHash := sha256.Sum256(appBytes) + buf = append(buf, appHash[:]...) + + buf = append([]byte{byte(2)}, buf...) + + hash := sha256.Sum256(buf) + return hash[:] +} + +// hashPayload returns the hash of the payload. +func hashPayload(data Payload) []byte { + var buf []byte + sourceHash := sha256.Sum256([]byte(data.SourcePort)) + buf = append(buf, sourceHash[:]...) + destHash := sha256.Sum256([]byte(data.DestinationPort)) + buf = append(buf, destHash[:]...) + payloadVersionHash := sha256.Sum256([]byte(data.Version)) + buf = append(buf, payloadVersionHash[:]...) + payloadEncodingHash := sha256.Sum256([]byte(data.Encoding)) + buf = append(buf, payloadEncodingHash[:]...) + payloadValueHash := sha256.Sum256(data.Value) + buf = append(buf, payloadValueHash[:]...) + hash := sha256.Sum256(buf) + return hash[:] +} + +// CommitAcknowledgement returns the V2 acknowledgement commitment bytes. The commitment consists of: +// sha256_hash(0x02 + sha256_hash(ack1) + sha256_hash(ack2) + ...) from a given acknowledgement. +func CommitAcknowledgement(acknowledgement Acknowledgement) []byte { + var buf []byte + for _, ack := range acknowledgement.GetAppAcknowledgements() { + hash := sha256.Sum256(ack) + buf = append(buf, hash[:]...) + } + + buf = append([]byte{byte(2)}, buf...) + + hash := sha256.Sum256(buf) + return hash[:] +} diff --git a/modules/core/04-channel/v2/types/commitment_test.go b/modules/core/04-channel/v2/types/commitment_test.go new file mode 100644 index 0000000..c86f52d --- /dev/null +++ b/modules/core/04-channel/v2/types/commitment_test.go @@ -0,0 +1,101 @@ +package types_test + +import ( + "encoding/hex" + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" + + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" +) + +// TestCommitPacket is primarily used to document the expected commitment output +// so that other implementations (such as the IBC Solidity) can replicate the +// same commitment output. But it is also useful to catch any changes in the commitment. +func TestCommitPacket(t *testing.T) { + var packet types.Packet + testCases := []struct { + name string + malleate func() + expectedHash string + }{ + { + "json packet", + func() {}, // default is json packet + "a096722aa6534040a0efbdae05765132a7b223ad306d6512f3734821bd046505", + }, + { + "abi packet", + func() { + transferData, err := transfertypes.EncodeABIFungibleTokenPacketData(&transfertypes.FungibleTokenPacketData{ + Denom: "uatom", + Amount: "1000000", + Sender: "sender", + Receiver: "receiver", + Memo: "memo", + }) + require.NoError(t, err) + packet.Payloads[0].Value = transferData + packet.Payloads[0].Encoding = transfertypes.EncodingABI + }, + "d408dca5088b9b375edb3c4df6bae0e18084fc0dbd90fcd0d028506553c81b25", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + transferData, err := json.Marshal(transfertypes.FungibleTokenPacketData{ + Denom: "uatom", + Amount: "1000000", + Sender: "sender", + Receiver: "receiver", + Memo: "memo", + }) + require.NoError(t, err) + packet = types.Packet{ + Sequence: 1, + SourceClient: "07-tendermint-0", + DestinationClient: "07-tendermint-1", + TimeoutTimestamp: 100, + Payloads: []types.Payload{ + { + SourcePort: transfertypes.PortID, + DestinationPort: transfertypes.PortID, + Version: transfertypes.V1, + Encoding: transfertypes.EncodingJSON, + Value: transferData, + }, + }, + } + tc.malleate() + commitment := types.CommitPacket(packet) + require.Equal(t, tc.expectedHash, hex.EncodeToString(commitment)) + require.Len(t, commitment, 32) + }) + } +} + +// TestCommitAcknowledgement is primarily used to document the expected commitment output +// so that other implementations (such as the IBC Solidity) can replicate the +// same commitment output. But it is also useful to catch any changes in the commitment. +func TestCommitAcknowledgement(t *testing.T) { + ack := types.Acknowledgement{ + AppAcknowledgements: [][]byte{ + []byte("some bytes"), + }, + } + + commitment := types.CommitAcknowledgement(ack) + require.Equal(t, "f03b4667413e56aaf086663267913e525c442b56fa1af4fa3f3dab9f37044c5b", hex.EncodeToString(commitment)) + + failedAck := types.Acknowledgement{ + AppAcknowledgements: [][]byte{ + types.ErrorAcknowledgement[:], + }, + } + + failedAckCommitment := types.CommitAcknowledgement(failedAck) + require.Equal(t, "e2fb30dfbf7abdeaca82d426534d2b3a9d5444dd2a87fa16d38b77ba1a13ced7", hex.EncodeToString(failedAckCommitment)) +} diff --git a/modules/core/04-channel/v2/types/errors.go b/modules/core/04-channel/v2/types/errors.go new file mode 100644 index 0000000..f677060 --- /dev/null +++ b/modules/core/04-channel/v2/types/errors.go @@ -0,0 +1,19 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" +) + +var ( + ErrInvalidPacket = errorsmod.Register(SubModuleName, 2, "invalid packet") + ErrInvalidPayload = errorsmod.Register(SubModuleName, 3, "invalid payload") + ErrSequenceSendNotFound = errorsmod.Register(SubModuleName, 4, "sequence send not found") + ErrInvalidAcknowledgement = errorsmod.Register(SubModuleName, 5, "invalid acknowledgement") + ErrPacketCommitmentNotFound = errorsmod.Register(SubModuleName, 6, "packet commitment not found") + ErrAcknowledgementNotFound = errorsmod.Register(SubModuleName, 7, "packet acknowledgement not found") + ErrInvalidTimeout = errorsmod.Register(SubModuleName, 8, "invalid packet timeout") + ErrTimeoutElapsed = errorsmod.Register(SubModuleName, 9, "timeout elapsed") + ErrTimeoutNotReached = errorsmod.Register(SubModuleName, 10, "timeout not reached") + ErrAcknowledgementExists = errorsmod.Register(SubModuleName, 11, "acknowledgement for packet already exists") + ErrNoOpMsg = errorsmod.Register(SubModuleName, 12, "message is redundant, no-op will be performed") +) diff --git a/modules/core/04-channel/v2/types/events.go b/modules/core/04-channel/v2/types/events.go new file mode 100644 index 0000000..41d6964 --- /dev/null +++ b/modules/core/04-channel/v2/types/events.go @@ -0,0 +1,28 @@ +package types + +import ( + "fmt" + + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// IBC v2 core events +const ( + EventTypeSendPacket = "send_packet" + EventTypeRecvPacket = "recv_packet" + EventTypeTimeoutPacket = "timeout_packet" + EventTypeAcknowledgePacket = "acknowledge_packet" + EventTypeWriteAck = "write_acknowledgement" + + AttributeKeySrcClient = "packet_source_client" + AttributeKeyDstClient = "packet_dest_client" + AttributeKeySequence = "packet_sequence" + AttributeKeyTimeoutTimestamp = "packet_timeout_timestamp" + AttributeKeyEncodedPacketHex = "encoded_packet_hex" + AttributeKeyEncodedAckHex = "encoded_acknowledgement_hex" +) + +// IBC v2 core events vars +var ( + AttributeValueCategory = fmt.Sprintf("%s_%s", ibcexported.ModuleName, SubModuleName) +) diff --git a/modules/core/04-channel/v2/types/expected_keepers.go b/modules/core/04-channel/v2/types/expected_keepers.go new file mode 100644 index 0000000..8284d2b --- /dev/null +++ b/modules/core/04-channel/v2/types/expected_keepers.go @@ -0,0 +1,28 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +type ClientKeeper interface { + // VerifyMembership retrieves the light client module for the clientID and verifies the proof of the existence of a key-value pair at a specified height. + VerifyMembership(ctx sdk.Context, clientID string, height exported.Height, delayTimePeriod uint64, delayBlockPeriod uint64, proof []byte, path exported.Path, value []byte) error + // VerifyNonMembership retrieves the light client module for the clientID and verifies the absence of a given key at a specified height. + VerifyNonMembership(ctx sdk.Context, clientID string, height exported.Height, delayTimePeriod uint64, delayBlockPeriod uint64, proof []byte, path exported.Path) error + // GetClientStatus returns the status of a client given the client ID + GetClientStatus(ctx sdk.Context, clientID string) exported.Status + // GetClientLatestHeight returns the latest height of a client given the client ID + GetClientLatestHeight(ctx sdk.Context, clientID string) clienttypes.Height + // GetClientTimestampAtHeight returns the timestamp for a given height on the client + // given its client ID and height + GetClientTimestampAtHeight(ctx sdk.Context, clientID string, height exported.Height) (uint64, error) + // GetClientState gets a particular client from the store + GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool) + // GetClientConsensusState gets the stored consensus state from a client at a given height. + GetClientConsensusState(ctx sdk.Context, clientID string, height exported.Height) (exported.ConsensusState, bool) + // GetAllGenesisClients returns all the clients in state with their client ids returned as IdentifiedClientState + GetAllGenesisClients(ctx sdk.Context) clienttypes.IdentifiedClientStates +} diff --git a/modules/core/04-channel/v2/types/genesis.go b/modules/core/04-channel/v2/types/genesis.go new file mode 100644 index 0000000..3e6abad --- /dev/null +++ b/modules/core/04-channel/v2/types/genesis.go @@ -0,0 +1,117 @@ +package types + +import ( + "errors" + "fmt" + + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +// NewPacketState creates a new PacketState instance. +func NewPacketState(clientID string, sequence uint64, data []byte) PacketState { + return PacketState{ + ClientId: clientID, + Sequence: sequence, + Data: data, + } +} + +// Validate performs basic validation of fields returning an error upon any failure. +func (ps PacketState) Validate() error { + if ps.Data == nil { + return errors.New("data bytes cannot be nil") + } + return validateGenFields(ps.ClientId, ps.Sequence) +} + +// NewPacketSequence creates a new PacketSequences instance. +func NewPacketSequence(clientID string, sequence uint64) PacketSequence { + return PacketSequence{ + ClientId: clientID, + Sequence: sequence, + } +} + +// Validate performs basic validation of fields returning an error upon any failure. +func (ps PacketSequence) Validate() error { + return validateGenFields(ps.ClientId, ps.Sequence) +} + +// NewGenesisState creates a GenesisState instance. +func NewGenesisState( + acks, receipts, commitments, asyncPackets []PacketState, + sendSeqs []PacketSequence, +) GenesisState { + return GenesisState{ + Acknowledgements: acks, + Receipts: receipts, + Commitments: commitments, + AsyncPackets: asyncPackets, + SendSequences: sendSeqs, + } +} + +// DefaultGenesisState returns the ibc channel v2 submodule's default genesis state. +func DefaultGenesisState() GenesisState { + return GenesisState{ + Acknowledgements: []PacketState{}, + Receipts: []PacketState{}, + Commitments: []PacketState{}, + AsyncPackets: []PacketState{}, + SendSequences: []PacketSequence{}, + } +} + +// Validate performs basic genesis state validation returning an error upon any failure. +func (gs GenesisState) Validate() error { + for i, ack := range gs.Acknowledgements { + if err := ack.Validate(); err != nil { + return fmt.Errorf("invalid acknowledgement %v ack index %d: %w", ack, i, err) + } + if len(ack.Data) == 0 { + return fmt.Errorf("invalid acknowledgement %v ack index %d: data bytes cannot be empty", ack, i) + } + } + + for i, receipt := range gs.Receipts { + if err := receipt.Validate(); err != nil { + return fmt.Errorf("invalid acknowledgement %v ack index %d: %w", receipt, i, err) + } + } + + for i, commitment := range gs.Commitments { + if err := commitment.Validate(); err != nil { + return fmt.Errorf("invalid commitment %v index %d: %w", commitment, i, err) + } + if len(commitment.Data) == 0 { + return fmt.Errorf("invalid acknowledgement %v ack index %d: data bytes cannot be empty", commitment, i) + } + } + + for i, ap := range gs.AsyncPackets { + if err := ap.Validate(); err != nil { + return fmt.Errorf("invalid async packet %v index %d: %w", ap, i, err) + } + if len(ap.Data) == 0 { + return fmt.Errorf("invalid async packet %v index %d: data bytes cannot be empty", ap, i) + } + } + + for i, ss := range gs.SendSequences { + if err := ss.Validate(); err != nil { + return fmt.Errorf("invalid send sequence %v index %d: %w", ss, i, err) + } + } + + return nil +} + +func validateGenFields(clientID string, sequence uint64) error { + if err := host.ClientIdentifierValidator(clientID); err != nil { + return fmt.Errorf("invalid channel Id: %w", err) + } + if sequence == 0 { + return errors.New("sequence cannot be 0") + } + return nil +} diff --git a/modules/core/04-channel/v2/types/genesis.pb.go b/modules/core/04-channel/v2/types/genesis.pb.go new file mode 100644 index 0000000..94fe55f --- /dev/null +++ b/modules/core/04-channel/v2/types/genesis.pb.go @@ -0,0 +1,1042 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/channel/v2/genesis.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// GenesisState defines the ibc channel/v2 submodule's genesis state. +type GenesisState struct { + Acknowledgements []PacketState `protobuf:"bytes,2,rep,name=acknowledgements,proto3" json:"acknowledgements"` + Commitments []PacketState `protobuf:"bytes,3,rep,name=commitments,proto3" json:"commitments"` + Receipts []PacketState `protobuf:"bytes,4,rep,name=receipts,proto3" json:"receipts"` + AsyncPackets []PacketState `protobuf:"bytes,5,rep,name=async_packets,json=asyncPackets,proto3" json:"async_packets"` + SendSequences []PacketSequence `protobuf:"bytes,6,rep,name=send_sequences,json=sendSequences,proto3" json:"send_sequences"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_b5d374f126f051c3, []int{0} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetAcknowledgements() []PacketState { + if m != nil { + return m.Acknowledgements + } + return nil +} + +func (m *GenesisState) GetCommitments() []PacketState { + if m != nil { + return m.Commitments + } + return nil +} + +func (m *GenesisState) GetReceipts() []PacketState { + if m != nil { + return m.Receipts + } + return nil +} + +func (m *GenesisState) GetAsyncPackets() []PacketState { + if m != nil { + return m.AsyncPackets + } + return nil +} + +func (m *GenesisState) GetSendSequences() []PacketSequence { + if m != nil { + return m.SendSequences + } + return nil +} + +// PacketState defines the generic type necessary to retrieve and store +// packet commitments, acknowledgements, and receipts. +// Caller is responsible for knowing the context necessary to interpret this +// state as a commitment, acknowledgement, or a receipt. +type PacketState struct { + // client unique identifier. + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // packet sequence. + Sequence uint64 `protobuf:"varint,2,opt,name=sequence,proto3" json:"sequence,omitempty"` + // embedded data that represents packet state. + Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` +} + +func (m *PacketState) Reset() { *m = PacketState{} } +func (m *PacketState) String() string { return proto.CompactTextString(m) } +func (*PacketState) ProtoMessage() {} +func (*PacketState) Descriptor() ([]byte, []int) { + return fileDescriptor_b5d374f126f051c3, []int{1} +} +func (m *PacketState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PacketState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PacketState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *PacketState) XXX_Merge(src proto.Message) { + xxx_messageInfo_PacketState.Merge(m, src) +} +func (m *PacketState) XXX_Size() int { + return m.Size() +} +func (m *PacketState) XXX_DiscardUnknown() { + xxx_messageInfo_PacketState.DiscardUnknown(m) +} + +var xxx_messageInfo_PacketState proto.InternalMessageInfo + +// PacketSequence defines the genesis type necessary to retrieve and store next send sequences. +type PacketSequence struct { + // client unique identifier. + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // packet sequence + Sequence uint64 `protobuf:"varint,2,opt,name=sequence,proto3" json:"sequence,omitempty"` +} + +func (m *PacketSequence) Reset() { *m = PacketSequence{} } +func (m *PacketSequence) String() string { return proto.CompactTextString(m) } +func (*PacketSequence) ProtoMessage() {} +func (*PacketSequence) Descriptor() ([]byte, []int) { + return fileDescriptor_b5d374f126f051c3, []int{2} +} +func (m *PacketSequence) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PacketSequence) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PacketSequence.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *PacketSequence) XXX_Merge(src proto.Message) { + xxx_messageInfo_PacketSequence.Merge(m, src) +} +func (m *PacketSequence) XXX_Size() int { + return m.Size() +} +func (m *PacketSequence) XXX_DiscardUnknown() { + xxx_messageInfo_PacketSequence.DiscardUnknown(m) +} + +var xxx_messageInfo_PacketSequence proto.InternalMessageInfo + +func (m *PacketSequence) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +func (m *PacketSequence) GetSequence() uint64 { + if m != nil { + return m.Sequence + } + return 0 +} + +func init() { + proto.RegisterType((*GenesisState)(nil), "ibc.core.channel.v2.GenesisState") + proto.RegisterType((*PacketState)(nil), "ibc.core.channel.v2.PacketState") + proto.RegisterType((*PacketSequence)(nil), "ibc.core.channel.v2.PacketSequence") +} + +func init() { proto.RegisterFile("ibc/core/channel/v2/genesis.proto", fileDescriptor_b5d374f126f051c3) } + +var fileDescriptor_b5d374f126f051c3 = []byte{ + // 394 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x92, 0x3f, 0x6f, 0xda, 0x40, + 0x18, 0xc6, 0x6d, 0xec, 0x22, 0x38, 0xfe, 0xa8, 0xba, 0x76, 0xb0, 0xa8, 0x64, 0x5c, 0xba, 0x78, + 0xc1, 0x47, 0x69, 0xa7, 0x4a, 0x5d, 0x58, 0x5a, 0xd4, 0x05, 0xb9, 0x43, 0xa5, 0x2e, 0xd4, 0x3e, + 0xbf, 0x32, 0x27, 0xec, 0x3b, 0x97, 0x3b, 0x88, 0xf8, 0x06, 0xd9, 0x92, 0x8f, 0x90, 0x8f, 0xc3, + 0xc8, 0x98, 0x29, 0x8a, 0xe0, 0x8b, 0x44, 0xd8, 0x80, 0x88, 0x12, 0x45, 0x22, 0xdb, 0xeb, 0xd7, + 0xcf, 0xef, 0xf7, 0xde, 0xf0, 0xa0, 0x8f, 0x2c, 0xa4, 0x84, 0x8a, 0x19, 0x10, 0x3a, 0x09, 0x38, + 0x87, 0x84, 0x2c, 0xfa, 0x24, 0x06, 0x0e, 0x92, 0x49, 0x2f, 0x9b, 0x09, 0x25, 0xf0, 0x3b, 0x16, + 0x52, 0x6f, 0x17, 0xf1, 0xf6, 0x11, 0x6f, 0xd1, 0x6f, 0xbd, 0x8f, 0x45, 0x2c, 0xf2, 0xff, 0x64, + 0x37, 0x15, 0xd1, 0xce, 0x95, 0x81, 0xea, 0x3f, 0x0a, 0xf8, 0xb7, 0x0a, 0x14, 0x60, 0x1f, 0xbd, + 0x0d, 0xe8, 0x94, 0x8b, 0x8b, 0x04, 0xa2, 0x18, 0x52, 0xe0, 0x4a, 0x5a, 0x25, 0xc7, 0x70, 0x6b, + 0x7d, 0xc7, 0x7b, 0x46, 0xeb, 0x8d, 0x02, 0x3a, 0x05, 0x95, 0xb3, 0x03, 0x73, 0x75, 0xd7, 0xd6, + 0xfc, 0x27, 0x3c, 0xfe, 0x89, 0x6a, 0x54, 0xa4, 0x29, 0x53, 0x85, 0xce, 0x38, 0x4b, 0x77, 0x8a, + 0xe2, 0x01, 0xaa, 0xcc, 0x80, 0x02, 0xcb, 0x94, 0xb4, 0xcc, 0xb3, 0x34, 0x47, 0x0e, 0xff, 0x42, + 0x8d, 0x40, 0x2e, 0x39, 0x1d, 0x67, 0x79, 0x48, 0x5a, 0x6f, 0xce, 0x12, 0xd5, 0x73, 0xb8, 0xd8, + 0x4b, 0x3c, 0x42, 0x4d, 0x09, 0x3c, 0x1a, 0x4b, 0xf8, 0x3f, 0x07, 0x4e, 0x41, 0x5a, 0xe5, 0xdc, + 0xf6, 0xe9, 0x25, 0xdb, 0x3e, 0xbb, 0x17, 0x36, 0x76, 0x82, 0xc3, 0x4e, 0x76, 0xfe, 0xa1, 0xda, + 0xc9, 0x51, 0xfc, 0x01, 0x55, 0x69, 0xc2, 0x80, 0xab, 0x31, 0x8b, 0x2c, 0xdd, 0xd1, 0xdd, 0xaa, + 0x5f, 0x29, 0x16, 0xc3, 0x08, 0xb7, 0x50, 0xe5, 0x70, 0xd8, 0x2a, 0x39, 0xba, 0x6b, 0xfa, 0xc7, + 0x6f, 0x8c, 0x91, 0x19, 0x05, 0x2a, 0xb0, 0x0c, 0x47, 0x77, 0xeb, 0x7e, 0x3e, 0x7f, 0x33, 0x2f, + 0x6f, 0xda, 0x5a, 0x67, 0x88, 0x9a, 0x8f, 0x1f, 0xf2, 0xea, 0x23, 0x83, 0x3f, 0xab, 0x8d, 0xad, + 0xaf, 0x37, 0xb6, 0x7e, 0xbf, 0xb1, 0xf5, 0xeb, 0xad, 0xad, 0xad, 0xb7, 0xb6, 0x76, 0xbb, 0xb5, + 0xb5, 0xbf, 0xdf, 0x63, 0xa6, 0x26, 0xf3, 0xd0, 0xa3, 0x22, 0x25, 0x54, 0xc8, 0x54, 0x48, 0xc2, + 0x42, 0xda, 0x8d, 0x05, 0x59, 0x7c, 0xee, 0x91, 0x54, 0x44, 0xf3, 0x04, 0x64, 0xd1, 0xe3, 0xde, + 0xd7, 0xee, 0x49, 0x95, 0xd5, 0x32, 0x03, 0x19, 0x96, 0xf3, 0x7a, 0x7e, 0x79, 0x08, 0x00, 0x00, + 0xff, 0xff, 0x9c, 0x0e, 0x36, 0xab, 0xee, 0x02, 0x00, 0x00, +} + +func (m *GenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.SendSequences) > 0 { + for iNdEx := len(m.SendSequences) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.SendSequences[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + } + if len(m.AsyncPackets) > 0 { + for iNdEx := len(m.AsyncPackets) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.AsyncPackets[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + } + if len(m.Receipts) > 0 { + for iNdEx := len(m.Receipts) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Receipts[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + } + if len(m.Commitments) > 0 { + for iNdEx := len(m.Commitments) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Commitments[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + if len(m.Acknowledgements) > 0 { + for iNdEx := len(m.Acknowledgements) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Acknowledgements[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + return len(dAtA) - i, nil +} + +func (m *PacketState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PacketState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PacketState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0x1a + } + if m.Sequence != 0 { + i = encodeVarintGenesis(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x10 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *PacketSequence) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PacketSequence) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PacketSequence) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Sequence != 0 { + i = encodeVarintGenesis(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x10 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Acknowledgements) > 0 { + for _, e := range m.Acknowledgements { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.Commitments) > 0 { + for _, e := range m.Commitments { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.Receipts) > 0 { + for _, e := range m.Receipts { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.AsyncPackets) > 0 { + for _, e := range m.AsyncPackets { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.SendSequences) > 0 { + for _, e := range m.SendSequences { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + return n +} + +func (m *PacketState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + if m.Sequence != 0 { + n += 1 + sovGenesis(uint64(m.Sequence)) + } + l = len(m.Data) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + return n +} + +func (m *PacketSequence) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + if m.Sequence != 0 { + n += 1 + sovGenesis(uint64(m.Sequence)) + } + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Acknowledgements", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Acknowledgements = append(m.Acknowledgements, PacketState{}) + if err := m.Acknowledgements[len(m.Acknowledgements)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Commitments", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Commitments = append(m.Commitments, PacketState{}) + if err := m.Commitments[len(m.Commitments)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Receipts", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Receipts = append(m.Receipts, PacketState{}) + if err := m.Receipts[len(m.Receipts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AsyncPackets", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AsyncPackets = append(m.AsyncPackets, PacketState{}) + if err := m.AsyncPackets[len(m.AsyncPackets)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SendSequences", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SendSequences = append(m.SendSequences, PacketSequence{}) + if err := m.SendSequences[len(m.SendSequences)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PacketState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PacketState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PacketState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PacketSequence) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PacketSequence: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PacketSequence: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/04-channel/v2/types/genesis_test.go b/modules/core/04-channel/v2/types/genesis_test.go new file mode 100644 index 0000000..7fea8c2 --- /dev/null +++ b/modules/core/04-channel/v2/types/genesis_test.go @@ -0,0 +1,84 @@ +package types_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func TestValidateGenesis(t *testing.T) { + testCases := []struct { + name string + genState types.GenesisState + expError error + }{ + { + "default", + types.DefaultGenesisState(), + nil, + }, + { + "valid genesis", + types.NewGenesisState( + []types.PacketState{types.NewPacketState(ibctesting.FirstChannelID, 1, []byte("ack"))}, + []types.PacketState{types.NewPacketState(ibctesting.SecondChannelID, 1, []byte(""))}, + []types.PacketState{types.NewPacketState(ibctesting.FirstChannelID, 1, []byte("commit_hash"))}, + []types.PacketState{types.NewPacketState(ibctesting.SecondChannelID, 1, []byte("async_packet"))}, + []types.PacketSequence{types.NewPacketSequence(ibctesting.SecondChannelID, 1)}, + ), + nil, + }, + { + "invalid ack", + types.GenesisState{ + Acknowledgements: []types.PacketState{ + types.NewPacketState(ibctesting.SecondChannelID, 1, nil), + }, + }, + errors.New("data bytes cannot be nil"), + }, + { + "invalid commitment", + types.GenesisState{ + Commitments: []types.PacketState{ + types.NewPacketState(ibctesting.FirstChannelID, 1, nil), + }, + }, + errors.New("data bytes cannot be nil"), + }, + { + "invalid async packet", + types.GenesisState{ + AsyncPackets: []types.PacketState{ + types.NewPacketState(ibctesting.FirstChannelID, 1, nil), + }, + }, + errors.New("data bytes cannot be nil"), + }, + { + "invalid send seq", + types.GenesisState{ + SendSequences: []types.PacketSequence{ + types.NewPacketSequence(ibctesting.FirstChannelID, 0), + }, + }, + errors.New("sequence cannot be 0"), + }, + } + + for _, tc := range testCases { + + err := tc.genState.Validate() + + expPass := tc.expError == nil + if expPass { + require.NoError(t, err) + } else { + ibctesting.RequireErrorIsOrContains(t, err, tc.expError) + } + } +} diff --git a/modules/core/04-channel/v2/types/keys.go b/modules/core/04-channel/v2/types/keys.go new file mode 100644 index 0000000..e1f36f7 --- /dev/null +++ b/modules/core/04-channel/v2/types/keys.go @@ -0,0 +1,25 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + // SubModuleName defines the channelv2 module name. + SubModuleName = "channelv2" + + // KeyAsyncPacket defines the key to store the async packet. + KeyAsyncPacket = "async_packet" +) + +// AsyncPacketKey returns the key under which the packet is stored +// if the receiving application returns an async acknowledgement. +func AsyncPacketKey(clientID string, sequence uint64) []byte { + return append(AsyncPacketPrefixKey(clientID), sdk.Uint64ToBigEndian(sequence)...) +} + +// AsyncPacketPrefixKey returns the prefix key under which all async packets are stored +// for a given clientID. +func AsyncPacketPrefixKey(clientID string) []byte { + return append([]byte(clientID), []byte(KeyAsyncPacket)...) +} diff --git a/modules/core/04-channel/v2/types/merkle.go b/modules/core/04-channel/v2/types/merkle.go new file mode 100644 index 0000000..cc731b2 --- /dev/null +++ b/modules/core/04-channel/v2/types/merkle.go @@ -0,0 +1,22 @@ +package types + +import ( + "slices" + + commitmenttypesv2 "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types/v2" +) + +// BuildMerklePath takes the merkle path prefix and an ICS24 path +// and builds a new path by appending the ICS24 path to the last element of the merkle path prefix. +func BuildMerklePath(prefix [][]byte, path []byte) commitmenttypesv2.MerklePath { + prefixLength := len(prefix) + if prefixLength == 0 { + panic("cannot build merkle path with empty prefix") + } + + // copy prefix to avoid modifying the original slice + fullPath := slices.Clone(prefix) + // append path to last element + fullPath[prefixLength-1] = append(fullPath[prefixLength-1], path...) + return commitmenttypesv2.NewMerklePath(fullPath...) +} diff --git a/modules/core/04-channel/v2/types/merkle_test.go b/modules/core/04-channel/v2/types/merkle_test.go new file mode 100644 index 0000000..798c3f3 --- /dev/null +++ b/modules/core/04-channel/v2/types/merkle_test.go @@ -0,0 +1,69 @@ +package types_test + +import ( + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + commitmenttypesv2 "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types/v2" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (s *TypesTestSuite) TestBuildMerklePath() { + path := ibctesting.NewPath(s.chainA, s.chainB) + path.SetupV2() + + prefixPath := [][]byte{[]byte("ibc"), []byte("")} + packetCommitmentKey := host.PacketCommitmentKey(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, 1) + emptyPrefixPanicMsg := "cannot build merkle path with empty prefix" + + testCases := []struct { + name string + prefix [][]byte + path []byte + expPath commitmenttypesv2.MerklePath + expPanics *string + }{ + { + name: "standard ibc path", + prefix: prefixPath, + path: packetCommitmentKey, + expPath: commitmenttypesv2.NewMerklePath([]byte("ibc"), packetCommitmentKey), + }, + { + name: "non-empty last element prefix path", + prefix: [][]byte{[]byte("ibc"), []byte("abc")}, + path: packetCommitmentKey, + expPath: commitmenttypesv2.NewMerklePath([]byte("ibc"), append([]byte("abc"), packetCommitmentKey...)), + }, + { + name: "many elements in prefix path", + prefix: [][]byte{[]byte("ibc"), []byte("a"), []byte("b"), []byte("c"), []byte("d")}, + path: packetCommitmentKey, + expPath: commitmenttypesv2.NewMerklePath([]byte("ibc"), []byte("a"), []byte("b"), []byte("c"), append([]byte("d"), packetCommitmentKey...)), + }, + { + name: "empty prefix", + prefix: [][]byte{}, + path: packetCommitmentKey, + expPanics: &emptyPrefixPanicMsg, + }, + { + name: "empty path", + prefix: prefixPath, + path: []byte{}, + expPath: commitmenttypesv2.NewMerklePath([]byte("ibc"), []byte("")), + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + if tc.expPanics == nil { + merklePath := types.BuildMerklePath(tc.prefix, tc.path) + s.Require().Equal(tc.expPath, merklePath) + } else { + s.Require().PanicsWithValue(*tc.expPanics, func() { + _ = types.BuildMerklePath(tc.prefix, tc.path) + }) + } + }) + } +} diff --git a/modules/core/04-channel/v2/types/msgs.go b/modules/core/04-channel/v2/types/msgs.go new file mode 100644 index 0000000..fdf0b69 --- /dev/null +++ b/modules/core/04-channel/v2/types/msgs.go @@ -0,0 +1,145 @@ +package types + +import ( + "time" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypesv1 "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +const MaxTimeoutDelta time.Duration = 24 * time.Hour + +var ( + _ sdk.Msg = (*MsgSendPacket)(nil) + _ sdk.HasValidateBasic = (*MsgSendPacket)(nil) + + _ sdk.Msg = (*MsgRecvPacket)(nil) + _ sdk.HasValidateBasic = (*MsgRecvPacket)(nil) + + _ sdk.Msg = (*MsgTimeout)(nil) + _ sdk.HasValidateBasic = (*MsgTimeout)(nil) + + _ sdk.Msg = (*MsgAcknowledgement)(nil) + _ sdk.HasValidateBasic = (*MsgAcknowledgement)(nil) +) + +// NewMsgSendPacket creates a new MsgSendPacket instance. +func NewMsgSendPacket(sourceClient string, timeoutTimestamp uint64, signer string, payloads ...Payload) *MsgSendPacket { + return &MsgSendPacket{ + SourceClient: sourceClient, + TimeoutTimestamp: timeoutTimestamp, + Payloads: payloads, + Signer: signer, + } +} + +// ValidateBasic performs basic checks on a MsgSendPacket. +func (msg *MsgSendPacket) ValidateBasic() error { + if err := host.ClientIdentifierValidator(msg.SourceClient); err != nil { + return err + } + + if msg.TimeoutTimestamp == 0 { + return errorsmod.Wrap(ErrInvalidTimeout, "timeout must not be 0") + } + + if len(msg.Payloads) != 1 { + return errorsmod.Wrapf(ErrInvalidPayload, "payloads must be of length 1, got %d instead", len(msg.Payloads)) + } + + for _, pd := range msg.Payloads { + if err := pd.ValidateBasic(); err != nil { + return err + } + } + + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + + return nil +} + +// NewMsgRecvPacket creates a new MsgRecvPacket instance. +func NewMsgRecvPacket(packet Packet, proofCommitment []byte, proofHeight clienttypes.Height, signer string) *MsgRecvPacket { + return &MsgRecvPacket{ + Packet: packet, + ProofCommitment: proofCommitment, + ProofHeight: proofHeight, + Signer: signer, + } +} + +// ValidateBasic performs basic checks on a MsgRecvPacket. +func (msg *MsgRecvPacket) ValidateBasic() error { + if len(msg.ProofCommitment) == 0 { + return errorsmod.Wrap(commitmenttypesv1.ErrInvalidProof, "proof commitment can not be empty") + } + + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + + return msg.Packet.ValidateBasic() +} + +// NewMsgAcknowledgement creates a new MsgAcknowledgement instance +func NewMsgAcknowledgement(packet Packet, acknowledgement Acknowledgement, proofAcked []byte, proofHeight clienttypes.Height, signer string) *MsgAcknowledgement { + return &MsgAcknowledgement{ + Packet: packet, + Acknowledgement: acknowledgement, + ProofAcked: proofAcked, + ProofHeight: proofHeight, + Signer: signer, + } +} + +// ValidateBasic performs basic checks on a MsgAcknowledgement. +func (msg *MsgAcknowledgement) ValidateBasic() error { + if len(msg.ProofAcked) == 0 { + return errorsmod.Wrap(commitmenttypesv1.ErrInvalidProof, "cannot submit an empty acknowledgement proof") + } + + if err := msg.Acknowledgement.Validate(); err != nil { + return err + } + + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + + return msg.Packet.ValidateBasic() +} + +// NewMsgTimeout creates a new MsgTimeout instance +func NewMsgTimeout(packet Packet, proofUnreceived []byte, proofHeight clienttypes.Height, signer string) *MsgTimeout { + return &MsgTimeout{ + Packet: packet, + ProofUnreceived: proofUnreceived, + ProofHeight: proofHeight, + Signer: signer, + } +} + +// ValidateBasic performs basic checks on a MsgTimeout +func (msg *MsgTimeout) ValidateBasic() error { + if len(msg.ProofUnreceived) == 0 { + return errorsmod.Wrap(commitmenttypesv1.ErrInvalidProof, "proof unreceived can not be empty") + } + + _, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + + return msg.Packet.ValidateBasic() +} diff --git a/modules/core/04-channel/v2/types/msgs_test.go b/modules/core/04-channel/v2/types/msgs_test.go new file mode 100644 index 0000000..1d88cfb --- /dev/null +++ b/modules/core/04-channel/v2/types/msgs_test.go @@ -0,0 +1,305 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" + + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + mockv2 "github.com/cosmos/ibc-go/v10/testing/mock/v2" +) + +var testProof = []byte("test") + +type TypesTestSuite struct { + suite.Suite + + coordinator *ibctesting.Coordinator + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain +} + +func (s *TypesTestSuite) SetupTest() { + s.coordinator = ibctesting.NewCoordinator(s.T(), 2) + s.chainA = s.coordinator.GetChain(ibctesting.GetChainID(1)) + s.chainB = s.coordinator.GetChain(ibctesting.GetChainID(2)) +} + +func TestTypesTestSuite(t *testing.T) { + suite.Run(t, new(TypesTestSuite)) +} + +func (s *TypesTestSuite) TestMsgSendPacketValidateBasic() { + var msg *types.MsgSendPacket + testCases := []struct { + name string + malleate func() + expError error + }{ + { + name: "success", + malleate: func() {}, + }, + { + name: "failure: invalid source channel", + malleate: func() { + msg.SourceClient = "" + }, + expError: host.ErrInvalidID, + }, + { + name: "failure: invalid timestamp", + malleate: func() { + msg.TimeoutTimestamp = 0 + }, + expError: types.ErrInvalidTimeout, + }, + { + name: "failure: invalid length for payload", + malleate: func() { + msg.Payloads = []types.Payload{{}, {}} + }, + expError: types.ErrInvalidPayload, + }, + { + name: "failure: invalid packetdata", + malleate: func() { + msg.Payloads = []types.Payload{} + }, + expError: types.ErrInvalidPayload, + }, + { + name: "failure: invalid payload", + malleate: func() { + msg.Payloads[0].DestinationPort = "" + }, + expError: host.ErrInvalidID, + }, + { + name: "failure: invalid signer", + malleate: func() { + msg.Signer = "" + }, + expError: ibcerrors.ErrInvalidAddress, + }, + } + for _, tc := range testCases { + s.Run(tc.name, func() { + msg = types.NewMsgSendPacket( + ibctesting.FirstChannelID, s.chainA.GetTimeoutTimestamp(), + s.chainA.SenderAccount.GetAddress().String(), + types.Payload{SourcePort: ibctesting.MockPort, DestinationPort: ibctesting.MockPort, Version: "ics20-1", Encoding: transfertypes.EncodingJSON, Value: ibctesting.MockPacketData}, + ) + + tc.malleate() + + err := msg.ValidateBasic() + expPass := tc.expError == nil + if expPass { + s.Require().NoError(err) + } else { + ibctesting.RequireErrorIsOrContains(s.T(), err, tc.expError) + } + }) + } +} + +func (s *TypesTestSuite) TestMsgRecvPacketValidateBasic() { + var msg *types.MsgRecvPacket + testCases := []struct { + name string + malleate func() + expError error + }{ + { + name: "success", + malleate: func() {}, + }, + { + name: "failure: invalid packet", + malleate: func() { + msg.Packet.Payloads = []types.Payload{} + }, + expError: types.ErrInvalidPacket, + }, + { + name: "failure: invalid proof commitment", + malleate: func() { + msg.ProofCommitment = []byte{} + }, + expError: commitmenttypes.ErrInvalidProof, + }, + { + name: "failure: invalid length for packet payloads", + malleate: func() { + msg.Packet.Payloads = []types.Payload{{}, {}} + }, + expError: types.ErrInvalidPacket, + }, + { + name: "failure: invalid signer", + malleate: func() { + msg.Signer = "" + }, + expError: ibcerrors.ErrInvalidAddress, + }, + } + for _, tc := range testCases { + s.Run(tc.name, func() { + packet := types.NewPacket(1, ibctesting.FirstChannelID, ibctesting.SecondChannelID, s.chainA.GetTimeoutTimestamp(), mockv2.NewMockPayload(mockv2.ModuleNameA, mockv2.ModuleNameB)) + + msg = types.NewMsgRecvPacket(packet, testProof, s.chainA.GetTimeoutHeight(), s.chainA.SenderAccount.GetAddress().String()) + + tc.malleate() + + err := msg.ValidateBasic() + + expPass := tc.expError == nil + + if expPass { + s.Require().NoError(err) + } else { + ibctesting.RequireErrorIsOrContains(s.T(), err, tc.expError) + } + }) + } +} + +func (s *TypesTestSuite) TestMsgAcknowledge_ValidateBasic() { + var msg *types.MsgAcknowledgement + testCases := []struct { + name string + malleate func() + expError error + }{ + { + name: "success", + malleate: func() {}, + }, + { + name: "failure: invalid proof of acknowledgement", + malleate: func() { + msg.ProofAcked = []byte{} + }, + expError: commitmenttypes.ErrInvalidProof, + }, + { + name: "failure: invalid length for packet payloads", + malleate: func() { + msg.Packet.Payloads = []types.Payload{{}, {}} + }, + expError: types.ErrInvalidPacket, + }, + { + name: "failure: invalid signer", + malleate: func() { + msg.Signer = "" + }, + expError: ibcerrors.ErrInvalidAddress, + }, + { + name: "failure: invalid packet", + malleate: func() { + msg.Packet.Sequence = 0 + }, + expError: types.ErrInvalidPacket, + }, + { + name: "failure: invalid acknowledgement", + malleate: func() { + msg.Acknowledgement = types.NewAcknowledgement([]byte("")) + }, + expError: types.ErrInvalidAcknowledgement, + }, + } + for _, tc := range testCases { + s.Run(tc.name, func() { + msg = types.NewMsgAcknowledgement( + types.NewPacket(1, ibctesting.FirstChannelID, ibctesting.SecondChannelID, s.chainA.GetTimeoutTimestamp(), mockv2.NewMockPayload(mockv2.ModuleNameA, mockv2.ModuleNameB)), + types.NewAcknowledgement([]byte("appAck1")), + testProof, + clienttypes.ZeroHeight(), + s.chainA.SenderAccount.GetAddress().String(), + ) + + tc.malleate() + + err := msg.ValidateBasic() + expPass := tc.expError == nil + if expPass { + s.Require().NoError(err) + } else { + ibctesting.RequireErrorIsOrContains(s.T(), err, tc.expError) + } + }) + } +} + +func (s *TypesTestSuite) TestMsgTimeoutValidateBasic() { + var msg *types.MsgTimeout + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + name: "success", + malleate: func() {}, + }, + { + name: "failure: invalid signer", + malleate: func() { + msg.Signer = "" + }, + expError: ibcerrors.ErrInvalidAddress, + }, + { + name: "failure: invalid length for packet payloads", + malleate: func() { + msg.Packet.Payloads = []types.Payload{{}, {}} + }, + expError: types.ErrInvalidPacket, + }, + { + name: "failure: invalid packet", + malleate: func() { + msg.Packet.Sequence = 0 + }, + expError: types.ErrInvalidPacket, + }, + { + name: "failure: invalid proof unreceived", + malleate: func() { + msg.ProofUnreceived = []byte{} + }, + expError: commitmenttypes.ErrInvalidProof, + }, + } + for _, tc := range testCases { + s.Run(tc.name, func() { + msg = types.NewMsgTimeout( + types.NewPacket(1, ibctesting.FirstChannelID, ibctesting.SecondChannelID, s.chainA.GetTimeoutTimestamp(), mockv2.NewMockPayload(mockv2.ModuleNameA, mockv2.ModuleNameB)), + testProof, + clienttypes.ZeroHeight(), + s.chainA.SenderAccount.GetAddress().String(), + ) + + tc.malleate() + + err := msg.ValidateBasic() + expPass := tc.expError == nil + if expPass { + s.Require().NoError(err) + } else { + ibctesting.RequireErrorIsOrContains(s.T(), err, tc.expError) + } + }) + } +} diff --git a/modules/core/04-channel/v2/types/packet.go b/modules/core/04-channel/v2/types/packet.go new file mode 100644 index 0000000..ef7d40c --- /dev/null +++ b/modules/core/04-channel/v2/types/packet.go @@ -0,0 +1,87 @@ +package types + +import ( + "strings" + + errorsmod "cosmossdk.io/errors" + + channeltypesv1 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +// NewPacket constructs a new packet. +func NewPacket(sequence uint64, sourceClient, destinationClient string, timeoutTimestamp uint64, payloads ...Payload) Packet { + return Packet{ + Sequence: sequence, + SourceClient: sourceClient, + DestinationClient: destinationClient, + TimeoutTimestamp: timeoutTimestamp, + Payloads: payloads, + } +} + +// NewPayload constructs a new Payload +func NewPayload(sourcePort, destPort, version, encoding string, value []byte) Payload { + return Payload{ + SourcePort: sourcePort, + DestinationPort: destPort, + Version: version, + Encoding: encoding, + Value: value, + } +} + +// ValidateBasic validates that a Packet satisfies the basic requirements. +func (p Packet) ValidateBasic() error { + if len(p.Payloads) != 1 { + return errorsmod.Wrap(ErrInvalidPacket, "payloads must contain exactly one payload") + } + + totalPayloadsSize := 0 + for _, pd := range p.Payloads { + if err := pd.ValidateBasic(); err != nil { + return errorsmod.Wrap(err, "invalid Payload") + } + totalPayloadsSize += len(pd.Value) + } + + if totalPayloadsSize > channeltypesv1.MaximumPayloadsSize { + return errorsmod.Wrapf(ErrInvalidPacket, "packet data bytes cannot exceed %d bytes", channeltypesv1.MaximumPayloadsSize) + } + + if err := host.ChannelIdentifierValidator(p.SourceClient); err != nil { + return errorsmod.Wrap(err, "invalid source ID") + } + if err := host.ChannelIdentifierValidator(p.DestinationClient); err != nil { + return errorsmod.Wrap(err, "invalid destination ID") + } + + if p.Sequence == 0 { + return errorsmod.Wrap(ErrInvalidPacket, "packet sequence cannot be 0") + } + if p.TimeoutTimestamp == 0 { + return errorsmod.Wrap(ErrInvalidPacket, "packet timeout timestamp cannot be 0") + } + + return nil +} + +// ValidateBasic validates a Payload. +func (p Payload) ValidateBasic() error { + if err := host.PortIdentifierValidator(p.SourcePort); err != nil { + return errorsmod.Wrap(err, "invalid source port ID") + } + if err := host.PortIdentifierValidator(p.DestinationPort); err != nil { + return errorsmod.Wrap(err, "invalid destination port ID") + } + if strings.TrimSpace(p.Version) == "" { + return errorsmod.Wrap(ErrInvalidPayload, "payload version cannot be empty") + } + if strings.TrimSpace(p.Encoding) == "" { + return errorsmod.Wrap(ErrInvalidPayload, "payload encoding cannot be empty") + } + if len(p.Value) == 0 { + return errorsmod.Wrap(ErrInvalidPayload, "payload value cannot be empty") + } + return nil +} diff --git a/modules/core/04-channel/v2/types/packet.pb.go b/modules/core/04-channel/v2/types/packet.pb.go new file mode 100644 index 0000000..06b1da3 --- /dev/null +++ b/modules/core/04-channel/v2/types/packet.pb.go @@ -0,0 +1,1342 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/channel/v2/packet.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// PacketStatus specifies the status of a RecvPacketResult. +type PacketStatus int32 + +const ( + // PACKET_STATUS_UNSPECIFIED indicates an unknown packet status. + PacketStatus_NONE PacketStatus = 0 + // PACKET_STATUS_SUCCESS indicates a successful packet receipt. + PacketStatus_Success PacketStatus = 1 + // PACKET_STATUS_FAILURE indicates a failed packet receipt. + PacketStatus_Failure PacketStatus = 2 + // PACKET_STATUS_ASYNC indicates an async packet receipt. + PacketStatus_Async PacketStatus = 3 +) + +var PacketStatus_name = map[int32]string{ + 0: "PACKET_STATUS_UNSPECIFIED", + 1: "PACKET_STATUS_SUCCESS", + 2: "PACKET_STATUS_FAILURE", + 3: "PACKET_STATUS_ASYNC", +} + +var PacketStatus_value = map[string]int32{ + "PACKET_STATUS_UNSPECIFIED": 0, + "PACKET_STATUS_SUCCESS": 1, + "PACKET_STATUS_FAILURE": 2, + "PACKET_STATUS_ASYNC": 3, +} + +func (x PacketStatus) String() string { + return proto.EnumName(PacketStatus_name, int32(x)) +} + +func (PacketStatus) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_2f814aba9ca97169, []int{0} +} + +// Packet defines a type that carries data across different chains through IBC +type Packet struct { + // number corresponds to the order of sends and receives, where a Packet + // with an earlier sequence number must be sent and received before a Packet + // with a later sequence number. + Sequence uint64 `protobuf:"varint,1,opt,name=sequence,proto3" json:"sequence,omitempty"` + // identifies the sending client on the sending chain. + SourceClient string `protobuf:"bytes,2,opt,name=source_client,json=sourceClient,proto3" json:"source_client,omitempty"` + // identifies the receiving client on the receiving chain. + DestinationClient string `protobuf:"bytes,3,opt,name=destination_client,json=destinationClient,proto3" json:"destination_client,omitempty"` + // timeout timestamp in seconds after which the packet times out. + TimeoutTimestamp uint64 `protobuf:"varint,4,opt,name=timeout_timestamp,json=timeoutTimestamp,proto3" json:"timeout_timestamp,omitempty"` + // a list of payloads, each one for a specific application. + Payloads []Payload `protobuf:"bytes,5,rep,name=payloads,proto3" json:"payloads"` +} + +func (m *Packet) Reset() { *m = Packet{} } +func (m *Packet) String() string { return proto.CompactTextString(m) } +func (*Packet) ProtoMessage() {} +func (*Packet) Descriptor() ([]byte, []int) { + return fileDescriptor_2f814aba9ca97169, []int{0} +} +func (m *Packet) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Packet) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Packet.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Packet) XXX_Merge(src proto.Message) { + xxx_messageInfo_Packet.Merge(m, src) +} +func (m *Packet) XXX_Size() int { + return m.Size() +} +func (m *Packet) XXX_DiscardUnknown() { + xxx_messageInfo_Packet.DiscardUnknown(m) +} + +var xxx_messageInfo_Packet proto.InternalMessageInfo + +func (m *Packet) GetSequence() uint64 { + if m != nil { + return m.Sequence + } + return 0 +} + +func (m *Packet) GetSourceClient() string { + if m != nil { + return m.SourceClient + } + return "" +} + +func (m *Packet) GetDestinationClient() string { + if m != nil { + return m.DestinationClient + } + return "" +} + +func (m *Packet) GetTimeoutTimestamp() uint64 { + if m != nil { + return m.TimeoutTimestamp + } + return 0 +} + +func (m *Packet) GetPayloads() []Payload { + if m != nil { + return m.Payloads + } + return nil +} + +// Payload contains the source and destination ports and payload for the application (version, encoding, raw bytes) +type Payload struct { + // specifies the source port of the packet. + SourcePort string `protobuf:"bytes,1,opt,name=source_port,json=sourcePort,proto3" json:"source_port,omitempty"` + // specifies the destination port of the packet. + DestinationPort string `protobuf:"bytes,2,opt,name=destination_port,json=destinationPort,proto3" json:"destination_port,omitempty"` + // version of the specified application. + Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` + // the encoding used for the provided value. + Encoding string `protobuf:"bytes,4,opt,name=encoding,proto3" json:"encoding,omitempty"` + // the raw bytes for the payload. + Value []byte `protobuf:"bytes,5,opt,name=value,proto3" json:"value,omitempty"` +} + +func (m *Payload) Reset() { *m = Payload{} } +func (m *Payload) String() string { return proto.CompactTextString(m) } +func (*Payload) ProtoMessage() {} +func (*Payload) Descriptor() ([]byte, []int) { + return fileDescriptor_2f814aba9ca97169, []int{1} +} +func (m *Payload) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Payload) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Payload.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Payload) XXX_Merge(src proto.Message) { + xxx_messageInfo_Payload.Merge(m, src) +} +func (m *Payload) XXX_Size() int { + return m.Size() +} +func (m *Payload) XXX_DiscardUnknown() { + xxx_messageInfo_Payload.DiscardUnknown(m) +} + +var xxx_messageInfo_Payload proto.InternalMessageInfo + +func (m *Payload) GetSourcePort() string { + if m != nil { + return m.SourcePort + } + return "" +} + +func (m *Payload) GetDestinationPort() string { + if m != nil { + return m.DestinationPort + } + return "" +} + +func (m *Payload) GetVersion() string { + if m != nil { + return m.Version + } + return "" +} + +func (m *Payload) GetEncoding() string { + if m != nil { + return m.Encoding + } + return "" +} + +func (m *Payload) GetValue() []byte { + if m != nil { + return m.Value + } + return nil +} + +// Acknowledgement contains a list of all ack results associated with a single packet. +// In the case of a successful receive, the acknowledgement will contain an app acknowledgement +// for each application that received a payload in the same order that the payloads were sent +// in the packet. +// If the receive is not successful, the acknowledgement will contain a single app acknowledgment +// which will be a constant error acknowledgment as defined by the IBC v2 protocol. +type Acknowledgement struct { + AppAcknowledgements [][]byte `protobuf:"bytes,1,rep,name=app_acknowledgements,json=appAcknowledgements,proto3" json:"app_acknowledgements,omitempty"` +} + +func (m *Acknowledgement) Reset() { *m = Acknowledgement{} } +func (m *Acknowledgement) String() string { return proto.CompactTextString(m) } +func (*Acknowledgement) ProtoMessage() {} +func (*Acknowledgement) Descriptor() ([]byte, []int) { + return fileDescriptor_2f814aba9ca97169, []int{2} +} +func (m *Acknowledgement) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Acknowledgement) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Acknowledgement.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Acknowledgement) XXX_Merge(src proto.Message) { + xxx_messageInfo_Acknowledgement.Merge(m, src) +} +func (m *Acknowledgement) XXX_Size() int { + return m.Size() +} +func (m *Acknowledgement) XXX_DiscardUnknown() { + xxx_messageInfo_Acknowledgement.DiscardUnknown(m) +} + +var xxx_messageInfo_Acknowledgement proto.InternalMessageInfo + +func (m *Acknowledgement) GetAppAcknowledgements() [][]byte { + if m != nil { + return m.AppAcknowledgements + } + return nil +} + +// RecvPacketResult speecifies the status of a packet as well as the acknowledgement bytes. +type RecvPacketResult struct { + // status of the packet + Status PacketStatus `protobuf:"varint,1,opt,name=status,proto3,enum=ibc.core.channel.v2.PacketStatus" json:"status,omitempty"` + // acknowledgement of the packet + Acknowledgement []byte `protobuf:"bytes,2,opt,name=acknowledgement,proto3" json:"acknowledgement,omitempty"` +} + +func (m *RecvPacketResult) Reset() { *m = RecvPacketResult{} } +func (m *RecvPacketResult) String() string { return proto.CompactTextString(m) } +func (*RecvPacketResult) ProtoMessage() {} +func (*RecvPacketResult) Descriptor() ([]byte, []int) { + return fileDescriptor_2f814aba9ca97169, []int{3} +} +func (m *RecvPacketResult) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RecvPacketResult) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RecvPacketResult.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *RecvPacketResult) XXX_Merge(src proto.Message) { + xxx_messageInfo_RecvPacketResult.Merge(m, src) +} +func (m *RecvPacketResult) XXX_Size() int { + return m.Size() +} +func (m *RecvPacketResult) XXX_DiscardUnknown() { + xxx_messageInfo_RecvPacketResult.DiscardUnknown(m) +} + +var xxx_messageInfo_RecvPacketResult proto.InternalMessageInfo + +func (m *RecvPacketResult) GetStatus() PacketStatus { + if m != nil { + return m.Status + } + return PacketStatus_NONE +} + +func (m *RecvPacketResult) GetAcknowledgement() []byte { + if m != nil { + return m.Acknowledgement + } + return nil +} + +func init() { + proto.RegisterEnum("ibc.core.channel.v2.PacketStatus", PacketStatus_name, PacketStatus_value) + proto.RegisterType((*Packet)(nil), "ibc.core.channel.v2.Packet") + proto.RegisterType((*Payload)(nil), "ibc.core.channel.v2.Payload") + proto.RegisterType((*Acknowledgement)(nil), "ibc.core.channel.v2.Acknowledgement") + proto.RegisterType((*RecvPacketResult)(nil), "ibc.core.channel.v2.RecvPacketResult") +} + +func init() { proto.RegisterFile("ibc/core/channel/v2/packet.proto", fileDescriptor_2f814aba9ca97169) } + +var fileDescriptor_2f814aba9ca97169 = []byte{ + // 591 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x93, 0xc1, 0x6e, 0xd3, 0x4c, + 0x14, 0x85, 0x33, 0x4d, 0xd2, 0x36, 0xd3, 0xfc, 0x7f, 0xdd, 0x69, 0x91, 0x4c, 0x84, 0x52, 0x13, + 0x24, 0x08, 0xa0, 0xda, 0x6d, 0x60, 0xc3, 0x02, 0xa4, 0xd4, 0x75, 0xa5, 0x0a, 0x14, 0xa2, 0x71, + 0x22, 0x04, 0x9b, 0x68, 0x32, 0x19, 0xb9, 0x56, 0x6d, 0x8f, 0xf1, 0x8c, 0x5d, 0xf5, 0x15, 0xba, + 0xe2, 0x05, 0xba, 0x60, 0xcd, 0x8b, 0x74, 0xd9, 0x25, 0x2b, 0x84, 0x5a, 0xf1, 0x1e, 0xc8, 0x63, + 0xb7, 0x4a, 0x0b, 0xac, 0xec, 0x7b, 0xee, 0x77, 0x7c, 0x75, 0xae, 0x75, 0xa1, 0xe1, 0x4f, 0xa9, + 0x45, 0x79, 0xc2, 0x2c, 0x7a, 0x48, 0xa2, 0x88, 0x05, 0x56, 0xd6, 0xb3, 0x62, 0x42, 0x8f, 0x98, + 0x34, 0xe3, 0x84, 0x4b, 0x8e, 0xd6, 0xfd, 0x29, 0x35, 0x73, 0xc2, 0x2c, 0x09, 0x33, 0xeb, 0xb5, + 0x36, 0x3c, 0xee, 0x71, 0xd5, 0xb7, 0xf2, 0xb7, 0x02, 0xed, 0xfc, 0x02, 0x70, 0x71, 0xa8, 0xbc, + 0xa8, 0x05, 0x97, 0x05, 0xfb, 0x9c, 0xb2, 0x88, 0x32, 0x1d, 0x18, 0xa0, 0x5b, 0xc3, 0x37, 0x35, + 0x7a, 0x04, 0xff, 0x13, 0x3c, 0x4d, 0x28, 0x9b, 0xd0, 0xc0, 0x67, 0x91, 0xd4, 0x17, 0x0c, 0xd0, + 0x6d, 0xe0, 0x66, 0x21, 0xda, 0x4a, 0x43, 0x5b, 0x10, 0xcd, 0x98, 0x90, 0x7e, 0x44, 0xa4, 0xcf, + 0xa3, 0x6b, 0xb2, 0xaa, 0xc8, 0xb5, 0xb9, 0x4e, 0x89, 0x3f, 0x87, 0x6b, 0xd2, 0x0f, 0x19, 0x4f, + 0xe5, 0x24, 0x7f, 0x0a, 0x49, 0xc2, 0x58, 0xaf, 0xa9, 0xc1, 0x5a, 0xd9, 0x18, 0x5d, 0xeb, 0xe8, + 0x0d, 0x5c, 0x8e, 0xc9, 0x49, 0xc0, 0xc9, 0x4c, 0xe8, 0x75, 0xa3, 0xda, 0x5d, 0xe9, 0x3d, 0x30, + 0xff, 0x92, 0xd2, 0x1c, 0x16, 0xd0, 0x6e, 0xed, 0xfc, 0xc7, 0x66, 0x05, 0xdf, 0x78, 0x3a, 0x5f, + 0x01, 0x5c, 0x2a, 0x7b, 0x68, 0x13, 0xae, 0x94, 0x61, 0x62, 0x9e, 0x48, 0x95, 0xb5, 0x81, 0x61, + 0x21, 0x0d, 0x79, 0x22, 0xd1, 0x53, 0xa8, 0xcd, 0x07, 0x51, 0x54, 0x11, 0x78, 0x75, 0x4e, 0x57, + 0xa8, 0x0e, 0x97, 0x32, 0x96, 0x08, 0x9f, 0x47, 0x65, 0xd0, 0xeb, 0x32, 0x5f, 0x27, 0x8b, 0x28, + 0x9f, 0xf9, 0x91, 0xa7, 0x52, 0x35, 0xf0, 0x4d, 0x8d, 0x36, 0x60, 0x3d, 0x23, 0x41, 0xca, 0xf4, + 0xba, 0x01, 0xba, 0x4d, 0x5c, 0x14, 0x9d, 0x3d, 0xb8, 0xda, 0xa7, 0x47, 0x11, 0x3f, 0x0e, 0xd8, + 0xcc, 0x63, 0x61, 0xbe, 0xa3, 0x1d, 0xb8, 0x41, 0xe2, 0x78, 0x42, 0x6e, 0xcb, 0x42, 0x07, 0x46, + 0xb5, 0xdb, 0xc4, 0xeb, 0x24, 0x8e, 0xef, 0x38, 0x44, 0xe7, 0x18, 0x6a, 0x98, 0xd1, 0xac, 0xf8, + 0xa9, 0x98, 0x89, 0x34, 0x90, 0xe8, 0x15, 0x5c, 0x14, 0x92, 0xc8, 0x54, 0xa8, 0xb0, 0xff, 0xf7, + 0x1e, 0xfe, 0x63, 0x77, 0xb9, 0xc5, 0x55, 0x20, 0x2e, 0x0d, 0xa8, 0x0b, 0x57, 0xef, 0x4c, 0x57, + 0xab, 0x68, 0xe2, 0xbb, 0xf2, 0xb3, 0x6f, 0x00, 0x36, 0xe7, 0x3f, 0x81, 0x9e, 0xc0, 0xfb, 0xc3, + 0xbe, 0xfd, 0xd6, 0x19, 0x4d, 0xdc, 0x51, 0x7f, 0x34, 0x76, 0x27, 0xe3, 0x81, 0x3b, 0x74, 0xec, + 0x83, 0xfd, 0x03, 0x67, 0x4f, 0xab, 0xb4, 0x96, 0x4f, 0xcf, 0x8c, 0xda, 0xe0, 0xfd, 0xc0, 0x41, + 0x8f, 0xe1, 0xbd, 0xdb, 0xa0, 0x3b, 0xb6, 0x6d, 0xc7, 0x75, 0x35, 0xd0, 0x5a, 0x39, 0x3d, 0x33, + 0x96, 0xdc, 0x94, 0x52, 0x26, 0xc4, 0x9f, 0xdc, 0x7e, 0xff, 0xe0, 0xdd, 0x18, 0x3b, 0xda, 0x42, + 0xc1, 0xed, 0x13, 0x3f, 0x48, 0x13, 0x86, 0x3a, 0x70, 0xfd, 0x36, 0xd7, 0x77, 0x3f, 0x0e, 0x6c, + 0xad, 0xda, 0x6a, 0x9c, 0x9e, 0x19, 0xf5, 0xbe, 0x38, 0x89, 0xe8, 0xee, 0x87, 0xf3, 0xcb, 0x36, + 0xb8, 0xb8, 0x6c, 0x83, 0x9f, 0x97, 0x6d, 0xf0, 0xe5, 0xaa, 0x5d, 0xb9, 0xb8, 0x6a, 0x57, 0xbe, + 0x5f, 0xb5, 0x2b, 0x9f, 0x5e, 0x7b, 0xbe, 0x3c, 0x4c, 0xa7, 0x26, 0xe5, 0xa1, 0x45, 0xb9, 0x08, + 0xb9, 0xb0, 0xfc, 0x29, 0xdd, 0xf2, 0xb8, 0x95, 0xed, 0x6c, 0x5b, 0x21, 0x9f, 0xa5, 0x01, 0x13, + 0xc5, 0x01, 0x6e, 0xbf, 0xdc, 0x9a, 0xbb, 0x41, 0x79, 0x12, 0x33, 0x31, 0x5d, 0x54, 0x87, 0xf5, + 0xe2, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0x31, 0xab, 0x02, 0xa6, 0xa7, 0x03, 0x00, 0x00, +} + +func (m *Packet) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Packet) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Packet) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Payloads) > 0 { + for iNdEx := len(m.Payloads) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Payloads[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintPacket(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + } + if m.TimeoutTimestamp != 0 { + i = encodeVarintPacket(dAtA, i, uint64(m.TimeoutTimestamp)) + i-- + dAtA[i] = 0x20 + } + if len(m.DestinationClient) > 0 { + i -= len(m.DestinationClient) + copy(dAtA[i:], m.DestinationClient) + i = encodeVarintPacket(dAtA, i, uint64(len(m.DestinationClient))) + i-- + dAtA[i] = 0x1a + } + if len(m.SourceClient) > 0 { + i -= len(m.SourceClient) + copy(dAtA[i:], m.SourceClient) + i = encodeVarintPacket(dAtA, i, uint64(len(m.SourceClient))) + i-- + dAtA[i] = 0x12 + } + if m.Sequence != 0 { + i = encodeVarintPacket(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *Payload) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Payload) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Payload) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Value) > 0 { + i -= len(m.Value) + copy(dAtA[i:], m.Value) + i = encodeVarintPacket(dAtA, i, uint64(len(m.Value))) + i-- + dAtA[i] = 0x2a + } + if len(m.Encoding) > 0 { + i -= len(m.Encoding) + copy(dAtA[i:], m.Encoding) + i = encodeVarintPacket(dAtA, i, uint64(len(m.Encoding))) + i-- + dAtA[i] = 0x22 + } + if len(m.Version) > 0 { + i -= len(m.Version) + copy(dAtA[i:], m.Version) + i = encodeVarintPacket(dAtA, i, uint64(len(m.Version))) + i-- + dAtA[i] = 0x1a + } + if len(m.DestinationPort) > 0 { + i -= len(m.DestinationPort) + copy(dAtA[i:], m.DestinationPort) + i = encodeVarintPacket(dAtA, i, uint64(len(m.DestinationPort))) + i-- + dAtA[i] = 0x12 + } + if len(m.SourcePort) > 0 { + i -= len(m.SourcePort) + copy(dAtA[i:], m.SourcePort) + i = encodeVarintPacket(dAtA, i, uint64(len(m.SourcePort))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Acknowledgement) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Acknowledgement) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Acknowledgement) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.AppAcknowledgements) > 0 { + for iNdEx := len(m.AppAcknowledgements) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.AppAcknowledgements[iNdEx]) + copy(dAtA[i:], m.AppAcknowledgements[iNdEx]) + i = encodeVarintPacket(dAtA, i, uint64(len(m.AppAcknowledgements[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *RecvPacketResult) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RecvPacketResult) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RecvPacketResult) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Acknowledgement) > 0 { + i -= len(m.Acknowledgement) + copy(dAtA[i:], m.Acknowledgement) + i = encodeVarintPacket(dAtA, i, uint64(len(m.Acknowledgement))) + i-- + dAtA[i] = 0x12 + } + if m.Status != 0 { + i = encodeVarintPacket(dAtA, i, uint64(m.Status)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintPacket(dAtA []byte, offset int, v uint64) int { + offset -= sovPacket(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Packet) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Sequence != 0 { + n += 1 + sovPacket(uint64(m.Sequence)) + } + l = len(m.SourceClient) + if l > 0 { + n += 1 + l + sovPacket(uint64(l)) + } + l = len(m.DestinationClient) + if l > 0 { + n += 1 + l + sovPacket(uint64(l)) + } + if m.TimeoutTimestamp != 0 { + n += 1 + sovPacket(uint64(m.TimeoutTimestamp)) + } + if len(m.Payloads) > 0 { + for _, e := range m.Payloads { + l = e.Size() + n += 1 + l + sovPacket(uint64(l)) + } + } + return n +} + +func (m *Payload) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.SourcePort) + if l > 0 { + n += 1 + l + sovPacket(uint64(l)) + } + l = len(m.DestinationPort) + if l > 0 { + n += 1 + l + sovPacket(uint64(l)) + } + l = len(m.Version) + if l > 0 { + n += 1 + l + sovPacket(uint64(l)) + } + l = len(m.Encoding) + if l > 0 { + n += 1 + l + sovPacket(uint64(l)) + } + l = len(m.Value) + if l > 0 { + n += 1 + l + sovPacket(uint64(l)) + } + return n +} + +func (m *Acknowledgement) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.AppAcknowledgements) > 0 { + for _, b := range m.AppAcknowledgements { + l = len(b) + n += 1 + l + sovPacket(uint64(l)) + } + } + return n +} + +func (m *RecvPacketResult) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Status != 0 { + n += 1 + sovPacket(uint64(m.Status)) + } + l = len(m.Acknowledgement) + if l > 0 { + n += 1 + l + sovPacket(uint64(l)) + } + return n +} + +func sovPacket(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozPacket(x uint64) (n int) { + return sovPacket(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Packet) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Packet: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Packet: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SourceClient", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthPacket + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthPacket + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SourceClient = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DestinationClient", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthPacket + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthPacket + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DestinationClient = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeoutTimestamp", wireType) + } + m.TimeoutTimestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TimeoutTimestamp |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payloads", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthPacket + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthPacket + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payloads = append(m.Payloads, Payload{}) + if err := m.Payloads[len(m.Payloads)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipPacket(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthPacket + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Payload) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Payload: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Payload: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SourcePort", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthPacket + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthPacket + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SourcePort = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DestinationPort", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthPacket + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthPacket + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DestinationPort = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthPacket + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthPacket + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Version = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Encoding", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthPacket + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthPacket + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Encoding = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthPacket + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthPacket + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Value = append(m.Value[:0], dAtA[iNdEx:postIndex]...) + if m.Value == nil { + m.Value = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipPacket(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthPacket + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Acknowledgement) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Acknowledgement: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Acknowledgement: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppAcknowledgements", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthPacket + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthPacket + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppAcknowledgements = append(m.AppAcknowledgements, make([]byte, postIndex-iNdEx)) + copy(m.AppAcknowledgements[len(m.AppAcknowledgements)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipPacket(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthPacket + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *RecvPacketResult) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RecvPacketResult: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RecvPacketResult: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + m.Status = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Status |= PacketStatus(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Acknowledgement", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowPacket + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthPacket + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthPacket + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Acknowledgement = append(m.Acknowledgement[:0], dAtA[iNdEx:postIndex]...) + if m.Acknowledgement == nil { + m.Acknowledgement = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipPacket(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthPacket + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipPacket(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowPacket + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowPacket + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowPacket + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthPacket + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupPacket + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthPacket + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthPacket = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowPacket = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupPacket = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/04-channel/v2/types/packet_test.go b/modules/core/04-channel/v2/types/packet_test.go new file mode 100644 index 0000000..ea310f1 --- /dev/null +++ b/modules/core/04-channel/v2/types/packet_test.go @@ -0,0 +1,144 @@ +package types_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + channeltypesv1 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + "github.com/cosmos/ibc-go/v10/testing/mock" +) + +// TestValidateBasic tests the ValidateBasic function of Packet +func TestValidateBasic(t *testing.T) { + var packet types.Packet + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() {}, + nil, + }, + { + "success, single payload just below MaxPayloadsSize", + func() { + packet.Payloads[0].Value = make([]byte, channeltypesv1.MaximumPayloadsSize-1) + }, + nil, + }, + { + "failure: invalid single payloads size", + func() { + // bytes that are larger than MaxPayloadsSize + packet.Payloads[0].Value = make([]byte, channeltypesv1.MaximumPayloadsSize+1) + }, + types.ErrInvalidPacket, + }, + // TODO: add test cases for multiple payloads when enabled (#7008) + { + "failure: payloads is nil", + func() { + packet.Payloads = nil + }, + types.ErrInvalidPacket, + }, + { + "failure: empty payload", + func() { + packet.Payloads = []types.Payload{} + }, + types.ErrInvalidPacket, + }, + { + "failure: invalid payload source port ID", + func() { + packet.Payloads[0].SourcePort = "" + }, + host.ErrInvalidID, + }, + { + "failure: invalid payload dest port ID", + func() { + packet.Payloads[0].DestinationPort = "" + }, + host.ErrInvalidID, + }, + { + "failure: invalid source ID", + func() { + packet.SourceClient = "" + }, + host.ErrInvalidID, + }, + { + "failure: invalid dest ID", + func() { + packet.DestinationClient = "" + }, + host.ErrInvalidID, + }, + { + "failure: invalid sequence", + func() { + packet.Sequence = 0 + }, + types.ErrInvalidPacket, + }, + { + "failure: invalid timestamp", + func() { + packet.TimeoutTimestamp = 0 + }, + types.ErrInvalidPacket, + }, + { + "failure: empty version", + func() { + packet.Payloads[0].Version = "" + }, + types.ErrInvalidPayload, + }, + { + "failure: empty encoding", + func() { + packet.Payloads[0].Encoding = "" + }, + types.ErrInvalidPayload, + }, + { + "failure: empty value", + func() { + packet.Payloads[0].Value = []byte{} + }, + types.ErrInvalidPayload, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + packet = types.NewPacket(1, ibctesting.FirstChannelID, ibctesting.SecondChannelID, uint64(time.Now().Unix()), types.Payload{ + SourcePort: ibctesting.MockPort, + DestinationPort: ibctesting.MockPort, + Version: "ics20-v2", + Encoding: transfertypes.EncodingProtobuf, + Value: mock.MockPacketData, + }) + + tc.malleate() + + err := packet.ValidateBasic() + if tc.expErr == nil { + require.NoError(t, err) + } else { + require.ErrorIs(t, err, tc.expErr) + } + }) + } +} diff --git a/modules/core/04-channel/v2/types/query.go b/modules/core/04-channel/v2/types/query.go new file mode 100644 index 0000000..556563b --- /dev/null +++ b/modules/core/04-channel/v2/types/query.go @@ -0,0 +1,82 @@ +package types + +import ( + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" +) + +// NewQueryNextSequenceSendRequest creates a new next sequence send query. +func NewQueryNextSequenceSendRequest(clientID string) *QueryNextSequenceSendRequest { + return &QueryNextSequenceSendRequest{ + ClientId: clientID, + } +} + +// NewQueryNextSequenceSendResponse creates a new QueryNextSequenceSendResponse instance +func NewQueryNextSequenceSendResponse( + sequence uint64, proof []byte, height clienttypes.Height, +) *QueryNextSequenceSendResponse { + return &QueryNextSequenceSendResponse{ + NextSequenceSend: sequence, + Proof: proof, + ProofHeight: height, + } +} + +// NewQueryPacketCommitmentRequest creates and returns a new packet commitment query request. +func NewQueryPacketCommitmentRequest(clientID string, sequence uint64) *QueryPacketCommitmentRequest { + return &QueryPacketCommitmentRequest{ + ClientId: clientID, + Sequence: sequence, + } +} + +// NewQueryPacketCommitmentResponse creates and returns a new packet commitment query response. +func NewQueryPacketCommitmentResponse(commitmentHash []byte, proof []byte, proofHeight clienttypes.Height) *QueryPacketCommitmentResponse { + return &QueryPacketCommitmentResponse{ + Commitment: commitmentHash, + Proof: proof, + ProofHeight: proofHeight, + } +} + +// NewQueryPacketAcknowledgementRequest creates and returns a new packet acknowledgement query request. +func NewQueryPacketAcknowledgementRequest(clientID string, sequence uint64) *QueryPacketAcknowledgementRequest { + return &QueryPacketAcknowledgementRequest{ + ClientId: clientID, + Sequence: sequence, + } +} + +// NewQueryPacketAcknowledgementResponse creates and returns a new packet acknowledgement query response. +func NewQueryPacketAcknowledgementResponse(acknowledgementHash []byte, proof []byte, proofHeight clienttypes.Height) *QueryPacketAcknowledgementResponse { + return &QueryPacketAcknowledgementResponse{ + Acknowledgement: acknowledgementHash, + Proof: proof, + ProofHeight: proofHeight, + } +} + +// NewQueryPacketReceiptRequest creates and returns a new packet receipt query request. +func NewQueryPacketReceiptRequest(clientID string, sequence uint64) *QueryPacketReceiptRequest { + return &QueryPacketReceiptRequest{ + ClientId: clientID, + Sequence: sequence, + } +} + +// NewQueryPacketReceiptResponse creates and returns a new packet receipt query response. +func NewQueryPacketReceiptResponse(exists bool, proof []byte, height clienttypes.Height) *QueryPacketReceiptResponse { + return &QueryPacketReceiptResponse{ + Received: exists, + Proof: proof, + ProofHeight: height, + } +} + +// NewQueryPacketReceiptRequest creates and returns a new packet receipt query request. +func NewQueryUnreceivedPacketsRequest(clientID string, sequences []uint64) *QueryUnreceivedPacketsRequest { + return &QueryUnreceivedPacketsRequest{ + ClientId: clientID, + Sequences: sequences, + } +} diff --git a/modules/core/04-channel/v2/types/query.pb.go b/modules/core/04-channel/v2/types/query.pb.go new file mode 100644 index 0000000..e038049 --- /dev/null +++ b/modules/core/04-channel/v2/types/query.pb.go @@ -0,0 +1,4757 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/channel/v2/query.proto + +package types + +import ( + context "context" + fmt "fmt" + query "github.com/cosmos/cosmos-sdk/types/query" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + types "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// QueryNextSequenceSendRequest is the request type for the Query/QueryNextSequenceSend RPC method +type QueryNextSequenceSendRequest struct { + // client unique identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` +} + +func (m *QueryNextSequenceSendRequest) Reset() { *m = QueryNextSequenceSendRequest{} } +func (m *QueryNextSequenceSendRequest) String() string { return proto.CompactTextString(m) } +func (*QueryNextSequenceSendRequest) ProtoMessage() {} +func (*QueryNextSequenceSendRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_a328cba4986edcab, []int{0} +} +func (m *QueryNextSequenceSendRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryNextSequenceSendRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryNextSequenceSendRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryNextSequenceSendRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryNextSequenceSendRequest.Merge(m, src) +} +func (m *QueryNextSequenceSendRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryNextSequenceSendRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryNextSequenceSendRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryNextSequenceSendRequest proto.InternalMessageInfo + +func (m *QueryNextSequenceSendRequest) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +// QueryNextSequenceSendResponse is the response type for the Query/QueryNextSequenceSend RPC method +type QueryNextSequenceSendResponse struct { + // next sequence send number + NextSequenceSend uint64 `protobuf:"varint,1,opt,name=next_sequence_send,json=nextSequenceSend,proto3" json:"next_sequence_send,omitempty"` + // merkle proof of existence + Proof []byte `protobuf:"bytes,2,opt,name=proof,proto3" json:"proof,omitempty"` + // height at which the proof was retrieved + ProofHeight types.Height `protobuf:"bytes,3,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` +} + +func (m *QueryNextSequenceSendResponse) Reset() { *m = QueryNextSequenceSendResponse{} } +func (m *QueryNextSequenceSendResponse) String() string { return proto.CompactTextString(m) } +func (*QueryNextSequenceSendResponse) ProtoMessage() {} +func (*QueryNextSequenceSendResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_a328cba4986edcab, []int{1} +} +func (m *QueryNextSequenceSendResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryNextSequenceSendResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryNextSequenceSendResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryNextSequenceSendResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryNextSequenceSendResponse.Merge(m, src) +} +func (m *QueryNextSequenceSendResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryNextSequenceSendResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryNextSequenceSendResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryNextSequenceSendResponse proto.InternalMessageInfo + +func (m *QueryNextSequenceSendResponse) GetNextSequenceSend() uint64 { + if m != nil { + return m.NextSequenceSend + } + return 0 +} + +func (m *QueryNextSequenceSendResponse) GetProof() []byte { + if m != nil { + return m.Proof + } + return nil +} + +func (m *QueryNextSequenceSendResponse) GetProofHeight() types.Height { + if m != nil { + return m.ProofHeight + } + return types.Height{} +} + +// QueryPacketCommitmentRequest is the request type for the Query/PacketCommitment RPC method. +type QueryPacketCommitmentRequest struct { + // client unique identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // packet sequence + Sequence uint64 `protobuf:"varint,2,opt,name=sequence,proto3" json:"sequence,omitempty"` +} + +func (m *QueryPacketCommitmentRequest) Reset() { *m = QueryPacketCommitmentRequest{} } +func (m *QueryPacketCommitmentRequest) String() string { return proto.CompactTextString(m) } +func (*QueryPacketCommitmentRequest) ProtoMessage() {} +func (*QueryPacketCommitmentRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_a328cba4986edcab, []int{2} +} +func (m *QueryPacketCommitmentRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPacketCommitmentRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPacketCommitmentRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPacketCommitmentRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPacketCommitmentRequest.Merge(m, src) +} +func (m *QueryPacketCommitmentRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryPacketCommitmentRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPacketCommitmentRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPacketCommitmentRequest proto.InternalMessageInfo + +func (m *QueryPacketCommitmentRequest) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +func (m *QueryPacketCommitmentRequest) GetSequence() uint64 { + if m != nil { + return m.Sequence + } + return 0 +} + +// QueryPacketCommitmentResponse is the response type for the Query/PacketCommitment RPC method. +type QueryPacketCommitmentResponse struct { + // packet associated with the request fields + Commitment []byte `protobuf:"bytes,1,opt,name=commitment,proto3" json:"commitment,omitempty"` + // merkle proof of existence + Proof []byte `protobuf:"bytes,2,opt,name=proof,proto3" json:"proof,omitempty"` + // height at which the proof was retrieved + ProofHeight types.Height `protobuf:"bytes,3,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` +} + +func (m *QueryPacketCommitmentResponse) Reset() { *m = QueryPacketCommitmentResponse{} } +func (m *QueryPacketCommitmentResponse) String() string { return proto.CompactTextString(m) } +func (*QueryPacketCommitmentResponse) ProtoMessage() {} +func (*QueryPacketCommitmentResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_a328cba4986edcab, []int{3} +} +func (m *QueryPacketCommitmentResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPacketCommitmentResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPacketCommitmentResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPacketCommitmentResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPacketCommitmentResponse.Merge(m, src) +} +func (m *QueryPacketCommitmentResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryPacketCommitmentResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPacketCommitmentResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPacketCommitmentResponse proto.InternalMessageInfo + +func (m *QueryPacketCommitmentResponse) GetCommitment() []byte { + if m != nil { + return m.Commitment + } + return nil +} + +func (m *QueryPacketCommitmentResponse) GetProof() []byte { + if m != nil { + return m.Proof + } + return nil +} + +func (m *QueryPacketCommitmentResponse) GetProofHeight() types.Height { + if m != nil { + return m.ProofHeight + } + return types.Height{} +} + +// QueryPacketCommitmentsRequest is the request type for the Query/PacketCommitments RPC method. +type QueryPacketCommitmentsRequest struct { + // client unique identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // pagination request + Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryPacketCommitmentsRequest) Reset() { *m = QueryPacketCommitmentsRequest{} } +func (m *QueryPacketCommitmentsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryPacketCommitmentsRequest) ProtoMessage() {} +func (*QueryPacketCommitmentsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_a328cba4986edcab, []int{4} +} +func (m *QueryPacketCommitmentsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPacketCommitmentsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPacketCommitmentsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPacketCommitmentsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPacketCommitmentsRequest.Merge(m, src) +} +func (m *QueryPacketCommitmentsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryPacketCommitmentsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPacketCommitmentsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPacketCommitmentsRequest proto.InternalMessageInfo + +func (m *QueryPacketCommitmentsRequest) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +func (m *QueryPacketCommitmentsRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryPacketCommitmentResponse is the response type for the Query/PacketCommitment RPC method. +type QueryPacketCommitmentsResponse struct { + // collection of packet commitments for the requested channel identifier. + Commitments []*PacketState `protobuf:"bytes,1,rep,name=commitments,proto3" json:"commitments,omitempty"` + // pagination response. + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` + // query block height. + Height types.Height `protobuf:"bytes,3,opt,name=height,proto3" json:"height"` +} + +func (m *QueryPacketCommitmentsResponse) Reset() { *m = QueryPacketCommitmentsResponse{} } +func (m *QueryPacketCommitmentsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryPacketCommitmentsResponse) ProtoMessage() {} +func (*QueryPacketCommitmentsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_a328cba4986edcab, []int{5} +} +func (m *QueryPacketCommitmentsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPacketCommitmentsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPacketCommitmentsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPacketCommitmentsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPacketCommitmentsResponse.Merge(m, src) +} +func (m *QueryPacketCommitmentsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryPacketCommitmentsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPacketCommitmentsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPacketCommitmentsResponse proto.InternalMessageInfo + +func (m *QueryPacketCommitmentsResponse) GetCommitments() []*PacketState { + if m != nil { + return m.Commitments + } + return nil +} + +func (m *QueryPacketCommitmentsResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + +func (m *QueryPacketCommitmentsResponse) GetHeight() types.Height { + if m != nil { + return m.Height + } + return types.Height{} +} + +// QueryPacketAcknowledgementRequest is the request type for the Query/PacketAcknowledgement RPC method. +type QueryPacketAcknowledgementRequest struct { + // client unique identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // packet sequence + Sequence uint64 `protobuf:"varint,2,opt,name=sequence,proto3" json:"sequence,omitempty"` +} + +func (m *QueryPacketAcknowledgementRequest) Reset() { *m = QueryPacketAcknowledgementRequest{} } +func (m *QueryPacketAcknowledgementRequest) String() string { return proto.CompactTextString(m) } +func (*QueryPacketAcknowledgementRequest) ProtoMessage() {} +func (*QueryPacketAcknowledgementRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_a328cba4986edcab, []int{6} +} +func (m *QueryPacketAcknowledgementRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPacketAcknowledgementRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPacketAcknowledgementRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPacketAcknowledgementRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPacketAcknowledgementRequest.Merge(m, src) +} +func (m *QueryPacketAcknowledgementRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryPacketAcknowledgementRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPacketAcknowledgementRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPacketAcknowledgementRequest proto.InternalMessageInfo + +func (m *QueryPacketAcknowledgementRequest) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +func (m *QueryPacketAcknowledgementRequest) GetSequence() uint64 { + if m != nil { + return m.Sequence + } + return 0 +} + +// QueryPacketAcknowledgementResponse is the response type for the Query/PacketAcknowledgement RPC method. +type QueryPacketAcknowledgementResponse struct { + // acknowledgement associated with the request fields + Acknowledgement []byte `protobuf:"bytes,1,opt,name=acknowledgement,proto3" json:"acknowledgement,omitempty"` + // merkle proof of existence + Proof []byte `protobuf:"bytes,2,opt,name=proof,proto3" json:"proof,omitempty"` + // height at which the proof was retrieved + ProofHeight types.Height `protobuf:"bytes,3,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` +} + +func (m *QueryPacketAcknowledgementResponse) Reset() { *m = QueryPacketAcknowledgementResponse{} } +func (m *QueryPacketAcknowledgementResponse) String() string { return proto.CompactTextString(m) } +func (*QueryPacketAcknowledgementResponse) ProtoMessage() {} +func (*QueryPacketAcknowledgementResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_a328cba4986edcab, []int{7} +} +func (m *QueryPacketAcknowledgementResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPacketAcknowledgementResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPacketAcknowledgementResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPacketAcknowledgementResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPacketAcknowledgementResponse.Merge(m, src) +} +func (m *QueryPacketAcknowledgementResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryPacketAcknowledgementResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPacketAcknowledgementResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPacketAcknowledgementResponse proto.InternalMessageInfo + +func (m *QueryPacketAcknowledgementResponse) GetAcknowledgement() []byte { + if m != nil { + return m.Acknowledgement + } + return nil +} + +func (m *QueryPacketAcknowledgementResponse) GetProof() []byte { + if m != nil { + return m.Proof + } + return nil +} + +func (m *QueryPacketAcknowledgementResponse) GetProofHeight() types.Height { + if m != nil { + return m.ProofHeight + } + return types.Height{} +} + +// QueryPacketAcknowledgementsRequest is the request type for the +// Query/QueryPacketCommitments RPC method +type QueryPacketAcknowledgementsRequest struct { + // client unique identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // pagination request + Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` + // list of packet sequences + PacketCommitmentSequences []uint64 `protobuf:"varint,3,rep,packed,name=packet_commitment_sequences,json=packetCommitmentSequences,proto3" json:"packet_commitment_sequences,omitempty"` +} + +func (m *QueryPacketAcknowledgementsRequest) Reset() { *m = QueryPacketAcknowledgementsRequest{} } +func (m *QueryPacketAcknowledgementsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryPacketAcknowledgementsRequest) ProtoMessage() {} +func (*QueryPacketAcknowledgementsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_a328cba4986edcab, []int{8} +} +func (m *QueryPacketAcknowledgementsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPacketAcknowledgementsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPacketAcknowledgementsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPacketAcknowledgementsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPacketAcknowledgementsRequest.Merge(m, src) +} +func (m *QueryPacketAcknowledgementsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryPacketAcknowledgementsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPacketAcknowledgementsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPacketAcknowledgementsRequest proto.InternalMessageInfo + +func (m *QueryPacketAcknowledgementsRequest) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +func (m *QueryPacketAcknowledgementsRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +func (m *QueryPacketAcknowledgementsRequest) GetPacketCommitmentSequences() []uint64 { + if m != nil { + return m.PacketCommitmentSequences + } + return nil +} + +// QueryPacketAcknowledgemetsResponse is the request type for the +// Query/QueryPacketAcknowledgements RPC method +type QueryPacketAcknowledgementsResponse struct { + Acknowledgements []*PacketState `protobuf:"bytes,1,rep,name=acknowledgements,proto3" json:"acknowledgements,omitempty"` + // pagination response + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` + // query block height + Height types.Height `protobuf:"bytes,3,opt,name=height,proto3" json:"height"` +} + +func (m *QueryPacketAcknowledgementsResponse) Reset() { *m = QueryPacketAcknowledgementsResponse{} } +func (m *QueryPacketAcknowledgementsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryPacketAcknowledgementsResponse) ProtoMessage() {} +func (*QueryPacketAcknowledgementsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_a328cba4986edcab, []int{9} +} +func (m *QueryPacketAcknowledgementsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPacketAcknowledgementsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPacketAcknowledgementsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPacketAcknowledgementsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPacketAcknowledgementsResponse.Merge(m, src) +} +func (m *QueryPacketAcknowledgementsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryPacketAcknowledgementsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPacketAcknowledgementsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPacketAcknowledgementsResponse proto.InternalMessageInfo + +func (m *QueryPacketAcknowledgementsResponse) GetAcknowledgements() []*PacketState { + if m != nil { + return m.Acknowledgements + } + return nil +} + +func (m *QueryPacketAcknowledgementsResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + +func (m *QueryPacketAcknowledgementsResponse) GetHeight() types.Height { + if m != nil { + return m.Height + } + return types.Height{} +} + +// QueryPacketReceiptRequest is the request type for the Query/PacketReceipt RPC method. +type QueryPacketReceiptRequest struct { + // client unique identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // packet sequence + Sequence uint64 `protobuf:"varint,2,opt,name=sequence,proto3" json:"sequence,omitempty"` +} + +func (m *QueryPacketReceiptRequest) Reset() { *m = QueryPacketReceiptRequest{} } +func (m *QueryPacketReceiptRequest) String() string { return proto.CompactTextString(m) } +func (*QueryPacketReceiptRequest) ProtoMessage() {} +func (*QueryPacketReceiptRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_a328cba4986edcab, []int{10} +} +func (m *QueryPacketReceiptRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPacketReceiptRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPacketReceiptRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPacketReceiptRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPacketReceiptRequest.Merge(m, src) +} +func (m *QueryPacketReceiptRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryPacketReceiptRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPacketReceiptRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPacketReceiptRequest proto.InternalMessageInfo + +func (m *QueryPacketReceiptRequest) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +func (m *QueryPacketReceiptRequest) GetSequence() uint64 { + if m != nil { + return m.Sequence + } + return 0 +} + +// QueryPacketReceiptResponse is the response type for the Query/PacketReceipt RPC method. +type QueryPacketReceiptResponse struct { + // success flag for if receipt exists + Received bool `protobuf:"varint,2,opt,name=received,proto3" json:"received,omitempty"` + // merkle proof of existence or absence + Proof []byte `protobuf:"bytes,3,opt,name=proof,proto3" json:"proof,omitempty"` + // height at which the proof was retrieved + ProofHeight types.Height `protobuf:"bytes,4,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` +} + +func (m *QueryPacketReceiptResponse) Reset() { *m = QueryPacketReceiptResponse{} } +func (m *QueryPacketReceiptResponse) String() string { return proto.CompactTextString(m) } +func (*QueryPacketReceiptResponse) ProtoMessage() {} +func (*QueryPacketReceiptResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_a328cba4986edcab, []int{11} +} +func (m *QueryPacketReceiptResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPacketReceiptResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPacketReceiptResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPacketReceiptResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPacketReceiptResponse.Merge(m, src) +} +func (m *QueryPacketReceiptResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryPacketReceiptResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPacketReceiptResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPacketReceiptResponse proto.InternalMessageInfo + +func (m *QueryPacketReceiptResponse) GetReceived() bool { + if m != nil { + return m.Received + } + return false +} + +func (m *QueryPacketReceiptResponse) GetProof() []byte { + if m != nil { + return m.Proof + } + return nil +} + +func (m *QueryPacketReceiptResponse) GetProofHeight() types.Height { + if m != nil { + return m.ProofHeight + } + return types.Height{} +} + +// QueryUnreceivedPacketsRequest is the request type for the Query/UnreceivedPackets RPC method +type QueryUnreceivedPacketsRequest struct { + // client unique identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // list of packet sequences + Sequences []uint64 `protobuf:"varint,2,rep,packed,name=sequences,proto3" json:"sequences,omitempty"` +} + +func (m *QueryUnreceivedPacketsRequest) Reset() { *m = QueryUnreceivedPacketsRequest{} } +func (m *QueryUnreceivedPacketsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryUnreceivedPacketsRequest) ProtoMessage() {} +func (*QueryUnreceivedPacketsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_a328cba4986edcab, []int{12} +} +func (m *QueryUnreceivedPacketsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryUnreceivedPacketsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryUnreceivedPacketsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryUnreceivedPacketsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryUnreceivedPacketsRequest.Merge(m, src) +} +func (m *QueryUnreceivedPacketsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryUnreceivedPacketsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryUnreceivedPacketsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryUnreceivedPacketsRequest proto.InternalMessageInfo + +func (m *QueryUnreceivedPacketsRequest) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +func (m *QueryUnreceivedPacketsRequest) GetSequences() []uint64 { + if m != nil { + return m.Sequences + } + return nil +} + +// QueryUnreceivedPacketsResponse is the response type for the Query/UnreceivedPacketCommitments RPC method +type QueryUnreceivedPacketsResponse struct { + // list of unreceived packet sequences + Sequences []uint64 `protobuf:"varint,1,rep,packed,name=sequences,proto3" json:"sequences,omitempty"` + // query block height + Height types.Height `protobuf:"bytes,2,opt,name=height,proto3" json:"height"` +} + +func (m *QueryUnreceivedPacketsResponse) Reset() { *m = QueryUnreceivedPacketsResponse{} } +func (m *QueryUnreceivedPacketsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryUnreceivedPacketsResponse) ProtoMessage() {} +func (*QueryUnreceivedPacketsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_a328cba4986edcab, []int{13} +} +func (m *QueryUnreceivedPacketsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryUnreceivedPacketsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryUnreceivedPacketsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryUnreceivedPacketsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryUnreceivedPacketsResponse.Merge(m, src) +} +func (m *QueryUnreceivedPacketsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryUnreceivedPacketsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryUnreceivedPacketsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryUnreceivedPacketsResponse proto.InternalMessageInfo + +func (m *QueryUnreceivedPacketsResponse) GetSequences() []uint64 { + if m != nil { + return m.Sequences + } + return nil +} + +func (m *QueryUnreceivedPacketsResponse) GetHeight() types.Height { + if m != nil { + return m.Height + } + return types.Height{} +} + +// QueryUnreceivedAcks is the request type for the +// Query/UnreceivedAcks RPC method +type QueryUnreceivedAcksRequest struct { + // client unique identifier + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // list of acknowledgement sequences + PacketAckSequences []uint64 `protobuf:"varint,2,rep,packed,name=packet_ack_sequences,json=packetAckSequences,proto3" json:"packet_ack_sequences,omitempty"` +} + +func (m *QueryUnreceivedAcksRequest) Reset() { *m = QueryUnreceivedAcksRequest{} } +func (m *QueryUnreceivedAcksRequest) String() string { return proto.CompactTextString(m) } +func (*QueryUnreceivedAcksRequest) ProtoMessage() {} +func (*QueryUnreceivedAcksRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_a328cba4986edcab, []int{14} +} +func (m *QueryUnreceivedAcksRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryUnreceivedAcksRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryUnreceivedAcksRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryUnreceivedAcksRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryUnreceivedAcksRequest.Merge(m, src) +} +func (m *QueryUnreceivedAcksRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryUnreceivedAcksRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryUnreceivedAcksRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryUnreceivedAcksRequest proto.InternalMessageInfo + +func (m *QueryUnreceivedAcksRequest) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +func (m *QueryUnreceivedAcksRequest) GetPacketAckSequences() []uint64 { + if m != nil { + return m.PacketAckSequences + } + return nil +} + +// QueryUnreceivedAcksResponse is the response type for the +// Query/UnreceivedAcks RPC method +type QueryUnreceivedAcksResponse struct { + // list of unreceived acknowledgement sequences + Sequences []uint64 `protobuf:"varint,1,rep,packed,name=sequences,proto3" json:"sequences,omitempty"` + // query block height + Height types.Height `protobuf:"bytes,2,opt,name=height,proto3" json:"height"` +} + +func (m *QueryUnreceivedAcksResponse) Reset() { *m = QueryUnreceivedAcksResponse{} } +func (m *QueryUnreceivedAcksResponse) String() string { return proto.CompactTextString(m) } +func (*QueryUnreceivedAcksResponse) ProtoMessage() {} +func (*QueryUnreceivedAcksResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_a328cba4986edcab, []int{15} +} +func (m *QueryUnreceivedAcksResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryUnreceivedAcksResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryUnreceivedAcksResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryUnreceivedAcksResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryUnreceivedAcksResponse.Merge(m, src) +} +func (m *QueryUnreceivedAcksResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryUnreceivedAcksResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryUnreceivedAcksResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryUnreceivedAcksResponse proto.InternalMessageInfo + +func (m *QueryUnreceivedAcksResponse) GetSequences() []uint64 { + if m != nil { + return m.Sequences + } + return nil +} + +func (m *QueryUnreceivedAcksResponse) GetHeight() types.Height { + if m != nil { + return m.Height + } + return types.Height{} +} + +func init() { + proto.RegisterType((*QueryNextSequenceSendRequest)(nil), "ibc.core.channel.v2.QueryNextSequenceSendRequest") + proto.RegisterType((*QueryNextSequenceSendResponse)(nil), "ibc.core.channel.v2.QueryNextSequenceSendResponse") + proto.RegisterType((*QueryPacketCommitmentRequest)(nil), "ibc.core.channel.v2.QueryPacketCommitmentRequest") + proto.RegisterType((*QueryPacketCommitmentResponse)(nil), "ibc.core.channel.v2.QueryPacketCommitmentResponse") + proto.RegisterType((*QueryPacketCommitmentsRequest)(nil), "ibc.core.channel.v2.QueryPacketCommitmentsRequest") + proto.RegisterType((*QueryPacketCommitmentsResponse)(nil), "ibc.core.channel.v2.QueryPacketCommitmentsResponse") + proto.RegisterType((*QueryPacketAcknowledgementRequest)(nil), "ibc.core.channel.v2.QueryPacketAcknowledgementRequest") + proto.RegisterType((*QueryPacketAcknowledgementResponse)(nil), "ibc.core.channel.v2.QueryPacketAcknowledgementResponse") + proto.RegisterType((*QueryPacketAcknowledgementsRequest)(nil), "ibc.core.channel.v2.QueryPacketAcknowledgementsRequest") + proto.RegisterType((*QueryPacketAcknowledgementsResponse)(nil), "ibc.core.channel.v2.QueryPacketAcknowledgementsResponse") + proto.RegisterType((*QueryPacketReceiptRequest)(nil), "ibc.core.channel.v2.QueryPacketReceiptRequest") + proto.RegisterType((*QueryPacketReceiptResponse)(nil), "ibc.core.channel.v2.QueryPacketReceiptResponse") + proto.RegisterType((*QueryUnreceivedPacketsRequest)(nil), "ibc.core.channel.v2.QueryUnreceivedPacketsRequest") + proto.RegisterType((*QueryUnreceivedPacketsResponse)(nil), "ibc.core.channel.v2.QueryUnreceivedPacketsResponse") + proto.RegisterType((*QueryUnreceivedAcksRequest)(nil), "ibc.core.channel.v2.QueryUnreceivedAcksRequest") + proto.RegisterType((*QueryUnreceivedAcksResponse)(nil), "ibc.core.channel.v2.QueryUnreceivedAcksResponse") +} + +func init() { proto.RegisterFile("ibc/core/channel/v2/query.proto", fileDescriptor_a328cba4986edcab) } + +var fileDescriptor_a328cba4986edcab = []byte{ + // 1033 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0x4f, 0x6f, 0x1b, 0xc5, + 0x1b, 0xce, 0xc4, 0x6e, 0x95, 0xbc, 0x4e, 0x7f, 0xbf, 0x74, 0x08, 0xc8, 0xdd, 0x04, 0xd7, 0x5d, + 0x24, 0xb0, 0x10, 0xdd, 0x89, 0x5d, 0x04, 0x95, 0xaa, 0x02, 0x49, 0x44, 0x1b, 0x04, 0xaa, 0xca, + 0x06, 0x54, 0x29, 0xaa, 0x64, 0xad, 0xd7, 0xc3, 0x66, 0xb1, 0xbd, 0xb3, 0xf5, 0xac, 0x4d, 0xaa, + 0x2a, 0x17, 0xc4, 0x85, 0x1b, 0x52, 0x6f, 0x7c, 0x02, 0xf8, 0x10, 0x20, 0x71, 0x6b, 0x6f, 0x45, + 0x08, 0x89, 0x13, 0x54, 0x09, 0x12, 0xdf, 0x80, 0x33, 0xf2, 0xcc, 0xd8, 0xde, 0xb5, 0xd7, 0x9b, + 0xdd, 0xb4, 0x41, 0xdc, 0x66, 0xc7, 0xef, 0x9f, 0xe7, 0x79, 0xe7, 0x79, 0xe7, 0x9d, 0x04, 0x2e, + 0xba, 0x0d, 0x9b, 0xd8, 0xac, 0x4b, 0x89, 0xbd, 0x67, 0x79, 0x1e, 0x6d, 0x93, 0x7e, 0x8d, 0xdc, + 0xeb, 0xd1, 0xee, 0x7d, 0xc3, 0xef, 0xb2, 0x80, 0xe1, 0x17, 0xdc, 0x86, 0x6d, 0x0c, 0x0c, 0x0c, + 0x65, 0x60, 0xf4, 0x6b, 0xda, 0xeb, 0x36, 0xe3, 0x1d, 0xc6, 0x49, 0xc3, 0xe2, 0x54, 0x5a, 0x93, + 0x7e, 0xb5, 0x41, 0x03, 0xab, 0x4a, 0x7c, 0xcb, 0x71, 0x3d, 0x2b, 0x70, 0x99, 0x27, 0x03, 0x68, + 0x97, 0xe2, 0x32, 0x38, 0xd4, 0xa3, 0xdc, 0xe5, 0xca, 0x24, 0x04, 0xa2, 0xed, 0x52, 0x2f, 0x20, + 0xfd, 0xaa, 0x5a, 0x29, 0x83, 0x35, 0x87, 0x31, 0xa7, 0x4d, 0x89, 0xe5, 0xbb, 0xc4, 0xf2, 0x3c, + 0x16, 0x88, 0x04, 0x43, 0xf7, 0x15, 0x87, 0x39, 0x4c, 0x2c, 0xc9, 0x60, 0x25, 0x77, 0xf5, 0x6b, + 0xb0, 0xf6, 0xf1, 0x00, 0xd9, 0x2d, 0xba, 0x1f, 0xec, 0xd0, 0x7b, 0x3d, 0xea, 0xd9, 0x74, 0x87, + 0x7a, 0x4d, 0x73, 0xb0, 0xe6, 0x01, 0x5e, 0x85, 0x45, 0x99, 0xa3, 0xee, 0x36, 0x8b, 0xa8, 0x8c, + 0x2a, 0x8b, 0xe6, 0x82, 0xdc, 0xf8, 0xa0, 0xa9, 0x7f, 0x87, 0xe0, 0xe5, 0x19, 0xde, 0xdc, 0x67, + 0x1e, 0xa7, 0xf8, 0x0d, 0xc0, 0x1e, 0xdd, 0x0f, 0xea, 0x5c, 0xfd, 0x58, 0xe7, 0xd4, 0x93, 0x71, + 0xf2, 0xe6, 0xb2, 0x37, 0xe1, 0x85, 0x57, 0xe0, 0x8c, 0xdf, 0x65, 0xec, 0xb3, 0xe2, 0x7c, 0x19, + 0x55, 0x96, 0x4c, 0xf9, 0x81, 0xb7, 0x60, 0x49, 0x2c, 0xea, 0x7b, 0xd4, 0x75, 0xf6, 0x82, 0x62, + 0xae, 0x8c, 0x2a, 0x85, 0x9a, 0x66, 0x8c, 0x4b, 0x2e, 0x8b, 0xd0, 0xaf, 0x1a, 0xdb, 0xc2, 0x62, + 0x33, 0xff, 0xe8, 0xf7, 0x8b, 0x73, 0x66, 0x41, 0x78, 0xc9, 0x2d, 0xfd, 0x8e, 0xe2, 0x79, 0xdb, + 0xb2, 0x5b, 0x34, 0xd8, 0x62, 0x9d, 0x8e, 0x1b, 0x74, 0xa8, 0x17, 0xa4, 0xe1, 0x89, 0x35, 0x58, + 0x18, 0x12, 0x10, 0xd0, 0xf2, 0xe6, 0xe8, 0x5b, 0xff, 0x76, 0x58, 0x83, 0xe9, 0xc8, 0xaa, 0x06, + 0x25, 0x00, 0x7b, 0xb4, 0x2b, 0x62, 0x2f, 0x99, 0xa1, 0x9d, 0xd3, 0x64, 0xfd, 0xd5, 0x2c, 0x70, + 0x3c, 0x15, 0xef, 0x1b, 0x00, 0x63, 0xa1, 0x0a, 0x78, 0x85, 0xda, 0xab, 0x86, 0x54, 0xb5, 0x31, + 0x50, 0xb5, 0x21, 0x7b, 0x40, 0xa9, 0xda, 0xb8, 0x6d, 0x39, 0x54, 0x05, 0x36, 0x43, 0x9e, 0xfa, + 0x5f, 0x08, 0x4a, 0xb3, 0x60, 0xa8, 0x22, 0x6d, 0x42, 0x61, 0x5c, 0x12, 0x5e, 0x44, 0xe5, 0x5c, + 0xa5, 0x50, 0x2b, 0x1b, 0x31, 0x6d, 0x65, 0xc8, 0x20, 0x3b, 0x81, 0x15, 0x50, 0x33, 0xec, 0x84, + 0x6f, 0xc6, 0xc0, 0x7d, 0xed, 0x58, 0xb8, 0x12, 0x40, 0x18, 0x2f, 0xbe, 0x0a, 0x67, 0x33, 0x56, + 0x5d, 0xd9, 0xeb, 0x77, 0xe1, 0x52, 0x88, 0xe8, 0x86, 0xdd, 0xf2, 0xd8, 0x17, 0x6d, 0xda, 0x74, + 0xe8, 0x73, 0xd1, 0xda, 0xf7, 0x08, 0xf4, 0xa4, 0xf0, 0xaa, 0x96, 0x15, 0xf8, 0xbf, 0x15, 0xfd, + 0x49, 0xa9, 0x6e, 0x72, 0xfb, 0x34, 0xa5, 0xf7, 0x38, 0x11, 0xeb, 0xbf, 0xaa, 0x3f, 0xfc, 0x0e, + 0xac, 0xfa, 0x02, 0x45, 0x7d, 0x2c, 0x97, 0xd1, 0x95, 0xc4, 0x8b, 0xb9, 0x72, 0xae, 0x92, 0x37, + 0x2f, 0xf8, 0x13, 0xe2, 0x1c, 0x5e, 0x4d, 0x5c, 0xff, 0x1b, 0xc1, 0x2b, 0x89, 0x5c, 0x54, 0xe1, + 0x3f, 0x82, 0xe5, 0x89, 0x0a, 0xa7, 0x57, 0xf2, 0x94, 0xe7, 0x7f, 0x41, 0xce, 0x9f, 0xc0, 0x85, + 0x10, 0x6f, 0x93, 0xda, 0xd4, 0xf5, 0x9f, 0x5d, 0xc6, 0x0f, 0x11, 0x68, 0x71, 0x61, 0x55, 0x15, + 0x35, 0x58, 0xe8, 0x0e, 0xb6, 0xfa, 0xb4, 0x29, 0x5c, 0x17, 0xcc, 0xd1, 0xf7, 0x58, 0xb0, 0xb9, + 0x24, 0xc1, 0xe6, 0x4f, 0x22, 0xd8, 0x5d, 0x75, 0x55, 0x7e, 0xea, 0x0d, 0xb3, 0x49, 0x78, 0xe9, + 0xa4, 0xba, 0x06, 0x8b, 0x63, 0x41, 0xcd, 0x0b, 0x41, 0x8d, 0x37, 0xf4, 0x7d, 0x75, 0xff, 0xc5, + 0xc4, 0x56, 0xa4, 0x23, 0xfe, 0x68, 0xc2, 0x3f, 0x74, 0x82, 0xf3, 0x19, 0x4f, 0xb0, 0xa5, 0x4a, + 0x3d, 0xce, 0xbc, 0x61, 0xb7, 0xd2, 0x51, 0x5a, 0x87, 0x15, 0xd5, 0x35, 0x96, 0xdd, 0xaa, 0x4f, + 0xb2, 0xc3, 0xfe, 0xb0, 0x17, 0xc6, 0x7d, 0xd2, 0x83, 0xd5, 0xd8, 0x64, 0xa7, 0xcb, 0xb1, 0xf6, + 0xf5, 0x39, 0x38, 0x23, 0xf2, 0xe2, 0x1f, 0x11, 0x2c, 0x4f, 0xbe, 0x45, 0x70, 0x35, 0xb6, 0xf7, + 0x92, 0x5e, 0x3d, 0x5a, 0x2d, 0x8b, 0x8b, 0x64, 0xa7, 0x6f, 0x7d, 0xf9, 0xcb, 0x9f, 0x0f, 0xe7, + 0xaf, 0xe3, 0x6b, 0x24, 0xee, 0x29, 0x27, 0x29, 0x70, 0xf2, 0x60, 0x54, 0xef, 0x03, 0x32, 0xfd, + 0x32, 0xc2, 0x8f, 0x11, 0x2c, 0x4f, 0x0e, 0xc9, 0x24, 0x02, 0x33, 0x9e, 0x33, 0x49, 0x04, 0x66, + 0xbd, 0x53, 0xf4, 0x5b, 0x82, 0xc0, 0x36, 0xbe, 0x91, 0x9a, 0xc0, 0xd4, 0xa5, 0xca, 0xc9, 0x83, + 0x21, 0x9f, 0x03, 0xfc, 0x13, 0x82, 0xf3, 0x53, 0x03, 0x1f, 0x67, 0x40, 0x36, 0x94, 0xa9, 0x76, + 0x25, 0x93, 0xcf, 0x89, 0xcf, 0x63, 0x9a, 0x0e, 0xfe, 0x19, 0xc1, 0x8b, 0xb1, 0x97, 0x3e, 0x7e, + 0xeb, 0x38, 0x4c, 0xf1, 0xc3, 0x5f, 0x7b, 0x3b, 0xb3, 0x9f, 0xe2, 0x73, 0x53, 0xf0, 0xd9, 0xc0, + 0xef, 0x66, 0xe5, 0x63, 0xd9, 0xad, 0xc8, 0xb9, 0xfc, 0x8a, 0xe0, 0xa5, 0xf8, 0x41, 0x86, 0xb3, + 0x82, 0x1b, 0x9d, 0xd0, 0xd5, 0xec, 0x8e, 0x8a, 0xd6, 0xb6, 0xa0, 0xb5, 0x89, 0xdf, 0x3b, 0x01, + 0xad, 0x28, 0xf8, 0x1f, 0x10, 0x9c, 0x8b, 0x4c, 0x14, 0x6c, 0x1c, 0x87, 0x2a, 0x3a, 0xd1, 0x34, + 0x92, 0xda, 0x5e, 0x81, 0xff, 0x50, 0x80, 0x7f, 0x1f, 0x6f, 0x65, 0x05, 0xdf, 0x95, 0x81, 0x22, + 0xe7, 0xf2, 0x14, 0xc1, 0xf9, 0xa9, 0x01, 0x91, 0xd4, 0x2f, 0xb3, 0x26, 0x55, 0x52, 0xbf, 0xcc, + 0x9c, 0x40, 0x7a, 0x43, 0x70, 0xb9, 0x8b, 0x77, 0x9f, 0x4b, 0xfb, 0xf3, 0x03, 0xd2, 0x1b, 0xa5, + 0xaa, 0xfb, 0x8a, 0xcc, 0x1f, 0x08, 0xfe, 0x17, 0x1d, 0x0e, 0x98, 0xa4, 0xc1, 0x1a, 0x9a, 0x59, + 0xda, 0x7a, 0x7a, 0x07, 0xc5, 0xec, 0x73, 0xc1, 0xac, 0x89, 0x1b, 0xcf, 0xc4, 0x2c, 0x6e, 0x16, + 0x46, 0x48, 0x0e, 0xfa, 0x6c, 0xf3, 0xce, 0xa3, 0xc3, 0x12, 0x7a, 0x72, 0x58, 0x42, 0x4f, 0x0f, + 0x4b, 0xe8, 0x9b, 0xa3, 0xd2, 0xdc, 0x93, 0xa3, 0xd2, 0xdc, 0x6f, 0x47, 0xa5, 0xb9, 0xdd, 0xeb, + 0x8e, 0x1b, 0xec, 0xf5, 0x1a, 0x86, 0xcd, 0x3a, 0x44, 0xfd, 0x63, 0xc0, 0x6d, 0xd8, 0x97, 0x1d, + 0x46, 0xfa, 0xd5, 0x75, 0xd2, 0x61, 0xcd, 0x5e, 0x9b, 0x72, 0x89, 0x6e, 0xfd, 0xcd, 0xcb, 0x21, + 0x80, 0xc1, 0x7d, 0x9f, 0xf2, 0xc6, 0x59, 0xf1, 0xf7, 0xfa, 0x95, 0x7f, 0x02, 0x00, 0x00, 0xff, + 0xff, 0x2d, 0xe8, 0xdc, 0xcf, 0x8b, 0x10, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // NextSequenceSend returns the next send sequence for a given channel. + NextSequenceSend(ctx context.Context, in *QueryNextSequenceSendRequest, opts ...grpc.CallOption) (*QueryNextSequenceSendResponse, error) + // PacketCommitment queries a stored packet commitment hash. + PacketCommitment(ctx context.Context, in *QueryPacketCommitmentRequest, opts ...grpc.CallOption) (*QueryPacketCommitmentResponse, error) + // PacketCommitments queries a stored packet commitment hash. + PacketCommitments(ctx context.Context, in *QueryPacketCommitmentsRequest, opts ...grpc.CallOption) (*QueryPacketCommitmentsResponse, error) + // PacketAcknowledgement queries a stored acknowledgement commitment hash. + PacketAcknowledgement(ctx context.Context, in *QueryPacketAcknowledgementRequest, opts ...grpc.CallOption) (*QueryPacketAcknowledgementResponse, error) + // PacketAcknowledgements returns all packet acknowledgements associated with a channel. + PacketAcknowledgements(ctx context.Context, in *QueryPacketAcknowledgementsRequest, opts ...grpc.CallOption) (*QueryPacketAcknowledgementsResponse, error) + // PacketReceipt queries a stored packet receipt. + PacketReceipt(ctx context.Context, in *QueryPacketReceiptRequest, opts ...grpc.CallOption) (*QueryPacketReceiptResponse, error) + // UnreceivedPackets returns all the unreceived IBC packets associated with a channel and sequences. + UnreceivedPackets(ctx context.Context, in *QueryUnreceivedPacketsRequest, opts ...grpc.CallOption) (*QueryUnreceivedPacketsResponse, error) + // UnreceivedAcks returns all the unreceived IBC acknowledgements associated with a channel and sequences. + UnreceivedAcks(ctx context.Context, in *QueryUnreceivedAcksRequest, opts ...grpc.CallOption) (*QueryUnreceivedAcksResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) NextSequenceSend(ctx context.Context, in *QueryNextSequenceSendRequest, opts ...grpc.CallOption) (*QueryNextSequenceSendResponse, error) { + out := new(QueryNextSequenceSendResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v2.Query/NextSequenceSend", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) PacketCommitment(ctx context.Context, in *QueryPacketCommitmentRequest, opts ...grpc.CallOption) (*QueryPacketCommitmentResponse, error) { + out := new(QueryPacketCommitmentResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v2.Query/PacketCommitment", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) PacketCommitments(ctx context.Context, in *QueryPacketCommitmentsRequest, opts ...grpc.CallOption) (*QueryPacketCommitmentsResponse, error) { + out := new(QueryPacketCommitmentsResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v2.Query/PacketCommitments", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) PacketAcknowledgement(ctx context.Context, in *QueryPacketAcknowledgementRequest, opts ...grpc.CallOption) (*QueryPacketAcknowledgementResponse, error) { + out := new(QueryPacketAcknowledgementResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v2.Query/PacketAcknowledgement", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) PacketAcknowledgements(ctx context.Context, in *QueryPacketAcknowledgementsRequest, opts ...grpc.CallOption) (*QueryPacketAcknowledgementsResponse, error) { + out := new(QueryPacketAcknowledgementsResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v2.Query/PacketAcknowledgements", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) PacketReceipt(ctx context.Context, in *QueryPacketReceiptRequest, opts ...grpc.CallOption) (*QueryPacketReceiptResponse, error) { + out := new(QueryPacketReceiptResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v2.Query/PacketReceipt", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) UnreceivedPackets(ctx context.Context, in *QueryUnreceivedPacketsRequest, opts ...grpc.CallOption) (*QueryUnreceivedPacketsResponse, error) { + out := new(QueryUnreceivedPacketsResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v2.Query/UnreceivedPackets", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) UnreceivedAcks(ctx context.Context, in *QueryUnreceivedAcksRequest, opts ...grpc.CallOption) (*QueryUnreceivedAcksResponse, error) { + out := new(QueryUnreceivedAcksResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v2.Query/UnreceivedAcks", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // NextSequenceSend returns the next send sequence for a given channel. + NextSequenceSend(context.Context, *QueryNextSequenceSendRequest) (*QueryNextSequenceSendResponse, error) + // PacketCommitment queries a stored packet commitment hash. + PacketCommitment(context.Context, *QueryPacketCommitmentRequest) (*QueryPacketCommitmentResponse, error) + // PacketCommitments queries a stored packet commitment hash. + PacketCommitments(context.Context, *QueryPacketCommitmentsRequest) (*QueryPacketCommitmentsResponse, error) + // PacketAcknowledgement queries a stored acknowledgement commitment hash. + PacketAcknowledgement(context.Context, *QueryPacketAcknowledgementRequest) (*QueryPacketAcknowledgementResponse, error) + // PacketAcknowledgements returns all packet acknowledgements associated with a channel. + PacketAcknowledgements(context.Context, *QueryPacketAcknowledgementsRequest) (*QueryPacketAcknowledgementsResponse, error) + // PacketReceipt queries a stored packet receipt. + PacketReceipt(context.Context, *QueryPacketReceiptRequest) (*QueryPacketReceiptResponse, error) + // UnreceivedPackets returns all the unreceived IBC packets associated with a channel and sequences. + UnreceivedPackets(context.Context, *QueryUnreceivedPacketsRequest) (*QueryUnreceivedPacketsResponse, error) + // UnreceivedAcks returns all the unreceived IBC acknowledgements associated with a channel and sequences. + UnreceivedAcks(context.Context, *QueryUnreceivedAcksRequest) (*QueryUnreceivedAcksResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) NextSequenceSend(ctx context.Context, req *QueryNextSequenceSendRequest) (*QueryNextSequenceSendResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method NextSequenceSend not implemented") +} +func (*UnimplementedQueryServer) PacketCommitment(ctx context.Context, req *QueryPacketCommitmentRequest) (*QueryPacketCommitmentResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PacketCommitment not implemented") +} +func (*UnimplementedQueryServer) PacketCommitments(ctx context.Context, req *QueryPacketCommitmentsRequest) (*QueryPacketCommitmentsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PacketCommitments not implemented") +} +func (*UnimplementedQueryServer) PacketAcknowledgement(ctx context.Context, req *QueryPacketAcknowledgementRequest) (*QueryPacketAcknowledgementResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PacketAcknowledgement not implemented") +} +func (*UnimplementedQueryServer) PacketAcknowledgements(ctx context.Context, req *QueryPacketAcknowledgementsRequest) (*QueryPacketAcknowledgementsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PacketAcknowledgements not implemented") +} +func (*UnimplementedQueryServer) PacketReceipt(ctx context.Context, req *QueryPacketReceiptRequest) (*QueryPacketReceiptResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PacketReceipt not implemented") +} +func (*UnimplementedQueryServer) UnreceivedPackets(ctx context.Context, req *QueryUnreceivedPacketsRequest) (*QueryUnreceivedPacketsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UnreceivedPackets not implemented") +} +func (*UnimplementedQueryServer) UnreceivedAcks(ctx context.Context, req *QueryUnreceivedAcksRequest) (*QueryUnreceivedAcksResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UnreceivedAcks not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_NextSequenceSend_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryNextSequenceSendRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).NextSequenceSend(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v2.Query/NextSequenceSend", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).NextSequenceSend(ctx, req.(*QueryNextSequenceSendRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_PacketCommitment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryPacketCommitmentRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).PacketCommitment(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v2.Query/PacketCommitment", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).PacketCommitment(ctx, req.(*QueryPacketCommitmentRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_PacketCommitments_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryPacketCommitmentsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).PacketCommitments(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v2.Query/PacketCommitments", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).PacketCommitments(ctx, req.(*QueryPacketCommitmentsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_PacketAcknowledgement_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryPacketAcknowledgementRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).PacketAcknowledgement(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v2.Query/PacketAcknowledgement", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).PacketAcknowledgement(ctx, req.(*QueryPacketAcknowledgementRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_PacketAcknowledgements_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryPacketAcknowledgementsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).PacketAcknowledgements(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v2.Query/PacketAcknowledgements", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).PacketAcknowledgements(ctx, req.(*QueryPacketAcknowledgementsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_PacketReceipt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryPacketReceiptRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).PacketReceipt(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v2.Query/PacketReceipt", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).PacketReceipt(ctx, req.(*QueryPacketReceiptRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_UnreceivedPackets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryUnreceivedPacketsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).UnreceivedPackets(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v2.Query/UnreceivedPackets", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).UnreceivedPackets(ctx, req.(*QueryUnreceivedPacketsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_UnreceivedAcks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryUnreceivedAcksRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).UnreceivedAcks(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v2.Query/UnreceivedAcks", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).UnreceivedAcks(ctx, req.(*QueryUnreceivedAcksRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ibc.core.channel.v2.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "NextSequenceSend", + Handler: _Query_NextSequenceSend_Handler, + }, + { + MethodName: "PacketCommitment", + Handler: _Query_PacketCommitment_Handler, + }, + { + MethodName: "PacketCommitments", + Handler: _Query_PacketCommitments_Handler, + }, + { + MethodName: "PacketAcknowledgement", + Handler: _Query_PacketAcknowledgement_Handler, + }, + { + MethodName: "PacketAcknowledgements", + Handler: _Query_PacketAcknowledgements_Handler, + }, + { + MethodName: "PacketReceipt", + Handler: _Query_PacketReceipt_Handler, + }, + { + MethodName: "UnreceivedPackets", + Handler: _Query_UnreceivedPackets_Handler, + }, + { + MethodName: "UnreceivedAcks", + Handler: _Query_UnreceivedAcks_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "ibc/core/channel/v2/query.proto", +} + +func (m *QueryNextSequenceSendRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryNextSequenceSendRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryNextSequenceSendRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryNextSequenceSendResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryNextSequenceSendResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryNextSequenceSendResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.Proof) > 0 { + i -= len(m.Proof) + copy(dAtA[i:], m.Proof) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Proof))) + i-- + dAtA[i] = 0x12 + } + if m.NextSequenceSend != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.NextSequenceSend)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *QueryPacketCommitmentRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPacketCommitmentRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPacketCommitmentRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Sequence != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x10 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryPacketCommitmentResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPacketCommitmentResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPacketCommitmentResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.Proof) > 0 { + i -= len(m.Proof) + copy(dAtA[i:], m.Proof) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Proof))) + i-- + dAtA[i] = 0x12 + } + if len(m.Commitment) > 0 { + i -= len(m.Commitment) + copy(dAtA[i:], m.Commitment) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Commitment))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryPacketCommitmentsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPacketCommitmentsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPacketCommitmentsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryPacketCommitmentsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPacketCommitmentsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPacketCommitmentsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Height.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Commitments) > 0 { + for iNdEx := len(m.Commitments) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Commitments[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *QueryPacketAcknowledgementRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPacketAcknowledgementRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPacketAcknowledgementRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Sequence != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x10 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryPacketAcknowledgementResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPacketAcknowledgementResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPacketAcknowledgementResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.Proof) > 0 { + i -= len(m.Proof) + copy(dAtA[i:], m.Proof) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Proof))) + i-- + dAtA[i] = 0x12 + } + if len(m.Acknowledgement) > 0 { + i -= len(m.Acknowledgement) + copy(dAtA[i:], m.Acknowledgement) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Acknowledgement))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryPacketAcknowledgementsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPacketAcknowledgementsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPacketAcknowledgementsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.PacketCommitmentSequences) > 0 { + dAtA8 := make([]byte, len(m.PacketCommitmentSequences)*10) + var j7 int + for _, num := range m.PacketCommitmentSequences { + for num >= 1<<7 { + dAtA8[j7] = uint8(uint64(num)&0x7f | 0x80) + num >>= 7 + j7++ + } + dAtA8[j7] = uint8(num) + j7++ + } + i -= j7 + copy(dAtA[i:], dAtA8[:j7]) + i = encodeVarintQuery(dAtA, i, uint64(j7)) + i-- + dAtA[i] = 0x1a + } + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryPacketAcknowledgementsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPacketAcknowledgementsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPacketAcknowledgementsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Height.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Acknowledgements) > 0 { + for iNdEx := len(m.Acknowledgements) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Acknowledgements[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *QueryPacketReceiptRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPacketReceiptRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPacketReceiptRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Sequence != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x10 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryPacketReceiptResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPacketReceiptResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPacketReceiptResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + if len(m.Proof) > 0 { + i -= len(m.Proof) + copy(dAtA[i:], m.Proof) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Proof))) + i-- + dAtA[i] = 0x1a + } + if m.Received { + i-- + if m.Received { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x10 + } + return len(dAtA) - i, nil +} + +func (m *QueryUnreceivedPacketsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryUnreceivedPacketsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryUnreceivedPacketsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Sequences) > 0 { + dAtA14 := make([]byte, len(m.Sequences)*10) + var j13 int + for _, num := range m.Sequences { + for num >= 1<<7 { + dAtA14[j13] = uint8(uint64(num)&0x7f | 0x80) + num >>= 7 + j13++ + } + dAtA14[j13] = uint8(num) + j13++ + } + i -= j13 + copy(dAtA[i:], dAtA14[:j13]) + i = encodeVarintQuery(dAtA, i, uint64(j13)) + i-- + dAtA[i] = 0x12 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryUnreceivedPacketsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryUnreceivedPacketsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryUnreceivedPacketsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Height.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Sequences) > 0 { + dAtA17 := make([]byte, len(m.Sequences)*10) + var j16 int + for _, num := range m.Sequences { + for num >= 1<<7 { + dAtA17[j16] = uint8(uint64(num)&0x7f | 0x80) + num >>= 7 + j16++ + } + dAtA17[j16] = uint8(num) + j16++ + } + i -= j16 + copy(dAtA[i:], dAtA17[:j16]) + i = encodeVarintQuery(dAtA, i, uint64(j16)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryUnreceivedAcksRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryUnreceivedAcksRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryUnreceivedAcksRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.PacketAckSequences) > 0 { + dAtA19 := make([]byte, len(m.PacketAckSequences)*10) + var j18 int + for _, num := range m.PacketAckSequences { + for num >= 1<<7 { + dAtA19[j18] = uint8(uint64(num)&0x7f | 0x80) + num >>= 7 + j18++ + } + dAtA19[j18] = uint8(num) + j18++ + } + i -= j18 + copy(dAtA[i:], dAtA19[:j18]) + i = encodeVarintQuery(dAtA, i, uint64(j18)) + i-- + dAtA[i] = 0x12 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryUnreceivedAcksResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryUnreceivedAcksResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryUnreceivedAcksResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Height.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Sequences) > 0 { + dAtA22 := make([]byte, len(m.Sequences)*10) + var j21 int + for _, num := range m.Sequences { + for num >= 1<<7 { + dAtA22[j21] = uint8(uint64(num)&0x7f | 0x80) + num >>= 7 + j21++ + } + dAtA22[j21] = uint8(num) + j21++ + } + i -= j21 + copy(dAtA[i:], dAtA22[:j21]) + i = encodeVarintQuery(dAtA, i, uint64(j21)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryNextSequenceSendRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryNextSequenceSendResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.NextSequenceSend != 0 { + n += 1 + sovQuery(uint64(m.NextSequenceSend)) + } + l = len(m.Proof) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryPacketCommitmentRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.Sequence != 0 { + n += 1 + sovQuery(uint64(m.Sequence)) + } + return n +} + +func (m *QueryPacketCommitmentResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Commitment) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.Proof) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryPacketCommitmentsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryPacketCommitmentsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Commitments) > 0 { + for _, e := range m.Commitments { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + l = m.Height.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryPacketAcknowledgementRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.Sequence != 0 { + n += 1 + sovQuery(uint64(m.Sequence)) + } + return n +} + +func (m *QueryPacketAcknowledgementResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Acknowledgement) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.Proof) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryPacketAcknowledgementsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + if len(m.PacketCommitmentSequences) > 0 { + l = 0 + for _, e := range m.PacketCommitmentSequences { + l += sovQuery(uint64(e)) + } + n += 1 + sovQuery(uint64(l)) + l + } + return n +} + +func (m *QueryPacketAcknowledgementsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Acknowledgements) > 0 { + for _, e := range m.Acknowledgements { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + l = m.Height.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryPacketReceiptRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.Sequence != 0 { + n += 1 + sovQuery(uint64(m.Sequence)) + } + return n +} + +func (m *QueryPacketReceiptResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Received { + n += 2 + } + l = len(m.Proof) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryUnreceivedPacketsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if len(m.Sequences) > 0 { + l = 0 + for _, e := range m.Sequences { + l += sovQuery(uint64(e)) + } + n += 1 + sovQuery(uint64(l)) + l + } + return n +} + +func (m *QueryUnreceivedPacketsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Sequences) > 0 { + l = 0 + for _, e := range m.Sequences { + l += sovQuery(uint64(e)) + } + n += 1 + sovQuery(uint64(l)) + l + } + l = m.Height.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryUnreceivedAcksRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if len(m.PacketAckSequences) > 0 { + l = 0 + for _, e := range m.PacketAckSequences { + l += sovQuery(uint64(e)) + } + n += 1 + sovQuery(uint64(l)) + l + } + return n +} + +func (m *QueryUnreceivedAcksResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Sequences) > 0 { + l = 0 + for _, e := range m.Sequences { + l += sovQuery(uint64(e)) + } + n += 1 + sovQuery(uint64(l)) + l + } + l = m.Height.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryNextSequenceSendRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryNextSequenceSendRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryNextSequenceSendRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryNextSequenceSendResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryNextSequenceSendResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryNextSequenceSendResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NextSequenceSend", wireType) + } + m.NextSequenceSend = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.NextSequenceSend |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proof", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Proof = append(m.Proof[:0], dAtA[iNdEx:postIndex]...) + if m.Proof == nil { + m.Proof = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPacketCommitmentRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPacketCommitmentRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPacketCommitmentRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPacketCommitmentResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPacketCommitmentResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPacketCommitmentResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Commitment", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Commitment = append(m.Commitment[:0], dAtA[iNdEx:postIndex]...) + if m.Commitment == nil { + m.Commitment = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proof", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Proof = append(m.Proof[:0], dAtA[iNdEx:postIndex]...) + if m.Proof == nil { + m.Proof = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPacketCommitmentsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPacketCommitmentsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPacketCommitmentsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPacketCommitmentsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPacketCommitmentsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPacketCommitmentsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Commitments", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Commitments = append(m.Commitments, &PacketState{}) + if err := m.Commitments[len(m.Commitments)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Height.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPacketAcknowledgementRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPacketAcknowledgementRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPacketAcknowledgementRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPacketAcknowledgementResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPacketAcknowledgementResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPacketAcknowledgementResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Acknowledgement", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Acknowledgement = append(m.Acknowledgement[:0], dAtA[iNdEx:postIndex]...) + if m.Acknowledgement == nil { + m.Acknowledgement = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proof", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Proof = append(m.Proof[:0], dAtA[iNdEx:postIndex]...) + if m.Proof == nil { + m.Proof = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPacketAcknowledgementsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPacketAcknowledgementsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPacketAcknowledgementsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType == 0 { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.PacketCommitmentSequences = append(m.PacketCommitmentSequences, v) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + var count int + for _, integer := range dAtA[iNdEx:postIndex] { + if integer < 128 { + count++ + } + } + elementCount = count + if elementCount != 0 && len(m.PacketCommitmentSequences) == 0 { + m.PacketCommitmentSequences = make([]uint64, 0, elementCount) + } + for iNdEx < postIndex { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.PacketCommitmentSequences = append(m.PacketCommitmentSequences, v) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field PacketCommitmentSequences", wireType) + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPacketAcknowledgementsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPacketAcknowledgementsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPacketAcknowledgementsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Acknowledgements", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Acknowledgements = append(m.Acknowledgements, &PacketState{}) + if err := m.Acknowledgements[len(m.Acknowledgements)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Height.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPacketReceiptRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPacketReceiptRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPacketReceiptRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPacketReceiptResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPacketReceiptResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPacketReceiptResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Received", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Received = bool(v != 0) + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proof", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Proof = append(m.Proof[:0], dAtA[iNdEx:postIndex]...) + if m.Proof == nil { + m.Proof = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryUnreceivedPacketsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryUnreceivedPacketsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryUnreceivedPacketsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType == 0 { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Sequences = append(m.Sequences, v) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + var count int + for _, integer := range dAtA[iNdEx:postIndex] { + if integer < 128 { + count++ + } + } + elementCount = count + if elementCount != 0 && len(m.Sequences) == 0 { + m.Sequences = make([]uint64, 0, elementCount) + } + for iNdEx < postIndex { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Sequences = append(m.Sequences, v) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field Sequences", wireType) + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryUnreceivedPacketsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryUnreceivedPacketsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryUnreceivedPacketsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType == 0 { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Sequences = append(m.Sequences, v) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + var count int + for _, integer := range dAtA[iNdEx:postIndex] { + if integer < 128 { + count++ + } + } + elementCount = count + if elementCount != 0 && len(m.Sequences) == 0 { + m.Sequences = make([]uint64, 0, elementCount) + } + for iNdEx < postIndex { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Sequences = append(m.Sequences, v) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field Sequences", wireType) + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Height.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryUnreceivedAcksRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryUnreceivedAcksRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryUnreceivedAcksRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType == 0 { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.PacketAckSequences = append(m.PacketAckSequences, v) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + var count int + for _, integer := range dAtA[iNdEx:postIndex] { + if integer < 128 { + count++ + } + } + elementCount = count + if elementCount != 0 && len(m.PacketAckSequences) == 0 { + m.PacketAckSequences = make([]uint64, 0, elementCount) + } + for iNdEx < postIndex { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.PacketAckSequences = append(m.PacketAckSequences, v) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field PacketAckSequences", wireType) + } + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryUnreceivedAcksResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryUnreceivedAcksResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryUnreceivedAcksResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType == 0 { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Sequences = append(m.Sequences, v) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + var count int + for _, integer := range dAtA[iNdEx:postIndex] { + if integer < 128 { + count++ + } + } + elementCount = count + if elementCount != 0 && len(m.Sequences) == 0 { + m.Sequences = make([]uint64, 0, elementCount) + } + for iNdEx < postIndex { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Sequences = append(m.Sequences, v) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field Sequences", wireType) + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Height.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/04-channel/v2/types/query.pb.gw.go b/modules/core/04-channel/v2/types/query.pb.gw.go new file mode 100644 index 0000000..2ff5a5e --- /dev/null +++ b/modules/core/04-channel/v2/types/query.pb.gw.go @@ -0,0 +1,1042 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: ibc/core/channel/v2/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +func request_Query_NextSequenceSend_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryNextSequenceSendRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + msg, err := client.NextSequenceSend(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_NextSequenceSend_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryNextSequenceSendRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + msg, err := server.NextSequenceSend(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_PacketCommitment_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPacketCommitmentRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + val, ok = pathParams["sequence"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "sequence") + } + + protoReq.Sequence, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "sequence", err) + } + + msg, err := client.PacketCommitment(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_PacketCommitment_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPacketCommitmentRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + val, ok = pathParams["sequence"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "sequence") + } + + protoReq.Sequence, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "sequence", err) + } + + msg, err := server.PacketCommitment(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Query_PacketCommitments_0 = &utilities.DoubleArray{Encoding: map[string]int{"client_id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} +) + +func request_Query_PacketCommitments_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPacketCommitmentsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_PacketCommitments_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.PacketCommitments(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_PacketCommitments_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPacketCommitmentsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_PacketCommitments_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.PacketCommitments(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_PacketAcknowledgement_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPacketAcknowledgementRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + val, ok = pathParams["sequence"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "sequence") + } + + protoReq.Sequence, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "sequence", err) + } + + msg, err := client.PacketAcknowledgement(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_PacketAcknowledgement_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPacketAcknowledgementRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + val, ok = pathParams["sequence"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "sequence") + } + + protoReq.Sequence, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "sequence", err) + } + + msg, err := server.PacketAcknowledgement(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Query_PacketAcknowledgements_0 = &utilities.DoubleArray{Encoding: map[string]int{"client_id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} +) + +func request_Query_PacketAcknowledgements_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPacketAcknowledgementsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_PacketAcknowledgements_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.PacketAcknowledgements(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_PacketAcknowledgements_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPacketAcknowledgementsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_PacketAcknowledgements_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.PacketAcknowledgements(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_PacketReceipt_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPacketReceiptRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + val, ok = pathParams["sequence"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "sequence") + } + + protoReq.Sequence, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "sequence", err) + } + + msg, err := client.PacketReceipt(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_PacketReceipt_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPacketReceiptRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + val, ok = pathParams["sequence"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "sequence") + } + + protoReq.Sequence, err = runtime.Uint64(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "sequence", err) + } + + msg, err := server.PacketReceipt(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_UnreceivedPackets_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryUnreceivedPacketsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + val, ok = pathParams["sequences"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "sequences") + } + + protoReq.Sequences, err = runtime.Uint64Slice(val, ",") + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "sequences", err) + } + + msg, err := client.UnreceivedPackets(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_UnreceivedPackets_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryUnreceivedPacketsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + val, ok = pathParams["sequences"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "sequences") + } + + protoReq.Sequences, err = runtime.Uint64Slice(val, ",") + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "sequences", err) + } + + msg, err := server.UnreceivedPackets(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_UnreceivedAcks_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryUnreceivedAcksRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + val, ok = pathParams["packet_ack_sequences"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "packet_ack_sequences") + } + + protoReq.PacketAckSequences, err = runtime.Uint64Slice(val, ",") + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "packet_ack_sequences", err) + } + + msg, err := client.UnreceivedAcks(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_UnreceivedAcks_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryUnreceivedAcksRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["client_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id") + } + + protoReq.ClientId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err) + } + + val, ok = pathParams["packet_ack_sequences"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "packet_ack_sequences") + } + + protoReq.PacketAckSequences, err = runtime.Uint64Slice(val, ",") + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "packet_ack_sequences", err) + } + + msg, err := server.UnreceivedAcks(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_NextSequenceSend_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_NextSequenceSend_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_NextSequenceSend_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PacketCommitment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_PacketCommitment_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PacketCommitment_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PacketCommitments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_PacketCommitments_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PacketCommitments_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PacketAcknowledgement_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_PacketAcknowledgement_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PacketAcknowledgement_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PacketAcknowledgements_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_PacketAcknowledgements_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PacketAcknowledgements_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PacketReceipt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_PacketReceipt_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PacketReceipt_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_UnreceivedPackets_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_UnreceivedPackets_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_UnreceivedPackets_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_UnreceivedAcks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_UnreceivedAcks_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_UnreceivedAcks_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_NextSequenceSend_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_NextSequenceSend_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_NextSequenceSend_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PacketCommitment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_PacketCommitment_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PacketCommitment_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PacketCommitments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_PacketCommitments_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PacketCommitments_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PacketAcknowledgement_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_PacketAcknowledgement_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PacketAcknowledgement_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PacketAcknowledgements_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_PacketAcknowledgements_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PacketAcknowledgements_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PacketReceipt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_PacketReceipt_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PacketReceipt_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_UnreceivedPackets_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_UnreceivedPackets_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_UnreceivedPackets_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_UnreceivedAcks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_UnreceivedAcks_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_UnreceivedAcks_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_NextSequenceSend_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6}, []string{"ibc", "core", "channel", "v2", "clients", "client_id", "next_sequence_send"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_PacketCommitment_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 1, 0, 4, 1, 5, 7}, []string{"ibc", "core", "channel", "v2", "clients", "client_id", "packet_commitments", "sequence"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_PacketCommitments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6}, []string{"ibc", "core", "channel", "v2", "clients", "client_id", "packet_commitments"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_PacketAcknowledgement_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 1, 0, 4, 1, 5, 7}, []string{"ibc", "core", "channel", "v2", "clients", "client_id", "packet_acks", "sequence"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_PacketAcknowledgements_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6}, []string{"ibc", "core", "channel", "v2", "clients", "client_id", "packet_acknowledgements"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_PacketReceipt_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 1, 0, 4, 1, 5, 7}, []string{"ibc", "core", "channel", "v2", "clients", "client_id", "packet_receipts", "sequence"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_UnreceivedPackets_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 1, 0, 4, 1, 5, 7, 2, 8}, []string{"ibc", "core", "channel", "v2", "clients", "client_id", "packet_commitments", "sequences", "unreceived_packets"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_UnreceivedAcks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6, 1, 0, 4, 1, 5, 7, 2, 8}, []string{"ibc", "core", "channel", "v2", "clients", "client_id", "packet_commitments", "packet_ack_sequences", "unreceived_acks"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Query_NextSequenceSend_0 = runtime.ForwardResponseMessage + + forward_Query_PacketCommitment_0 = runtime.ForwardResponseMessage + + forward_Query_PacketCommitments_0 = runtime.ForwardResponseMessage + + forward_Query_PacketAcknowledgement_0 = runtime.ForwardResponseMessage + + forward_Query_PacketAcknowledgements_0 = runtime.ForwardResponseMessage + + forward_Query_PacketReceipt_0 = runtime.ForwardResponseMessage + + forward_Query_UnreceivedPackets_0 = runtime.ForwardResponseMessage + + forward_Query_UnreceivedAcks_0 = runtime.ForwardResponseMessage +) diff --git a/modules/core/04-channel/v2/types/tx.pb.go b/modules/core/04-channel/v2/types/tx.pb.go new file mode 100644 index 0000000..8344175 --- /dev/null +++ b/modules/core/04-channel/v2/types/tx.pb.go @@ -0,0 +1,2261 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/channel/v2/tx.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/cosmos/cosmos-sdk/types/msgservice" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + types "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// ResponseResultType defines the possible outcomes of the execution of a message +type ResponseResultType int32 + +const ( + // Default zero value enumeration + UNSPECIFIED ResponseResultType = 0 + // The message did not call the IBC application callbacks (because, for example, the packet had already been relayed) + NOOP ResponseResultType = 1 + // The message was executed successfully + SUCCESS ResponseResultType = 2 + // The message was executed unsuccessfully + FAILURE ResponseResultType = 3 +) + +var ResponseResultType_name = map[int32]string{ + 0: "RESPONSE_RESULT_TYPE_UNSPECIFIED", + 1: "RESPONSE_RESULT_TYPE_NOOP", + 2: "RESPONSE_RESULT_TYPE_SUCCESS", + 3: "RESPONSE_RESULT_TYPE_FAILURE", +} + +var ResponseResultType_value = map[string]int32{ + "RESPONSE_RESULT_TYPE_UNSPECIFIED": 0, + "RESPONSE_RESULT_TYPE_NOOP": 1, + "RESPONSE_RESULT_TYPE_SUCCESS": 2, + "RESPONSE_RESULT_TYPE_FAILURE": 3, +} + +func (x ResponseResultType) String() string { + return proto.EnumName(ResponseResultType_name, int32(x)) +} + +func (ResponseResultType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_d421c7119e969b99, []int{0} +} + +// MsgSendPacket sends an outgoing IBC packet. +type MsgSendPacket struct { + SourceClient string `protobuf:"bytes,1,opt,name=source_client,json=sourceClient,proto3" json:"source_client,omitempty"` + TimeoutTimestamp uint64 `protobuf:"varint,2,opt,name=timeout_timestamp,json=timeoutTimestamp,proto3" json:"timeout_timestamp,omitempty"` + Payloads []Payload `protobuf:"bytes,3,rep,name=payloads,proto3" json:"payloads"` + Signer string `protobuf:"bytes,4,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgSendPacket) Reset() { *m = MsgSendPacket{} } +func (m *MsgSendPacket) String() string { return proto.CompactTextString(m) } +func (*MsgSendPacket) ProtoMessage() {} +func (*MsgSendPacket) Descriptor() ([]byte, []int) { + return fileDescriptor_d421c7119e969b99, []int{0} +} +func (m *MsgSendPacket) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSendPacket) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSendPacket.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgSendPacket) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSendPacket.Merge(m, src) +} +func (m *MsgSendPacket) XXX_Size() int { + return m.Size() +} +func (m *MsgSendPacket) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSendPacket.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSendPacket proto.InternalMessageInfo + +// MsgSendPacketResponse defines the Msg/SendPacket response type. +type MsgSendPacketResponse struct { + Sequence uint64 `protobuf:"varint,1,opt,name=sequence,proto3" json:"sequence,omitempty"` +} + +func (m *MsgSendPacketResponse) Reset() { *m = MsgSendPacketResponse{} } +func (m *MsgSendPacketResponse) String() string { return proto.CompactTextString(m) } +func (*MsgSendPacketResponse) ProtoMessage() {} +func (*MsgSendPacketResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_d421c7119e969b99, []int{1} +} +func (m *MsgSendPacketResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSendPacketResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSendPacketResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgSendPacketResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSendPacketResponse.Merge(m, src) +} +func (m *MsgSendPacketResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgSendPacketResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSendPacketResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSendPacketResponse proto.InternalMessageInfo + +// MsgRecvPacket receives an incoming IBC packet. +type MsgRecvPacket struct { + Packet Packet `protobuf:"bytes,1,opt,name=packet,proto3" json:"packet"` + ProofCommitment []byte `protobuf:"bytes,2,opt,name=proof_commitment,json=proofCommitment,proto3" json:"proof_commitment,omitempty"` + ProofHeight types.Height `protobuf:"bytes,3,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` + Signer string `protobuf:"bytes,4,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgRecvPacket) Reset() { *m = MsgRecvPacket{} } +func (m *MsgRecvPacket) String() string { return proto.CompactTextString(m) } +func (*MsgRecvPacket) ProtoMessage() {} +func (*MsgRecvPacket) Descriptor() ([]byte, []int) { + return fileDescriptor_d421c7119e969b99, []int{2} +} +func (m *MsgRecvPacket) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgRecvPacket) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgRecvPacket.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgRecvPacket) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgRecvPacket.Merge(m, src) +} +func (m *MsgRecvPacket) XXX_Size() int { + return m.Size() +} +func (m *MsgRecvPacket) XXX_DiscardUnknown() { + xxx_messageInfo_MsgRecvPacket.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgRecvPacket proto.InternalMessageInfo + +// MsgRecvPacketResponse defines the Msg/RecvPacket response type. +type MsgRecvPacketResponse struct { + Result ResponseResultType `protobuf:"varint,1,opt,name=result,proto3,enum=ibc.core.channel.v2.ResponseResultType" json:"result,omitempty"` +} + +func (m *MsgRecvPacketResponse) Reset() { *m = MsgRecvPacketResponse{} } +func (m *MsgRecvPacketResponse) String() string { return proto.CompactTextString(m) } +func (*MsgRecvPacketResponse) ProtoMessage() {} +func (*MsgRecvPacketResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_d421c7119e969b99, []int{3} +} +func (m *MsgRecvPacketResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgRecvPacketResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgRecvPacketResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgRecvPacketResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgRecvPacketResponse.Merge(m, src) +} +func (m *MsgRecvPacketResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgRecvPacketResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgRecvPacketResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgRecvPacketResponse proto.InternalMessageInfo + +// MsgTimeout receives timed-out packet +type MsgTimeout struct { + Packet Packet `protobuf:"bytes,1,opt,name=packet,proto3" json:"packet"` + ProofUnreceived []byte `protobuf:"bytes,2,opt,name=proof_unreceived,json=proofUnreceived,proto3" json:"proof_unreceived,omitempty"` + ProofHeight types.Height `protobuf:"bytes,3,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` + Signer string `protobuf:"bytes,5,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgTimeout) Reset() { *m = MsgTimeout{} } +func (m *MsgTimeout) String() string { return proto.CompactTextString(m) } +func (*MsgTimeout) ProtoMessage() {} +func (*MsgTimeout) Descriptor() ([]byte, []int) { + return fileDescriptor_d421c7119e969b99, []int{4} +} +func (m *MsgTimeout) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgTimeout) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgTimeout.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgTimeout) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgTimeout.Merge(m, src) +} +func (m *MsgTimeout) XXX_Size() int { + return m.Size() +} +func (m *MsgTimeout) XXX_DiscardUnknown() { + xxx_messageInfo_MsgTimeout.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgTimeout proto.InternalMessageInfo + +// MsgTimeoutResponse defines the Msg/Timeout response type. +type MsgTimeoutResponse struct { + Result ResponseResultType `protobuf:"varint,1,opt,name=result,proto3,enum=ibc.core.channel.v2.ResponseResultType" json:"result,omitempty"` +} + +func (m *MsgTimeoutResponse) Reset() { *m = MsgTimeoutResponse{} } +func (m *MsgTimeoutResponse) String() string { return proto.CompactTextString(m) } +func (*MsgTimeoutResponse) ProtoMessage() {} +func (*MsgTimeoutResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_d421c7119e969b99, []int{5} +} +func (m *MsgTimeoutResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgTimeoutResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgTimeoutResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgTimeoutResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgTimeoutResponse.Merge(m, src) +} +func (m *MsgTimeoutResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgTimeoutResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgTimeoutResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgTimeoutResponse proto.InternalMessageInfo + +// MsgAcknowledgement receives incoming IBC acknowledgement. +type MsgAcknowledgement struct { + Packet Packet `protobuf:"bytes,1,opt,name=packet,proto3" json:"packet"` + Acknowledgement Acknowledgement `protobuf:"bytes,2,opt,name=acknowledgement,proto3" json:"acknowledgement"` + ProofAcked []byte `protobuf:"bytes,3,opt,name=proof_acked,json=proofAcked,proto3" json:"proof_acked,omitempty"` + ProofHeight types.Height `protobuf:"bytes,4,opt,name=proof_height,json=proofHeight,proto3" json:"proof_height"` + Signer string `protobuf:"bytes,5,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgAcknowledgement) Reset() { *m = MsgAcknowledgement{} } +func (m *MsgAcknowledgement) String() string { return proto.CompactTextString(m) } +func (*MsgAcknowledgement) ProtoMessage() {} +func (*MsgAcknowledgement) Descriptor() ([]byte, []int) { + return fileDescriptor_d421c7119e969b99, []int{6} +} +func (m *MsgAcknowledgement) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgAcknowledgement) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgAcknowledgement.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgAcknowledgement) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAcknowledgement.Merge(m, src) +} +func (m *MsgAcknowledgement) XXX_Size() int { + return m.Size() +} +func (m *MsgAcknowledgement) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAcknowledgement.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgAcknowledgement proto.InternalMessageInfo + +// MsgAcknowledgementResponse defines the Msg/Acknowledgement response type. +type MsgAcknowledgementResponse struct { + Result ResponseResultType `protobuf:"varint,1,opt,name=result,proto3,enum=ibc.core.channel.v2.ResponseResultType" json:"result,omitempty"` +} + +func (m *MsgAcknowledgementResponse) Reset() { *m = MsgAcknowledgementResponse{} } +func (m *MsgAcknowledgementResponse) String() string { return proto.CompactTextString(m) } +func (*MsgAcknowledgementResponse) ProtoMessage() {} +func (*MsgAcknowledgementResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_d421c7119e969b99, []int{7} +} +func (m *MsgAcknowledgementResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgAcknowledgementResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgAcknowledgementResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgAcknowledgementResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAcknowledgementResponse.Merge(m, src) +} +func (m *MsgAcknowledgementResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgAcknowledgementResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAcknowledgementResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgAcknowledgementResponse proto.InternalMessageInfo + +func init() { + proto.RegisterEnum("ibc.core.channel.v2.ResponseResultType", ResponseResultType_name, ResponseResultType_value) + proto.RegisterType((*MsgSendPacket)(nil), "ibc.core.channel.v2.MsgSendPacket") + proto.RegisterType((*MsgSendPacketResponse)(nil), "ibc.core.channel.v2.MsgSendPacketResponse") + proto.RegisterType((*MsgRecvPacket)(nil), "ibc.core.channel.v2.MsgRecvPacket") + proto.RegisterType((*MsgRecvPacketResponse)(nil), "ibc.core.channel.v2.MsgRecvPacketResponse") + proto.RegisterType((*MsgTimeout)(nil), "ibc.core.channel.v2.MsgTimeout") + proto.RegisterType((*MsgTimeoutResponse)(nil), "ibc.core.channel.v2.MsgTimeoutResponse") + proto.RegisterType((*MsgAcknowledgement)(nil), "ibc.core.channel.v2.MsgAcknowledgement") + proto.RegisterType((*MsgAcknowledgementResponse)(nil), "ibc.core.channel.v2.MsgAcknowledgementResponse") +} + +func init() { proto.RegisterFile("ibc/core/channel/v2/tx.proto", fileDescriptor_d421c7119e969b99) } + +var fileDescriptor_d421c7119e969b99 = []byte{ + // 804 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x96, 0x4d, 0x4f, 0xdb, 0x48, + 0x18, 0xc7, 0x63, 0x62, 0x02, 0x3b, 0x09, 0x9b, 0xac, 0xf7, 0x2d, 0xeb, 0x45, 0x89, 0x95, 0x5d, + 0x09, 0x36, 0x2b, 0x6c, 0xc8, 0xee, 0x1e, 0x40, 0xda, 0x5d, 0x41, 0xd6, 0x68, 0x91, 0x78, 0x89, + 0xec, 0x44, 0x55, 0x5b, 0xd4, 0x28, 0x71, 0xa6, 0x8e, 0x45, 0xec, 0x71, 0x33, 0x4e, 0x5a, 0x6e, + 0x55, 0x4f, 0x28, 0xa7, 0x7e, 0x01, 0xa4, 0x4a, 0xfd, 0x02, 0x1c, 0xfa, 0x21, 0x50, 0x4f, 0x1c, + 0x39, 0x55, 0x88, 0x1c, 0xf8, 0x1a, 0x95, 0x67, 0x26, 0xaf, 0x38, 0x05, 0xa9, 0xe9, 0x29, 0x9e, + 0xff, 0xfc, 0x9e, 0xe7, 0x99, 0xe7, 0x3f, 0xce, 0x8c, 0xc1, 0xa2, 0x55, 0x35, 0x14, 0x03, 0x35, + 0xa1, 0x62, 0xd4, 0x2b, 0x8e, 0x03, 0x1b, 0x4a, 0x3b, 0xa7, 0x78, 0x2f, 0x64, 0xb7, 0x89, 0x3c, + 0x24, 0x7c, 0x6b, 0x55, 0x0d, 0xd9, 0x9f, 0x95, 0xd9, 0xac, 0xdc, 0xce, 0x89, 0xdf, 0x99, 0xc8, + 0x44, 0x64, 0x5e, 0xf1, 0x9f, 0x28, 0x2a, 0xfe, 0x68, 0x20, 0x6c, 0x23, 0xac, 0xd8, 0xd8, 0x54, + 0xda, 0x6b, 0xfe, 0x0f, 0x9b, 0x90, 0x82, 0x2a, 0xb8, 0x15, 0xe3, 0x08, 0x7a, 0x8c, 0x48, 0x0f, + 0x88, 0x86, 0x05, 0x1d, 0xcf, 0x8f, 0xa7, 0x4f, 0x14, 0xc8, 0xbc, 0xe7, 0xc0, 0xc2, 0x1e, 0x36, + 0x75, 0xe8, 0xd4, 0x0a, 0x24, 0x50, 0xf8, 0x05, 0x2c, 0x60, 0xd4, 0x6a, 0x1a, 0xb0, 0x4c, 0xc1, + 0x24, 0x27, 0x71, 0xcb, 0x5f, 0x69, 0x31, 0x2a, 0xe6, 0x89, 0x26, 0xfc, 0x0e, 0xbe, 0xf1, 0x2c, + 0x1b, 0xa2, 0x96, 0x57, 0xf6, 0x7f, 0xb1, 0x57, 0xb1, 0xdd, 0xe4, 0x8c, 0xc4, 0x2d, 0xf3, 0x5a, + 0x82, 0x4d, 0x14, 0x7b, 0xba, 0xf0, 0x0f, 0x98, 0x77, 0x2b, 0xc7, 0x0d, 0x54, 0xa9, 0xe1, 0x64, + 0x58, 0x0a, 0x2f, 0x47, 0x73, 0x8b, 0x72, 0x40, 0xf7, 0x72, 0x81, 0x42, 0x5b, 0xfc, 0xf9, 0x87, + 0x74, 0x48, 0xeb, 0xc7, 0x08, 0x3f, 0x80, 0x08, 0xb6, 0x4c, 0x07, 0x36, 0x93, 0x3c, 0x59, 0x0a, + 0x1b, 0x6d, 0xc4, 0x4f, 0xde, 0xa4, 0x43, 0xaf, 0x6e, 0xce, 0xb2, 0x4c, 0xc8, 0xac, 0x83, 0xef, + 0x47, 0x7a, 0xd1, 0x20, 0x76, 0x91, 0x83, 0xa1, 0x20, 0x82, 0x79, 0x0c, 0x9f, 0xb5, 0xa0, 0x63, + 0x40, 0xd2, 0x0e, 0xaf, 0xf5, 0xc7, 0x1b, 0xbc, 0x9f, 0x25, 0xd3, 0xa5, 0x3e, 0x68, 0xd0, 0x68, + 0x33, 0x1f, 0xd6, 0x41, 0x84, 0x5a, 0x49, 0x22, 0xa2, 0xb9, 0x9f, 0x27, 0xac, 0xd9, 0x47, 0xd8, + 0x92, 0x59, 0x80, 0xf0, 0x1b, 0x48, 0xb8, 0x4d, 0x84, 0x9e, 0x96, 0x0d, 0x64, 0xdb, 0x96, 0x67, + 0xfb, 0x2e, 0xfa, 0xe6, 0xc4, 0xb4, 0x38, 0xd1, 0xf3, 0x7d, 0x59, 0xc8, 0x83, 0x18, 0x45, 0xeb, + 0xd0, 0x32, 0xeb, 0x5e, 0x32, 0x4c, 0x6a, 0x89, 0x43, 0xb5, 0xe8, 0x6e, 0xb5, 0xd7, 0xe4, 0xff, + 0x09, 0xc1, 0x4a, 0x45, 0x49, 0x14, 0x95, 0xee, 0x6f, 0xd0, 0x13, 0x62, 0xd0, 0xa0, 0xc9, 0xbe, + 0x41, 0xff, 0x82, 0x48, 0x13, 0xe2, 0x56, 0x83, 0x36, 0xfb, 0x75, 0x6e, 0x29, 0xb0, 0xd9, 0x1e, + 0xae, 0x11, 0xb4, 0x78, 0xec, 0x42, 0x8d, 0x85, 0x31, 0x17, 0xaf, 0x38, 0x00, 0xf6, 0xb0, 0x59, + 0xa4, 0x6f, 0xc0, 0x54, 0x2c, 0x6c, 0x39, 0x4d, 0x68, 0x40, 0xab, 0x0d, 0x6b, 0x23, 0x16, 0x96, + 0xfa, 0xf2, 0xb4, 0x2d, 0x9c, 0xfd, 0xb4, 0x85, 0x8f, 0x81, 0x30, 0xe8, 0x70, 0xda, 0xfe, 0xbd, + 0x9b, 0x21, 0xd9, 0x37, 0x8d, 0x23, 0x07, 0x3d, 0x6f, 0xc0, 0x9a, 0x09, 0xc9, 0x4b, 0xf2, 0x19, + 0x3e, 0x16, 0x41, 0xbc, 0x32, 0x9a, 0x8d, 0xd8, 0x18, 0xcd, 0xfd, 0x1a, 0x98, 0x63, 0xac, 0x32, + 0x4b, 0x36, 0x9e, 0x42, 0x48, 0x03, 0x6a, 0x5e, 0xd9, 0x2f, 0x52, 0x23, 0x8e, 0xc7, 0x34, 0x40, + 0xa4, 0x4d, 0x5f, 0xb9, 0xb5, 0x27, 0xfc, 0x17, 0xdd, 0x13, 0x03, 0x88, 0xb7, 0x5d, 0x9b, 0xf2, + 0xde, 0x64, 0x2f, 0x39, 0x20, 0xdc, 0x86, 0x84, 0xbf, 0x80, 0xa4, 0xa9, 0x7a, 0xe1, 0x60, 0x5f, + 0x57, 0xcb, 0x9a, 0xaa, 0x97, 0x76, 0x8b, 0xe5, 0xe2, 0xc3, 0x82, 0x5a, 0x2e, 0xed, 0xeb, 0x05, + 0x35, 0xbf, 0xb3, 0xbd, 0xa3, 0xfe, 0x97, 0x08, 0x89, 0xf1, 0xce, 0xa9, 0x14, 0x1d, 0x92, 0x84, + 0x25, 0xf0, 0x53, 0x60, 0xd8, 0xfe, 0xc1, 0x41, 0x21, 0xc1, 0x89, 0xf3, 0x9d, 0x53, 0x89, 0xf7, + 0x9f, 0x85, 0x15, 0xb0, 0x18, 0x08, 0xea, 0xa5, 0x7c, 0x5e, 0xd5, 0xf5, 0xc4, 0x8c, 0x18, 0xed, + 0x9c, 0x4a, 0x73, 0x6c, 0x38, 0x11, 0xdf, 0xde, 0xdc, 0xd9, 0x2d, 0x69, 0x6a, 0x22, 0x4c, 0x71, + 0x36, 0x14, 0xf9, 0x93, 0xb7, 0xa9, 0x50, 0xae, 0x13, 0x06, 0xe1, 0x3d, 0x6c, 0x0a, 0x87, 0x00, + 0x0c, 0x5d, 0x04, 0x99, 0x40, 0x9f, 0x46, 0x0e, 0x58, 0x31, 0x7b, 0x37, 0xd3, 0xdf, 0x87, 0x43, + 0x00, 0x86, 0x8e, 0xd7, 0x89, 0xd9, 0x07, 0xcc, 0xe4, 0xec, 0x01, 0x27, 0x98, 0x0e, 0xe6, 0x7a, + 0xc7, 0x4e, 0x7a, 0x52, 0x18, 0x03, 0xc4, 0xa5, 0x3b, 0x80, 0x7e, 0xd2, 0x23, 0x10, 0x1f, 0xff, + 0x2f, 0x4e, 0x8c, 0x1d, 0x03, 0x45, 0xe5, 0x9e, 0x60, 0xaf, 0x98, 0x38, 0xfb, 0xf2, 0xe6, 0x2c, + 0xcb, 0x6d, 0x3d, 0x38, 0xbf, 0x4e, 0x71, 0x17, 0xd7, 0x29, 0xee, 0xea, 0x3a, 0xc5, 0xbd, 0xee, + 0xa6, 0x42, 0x17, 0xdd, 0x54, 0xe8, 0xb2, 0x9b, 0x0a, 0x3d, 0xfa, 0xdb, 0xb4, 0xbc, 0x7a, 0xab, + 0x2a, 0x1b, 0xc8, 0x56, 0xd8, 0x27, 0x81, 0x55, 0x35, 0x56, 0x4c, 0xa4, 0xb4, 0xd7, 0x56, 0x15, + 0x1b, 0xd5, 0x5a, 0x0d, 0x88, 0xe9, 0x6d, 0xbf, 0xfa, 0xe7, 0xca, 0xf0, 0x47, 0xc7, 0xb1, 0x0b, + 0x71, 0x35, 0x42, 0x6e, 0xfc, 0x3f, 0x3e, 0x06, 0x00, 0x00, 0xff, 0xff, 0x76, 0x66, 0x88, 0x36, + 0x98, 0x08, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MsgClient is the client API for Msg service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MsgClient interface { + // SendPacket defines a rpc handler method for MsgSendPacket. + SendPacket(ctx context.Context, in *MsgSendPacket, opts ...grpc.CallOption) (*MsgSendPacketResponse, error) + // RecvPacket defines a rpc handler method for MsgRecvPacket. + RecvPacket(ctx context.Context, in *MsgRecvPacket, opts ...grpc.CallOption) (*MsgRecvPacketResponse, error) + // Timeout defines a rpc handler method for MsgTimeout. + Timeout(ctx context.Context, in *MsgTimeout, opts ...grpc.CallOption) (*MsgTimeoutResponse, error) + // Acknowledgement defines a rpc handler method for MsgAcknowledgement. + Acknowledgement(ctx context.Context, in *MsgAcknowledgement, opts ...grpc.CallOption) (*MsgAcknowledgementResponse, error) +} + +type msgClient struct { + cc grpc1.ClientConn +} + +func NewMsgClient(cc grpc1.ClientConn) MsgClient { + return &msgClient{cc} +} + +func (c *msgClient) SendPacket(ctx context.Context, in *MsgSendPacket, opts ...grpc.CallOption) (*MsgSendPacketResponse, error) { + out := new(MsgSendPacketResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v2.Msg/SendPacket", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) RecvPacket(ctx context.Context, in *MsgRecvPacket, opts ...grpc.CallOption) (*MsgRecvPacketResponse, error) { + out := new(MsgRecvPacketResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v2.Msg/RecvPacket", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) Timeout(ctx context.Context, in *MsgTimeout, opts ...grpc.CallOption) (*MsgTimeoutResponse, error) { + out := new(MsgTimeoutResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v2.Msg/Timeout", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) Acknowledgement(ctx context.Context, in *MsgAcknowledgement, opts ...grpc.CallOption) (*MsgAcknowledgementResponse, error) { + out := new(MsgAcknowledgementResponse) + err := c.cc.Invoke(ctx, "/ibc.core.channel.v2.Msg/Acknowledgement", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MsgServer is the server API for Msg service. +type MsgServer interface { + // SendPacket defines a rpc handler method for MsgSendPacket. + SendPacket(context.Context, *MsgSendPacket) (*MsgSendPacketResponse, error) + // RecvPacket defines a rpc handler method for MsgRecvPacket. + RecvPacket(context.Context, *MsgRecvPacket) (*MsgRecvPacketResponse, error) + // Timeout defines a rpc handler method for MsgTimeout. + Timeout(context.Context, *MsgTimeout) (*MsgTimeoutResponse, error) + // Acknowledgement defines a rpc handler method for MsgAcknowledgement. + Acknowledgement(context.Context, *MsgAcknowledgement) (*MsgAcknowledgementResponse, error) +} + +// UnimplementedMsgServer can be embedded to have forward compatible implementations. +type UnimplementedMsgServer struct { +} + +func (*UnimplementedMsgServer) SendPacket(ctx context.Context, req *MsgSendPacket) (*MsgSendPacketResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SendPacket not implemented") +} +func (*UnimplementedMsgServer) RecvPacket(ctx context.Context, req *MsgRecvPacket) (*MsgRecvPacketResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RecvPacket not implemented") +} +func (*UnimplementedMsgServer) Timeout(ctx context.Context, req *MsgTimeout) (*MsgTimeoutResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Timeout not implemented") +} +func (*UnimplementedMsgServer) Acknowledgement(ctx context.Context, req *MsgAcknowledgement) (*MsgAcknowledgementResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Acknowledgement not implemented") +} + +func RegisterMsgServer(s grpc1.Server, srv MsgServer) { + s.RegisterService(&_Msg_serviceDesc, srv) +} + +func _Msg_SendPacket_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgSendPacket) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).SendPacket(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v2.Msg/SendPacket", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).SendPacket(ctx, req.(*MsgSendPacket)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_RecvPacket_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgRecvPacket) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).RecvPacket(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v2.Msg/RecvPacket", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).RecvPacket(ctx, req.(*MsgRecvPacket)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_Timeout_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgTimeout) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).Timeout(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v2.Msg/Timeout", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).Timeout(ctx, req.(*MsgTimeout)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_Acknowledgement_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgAcknowledgement) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).Acknowledgement(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.core.channel.v2.Msg/Acknowledgement", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).Acknowledgement(ctx, req.(*MsgAcknowledgement)) + } + return interceptor(ctx, in, info, handler) +} + +var _Msg_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ibc.core.channel.v2.Msg", + HandlerType: (*MsgServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SendPacket", + Handler: _Msg_SendPacket_Handler, + }, + { + MethodName: "RecvPacket", + Handler: _Msg_RecvPacket_Handler, + }, + { + MethodName: "Timeout", + Handler: _Msg_Timeout_Handler, + }, + { + MethodName: "Acknowledgement", + Handler: _Msg_Acknowledgement_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "ibc/core/channel/v2/tx.proto", +} + +func (m *MsgSendPacket) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgSendPacket) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSendPacket) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x22 + } + if len(m.Payloads) > 0 { + for iNdEx := len(m.Payloads) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Payloads[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + if m.TimeoutTimestamp != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.TimeoutTimestamp)) + i-- + dAtA[i] = 0x10 + } + if len(m.SourceClient) > 0 { + i -= len(m.SourceClient) + copy(dAtA[i:], m.SourceClient) + i = encodeVarintTx(dAtA, i, uint64(len(m.SourceClient))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgSendPacketResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgSendPacketResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSendPacketResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Sequence != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *MsgRecvPacket) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgRecvPacket) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgRecvPacket) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x22 + } + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.ProofCommitment) > 0 { + i -= len(m.ProofCommitment) + copy(dAtA[i:], m.ProofCommitment) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProofCommitment))) + i-- + dAtA[i] = 0x12 + } + { + size, err := m.Packet.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *MsgRecvPacketResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgRecvPacketResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgRecvPacketResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Result != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.Result)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *MsgTimeout) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgTimeout) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgTimeout) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x2a + } + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.ProofUnreceived) > 0 { + i -= len(m.ProofUnreceived) + copy(dAtA[i:], m.ProofUnreceived) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProofUnreceived))) + i-- + dAtA[i] = 0x12 + } + { + size, err := m.Packet.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *MsgTimeoutResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgTimeoutResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgTimeoutResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Result != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.Result)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *MsgAcknowledgement) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgAcknowledgement) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgAcknowledgement) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0x2a + } + { + size, err := m.ProofHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + if len(m.ProofAcked) > 0 { + i -= len(m.ProofAcked) + copy(dAtA[i:], m.ProofAcked) + i = encodeVarintTx(dAtA, i, uint64(len(m.ProofAcked))) + i-- + dAtA[i] = 0x1a + } + { + size, err := m.Acknowledgement.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.Packet.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *MsgAcknowledgementResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgAcknowledgementResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgAcknowledgementResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Result != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.Result)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgSendPacket) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.SourceClient) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.TimeoutTimestamp != 0 { + n += 1 + sovTx(uint64(m.TimeoutTimestamp)) + } + if len(m.Payloads) > 0 { + for _, e := range m.Payloads { + l = e.Size() + n += 1 + l + sovTx(uint64(l)) + } + } + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgSendPacketResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Sequence != 0 { + n += 1 + sovTx(uint64(m.Sequence)) + } + return n +} + +func (m *MsgRecvPacket) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Packet.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.ProofCommitment) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgRecvPacketResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Result != 0 { + n += 1 + sovTx(uint64(m.Result)) + } + return n +} + +func (m *MsgTimeout) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Packet.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.ProofUnreceived) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgTimeoutResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Result != 0 { + n += 1 + sovTx(uint64(m.Result)) + } + return n +} + +func (m *MsgAcknowledgement) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Packet.Size() + n += 1 + l + sovTx(uint64(l)) + l = m.Acknowledgement.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.ProofAcked) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.ProofHeight.Size() + n += 1 + l + sovTx(uint64(l)) + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgAcknowledgementResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Result != 0 { + n += 1 + sovTx(uint64(m.Result)) + } + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgSendPacket) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgSendPacket: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSendPacket: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SourceClient", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SourceClient = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeoutTimestamp", wireType) + } + m.TimeoutTimestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TimeoutTimestamp |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payloads", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payloads = append(m.Payloads, Payload{}) + if err := m.Payloads[len(m.Payloads)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgSendPacketResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgSendPacketResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSendPacketResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgRecvPacket) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgRecvPacket: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgRecvPacket: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Packet", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Packet.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofCommitment", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProofCommitment = append(m.ProofCommitment[:0], dAtA[iNdEx:postIndex]...) + if m.ProofCommitment == nil { + m.ProofCommitment = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgRecvPacketResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgRecvPacketResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgRecvPacketResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Result", wireType) + } + m.Result = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Result |= ResponseResultType(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgTimeout) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgTimeout: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgTimeout: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Packet", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Packet.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofUnreceived", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProofUnreceived = append(m.ProofUnreceived[:0], dAtA[iNdEx:postIndex]...) + if m.ProofUnreceived == nil { + m.ProofUnreceived = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgTimeoutResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgTimeoutResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgTimeoutResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Result", wireType) + } + m.Result = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Result |= ResponseResultType(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgAcknowledgement) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgAcknowledgement: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgAcknowledgement: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Packet", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Packet.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Acknowledgement", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Acknowledgement.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofAcked", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProofAcked = append(m.ProofAcked[:0], dAtA[iNdEx:postIndex]...) + if m.ProofAcked == nil { + m.ProofAcked = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProofHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgAcknowledgementResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgAcknowledgementResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgAcknowledgementResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Result", wireType) + } + m.Result = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Result |= ResponseResultType(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTx(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/05-port/doc.go b/modules/core/05-port/doc.go new file mode 100644 index 0000000..e9a5a71 --- /dev/null +++ b/modules/core/05-port/doc.go @@ -0,0 +1,7 @@ +/* +Package port implements the ICS 05 - Port Allocation specification +(https://github.com/cosmos/ibc/tree/main/spec/core/ics-005-port-allocation). This +concrete implementation defines types and methods with which modules can bind +to uniquely named ports allocated by the IBC handler. +*/ +package port diff --git a/modules/core/05-port/keeper/keeper.go b/modules/core/05-port/keeper/keeper.go new file mode 100644 index 0000000..009135e --- /dev/null +++ b/modules/core/05-port/keeper/keeper.go @@ -0,0 +1,47 @@ +package keeper + +import ( + "strings" + + "cosmossdk.io/log" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + "github.com/cosmos/ibc-go/v10/modules/core/api" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// Keeper defines the IBC connection keeper +type Keeper struct { + Router *types.Router + RouterV2 *api.Router +} + +// NewKeeper creates a new IBC connection Keeper instance +func NewKeeper() *Keeper { + return &Keeper{} +} + +// Logger returns a module-specific logger. +func (Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", "x/"+exported.ModuleName+"/"+types.SubModuleName) +} + +// Route returns a IBCModule for a given module, and a boolean indicating +// whether or not the route is present. +func (k *Keeper) Route(module string) (types.IBCModule, bool) { + routes, ok := k.Router.Route(module) + + if ok { + return routes, true + } + + for _, prefix := range k.Router.Keys() { + if strings.Contains(module, prefix) { + return k.Router.Route(prefix) + } + } + + return nil, false +} diff --git a/modules/core/05-port/keeper/keeper_test.go b/modules/core/05-port/keeper/keeper_test.go new file mode 100644 index 0000000..4c052e3 --- /dev/null +++ b/modules/core/05-port/keeper/keeper_test.go @@ -0,0 +1,31 @@ +package keeper_test + +import ( + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/05-port/keeper" + "github.com/cosmos/ibc-go/v10/testing/simapp" +) + +type KeeperTestSuite struct { + testifysuite.Suite + + ctx sdk.Context + keeper *keeper.Keeper +} + +func (suite *KeeperTestSuite) SetupTest() { + isCheckTx := false + app := simapp.Setup(suite.T(), isCheckTx) + + suite.ctx = app.NewContext(isCheckTx) + suite.keeper = app.IBCKeeper.PortKeeper +} + +func TestKeeperTestSuite(t *testing.T) { + testifysuite.Run(t, new(KeeperTestSuite)) +} diff --git a/modules/core/05-port/module.go b/modules/core/05-port/module.go new file mode 100644 index 0000000..32dae4e --- /dev/null +++ b/modules/core/05-port/module.go @@ -0,0 +1,18 @@ +package port + +import ( + "github.com/spf13/cobra" + + "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + "github.com/cosmos/ibc-go/v10/modules/core/client/cli" +) + +// Name returns the IBC port ICS name. +func Name() string { + return types.SubModuleName +} + +// GetQueryCmd returns the root query command for IBC ports. +func GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd() +} diff --git a/modules/core/05-port/types/errors.go b/modules/core/05-port/types/errors.go new file mode 100644 index 0000000..551aeba --- /dev/null +++ b/modules/core/05-port/types/errors.go @@ -0,0 +1,13 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" +) + +// IBC port sentinel errors +var ( + ErrPortExists = errorsmod.Register(SubModuleName, 2, "port is already binded") + ErrPortNotFound = errorsmod.Register(SubModuleName, 3, "port not found") + ErrInvalidPort = errorsmod.Register(SubModuleName, 4, "invalid port") + ErrInvalidRoute = errorsmod.Register(SubModuleName, 5, "route not found") +) diff --git a/modules/core/05-port/types/keys.go b/modules/core/05-port/types/keys.go new file mode 100644 index 0000000..6a77f30 --- /dev/null +++ b/modules/core/05-port/types/keys.go @@ -0,0 +1,15 @@ +package types + +const ( + // SubModuleName defines the IBC port name + SubModuleName = "port" + + // StoreKey is the store key string for IBC ports + StoreKey = SubModuleName + + // RouterKey is the message route for IBC ports + RouterKey = SubModuleName + + // QuerierRoute is the querier route for IBC ports + QuerierRoute = SubModuleName +) diff --git a/modules/core/05-port/types/module.go b/modules/core/05-port/types/module.go new file mode 100644 index 0000000..0139267 --- /dev/null +++ b/modules/core/05-port/types/module.go @@ -0,0 +1,147 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// IBCModule defines an interface that implements all the callbacks +// that modules must define as specified in ICS-26 +type IBCModule interface { + // OnChanOpenInit will verify that the relayer-chosen parameters + // are valid and perform any custom INIT logic. + // It may return an error if the chosen parameters are invalid + // in which case the handshake is aborted. + // If the provided version string is non-empty, OnChanOpenInit should return + // the version string if valid or an error if the provided version is invalid. + // If the version string is empty, OnChanOpenInit is expected to + // return a default version string representing the version(s) it supports. + // If there is no default version string for the application, + // it should return an error if provided version is empty string. + OnChanOpenInit( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + counterparty channeltypes.Counterparty, + version string, + ) (string, error) + + // OnChanOpenTry will verify the relayer-chosen parameters along with the + // counterparty-chosen version string and perform custom TRY logic. + // If the relayer-chosen parameters are invalid, the callback must return + // an error to abort the handshake. If the counterparty-chosen version is not + // compatible with this modules supported versions, the callback must return + // an error to abort the handshake. If the versions are compatible, the try callback + // must select the final version string and return it to core IBC. + // OnChanOpenTry may also perform custom initialization logic + OnChanOpenTry( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + counterparty channeltypes.Counterparty, + counterpartyVersion string, + ) (version string, err error) + + // OnChanOpenAck will error if the counterparty selected version string + // is invalid to abort the handshake. It may also perform custom ACK logic. + OnChanOpenAck( + ctx sdk.Context, + portID, + channelID string, + counterpartyChannelID string, + counterpartyVersion string, + ) error + + // OnChanOpenConfirm will perform custom CONFIRM logic and may error to abort the handshake. + OnChanOpenConfirm( + ctx sdk.Context, + portID, + channelID string, + ) error + + OnChanCloseInit( + ctx sdk.Context, + portID, + channelID string, + ) error + + OnChanCloseConfirm( + ctx sdk.Context, + portID, + channelID string, + ) error + + // OnRecvPacket must return an acknowledgement that implements the Acknowledgement interface. + // In the case of an asynchronous acknowledgement, nil should be returned. + // If the acknowledgement returned is successful, the state changes on callback are written, + // otherwise the application state changes are discarded. In either case the packet is received + // and the acknowledgement is written (in synchronous cases). + OnRecvPacket( + ctx sdk.Context, + channelVersion string, + packet channeltypes.Packet, + relayer sdk.AccAddress, + ) exported.Acknowledgement + + OnAcknowledgementPacket( + ctx sdk.Context, + channelVersion string, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, + ) error + + OnTimeoutPacket( + ctx sdk.Context, + channelVersion string, + packet channeltypes.Packet, + relayer sdk.AccAddress, + ) error +} + +// ICS4Wrapper implements the ICS4 interfaces that IBC applications use to send packets and acknowledgements. +type ICS4Wrapper interface { + SendPacket( + ctx sdk.Context, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + data []byte, + ) (sequence uint64, err error) + + WriteAcknowledgement( + ctx sdk.Context, + packet exported.PacketI, + ack exported.Acknowledgement, + ) error + + GetAppVersion( + ctx sdk.Context, + portID, + channelID string, + ) (string, bool) +} + +// Middleware must implement IBCModule to wrap communication from core IBC to underlying application +// and ICS4Wrapper to wrap communication from underlying application to core IBC. +type Middleware interface { + IBCModule + ICS4Wrapper +} + +// PacketDataUnmarshaler defines an optional interface which allows a middleware to +// request the packet data to be unmarshaled by the base application. +type PacketDataUnmarshaler interface { + // UnmarshalPacketData unmarshals the packet data into a concrete type + // ctx, portID, channelID are provided as arguments, so that (if needed) + // the packet data can be unmarshaled based on the channel version. + UnmarshalPacketData(ctx sdk.Context, portID string, channelID string, bz []byte) (any, string, error) +} diff --git a/modules/core/05-port/types/router.go b/modules/core/05-port/types/router.go new file mode 100644 index 0000000..d28ec9a --- /dev/null +++ b/modules/core/05-port/types/router.go @@ -0,0 +1,79 @@ +package types + +import ( + "errors" + "fmt" + "sort" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// The router is a map from module name to the IBCModule +// which contains all the module-defined callbacks required by ICS-26 +type Router struct { + routes map[string]IBCModule + sealed bool +} + +func NewRouter() *Router { + return &Router{ + routes: make(map[string]IBCModule), + } +} + +// Seal prevents the Router from any subsequent route handlers to be registered. +// Seal will panic if called more than once. +func (rtr *Router) Seal() { + if rtr.sealed { + panic(errors.New("router already sealed")) + } + rtr.sealed = true +} + +// Sealed returns a boolean signifying if the Router is sealed or not. +func (rtr Router) Sealed() bool { + return rtr.sealed +} + +// AddRoute adds IBCModule for a given module name. It returns the Router +// so AddRoute calls can be linked. It will panic if the Router is sealed. +func (rtr *Router) AddRoute(module string, cbs IBCModule) *Router { + if rtr.sealed { + panic(fmt.Errorf("router sealed; cannot register %s route callbacks", module)) + } + if !sdk.IsAlphaNumeric(module) { + panic(errors.New("route expressions can only contain alphanumeric characters")) + } + if rtr.HasRoute(module) { + panic(fmt.Errorf("route %s has already been registered", module)) + } + + rtr.routes[module] = cbs + return rtr +} + +// HasRoute returns true if the Router has a module registered or false otherwise. +func (rtr *Router) HasRoute(module string) bool { + _, ok := rtr.routes[module] + return ok +} + +// Route returns a IBCModule for a given module. +func (rtr *Router) Route(module string) (IBCModule, bool) { + if !rtr.HasRoute(module) { + return nil, false + } + return rtr.routes[module], true +} + +// Keys returns the keys of the routes map. +func (rtr *Router) Keys() []string { + keys := make([]string, 0, len(rtr.routes)) + + for k := range rtr.routes { + keys = append(keys, k) + } + + sort.Strings(keys) + return keys +} diff --git a/modules/core/23-commitment/types/codec.go b/modules/core/23-commitment/types/codec.go new file mode 100644 index 0000000..b991f47 --- /dev/null +++ b/modules/core/23-commitment/types/codec.go @@ -0,0 +1,37 @@ +package types + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + + "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types/v2" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// RegisterInterfaces registers the commitment interfaces to protobuf Any. +func RegisterInterfaces(registry codectypes.InterfaceRegistry) { + registry.RegisterInterface( + "ibc.core.commitment.v1.Root", + (*exported.Root)(nil), + ) + registry.RegisterInterface( + "ibc.core.commitment.v1.Prefix", + (*exported.Prefix)(nil), + ) + registry.RegisterInterface( + "ibc.core.commitment.v1.Path", + (*exported.Path)(nil), + ) + + registry.RegisterImplementations( + (*exported.Root)(nil), + &MerkleRoot{}, + ) + registry.RegisterImplementations( + (*exported.Prefix)(nil), + &MerklePrefix{}, + ) + registry.RegisterImplementations( + (*exported.Path)(nil), + &v2.MerklePath{}, + ) +} diff --git a/modules/core/23-commitment/types/codec_test.go b/modules/core/23-commitment/types/codec_test.go new file mode 100644 index 0000000..79d7275 --- /dev/null +++ b/modules/core/23-commitment/types/codec_test.go @@ -0,0 +1,56 @@ +package types_test + +import ( + "errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + + ibc "github.com/cosmos/ibc-go/v10/modules/core" + "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types/v2" +) + +func (suite *MerkleTestSuite) TestCodecTypeRegistration() { + testCases := []struct { + name string + typeURL string + expErr error + }{ + { + "success: MerkleRoot", + sdk.MsgTypeURL(&types.MerkleRoot{}), + nil, + }, + { + "success: MerklePrefix", + sdk.MsgTypeURL(&types.MerklePrefix{}), + nil, + }, + { + "success: MerklePath", + sdk.MsgTypeURL(&v2.MerklePath{}), + nil, + }, + { + "type not registered on codec", + "ibc.invalid.MsgTypeURL", + errors.New("unable to resolve type URL ibc.invalid.MsgTypeURL"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + encodingCfg := moduletestutil.MakeTestEncodingConfig(ibc.AppModuleBasic{}) + msg, err := encodingCfg.Codec.InterfaceRegistry().Resolve(tc.typeURL) + + if tc.expErr == nil { + suite.NotNil(msg) + suite.Require().NoError(err) + } else { + suite.Nil(msg) + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } +} diff --git a/modules/core/23-commitment/types/commitment.pb.go b/modules/core/23-commitment/types/commitment.pb.go new file mode 100644 index 0000000..0326ad8 --- /dev/null +++ b/modules/core/23-commitment/types/commitment.pb.go @@ -0,0 +1,685 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/commitment/v1/commitment.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + _go "github.com/cosmos/ics23/go" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// MerkleRoot defines a merkle root hash. +// In the Cosmos SDK, the AppHash of a block header becomes the root. +type MerkleRoot struct { + Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` +} + +func (m *MerkleRoot) Reset() { *m = MerkleRoot{} } +func (m *MerkleRoot) String() string { return proto.CompactTextString(m) } +func (*MerkleRoot) ProtoMessage() {} +func (*MerkleRoot) Descriptor() ([]byte, []int) { + return fileDescriptor_7921d88972a41469, []int{0} +} +func (m *MerkleRoot) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MerkleRoot) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MerkleRoot.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MerkleRoot) XXX_Merge(src proto.Message) { + xxx_messageInfo_MerkleRoot.Merge(m, src) +} +func (m *MerkleRoot) XXX_Size() int { + return m.Size() +} +func (m *MerkleRoot) XXX_DiscardUnknown() { + xxx_messageInfo_MerkleRoot.DiscardUnknown(m) +} + +var xxx_messageInfo_MerkleRoot proto.InternalMessageInfo + +// MerklePrefix is merkle path prefixed to the key. +// The constructed key from the Path and the key will be append(Path.KeyPath, +// append(Path.KeyPrefix, key...)) +type MerklePrefix struct { + KeyPrefix []byte `protobuf:"bytes,1,opt,name=key_prefix,json=keyPrefix,proto3" json:"key_prefix,omitempty"` +} + +func (m *MerklePrefix) Reset() { *m = MerklePrefix{} } +func (m *MerklePrefix) String() string { return proto.CompactTextString(m) } +func (*MerklePrefix) ProtoMessage() {} +func (*MerklePrefix) Descriptor() ([]byte, []int) { + return fileDescriptor_7921d88972a41469, []int{1} +} +func (m *MerklePrefix) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MerklePrefix) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MerklePrefix.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MerklePrefix) XXX_Merge(src proto.Message) { + xxx_messageInfo_MerklePrefix.Merge(m, src) +} +func (m *MerklePrefix) XXX_Size() int { + return m.Size() +} +func (m *MerklePrefix) XXX_DiscardUnknown() { + xxx_messageInfo_MerklePrefix.DiscardUnknown(m) +} + +var xxx_messageInfo_MerklePrefix proto.InternalMessageInfo + +func (m *MerklePrefix) GetKeyPrefix() []byte { + if m != nil { + return m.KeyPrefix + } + return nil +} + +// MerkleProof is a wrapper type over a chain of CommitmentProofs. +// It demonstrates membership or non-membership for an element or set of +// elements, verifiable in conjunction with a known commitment root. Proofs +// should be succinct. +// MerkleProofs are ordered from leaf-to-root +type MerkleProof struct { + Proofs []*_go.CommitmentProof `protobuf:"bytes,1,rep,name=proofs,proto3" json:"proofs,omitempty"` +} + +func (m *MerkleProof) Reset() { *m = MerkleProof{} } +func (m *MerkleProof) String() string { return proto.CompactTextString(m) } +func (*MerkleProof) ProtoMessage() {} +func (*MerkleProof) Descriptor() ([]byte, []int) { + return fileDescriptor_7921d88972a41469, []int{2} +} +func (m *MerkleProof) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MerkleProof) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MerkleProof.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MerkleProof) XXX_Merge(src proto.Message) { + xxx_messageInfo_MerkleProof.Merge(m, src) +} +func (m *MerkleProof) XXX_Size() int { + return m.Size() +} +func (m *MerkleProof) XXX_DiscardUnknown() { + xxx_messageInfo_MerkleProof.DiscardUnknown(m) +} + +var xxx_messageInfo_MerkleProof proto.InternalMessageInfo + +func (m *MerkleProof) GetProofs() []*_go.CommitmentProof { + if m != nil { + return m.Proofs + } + return nil +} + +func init() { + proto.RegisterType((*MerkleRoot)(nil), "ibc.core.commitment.v1.MerkleRoot") + proto.RegisterType((*MerklePrefix)(nil), "ibc.core.commitment.v1.MerklePrefix") + proto.RegisterType((*MerkleProof)(nil), "ibc.core.commitment.v1.MerkleProof") +} + +func init() { + proto.RegisterFile("ibc/core/commitment/v1/commitment.proto", fileDescriptor_7921d88972a41469) +} + +var fileDescriptor_7921d88972a41469 = []byte{ + // 291 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xcf, 0x4c, 0x4a, 0xd6, + 0x4f, 0xce, 0x2f, 0x4a, 0xd5, 0x4f, 0xce, 0xcf, 0xcd, 0xcd, 0x2c, 0xc9, 0x4d, 0xcd, 0x2b, 0xd1, + 0x2f, 0x33, 0x44, 0xe2, 0xe9, 0x15, 0x14, 0xe5, 0x97, 0xe4, 0x0b, 0x89, 0x65, 0x26, 0x25, 0xeb, + 0x81, 0x14, 0xea, 0x21, 0x49, 0x95, 0x19, 0x4a, 0x89, 0xa4, 0xe7, 0xa7, 0xe7, 0x83, 0x95, 0xe8, + 0x83, 0x58, 0x10, 0xd5, 0x52, 0x32, 0xc9, 0xf9, 0xc5, 0xb9, 0xf9, 0xc5, 0xfa, 0x99, 0xc9, 0xc5, + 0x46, 0xc6, 0x20, 0xf3, 0x0a, 0x8a, 0xf2, 0xf3, 0xd3, 0x8a, 0x21, 0xb2, 0x4a, 0x6a, 0x5c, 0x5c, + 0xbe, 0xa9, 0x45, 0xd9, 0x39, 0xa9, 0x41, 0xf9, 0xf9, 0x25, 0x42, 0x42, 0x5c, 0x2c, 0x19, 0x89, + 0xc5, 0x19, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x3c, 0x41, 0x60, 0xb6, 0x15, 0x4b, 0xc7, 0x02, 0x79, + 0x06, 0x25, 0x5d, 0x2e, 0x1e, 0x88, 0xba, 0x80, 0xa2, 0xd4, 0xb4, 0xcc, 0x0a, 0x21, 0x59, 0x2e, + 0xae, 0xec, 0xd4, 0xca, 0xf8, 0x02, 0x30, 0x0f, 0xaa, 0x9e, 0x33, 0x3b, 0xb5, 0x12, 0x22, 0xad, + 0xe4, 0xce, 0xc5, 0x0d, 0x53, 0x9e, 0x9f, 0x9f, 0x26, 0x64, 0xc1, 0xc5, 0x06, 0xb1, 0x55, 0x82, + 0x51, 0x81, 0x59, 0x83, 0xdb, 0x48, 0x41, 0x0f, 0xe2, 0x28, 0x3d, 0xb0, 0xa3, 0xf4, 0xca, 0x0c, + 0xf5, 0x9c, 0xe1, 0x3e, 0x01, 0xeb, 0x08, 0x82, 0xaa, 0x77, 0x0a, 0x3f, 0xf1, 0x48, 0x8e, 0xf1, + 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, + 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x28, 0xdb, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0x50, 0x18, 0xe8, + 0xc3, 0xbc, 0x98, 0x94, 0xac, 0x9b, 0x9e, 0xaf, 0x5f, 0x66, 0x68, 0xa0, 0x9f, 0x9b, 0x9f, 0x52, + 0x9a, 0x93, 0x5a, 0x0c, 0x09, 0x4f, 0x23, 0x63, 0x5d, 0xa4, 0x20, 0x2d, 0xa9, 0x2c, 0x48, 0x2d, + 0x4e, 0x62, 0x03, 0xfb, 0xdf, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x97, 0x00, 0x6d, 0x7e, 0x76, + 0x01, 0x00, 0x00, +} + +func (m *MerkleRoot) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MerkleRoot) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MerkleRoot) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Hash) > 0 { + i -= len(m.Hash) + copy(dAtA[i:], m.Hash) + i = encodeVarintCommitment(dAtA, i, uint64(len(m.Hash))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MerklePrefix) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MerklePrefix) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MerklePrefix) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.KeyPrefix) > 0 { + i -= len(m.KeyPrefix) + copy(dAtA[i:], m.KeyPrefix) + i = encodeVarintCommitment(dAtA, i, uint64(len(m.KeyPrefix))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MerkleProof) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MerkleProof) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MerkleProof) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Proofs) > 0 { + for iNdEx := len(m.Proofs) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Proofs[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintCommitment(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintCommitment(dAtA []byte, offset int, v uint64) int { + offset -= sovCommitment(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MerkleRoot) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Hash) + if l > 0 { + n += 1 + l + sovCommitment(uint64(l)) + } + return n +} + +func (m *MerklePrefix) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.KeyPrefix) + if l > 0 { + n += 1 + l + sovCommitment(uint64(l)) + } + return n +} + +func (m *MerkleProof) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Proofs) > 0 { + for _, e := range m.Proofs { + l = e.Size() + n += 1 + l + sovCommitment(uint64(l)) + } + } + return n +} + +func sovCommitment(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozCommitment(x uint64) (n int) { + return sovCommitment(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MerkleRoot) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommitment + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MerkleRoot: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MerkleRoot: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommitment + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthCommitment + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthCommitment + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Hash = append(m.Hash[:0], dAtA[iNdEx:postIndex]...) + if m.Hash == nil { + m.Hash = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipCommitment(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthCommitment + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MerklePrefix) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommitment + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MerklePrefix: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MerklePrefix: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field KeyPrefix", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommitment + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthCommitment + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthCommitment + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.KeyPrefix = append(m.KeyPrefix[:0], dAtA[iNdEx:postIndex]...) + if m.KeyPrefix == nil { + m.KeyPrefix = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipCommitment(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthCommitment + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MerkleProof) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommitment + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MerkleProof: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MerkleProof: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proofs", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommitment + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthCommitment + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthCommitment + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Proofs = append(m.Proofs, &_go.CommitmentProof{}) + if err := m.Proofs[len(m.Proofs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipCommitment(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthCommitment + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipCommitment(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowCommitment + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowCommitment + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowCommitment + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthCommitment + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupCommitment + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthCommitment + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthCommitment = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowCommitment = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupCommitment = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/23-commitment/types/commitment_test.go b/modules/core/23-commitment/types/commitment_test.go new file mode 100644 index 0000000..000f4a9 --- /dev/null +++ b/modules/core/23-commitment/types/commitment_test.go @@ -0,0 +1,41 @@ +package types_test + +import ( + "testing" + + dbm "github.com/cosmos/cosmos-db" + testifysuite "github.com/stretchr/testify/suite" + + "cosmossdk.io/log" + "cosmossdk.io/store/iavl" + "cosmossdk.io/store/metrics" + "cosmossdk.io/store/rootmulti" + storetypes "cosmossdk.io/store/types" +) + +type MerkleTestSuite struct { + testifysuite.Suite + + store *rootmulti.Store + storeKey *storetypes.KVStoreKey + iavlStore *iavl.Store +} + +func (suite *MerkleTestSuite) SetupTest() { + db := dbm.NewMemDB() + suite.store = rootmulti.NewStore(db, log.NewNopLogger(), metrics.NewNoOpMetrics()) + + suite.storeKey = storetypes.NewKVStoreKey("iavlStoreKey") + + suite.store.MountStoreWithDB(suite.storeKey, storetypes.StoreTypeIAVL, nil) + err := suite.store.LoadVersion(0) + suite.Require().NoError(err) + + var ok bool + suite.iavlStore, ok = suite.store.GetCommitStore(suite.storeKey).(*iavl.Store) + suite.Require().True(ok) +} + +func TestMerkleTestSuite(t *testing.T) { + testifysuite.Run(t, new(MerkleTestSuite)) +} diff --git a/modules/core/23-commitment/types/errors.go b/modules/core/23-commitment/types/errors.go new file mode 100644 index 0000000..5505a69 --- /dev/null +++ b/modules/core/23-commitment/types/errors.go @@ -0,0 +1,15 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" +) + +// SubModuleName is the error codespace +const SubModuleName string = "commitment" + +// IBC connection sentinel errors +var ( + ErrInvalidProof = errorsmod.Register(SubModuleName, 2, "invalid proof") + ErrInvalidPrefix = errorsmod.Register(SubModuleName, 3, "invalid prefix") + ErrInvalidMerkleProof = errorsmod.Register(SubModuleName, 4, "invalid merkle proof") +) diff --git a/modules/core/23-commitment/types/merkle.go b/modules/core/23-commitment/types/merkle.go new file mode 100644 index 0000000..210d0fd --- /dev/null +++ b/modules/core/23-commitment/types/merkle.go @@ -0,0 +1,218 @@ +package types + +import ( + "bytes" + + ics23 "github.com/cosmos/ics23/go" + + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types/v2" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// var representing the proofspecs for an SDK chain +var sdkSpecs = []*ics23.ProofSpec{ics23.IavlSpec, ics23.TendermintSpec} + +// ICS 023 Merkle Types Implementation +// +// This file defines Merkle commitment types that implements ICS 023. + +// Merkle proof implementation of the Proof interface +// Applied on SDK-based IBC implementation +var _ exported.Root = (*MerkleRoot)(nil) + +// GetSDKSpecs is a getter function for the proofspecs of an sdk chain +func GetSDKSpecs() []*ics23.ProofSpec { + return sdkSpecs +} + +// NewMerkleRoot constructs a new MerkleRoot +func NewMerkleRoot(hash []byte) MerkleRoot { + return MerkleRoot{ + Hash: hash, + } +} + +// GetHash implements RootI interface +func (mr MerkleRoot) GetHash() []byte { + return mr.Hash +} + +// Empty returns true if the root is empty +func (mr MerkleRoot) Empty() bool { + return len(mr.GetHash()) == 0 +} + +var _ exported.Prefix = (*MerklePrefix)(nil) + +// NewMerklePrefix constructs new MerklePrefix instance +func NewMerklePrefix(keyPrefix []byte) MerklePrefix { + return MerklePrefix{ + KeyPrefix: keyPrefix, + } +} + +// Bytes returns the key prefix bytes +func (mp MerklePrefix) Bytes() []byte { + return mp.KeyPrefix +} + +// Empty returns true if the prefix is empty +func (mp MerklePrefix) Empty() bool { + return len(mp.Bytes()) == 0 +} + +// NewMerklePath creates a new MerklePath instance +// The keys must be passed in from root-to-leaf order. +// NOTE: NewMerklePath returns a commitment/v2 MerklePath. +var NewMerklePath = v2.NewMerklePath + +// ApplyPrefix constructs a new commitment path from the arguments. It prepends the prefix key +// with the given path. +func ApplyPrefix(prefix exported.Prefix, path v2.MerklePath) (v2.MerklePath, error) { + if prefix == nil || prefix.Empty() { + return v2.MerklePath{}, errorsmod.Wrap(ErrInvalidPrefix, "prefix can't be empty") + } + + return v2.MerklePath{ + KeyPath: append([][]byte{prefix.Bytes()}, path.KeyPath...), + }, nil +} + +// VerifyMembership verifies the membership of a merkle proof against the given root, path, and value. +// Note that the path is expected as []string{, }. +func (proof MerkleProof) VerifyMembership(specs []*ics23.ProofSpec, root exported.Root, path exported.Path, value []byte) error { + mpath, ok := path.(v2.MerklePath) + if !ok { + return errorsmod.Wrapf(ErrInvalidProof, "path %v is not of type MerklePath", path) + } + + if err := validateVerificationArgs(proof, mpath, specs, root); err != nil { + return err + } + + // VerifyMembership specific argument validation + if len(value) == 0 { + return errorsmod.Wrap(ErrInvalidProof, "empty value in membership proof") + } + + // Since every proof in chain is a membership proof we can use verifyChainedMembershipProof from index 0 + // to validate entire proof + return verifyChainedMembershipProof(root.GetHash(), specs, proof.Proofs, mpath, value, 0) +} + +// VerifyNonMembership verifies the absence of a merkle proof against the given root and path. +// VerifyNonMembership verifies a chained proof where the absence of a given path is proven +// at the lowest subtree and then each subtree's inclusion is proved up to the final root. +func (proof MerkleProof) VerifyNonMembership(specs []*ics23.ProofSpec, root exported.Root, path exported.Path) error { + mpath, ok := path.(v2.MerklePath) + if !ok { + return errorsmod.Wrapf(ErrInvalidProof, "path %v is not of type MerkleProof", path) + } + + if err := validateVerificationArgs(proof, mpath, specs, root); err != nil { + return err + } + + // VerifyNonMembership will verify the absence of key in lowest subtree, and then chain inclusion proofs + // of all subroots up to final root + subroot, err := proof.Proofs[0].Calculate() + if err != nil { + return errorsmod.Wrapf(ErrInvalidProof, "could not calculate root for proof index 0, merkle tree is likely empty. %v", err) + } + + key, err := mpath.GetKey(uint64(len(mpath.KeyPath) - 1)) + if err != nil { + return errorsmod.Wrapf(ErrInvalidProof, "could not retrieve key bytes for key: %s", mpath.KeyPath[len(mpath.KeyPath)-1]) + } + + np := proof.Proofs[0].GetNonexist() + if np == nil { + return errorsmod.Wrapf(ErrInvalidProof, "commitment proof must be non-existence proof for verifying non-membership. got: %T", proof.Proofs[0]) + } + + if err := np.Verify(specs[0], subroot, key); err != nil { + return errorsmod.Wrapf(ErrInvalidProof, "failed to verify non-membership proof with key %s: %v", string(key), err) + } + + // Verify chained membership proof starting from index 1 with value = subroot + return verifyChainedMembershipProof(root.GetHash(), specs, proof.Proofs, mpath, subroot, 1) +} + +// verifyChainedMembershipProof takes a list of proofs and specs and verifies each proof sequentially ensuring that the value is committed to +// by first proof and each subsequent subroot is committed to by the next subroot and checking that the final calculated root is equal to the given roothash. +// The proofs and specs are passed in from lowest subtree to the highest subtree, but the keys are passed in from highest subtree to lowest. +// The index specifies what index to start chaining the membership proofs, this is useful since the lowest proof may not be a membership proof, thus we +// will want to start the membership proof chaining from index 1 with value being the lowest subroot +func verifyChainedMembershipProof(root []byte, specs []*ics23.ProofSpec, proofs []*ics23.CommitmentProof, keys v2.MerklePath, value []byte, index int) error { + var ( + subroot []byte + err error + ) + // Initialize subroot to value since the proofs list may be empty. + // This may happen if this call is verifying intermediate proofs after the lowest proof has been executed. + // In this case, there may be no intermediate proofs to verify and we just check that lowest proof root equals final root + subroot = value + for i := index; i < len(proofs); i++ { + subroot, err = proofs[i].Calculate() + if err != nil { + return errorsmod.Wrapf(ErrInvalidProof, "could not calculate proof root at index %d, merkle tree may be empty. %v", i, err) + } + + // Since keys are passed in from highest to lowest, we must grab their indices in reverse order + // from the proofs and specs which are lowest to highest + key, err := keys.GetKey(uint64(len(keys.KeyPath) - 1 - i)) + if err != nil { + return errorsmod.Wrapf(ErrInvalidProof, "could not retrieve key bytes for key %s: %v", keys.KeyPath[len(keys.KeyPath)-1-i], err) + } + + ep := proofs[i].GetExist() + if ep == nil { + return errorsmod.Wrapf(ErrInvalidProof, "commitment proof must be existence proof. got: %T at index %d", i, proofs[i]) + } + + // verify membership of the proof at this index with appropriate key and value + if err := ep.Verify(specs[i], subroot, key, value); err != nil { + return errorsmod.Wrapf(ErrInvalidProof, "failed to verify membership proof at index %d: %v", i, err) + } + // Set value to subroot so that we verify next proof in chain commits to this subroot + value = subroot + } + + // Check that chained proof root equals passed-in root + if !bytes.Equal(root, subroot) { + return errorsmod.Wrapf(ErrInvalidProof, "proof did not commit to expected root: %X, got: %X. Please ensure proof was submitted with correct proofHeight and to the correct chain.", root, subroot) + } + + return nil +} + +// validateVerificationArgs verifies the proof arguments are valid. +// The merkle path and merkle proof contain a list of keys and their proofs +// which correspond to individual trees. The length of these keys and their proofs +// must equal the length of the given specs. All arguments must be non-empty. +func validateVerificationArgs(proof MerkleProof, path v2.MerklePath, specs []*ics23.ProofSpec, root exported.Root) error { + if proof.GetProofs() == nil { + return errorsmod.Wrap(ErrInvalidMerkleProof, "proof must not be empty") + } + + if root == nil || root.Empty() { + return errorsmod.Wrap(ErrInvalidMerkleProof, "root cannot be empty") + } + + if len(specs) != len(proof.Proofs) { + return errorsmod.Wrapf(ErrInvalidMerkleProof, "length of specs: %d not equal to length of proof: %d", len(specs), len(proof.Proofs)) + } + + if len(path.KeyPath) != len(specs) { + return errorsmod.Wrapf(ErrInvalidProof, "path length %d not same as proof %d", len(path.KeyPath), len(specs)) + } + + for i, spec := range specs { + if spec == nil { + return errorsmod.Wrapf(ErrInvalidProof, "spec at position %d is nil", i) + } + } + return nil +} diff --git a/modules/core/23-commitment/types/merkle_test.go b/modules/core/23-commitment/types/merkle_test.go new file mode 100644 index 0000000..8b20d63 --- /dev/null +++ b/modules/core/23-commitment/types/merkle_test.go @@ -0,0 +1,155 @@ +package types_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types/v2" +) + +func (suite *MerkleTestSuite) TestVerifyMembership() { + suite.iavlStore.Set([]byte("MYKEY"), []byte("MYVALUE")) + cid := suite.store.Commit() + + res, err := suite.store.Query(&storetypes.RequestQuery{ + Path: fmt.Sprintf("/%s/key", suite.storeKey.Name()), // required path to get key/value+proof + Data: []byte("MYKEY"), + Prove: true, + }) + require.NoError(suite.T(), err) + require.NotNil(suite.T(), res.ProofOps) + + proof, err := types.ConvertProofs(res.ProofOps) + require.NoError(suite.T(), err) + + cases := []struct { + name string + root []byte + pathArr [][]byte + value []byte + malleate func() + shouldPass bool + }{ + {"valid proof", cid.Hash, [][]byte{[]byte(suite.storeKey.Name()), []byte("MYKEY")}, []byte("MYVALUE"), func() {}, true}, // valid proof + {"wrong value", cid.Hash, [][]byte{[]byte(suite.storeKey.Name()), []byte("MYKEY")}, []byte("WRONGVALUE"), func() {}, false}, // invalid proof with wrong value + {"nil value", cid.Hash, [][]byte{[]byte(suite.storeKey.Name()), []byte("MYKEY")}, []byte(nil), func() {}, false}, // invalid proof with nil value + {"wrong key", cid.Hash, [][]byte{[]byte(suite.storeKey.Name()), []byte("NOTMYKEY")}, []byte("MYVALUE"), func() {}, false}, // invalid proof with wrong key + {"wrong path 1", cid.Hash, [][]byte{[]byte(suite.storeKey.Name()), []byte("MYKEY"), []byte("MYKEY")}, []byte("MYVALUE"), func() {}, false}, // invalid proof with wrong path + {"wrong path 2", cid.Hash, [][]byte{[]byte(suite.storeKey.Name())}, []byte("MYVALUE"), func() {}, false}, // invalid proof with wrong path + {"wrong path 3", cid.Hash, [][]byte{[]byte("MYKEY")}, []byte("MYVALUE"), func() {}, false}, // invalid proof with wrong path + {"wrong storekey", cid.Hash, [][]byte{[]byte("otherStoreKey"), []byte("MYKEY")}, []byte("MYVALUE"), func() {}, false}, // invalid proof with wrong store prefix + {"wrong root", []byte("WRONGROOT"), [][]byte{[]byte(suite.storeKey.Name()), []byte("MYKEY")}, []byte("MYVALUE"), func() {}, false}, // invalid proof with wrong root + {"nil root", []byte(nil), [][]byte{[]byte(suite.storeKey.Name()), []byte("MYKEY")}, []byte("MYVALUE"), func() {}, false}, // invalid proof with nil root + {"proof is wrong length", cid.Hash, [][]byte{[]byte(suite.storeKey.Name()), []byte("MYKEY")}, []byte("MYVALUE"), func() { + proof = types.MerkleProof{ + Proofs: proof.Proofs[1:], + } + }, false}, // invalid proof with wrong length + + } + + for i, tc := range cases { + suite.Run(tc.name, func() { + tc.malleate() + + root := types.NewMerkleRoot(tc.root) + path := types.NewMerklePath(tc.pathArr...) + + err := proof.VerifyMembership(types.GetSDKSpecs(), &root, path, tc.value) + + if tc.shouldPass { + // nolint: scopelint + suite.Require().NoError(err, "test case %d should have passed", i) + } else { + // nolint: scopelint + suite.Require().Error(err, "test case %d should have failed", i) + } + }) + } +} + +func (suite *MerkleTestSuite) TestVerifyNonMembership() { + suite.iavlStore.Set([]byte("MYKEY"), []byte("MYVALUE")) + cid := suite.store.Commit() + + // Get Proof + res, err := suite.store.Query(&storetypes.RequestQuery{ + Path: fmt.Sprintf("/%s/key", suite.storeKey.Name()), // required path to get key/value+proof + Data: []byte("MYABSENTKEY"), + Prove: true, + }) + require.NoError(suite.T(), err) + require.NotNil(suite.T(), res.ProofOps) + + proof, err := types.ConvertProofs(res.ProofOps) + require.NoError(suite.T(), err) + + cases := []struct { + name string + root []byte + pathArr [][]byte + malleate func() + shouldPass bool + }{ + {"valid proof", cid.Hash, [][]byte{[]byte(suite.storeKey.Name()), []byte("MYABSENTKEY")}, func() {}, true}, // valid proof + {"wrong key", cid.Hash, [][]byte{[]byte(suite.storeKey.Name()), []byte("MYKEY")}, func() {}, false}, // invalid proof with existent key + {"wrong path 1", cid.Hash, [][]byte{[]byte(suite.storeKey.Name()), []byte("MYKEY"), []byte("MYABSENTKEY")}, func() {}, false}, // invalid proof with wrong path + {"wrong path 2", cid.Hash, [][]byte{[]byte(suite.storeKey.Name()), []byte("MYABSENTKEY"), []byte("MYKEY")}, func() {}, false}, // invalid proof with wrong path + {"wrong path 3", cid.Hash, [][]byte{[]byte(suite.storeKey.Name())}, func() {}, false}, // invalid proof with wrong path + {"wrong path 4", cid.Hash, [][]byte{[]byte("MYABSENTKEY")}, func() {}, false}, // invalid proof with wrong path + {"wrong storeKey", cid.Hash, [][]byte{[]byte("otherStoreKey"), []byte("MYABSENTKEY")}, func() {}, false}, // invalid proof with wrong store prefix + {"wrong root", []byte("WRONGROOT"), [][]byte{[]byte(suite.storeKey.Name()), []byte("MYABSENTKEY")}, func() {}, false}, // invalid proof with wrong root + {"nil root", []byte(nil), [][]byte{[]byte(suite.storeKey.Name()), []byte("MYABSENTKEY")}, func() {}, false}, // invalid proof with nil root + {"proof is wrong length", cid.Hash, [][]byte{[]byte(suite.storeKey.Name()), []byte("MYKEY")}, func() { + proof = types.MerkleProof{ + Proofs: proof.Proofs[1:], + } + }, false}, // invalid proof with wrong length + + } + + for i, tc := range cases { + suite.Run(tc.name, func() { + tc.malleate() + + root := types.NewMerkleRoot(tc.root) + path := types.NewMerklePath(tc.pathArr...) + + err := proof.VerifyNonMembership(types.GetSDKSpecs(), &root, path) + + if tc.shouldPass { + // nolint: scopelint + suite.Require().NoError(err, "test case %d should have passed", i) + } else { + // nolint: scopelint + suite.Require().Error(err, "test case %d should have failed", i) + } + }) + } +} + +func TestApplyPrefix(t *testing.T) { + prefix := types.NewMerklePrefix([]byte("storePrefixKey")) + + pathBz := []byte("pathone/pathtwo/paththree/key") + path := v2.MerklePath{ + KeyPath: [][]byte{pathBz}, + } + + prefixedPath, err := types.ApplyPrefix(prefix, path) + require.NoError(t, err, "valid prefix returns error") + require.Len(t, prefixedPath.GetKeyPath(), 2, "unexpected key path length") + + key0, err := prefixedPath.GetKey(0) + require.NoError(t, err, "get key 0 returns error") + require.Equal(t, prefix.KeyPrefix, key0, "key 0 does not match expected value") + + key1, err := prefixedPath.GetKey(1) + require.NoError(t, err, "get key 1 returns error") + require.Equal(t, pathBz, key1, "key 1 does not match expected value") +} diff --git a/modules/core/23-commitment/types/utils.go b/modules/core/23-commitment/types/utils.go new file mode 100644 index 0000000..00cdf68 --- /dev/null +++ b/modules/core/23-commitment/types/utils.go @@ -0,0 +1,29 @@ +package types + +import ( + ics23 "github.com/cosmos/ics23/go" + + errorsmod "cosmossdk.io/errors" + + "github.com/cometbft/cometbft/proto/tendermint/crypto" +) + +// ConvertProofs converts crypto.ProofOps into MerkleProof +func ConvertProofs(tmProof *crypto.ProofOps) (MerkleProof, error) { + if tmProof == nil { + return MerkleProof{}, errorsmod.Wrapf(ErrInvalidMerkleProof, "tendermint proof is nil") + } + // Unmarshal all proof ops to CommitmentProof + proofs := make([]*ics23.CommitmentProof, len(tmProof.Ops)) + for i, op := range tmProof.Ops { + var p ics23.CommitmentProof + err := p.Unmarshal(op.Data) + if err != nil || p.Proof == nil { + return MerkleProof{}, errorsmod.Wrapf(ErrInvalidMerkleProof, "could not unmarshal proof op into CommitmentProof at index %d: %v", i, err) + } + proofs[i] = &p + } + return MerkleProof{ + Proofs: proofs, + }, nil +} diff --git a/modules/core/23-commitment/types/utils_test.go b/modules/core/23-commitment/types/utils_test.go new file mode 100644 index 0000000..2d13b44 --- /dev/null +++ b/modules/core/23-commitment/types/utils_test.go @@ -0,0 +1,105 @@ +package types_test + +import ( + "fmt" + + "github.com/stretchr/testify/require" + + storetypes "cosmossdk.io/store/types" + + "github.com/cometbft/cometbft/proto/tendermint/crypto" + + "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" +) + +func (suite *MerkleTestSuite) TestConvertProofs() { + suite.iavlStore.Set([]byte("MYKEY"), []byte("MYVALUE")) + cid := suite.store.Commit() + + root := types.NewMerkleRoot(cid.Hash) + existsPath := types.NewMerklePath([]byte(suite.storeKey.Name()), []byte("MYKEY")) + nonexistPath := types.NewMerklePath([]byte(suite.storeKey.Name()), []byte("NOTMYKEY")) + value := []byte("MYVALUE") + + var proofOps *crypto.ProofOps + testcases := []struct { + name string + malleate func() + keyExists bool + expErr error + }{ + { + "success for ExistenceProof", + func() { + res, err := suite.store.Query(&storetypes.RequestQuery{ + Path: fmt.Sprintf("/%s/key", suite.storeKey.Name()), // required path to get key/value+proof + Data: []byte("MYKEY"), + Prove: true, + }) + require.NoError(suite.T(), err) + require.NotNil(suite.T(), res.ProofOps) + + proofOps = res.ProofOps + }, + true, nil, + }, + { + "success for NonexistenceProof", + func() { + res, err := suite.store.Query(&storetypes.RequestQuery{ + Path: fmt.Sprintf("/%s/key", suite.storeKey.Name()), + Data: []byte("NOTMYKEY"), + Prove: true, + }) + require.NoError(suite.T(), err) + require.NotNil(suite.T(), res.ProofOps) + + proofOps = res.ProofOps + }, + false, nil, + }, + { + "nil proofOps", + func() { + proofOps = nil + }, + true, types.ErrInvalidMerkleProof, + }, + { + "proof op data is nil", + func() { + res, err := suite.store.Query(&storetypes.RequestQuery{ + Path: fmt.Sprintf("/%s/key", suite.storeKey.Name()), // required path to get key/value+proof + Data: []byte("MYKEY"), + Prove: true, + }) + require.NoError(suite.T(), err) + require.NotNil(suite.T(), res.ProofOps) + + proofOps = res.ProofOps + proofOps.Ops[0].Data = nil + }, + true, types.ErrInvalidMerkleProof, + }, + } + + for _, tc := range testcases { + + tc.malleate() + + proof, err := types.ConvertProofs(proofOps) + if tc.expErr == nil { + suite.Require().NoError(err, "ConvertProofs unexpectedly returned error for case: %s", tc.name) + if tc.keyExists { + err := proof.VerifyMembership(types.GetSDKSpecs(), &root, existsPath, value) + suite.Require().NoError(err, "converted proof failed to verify membership for case: %s", tc.name) + } else { + err := proof.VerifyNonMembership(types.GetSDKSpecs(), &root, nonexistPath) + suite.Require().NoError(err, "converted proof failed to verify non-membership for case: %s", tc.name) + } + } else { + suite.Require().Error(err, "ConvertProofs passed on invalid case for case: %s", tc.name) + suite.Require().ErrorIs(err, tc.expErr, "unexpected error returned for case: %s", tc.name) + } + } +} diff --git a/modules/core/23-commitment/types/v2/commitment.pb.go b/modules/core/23-commitment/types/v2/commitment.pb.go new file mode 100644 index 0000000..1f1e2ac --- /dev/null +++ b/modules/core/23-commitment/types/v2/commitment.pb.go @@ -0,0 +1,353 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/commitment/v2/commitment.proto + +package v2 + +import ( + fmt "fmt" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// MerklePath is the path used to verify commitment proofs, which can be an +// arbitrary structured object (defined by a commitment type). +// ICS-23 verification supports membership proofs for nested merkle trees. +// The ICS-24 standard provable keys MUST be stored in the lowest level tree with an optional prefix. +// The IC24 provable tree may then be stored in a higher level tree(s) that hash up to the root hash +// stored in the consensus state of the client. +// Each element of the path represents the key of a merkle tree from the root to the leaf. +// The elements of the path before the final element must be the path to the tree that contains +// the ICS24 provable store. Thus, it should remain constant for all ICS24 proofs. +// The final element of the path is the key of the leaf in the ICS24 provable store, +// Thus IBC core will append the ICS24 path to the final element of the MerklePath +// stored in the counterparty to create the full path to the leaf for proof verification. +// Examples: +// Cosmos SDK: +// The Cosmos SDK commits to a multi-tree where each store is an IAVL tree and all store hashes +// are hashed in a simple merkle tree to get the final root hash. Thus, the MerklePath in the counterparty +// MerklePrefix has the following structure: ["ibc", ""] +// The core IBC handler will append the ICS24 path to the final element of the MerklePath +// like so: ["ibc", "{packetCommitmentPath}"] which will then be used for final verification. +// Ethereum: +// The Ethereum client commits to a single Patricia merkle trie. The ICS24 provable store is managed +// by the smart contract state. Each smart contract has a specific prefix reserved within the global trie. +// Thus the MerklePath in the counterparty is the prefix to the smart contract state in the global trie. +// Since there is only one tree in the commitment structure of ethereum the MerklePath in the counterparty +// MerklePrefix has the following structure: ["IBCCoreContractAddressStoragePrefix"] +// The core IBC handler will append the ICS24 path to the final element of the MerklePath +// like so: ["IBCCoreContractAddressStoragePrefix{packetCommitmentPath}"] which will then be used for final +// verification. Thus the MerklePath in the counterparty MerklePrefix is the nested key path from the root hash of the +// consensus state down to the ICS24 provable store. The IBC handler retrieves the counterparty key path to the ICS24 +// provable store from the MerklePath and appends the ICS24 path to get the final key path to the value being verified +// by the client against the root hash in the client's consensus state. +type MerklePath struct { + KeyPath [][]byte `protobuf:"bytes,1,rep,name=key_path,json=keyPath,proto3" json:"key_path,omitempty"` +} + +func (m *MerklePath) Reset() { *m = MerklePath{} } +func (m *MerklePath) String() string { return proto.CompactTextString(m) } +func (*MerklePath) ProtoMessage() {} +func (*MerklePath) Descriptor() ([]byte, []int) { + return fileDescriptor_8f65a9eb5e4ee5fc, []int{0} +} +func (m *MerklePath) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MerklePath) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MerklePath.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MerklePath) XXX_Merge(src proto.Message) { + xxx_messageInfo_MerklePath.Merge(m, src) +} +func (m *MerklePath) XXX_Size() int { + return m.Size() +} +func (m *MerklePath) XXX_DiscardUnknown() { + xxx_messageInfo_MerklePath.DiscardUnknown(m) +} + +var xxx_messageInfo_MerklePath proto.InternalMessageInfo + +func (m *MerklePath) GetKeyPath() [][]byte { + if m != nil { + return m.KeyPath + } + return nil +} + +func init() { + proto.RegisterType((*MerklePath)(nil), "ibc.core.commitment.v2.MerklePath") +} + +func init() { + proto.RegisterFile("ibc/core/commitment/v2/commitment.proto", fileDescriptor_8f65a9eb5e4ee5fc) +} + +var fileDescriptor_8f65a9eb5e4ee5fc = []byte{ + // 192 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xcf, 0x4c, 0x4a, 0xd6, + 0x4f, 0xce, 0x2f, 0x4a, 0xd5, 0x4f, 0xce, 0xcf, 0xcd, 0xcd, 0x2c, 0xc9, 0x4d, 0xcd, 0x2b, 0xd1, + 0x2f, 0x33, 0x42, 0xe2, 0xe9, 0x15, 0x14, 0xe5, 0x97, 0xe4, 0x0b, 0x89, 0x65, 0x26, 0x25, 0xeb, + 0x81, 0x14, 0xea, 0x21, 0x49, 0x95, 0x19, 0x29, 0xa9, 0x73, 0x71, 0xf9, 0xa6, 0x16, 0x65, 0xe7, + 0xa4, 0x06, 0x24, 0x96, 0x64, 0x08, 0x49, 0x72, 0x71, 0x64, 0xa7, 0x56, 0xc6, 0x17, 0x24, 0x96, + 0x64, 0x48, 0x30, 0x2a, 0x30, 0x6b, 0xf0, 0x04, 0xb1, 0x67, 0xa7, 0x56, 0x82, 0xa4, 0x9c, 0xa2, + 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, + 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0xca, 0x21, 0x3d, 0xb3, 0x24, 0xa3, + 0x34, 0x09, 0x64, 0xb0, 0x7e, 0x72, 0x7e, 0x71, 0x6e, 0x7e, 0xb1, 0x7e, 0x66, 0x52, 0xb2, 0x6e, + 0x7a, 0xbe, 0x7e, 0x99, 0xa1, 0x81, 0x7e, 0x6e, 0x7e, 0x4a, 0x69, 0x4e, 0x6a, 0x31, 0xc4, 0x91, + 0x46, 0xc6, 0xba, 0x48, 0xee, 0x2c, 0xa9, 0x2c, 0x48, 0x2d, 0xd6, 0x2f, 0x33, 0x4a, 0x62, 0x03, + 0xbb, 0xd1, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x01, 0x78, 0xcd, 0xbf, 0xce, 0x00, 0x00, 0x00, +} + +func (m *MerklePath) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MerklePath) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MerklePath) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.KeyPath) > 0 { + for iNdEx := len(m.KeyPath) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.KeyPath[iNdEx]) + copy(dAtA[i:], m.KeyPath[iNdEx]) + i = encodeVarintCommitment(dAtA, i, uint64(len(m.KeyPath[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintCommitment(dAtA []byte, offset int, v uint64) int { + offset -= sovCommitment(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MerklePath) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.KeyPath) > 0 { + for _, b := range m.KeyPath { + l = len(b) + n += 1 + l + sovCommitment(uint64(l)) + } + } + return n +} + +func sovCommitment(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozCommitment(x uint64) (n int) { + return sovCommitment(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MerklePath) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommitment + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MerklePath: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MerklePath: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field KeyPath", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommitment + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthCommitment + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthCommitment + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.KeyPath = append(m.KeyPath, make([]byte, postIndex-iNdEx)) + copy(m.KeyPath[len(m.KeyPath)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipCommitment(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthCommitment + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipCommitment(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowCommitment + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowCommitment + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowCommitment + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthCommitment + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupCommitment + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthCommitment + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthCommitment = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowCommitment = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupCommitment = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/core/23-commitment/types/v2/merkle.go b/modules/core/23-commitment/types/v2/merkle.go new file mode 100644 index 0000000..0b932f0 --- /dev/null +++ b/modules/core/23-commitment/types/v2/merkle.go @@ -0,0 +1,73 @@ +package v2 + +import ( + "errors" + "fmt" + + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var _ exported.Path = (*MerklePath)(nil) + +// NewMerklePath creates a new MerklePath instance +// The keys must be passed in from root-to-leaf order +func NewMerklePath(keyPath ...[]byte) MerklePath { + return MerklePath{ + KeyPath: keyPath, + } +} + +// GetKey will return a byte representation of the key +func (mp MerklePath) GetKey(i uint64) ([]byte, error) { + if i >= uint64(len(mp.KeyPath)) { + return nil, fmt.Errorf("index out of range. %d (index) >= %d (len)", i, len(mp.KeyPath)) + } + return mp.KeyPath[i], nil +} + +// Empty returns true if the path is empty +func (mp MerklePath) Empty() bool { + return len(mp.KeyPath) == 0 +} + +// ValidateAsPrefix validates the MerklePath to ensure it is a valid prefix +// Thus every element of the merkle path must be non-empty except for the last element +// which may be empty. In this case, the ICS24 path will be appended to the last element +// to form the full path. +// This is the MerklePath being stored in the CounterpartyInfo +// It is interpreted as a prefix to the full path in particular it is the keypath +// from the root to the provable ICS24 store. +// Since it is not the full path to a leaf, the last element may be empty. +// This can occur if the commitment structure is a nested merkle tree and the ICS24 +// store is itself a merkle tree. +func (mp MerklePath) ValidateAsPrefix() error { + if mp.Empty() { + return errors.New("path cannot have length 0") + } + + for i, key := range mp.KeyPath { + if len(key) == 0 && i != len(mp.KeyPath)-1 { + return fmt.Errorf("key at index %d cannot be empty", i) + } + } + return nil +} + +// ValidateAsPath validates the MerklePath as a fully constructed path. +// Here every element must be non-empty since the MerklePath is no longer +// acting as a prefix but is instead the full path intended for verification. +// This is the full path to a leaf in the commitment tree constructed by IBC handler +// and it will be passed to the client for verification. Thus, at this point +// every element must be non-empty. +func (mp MerklePath) ValidateAsPath() error { + if mp.Empty() { + return errors.New("path cannot have length 0") + } + + for i, key := range mp.KeyPath { + if len(key) == 0 { + return fmt.Errorf("key at index %d cannot be empty", i) + } + } + return nil +} diff --git a/modules/core/23-commitment/types/v2/merkle_test.go b/modules/core/23-commitment/types/v2/merkle_test.go new file mode 100644 index 0000000..e5460a8 --- /dev/null +++ b/modules/core/23-commitment/types/v2/merkle_test.go @@ -0,0 +1,64 @@ +package v2 + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMerklePathValidation(t *testing.T) { + cases := []struct { + name string + path MerklePath + expPrefixErr error + expPathErr error + }{ + { + "success: prefix and path", + NewMerklePath([]byte("key1"), []byte("key2")), + nil, + nil, + }, + { + "prefix with empty last key", + NewMerklePath([]byte("key1"), []byte("")), + nil, + errors.New("key at index 1 cannot be empty"), + }, + { + "prefix with single empty key", + NewMerklePath([]byte("")), + nil, + errors.New("key at index 0 cannot be empty"), + }, + { + "failure: empty path", + NewMerklePath(), + errors.New("path cannot have length 0"), + errors.New("path cannot have length 0"), + }, + { + "failure: prefix with empty first key", + NewMerklePath([]byte(""), []byte("key2")), + errors.New("key at index 0 cannot be empty"), + errors.New("key at index 0 cannot be empty"), + }, + } + + for _, tc := range cases { + err := tc.path.ValidateAsPrefix() + if tc.expPrefixErr == nil { + require.NoError(t, err, tc.name) + } else { + require.ErrorContains(t, err, tc.expPrefixErr.Error(), tc.name) + } + + err = tc.path.ValidateAsPath() + if tc.expPathErr == nil { + require.NoError(t, err, tc.name) + } else { + require.ErrorContains(t, err, tc.expPathErr.Error(), tc.name) + } + } +} diff --git a/modules/core/24-host/channel_keys.go b/modules/core/24-host/channel_keys.go new file mode 100644 index 0000000..d54b8ad --- /dev/null +++ b/modules/core/24-host/channel_keys.go @@ -0,0 +1,20 @@ +package host + +import "fmt" + +const ( + KeyChannelEndPrefix = "channelEnds" + KeyChannelPrefix = "channels" +) + +// ICS04 +// The following paths are the keys to the store as defined in https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#store-paths + +// ChannelKey returns the store key for a particular channel +func ChannelKey(portID, channelID string) []byte { + return fmt.Appendf(nil, "%s/%s", KeyChannelEndPrefix, channelPath(portID, channelID)) +} + +func channelPath(portID, channelID string) string { + return fmt.Sprintf("%s/%s/%s/%s", KeyPortPrefix, portID, KeyChannelPrefix, channelID) +} diff --git a/modules/core/24-host/client_keys.go b/modules/core/24-host/client_keys.go new file mode 100644 index 0000000..58f2467 --- /dev/null +++ b/modules/core/24-host/client_keys.go @@ -0,0 +1,52 @@ +package host + +import ( + "fmt" + + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// KeyClientStorePrefix defines the KVStore key prefix for IBC clients +var KeyClientStorePrefix = []byte("clients") + +const ( + KeyClientState = "clientState" + KeyConsensusStatePrefix = "consensusStates" +) + +// FullClientKey returns the full path of specific client path in the format: +// "clients/{clientID}/{path}" as a byte array. +func FullClientKey(clientID string, path []byte) []byte { + return fmt.Appendf(nil, "%s/%s/%s", KeyClientStorePrefix, clientID, path) +} + +// PrefixedClientStoreKey returns a key which can be used for prefixed +// key store iteration. The prefix may be a clientType, clientID, or any +// valid key prefix which may be concatenated with the client store constant. +func PrefixedClientStoreKey(prefix []byte) []byte { + return fmt.Appendf(nil, "%s/%s", KeyClientStorePrefix, prefix) +} + +// FullClientStateKey takes a client identifier and returns a Key under which to store a +// particular client state. +func FullClientStateKey(clientID string) []byte { + return FullClientKey(clientID, []byte(KeyClientState)) +} + +// ClientStateKey returns a store key under which a particular client state is stored +// in a client prefixed store +func ClientStateKey() []byte { + return []byte(KeyClientState) +} + +// FullConsensusStateKey returns the store key for the consensus state of a particular +// client. +func FullConsensusStateKey(clientID string, height exported.Height) []byte { + return FullClientKey(clientID, ConsensusStateKey(height)) +} + +// ConsensusStateKey returns the store key for a the consensus state of a particular +// client stored in a client prefixed store. +func ConsensusStateKey(height exported.Height) []byte { + return fmt.Appendf(nil, "%s/%s", KeyConsensusStatePrefix, height) +} diff --git a/modules/core/24-host/connection_keys.go b/modules/core/24-host/connection_keys.go new file mode 100644 index 0000000..0d0f287 --- /dev/null +++ b/modules/core/24-host/connection_keys.go @@ -0,0 +1,18 @@ +package host + +import "fmt" + +const KeyConnectionPrefix = "connections" + +// ICS03 +// The following paths are the keys to the store as defined in https://github.com/cosmos/ibc/blob/master/spec/core/ics-003-connection-semantics#store-paths + +// ClientConnectionsKey returns the store key for the connections of a given client +func ClientConnectionsKey(clientID string) []byte { + return FullClientKey(clientID, []byte(KeyConnectionPrefix)) +} + +// ConnectionKey returns the store key for a particular connection +func ConnectionKey(connectionID string) []byte { + return fmt.Appendf(nil, "%s/%s", KeyConnectionPrefix, connectionID) +} diff --git a/modules/core/24-host/doc.go b/modules/core/24-host/doc.go new file mode 100644 index 0000000..10e7dd2 --- /dev/null +++ b/modules/core/24-host/doc.go @@ -0,0 +1,8 @@ +/* +24-host is an implementation of ICS 24. + +The storage path supported are defined in ICS 24 (https://github.com/cosmos/ibc/blob/master/spec/core/ics-024-host-requirements#path-space). + +Hostname validation is implemented as defined in ICS 24 (https://github.com/cosmos/ibc/tree/master/spec/core/ics-024-host-requirements). +*/ +package host diff --git a/modules/core/24-host/errors.go b/modules/core/24-host/errors.go new file mode 100644 index 0000000..7443f99 --- /dev/null +++ b/modules/core/24-host/errors.go @@ -0,0 +1,15 @@ +package host + +import ( + errorsmod "cosmossdk.io/errors" +) + +// SubModuleName defines the ICS 24 host +const SubModuleName = "host" + +// IBC client sentinel errors +var ( + ErrInvalidID = errorsmod.Register(SubModuleName, 2, "invalid identifier") + ErrInvalidPath = errorsmod.Register(SubModuleName, 3, "invalid path") + ErrInvalidPacket = errorsmod.Register(SubModuleName, 4, "invalid packet") +) diff --git a/modules/core/24-host/packet_keys.go b/modules/core/24-host/packet_keys.go new file mode 100644 index 0000000..241bc91 --- /dev/null +++ b/modules/core/24-host/packet_keys.go @@ -0,0 +1,72 @@ +package host + +import "fmt" + +const ( + KeySequencePrefix = "sequences" + KeyNextSeqSendPrefix = "nextSequenceSend" + KeyNextSeqRecvPrefix = "nextSequenceRecv" + KeyNextSeqAckPrefix = "nextSequenceAck" + KeyPacketCommitmentPrefix = "commitments" + KeyPacketAckPrefix = "acks" + KeyPacketReceiptPrefix = "receipts" + KeyRecvStartSequence = "recvStartSequence" +) + +// ICS04 +// The following paths are the keys to the store as defined in https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#store-paths + +// NextSequenceSendKey returns the store key for the send sequence of a particular +// channel binded to a specific port. +func NextSequenceSendKey(portID, channelID string) []byte { + return fmt.Appendf(nil, "%s/%s", KeyNextSeqSendPrefix, channelPath(portID, channelID)) +} + +// NextSequenceRecvKey returns the store key for the receive sequence of a particular +// channel binded to a specific port +func NextSequenceRecvKey(portID, channelID string) []byte { + return fmt.Appendf(nil, "%s/%s", KeyNextSeqRecvPrefix, channelPath(portID, channelID)) +} + +// NextSequenceAckKey returns the store key for the acknowledgement sequence of +// a particular channel binded to a specific port. +func NextSequenceAckKey(portID, channelID string) []byte { + return fmt.Appendf(nil, "%s/%s", KeyNextSeqAckPrefix, channelPath(portID, channelID)) +} + +// PacketCommitmentKey returns the store key of under which a packet commitment +// is stored +func PacketCommitmentKey(portID, channelID string, sequence uint64) []byte { + return fmt.Appendf(nil, "%s/%d", PacketCommitmentPrefixKey(portID, channelID), sequence) +} + +// PacketCommitmentPrefixKey defines the prefix for commitments to packet data fields store path. +func PacketCommitmentPrefixKey(portID, channelID string) []byte { + return fmt.Appendf(nil, "%s/%s/%s", KeyPacketCommitmentPrefix, channelPath(portID, channelID), KeySequencePrefix) +} + +// PacketAcknowledgementKey returns the store key of under which a packet +// acknowledgement is stored +func PacketAcknowledgementKey(portID, channelID string, sequence uint64) []byte { + return fmt.Appendf(nil, "%s/%d", PacketAcknowledgementPrefixKey(portID, channelID), sequence) +} + +// PacketAcknowledgementPrefixKey defines the prefix for commitments to packet data fields store path. +func PacketAcknowledgementPrefixKey(portID, channelID string) []byte { + return fmt.Appendf(nil, "%s/%s/%s", KeyPacketAckPrefix, channelPath(portID, channelID), KeySequencePrefix) +} + +// PacketReceiptKey returns the store key of under which a packet +// receipt is stored +func PacketReceiptKey(portID, channelID string, sequence uint64) []byte { + return fmt.Appendf(nil, "%s/%s/%s", KeyPacketReceiptPrefix, channelPath(portID, channelID), sequencePath(sequence)) +} + +// RecvStartSequenceKey returns the store key for the recv start sequence of a particular channel +func RecvStartSequenceKey(portID, channelID string) []byte { + return fmt.Appendf(nil, "%s/%s", KeyRecvStartSequence, channelPath(portID, channelID)) +} + +func sequencePath(sequence uint64) string { + return fmt.Sprintf("%s/%d", KeySequencePrefix, sequence) +} diff --git a/modules/core/24-host/parse.go b/modules/core/24-host/parse.go new file mode 100644 index 0000000..0b4b32e --- /dev/null +++ b/modules/core/24-host/parse.go @@ -0,0 +1,120 @@ +package host + +import ( + "strconv" + "strings" + + errorsmod "cosmossdk.io/errors" +) + +// ParseIdentifier parses the sequence from the identifier using the provided prefix. This function +// does not need to be used by counterparty chains. SDK generated connection and channel identifiers +// are required to use this format. +func ParseIdentifier(identifier, prefix string) (uint64, error) { + if !strings.HasPrefix(identifier, prefix) { + return 0, errorsmod.Wrapf(ErrInvalidID, "identifier doesn't contain prefix `%s`", prefix) + } + + splitStr := strings.Split(identifier, prefix) + if len(splitStr) != 2 { + return 0, errorsmod.Wrapf(ErrInvalidID, "identifier must be in format: `%s{N}`", prefix) + } + + // sanity check + if splitStr[0] != "" { + return 0, errorsmod.Wrapf(ErrInvalidID, "identifier must begin with prefix %s", prefix) + } + + sequence, err := strconv.ParseUint(splitStr[1], 10, 64) + if err != nil { + return 0, errorsmod.Wrap(err, "failed to parse identifier sequence") + } + return sequence, nil +} + +// MustParseClientStatePath returns the client ID from a client state path. It panics +// if the provided path is invalid or if the clientID is empty. +func MustParseClientStatePath(path string) string { + clientID, err := parseClientStatePath(path) + if err != nil { + panic(err.Error()) + } + + return clientID +} + +// parseClientStatePath returns the client ID from a client state path. It returns +// an error if the provided path is invalid. +func parseClientStatePath(path string) (string, error) { + split := strings.Split(path, "/") + if len(split) != 3 { + return "", errorsmod.Wrapf(ErrInvalidPath, "cannot parse client state path %s", path) + } + + if split[0] != string(KeyClientStorePrefix) { + return "", errorsmod.Wrapf(ErrInvalidPath, "path does not begin with client store prefix: expected %s, got %s", KeyClientStorePrefix, split[0]) + } + + if split[2] != KeyClientState { + return "", errorsmod.Wrapf(ErrInvalidPath, "path does not end with client state key: expected %s, got %s", KeyClientState, split[2]) + } + + if strings.TrimSpace(split[1]) == "" { + return "", errorsmod.Wrap(ErrInvalidPath, "clientID is empty") + } + + clientID := split[1] + + return clientID, nil +} + +// ParseConnectionPath returns the connection ID from a full path. It returns +// an error if the provided path is invalid. +func ParseConnectionPath(path string) (string, error) { + split := strings.Split(path, "/") + if len(split) != 2 { + return "", errorsmod.Wrapf(ErrInvalidPath, "cannot parse connection path %s", path) + } + + connectionID := split[1] + + return connectionID, nil +} + +// ParseChannelPath returns the port and channel ID from a full path. It returns +// an error if the provided path is invalid. +func ParseChannelPath(path string) (string, string, error) { + split := strings.Split(path, "/") + if len(split) < 5 { + return "", "", errorsmod.Wrapf(ErrInvalidPath, "cannot parse channel path %s", path) + } + + if split[1] != KeyPortPrefix || split[3] != KeyChannelPrefix { + return "", "", errorsmod.Wrapf(ErrInvalidPath, "cannot parse channel path %s", path) + } + + portID := split[2] + channelID := split[4] + + return portID, channelID, nil +} + +// MustParseConnectionPath returns the connection ID from a full path. Panics +// if the provided path is invalid. +func MustParseConnectionPath(path string) string { + connectionID, err := ParseConnectionPath(path) + if err != nil { + panic(err) + } + return connectionID +} + +// MustParseChannelPath returns the port and channel ID from a full path. Panics +// if the provided path is invalid. +func MustParseChannelPath(path string) (string, string) { + portID, channelID, err := ParseChannelPath(path) + if err != nil { + panic(err) + } + return portID, channelID +} diff --git a/modules/core/24-host/parse_test.go b/modules/core/24-host/parse_test.go new file mode 100644 index 0000000..4ebe8cf --- /dev/null +++ b/modules/core/24-host/parse_test.go @@ -0,0 +1,106 @@ +package host_test + +import ( + "errors" + "fmt" + "math" + "testing" + + "github.com/stretchr/testify/require" + + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func TestParseIdentifier(t *testing.T) { + testCases := []struct { + name string + identifier string + prefix string + expSeq uint64 + expErr error + }{ + {"valid 0", "connection-0", "connection-", 0, nil}, + {"valid 1", "connection-1", "connection-", 1, nil}, + {"valid large sequence", connectiontypes.FormatConnectionIdentifier(math.MaxUint64), "connection-", math.MaxUint64, nil}, + // one above uint64 max + {"invalid uint64", "connection-18446744073709551616", "connection-", 0, errors.New("the value '18446744073709551616' cannot be parsed as a valid uint64")}, + // uint64 == 20 characters + {"invalid large sequence", "connection-2345682193567182931243", "connection-", 0, errors.New("the sequence number '2345682193567182931243' exceeds the valid range for a uint64")}, + {"capital prefix", "Connection-0", "connection-", 0, errors.New("the prefix 'Connection' should be in lowercase")}, + {"double prefix", "connection-connection-0", "connection-", 0, errors.New("only a single 'connection-' prefix is allowed")}, + {"doesn't have prefix", "connection-0", "prefix", 0, errors.New("the connection ID is missing the required prefix 'connection-'")}, + {"missing dash", "connection0", "connection-", 0, errors.New("the connection ID is missing the dash ('-') between the prefix 'connection' and the sequence number")}, + {"blank id", " ", "connection-", 0, errors.New("invalid blank connection ID")}, + {"empty id", "", "connection-", 0, errors.New("invalid empty connection id")}, + {"negative sequence", "connection--1", "connection-", 0, errors.New("the sequence number '-1' is negative and invalid")}, + } + + for _, tc := range testCases { + + seq, err := host.ParseIdentifier(tc.identifier, tc.prefix) + require.Equal(t, tc.expSeq, seq) + + if tc.expErr == nil { + require.NoError(t, err, tc.name) + } else { + require.Error(t, err, tc.name) + } + } +} + +func TestMustParseClientStatePath(t *testing.T) { + testCases := []struct { + name string + path string + expErr error + }{ + {"valid", string(host.FullClientStateKey(ibctesting.FirstClientID)), nil}, + {"path too large", fmt.Sprintf("clients/clients/%s/clientState", ibctesting.FirstClientID), errors.New("path exceeds maximum allowed length for a client state path")}, + {"path too small", fmt.Sprintf("clients/%s", ibctesting.FirstClientID), errors.New("path is shorter than the minimum allowed length")}, + {"path does not begin with client store", fmt.Sprintf("cli/%s/%s", ibctesting.FirstClientID, host.KeyClientState), errors.New("the path must start with 'clients/' but starts with 'cli/'")}, + {"path does not end with client state key", fmt.Sprintf("%s/%s/consensus", string(host.KeyClientStorePrefix), ibctesting.FirstClientID), errors.New("the path must end with the client state key 'clientState'")}, + {"client ID is empty", string(host.FullClientStateKey("")), errors.New("the client ID is empty, which is invalid")}, + {"client ID is only spaces", string(host.FullClientStateKey(" ")), errors.New("Ensure the client ID is not empty and does not contain only spaces")}, + } + + for _, tc := range testCases { + if tc.expErr == nil { + require.NotPanics(t, func() { + clientID := host.MustParseClientStatePath(tc.path) + require.Equal(t, ibctesting.FirstClientID, clientID) + }) + } else { + require.Panics(t, func() { + host.MustParseClientStatePath(tc.path) + }) + } + } +} + +func TestMustParseConnectionPath(t *testing.T) { + testCases := []struct { + name string + path string + expected string + expErr error + }{ + {"valid", "a/connection", "connection", nil}, + {"valid localhost", "/connection-localhost", "connection-localhost", nil}, + {"invalid empty path", "", "", errors.New("path cannot be empty")}, + } + + for _, tc := range testCases { + if tc.expErr == nil { + require.NotPanics(t, func() { + connID := host.MustParseConnectionPath(tc.path) + require.Equal(t, connID, tc.expected) + }) + } else { + require.Panics(t, func() { + host.MustParseConnectionPath(tc.path) + }) + } + } +} diff --git a/modules/core/24-host/port_keys.go b/modules/core/24-host/port_keys.go new file mode 100644 index 0000000..2faeae0 --- /dev/null +++ b/modules/core/24-host/port_keys.go @@ -0,0 +1,8 @@ +package host + +const ( + KeyPortPrefix = "ports" +) + +// ICS05 +// The following paths are the keys to the store as defined in https://github.com/cosmos/ibc/tree/master/spec/core/ics-005-port-allocation#store-paths diff --git a/modules/core/24-host/v2/packet_key_test.go b/modules/core/24-host/v2/packet_key_test.go new file mode 100644 index 0000000..7e34195 --- /dev/null +++ b/modules/core/24-host/v2/packet_key_test.go @@ -0,0 +1,34 @@ +package v2_test + +import ( + "encoding/hex" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc-go/v10/modules/core/24-host/v2" +) + +// TestPacketCommitmentKey is primarily used to document the expected key output +// so that other implementations (such as the IBC Solidity) can replicate the +// same key output. But it is also useful to catch any changes in the keys. +func TestPacketCommitmentKey(t *testing.T) { + actual := hex.EncodeToString(v2.PacketCommitmentKey("channel-0", 1)) + require.Equal(t, "6368616e6e656c2d30010000000000000001", actual) +} + +// TestPacketReceiptKey is primarily used to document the expected key output +// so that other implementations (such as the IBC Solidity) can replicate the +// same key output. But it is also useful to catch any changes in the keys. +func TestPacketReceiptKey(t *testing.T) { + actual := hex.EncodeToString(v2.PacketReceiptKey("channel-0", 1)) + require.Equal(t, "6368616e6e656c2d30020000000000000001", actual) +} + +// TestPacketAcknowledgementKey is primarily used to document the expected key output +// so that other implementations (such as the IBC Solidity) can replicate the +// same key output. But it is also useful to catch any changes in the keys. +func TestPacketAcknowledgementKey(t *testing.T) { + actual := hex.EncodeToString(v2.PacketAcknowledgementKey("channel-0", 1)) + require.Equal(t, "6368616e6e656c2d30030000000000000001", actual) +} diff --git a/modules/core/24-host/v2/packet_keys.go b/modules/core/24-host/v2/packet_keys.go new file mode 100644 index 0000000..d4185ff --- /dev/null +++ b/modules/core/24-host/v2/packet_keys.go @@ -0,0 +1,54 @@ +package v2 + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + PacketCommitmentBasePrefix = byte(1) + PacketReceiptBasePrefix = byte(2) + PacketAcknowledgementBasePrefix = byte(3) +) + +// PacketCommitmentPrefixKey returns the store key prefix under which packet commitments for a particular channel are stored. +// channelID must be a generated identifier, not provided externally so key collisions are not possible. +func PacketCommitmentPrefixKey(channelID string) []byte { + return append([]byte(channelID), PacketCommitmentBasePrefix) +} + +// PacketCommitmentKey returns the store key of under which a packet commitment is stored. +// channelID must be a generated identifier, not provided externally so key collisions are not possible. +func PacketCommitmentKey(channelID string, sequence uint64) []byte { + return append(PacketCommitmentPrefixKey(channelID), sdk.Uint64ToBigEndian(sequence)...) +} + +// PacketReceiptPrefixKey returns the store key prefix under which packet receipts for a particular channel are stored. +// channelID must be a generated identifier, not provided externally so key collisions are not possible. +func PacketReceiptPrefixKey(channelID string) []byte { + return append([]byte(channelID), PacketReceiptBasePrefix) +} + +// PacketReceiptKey returns the store key of under which a packet receipt is stored. +// channelID must be a generated identifier, not provided externally so key collisions are not possible. +func PacketReceiptKey(channelID string, sequence uint64) []byte { + return append(PacketReceiptPrefixKey(channelID), sdk.Uint64ToBigEndian(sequence)...) +} + +// PacketAcknowledgementPrefixKey returns the store key prefix under which packet acknowledgements for a particular channel are stored. +// channelID must be a generated identifier, not provided externally so key collisions are not possible. +func PacketAcknowledgementPrefixKey(channelID string) []byte { + return append([]byte(channelID), PacketAcknowledgementBasePrefix) +} + +// PacketAcknowledgementKey returns the store key of under which a packet acknowledgement is stored. +// channelID must be a generated identifier, not provided externally so key collisions are not possible. +func PacketAcknowledgementKey(channelID string, sequence uint64) []byte { + return append(PacketAcknowledgementPrefixKey(channelID), sdk.Uint64ToBigEndian(sequence)...) +} + +// NextSequenceSendKey returns the store key for the next sequence send of a given channelID. +func NextSequenceSendKey(channelID string) []byte { + return fmt.Appendf(nil, "nextSequenceSend/%s", channelID) +} diff --git a/modules/core/24-host/validate.go b/modules/core/24-host/validate.go new file mode 100644 index 0000000..ca7485d --- /dev/null +++ b/modules/core/24-host/validate.go @@ -0,0 +1,118 @@ +package host + +import ( + "regexp" + "strings" + + errorsmod "cosmossdk.io/errors" +) + +// DefaultMaxCharacterLength defines the default maximum character length used +// in validation of identifiers including the client, connection, port and +// channel identifiers. +// +// NOTE: this restriction is specific to this golang implementation of IBC. If +// your use case demands a higher limit, please open an issue and we will consider +// adjusting this restriction. +const DefaultMaxCharacterLength = 64 + +// DefaultMaxPortCharacterLength defines the default maximum character length used +// in validation of port identifiers. +var DefaultMaxPortCharacterLength = 128 + +// IsValidID defines regular expression to check if the string consist of +// characters in one of the following categories only: +// - Alphanumeric +// - `.`, `_`, `+`, `-`, `#` +// - `[`, `]`, `<`, `>` +var IsValidID = regexp.MustCompile(`^[a-zA-Z0-9\.\_\+\-\#\[\]\<\>]+$`).MatchString + +// ICS 024 Identifier and Path Validation Implementation +// +// This file defines ValidateFn to validate identifier and path strings +// The spec for ICS 024 can be located here: +// https://github.com/cosmos/ibc/tree/master/spec/core/ics-024-host-requirements + +// ValidateFn function type to validate path and identifier bytestrings +type ValidateFn func(string) error + +func defaultIdentifierValidator(id string, minLength, maxLength int) error { + if strings.TrimSpace(id) == "" { + return errorsmod.Wrap(ErrInvalidID, "identifier cannot be blank") + } + // valid id MUST NOT contain "/" separator + if strings.Contains(id, "/") { + return errorsmod.Wrapf(ErrInvalidID, "identifier %s cannot contain separator '/'", id) + } + // valid id must fit the length requirements + if len(id) < minLength || len(id) > maxLength { + return errorsmod.Wrapf(ErrInvalidID, "identifier %s has invalid length: %d, must be between %d-%d characters", id, len(id), minLength, maxLength) + } + // valid id must contain only alphanumeric characters and some allowed symbols. + if !IsValidID(id) { + return errorsmod.Wrapf( + ErrInvalidID, + "identifier %s must contain only alphanumeric or the following characters: '.', '_', '+', '-', '#', '[', ']', '<', '>'", + id, + ) + } + return nil +} + +// ClientIdentifierValidator is the default validator function for Client identifiers. +// A valid Identifier must be between 4-64 characters and only contain alphanumeric and some allowed +// special characters (see IsValidID). +func ClientIdentifierValidator(id string) error { + return defaultIdentifierValidator(id, 4, DefaultMaxCharacterLength) +} + +// ConnectionIdentifierValidator is the default validator function for Connection identifiers. +// A valid Identifier must be between 10-64 characters and only contain alphanumeric and some allowed +// special characters (see IsValidID). +func ConnectionIdentifierValidator(id string) error { + return defaultIdentifierValidator(id, 10, DefaultMaxCharacterLength) +} + +// ChannelIdentifierValidator is the default validator function for Channel identifiers. +// A valid Identifier must be between 8-64 characters and only contain alphanumeric and some allowed +// special characters (see IsValidID). +func ChannelIdentifierValidator(id string) error { + return defaultIdentifierValidator(id, 8, DefaultMaxCharacterLength) +} + +// PortIdentifierValidator is the default validator function for Port identifiers. +// A valid Identifier must be between 2-64 characters and only contain alphanumeric and some allowed +// special characters (see IsValidID). +func PortIdentifierValidator(id string) error { + return defaultIdentifierValidator(id, 2, DefaultMaxPortCharacterLength) +} + +// NewPathValidator takes in an Identifier Validator function and returns +// a Path Validator function which requires path to consist of `/`-separated valid identifiers, +// where a valid identifier is between 1-64 characters, contains only alphanumeric and some allowed +// special characters (see IsValidID), and satisfies the custom `idValidator` function. +func NewPathValidator(idValidator ValidateFn) ValidateFn { + return func(path string) error { + pathArr := strings.Split(path, "/") + if len(pathArr) > 0 && pathArr[0] == path { + return errorsmod.Wrapf(ErrInvalidPath, "path %s doesn't contain any separator '/'", path) + } + + for _, p := range pathArr { + // a path beginning or ending in a separator returns empty string elements. + if p == "" { + return errorsmod.Wrapf(ErrInvalidPath, "path %s cannot begin or end with '/'", path) + } + + if err := idValidator(p); err != nil { + return err + } + // Each path element must either be a valid identifier or constant number + if err := defaultIdentifierValidator(p, 1, DefaultMaxCharacterLength); err != nil { + return errorsmod.Wrapf(err, "path %s contains an invalid identifier: '%s'", path, p) + } + } + + return nil + } +} diff --git a/modules/core/24-host/validate_test.go b/modules/core/24-host/validate_test.go new file mode 100644 index 0000000..01e5326 --- /dev/null +++ b/modules/core/24-host/validate_test.go @@ -0,0 +1,152 @@ +package host + +import ( + "errors" + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +// 195 characters +var longID = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis eros neque, ultricies vel ligula ac, convallis porttitor elit. Maecenas tincidunt turpis elit, vel faucibus nisl pellentesque sodales" + +type testCase struct { + msg string + id string + expErr error +} + +func TestDefaultIdentifierValidator(t *testing.T) { + testCases := []testCase{ + {"valid lowercase", "lowercaseid", nil}, + {"valid id special chars", "._+-#[]<>._+-#[]<>", nil}, + {"valid id lower and special chars", "lower._+-#[]<>", nil}, + {"numeric id", "1234567890", nil}, + {"uppercase id", "NOTLOWERCASE", nil}, + {"numeric id", "1234567890", nil}, + {"blank id", " ", errors.New("the ID is blank - contains only spaces")}, + {"id length out of range", "1", errors.New("the ID length is too short")}, + {"id is too long", "this identifier is too long to be used as a valid identifier", errors.New("the ID exceeds the maximum allowed length")}, + {"path-like id", "lower/case/id", errors.New("the ID contains path-like characters, which are invalid for an ID")}, + {"invalid id", "(clientid)", errors.New("the ID contains invalid characters")}, + {"empty string", "", errors.New("the ID cannot be empty")}, + } + + for _, tc := range testCases { + + err := ClientIdentifierValidator(tc.id) + err1 := ConnectionIdentifierValidator(tc.id) + err2 := ChannelIdentifierValidator(tc.id) + err3 := PortIdentifierValidator(tc.id) + if tc.expErr == nil { + require.NoError(t, err, tc.msg) + require.NoError(t, err1, tc.msg) + require.NoError(t, err2, tc.msg) + require.NoError(t, err3, tc.msg) + } else { + require.Error(t, err, tc.msg) + require.Error(t, err1, tc.msg) + require.Error(t, err2, tc.msg) + require.Error(t, err3, tc.msg) + } + } +} + +func TestPortIdentifierValidator(t *testing.T) { + testCases := []testCase{ + {"valid lowercase", "transfer", nil}, + {"valid id special chars", "._+-#[]<>._+-#[]<>", nil}, + {"valid id lower and special chars", "lower._+-#[]<>", nil}, + {"numeric id", "1234567890", nil}, + {"uppercase id", "NOTLOWERCASE", nil}, + {"numeric id", "1234567890", nil}, + {"blank id", " ", errors.New("the ID is blank - contains only spaces")}, + {"id length out of range", "1", errors.New("the ID length is too short")}, + {"id is too long", longID, errors.New("the ID exceeds the maximum allowed length")}, + {"path-like id", "lower/case/id", errors.New("the ID contains path-like characters, which are invalid for an ID")}, + {"invalid id", "(clientid)", errors.New("the ID contains invalid characters")}, + {"empty string", "", errors.New("the ID cannot be empty")}, + } + + for _, tc := range testCases { + + err := PortIdentifierValidator(tc.id) + if tc.expErr == nil { + require.NoError(t, err, tc.msg) + } else { + require.Error(t, err, tc.msg) + } + } +} + +func TestPathValidator(t *testing.T) { + testCases := []testCase{ + {"valid lowercase", "p/lowercaseid", nil}, + {"numeric path", "p/239123", nil}, + {"valid id special chars", "p/._+-#[]<>._+-#[]<>", nil}, + {"valid id lower and special chars", "lower/._+-#[]<>", nil}, + {"id length out of range", "p/l", nil}, + {"uppercase id", "p/NOTLOWERCASE", nil}, + {"invalid path", "lowercaseid", errors.New("the path is invalid")}, + {"blank id", "p/ ", errors.New("the ID is blank or contains only whitespace after the separator")}, + {"id length out of range", "p/12345678901234567890123456789012345678901234567890123456789012345", errors.New("the ID exceeds the maximum allowed length")}, + {"invalid id", "p/(clientid)", errors.New("the ID contains invalid characters, such as parentheses")}, + {"empty string", "", errors.New("the ID cannot be empty")}, + {"separators only", "////", errors.New("the ID contains only separators, which is invalid")}, + {"just separator", "/", errors.New("the ID cannot be just a separator")}, + {"begins with separator", "/id", errors.New("the ID should not begin with a separator")}, + {"blank before separator", " /id", errors.New("the ID cannot have leading spaces before the separator")}, + {"ends with separator", "id/", errors.New("the ID cannot end with a separator")}, + {"blank after separator", "id/ ", errors.New("the ID cannot have trailing spaces after the separator")}, + {"blanks with separator", " / ", errors.New("the ID cannot have spaces before or after the separator")}, + } + + for _, tc := range testCases { + + f := NewPathValidator(func(path string) error { + return nil + }) + + err := f(tc.id) + + if tc.expErr == nil { + seps := strings.Count(tc.id, "/") + require.Equal(t, 1, seps) + require.NoError(t, err, tc.msg) + } else { + require.Error(t, err, tc.msg) + } + } +} + +func TestCustomPathValidator(t *testing.T) { + validateFn := NewPathValidator(func(path string) error { + if !strings.HasPrefix(path, "id_") { + return fmt.Errorf("identifier %s must start with 'id_", path) + } + return nil + }) + + testCases := []testCase{ + {"valid custom path", "id_client/id_one", nil}, + {"invalid path", "client", errors.New("the path is invalid")}, + {"invalid custom path", "id_one/client", errors.New("the path contains an invalid structure")}, + {"invalid identifier", "id_client/id_1234567890123456789012345678901234567890123457890123456789012345", errors.New("the identifier exceeds the maximum allowed length for an ID")}, + {"separators only", "////", errors.New("the path contains only separators, which is invalid")}, + {"just separator", "/", errors.New("the path cannot be just a separator")}, + {"ends with separator", "id_client/id_one/", errors.New("the path cannot end with a separator")}, + {"beings with separator", "/id_client/id_one", errors.New("the path cannot begin with a separator")}, + } + + for _, tc := range testCases { + + err := validateFn(tc.id) + if tc.expErr == nil { + require.NoError(t, err, tc.msg) + } else { + require.Error(t, err, tc.msg) + } + } +} diff --git a/modules/core/ante/ante.go b/modules/core/ante/ante.go new file mode 100644 index 0000000..993724c --- /dev/null +++ b/modules/core/ante/ante.go @@ -0,0 +1,208 @@ +package ante + +import ( + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + "github.com/cosmos/ibc-go/v10/modules/core/keeper" +) + +type RedundantRelayDecorator struct { + k *keeper.Keeper +} + +func NewRedundantRelayDecorator(k *keeper.Keeper) RedundantRelayDecorator { + return RedundantRelayDecorator{k: k} +} + +// AnteHandle returns an error if a multiMsg tx only contains packet messages (Recv, Ack, Timeout) and additional update messages +// and all packet messages are redundant. If the transaction is just a single UpdateClient message, or the multimsg transaction +// contains some other message type, then the antedecorator returns no error and continues processing to ensure these transactions +// are included. This will ensure that relayers do not waste fees on multiMsg transactions when another relayer has already submitted +// all packets, by rejecting the tx at the mempool layer. +func (rrd RedundantRelayDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + // do not run redundancy check on DeliverTx or simulate + if (ctx.IsCheckTx() || ctx.IsReCheckTx()) && !simulate { + // keep track of total packet messages and number of redundancies across `RecvPacket`, `AcknowledgePacket`, and `TimeoutPacket/OnClose` + redundancies := 0 + packetMsgs := 0 + for _, m := range tx.GetMsgs() { + switch msg := m.(type) { + case *channeltypes.MsgRecvPacket: + var ( + response *channeltypes.MsgRecvPacketResponse + err error + ) + // when we are in ReCheckTx mode, ctx.IsCheckTx() will also return true + // therefore we must start the if statement on ctx.IsReCheckTx() to correctly + // determine which mode we are in + if ctx.IsReCheckTx() { + response, err = rrd.recvPacketReCheckTx(ctx, msg) + } else { + response, err = rrd.recvPacketCheckTx(ctx, msg) + } + if err != nil { + return ctx, err + } + + if response.Result == channeltypes.NOOP { + redundancies++ + } + packetMsgs++ + + case *channeltypes.MsgAcknowledgement: + response, err := rrd.k.Acknowledgement(ctx, msg) + if err != nil { + return ctx, err + } + if response.Result == channeltypes.NOOP { + redundancies++ + } + packetMsgs++ + + case *channeltypes.MsgTimeout: + response, err := rrd.k.Timeout(ctx, msg) + if err != nil { + return ctx, err + } + if response.Result == channeltypes.NOOP { + redundancies++ + } + packetMsgs++ + + case *channeltypes.MsgTimeoutOnClose: + response, err := rrd.k.TimeoutOnClose(ctx, msg) + if err != nil { + return ctx, err + } + if response.Result == channeltypes.NOOP { + redundancies++ + } + packetMsgs++ + + case *clienttypes.MsgUpdateClient: + if err := rrd.updateClientCheckTx(ctx, msg); err != nil { + return ctx, err + } + + case *channeltypesv2.MsgTimeout: + response, err := rrd.k.ChannelKeeperV2.Timeout(ctx, msg) + if err != nil { + return ctx, err + } + + if response.Result == channeltypesv2.NOOP { + redundancies++ + } + packetMsgs++ + case *channeltypesv2.MsgAcknowledgement: + response, err := rrd.k.ChannelKeeperV2.Acknowledgement(ctx, msg) + if err != nil { + return ctx, err + } + + if response.Result == channeltypesv2.NOOP { + redundancies++ + } + packetMsgs++ + case *channeltypesv2.MsgRecvPacket: + response, err := rrd.k.ChannelKeeperV2.RecvPacket(ctx, msg) + if err != nil { + return ctx, err + } + + if response.Result == channeltypesv2.NOOP { + redundancies++ + } + packetMsgs++ + default: + // if the multiMsg tx has a msg that is not a packet msg or update msg, then we will not return error + // regardless of if all packet messages are redundant. This ensures that non-packet messages get processed + // even if they get batched with redundant packet messages. + return next(ctx, tx, simulate) + } + } + + // only return error if all packet messages are redundant + if redundancies == packetMsgs && packetMsgs > 0 { + return ctx, channeltypes.ErrRedundantTx + } + } + return next(ctx, tx, simulate) +} + +// recvPacketCheckTx runs a subset of ibc recv packet logic to be used specifically within the RedundantRelayDecorator AnteHandler. +// It only performs core IBC receiving logic and skips any application logic. +func (rrd RedundantRelayDecorator) recvPacketCheckTx(ctx sdk.Context, msg *channeltypes.MsgRecvPacket) (*channeltypes.MsgRecvPacketResponse, error) { + // If the packet was already received, perform a no-op + // Use a cached context to prevent accidental state changes + cacheCtx, writeFn := ctx.CacheContext() + _, err := rrd.k.ChannelKeeper.RecvPacket(cacheCtx, msg.Packet, msg.ProofCommitment, msg.ProofHeight) + + switch err { + case nil: + writeFn() + case channeltypes.ErrNoOpMsg: + return &channeltypes.MsgRecvPacketResponse{Result: channeltypes.NOOP}, nil + default: + return nil, errorsmod.Wrap(err, "receive packet verification failed") + } + + return &channeltypes.MsgRecvPacketResponse{Result: channeltypes.SUCCESS}, nil +} + +// recvPacketReCheckTx runs a subset of ibc recv packet logic to be used specifically within the RedundantRelayDecorator AnteHandler. +// It only performs core IBC receiving logic and skips any application logic. +func (rrd RedundantRelayDecorator) recvPacketReCheckTx(ctx sdk.Context, msg *channeltypes.MsgRecvPacket) (*channeltypes.MsgRecvPacketResponse, error) { + // If the packet was already received, perform a no-op + // Use a cached context to prevent accidental state changes + cacheCtx, writeFn := ctx.CacheContext() + err := rrd.k.ChannelKeeper.RecvPacketReCheckTx(cacheCtx, msg.Packet) + + switch err { + case nil: + writeFn() + case channeltypes.ErrNoOpMsg: + return &channeltypes.MsgRecvPacketResponse{Result: channeltypes.NOOP}, nil + default: + return nil, errorsmod.Wrap(err, "receive packet verification failed") + } + + return &channeltypes.MsgRecvPacketResponse{Result: channeltypes.SUCCESS}, nil +} + +// updateClientCheckTx runs a subset of ibc client update logic to be used specifically within the RedundantRelayDecorator AnteHandler. +// The following function performs ibc client message verification for CheckTx only and state updates in both CheckTx and ReCheckTx. +// Note that misbehaviour checks are omitted. +func (rrd RedundantRelayDecorator) updateClientCheckTx(ctx sdk.Context, msg *clienttypes.MsgUpdateClient) error { + clientMsg, err := clienttypes.UnpackClientMessage(msg.ClientMessage) + if err != nil { + return err + } + + if status := rrd.k.ClientKeeper.GetClientStatus(ctx, msg.ClientId); status != exported.Active { + return errorsmod.Wrapf(clienttypes.ErrClientNotActive, "cannot update client (%s) with status %s", msg.ClientId, status) + } + + clientModule, err := rrd.k.ClientKeeper.Route(ctx, msg.ClientId) + if err != nil { + return err + } + + if !ctx.IsReCheckTx() { + if err := clientModule.VerifyClientMessage(ctx, msg.ClientId, clientMsg); err != nil { + return err + } + } + + heights := clientModule.UpdateState(ctx, msg.ClientId, clientMsg) + + ctx.Logger().With("module", "x/"+exported.ModuleName).Debug("ante ibc client update", "consensusHeights", heights) + + return nil +} diff --git a/modules/core/ante/ante_test.go b/modules/core/ante/ante_test.go new file mode 100644 index 0000000..8c7abf0 --- /dev/null +++ b/modules/core/ante/ante_test.go @@ -0,0 +1,745 @@ +package ante_test + +import ( + "errors" + "testing" + "time" + + "github.com/stretchr/testify/require" + testifysuite "github.com/stretchr/testify/suite" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + hostv2 "github.com/cosmos/ibc-go/v10/modules/core/24-host/v2" + "github.com/cosmos/ibc-go/v10/modules/core/ante" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + "github.com/cosmos/ibc-go/v10/testing/mock/v2" +) + +type AnteTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains used for convenience and readability + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain + + path *ibctesting.Path +} + +// SetupTest creates a coordinator with 2 test chains. +func (suite *AnteTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) + // commit some blocks so that QueryProof returns valid proof (cannot return valid query if height <= 1) + suite.coordinator.CommitNBlocks(suite.chainA, 2) + suite.coordinator.CommitNBlocks(suite.chainB, 2) + suite.path = ibctesting.NewPath(suite.chainA, suite.chainB) + suite.path.Setup() +} + +// TestAnteTestSuite runs all the tests within this package. +func TestAnteTestSuite(t *testing.T) { + testifysuite.Run(t, new(AnteTestSuite)) +} + +// createRecvPacketMessage creates a RecvPacket message for a packet sent from chain A to chain B. +func (suite *AnteTestSuite) createRecvPacketMessage(isRedundant bool) *channeltypes.MsgRecvPacket { + sequence, err := suite.path.EndpointA.SendPacket(clienttypes.NewHeight(2, 0), 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet := channeltypes.NewPacket(ibctesting.MockPacketData, sequence, + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + clienttypes.NewHeight(2, 0), 0) + + if isRedundant { + err = suite.path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + } + + err = suite.path.EndpointB.UpdateClient() + suite.Require().NoError(err) + + packetKey := host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + proof, proofHeight := suite.chainA.QueryProof(packetKey) + + return channeltypes.NewMsgRecvPacket(packet, proof, proofHeight, suite.path.EndpointA.Chain.SenderAccount.GetAddress().String()) +} + +// createRecvPacketMessageV2 creates a V2 RecvPacket message for a packet sent from chain A to chain B. +func (suite *AnteTestSuite) createRecvPacketMessageV2(isRedundant bool) *channeltypesv2.MsgRecvPacket { + packet, err := suite.path.EndpointA.MsgSendPacket(suite.chainA.GetTimeoutTimestampSecs(), mock.NewMockPayload(mock.ModuleNameA, mock.ModuleNameB)) + suite.Require().NoError(err) + + if isRedundant { + err = suite.path.EndpointB.MsgRecvPacket(packet) + suite.Require().NoError(err) + } + + err = suite.path.EndpointB.UpdateClient() + suite.Require().NoError(err) + + packetKey := hostv2.PacketCommitmentKey(packet.SourceClient, packet.Sequence) + proof, proofHeight := suite.chainA.QueryProof(packetKey) + + return channeltypesv2.NewMsgRecvPacket(packet, proof, proofHeight, suite.path.EndpointA.Chain.SenderAccount.GetAddress().String()) +} + +// createAcknowledgementMessage creates an Acknowledgement message for a packet sent from chain B to chain A. +func (suite *AnteTestSuite) createAcknowledgementMessage(isRedundant bool) sdk.Msg { + sequence, err := suite.path.EndpointB.SendPacket(clienttypes.NewHeight(2, 0), 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet := channeltypes.NewPacket(ibctesting.MockPacketData, sequence, + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + clienttypes.NewHeight(2, 0), 0) + err = suite.path.EndpointA.RecvPacket(packet) + suite.Require().NoError(err) + + if isRedundant { + err = suite.path.EndpointB.AcknowledgePacket(packet, ibctesting.MockAcknowledgement) + suite.Require().NoError(err) + } + + packetKey := host.PacketAcknowledgementKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + proof, proofHeight := suite.chainA.QueryProof(packetKey) + + return channeltypes.NewMsgAcknowledgement(packet, ibctesting.MockAcknowledgement, proof, proofHeight, suite.path.EndpointA.Chain.SenderAccount.GetAddress().String()) +} + +// createAcknowledgementMessageV2 creates a V2 Acknowledgement message for a packet sent from chain B to chain A. +func (suite *AnteTestSuite) createAcknowledgementMessageV2(isRedundant bool) *channeltypesv2.MsgAcknowledgement { + packet, err := suite.path.EndpointB.MsgSendPacket(suite.chainB.GetTimeoutTimestampSecs(), mock.NewMockPayload(mock.ModuleNameA, mock.ModuleNameB)) + suite.Require().NoError(err) + + err = suite.path.EndpointA.MsgRecvPacket(packet) + suite.Require().NoError(err) + + ack := channeltypesv2.Acknowledgement{AppAcknowledgements: [][]byte{mock.MockRecvPacketResult.Acknowledgement}} + if isRedundant { + err = suite.path.EndpointB.MsgAcknowledgePacket(packet, ack) + suite.Require().NoError(err) + } + + packetKey := hostv2.PacketAcknowledgementKey(packet.DestinationClient, packet.Sequence) + proof, proofHeight := suite.chainA.QueryProof(packetKey) + + return channeltypesv2.NewMsgAcknowledgement(packet, ack, proof, proofHeight, suite.path.EndpointA.Chain.SenderAccount.GetAddress().String()) +} + +// createTimeoutMessage creates an Timeout message for a packet sent from chain B to chain A. +func (suite *AnteTestSuite) createTimeoutMessage(isRedundant bool) sdk.Msg { + height := suite.chainA.LatestCommittedHeader.GetHeight() + timeoutHeight := clienttypes.NewHeight(height.GetRevisionNumber(), height.GetRevisionHeight()+1) + + sequence, err := suite.path.EndpointB.SendPacket(timeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + suite.coordinator.CommitNBlocks(suite.chainA, 3) + + err = suite.path.EndpointB.UpdateClient() + suite.Require().NoError(err) + + packet := channeltypes.NewPacket(ibctesting.MockPacketData, sequence, + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + timeoutHeight, 0) + + if isRedundant { + err = suite.path.EndpointB.TimeoutPacket(packet) + suite.Require().NoError(err) + } + + packetKey := host.PacketReceiptKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + proof, proofHeight := suite.chainA.QueryProof(packetKey) + + return channeltypes.NewMsgTimeout(packet, sequence, proof, proofHeight, suite.path.EndpointA.Chain.SenderAccount.GetAddress().String()) +} + +// createTimeoutMessageV2 creates a V2 Timeout message for a packet sent from chain B to chain A. +func (suite *AnteTestSuite) createTimeoutMessageV2(isRedundant bool) *channeltypesv2.MsgTimeout { + timeoutTimestamp := uint64(suite.chainB.GetContext().BlockTime().Add(time.Second).Unix()) + packet, err := suite.path.EndpointB.MsgSendPacket(timeoutTimestamp, mock.NewMockPayload(mock.ModuleNameA, mock.ModuleNameB)) + suite.Require().NoError(err) + + suite.coordinator.IncrementTimeBy(time.Hour) + err = suite.path.EndpointB.UpdateClient() + suite.Require().NoError(err) + + if isRedundant { + err = suite.path.EndpointB.MsgTimeoutPacket(packet) + suite.Require().NoError(err) + } + + packetKey := hostv2.PacketReceiptKey(packet.SourceClient, packet.Sequence) + proof, proofHeight := suite.chainA.QueryProof(packetKey) + + return channeltypesv2.NewMsgTimeout(packet, proof, proofHeight, suite.path.EndpointA.Chain.SenderAccount.GetAddress().String()) +} + +// createTimeoutOnCloseMessage creates an TimeoutOnClose message for a packet sent from chain B to chain A. +func (suite *AnteTestSuite) createTimeoutOnCloseMessage(isRedundant bool) sdk.Msg { + height := suite.chainA.LatestCommittedHeader.GetHeight() + timeoutHeight := clienttypes.NewHeight(height.GetRevisionNumber(), height.GetRevisionHeight()+1) + + sequence, err := suite.path.EndpointB.SendPacket(timeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + suite.path.EndpointA.UpdateChannel(func(channel *channeltypes.Channel) { channel.State = channeltypes.CLOSED }) + + packet := channeltypes.NewPacket(ibctesting.MockPacketData, sequence, + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + timeoutHeight, 0) + + if isRedundant { + err = suite.path.EndpointB.TimeoutOnClose(packet) + suite.Require().NoError(err) + } + + packetKey := host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + proof, proofHeight := suite.chainA.QueryProof(packetKey) + + channelKey := host.ChannelKey(packet.GetDestPort(), packet.GetDestChannel()) + closedProof, _ := suite.chainA.QueryProof(channelKey) + + return channeltypes.NewMsgTimeoutOnClose(packet, 1, proof, closedProof, proofHeight, suite.path.EndpointA.Chain.SenderAccount.GetAddress().String()) +} + +func (suite *AnteTestSuite) createUpdateClientMessage() sdk.Msg { + endpoint := suite.path.EndpointB + + // ensure counterparty has committed state + endpoint.Chain.Coordinator.CommitBlock(endpoint.Counterparty.Chain) + + var header exported.ClientMessage + + switch endpoint.ClientConfig.GetClientType() { + case exported.Tendermint: + trustedHeight := endpoint.GetClientLatestHeight() + header, _ = endpoint.Counterparty.Chain.IBCClientHeader(endpoint.Counterparty.Chain.LatestCommittedHeader, trustedHeight.(clienttypes.Height)) + + default: + } + + msg, err := clienttypes.NewMsgUpdateClient( + endpoint.ClientID, header, + endpoint.Chain.SenderAccount.GetAddress().String(), + ) + require.NoError(endpoint.Chain.TB, err) + + return msg +} + +func (suite *AnteTestSuite) TestAnteDecoratorCheckTx() { + testCases := []struct { + name string + malleate func(suite *AnteTestSuite) []sdk.Msg + expError error + }{ + { + "success on one new RecvPacket message", + func(suite *AnteTestSuite) []sdk.Msg { + // the RecvPacket message has not been submitted to the chain yet, so it will succeed + return []sdk.Msg{suite.createRecvPacketMessage(false)} + }, + nil, + }, + { + "success on one new V2 RecvPacket message", + func(suite *AnteTestSuite) []sdk.Msg { + suite.path.SetupV2() + // the RecvPacket message has not been submitted to the chain yet, so it will succeed + return []sdk.Msg{suite.createRecvPacketMessageV2(false)} + }, + nil, + }, + { + "success on one new Acknowledgement message", + func(suite *AnteTestSuite) []sdk.Msg { + // the Acknowledgement message has not been submitted to the chain yet, so it will succeed + return []sdk.Msg{suite.createAcknowledgementMessage(false)} + }, + nil, + }, + { + "success on one new V2 Acknowledgement message", + func(suite *AnteTestSuite) []sdk.Msg { + suite.path.SetupV2() + // the Acknowledgement message has not been submitted to the chain yet, so it will succeed + return []sdk.Msg{suite.createAcknowledgementMessageV2(false)} + }, + nil, + }, + { + "success on one new Timeout message", + func(suite *AnteTestSuite) []sdk.Msg { + // the Timeout message has not been submitted to the chain yet, so it will succeed + return []sdk.Msg{suite.createTimeoutMessage(false)} + }, + nil, + }, + { + "success on one new Timeout V2 message", + func(suite *AnteTestSuite) []sdk.Msg { + suite.path.SetupV2() + // the Timeout message has not been submitted to the chain yet, so it will succeed + return []sdk.Msg{suite.createTimeoutMessageV2(false)} + }, + nil, + }, + { + "success on one new TimeoutOnClose message", + func(suite *AnteTestSuite) []sdk.Msg { + // the TimeoutOnClose message has not been submitted to the chain yet, so it will succeed + return []sdk.Msg{suite.createTimeoutOnCloseMessage(false)} + }, + nil, + }, + { + "success on three new messages of each type", + func(suite *AnteTestSuite) []sdk.Msg { + var msgs []sdk.Msg + + // none of the messages of each type has been submitted to the chain yet, + // the first message is succeed and the next two of each type will be rejected + // because they are redundant. + + // from A to B + for i := 1; i <= 3; i++ { + msgs = append(msgs, suite.createRecvPacketMessage(false)) + } + + // from B to A + for i := 1; i <= 9; i++ { + switch { + case i >= 1 && i <= 3: + msgs = append(msgs, suite.createAcknowledgementMessage(false)) + case i >= 4 && i <= 6: + msgs = append(msgs, suite.createTimeoutMessage(false)) + case i >= 7 && i <= 9: + msgs = append(msgs, suite.createTimeoutOnCloseMessage(false)) + } + } + return msgs + }, + nil, + }, + { + "success on three redundant messages of RecvPacket, Acknowledgement and TimeoutOnClose, and one new Timeout message", + func(suite *AnteTestSuite) []sdk.Msg { + var msgs []sdk.Msg + + // we pass three messages of RecvPacket, Acknowledgement and TimeoutOnClose that + // are all redundant (i.e. those messages have already been submitted and + // processed by the chain). But these messages will not be rejected because the + // Timeout message is new. + + // from A to B + for i := 1; i <= 3; i++ { + msgs = append(msgs, suite.createRecvPacketMessage(true)) + } + + // from B to A + for i := 1; i <= 7; i++ { + switch { + case i >= 1 && i <= 3: + msgs = append(msgs, suite.createAcknowledgementMessage(true)) + case i == 4: + msgs = append(msgs, suite.createTimeoutMessage(false)) + case i >= 5 && i <= 7: + msgs = append(msgs, suite.createTimeoutOnCloseMessage(true)) + } + } + return msgs + }, + nil, + }, + { + "success on one new message and two redundant messages of each type", + func(suite *AnteTestSuite) []sdk.Msg { + var msgs []sdk.Msg + + // For each type there is a new message and two messages that are redundant + // (i.e. they have been already submitted and processed by the chain). But all + // the redundant messages will not be rejected because there is a new message + // of each type. + + // from A to B + for i := 1; i <= 3; i++ { + msgs = append(msgs, suite.createRecvPacketMessage(i != 2)) + } + + // from B to A + for i := 1; i <= 9; i++ { + switch { + case i >= 1 && i <= 3: + msgs = append(msgs, suite.createAcknowledgementMessage(i != 2)) + case i >= 4 && i <= 6: + msgs = append(msgs, suite.createTimeoutMessage(i != 5)) + case i >= 7 && i <= 9: + msgs = append(msgs, suite.createTimeoutOnCloseMessage(i != 8)) + } + } + return msgs + }, + nil, + }, + { + "success on one new UpdateClient message", + func(suite *AnteTestSuite) []sdk.Msg { + return []sdk.Msg{suite.createUpdateClientMessage()} + }, + nil, + }, + { + "success on three new UpdateClient messages", + func(suite *AnteTestSuite) []sdk.Msg { + return []sdk.Msg{suite.createUpdateClientMessage(), suite.createUpdateClientMessage(), suite.createUpdateClientMessage()} + }, + nil, + }, + { + "success on three new Updateclient messages and one new RecvPacket message", + func(suite *AnteTestSuite) []sdk.Msg { + return []sdk.Msg{ + suite.createUpdateClientMessage(), + suite.createUpdateClientMessage(), + suite.createUpdateClientMessage(), + suite.createRecvPacketMessage(false), + } + }, + nil, + }, + { + "success on three redundant RecvPacket messages and one SubmitMisbehaviour message", + func(suite *AnteTestSuite) []sdk.Msg { + msgs := []sdk.Msg{suite.createUpdateClientMessage()} + + for i := 1; i <= 3; i++ { + msgs = append(msgs, suite.createRecvPacketMessage(true)) + } + + // append non packet and update message to msgs to ensure multimsg tx should pass + msgs = append(msgs, &clienttypes.MsgSubmitMisbehaviour{}) //nolint:staticcheck // we're using the deprecated message for testing + return msgs + }, + nil, + }, + { + "success on app callback error, app callbacks are skipped for performance", + func(suite *AnteTestSuite) []sdk.Msg { + suite.chainB.GetSimApp().IBCMockModule.IBCApp.OnRecvPacket = func( + ctx sdk.Context, channelVersion string, packet channeltypes.Packet, relayer sdk.AccAddress, + ) exported.Acknowledgement { + panic(errors.New("failed OnRecvPacket mock callback")) + } + + // the RecvPacket message has not been submitted to the chain yet, so it will succeed + return []sdk.Msg{suite.createRecvPacketMessage(false)} + }, + nil, + }, + { + "no success on one redundant RecvPacket message", + func(suite *AnteTestSuite) []sdk.Msg { + return []sdk.Msg{suite.createRecvPacketMessage(true)} + }, + channeltypes.ErrRedundantTx, + }, + { + "no success on one redundant V2 RecvPacket message", + func(suite *AnteTestSuite) []sdk.Msg { + suite.path.SetupV2() + return []sdk.Msg{suite.createRecvPacketMessageV2(true)} + }, + channeltypes.ErrRedundantTx, + }, + { + "no success on three redundant messages of each type", + func(suite *AnteTestSuite) []sdk.Msg { + var msgs []sdk.Msg + + // from A to B + for i := 1; i <= 3; i++ { + msgs = append(msgs, suite.createRecvPacketMessage(true)) + } + + // from B to A + for i := 1; i <= 9; i++ { + switch { + case i >= 1 && i <= 3: + msgs = append(msgs, suite.createAcknowledgementMessage(true)) + case i >= 4 && i <= 6: + msgs = append(msgs, suite.createTimeoutMessage(true)) + case i >= 7 && i <= 9: + msgs = append(msgs, suite.createTimeoutOnCloseMessage(true)) + } + } + return msgs + }, + channeltypes.ErrRedundantTx, + }, + { + "no success on one new UpdateClient message and three redundant RecvPacket messages", + func(suite *AnteTestSuite) []sdk.Msg { + msgs := []sdk.Msg{suite.createUpdateClientMessage()} + + for i := 1; i <= 3; i++ { + msgs = append(msgs, suite.createRecvPacketMessage(true)) + } + + return msgs + }, + channeltypes.ErrRedundantTx, + }, + { + "no success on one new UpdateClient message: invalid client identifier", + func(suite *AnteTestSuite) []sdk.Msg { + clientMsg, err := codectypes.NewAnyWithValue(&ibctm.Header{}) + suite.Require().NoError(err) + + msgs := []sdk.Msg{&clienttypes.MsgUpdateClient{ClientId: ibctesting.InvalidID, ClientMessage: clientMsg}} + return msgs + }, + clienttypes.ErrClientNotActive, + }, + { + "no success on one new UpdateClient message: client module not found", + func(suite *AnteTestSuite) []sdk.Msg { + clientMsg, err := codectypes.NewAnyWithValue(&ibctm.Header{}) + suite.Require().NoError(err) + + msgs := []sdk.Msg{&clienttypes.MsgUpdateClient{ClientId: clienttypes.FormatClientIdentifier("08-wasm", 1), ClientMessage: clientMsg}} + return msgs + }, + clienttypes.ErrClientNotActive, + }, + { + "no success on one new UpdateClient message: no consensus state for trusted height", + func(suite *AnteTestSuite) []sdk.Msg { + clientMsg, err := codectypes.NewAnyWithValue(&ibctm.Header{TrustedHeight: clienttypes.NewHeight(1, 10000)}) + suite.Require().NoError(err) + + msgs := []sdk.Msg{&clienttypes.MsgUpdateClient{ClientId: suite.path.EndpointA.ClientID, ClientMessage: clientMsg}} + return msgs + }, + clienttypes.ErrConsensusStateNotFound, + }, + { + "no success on three new UpdateClient messages and three redundant messages of each type", + func(suite *AnteTestSuite) []sdk.Msg { + msgs := []sdk.Msg{suite.createUpdateClientMessage(), suite.createUpdateClientMessage(), suite.createUpdateClientMessage()} + + // from A to B + for i := 1; i <= 3; i++ { + msgs = append(msgs, suite.createRecvPacketMessage(true)) + } + + // from B to A + for i := 1; i <= 9; i++ { + switch { + case i >= 1 && i <= 3: + msgs = append(msgs, suite.createAcknowledgementMessage(true)) + case i >= 4 && i <= 6: + msgs = append(msgs, suite.createTimeoutMessage(true)) + case i >= 7 && i <= 9: + msgs = append(msgs, suite.createTimeoutOnCloseMessage(true)) + } + } + return msgs + }, + channeltypes.ErrRedundantTx, + }, + { + "no success on one new message and one invalid message", + func(suite *AnteTestSuite) []sdk.Msg { + packet := channeltypes.NewPacket(ibctesting.MockPacketData, 2, + suite.path.EndpointA.ChannelConfig.PortID, suite.path.EndpointA.ChannelID, + suite.path.EndpointB.ChannelConfig.PortID, suite.path.EndpointB.ChannelID, + clienttypes.NewHeight(2, 0), 0) + + return []sdk.Msg{ + suite.createRecvPacketMessage(false), + channeltypes.NewMsgRecvPacket(packet, []byte("proof"), clienttypes.NewHeight(1, 1), "signer"), + } + }, + commitmenttypes.ErrInvalidProof, + }, + { + "no success on one new message and one redundant message in the same block", + func(suite *AnteTestSuite) []sdk.Msg { + msg := suite.createRecvPacketMessage(false) + + // We want to be able to run check tx with the non-redundant message without + // committing it to a block, so that the when check tx runs with the redundant + // message they are both in the same block + k := suite.chainB.App.GetIBCKeeper() + decorator := ante.NewRedundantRelayDecorator(k) + checkCtx := suite.chainB.GetContext().WithIsCheckTx(true) + next := func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { return ctx, nil } + txBuilder := suite.chainB.TxConfig.NewTxBuilder() + err := txBuilder.SetMsgs([]sdk.Msg{msg}...) + suite.Require().NoError(err) + tx := txBuilder.GetTx() + + _, err = decorator.AnteHandle(checkCtx, tx, false, next) + suite.Require().NoError(err) + + return []sdk.Msg{msg} + }, + channeltypes.ErrRedundantTx, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + // reset suite + suite.SetupTest() + + k := suite.chainB.App.GetIBCKeeper() + decorator := ante.NewRedundantRelayDecorator(k) + + msgs := tc.malleate(suite) + + deliverCtx := suite.chainB.GetContext().WithIsCheckTx(false) + checkCtx := suite.chainB.GetContext().WithIsCheckTx(true) + + // create multimsg tx + txBuilder := suite.chainB.TxConfig.NewTxBuilder() + err := txBuilder.SetMsgs(msgs...) + suite.Require().NoError(err) + tx := txBuilder.GetTx() + + next := func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { return ctx, nil } + + _, err = decorator.AnteHandle(deliverCtx, tx, false, next) + suite.Require().NoError(err, "antedecorator should not error on DeliverTx") + + _, err = decorator.AnteHandle(checkCtx, tx, false, next) + if tc.expError == nil { + suite.Require().NoError(err, "non-strict decorator did not pass as expected") + } else { + suite.Require().ErrorIs(err, tc.expError, "non-strict antehandler did not return error as expected") + } + }) + } +} + +func (suite *AnteTestSuite) TestAnteDecoratorReCheckTx() { + testCases := []struct { + name string + malleate func(suite *AnteTestSuite) []sdk.Msg + expError error + }{ + { + "success on one new RecvPacket message", + func(suite *AnteTestSuite) []sdk.Msg { + // the RecvPacket message has not been submitted to the chain yet, so it will succeed + return []sdk.Msg{suite.createRecvPacketMessage(false)} + }, + nil, + }, + { + "success on one new V2 RecvPacket message", + func(suite *AnteTestSuite) []sdk.Msg { + suite.path.SetupV2() + // the RecvPacket message has not been submitted to the chain yet, so it will succeed + return []sdk.Msg{suite.createRecvPacketMessageV2(false)} + }, + nil, + }, + { + "success on one redundant and one new RecvPacket message", + func(suite *AnteTestSuite) []sdk.Msg { + return []sdk.Msg{ + suite.createRecvPacketMessage(true), + suite.createRecvPacketMessage(false), + } + }, + nil, + }, + { + "success on invalid proof (proof checks occur in checkTx)", + func(suite *AnteTestSuite) []sdk.Msg { + msg := suite.createRecvPacketMessage(false) + msg.ProofCommitment = []byte("invalid-proof") + return []sdk.Msg{msg} + }, + nil, + }, + { + "success on app callback error, app callbacks are skipped for performance", + func(suite *AnteTestSuite) []sdk.Msg { + suite.chainB.GetSimApp().IBCMockModule.IBCApp.OnRecvPacket = func( + ctx sdk.Context, channelVersion string, packet channeltypes.Packet, relayer sdk.AccAddress, + ) exported.Acknowledgement { + panic(errors.New("failed OnRecvPacket mock callback")) + } + + // the RecvPacket message has not been submitted to the chain yet, so it will succeed + return []sdk.Msg{suite.createRecvPacketMessage(false)} + }, + nil, + }, + { + "no success on one redundant RecvPacket message", + func(suite *AnteTestSuite) []sdk.Msg { + return []sdk.Msg{suite.createRecvPacketMessage(true)} + }, + channeltypes.ErrRedundantTx, + }, + { + "no success on one redundant V2 RecvPacket message", + func(suite *AnteTestSuite) []sdk.Msg { + suite.path.SetupV2() + return []sdk.Msg{suite.createRecvPacketMessageV2(true)} + }, + channeltypes.ErrRedundantTx, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + // reset suite + suite.SetupTest() + + k := suite.chainB.App.GetIBCKeeper() + decorator := ante.NewRedundantRelayDecorator(k) + + msgs := tc.malleate(suite) + + deliverCtx := suite.chainB.GetContext().WithIsCheckTx(false) + reCheckCtx := suite.chainB.GetContext().WithIsReCheckTx(true) + + // create multimsg tx + txBuilder := suite.chainB.TxConfig.NewTxBuilder() + err := txBuilder.SetMsgs(msgs...) + suite.Require().NoError(err) + tx := txBuilder.GetTx() + + next := func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, err error) { return ctx, nil } + + _, err = decorator.AnteHandle(deliverCtx, tx, false, next) + suite.Require().NoError(err, "antedecorator should not error on DeliverTx") + + _, err = decorator.AnteHandle(reCheckCtx, tx, false, next) + if tc.expError == nil { + suite.Require().NoError(err, "non-strict decorator did not pass as expected") + } else { + suite.Require().ErrorIs(err, tc.expError, "non-strict antehandler did not return error as expected") + } + }) + } +} diff --git a/modules/core/api/api_test.go b/modules/core/api/api_test.go new file mode 100644 index 0000000..19c678e --- /dev/null +++ b/modules/core/api/api_test.go @@ -0,0 +1,15 @@ +package api_test + +import ( + "testing" + + testifysuite "github.com/stretchr/testify/suite" +) + +type APITestSuite struct { + testifysuite.Suite +} + +func TestApiTestSuite(t *testing.T) { + testifysuite.Run(t, new(APITestSuite)) +} diff --git a/modules/core/api/module.go b/modules/core/api/module.go new file mode 100644 index 0000000..2bfd664 --- /dev/null +++ b/modules/core/api/module.go @@ -0,0 +1,71 @@ +package api + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" +) + +// IBCModule defines an interface that implements all the callbacks +// that modules must define as specified in IBC Protocol V2. +type IBCModule interface { + // OnSendPacket is executed when a packet is being sent from sending chain. + // this callback is provided with the source and destination IDs, the signer, the packet sequence and the packet data + // for this specific application. + OnSendPacket( + ctx sdk.Context, + sourceClient string, + destinationClient string, + sequence uint64, + payload channeltypesv2.Payload, + signer sdk.AccAddress, + ) error + + OnRecvPacket( + ctx sdk.Context, + sourceClient string, + destinationClient string, + sequence uint64, + payload channeltypesv2.Payload, + relayer sdk.AccAddress, + ) channeltypesv2.RecvPacketResult + + // OnTimeoutPacket is executed when a packet has timed out on the receiving chain. + OnTimeoutPacket( + ctx sdk.Context, + sourceClient string, + destinationClient string, + sequence uint64, + payload channeltypesv2.Payload, + relayer sdk.AccAddress, + ) error + + // OnAcknowledgementPacket is executed when a packet gets acknowledged + OnAcknowledgementPacket( + ctx sdk.Context, + sourceClient string, + destinationClient string, + sequence uint64, + acknowledgement []byte, + payload channeltypesv2.Payload, + relayer sdk.AccAddress, + ) error +} + +type WriteAcknowledgementWrapper interface { + // WriteAcknowledgement writes the acknowledgement for an async acknowledgement + WriteAcknowledgement( + ctx sdk.Context, + srcClientID string, + sequence uint64, + ack channeltypesv2.Acknowledgement, + ) error +} + +// PacketDataUnmarshaler defines an optional interface which allows a middleware +// to request the packet data to be unmarshaled by the base application. +type PacketDataUnmarshaler interface { + // UnmarshalPacketData unmarshals the packet data into a concrete type + // the payload is provided and the packet data interface is returned + UnmarshalPacketData(payload channeltypesv2.Payload) (any, error) +} diff --git a/modules/core/api/router.go b/modules/core/api/router.go new file mode 100644 index 0000000..8575c05 --- /dev/null +++ b/modules/core/api/router.go @@ -0,0 +1,129 @@ +package api + +import ( + "errors" + "fmt" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Router contains all the module-defined callbacks required by IBC Protocol V2. +type Router struct { + // routes is a map from portID to IBCModule + routes map[string]IBCModule + // prefixRoutes is a map from portID prefix to IBCModule + prefixRoutes map[string]IBCModule +} + +// NewRouter creates a new Router instance. +func NewRouter() *Router { + return &Router{ + routes: make(map[string]IBCModule), + prefixRoutes: make(map[string]IBCModule), + } +} + +// AddRoute registers a route for a given portID to a given IBCModule. +// +// Panics: +// - if a route with the same portID has already been registered +// - if the portID is not alphanumeric +func (rtr *Router) AddRoute(portID string, cbs IBCModule) *Router { + if !sdk.IsAlphaNumeric(portID) { + panic(errors.New("route expressions can only contain alphanumeric characters")) + } + + if _, ok := rtr.routes[portID]; ok { + panic(fmt.Errorf("route %s has already been registered", portID)) + } + + for prefix := range rtr.prefixRoutes { + // Prevent existing prefix routes from colliding with the new direct route to avoid confusing behavior. + if strings.HasPrefix(portID, prefix) { + panic(fmt.Errorf("route %s is already matched by registered prefix route: %s", portID, prefix)) + } + } + + rtr.routes[portID] = cbs + + return rtr +} + +// AddPrefixRoute registers a route for a given portID prefix to a given IBCModule. +// A prefix route matches any portID that starts with the given prefix. +// +// Panics: +// - if `portIDPrefix` is not alphanumeric. +// - if a direct route `portIDPrefix` has already been registered. +// - if a prefix of `portIDPrefix` is already registered as a prefix. +// - if `portIDPrefix` is a prefix of am already registered prefix. +func (rtr *Router) AddPrefixRoute(portIDPrefix string, cbs IBCModule) *Router { + if !sdk.IsAlphaNumeric(portIDPrefix) { + panic(errors.New("route prefix can only contain alphanumeric characters")) + } + + // If the prefix is a prefix of an already registered route, we panic to avoid confusing behavior. + for portID := range rtr.routes { + if strings.HasPrefix(portID, portIDPrefix) { + panic(fmt.Errorf("route prefix %s is a prefix for already registered route: %s", portIDPrefix, portID)) + } + } + + for prefix := range rtr.prefixRoutes { + // Prevent two scenarios: + // * Adding a string that prefix is already registered e.g. + // add prefix "portPrefix" and try to add "portPrefixSomeSuffix". + // * Adding a string that is a prefix of already registered prefix route e.g. + // add prefix "portPrefix" and try to add "port". + if strings.HasPrefix(portIDPrefix, prefix) { + panic(fmt.Errorf("route prefix %s has already been covered by registered prefix: %s", portIDPrefix, prefix)) + } + if strings.HasPrefix(prefix, portIDPrefix) { + panic(fmt.Errorf("route prefix %s is a prefix for already registered prefix: %s", portIDPrefix, prefix)) + } + } + + rtr.prefixRoutes[portIDPrefix] = cbs + + return rtr +} + +// Route returns the IBCModule for a given portID. +func (rtr *Router) Route(portID string) IBCModule { + cbs, ok := rtr.getRoute(portID) + if !ok { + panic(fmt.Sprintf("no route for %s", portID)) + } + + return cbs +} + +// HasRoute returns true if the Router has a module registered (whether it's a direct or a prefix route) +// for the portID or false if no module is registered for it. +func (rtr *Router) HasRoute(portID string) bool { + _, ok := rtr.getRoute(portID) + return ok +} + +// getRoute is a helper function that retrieves the IBCModule for a given portID. +func (rtr *Router) getRoute(portID string) (IBCModule, bool) { + // Direct routes take precedence over prefix routes + route, ok := rtr.routes[portID] + if ok { + return route, true + } + + // If the portID is not found as a direct route, check for prefix routes + for prefix, cbs := range rtr.prefixRoutes { + // Note that this iteration is deterministic because there can only ever be one prefix route + // that matches a given portID. This is because of the checks in AddPrefixRoute preventing + // any colliding prefixes to be added. + if strings.HasPrefix(portID, prefix) { + return cbs, true + } + } + + // At this point neither a direct route nor a prefix route was found + return nil, false +} diff --git a/modules/core/api/router_test.go b/modules/core/api/router_test.go new file mode 100644 index 0000000..58b927c --- /dev/null +++ b/modules/core/api/router_test.go @@ -0,0 +1,135 @@ +package api_test + +import ( + "github.com/cosmos/ibc-go/v10/modules/core/api" + mockv2 "github.com/cosmos/ibc-go/v10/testing/mock/v2" +) + +func (suite *APITestSuite) TestRouter() { + var router *api.Router + + testCases := []struct { + name string + malleate func() + assertionFn func() + }{ + { + name: "success", + malleate: func() { + router.AddRoute("port01", &mockv2.IBCModule{}) + }, + assertionFn: func() { + suite.Require().True(router.HasRoute("port01")) + }, + }, + { + name: "success: multiple modules", + malleate: func() { + router.AddRoute("port01", &mockv2.IBCModule{}) + router.AddRoute("port02", &mockv2.IBCModule{}) + router.AddRoute("port03", &mockv2.IBCModule{}) + }, + assertionFn: func() { + suite.Require().True(router.HasRoute("port01")) + suite.Require().True(router.HasRoute("port02")) + suite.Require().True(router.HasRoute("port03")) + }, + }, + { + name: "success: prefix based routing works", + malleate: func() { + router.AddPrefixRoute("somemodule", &mockv2.IBCModule{}) + router.AddRoute("port01", &mockv2.IBCModule{}) + }, + assertionFn: func() { + suite.Require().True(router.HasRoute("somemodule")) + suite.Require().True(router.HasRoute("somemoduleport01")) + suite.Require().NotNil(router.Route("somemoduleport01")) + suite.Require().True(router.HasRoute("port01")) + }, + }, + { + name: "failure: panics on adding direct route after overlapping prefix route", + malleate: func() { + router.AddPrefixRoute("someModule", &mockv2.IBCModule{}) + }, + assertionFn: func() { + suite.Require().PanicsWithError("route someModuleWithSpecificPath is already matched by registered prefix route: someModule", func() { + router.AddRoute("someModuleWithSpecificPath", &mockv2.IBCModule{}) + }) + }, + }, + { + name: "failure: panics on adding prefix route after overlapping direct route", + malleate: func() { + router.AddRoute("someModuleWithSpecificPath", &mockv2.IBCModule{}) + }, + assertionFn: func() { + suite.Require().PanicsWithError("route prefix someModule is a prefix for already registered route: someModuleWithSpecificPath", func() { + router.AddPrefixRoute("someModule", &mockv2.IBCModule{}) + }) + }, + }, + { + name: "failure: panics on duplicate route", + malleate: func() { + router.AddRoute("port01", &mockv2.IBCModule{}) + }, + assertionFn: func() { + suite.Require().PanicsWithError("route port01 has already been registered", func() { + router.AddRoute("port01", &mockv2.IBCModule{}) + }) + }, + }, + { + name: "failure: panics on duplicate route / prefix route", + malleate: func() { + router.AddRoute("port01", &mockv2.IBCModule{}) + }, + assertionFn: func() { + suite.Require().PanicsWithError("route prefix port01 is a prefix for already registered route: port01", func() { + router.AddPrefixRoute("port01", &mockv2.IBCModule{}) + }) + }, + }, + { + name: "failure: panics on duplicate prefix route", + malleate: func() { + router.AddPrefixRoute("port01", &mockv2.IBCModule{}) + }, + assertionFn: func() { + suite.Require().PanicsWithError("route prefix port01 has already been covered by registered prefix: port01", func() { + router.AddPrefixRoute("port01", &mockv2.IBCModule{}) + }) + }, + }, + { + name: "failure: panics invalid-name", + malleate: func() {}, + assertionFn: func() { + suite.Require().PanicsWithError("route expressions can only contain alphanumeric characters", func() { + router.AddRoute("port-02", &mockv2.IBCModule{}) + }) + }, + }, + { + name: "failure: panics conflicting prefix routes registered, when shorter prefix is added", + malleate: func() {}, + assertionFn: func() { + suite.Require().PanicsWithError("route prefix someLonger is a prefix for already registered prefix: someLongerPrefixModule", func() { + router.AddPrefixRoute("someLongerPrefixModule", &mockv2.IBCModule{}) + router.AddPrefixRoute("someLonger", &mockv2.IBCModule{}) + }) + }, + }, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + router = api.NewRouter() + + tc.malleate() + + tc.assertionFn() + }) + } +} diff --git a/modules/core/client/cli/cli.go b/modules/core/client/cli/cli.go new file mode 100644 index 0000000..4f33af7 --- /dev/null +++ b/modules/core/client/cli/cli.go @@ -0,0 +1,52 @@ +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + + ibcclient "github.com/cosmos/ibc-go/v10/modules/core/02-client" + connection "github.com/cosmos/ibc-go/v10/modules/core/03-connection" + channel "github.com/cosmos/ibc-go/v10/modules/core/04-channel" + channelv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// GetTxCmd returns the transaction commands for this module +func GetTxCmd() *cobra.Command { + ibcTxCmd := &cobra.Command{ + Use: ibcexported.ModuleName, + Short: "IBC transaction subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + ibcTxCmd.AddCommand( + ibcclient.GetTxCmd(), + channelv2.GetTxCmd(), + ) + + return ibcTxCmd +} + +// GetQueryCmd returns the cli query commands for this module +func GetQueryCmd() *cobra.Command { + // Group ibc queries under a subcommand + ibcQueryCmd := &cobra.Command{ + Use: ibcexported.ModuleName, + Short: "Querying commands for the IBC module", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + ibcQueryCmd.AddCommand( + ibcclient.GetQueryCmd(), + connection.GetQueryCmd(), + channel.GetQueryCmd(), + channelv2.GetQueryCmd(), + ) + + return ibcQueryCmd +} diff --git a/modules/core/client/query.go b/modules/core/client/query.go new file mode 100644 index 0000000..693742a --- /dev/null +++ b/modules/core/client/query.go @@ -0,0 +1,69 @@ +package client + +import ( + "errors" + "fmt" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + + abci "github.com/cometbft/cometbft/abci/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// QueryTendermintProof performs an ABCI query with the given key and returns +// the value of the query, the proto encoded merkle proof, and the height of +// the Tendermint block containing the state root. The desired tendermint height +// to perform the query should be set in the client context. The query will be +// performed at one below this height (at the IAVL version) in order to obtain +// the correct merkle proof. Proof queries at height less than or equal to 2 are +// not supported. Queries with a client context height of 0 will perform a query +// at the latest state available. +// Issue: https://github.com/cosmos/cosmos-sdk/issues/6567 +func QueryTendermintProof(clientCtx client.Context, key []byte) ([]byte, []byte, clienttypes.Height, error) { + height := clientCtx.Height + + // ABCI queries at heights 1, 2 or less than or equal to 0 are not supported. + // Base app does not support queries for height less than or equal to 1. + // Therefore, a query at height 2 would be equivalent to a query at height 3. + // A height of 0 will query with the latest state. + if height != 0 && height <= 2 { + return nil, nil, clienttypes.Height{}, errors.New("proof queries at height <= 2 are not supported") + } + + // Use the IAVL height if a valid tendermint height is passed in. + // A height of 0 will query with the latest state. + if height != 0 { + height-- + } + + req := abci.RequestQuery{ + Path: fmt.Sprintf("store/%s/key", ibcexported.StoreKey), + Height: height, + Data: key, + Prove: true, + } + + res, err := clientCtx.QueryABCI(req) + if err != nil { + return nil, nil, clienttypes.Height{}, err + } + + merkleProof, err := commitmenttypes.ConvertProofs(res.ProofOps) + if err != nil { + return nil, nil, clienttypes.Height{}, err + } + + cdc := codec.NewProtoCodec(clientCtx.InterfaceRegistry) + + proofBz, err := cdc.Marshal(&merkleProof) + if err != nil { + return nil, nil, clienttypes.Height{}, err + } + + revision := clienttypes.ParseChainID(clientCtx.ChainID) + return res.Value, proofBz, clienttypes.NewHeight(revision, uint64(res.Height)+1), nil +} diff --git a/modules/core/errors/errors.go b/modules/core/errors/errors.go new file mode 100644 index 0000000..f1db3c0 --- /dev/null +++ b/modules/core/errors/errors.go @@ -0,0 +1,63 @@ +package errors + +import ( + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +const codespace = exported.ModuleName + +var ( + // ErrInvalidSequence is used the sequence number (nonce) is incorrect + // for the signature. + ErrInvalidSequence = errorsmod.Register(codespace, 1, "invalid sequence") + + // ErrUnauthorized is used whenever a request without sufficient + // authorization is handled. + ErrUnauthorized = errorsmod.Register(codespace, 2, "unauthorized") + + // ErrInsufficientFunds is used when the account cannot pay requested amount. + ErrInsufficientFunds = errorsmod.Register(codespace, 3, "insufficient funds") + + // ErrUnknownRequest is used when the request body. + ErrUnknownRequest = errorsmod.Register(codespace, 4, "unknown request") + + // ErrInvalidAddress is used when an address is found to be invalid. + ErrInvalidAddress = errorsmod.Register(codespace, 5, "invalid address") + + // ErrInvalidCoins is used when sdk.Coins are invalid. + ErrInvalidCoins = errorsmod.Register(codespace, 6, "invalid coins") + + // ErrOutOfGas is used when there is not enough gas. + ErrOutOfGas = errorsmod.Register(codespace, 7, "out of gas") + + // ErrInvalidRequest defines an ABCI typed error where the request contains + // invalid data. + ErrInvalidRequest = errorsmod.Register(codespace, 8, "invalid request") + + // ErrInvalidHeight defines an error for an invalid height + ErrInvalidHeight = errorsmod.Register(codespace, 9, "invalid height") + + // ErrInvalidVersion defines a general error for an invalid version + ErrInvalidVersion = errorsmod.Register(codespace, 10, "invalid version") + + // ErrInvalidChainID defines an error when the chain-id is invalid. + ErrInvalidChainID = errorsmod.Register(codespace, 11, "invalid chain-id") + + // ErrInvalidType defines an error an invalid type. + ErrInvalidType = errorsmod.Register(codespace, 12, "invalid type") + + // ErrPackAny defines an error when packing a protobuf message to Any fails. + ErrPackAny = errorsmod.Register(codespace, 13, "failed packing protobuf message to Any") + + // ErrUnpackAny defines an error when unpacking a protobuf message from Any fails. + ErrUnpackAny = errorsmod.Register(codespace, 14, "failed unpacking protobuf message from Any") + + // ErrLogic defines an internal logic error, e.g. an invariant or assertion + // that is violated. It is a programmer error, not a user-facing error. + ErrLogic = errorsmod.Register(codespace, 15, "internal logic error") + + // ErrNotFound defines an error when requested entity doesn't exist in the state. + ErrNotFound = errorsmod.Register(codespace, 16, "not found") +) diff --git a/modules/core/exported/client.go b/modules/core/exported/client.go new file mode 100644 index 0000000..5be1813 --- /dev/null +++ b/modules/core/exported/client.go @@ -0,0 +1,186 @@ +package exported + +import ( + "github.com/cosmos/gogoproto/proto" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Status represents the status of a client +type Status string + +const ( + // Solomachine is used to indicate that the light client is a solo machine. + Solomachine string = "06-solomachine" + + // Tendermint is used to indicate that the client uses the Tendermint Consensus Algorithm. + Tendermint string = "07-tendermint" + + // Localhost is the client type for the localhost client. + Localhost string = "09-localhost" + + // LocalhostClientID is the sentinel client ID for the localhost client. + LocalhostClientID string = Localhost + + // Active is a status type of a client. An active client is allowed to be used. + Active Status = "Active" + + // Frozen is a status type of a client. A frozen client is not allowed to be used. + Frozen Status = "Frozen" + + // Expired is a status type of a client. An expired client is not allowed to be used. + Expired Status = "Expired" + + // Unknown indicates there was an error in determining the status of a client. + Unknown Status = "Unknown" + + // Unauthorized indicates that the client type is not registered as an allowed client type. + Unauthorized Status = "Unauthorized" +) + +// LightClientModule is an interface which core IBC uses to interact with light client modules. +// Light client modules must implement this interface to integrate with core IBC. +type LightClientModule interface { + // Initialize is called upon client creation, it allows the client to perform validation on the client state and initial consensus state. + // The light client module is responsible for setting any client-specific data in the store. This includes the client state, + // initial consensus state and any associated metadata. + Initialize(ctx sdk.Context, clientID string, clientState, consensusState []byte) error + + // VerifyClientMessage must verify a ClientMessage. A ClientMessage could be a Header, Misbehaviour, or batch update. + // It must handle each type of ClientMessage appropriately. Calls to CheckForMisbehaviour, UpdateState, and UpdateStateOnMisbehaviour + // will assume that the content of the ClientMessage has been verified and can be trusted. An error should be returned + // if the ClientMessage fails to verify. + VerifyClientMessage(ctx sdk.Context, clientID string, clientMsg ClientMessage) error + + // Checks for evidence of a misbehaviour in Header or Misbehaviour type. It assumes the ClientMessage + // has already been verified. + CheckForMisbehaviour(ctx sdk.Context, clientID string, clientMsg ClientMessage) bool + + // UpdateStateOnMisbehaviour should perform appropriate state changes on a client state given that misbehaviour has been detected and verified + UpdateStateOnMisbehaviour(ctx sdk.Context, clientID string, clientMsg ClientMessage) + + // UpdateState updates and stores as necessary any associated information for an IBC client, such as the ClientState and corresponding ConsensusState. + // Upon successful update, a list of consensus heights is returned. It assumes the ClientMessage has already been verified. + UpdateState(ctx sdk.Context, clientID string, clientMsg ClientMessage) []Height + + // VerifyMembership is a generic proof verification method which verifies a proof of the existence of a value at a given CommitmentPath at the specified height. + // The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). + VerifyMembership( + ctx sdk.Context, + clientID string, + height Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path Path, + value []byte, + ) error + + // VerifyNonMembership is a generic proof verification method which verifies the absence of a given CommitmentPath at a specified height. + // The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). + VerifyNonMembership( + ctx sdk.Context, + clientID string, + height Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path Path, + ) error + + // Status must return the status of the client. Only Active clients are allowed to process packets. + Status(ctx sdk.Context, clientID string) Status + + // LatestHeight returns the latest height of the client. If no client is present for the provided client identifier a zero value height may be returned. + LatestHeight(ctx sdk.Context, clientID string) Height + + // TimestampAtHeight must return the timestamp for the consensus state associated with the provided height. + TimestampAtHeight( + ctx sdk.Context, + clientID string, + height Height, + ) (uint64, error) + + // RecoverClient must verify that the provided substitute may be used to update the subject client. + // The light client module must set the updated client and consensus states within the clientStore for the subject client. + RecoverClient(ctx sdk.Context, clientID, substituteClientID string) error + + // Upgrade functions + // NOTE: proof heights are not included as upgrade to a new revision is expected to pass only on the last + // height committed by the current revision. Clients are responsible for ensuring that the planned last + // height of the current revision is somehow encoded in the proof verification process. + // This is to ensure that no premature upgrades occur, since upgrade plans committed to by the counterparty + // may be cancelled or modified before the last planned height. + // If the upgrade is verified, the upgraded client and consensus states must be set in the client store. + VerifyUpgradeAndUpdateState( + ctx sdk.Context, + clientID string, + newClient []byte, + newConsState []byte, + upgradeClientProof, + upgradeConsensusStateProof []byte, + ) error +} + +// ClientState defines the required common functions for light clients. +type ClientState interface { + proto.Message + + ClientType() string + Validate() error +} + +// ConsensusState is the state of the consensus process +type ConsensusState interface { + proto.Message + + ClientType() string // Consensus kind + + // GetTimestamp returns the timestamp (in nanoseconds) of the consensus state + // + // Deprecated: GetTimestamp is not used outside of the light client implementations, + // and therefore it doesn't need to be an interface function. + GetTimestamp() uint64 + + ValidateBasic() error +} + +// ClientMessage is an interface used to update an IBC client. +// The update may be done by a single header, a batch of headers, misbehaviour, or any type which when verified produces +// a change to state of the IBC client +type ClientMessage interface { + proto.Message + + ClientType() string + ValidateBasic() error +} + +// Height is a wrapper interface over clienttypes.Height +// all clients must use the concrete implementation in types +type Height interface { + IsZero() bool + LT(Height) bool + LTE(Height) bool + EQ(Height) bool + GT(Height) bool + GTE(Height) bool + GetRevisionNumber() uint64 + GetRevisionHeight() uint64 + Increment() Height + Decrement() (Height, bool) + String() string +} + +// GenesisMetadata is a wrapper interface over clienttypes.GenesisMetadata +// all clients must use the concrete implementation in types +type GenesisMetadata interface { + // return store key that contains metadata without clientID-prefix + GetKey() []byte + // returns metadata value + GetValue() []byte +} + +// String returns the string representation of a client status. +func (s Status) String() string { + return string(s) +} diff --git a/modules/core/exported/commitment.go b/modules/core/exported/commitment.go new file mode 100644 index 0000000..f7fa2f4 --- /dev/null +++ b/modules/core/exported/commitment.go @@ -0,0 +1,30 @@ +package exported + +// ICS 023 Types Implementation +// +// This file includes types defined under +// https://github.com/cosmos/ibc/tree/master/spec/core/ics-023-vector-commitments + +// spec:Path and spec:Value are defined as bytestring + +// Root implements spec:CommitmentRoot. +// A root is constructed from a set of key-value pairs, +// and the inclusion or non-inclusion of an arbitrary key-value pair +// can be proven with the proof. +type Root interface { + GetHash() []byte + Empty() bool +} + +// Prefix implements spec:CommitmentPrefix. +// Prefix represents the common "prefix" that a set of keys shares. +type Prefix interface { + Bytes() []byte + Empty() bool +} + +// Path implements spec:CommitmentPath. +// A path is the additional information provided to the verification function. +type Path interface { + Empty() bool +} diff --git a/modules/core/exported/connection.go b/modules/core/exported/connection.go new file mode 100644 index 0000000..15107c6 --- /dev/null +++ b/modules/core/exported/connection.go @@ -0,0 +1,4 @@ +package exported + +// LocalhostConnectionID is the sentinel connection ID for the localhost connection. +const LocalhostConnectionID string = "connection-localhost" diff --git a/modules/core/exported/module.go b/modules/core/exported/module.go new file mode 100644 index 0000000..0624a62 --- /dev/null +++ b/modules/core/exported/module.go @@ -0,0 +1,12 @@ +package exported + +const ( + // ModuleName is the name of the IBC module + ModuleName = "ibc" + // StoreKey is the string store representation + StoreKey = ModuleName + // QuerierRoute is the querier route for the IBC module + QuerierRoute = ModuleName + // RouterKey is the msg router key for the IBC module + RouterKey = ModuleName +) diff --git a/modules/core/exported/packet.go b/modules/core/exported/packet.go new file mode 100644 index 0000000..bd3615e --- /dev/null +++ b/modules/core/exported/packet.go @@ -0,0 +1,52 @@ +package exported + +// PacketI defines the standard interface for IBC packets +type PacketI interface { + GetSequence() uint64 + GetTimeoutHeight() Height + GetTimeoutTimestamp() uint64 + GetSourcePort() string + GetSourceChannel() string + GetDestPort() string + GetDestChannel() string + GetData() []byte + ValidateBasic() error +} + +// Acknowledgement defines the interface used to return acknowledgements in the OnRecvPacket callback. +// The Acknowledgement interface is used by core IBC to ensure partial state changes are not committed +// when packet receives have not properly succeeded (typically resulting in an error acknowledgement being returned). +// The interface also allows core IBC to obtain the acknowledgement bytes whose encoding is determined by each IBC application or middleware. +// Each custom acknowledgement type must implement this interface. +type Acknowledgement interface { + // Success determines if the IBC application state should be persisted when handling `RecvPacket`. + // During `OnRecvPacket` IBC application callback execution, all state changes are held in a cache store and committed if: + // - the acknowledgement.Success() returns true + // - a nil acknowledgement is returned (asynchronous acknowledgements) + // + // Note 1: IBC application callback events are always persisted so long as `RecvPacket` succeeds without error. + // + // Note 2: The return value should account for the success of the underlying IBC application or middleware. Thus the `acknowledgement.Success` is representative of the entire IBC stack's success when receiving a packet. The individual success of each acknowledgement associated with an IBC application or middleware must be determined by obtaining the actual acknowledgement type after decoding the acknowledgement bytes. + // + // See https://github.com/cosmos/ibc-go/blob/v7.0.0/docs/ibc/apps.md for further explanations. + Success() bool + Acknowledgement() []byte +} + +// PacketData defines an optional interface which an application's packet data structure may implement. +type PacketData interface { + // GetPacketSender returns the sender address of the packet data. + // If the packet sender is unknown or undefined, an empty string should be returned. + GetPacketSender(sourcePortID string) string +} + +// PacketDataProvider defines an optional interfaces for retrieving custom packet data stored on behalf of another application. +// An existing problem in the IBC middleware design is the inability for a middleware to define its own packet data type and insert packet sender provided information. +// A short term solution was introduced into several application's packet data to utilize a memo field to carry this information on behalf of another application. +// This interfaces standardizes that behaviour. Upon realization of the ability for middleware's to define their own packet data types, this interface will be deprecated and removed with time. +type PacketDataProvider interface { + // GetCustomPacketData returns the packet data held on behalf of another application. + // The name the information is stored under should be provided as the key. + // If no custom packet data exists for the key, nil should be returned. + GetCustomPacketData(key string) any +} diff --git a/modules/core/genesis.go b/modules/core/genesis.go new file mode 100644 index 0000000..fc370d8 --- /dev/null +++ b/modules/core/genesis.go @@ -0,0 +1,34 @@ +package ibc + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + client "github.com/cosmos/ibc-go/v10/modules/core/02-client" + clientv2 "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2" + connection "github.com/cosmos/ibc-go/v10/modules/core/03-connection" + channel "github.com/cosmos/ibc-go/v10/modules/core/04-channel" + channelv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2" + "github.com/cosmos/ibc-go/v10/modules/core/keeper" + "github.com/cosmos/ibc-go/v10/modules/core/types" +) + +// InitGenesis initializes the ibc state from a provided genesis +// state. +func InitGenesis(ctx sdk.Context, k keeper.Keeper, gs *types.GenesisState) { + client.InitGenesis(ctx, k.ClientKeeper, gs.ClientGenesis) + clientv2.InitGenesis(ctx, k.ClientV2Keeper, gs.ClientV2Genesis) + connection.InitGenesis(ctx, k.ConnectionKeeper, gs.ConnectionGenesis) + channel.InitGenesis(ctx, k.ChannelKeeper, gs.ChannelGenesis) + channelv2.InitGenesis(ctx, k.ChannelKeeperV2, gs.ChannelV2Genesis) +} + +// ExportGenesis returns the ibc exported genesis. +func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { + return &types.GenesisState{ + ClientGenesis: client.ExportGenesis(ctx, k.ClientKeeper), + ClientV2Genesis: clientv2.ExportGenesis(ctx, k.ClientV2Keeper), + ConnectionGenesis: connection.ExportGenesis(ctx, k.ConnectionKeeper), + ChannelGenesis: channel.ExportGenesis(ctx, k.ChannelKeeper), + ChannelV2Genesis: channelv2.ExportGenesis(ctx, k.ChannelKeeperV2), + } +} diff --git a/modules/core/genesis_test.go b/modules/core/genesis_test.go new file mode 100644 index 0000000..051f9da --- /dev/null +++ b/modules/core/genesis_test.go @@ -0,0 +1,476 @@ +package ibc_test + +import ( + "errors" + "fmt" + "testing" + + proto "github.com/cosmos/gogoproto/proto" + testifysuite "github.com/stretchr/testify/suite" + + "github.com/cosmos/cosmos-sdk/codec" + + ibc "github.com/cosmos/ibc-go/v10/modules/core" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + clientv2types "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + channelv2types "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + "github.com/cosmos/ibc-go/v10/modules/core/types" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + mockv2 "github.com/cosmos/ibc-go/v10/testing/mock/v2" + "github.com/cosmos/ibc-go/v10/testing/simapp" +) + +const ( + connectionID = "connection-0" + clientID = "07-tendermint-0" + connectionID2 = "connection-1" + clientID2 = "07-tendermint-1" + + port1 = "firstport" + port2 = "secondport" + + channel1 = "channel-0" + channel2 = "channel-1" +) + +var clientHeight = clienttypes.NewHeight(1, 10) + +type IBCTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain +} + +// SetupTest creates a coordinator with 2 test chains. +func (suite *IBCTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) +} + +func TestIBCTestSuite(t *testing.T) { + testifysuite.Run(t, new(IBCTestSuite)) +} + +func (suite *IBCTestSuite) TestValidateGenesis() { + header := suite.chainA.CreateTMClientHeader(suite.chainA.ChainID, suite.chainA.ProposedHeader.Height, clienttypes.NewHeight(0, uint64(suite.chainA.ProposedHeader.Height-1)), suite.chainA.ProposedHeader.Time, suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) + + testCases := []struct { + name string + genState *types.GenesisState + expError error + }{ + { + name: "default", + genState: types.DefaultGenesisState(), + expError: nil, + }, + { + name: "valid genesis", + genState: &types.GenesisState{ + ClientGenesis: clienttypes.NewGenesisState( + []clienttypes.IdentifiedClientState{ + clienttypes.NewIdentifiedClientState( + clientID, ibctm.NewClientState(suite.chainA.ChainID, ibctm.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), + ), + }, + []clienttypes.ClientConsensusStates{ + clienttypes.NewClientConsensusStates( + clientID, + []clienttypes.ConsensusStateWithHeight{ + clienttypes.NewConsensusStateWithHeight( + header.GetHeight().(clienttypes.Height), + ibctm.NewConsensusState( + header.GetTime(), commitmenttypes.NewMerkleRoot(header.Header.AppHash), header.Header.NextValidatorsHash, + ), + ), + }, + ), + }, + []clienttypes.IdentifiedGenesisMetadata{ + clienttypes.NewIdentifiedGenesisMetadata( + clientID, + []clienttypes.GenesisMetadata{ + clienttypes.NewGenesisMetadata([]byte("key1"), []byte("val1")), + clienttypes.NewGenesisMetadata([]byte("key2"), []byte("val2")), + }, + ), + }, + clienttypes.NewParams(exported.Tendermint), + false, + 2, + ), + ClientV2Genesis: clientv2types.GenesisState{ + CounterpartyInfos: []clientv2types.GenesisCounterpartyInfo{ + { + ClientId: "test-1", + CounterpartyInfo: clientv2types.NewCounterpartyInfo([][]byte{{0o1}}, "test-0"), + }, + { + ClientId: "test-0", + CounterpartyInfo: clientv2types.NewCounterpartyInfo([][]byte{{0o1}}, "test-1"), + }, + }, + }, + ConnectionGenesis: connectiontypes.NewGenesisState( + []connectiontypes.IdentifiedConnection{ + connectiontypes.NewIdentifiedConnection(connectionID, connectiontypes.NewConnectionEnd(connectiontypes.INIT, clientID, connectiontypes.NewCounterparty(clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))), []*connectiontypes.Version{ibctesting.ConnectionVersion}, 0)), + }, + []connectiontypes.ConnectionPaths{ + connectiontypes.NewConnectionPaths(clientID, []string{connectionID}), + }, + 0, + connectiontypes.NewParams(10), + ), + ChannelGenesis: channeltypes.NewGenesisState( + []channeltypes.IdentifiedChannel{ + channeltypes.NewIdentifiedChannel( + port1, channel1, channeltypes.NewChannel( + channeltypes.INIT, channeltypes.ORDERED, + channeltypes.NewCounterparty(port2, channel2), []string{connectionID}, ibctesting.DefaultChannelVersion, + ), + ), + }, + []channeltypes.PacketState{ + channeltypes.NewPacketState(port2, channel2, 1, []byte("ack")), + }, + []channeltypes.PacketState{ + channeltypes.NewPacketState(port2, channel2, 1, []byte("")), + }, + []channeltypes.PacketState{ + channeltypes.NewPacketState(port1, channel1, 1, []byte("commit_hash")), + }, + []channeltypes.PacketSequence{ + channeltypes.NewPacketSequence(port1, channel1, 1), + }, + []channeltypes.PacketSequence{ + channeltypes.NewPacketSequence(port2, channel2, 1), + }, + []channeltypes.PacketSequence{ + channeltypes.NewPacketSequence(port2, channel2, 1), + }, + 0, + ), + ChannelV2Genesis: channelv2types.NewGenesisState( + []channelv2types.PacketState{ + channelv2types.NewPacketState(channel2, 1, []byte("ack")), + }, + []channelv2types.PacketState{ + channelv2types.NewPacketState(channel2, 1, []byte("")), + }, + []channelv2types.PacketState{ + channelv2types.NewPacketState(channel1, 1, []byte("commit_hash")), + }, + []channelv2types.PacketState{ + channelv2types.NewPacketState(channel2, 1, []byte("async_packet")), + }, + []channelv2types.PacketSequence{ + channelv2types.NewPacketSequence(channel1, 1), + }, + ), + }, + expError: nil, + }, + { + name: "invalid client genesis", + genState: &types.GenesisState{ + ClientGenesis: clienttypes.NewGenesisState( + []clienttypes.IdentifiedClientState{ + clienttypes.NewIdentifiedClientState( + clientID, ibctm.NewClientState(suite.chainA.ChainID, ibctm.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), + ), + }, + nil, + []clienttypes.IdentifiedGenesisMetadata{ + clienttypes.NewIdentifiedGenesisMetadata( + clientID, + []clienttypes.GenesisMetadata{ + clienttypes.NewGenesisMetadata([]byte(""), []byte("val1")), + clienttypes.NewGenesisMetadata([]byte("key2"), []byte("")), + }, + ), + }, + clienttypes.NewParams(exported.Tendermint), + false, + 2, + ), + ClientV2Genesis: clientv2types.DefaultGenesisState(), + ConnectionGenesis: connectiontypes.DefaultGenesisState(), + ChannelV2Genesis: channelv2types.DefaultGenesisState(), + }, + expError: errors.New("genesis metadata key cannot be empty"), + }, + { + name: "invalid connection genesis", + genState: &types.GenesisState{ + ClientGenesis: clienttypes.DefaultGenesisState(), + ClientV2Genesis: clientv2types.DefaultGenesisState(), + ConnectionGenesis: connectiontypes.NewGenesisState( + []connectiontypes.IdentifiedConnection{ + connectiontypes.NewIdentifiedConnection(connectionID, connectiontypes.NewConnectionEnd(connectiontypes.INIT, "(CLIENTIDONE)", connectiontypes.NewCounterparty(clientID, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))), []*connectiontypes.Version{connectiontypes.NewVersion("1.1", nil)}, 0)), + }, + []connectiontypes.ConnectionPaths{ + connectiontypes.NewConnectionPaths(clientID, []string{connectionID}), + }, + 0, + connectiontypes.Params{}, + ), + ChannelV2Genesis: channelv2types.DefaultGenesisState(), + }, + expError: errors.New("invalid connection"), + }, + { + name: "invalid channel genesis", + genState: &types.GenesisState{ + ClientGenesis: clienttypes.DefaultGenesisState(), + ClientV2Genesis: clientv2types.DefaultGenesisState(), + ConnectionGenesis: connectiontypes.DefaultGenesisState(), + ChannelGenesis: channeltypes.GenesisState{ + Acknowledgements: []channeltypes.PacketState{ + channeltypes.NewPacketState("(portID)", channel1, 1, []byte("ack")), + }, + }, + ChannelV2Genesis: channelv2types.DefaultGenesisState(), + }, + expError: errors.New("invalid acknowledgement"), + }, + { + name: "invalid channel v2 genesis", + genState: &types.GenesisState{ + ClientGenesis: clienttypes.DefaultGenesisState(), + ConnectionGenesis: connectiontypes.DefaultGenesisState(), + ChannelGenesis: channeltypes.DefaultGenesisState(), + ChannelV2Genesis: channelv2types.GenesisState{ + Acknowledgements: []channelv2types.PacketState{ + channelv2types.NewPacketState(channel1, 1, nil), + }, + }, + }, + expError: errors.New("invalid acknowledgement"), + }, + { + name: "invalid clientv2 genesis", + genState: &types.GenesisState{ + ClientGenesis: clienttypes.DefaultGenesisState(), + ClientV2Genesis: clientv2types.GenesisState{ + CounterpartyInfos: []clientv2types.GenesisCounterpartyInfo{ + { + ClientId: "", + CounterpartyInfo: clientv2types.NewCounterpartyInfo([][]byte{{0o1}}, "test-0"), + }, + { + ClientId: "test-0", + CounterpartyInfo: clientv2types.NewCounterpartyInfo([][]byte{{0o1}}, "test-1"), + }, + }, + }, + ConnectionGenesis: connectiontypes.DefaultGenesisState(), + ChannelGenesis: channeltypes.DefaultGenesisState(), + }, + expError: errors.New("counterparty client id cannot be empty"), + }, + } + + for _, tc := range testCases { + tc := tc + err := tc.genState.Validate() + if tc.expError == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().Error(err, tc.name) + suite.Require().Contains(err.Error(), tc.expError.Error()) + } + } +} + +func (suite *IBCTestSuite) TestInitGenesis() { + header := suite.chainA.CreateTMClientHeader(suite.chainA.ChainID, suite.chainA.ProposedHeader.Height, clienttypes.NewHeight(0, uint64(suite.chainA.ProposedHeader.Height-1)), suite.chainA.ProposedHeader.Time, suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Vals, suite.chainA.Signers) + + packet := channelv2types.NewPacket( + 1, "07-tendermint-0", "07-tendermint-1", + uint64(suite.chainA.GetContext().BlockTime().Unix()), mockv2.NewMockPayload("src", "dst"), + ) + bz, err := proto.Marshal(&packet) + suite.Require().NoError(err) + + testCases := []struct { + name string + genState *types.GenesisState + }{ + { + name: "default", + genState: types.DefaultGenesisState(), + }, + { + name: "valid genesis", + genState: &types.GenesisState{ + ClientGenesis: clienttypes.NewGenesisState( + []clienttypes.IdentifiedClientState{ + clienttypes.NewIdentifiedClientState( + clientID, ibctm.NewClientState(suite.chainA.ChainID, ibctm.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod, ibctesting.MaxClockDrift, clientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath), + ), + }, + []clienttypes.ClientConsensusStates{ + clienttypes.NewClientConsensusStates( + clientID, + []clienttypes.ConsensusStateWithHeight{ + clienttypes.NewConsensusStateWithHeight( + header.GetHeight().(clienttypes.Height), + ibctm.NewConsensusState( + header.GetTime(), commitmenttypes.NewMerkleRoot(header.Header.AppHash), header.Header.NextValidatorsHash, + ), + ), + }, + ), + }, + []clienttypes.IdentifiedGenesisMetadata{ + clienttypes.NewIdentifiedGenesisMetadata( + clientID, + []clienttypes.GenesisMetadata{ + clienttypes.NewGenesisMetadata([]byte("key1"), []byte("val1")), + clienttypes.NewGenesisMetadata([]byte("key2"), []byte("val2")), + }, + ), + }, + clienttypes.NewParams(exported.Tendermint), + false, + 0, + ), + ClientV2Genesis: clientv2types.GenesisState{ + CounterpartyInfos: []clientv2types.GenesisCounterpartyInfo{ + { + ClientId: "test-1", + CounterpartyInfo: clientv2types.NewCounterpartyInfo([][]byte{{0o1}}, "test-0"), + }, + { + ClientId: "test-0", + CounterpartyInfo: clientv2types.NewCounterpartyInfo([][]byte{{0o1}}, "test-1"), + }, + }, + }, + ConnectionGenesis: connectiontypes.NewGenesisState( + []connectiontypes.IdentifiedConnection{ + connectiontypes.NewIdentifiedConnection(connectionID, connectiontypes.NewConnectionEnd(connectiontypes.INIT, clientID, connectiontypes.NewCounterparty(clientID2, connectionID2, commitmenttypes.NewMerklePrefix([]byte("prefix"))), []*connectiontypes.Version{ibctesting.ConnectionVersion}, 0)), + }, + []connectiontypes.ConnectionPaths{ + connectiontypes.NewConnectionPaths(clientID, []string{connectionID}), + }, + 0, + connectiontypes.NewParams(10), + ), + ChannelGenesis: channeltypes.NewGenesisState( + []channeltypes.IdentifiedChannel{ + channeltypes.NewIdentifiedChannel( + port1, channel1, channeltypes.NewChannel( + channeltypes.INIT, channeltypes.ORDERED, + channeltypes.NewCounterparty(port2, channel2), []string{connectionID}, ibctesting.DefaultChannelVersion, + ), + ), + }, + []channeltypes.PacketState{ + channeltypes.NewPacketState(port2, channel2, 1, []byte("ack")), + }, + []channeltypes.PacketState{ + channeltypes.NewPacketState(port2, channel2, 1, []byte("")), + }, + []channeltypes.PacketState{ + channeltypes.NewPacketState(port1, channel1, 1, []byte("commit_hash")), + }, + []channeltypes.PacketSequence{ + channeltypes.NewPacketSequence(port1, channel1, 1), + }, + []channeltypes.PacketSequence{ + channeltypes.NewPacketSequence(port2, channel2, 1), + }, + []channeltypes.PacketSequence{ + channeltypes.NewPacketSequence(port2, channel2, 1), + }, + 0, + ), + ChannelV2Genesis: channelv2types.NewGenesisState( + []channelv2types.PacketState{ + channelv2types.NewPacketState(channel2, 1, []byte("ack")), + }, + []channelv2types.PacketState{ + channelv2types.NewPacketState(channel2, 1, []byte("")), + }, + []channelv2types.PacketState{ + channelv2types.NewPacketState(channel1, 1, []byte("commit_hash")), + }, + []channelv2types.PacketState{ + channelv2types.NewPacketState(channel2, 1, bz), + }, + []channelv2types.PacketSequence{ + channelv2types.NewPacketSequence(channel1, 1), + }, + ), + }, + }, + } + + for _, tc := range testCases { + tc := tc + + app := simapp.Setup(suite.T(), false) + + suite.NotPanics(func() { + ibc.InitGenesis(app.NewContext(false), *app.IBCKeeper, tc.genState) + }) + } +} + +func (suite *IBCTestSuite) TestExportGenesis() { + testCases := []struct { + msg string + malleate func() + }{ + { + "success", + func() { + // creates clients + ibctesting.NewPath(suite.chainA, suite.chainB).Setup() + // create extra clients + ibctesting.NewPath(suite.chainA, suite.chainB).SetupClients() + ibctesting.NewPath(suite.chainA, suite.chainB).SetupClients() + }, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() + + tc.malleate() + + var gs *types.GenesisState + suite.NotPanics(func() { + gs = ibc.ExportGenesis(suite.chainA.GetContext(), *suite.chainA.App.GetIBCKeeper()) + }) + + // init genesis based on export + suite.NotPanics(func() { + ibc.InitGenesis(suite.chainA.GetContext(), *suite.chainA.App.GetIBCKeeper(), gs) + }) + + suite.NotPanics(func() { + cdc := codec.NewProtoCodec(suite.chainA.GetSimApp().InterfaceRegistry()) + genState := cdc.MustMarshalJSON(gs) + cdc.MustUnmarshalJSON(genState, gs) + }) + + // init genesis based on marshal and unmarshal + suite.NotPanics(func() { + ibc.InitGenesis(suite.chainA.GetContext(), *suite.chainA.App.GetIBCKeeper(), gs) + }) + }) + } +} diff --git a/modules/core/internal/errors/errors.go b/modules/core/internal/errors/errors.go new file mode 100644 index 0000000..6958106 --- /dev/null +++ b/modules/core/internal/errors/errors.go @@ -0,0 +1,25 @@ +package errors + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + coretypes "github.com/cosmos/ibc-go/v10/modules/core/types" +) + +// ConvertToErrorEvents converts all events to error events by appending the +// error attribute prefix to each event's attribute key. +func ConvertToErrorEvents(events sdk.Events) sdk.Events { + if events == nil { + return nil + } + + newEvents := make(sdk.Events, len(events)) + for i, event := range events { + newEvents[i] = sdk.NewEvent(coretypes.ErrorAttributeKeyPrefix + event.Type) + for _, attribute := range event.Attributes { + newEvents[i] = newEvents[i].AppendAttributes(sdk.NewAttribute(coretypes.ErrorAttributeKeyPrefix+attribute.Key, attribute.Value)) + } + } + + return newEvents +} diff --git a/modules/core/internal/telemetry/client.go b/modules/core/internal/telemetry/client.go new file mode 100644 index 0000000..eb4406c --- /dev/null +++ b/modules/core/internal/telemetry/client.go @@ -0,0 +1,58 @@ +package telemetry + +import ( + metrics "github.com/hashicorp/go-metrics" + + "github.com/cosmos/cosmos-sdk/telemetry" + + ibcmetrics "github.com/cosmos/ibc-go/v10/modules/core/metrics" +) + +func ReportCreateClient(clientType string) { + telemetry.IncrCounterWithLabels( + []string{"ibc", "client", "create"}, + 1, + []metrics.Label{telemetry.NewLabel(ibcmetrics.LabelClientType, clientType)}, + ) +} + +func ReportUpdateClient(foundMisbehaviour bool, clientType, clientID string) { + labels := []metrics.Label{ + telemetry.NewLabel(ibcmetrics.LabelClientType, clientType), + telemetry.NewLabel(ibcmetrics.LabelClientID, clientID), + } + + var updateType string + if foundMisbehaviour { + labels = append(labels, telemetry.NewLabel(ibcmetrics.LabelMsgType, "update")) + updateType = "misbehaviour" + } else { + labels = append(labels, telemetry.NewLabel(ibcmetrics.LabelUpdateType, "msg")) + updateType = "update" + } + + telemetry.IncrCounterWithLabels([]string{"ibc", "client", updateType}, 1, labels) +} + +func ReportUpgradeClient(clientType, clientID string) { + telemetry.IncrCounterWithLabels( + []string{"ibc", "client", "upgrade"}, + 1, + []metrics.Label{ + telemetry.NewLabel(ibcmetrics.LabelClientType, clientType), + telemetry.NewLabel(ibcmetrics.LabelClientID, clientID), + }, + ) +} + +func ReportRecoverClient(clientType, subjectClientID string) { + telemetry.IncrCounterWithLabels( + []string{"ibc", "client", "update"}, + 1, + []metrics.Label{ + telemetry.NewLabel(ibcmetrics.LabelClientType, clientType), + telemetry.NewLabel(ibcmetrics.LabelClientID, subjectClientID), + telemetry.NewLabel(ibcmetrics.LabelUpdateType, "recovery"), + }, + ) +} diff --git a/modules/core/internal/telemetry/packet.go b/modules/core/internal/telemetry/packet.go new file mode 100644 index 0000000..b1ce5a7 --- /dev/null +++ b/modules/core/internal/telemetry/packet.go @@ -0,0 +1,44 @@ +package telemetry + +import ( + metrics "github.com/hashicorp/go-metrics" + + "github.com/cosmos/cosmos-sdk/telemetry" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibcmetrics "github.com/cosmos/ibc-go/v10/modules/core/metrics" +) + +func ReportRecvPacket(packet types.Packet) { + telemetry.IncrCounterWithLabels( + []string{"tx", "msg", "ibc", types.EventTypeRecvPacket}, + 1, + addPacketLabels(packet), + ) +} + +func ReportTimeoutPacket(packet types.Packet, timeoutType string) { + labels := append(addPacketLabels(packet), telemetry.NewLabel(ibcmetrics.LabelTimeoutType, timeoutType)) + telemetry.IncrCounterWithLabels( + []string{"ibc", "timeout", "packet"}, + 1, + labels, + ) +} + +func ReportAcknowledgePacket(packet types.Packet) { + telemetry.IncrCounterWithLabels( + []string{"tx", "msg", "ibc", types.EventTypeAcknowledgePacket}, + 1, + addPacketLabels(packet), + ) +} + +func addPacketLabels(packet types.Packet) []metrics.Label { + return []metrics.Label{ + telemetry.NewLabel(ibcmetrics.LabelSourcePort, packet.SourcePort), + telemetry.NewLabel(ibcmetrics.LabelSourceChannel, packet.SourceChannel), + telemetry.NewLabel(ibcmetrics.LabelDestinationPort, packet.DestinationPort), + telemetry.NewLabel(ibcmetrics.LabelDestinationChannel, packet.DestinationChannel), + } +} diff --git a/modules/core/internal/v2/telemetry/packet.go b/modules/core/internal/v2/telemetry/packet.go new file mode 100644 index 0000000..d118d9a --- /dev/null +++ b/modules/core/internal/v2/telemetry/packet.go @@ -0,0 +1,56 @@ +package telemetry + +import ( + metrics "github.com/hashicorp/go-metrics" + + "github.com/cosmos/cosmos-sdk/telemetry" + + "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + ibcmetrics "github.com/cosmos/ibc-go/v10/modules/core/metrics" +) + +func ReportRecvPacket(packet types.Packet) { + for _, payload := range packet.Payloads { + telemetry.IncrCounterWithLabels( + []string{"tx", "msg", "ibc", types.EventTypeRecvPacket}, + 1, + []metrics.Label{ + telemetry.NewLabel(ibcmetrics.LabelSourcePort, payload.SourcePort), + telemetry.NewLabel(ibcmetrics.LabelSourceChannel, packet.SourceClient), + telemetry.NewLabel(ibcmetrics.LabelDestinationPort, payload.DestinationPort), + telemetry.NewLabel(ibcmetrics.LabelDestinationChannel, packet.DestinationClient), + }, + ) + } +} + +func ReportTimeoutPacket(packet types.Packet) { + for _, payload := range packet.Payloads { + telemetry.IncrCounterWithLabels( + []string{"ibc", "timeout", "packet"}, + 1, + []metrics.Label{ + telemetry.NewLabel(ibcmetrics.LabelSourcePort, payload.SourcePort), + telemetry.NewLabel(ibcmetrics.LabelSourceChannel, packet.SourceClient), + telemetry.NewLabel(ibcmetrics.LabelDestinationPort, payload.DestinationPort), + telemetry.NewLabel(ibcmetrics.LabelDestinationChannel, packet.DestinationClient), + telemetry.NewLabel(ibcmetrics.LabelTimeoutType, "height"), + }, + ) + } +} + +func ReportAcknowledgePacket(packet types.Packet) { + for _, payload := range packet.Payloads { + telemetry.IncrCounterWithLabels( + []string{"tx", "msg", "ibc", types.EventTypeAcknowledgePacket}, + 1, + []metrics.Label{ + telemetry.NewLabel(ibcmetrics.LabelSourcePort, payload.SourcePort), + telemetry.NewLabel(ibcmetrics.LabelSourceChannel, packet.SourceClient), + telemetry.NewLabel(ibcmetrics.LabelDestinationPort, payload.DestinationPort), + telemetry.NewLabel(ibcmetrics.LabelDestinationChannel, packet.DestinationClient), + }, + ) + } +} diff --git a/modules/core/keeper/events_test.go b/modules/core/keeper/events_test.go new file mode 100644 index 0000000..19a2139 --- /dev/null +++ b/modules/core/keeper/events_test.go @@ -0,0 +1,106 @@ +package keeper_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + + internalerrors "github.com/cosmos/ibc-go/v10/modules/core/internal/errors" + "github.com/cosmos/ibc-go/v10/modules/core/types" +) + +func TestConvertToErrorEvents(t *testing.T) { + var ( + events sdk.Events + expEvents sdk.Events + ) + + tc := []struct { + name string + malleate func() + }{ + { + "success: nil events", + func() { + events = nil + expEvents = nil + }, + }, + { + "success: empty events", + func() { + events = sdk.Events{} + expEvents = sdk.Events{} + }, + }, + { + "success: event with no attributes", + func() { + events = sdk.Events{ + sdk.NewEvent("testevent"), + } + expEvents = sdk.Events{ + sdk.NewEvent(types.ErrorAttributeKeyPrefix + "testevent"), + } + }, + }, + { + "success: event with attributes", + func() { + events = sdk.Events{ + sdk.NewEvent("testevent", + sdk.NewAttribute("key1", "value1"), + sdk.NewAttribute("key2", "value2"), + ), + } + expEvents = sdk.Events{ + sdk.NewEvent(types.ErrorAttributeKeyPrefix+"testevent", + sdk.NewAttribute(types.ErrorAttributeKeyPrefix+"key1", "value1"), + sdk.NewAttribute(types.ErrorAttributeKeyPrefix+"key2", "value2"), + ), + } + }, + }, + { + "success: multiple events with attributes", + func() { + events = sdk.Events{ + sdk.NewEvent("testevent1", + sdk.NewAttribute("key1", "value1"), + sdk.NewAttribute("key2", "value2"), + ), + sdk.NewEvent("testevent2", + sdk.NewAttribute("key3", "value3"), + sdk.NewAttribute("key4", "value4"), + ), + } + expEvents = sdk.Events{ + sdk.NewEvent(types.ErrorAttributeKeyPrefix+"testevent1", + sdk.NewAttribute(types.ErrorAttributeKeyPrefix+"key1", "value1"), + sdk.NewAttribute(types.ErrorAttributeKeyPrefix+"key2", "value2"), + ), + sdk.NewEvent(types.ErrorAttributeKeyPrefix+"testevent2", + sdk.NewAttribute(types.ErrorAttributeKeyPrefix+"key3", "value3"), + sdk.NewAttribute(types.ErrorAttributeKeyPrefix+"key4", "value4"), + ), + } + }, + }, + } + + for _, tc := range tc { + t.Run(tc.name, func(t *testing.T) { + // initial events and expected events are reset so that the test fails if + // the malleate function does not set them + events = nil + expEvents = sdk.Events{} + + tc.malleate() + + newEvents := internalerrors.ConvertToErrorEvents(events) + require.Equal(t, expEvents, newEvents) + }) + } +} diff --git a/modules/core/keeper/expected_keeper.go b/modules/core/keeper/expected_keeper.go new file mode 100644 index 0000000..d83e4fc --- /dev/null +++ b/modules/core/keeper/expected_keeper.go @@ -0,0 +1,38 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +type PacketHandler interface { + RecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + proof []byte, + proofHeight exported.Height) (string, error) + + WriteAcknowledgement( + ctx sdk.Context, + packet exported.PacketI, + acknowledgement exported.Acknowledgement, + ) error + + AcknowledgePacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + proof []byte, + proofHeight exported.Height, + ) (string, error) + + TimeoutPacket( + ctx sdk.Context, + packet channeltypes.Packet, + proof []byte, + proofHeight exported.Height, + nextSequenceRecv uint64, + ) (string, error) +} diff --git a/modules/core/keeper/keeper.go b/modules/core/keeper/keeper.go new file mode 100644 index 0000000..372571a --- /dev/null +++ b/modules/core/keeper/keeper.go @@ -0,0 +1,111 @@ +package keeper + +import ( + "errors" + "reflect" + "strings" + + corestore "cosmossdk.io/core/store" + + "github.com/cosmos/cosmos-sdk/codec" + + clientkeeper "github.com/cosmos/ibc-go/v10/modules/core/02-client/keeper" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + clientv2keeper "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/keeper" + connectionkeeper "github.com/cosmos/ibc-go/v10/modules/core/03-connection/keeper" + channelkeeper "github.com/cosmos/ibc-go/v10/modules/core/04-channel/keeper" + channelkeeperv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/keeper" + portkeeper "github.com/cosmos/ibc-go/v10/modules/core/05-port/keeper" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + "github.com/cosmos/ibc-go/v10/modules/core/api" + "github.com/cosmos/ibc-go/v10/modules/core/types" +) + +// Keeper defines each ICS keeper for IBC +type Keeper struct { + ClientKeeper *clientkeeper.Keeper + ClientV2Keeper *clientv2keeper.Keeper + ConnectionKeeper *connectionkeeper.Keeper + ChannelKeeper *channelkeeper.Keeper + ChannelKeeperV2 *channelkeeperv2.Keeper + PortKeeper *portkeeper.Keeper + + cdc codec.BinaryCodec + + authority string +} + +// NewKeeper creates a new ibc Keeper +func NewKeeper( + cdc codec.BinaryCodec, storeService corestore.KVStoreService, paramSpace types.ParamSubspace, + upgradeKeeper clienttypes.UpgradeKeeper, authority string, +) *Keeper { + // panic if any of the keepers passed in is empty + if isEmpty(upgradeKeeper) { + panic(errors.New("cannot initialize IBC keeper: empty upgrade keeper")) + } + + if strings.TrimSpace(authority) == "" { + panic(errors.New("authority must be non-empty")) + } + + clientKeeper := clientkeeper.NewKeeper(cdc, storeService, paramSpace, upgradeKeeper) + clientV2Keeper := clientv2keeper.NewKeeper(cdc, clientKeeper) + connectionKeeper := connectionkeeper.NewKeeper(cdc, storeService, paramSpace, clientKeeper) + portKeeper := portkeeper.NewKeeper() + channelKeeper := channelkeeper.NewKeeper(cdc, storeService, clientKeeper, connectionKeeper) + channelKeeperV2 := channelkeeperv2.NewKeeper(cdc, storeService, clientKeeper, clientV2Keeper, channelKeeper, connectionKeeper) + + return &Keeper{ + cdc: cdc, + ClientKeeper: clientKeeper, + ClientV2Keeper: clientV2Keeper, + ConnectionKeeper: connectionKeeper, + ChannelKeeper: channelKeeper, + ChannelKeeperV2: channelKeeperV2, + PortKeeper: portKeeper, + authority: authority, + } +} + +// Codec returns the IBC module codec. +func (k *Keeper) Codec() codec.BinaryCodec { + return k.cdc +} + +// SetRouter sets the Router in IBC Keeper and seals it. The method panics if +// there is an existing router that's already sealed. +func (k *Keeper) SetRouter(rtr *porttypes.Router) { + if k.PortKeeper.Router != nil && k.PortKeeper.Router.Sealed() { + panic(errors.New("cannot reset a sealed router")) + } + + k.PortKeeper.Router = rtr + k.PortKeeper.Router.Seal() +} + +// SetRouterV2 sets the v2 router for the IBC Keeper. +func (k *Keeper) SetRouterV2(rtr *api.Router) { + k.ChannelKeeperV2.Router = rtr +} + +// GetAuthority returns the ibc module's authority. +func (k *Keeper) GetAuthority() string { + return k.authority +} + +// isEmpty checks if the interface is an empty struct or a pointer pointing +// to an empty struct +func isEmpty(keeper any) bool { + switch reflect.TypeOf(keeper).Kind() { + case reflect.Ptr: + if reflect.ValueOf(keeper).Elem().IsZero() { + return true + } + default: + if reflect.ValueOf(keeper).IsZero() { + return true + } + } + return false +} diff --git a/modules/core/keeper/keeper_test.go b/modules/core/keeper/keeper_test.go new file mode 100644 index 0000000..122194f --- /dev/null +++ b/modules/core/keeper/keeper_test.go @@ -0,0 +1,125 @@ +package keeper_test + +import ( + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + upgradekeeper "cosmossdk.io/x/upgrade/keeper" + + "github.com/cosmos/cosmos-sdk/runtime" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibckeeper "github.com/cosmos/ibc-go/v10/modules/core/keeper" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +type KeeperTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain +} + +func (suite *KeeperTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) + + // TODO: remove + // commit some blocks so that QueryProof returns valid proof (cannot return valid query if height <= 1) + suite.coordinator.CommitNBlocks(suite.chainA, 2) + suite.coordinator.CommitNBlocks(suite.chainB, 2) +} + +func TestKeeperTestSuite(t *testing.T) { + testifysuite.Run(t, new(KeeperTestSuite)) +} + +// Test ibckeeper.NewKeeper used to initialize IBCKeeper when creating an app instance. +// It verifies if ibckeeper.NewKeeper panic when any of the keepers passed in is empty. +func (suite *KeeperTestSuite) TestNewKeeper() { + var ( + upgradeKeeper clienttypes.UpgradeKeeper + newIBCKeeperFn func() + ) + + testCases := []struct { + name string + malleate func() + expPanic string + }{ + { + name: "failure: empty upgrade keeper value", + malleate: func() { + emptyUpgradeKeeperValue := upgradekeeper.Keeper{} + upgradeKeeper = emptyUpgradeKeeperValue + }, + expPanic: "cannot initialize IBC keeper: empty upgrade keeper", + }, + { + name: "failure: empty upgrade keeper pointer", + malleate: func() { + emptyUpgradeKeeperPointer := &upgradekeeper.Keeper{} + upgradeKeeper = emptyUpgradeKeeperPointer + }, + expPanic: "cannot initialize IBC keeper: empty upgrade keeper", + }, + { + name: "failure: empty authority", + malleate: func() { + newIBCKeeperFn = func() { + ibckeeper.NewKeeper( + suite.chainA.GetSimApp().AppCodec(), + runtime.NewKVStoreService(suite.chainA.GetSimApp().GetKey(ibcexported.StoreKey)), + suite.chainA.GetSimApp().GetSubspace(ibcexported.ModuleName), + upgradeKeeper, + "", // authority + ) + } + }, + expPanic: "authority cannot be empty", + }, + } + + for _, tc := range testCases { + + suite.SetupTest() + + suite.Run(tc.name, func() { + // set default behaviour + newIBCKeeperFn = func() { + ibckeeper.NewKeeper( + suite.chainA.GetSimApp().AppCodec(), + runtime.NewKVStoreService(suite.chainA.GetSimApp().GetKey(ibcexported.StoreKey)), + suite.chainA.GetSimApp().GetSubspace(ibcexported.ModuleName), + upgradeKeeper, + suite.chainA.App.GetIBCKeeper().GetAuthority(), + ) + } + + upgradeKeeper = suite.chainA.GetSimApp().UpgradeKeeper + + tc.malleate() + + if tc.expPanic != "" { + suite.Require().Panics(func() { + newIBCKeeperFn() + }, "expected panic but no panic occurred") + + defer func() { + if r := recover(); r != nil { + suite.Require().Contains(r.(error).Error(), tc.expPanic, "unexpected panic message") + } + }() + + } else { + suite.Require().NotPanics(newIBCKeeperFn, "unexpected panic occurred") + } + }) + } +} diff --git a/modules/core/keeper/msg_server.go b/modules/core/keeper/msg_server.go new file mode 100644 index 0000000..3835e82 --- /dev/null +++ b/modules/core/keeper/msg_server.go @@ -0,0 +1,701 @@ +package keeper + +import ( + "context" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + clientv2types "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + internalerrors "github.com/cosmos/ibc-go/v10/modules/core/internal/errors" + "github.com/cosmos/ibc-go/v10/modules/core/internal/telemetry" +) + +var ( + _ clienttypes.MsgServer = (*Keeper)(nil) + _ clientv2types.MsgServer = (*Keeper)(nil) + _ connectiontypes.MsgServer = (*Keeper)(nil) + _ channeltypes.MsgServer = (*Keeper)(nil) +) + +// CreateClient defines a rpc handler method for MsgCreateClient. +// NOTE: The raw bytes of the concrete types encoded into protobuf.Any is passed to the client keeper. +// The 02-client handler will route to the appropriate light client module based on client type and it is the responsibility +// of the light client module to unmarshal and interpret the proto encoded bytes. +// Backwards compatibility with older versions of ibc-go is maintained through the light client module reconstructing and encoding +// the expected concrete type to the protobuf.Any for proof verification. +func (k *Keeper) CreateClient(goCtx context.Context, msg *clienttypes.MsgCreateClient) (*clienttypes.MsgCreateClientResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + clientState, err := clienttypes.UnpackClientState(msg.ClientState) + if err != nil { + return nil, err + } + + clientID, err := k.ClientKeeper.CreateClient(ctx, clientState.ClientType(), msg.ClientState.Value, msg.ConsensusState.Value) + if err != nil { + return nil, err + } + + // set the client creator so that IBC v2 counterparty can be set by same relayer + k.ClientKeeper.SetClientCreator(ctx, clientID, sdk.MustAccAddressFromBech32(msg.Signer)) + + return &clienttypes.MsgCreateClientResponse{ClientId: clientID}, nil +} + +// RegisterCounterparty will register the IBC v2 counterparty info for the given client id +// it must be called by the same relayer that called CreateClient +func (k *Keeper) RegisterCounterparty(goCtx context.Context, msg *clientv2types.MsgRegisterCounterparty) (*clientv2types.MsgRegisterCounterpartyResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + creator := k.ClientKeeper.GetClientCreator(ctx, msg.ClientId) + if !creator.Equals(sdk.MustAccAddressFromBech32(msg.Signer)) { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "expected same signer as createClient submittor %s, got %s", creator, msg.Signer) + } + if _, ok := k.ClientV2Keeper.GetClientCounterparty(ctx, msg.ClientId); ok { + return nil, errorsmod.Wrapf(ibcerrors.ErrInvalidRequest, "cannot register counterparty once it is already set") + } + + counterpartyInfo := clientv2types.CounterpartyInfo{ + MerklePrefix: msg.CounterpartyMerklePrefix, + ClientId: msg.CounterpartyClientId, + } + k.ClientV2Keeper.SetClientCounterparty(ctx, msg.ClientId, counterpartyInfo) + + // initialize next sequence send to enable packet flow + k.ChannelKeeperV2.SetNextSequenceSend(ctx, msg.ClientId, 1) + + return &clientv2types.MsgRegisterCounterpartyResponse{}, nil +} + +// UpdateClient defines a rpc handler method for MsgUpdateClient. +func (k *Keeper) UpdateClient(goCtx context.Context, msg *clienttypes.MsgUpdateClient) (*clienttypes.MsgUpdateClientResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + clientMsg, err := clienttypes.UnpackClientMessage(msg.ClientMessage) + if err != nil { + return nil, err + } + + // only check v2 params if this chain is setup with v2 clientKeepr + if k.ClientV2Keeper != nil { + // check if this relayer is allowed to update if v2 configuration are set + config := k.ClientV2Keeper.GetConfig(ctx, msg.ClientId) + if !config.IsAllowedRelayer(sdk.MustAccAddressFromBech32(msg.Signer)) { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "relayer %s is not authorized to update client %s", msg.Signer, msg.ClientId) + } + } + + if err = k.ClientKeeper.UpdateClient(ctx, msg.ClientId, clientMsg); err != nil { + return nil, err + } + + return &clienttypes.MsgUpdateClientResponse{}, nil +} + +// UpgradeClient defines a rpc handler method for MsgUpgradeClient. +// NOTE: The raw bytes of the concrete types encoded into protobuf.Any is passed to the client keeper. +// The 02-client handler will route to the appropriate light client module based on client identifier and it is the responsibility +// of the light client module to unmarshal and interpret the proto encoded bytes. +// Backwards compatibility with older versions of ibc-go is maintained through the light client module reconstructing and encoding +// the expected concrete type to the protobuf.Any for proof verification. +func (k *Keeper) UpgradeClient(goCtx context.Context, msg *clienttypes.MsgUpgradeClient) (*clienttypes.MsgUpgradeClientResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := k.ClientKeeper.UpgradeClient( + ctx, msg.ClientId, + msg.ClientState.Value, + msg.ConsensusState.Value, + msg.ProofUpgradeClient, + msg.ProofUpgradeConsensusState, + ); err != nil { + return nil, err + } + + return &clienttypes.MsgUpgradeClientResponse{}, nil +} + +// SubmitMisbehaviour defines a rpc handler method for MsgSubmitMisbehaviour. +// Warning: DEPRECATED +// This handler is redundant as `MsgUpdateClient` is now capable of handling both a Header and a Misbehaviour +func (k *Keeper) SubmitMisbehaviour(goCtx context.Context, msg *clienttypes.MsgSubmitMisbehaviour) (*clienttypes.MsgSubmitMisbehaviourResponse, error) { //nolint:staticcheck // for now, we're using msgsubmitmisbehaviour. + ctx := sdk.UnwrapSDKContext(goCtx) + + misbehaviour, err := clienttypes.UnpackClientMessage(msg.Misbehaviour) + if err != nil { + return nil, err + } + + if err = k.ClientKeeper.UpdateClient(ctx, msg.ClientId, misbehaviour); err != nil { + return nil, err + } + + return &clienttypes.MsgSubmitMisbehaviourResponse{}, nil +} + +// RecoverClient defines a rpc handler method for MsgRecoverClient. +func (k *Keeper) RecoverClient(goCtx context.Context, msg *clienttypes.MsgRecoverClient) (*clienttypes.MsgRecoverClientResponse, error) { + if k.GetAuthority() != msg.Signer { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "expected %s, got %s", k.GetAuthority(), msg.Signer) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + if err := k.ClientKeeper.RecoverClient(ctx, msg.SubjectClientId, msg.SubstituteClientId); err != nil { + return nil, errorsmod.Wrap(err, "client recovery failed") + } + + return &clienttypes.MsgRecoverClientResponse{}, nil +} + +// IBCSoftwareUpgrade defines a rpc handler method for MsgIBCSoftwareUpgrade. +func (k *Keeper) IBCSoftwareUpgrade(goCtx context.Context, msg *clienttypes.MsgIBCSoftwareUpgrade) (*clienttypes.MsgIBCSoftwareUpgradeResponse, error) { + if k.GetAuthority() != msg.Signer { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "expected %s, got %s", k.GetAuthority(), msg.Signer) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + upgradedClientState, err := clienttypes.UnpackClientState(msg.UpgradedClientState) + if err != nil { + return nil, errorsmod.Wrapf(clienttypes.ErrInvalidClientType, "cannot unpack client state: %s", err) + } + + if err = k.ClientKeeper.ScheduleIBCSoftwareUpgrade(ctx, msg.Plan, upgradedClientState); err != nil { + return nil, errorsmod.Wrap(err, "failed to schedule upgrade") + } + + return &clienttypes.MsgIBCSoftwareUpgradeResponse{}, nil +} + +// ConnectionOpenInit defines a rpc handler method for MsgConnectionOpenInit. +func (k *Keeper) ConnectionOpenInit(goCtx context.Context, msg *connectiontypes.MsgConnectionOpenInit) (*connectiontypes.MsgConnectionOpenInitResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + if _, err := k.ConnectionKeeper.ConnOpenInit(ctx, msg.ClientId, msg.Counterparty, msg.Version, msg.DelayPeriod); err != nil { + return nil, errorsmod.Wrap(err, "connection handshake open init failed") + } + + return &connectiontypes.MsgConnectionOpenInitResponse{}, nil +} + +// ConnectionOpenTry defines a rpc handler method for MsgConnectionOpenTry. +func (k *Keeper) ConnectionOpenTry(goCtx context.Context, msg *connectiontypes.MsgConnectionOpenTry) (*connectiontypes.MsgConnectionOpenTryResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + if _, err := k.ConnectionKeeper.ConnOpenTry( + ctx, msg.Counterparty, msg.DelayPeriod, msg.ClientId, + msg.CounterpartyVersions, msg.ProofInit, msg.ProofHeight, + ); err != nil { + return nil, errorsmod.Wrap(err, "connection handshake open try failed") + } + + return &connectiontypes.MsgConnectionOpenTryResponse{}, nil +} + +// ConnectionOpenAck defines a rpc handler method for MsgConnectionOpenAck. +func (k *Keeper) ConnectionOpenAck(goCtx context.Context, msg *connectiontypes.MsgConnectionOpenAck) (*connectiontypes.MsgConnectionOpenAckResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := k.ConnectionKeeper.ConnOpenAck( + ctx, msg.ConnectionId, msg.Version, msg.CounterpartyConnectionId, + msg.ProofTry, msg.ProofHeight, + ); err != nil { + return nil, errorsmod.Wrap(err, "connection handshake open ack failed") + } + + return &connectiontypes.MsgConnectionOpenAckResponse{}, nil +} + +// ConnectionOpenConfirm defines a rpc handler method for MsgConnectionOpenConfirm. +func (k *Keeper) ConnectionOpenConfirm(goCtx context.Context, msg *connectiontypes.MsgConnectionOpenConfirm) (*connectiontypes.MsgConnectionOpenConfirmResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := k.ConnectionKeeper.ConnOpenConfirm( + ctx, msg.ConnectionId, msg.ProofAck, msg.ProofHeight, + ); err != nil { + return nil, errorsmod.Wrap(err, "connection handshake open confirm failed") + } + + return &connectiontypes.MsgConnectionOpenConfirmResponse{}, nil +} + +// ChannelOpenInit defines a rpc handler method for MsgChannelOpenInit. +// ChannelOpenInit will perform 04-channel checks, route to the application +// callback, and write an OpenInit channel into state upon successful execution. +func (k *Keeper) ChannelOpenInit(goCtx context.Context, msg *channeltypes.MsgChannelOpenInit) (*channeltypes.MsgChannelOpenInitResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Retrieve application callbacks from router + cbs, ok := k.PortKeeper.Route(msg.PortId) + if !ok { + ctx.Logger().Error("channel open init failed", "port-id", msg.PortId, "error", errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to portID: %s", msg.PortId)) + return nil, errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to portID: %s", msg.PortId) + } + + // Perform 04-channel verification + channelID, err := k.ChannelKeeper.ChanOpenInit( + ctx, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.PortId, msg.Channel.Counterparty, msg.Channel.Version, + ) + if err != nil { + ctx.Logger().Error("channel open init failed", "error", errorsmod.Wrap(err, "channel handshake open init failed")) + return nil, errorsmod.Wrap(err, "channel handshake open init failed") + } + + // Perform application logic callback + version, err := cbs.OnChanOpenInit(ctx, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.PortId, channelID, msg.Channel.Counterparty, msg.Channel.Version) + if err != nil { + ctx.Logger().Error("channel open init failed", "port-id", msg.PortId, "channel-id", channelID, "error", errorsmod.Wrap(err, "channel open init callback failed")) + return nil, errorsmod.Wrapf(err, "channel open init callback failed for port ID: %s, channel ID: %s", msg.PortId, channelID) + } + + // Write channel into state + k.ChannelKeeper.WriteOpenInitChannel(ctx, msg.PortId, channelID, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.Channel.Counterparty, version) + + ctx.Logger().Info("channel open init succeeded", "channel-id", channelID, "version", version) + + return &channeltypes.MsgChannelOpenInitResponse{ + ChannelId: channelID, + Version: version, + }, nil +} + +// ChannelOpenTry defines a rpc handler method for MsgChannelOpenTry. +// ChannelOpenTry will perform 04-channel checks, route to the application +// callback, and write an OpenTry channel into state upon successful execution. +func (k *Keeper) ChannelOpenTry(goCtx context.Context, msg *channeltypes.MsgChannelOpenTry) (*channeltypes.MsgChannelOpenTryResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Retrieve application callbacks from router + cbs, ok := k.PortKeeper.Route(msg.PortId) + if !ok { + ctx.Logger().Error("channel open try failed", "port-id", msg.PortId, "error", errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to portID: %s", msg.PortId)) + return nil, errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to portID: %s", msg.PortId) + } + + // Perform 04-channel verification + channelID, err := k.ChannelKeeper.ChanOpenTry(ctx, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.PortId, msg.Channel.Counterparty, msg.CounterpartyVersion, msg.ProofInit, msg.ProofHeight) + if err != nil { + ctx.Logger().Error("channel open try failed", "error", errorsmod.Wrap(err, "channel handshake open try failed")) + return nil, errorsmod.Wrap(err, "channel handshake open try failed") + } + + // Perform application logic callback + version, err := cbs.OnChanOpenTry(ctx, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.PortId, channelID, msg.Channel.Counterparty, msg.CounterpartyVersion) + if err != nil { + ctx.Logger().Error("channel open try failed", "port-id", msg.PortId, "channel-id", channelID, "error", errorsmod.Wrap(err, "channel open try callback failed")) + return nil, errorsmod.Wrapf(err, "channel open try callback failed for port ID: %s, channel ID: %s", msg.PortId, channelID) + } + + // Write channel into state + k.ChannelKeeper.WriteOpenTryChannel(ctx, msg.PortId, channelID, msg.Channel.Ordering, msg.Channel.ConnectionHops, msg.Channel.Counterparty, version) + + ctx.Logger().Info("channel open try succeeded", "channel-id", channelID, "port-id", msg.PortId, "version", version) + + return &channeltypes.MsgChannelOpenTryResponse{ + ChannelId: channelID, + Version: version, + }, nil +} + +// ChannelOpenAck defines a rpc handler method for MsgChannelOpenAck. +// ChannelOpenAck will perform 04-channel checks, route to the application +// callback, and write an OpenAck channel into state upon successful execution. +func (k *Keeper) ChannelOpenAck(goCtx context.Context, msg *channeltypes.MsgChannelOpenAck) (*channeltypes.MsgChannelOpenAckResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Retrieve application callbacks from router + cbs, ok := k.PortKeeper.Route(msg.PortId) + if !ok { + ctx.Logger().Error("channel open ack failed", "port-id", msg.PortId, "error", errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to portID: %s", msg.PortId)) + return nil, errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to portID: %s", msg.PortId) + } + + // Perform 04-channel verification + if err := k.ChannelKeeper.ChanOpenAck( + ctx, msg.PortId, msg.ChannelId, msg.CounterpartyVersion, msg.CounterpartyChannelId, msg.ProofTry, msg.ProofHeight, + ); err != nil { + ctx.Logger().Error("channel open ack failed", "error", err.Error()) + return nil, errorsmod.Wrap(err, "channel handshake open ack failed") + } + + // Write channel into state + k.ChannelKeeper.WriteOpenAckChannel(ctx, msg.PortId, msg.ChannelId, msg.CounterpartyVersion, msg.CounterpartyChannelId) + + // Perform application logic callback + if err := cbs.OnChanOpenAck(ctx, msg.PortId, msg.ChannelId, msg.CounterpartyChannelId, msg.CounterpartyVersion); err != nil { + ctx.Logger().Error("channel open ack failed", "port-id", msg.PortId, "channel-id", msg.ChannelId, "error", errorsmod.Wrap(err, "channel open ack callback failed")) + return nil, errorsmod.Wrapf(err, "channel open ack callback failed for port ID: %s, channel ID: %s", msg.PortId, msg.ChannelId) + } + + ctx.Logger().Info("channel open ack succeeded", "channel-id", msg.ChannelId, "port-id", msg.PortId) + + return &channeltypes.MsgChannelOpenAckResponse{}, nil +} + +// ChannelOpenConfirm defines a rpc handler method for MsgChannelOpenConfirm. +// ChannelOpenConfirm will perform 04-channel checks, route to the application +// callback, and write an OpenConfirm channel into state upon successful execution. +func (k *Keeper) ChannelOpenConfirm(goCtx context.Context, msg *channeltypes.MsgChannelOpenConfirm) (*channeltypes.MsgChannelOpenConfirmResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Retrieve application callbacks from router + cbs, ok := k.PortKeeper.Route(msg.PortId) + if !ok { + ctx.Logger().Error("channel open confirm failed", "port-id", msg.PortId, "error", errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to portID: %s", msg.PortId)) + return nil, errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to portID: %s", msg.PortId) + } + + // Perform 04-channel verification + if err := k.ChannelKeeper.ChanOpenConfirm(ctx, msg.PortId, msg.ChannelId, msg.ProofAck, msg.ProofHeight); err != nil { + ctx.Logger().Error("channel open confirm failed", "error", errorsmod.Wrap(err, "channel handshake open confirm failed")) + return nil, errorsmod.Wrap(err, "channel handshake open confirm failed") + } + + // Write channel into state + k.ChannelKeeper.WriteOpenConfirmChannel(ctx, msg.PortId, msg.ChannelId) + + // Perform application logic callback + if err := cbs.OnChanOpenConfirm(ctx, msg.PortId, msg.ChannelId); err != nil { + ctx.Logger().Error("channel open confirm failed", "port-id", msg.PortId, "channel-id", msg.ChannelId, "error", errorsmod.Wrap(err, "channel open confirm callback failed")) + return nil, errorsmod.Wrapf(err, "channel open confirm callback failed for port ID: %s, channel ID: %s", msg.PortId, msg.ChannelId) + } + + ctx.Logger().Info("channel open confirm succeeded", "channel-id", msg.ChannelId, "port-id", msg.PortId) + + return &channeltypes.MsgChannelOpenConfirmResponse{}, nil +} + +// ChannelCloseInit defines a rpc handler method for MsgChannelCloseInit. +func (k *Keeper) ChannelCloseInit(goCtx context.Context, msg *channeltypes.MsgChannelCloseInit) (*channeltypes.MsgChannelCloseInitResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Retrieve callbacks from router + cbs, ok := k.PortKeeper.Route(msg.PortId) + if !ok { + ctx.Logger().Error("channel close init failed", "port-id", msg.PortId, "error", errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to portID: %s", msg.PortId)) + return nil, errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to portID: %s", msg.PortId) + } + + if err := cbs.OnChanCloseInit(ctx, msg.PortId, msg.ChannelId); err != nil { + ctx.Logger().Error("channel close init failed", "port-id", msg.PortId, "channel-id", msg.ChannelId, "error", errorsmod.Wrap(err, "channel close init callback failed")) + return nil, errorsmod.Wrapf(err, "channel close init callback failed for port ID: %s, channel ID: %s", msg.PortId, msg.ChannelId) + } + + err := k.ChannelKeeper.ChanCloseInit(ctx, msg.PortId, msg.ChannelId) + if err != nil { + ctx.Logger().Error("channel close init failed", "port-id", msg.PortId, "channel-id", msg.ChannelId, "error", err.Error()) + return nil, errorsmod.Wrap(err, "channel handshake close init failed") + } + + ctx.Logger().Info("channel close init succeeded", "channel-id", msg.ChannelId, "port-id", msg.PortId) + + return &channeltypes.MsgChannelCloseInitResponse{}, nil +} + +// ChannelCloseConfirm defines a rpc handler method for MsgChannelCloseConfirm. +func (k *Keeper) ChannelCloseConfirm(goCtx context.Context, msg *channeltypes.MsgChannelCloseConfirm) (*channeltypes.MsgChannelCloseConfirmResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Retrieve callbacks from router + cbs, ok := k.PortKeeper.Route(msg.PortId) + if !ok { + ctx.Logger().Error("channel close confirm failed", "port-id", msg.PortId, "error", errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to portID: %s", msg.PortId)) + return nil, errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to portID: %s", msg.PortId) + } + + if err := cbs.OnChanCloseConfirm(ctx, msg.PortId, msg.ChannelId); err != nil { + ctx.Logger().Error("channel close confirm failed", "port-id", msg.PortId, "channel-id", msg.ChannelId, "error", errorsmod.Wrap(err, "channel close confirm callback failed")) + return nil, errorsmod.Wrapf(err, "channel close confirm callback failed for port ID: %s, channel ID: %s", msg.PortId, msg.ChannelId) + } + + err := k.ChannelKeeper.ChanCloseConfirm(ctx, msg.PortId, msg.ChannelId, msg.ProofInit, msg.ProofHeight) + if err != nil { + ctx.Logger().Error("channel close confirm failed", "port-id", msg.PortId, "channel-id", msg.ChannelId, "error", err.Error()) + return nil, errorsmod.Wrap(err, "channel handshake close confirm failed") + } + + ctx.Logger().Info("channel close confirm succeeded", "channel-id", msg.ChannelId, "port-id", msg.PortId) + + return &channeltypes.MsgChannelCloseConfirmResponse{}, nil +} + +// RecvPacket defines a rpc handler method for MsgRecvPacket. +func (k *Keeper) RecvPacket(goCtx context.Context, msg *channeltypes.MsgRecvPacket) (*channeltypes.MsgRecvPacketResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + relayer, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + ctx.Logger().Error("receive packet failed", "error", errorsmod.Wrap(err, "Invalid address for msg Signer")) + return nil, errorsmod.Wrap(err, "Invalid address for msg Signer") + } + + // Retrieve callbacks from router + cbs, ok := k.PortKeeper.Route(msg.Packet.DestinationPort) + if !ok { + ctx.Logger().Error("receive packet failed", "port-id", msg.Packet.SourcePort, "error", errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to portID: %s", msg.Packet.DestinationPort)) + return nil, errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to portID: %s", msg.Packet.DestinationPort) + } + + // Perform TAO verification + // + // If the packet was already received, perform a no-op + // Use a cached context to prevent accidental state changes + cacheCtx, writeFn := ctx.CacheContext() + channelVersion, err := k.ChannelKeeper.RecvPacket(cacheCtx, msg.Packet, msg.ProofCommitment, msg.ProofHeight) + + switch err { + case nil: + writeFn() + case channeltypes.ErrNoOpMsg: + // no-ops do not need event emission as they will be ignored + ctx.Logger().Debug("no-op on redundant relay", "port-id", msg.Packet.SourcePort, "channel-id", msg.Packet.SourceChannel) + return &channeltypes.MsgRecvPacketResponse{Result: channeltypes.NOOP}, nil + default: + ctx.Logger().Error("receive packet failed", "port-id", msg.Packet.SourcePort, "channel-id", msg.Packet.SourceChannel, "error", errorsmod.Wrap(err, "receive packet verification failed")) + return nil, errorsmod.Wrap(err, "receive packet verification failed") + } + + // Perform application logic callback + // + // Cache context so that we may discard state changes from callback if the acknowledgement is unsuccessful. + cacheCtx, writeFn = ctx.CacheContext() + ack := cbs.OnRecvPacket(cacheCtx, channelVersion, msg.Packet, relayer) + if ack == nil || ack.Success() { + // write application state changes for asynchronous and successful acknowledgements + writeFn() + } else { + // Modify events in cached context to reflect unsuccessful acknowledgement + ctx.EventManager().EmitEvents(internalerrors.ConvertToErrorEvents(cacheCtx.EventManager().Events())) + } + + // Set packet acknowledgement only if the acknowledgement is not nil. + // NOTE: IBC applications modules may call the WriteAcknowledgement asynchronously if the + // acknowledgement is nil. + if ack != nil { + if err := k.ChannelKeeper.WriteAcknowledgement(ctx, msg.Packet, ack); err != nil { + return nil, err + } + } + + defer telemetry.ReportRecvPacket(msg.Packet) + + ctx.Logger().Info("receive packet callback succeeded", "port-id", msg.Packet.SourcePort, "channel-id", msg.Packet.SourceChannel, "result", channeltypes.SUCCESS.String()) + + return &channeltypes.MsgRecvPacketResponse{Result: channeltypes.SUCCESS}, nil +} + +// Timeout defines a rpc handler method for MsgTimeout. +func (k *Keeper) Timeout(goCtx context.Context, msg *channeltypes.MsgTimeout) (*channeltypes.MsgTimeoutResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + relayer, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + ctx.Logger().Error("timeout failed", "error", errorsmod.Wrap(err, "Invalid address for msg Signer")) + return nil, errorsmod.Wrap(err, "Invalid address for msg Signer") + } + + // Retrieve callbacks from router + cbs, ok := k.PortKeeper.Route(msg.Packet.SourcePort) + if !ok { + ctx.Logger().Error("timeout failed", "port-id", msg.Packet.SourcePort, "error", errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to portID: %s", msg.Packet.SourcePort)) + return nil, errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to portID: %s", msg.Packet.SourcePort) + } + + // Perform TAO verification + // + // If the timeout was already received, perform a no-op + // Use a cached context to prevent accidental state changes + cacheCtx, writeFn := ctx.CacheContext() + channelVersion, err := k.ChannelKeeper.TimeoutPacket(cacheCtx, msg.Packet, msg.ProofUnreceived, msg.ProofHeight, msg.NextSequenceRecv) + + switch err { + case nil: + writeFn() + case channeltypes.ErrNoOpMsg: + // no-ops do not need event emission as they will be ignored + ctx.Logger().Debug("no-op on redundant relay", "port-id", msg.Packet.SourcePort, "channel-id", msg.Packet.SourceChannel) + return &channeltypes.MsgTimeoutResponse{Result: channeltypes.NOOP}, nil + default: + ctx.Logger().Error("timeout failed", "port-id", msg.Packet.SourcePort, "channel-id", msg.Packet.SourceChannel, "error", errorsmod.Wrap(err, "timeout packet verification failed")) + return nil, errorsmod.Wrap(err, "timeout packet verification failed") + } + + // Perform application logic callback + err = cbs.OnTimeoutPacket(ctx, channelVersion, msg.Packet, relayer) + if err != nil { + ctx.Logger().Error("timeout failed", "port-id", msg.Packet.SourcePort, "channel-id", msg.Packet.SourceChannel, "error", errorsmod.Wrap(err, "timeout packet callback failed")) + return nil, errorsmod.Wrap(err, "timeout packet callback failed") + } + + defer telemetry.ReportTimeoutPacket(msg.Packet, "height") + + ctx.Logger().Info("timeout packet callback succeeded", "port-id", msg.Packet.SourcePort, "channel-id", msg.Packet.SourceChannel, "result", channeltypes.SUCCESS.String()) + + return &channeltypes.MsgTimeoutResponse{Result: channeltypes.SUCCESS}, nil +} + +// TimeoutOnClose defines a rpc handler method for MsgTimeoutOnClose. +func (k *Keeper) TimeoutOnClose(goCtx context.Context, msg *channeltypes.MsgTimeoutOnClose) (*channeltypes.MsgTimeoutOnCloseResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + relayer, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + ctx.Logger().Error("timeout on close failed", "error", errorsmod.Wrap(err, "Invalid address for msg Signer")) + return nil, errorsmod.Wrap(err, "Invalid address for msg Signer") + } + + cbs, ok := k.PortKeeper.Route(msg.Packet.SourcePort) + if !ok { + ctx.Logger().Error("timeout on close failed", "port-id", msg.Packet.SourcePort, "error", errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to portID: %s", msg.Packet.SourcePort)) + return nil, errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to portID: %s", msg.Packet.SourcePort) + } + + // Perform TAO verification + // + // If the timeout was already received, perform a no-op + // Use a cached context to prevent accidental state changes + cacheCtx, writeFn := ctx.CacheContext() + channelVersion, err := k.ChannelKeeper.TimeoutOnClose(cacheCtx, msg.Packet, msg.ProofUnreceived, msg.ProofClose, msg.ProofHeight, msg.NextSequenceRecv) + + switch err { + case nil: + writeFn() + case channeltypes.ErrNoOpMsg: + // no-ops do not need event emission as they will be ignored + ctx.Logger().Debug("no-op on redundant relay", "port-id", msg.Packet.SourcePort, "channel-id", msg.Packet.SourceChannel) + return &channeltypes.MsgTimeoutOnCloseResponse{Result: channeltypes.NOOP}, nil + default: + ctx.Logger().Error("timeout on close failed", "port-id", msg.Packet.SourcePort, "channel-id", msg.Packet.SourceChannel, "error", errorsmod.Wrap(err, "timeout on close packet verification failed")) + return nil, errorsmod.Wrap(err, "timeout on close packet verification failed") + } + + // Perform application logic callback + // + // NOTE: MsgTimeout and MsgTimeoutOnClose use the same "OnTimeoutPacket" + // application logic callback. + err = cbs.OnTimeoutPacket(ctx, channelVersion, msg.Packet, relayer) + if err != nil { + ctx.Logger().Error("timeout on close failed", "port-id", msg.Packet.SourcePort, "channel-id", msg.Packet.SourceChannel, "error", errorsmod.Wrap(err, "timeout on close callback failed")) + return nil, errorsmod.Wrap(err, "timeout on close callback failed") + } + + defer telemetry.ReportTimeoutPacket(msg.Packet, "channel-closed") + + ctx.Logger().Info("timeout on close callback succeeded", "port-id", msg.Packet.SourcePort, "channel-id", msg.Packet.SourceChannel, "result", channeltypes.SUCCESS.String()) + + return &channeltypes.MsgTimeoutOnCloseResponse{Result: channeltypes.SUCCESS}, nil +} + +// Acknowledgement defines a rpc handler method for MsgAcknowledgement. +func (k *Keeper) Acknowledgement(goCtx context.Context, msg *channeltypes.MsgAcknowledgement) (*channeltypes.MsgAcknowledgementResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + relayer, err := sdk.AccAddressFromBech32(msg.Signer) + if err != nil { + ctx.Logger().Error("acknowledgement failed", "error", errorsmod.Wrap(err, "Invalid address for msg Signer")) + return nil, errorsmod.Wrap(err, "Invalid address for msg Signer") + } + + // Retrieve callbacks from router + cbs, ok := k.PortKeeper.Route(msg.Packet.SourcePort) + if !ok { + ctx.Logger().Error("acknowledgement failed", "port-id", msg.Packet.SourcePort, "error", errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to portID: %s", msg.Packet.SourcePort)) + return nil, errorsmod.Wrapf(porttypes.ErrInvalidRoute, "route not found to portID: %s", msg.Packet.SourcePort) + } + + // Perform TAO verification + // + // If the acknowledgement was already received, perform a no-op + // Use a cached context to prevent accidental state changes + cacheCtx, writeFn := ctx.CacheContext() + channelVersion, err := k.ChannelKeeper.AcknowledgePacket(cacheCtx, msg.Packet, msg.Acknowledgement, msg.ProofAcked, msg.ProofHeight) + + switch err { + case nil: + writeFn() + case channeltypes.ErrNoOpMsg: + // no-ops do not need event emission as they will be ignored + ctx.Logger().Debug("no-op on redundant relay", "port-id", msg.Packet.SourcePort, "channel-id", msg.Packet.SourceChannel) + return &channeltypes.MsgAcknowledgementResponse{Result: channeltypes.NOOP}, nil + default: + ctx.Logger().Error("acknowledgement failed", "port-id", msg.Packet.SourcePort, "channel-id", msg.Packet.SourceChannel, "error", errorsmod.Wrap(err, "acknowledge packet verification failed")) + return nil, errorsmod.Wrap(err, "acknowledge packet verification failed") + } + + // Perform application logic callback + err = cbs.OnAcknowledgementPacket(ctx, channelVersion, msg.Packet, msg.Acknowledgement, relayer) + if err != nil { + ctx.Logger().Error("acknowledgement failed", "port-id", msg.Packet.SourcePort, "channel-id", msg.Packet.SourceChannel, "error", errorsmod.Wrap(err, "acknowledge packet callback failed")) + return nil, errorsmod.Wrap(err, "acknowledge packet callback failed") + } + + defer telemetry.ReportAcknowledgePacket(msg.Packet) + + ctx.Logger().Info("acknowledgement succeeded", "port-id", msg.Packet.SourcePort, "channel-id", msg.Packet.SourceChannel, "result", channeltypes.SUCCESS.String()) + + return &channeltypes.MsgAcknowledgementResponse{Result: channeltypes.SUCCESS}, nil +} + +// UpdateClientParams defines a rpc handler method for MsgUpdateParams. +func (k *Keeper) UpdateClientParams(goCtx context.Context, msg *clienttypes.MsgUpdateParams) (*clienttypes.MsgUpdateParamsResponse, error) { + if k.GetAuthority() != msg.Signer { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "expected %s, got %s", k.GetAuthority(), msg.Signer) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + k.ClientKeeper.SetParams(ctx, msg.Params) + + return &clienttypes.MsgUpdateParamsResponse{}, nil +} + +// UpdateConnectionParams defines a rpc handler method for MsgUpdateParams for the 03-connection submodule. +func (k *Keeper) UpdateConnectionParams(goCtx context.Context, msg *connectiontypes.MsgUpdateParams) (*connectiontypes.MsgUpdateParamsResponse, error) { + if k.GetAuthority() != msg.Signer { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "expected %s, got %s", k.GetAuthority(), msg.Signer) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + k.ConnectionKeeper.SetParams(ctx, msg.Params) + + return &connectiontypes.MsgUpdateParamsResponse{}, nil +} + +// UpdateClientConfig defines an rpc handler method for MsgUpdateClientConfig for the 02-client v2 submodule. +func (k *Keeper) UpdateClientConfig(goCtx context.Context, msg *clientv2types.MsgUpdateClientConfig) (*clientv2types.MsgUpdateClientConfigResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + creator := k.ClientKeeper.GetClientCreator(ctx, msg.ClientId) + if k.GetAuthority() != msg.Signer && !creator.Equals(sdk.MustAccAddressFromBech32(msg.Signer)) { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "authority %s or client creator %s is authorized to update params for %s, got %s", + k.GetAuthority(), creator, msg.ClientId, msg.Signer, + ) + } + + k.ClientV2Keeper.SetConfig(ctx, msg.ClientId, msg.Config) + return &clientv2types.MsgUpdateClientConfigResponse{}, nil +} + +// DeleteClientCreator defines an rpc handler method for MsgDeleteClientCreator for the 02-client v1 submodule. +func (k *Keeper) DeleteClientCreator(goCtx context.Context, msg *clienttypes.MsgDeleteClientCreator) (*clienttypes.MsgDeleteClientCreatorResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + creator := k.ClientKeeper.GetClientCreator(ctx, msg.ClientId) + if creator == nil { + return nil, errorsmod.Wrapf(ibcerrors.ErrNotFound, "creator for client %s not found", msg.ClientId) + } + + // Check authorization + if k.GetAuthority() != msg.Signer && !creator.Equals(sdk.MustAccAddressFromBech32(msg.Signer)) { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "authority %s or client creator %s is authorized to delete creator for %s, got %s", + k.GetAuthority(), creator, msg.ClientId, msg.Signer, + ) + } + + k.ClientKeeper.DeleteClientCreator(ctx, msg.ClientId) + return &clienttypes.MsgDeleteClientCreatorResponse{}, nil +} diff --git a/modules/core/keeper/msg_server_test.go b/modules/core/keeper/msg_server_test.go new file mode 100644 index 0000000..9080ef3 --- /dev/null +++ b/modules/core/keeper/msg_server_test.go @@ -0,0 +1,1378 @@ +package keeper_test + +import ( + "errors" + + upgradetypes "cosmossdk.io/x/upgrade/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + clientv2types "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + internalerrors "github.com/cosmos/ibc-go/v10/modules/core/internal/errors" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + ibcmock "github.com/cosmos/ibc-go/v10/testing/mock" +) + +var ( + timeoutHeight = clienttypes.NewHeight(1, 10000) + maxSequence = uint64(10) +) + +// TestRegisterCounterparty tests that counterpartyInfo is correctly stored +// and only if the submittor is the same submittor as prior createClient msg +func (suite *KeeperTestSuite) TestRegisterCounterparty() { + var path *ibctesting.Path + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + path.SetupClients() + }, + nil, + }, + { + "client not created first", + func() {}, + ibcerrors.ErrUnauthorized, + }, + { + "creator is different than expected", + func() { + path.SetupClients() + path.EndpointA.Chain.App.GetIBCKeeper().ClientKeeper.SetClientCreator(suite.chainA.GetContext(), path.EndpointA.ClientID, sdk.AccAddress(ibctesting.TestAccAddress)) + }, + ibcerrors.ErrUnauthorized, + }, + { + "counterparty already registered", + func() { + path.SetupV2() + }, + ibcerrors.ErrInvalidRequest, + }, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + tc.malleate() + merklePrefix := [][]byte{[]byte("ibc"), []byte("channel-7")} + msg := clientv2types.NewMsgRegisterCounterparty(path.EndpointA.ClientID, merklePrefix, path.EndpointB.ClientID, suite.chainA.SenderAccount.GetAddress().String()) + _, err := suite.chainA.App.GetIBCKeeper().RegisterCounterparty(suite.chainA.GetContext(), msg) + if tc.expError != nil { + suite.Require().Error(err) + suite.Require().True(errors.Is(err, tc.expError)) + } else { + suite.Require().NoError(err) + counterpartyInfo, ok := suite.chainA.App.GetIBCKeeper().ClientV2Keeper.GetClientCounterparty(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(ok) + suite.Require().Equal(counterpartyInfo, clientv2types.NewCounterpartyInfo(merklePrefix, path.EndpointB.ClientID)) + nextSeqSend, ok := suite.chainA.App.GetIBCKeeper().ChannelKeeperV2.GetNextSequenceSend(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(ok) + suite.Require().Equal(nextSeqSend, uint64(1)) + } + }) + } +} + +// tests the IBC handler receiving a packet on ordered and unordered channels. +// It verifies that the storing of an acknowledgement on success occurs. It +// tests high level properties like ordering and basic sanity checks. More +// rigorous testing of 'RecvPacket' can be found in the +// 04-channel/keeper/packet_test.go. +func (suite *KeeperTestSuite) TestHandleRecvPacket() { + var ( + packet channeltypes.Packet + path *ibctesting.Path + ) + + testCases := []struct { + name string + malleate func() + expError error + expRevert bool + async bool // indicate no ack written + replay bool // indicate replay (no-op) + }{ + {"success: ORDERED", func() { + path.SetChannelOrdered() + path.Setup() + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + }, nil, false, false, false}, + {"success: UNORDERED", func() { + path.Setup() + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + }, nil, false, false, false}, + {"success: UNORDERED out of order packet", func() { + // setup uses an UNORDERED channel + path.Setup() + + // attempts to receive packet with sequence 10 without receiving packet with sequence 1 + for i := uint64(1); i < 10; i++ { + sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + } + }, nil, false, false, false}, + {"success: OnRecvPacket callback returns revert=true", func() { + path.Setup() + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibctesting.MockFailPacketData) + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibctesting.MockFailPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + }, nil, true, false, false}, + {"success: ORDERED - async acknowledgement", func() { + path.SetChannelOrdered() + path.Setup() + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibcmock.MockAsyncPacketData) + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibcmock.MockAsyncPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + }, nil, false, true, false}, + {"success: UNORDERED - async acknowledgement", func() { + path.Setup() + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibcmock.MockAsyncPacketData) + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibcmock.MockAsyncPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + }, nil, false, true, false}, + {"failure: ORDERED out of order packet", func() { + path.SetChannelOrdered() + path.Setup() + + // attempts to receive packet with sequence 10 without receiving packet with sequence 1 + for i := uint64(1); i < 10; i++ { + sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + } + }, errors.New("packet sequence is out of order"), false, false, false}, + {"channel does not exist", func() { + // any non-nil value of packet is valid + suite.Require().NotNil(packet) + }, errors.New("channel not found"), false, false, false}, + {"packet not sent", func() { + path.Setup() + packet = channeltypes.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + }, errors.New("receive packet verification failed: couldn't verify counterparty packet commitment"), false, false, false}, + {"successful no-op: ORDERED - packet already received (replay)", func() { + // mock will panic if application callback is called twice on the same packet + path.SetChannelOrdered() + path.Setup() + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + }, nil, false, false, true}, + {"successful no-op: UNORDERED - packet already received (replay)", func() { + // mock will panic if application callback is called twice on the same packet + path.Setup() + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + }, nil, false, false, true}, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + tc.malleate() + + var ( + proof []byte + proofHeight clienttypes.Height + ) + + // get proof of packet commitment from chainA + packetKey := host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + if path.EndpointA.ChannelID != "" { + proof, proofHeight = path.EndpointA.QueryProof(packetKey) + } + + msg := channeltypes.NewMsgRecvPacket(packet, proof, proofHeight, suite.chainB.SenderAccount.GetAddress().String()) + + ctx := suite.chainB.GetContext() + _, err := suite.chainB.App.GetIBCKeeper().RecvPacket(ctx, msg) + + events := ctx.EventManager().Events() + + if tc.expError == nil { + suite.Require().NoError(err) + + // replay should not fail since it will be treated as a no-op + _, err := suite.chainB.App.GetIBCKeeper().RecvPacket(suite.chainB.GetContext(), msg) + suite.Require().NoError(err) + + if tc.expRevert { + // context events should contain error events + suite.Require().Contains(events, internalerrors.ConvertToErrorEvents(sdk.Events{ibcmock.NewMockRecvPacketEvent()})[0]) + suite.Require().NotContains(events, ibcmock.NewMockRecvPacketEvent()) + } else { + if tc.replay { + // context should not contain application events + suite.Require().NotContains(events, ibcmock.NewMockRecvPacketEvent()) + suite.Require().NotContains(events, internalerrors.ConvertToErrorEvents(sdk.Events{ibcmock.NewMockRecvPacketEvent()})[0]) + } else { + // context events should contain application events + suite.Require().Contains(events, ibcmock.NewMockRecvPacketEvent()) + } + } + + // verify if ack was written + ack, found := suite.chainB.App.GetIBCKeeper().ChannelKeeper.GetPacketAcknowledgement(suite.chainB.GetContext(), packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + + if tc.async { + suite.Require().Nil(ack) + suite.Require().False(found) + + } else { + suite.Require().NotNil(ack) + suite.Require().True(found) + } + } else { + suite.Require().Error(err) + suite.Require().Contains(err.Error(), tc.expError.Error()) + } + }) + } +} + +func (suite *KeeperTestSuite) TestUpdateClient() { + var path *ibctesting.Path + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success: update client, no params", + func() {}, + nil, + }, + { + "success: update client, with v2 params set to correct relayer", + func() { + creator := suite.chainA.SenderAccount.GetAddress() + msg := clientv2types.NewMsgUpdateClientConfig(path.EndpointA.ClientID, creator.String(), clientv2types.NewConfig(suite.chainB.SenderAccount.GetAddress().String(), creator.String())) + _, err := suite.chainA.App.GetIBCKeeper().UpdateClientConfig(suite.chainA.GetContext(), msg) + suite.Require().NoError(err) + }, + nil, + }, + { + "failure: update client with invalid relayer", + func() { + creator := suite.chainA.SenderAccount.GetAddress() + msg := clientv2types.NewMsgUpdateClientConfig(path.EndpointA.ClientID, creator.String(), clientv2types.NewConfig(suite.chainB.SenderAccount.GetAddress().String())) + _, err := suite.chainA.App.GetIBCKeeper().UpdateClientConfig(suite.chainA.GetContext(), msg) + suite.Require().NoError(err) + }, + ibcerrors.ErrUnauthorized, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + tc.malleate() + + err := path.EndpointA.UpdateClient() + + if tc.expError == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().Contains(err.Error(), tc.expError.Error()) + } + }) + } +} + +func (suite *KeeperTestSuite) TestRecoverClient() { + var msg *clienttypes.MsgRecoverClient + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success: recover client", + func() {}, + nil, + }, + { + "signer doesn't match authority", + func() { + msg.Signer = ibctesting.InvalidID + }, + ibcerrors.ErrUnauthorized, + }, + { + "invalid subject client", + func() { + msg.SubjectClientId = ibctesting.InvalidID + }, + clienttypes.ErrRouteNotFound, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + subjectPath := ibctesting.NewPath(suite.chainA, suite.chainB) + subjectPath.SetupClients() + subject := subjectPath.EndpointA.ClientID + subjectClientState := suite.chainA.GetClientState(subject) + + substitutePath := ibctesting.NewPath(suite.chainA, suite.chainB) + substitutePath.SetupClients() + substitute := substitutePath.EndpointA.ClientID + + // update substitute twice + err := substitutePath.EndpointA.UpdateClient() + suite.Require().NoError(err) + err = substitutePath.EndpointA.UpdateClient() + suite.Require().NoError(err) + + tmClientState, ok := subjectClientState.(*ibctm.ClientState) + suite.Require().True(ok) + tmClientState.FrozenHeight = tmClientState.LatestHeight + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), subject, tmClientState) + + msg = clienttypes.NewMsgRecoverClient(suite.chainA.App.GetIBCKeeper().GetAuthority(), subject, substitute) + + tc.malleate() + + _, err = suite.chainA.App.GetIBCKeeper().RecoverClient(suite.chainA.GetContext(), msg) + + if tc.expErr == nil { + suite.Require().NoError(err) + + // Assert that client status is now Active + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), subjectPath.EndpointA.ClientID) + suite.Require().NoError(err) + suite.Require().Equal(lightClientModule.Status(suite.chainA.GetContext(), subjectPath.EndpointA.ClientID), exported.Active) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +// tests the IBC handler acknowledgement of a packet on ordered and unordered +// channels. It verifies that the deletion of packet commitments from state +// occurs. It test high level properties like ordering and basic sanity +// checks. More rigorous testing of 'AcknowledgePacket' +// can be found in the 04-channel/keeper/packet_test.go. +func (suite *KeeperTestSuite) TestHandleAcknowledgePacket() { + var ( + packet channeltypes.Packet + path *ibctesting.Path + ) + + testCases := []struct { + name string + malleate func() + expError error + replay bool // indicate replay (no-op) + }{ + {"success: ORDERED", func() { + path.SetChannelOrdered() + path.Setup() + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + }, nil, false}, + {"success: UNORDERED", func() { + path.Setup() + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + }, nil, false}, + {"success: UNORDERED acknowledge out of order packet", func() { + // setup uses an UNORDERED channel + path.Setup() + + // attempts to acknowledge ack with sequence 10 without acknowledging ack with sequence 1 (removing packet commitment) + for i := uint64(1); i < 10; i++ { + sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + } + }, nil, false}, + {"failure: ORDERED acknowledge out of order packet", func() { + path.SetChannelOrdered() + path.Setup() + + // attempts to acknowledge ack with sequence 10 without acknowledging ack with sequence 1 (removing packet commitment + for i := uint64(1); i < 10; i++ { + sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + } + }, errors.New("packet sequence is out of order"), false}, + {"channel does not exist", func() { + // any non-nil value of packet is valid + suite.Require().NotNil(packet) + }, errors.New("channel not found"), false}, + {"packet not received", func() { + path.Setup() + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + }, errors.New("invalid proof"), false}, + {"successful no-op: ORDERED - packet already acknowledged (replay)", func() { + path.SetChannelOrdered() + path.Setup() + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + + err = path.EndpointA.AcknowledgePacket(packet, ibctesting.MockAcknowledgement) + suite.Require().NoError(err) + }, nil, true}, + {"successful no-op: UNORDERED - packet already acknowledged (replay)", func() { + path.Setup() + + sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + err = path.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + + err = path.EndpointA.AcknowledgePacket(packet, ibctesting.MockAcknowledgement) + suite.Require().NoError(err) + }, nil, true}, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + tc.malleate() + + var ( + proof []byte + proofHeight clienttypes.Height + ) + packetKey := host.PacketAcknowledgementKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + if path.EndpointB.ChannelID != "" { + proof, proofHeight = path.EndpointB.QueryProof(packetKey) + } + + msg := channeltypes.NewMsgAcknowledgement(packet, ibcmock.MockAcknowledgement.Acknowledgement(), proof, proofHeight, suite.chainA.SenderAccount.GetAddress().String()) + + ctx := suite.chainA.GetContext() + _, err := suite.chainA.App.GetIBCKeeper().Acknowledgement(ctx, msg) + + events := ctx.EventManager().Events() + + if tc.expError == nil { + suite.Require().NoError(err) + + // verify packet commitment was deleted on source chain + has := suite.chainA.App.GetIBCKeeper().ChannelKeeper.HasPacketCommitment(suite.chainA.GetContext(), packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + suite.Require().False(has) + + // replay should not error as it is treated as a no-op + _, err := suite.chainA.App.GetIBCKeeper().Acknowledgement(suite.chainA.GetContext(), msg) + suite.Require().NoError(err) + + if tc.replay { + // context should not contain application events + suite.Require().NotContains(events, ibcmock.NewMockAckPacketEvent()) + } else { + // context events should contain application events + suite.Require().Contains(events, ibcmock.NewMockAckPacketEvent()) + } + } else { + suite.Require().Error(err) + suite.Require().Contains(err.Error(), tc.expError.Error()) + } + }) + } +} + +// tests the IBC handler timing out a packet on ordered and unordered channels. +// It verifies that the deletion of a packet commitment occurs. It tests +// high level properties like ordering and basic sanity checks. More +// rigorous testing of 'TimeoutPacket' and 'TimeoutExecuted' can be found in +// the 04-channel/keeper/timeout_test.go. +func (suite *KeeperTestSuite) TestHandleTimeoutPacket() { + var ( + packet channeltypes.Packet + packetKey []byte + path *ibctesting.Path + ) + + testCases := []struct { + name string + malleate func() + expErr error + noop bool // indicate no-op + }{ + {"success: ORDERED", func() { + path.SetChannelOrdered() + path.Setup() + + timeoutHeight := clienttypes.GetSelfHeight(suite.chainB.GetContext()) + timeoutTimestamp := uint64(suite.chainB.GetContext().BlockTime().UnixNano()) + + // create packet commitment + sequence, err := path.EndpointA.SendPacket(timeoutHeight, timeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + + // need to update chainA client to prove missing ack + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, timeoutTimestamp) + packetKey = host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) + }, nil, false}, + {"success: UNORDERED", func() { + path.Setup() + + timeoutHeight := clienttypes.GetSelfHeight(suite.chainB.GetContext()) + timeoutTimestamp := uint64(suite.chainB.GetContext().BlockTime().UnixNano()) + + // create packet commitment + sequence, err := path.EndpointA.SendPacket(timeoutHeight, timeoutTimestamp, ibctesting.MockPacketData) + suite.Require().NoError(err) + + // need to update chainA client to prove missing ack + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, timeoutTimestamp) + packetKey = host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + }, nil, false}, + {"success: UNORDERED timeout out of order packet", func() { + // setup uses an UNORDERED channel + path.Setup() + + // attempts to timeout the last packet sent without timing out the first packet + // packet sequences begin at 1 + for i := uint64(1); i < maxSequence; i++ { + timeoutHeight := clienttypes.GetSelfHeight(suite.chainB.GetContext()) + + // create packet commitment + sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + } + + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + packetKey = host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + }, nil, false}, + {"success: ORDERED timeout out of order packet", func() { + path.SetChannelOrdered() + path.Setup() + + // attempts to timeout the last packet sent without timing out the first packet + // packet sequences begin at 1 + for i := uint64(1); i < maxSequence; i++ { + timeoutHeight := clienttypes.GetSelfHeight(suite.chainB.GetContext()) + + // create packet commitment + sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + } + + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + packetKey = host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) + }, nil, false}, + {"channel does not exist", func() { + // any non-nil value of packet is valid + suite.Require().NotNil(packet) + + packetKey = host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) + }, errors.New("channel not found"), false}, + {"successful no-op: UNORDERED - packet not sent", func() { + path.Setup() + packet = channeltypes.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.NewHeight(0, 1), 0) + packetKey = host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + }, nil, true}, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + tc.malleate() + + var ( + proof []byte + proofHeight clienttypes.Height + ) + if path.EndpointB.ChannelID != "" { + proof, proofHeight = path.EndpointB.QueryProof(packetKey) + } + + msg := channeltypes.NewMsgTimeout(packet, 1, proof, proofHeight, suite.chainA.SenderAccount.GetAddress().String()) + + ctx := suite.chainA.GetContext() + _, err := suite.chainA.App.GetIBCKeeper().Timeout(ctx, msg) + + events := ctx.EventManager().Events() + + if tc.expErr == nil { + suite.Require().NoError(err) + + // replay should not return an error as it is treated as a no-op + _, err := suite.chainA.App.GetIBCKeeper().Timeout(suite.chainA.GetContext(), msg) + suite.Require().NoError(err) + + // verify packet commitment was deleted on source chain + has := suite.chainA.App.GetIBCKeeper().ChannelKeeper.HasPacketCommitment(suite.chainA.GetContext(), packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + suite.Require().False(has) + + if tc.noop { + // context should not contain application events + suite.Require().NotContains(events, ibcmock.NewMockTimeoutPacketEvent()) + } else { + // context should contain application events + suite.Require().Contains(events, ibcmock.NewMockTimeoutPacketEvent()) + } + + } else { + suite.Require().Error(err) + + suite.Require().Contains(err.Error(), tc.expErr.Error()) + } + }) + } +} + +// tests the IBC handler timing out a packet via channel closure on ordered +// and unordered channels. It verifies that the deletion of a packet +// commitment occurs. It tests high level properties like ordering and basic +// sanity checks. More rigorous testing of 'TimeoutOnClose' and +// 'TimeoutExecuted' can be found in the 04-channel/keeper/timeout_test.go. +func (suite *KeeperTestSuite) TestHandleTimeoutOnClosePacket() { + var ( + packet channeltypes.Packet + packetKey []byte + path *ibctesting.Path + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + {"success: ORDERED", func() { + path.SetChannelOrdered() + path.Setup() + + // create packet commitment + sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + // need to update chainA client to prove missing ack + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + packetKey = host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) + + // close counterparty channel + path.EndpointB.UpdateChannel(func(channel *channeltypes.Channel) { channel.State = channeltypes.CLOSED }) + }, nil}, + {"success: UNORDERED", func() { + path.Setup() + + // create packet commitment + sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + // need to update chainA client to prove missing ack + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + packetKey = host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + + // close counterparty channel + path.EndpointB.UpdateChannel(func(channel *channeltypes.Channel) { channel.State = channeltypes.CLOSED }) + }, nil}, + {"success: UNORDERED timeout out of order packet", func() { + // setup uses an UNORDERED channel + path.Setup() + + // attempts to timeout the last packet sent without timing out the first packet + // packet sequences begin at 1 + for i := uint64(1); i < maxSequence; i++ { + // create packet commitment + sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + } + + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + packetKey = host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + + // close counterparty channel + path.EndpointB.UpdateChannel(func(channel *channeltypes.Channel) { channel.State = channeltypes.CLOSED }) + }, nil}, + {"success: ORDERED timeout out of order packet", func() { + path.SetChannelOrdered() + path.Setup() + + // attempts to timeout the last packet sent without timing out the first packet + // packet sequences begin at 1 + for i := uint64(1); i < maxSequence; i++ { + // create packet commitment + sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + } + + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + packetKey = host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) + + // close counterparty channel + path.EndpointB.UpdateChannel(func(channel *channeltypes.Channel) { channel.State = channeltypes.CLOSED }) + }, nil}, + {"channel does not exist", func() { + // any non-nil value of packet is valid + suite.Require().NotNil(packet) + + packetKey = host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) + }, errors.New("channel not found")}, + {"successful no-op: UNORDERED - packet not sent", func() { + path.Setup() + packet = channeltypes.NewPacket(ibctesting.MockPacketData, 1, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, clienttypes.NewHeight(0, 1), 0) + packetKey = host.PacketAcknowledgementKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + + // close counterparty channel + path.EndpointB.UpdateChannel(func(channel *channeltypes.Channel) { channel.State = channeltypes.CLOSED }) + }, nil}, + {"ORDERED: channel not closed", func() { + path.SetChannelOrdered() + path.Setup() + + // create packet commitment + sequence, err := path.EndpointA.SendPacket(timeoutHeight, 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + // need to update chainA client to prove missing ack + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + packet = channeltypes.NewPacket(ibctesting.MockPacketData, sequence, path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, timeoutHeight, 0) + packetKey = host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) + }, errors.New("invalid proof")}, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + tc.malleate() + + proof, proofHeight := suite.chainB.QueryProof(packetKey) + + channelKey := host.ChannelKey(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID) + closedProof, _ := suite.chainB.QueryProof(channelKey) + + msg := channeltypes.NewMsgTimeoutOnClose(packet, 1, proof, closedProof, proofHeight, suite.chainA.SenderAccount.GetAddress().String()) + + _, err := suite.chainA.App.GetIBCKeeper().TimeoutOnClose(suite.chainA.GetContext(), msg) + + if tc.expError == nil { + suite.Require().NoError(err) + + // replay should not return an error as it will be treated as a no-op + _, err := suite.chainA.App.GetIBCKeeper().TimeoutOnClose(suite.chainA.GetContext(), msg) + suite.Require().NoError(err) + + // verify packet commitment was deleted on source chain + has := suite.chainA.App.GetIBCKeeper().ChannelKeeper.HasPacketCommitment(suite.chainA.GetContext(), packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + suite.Require().False(has) + + } else { + suite.Require().Error(err) + suite.Require().Contains(err.Error(), tc.expError.Error()) + } + }) + } +} + +func (suite *KeeperTestSuite) TestUpgradeClient() { + var ( + path *ibctesting.Path + newChainID string + newClientHeight clienttypes.Height + upgradedClient *ibctm.ClientState + upgradedConsState exported.ConsensusState + lastHeight exported.Height + msg *clienttypes.MsgUpgradeClient + ) + cases := []struct { + name string + setup func() + expErr error + }{ + { + name: "successful upgrade", + setup: func() { + upgradedClient = ibctm.NewClientState(newChainID, ibctm.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod+ibctesting.TrustingPeriod, ibctesting.MaxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath) + // Call ZeroCustomFields on upgraded clients to clear any client-chosen parameters in test-case upgradedClient + upgradedClient = upgradedClient.ZeroCustomFields() + + upgradedConsState = &ibctm.ConsensusState{ + NextValidatorsHash: []byte("nextValsHash"), + } + + // last Height is at next block + lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + + upgradedClientBz, err := clienttypes.MarshalClientState(suite.chainA.App.AppCodec(), upgradedClient) + suite.Require().NoError(err) + upgradedConsStateBz, err := clienttypes.MarshalConsensusState(suite.chainA.App.AppCodec(), upgradedConsState) + suite.Require().NoError(err) + + // zero custom fields and store in upgrade store + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) //nolint:errcheck // ignore error for testing + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) //nolint:errcheck // ignore error for testing + + // commit upgrade store changes and update clients + suite.coordinator.CommitBlock(suite.chainB) + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + latestHeight := path.EndpointA.GetClientLatestHeight() + upgradeClientProof, _ := suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), latestHeight.GetRevisionHeight()) + upgradedConsensusStateProof, _ := suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), latestHeight.GetRevisionHeight()) + + msg, err = clienttypes.NewMsgUpgradeClient(path.EndpointA.ClientID, upgradedClient, upgradedConsState, + upgradeClientProof, upgradedConsensusStateProof, suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + expErr: nil, + }, + { + name: "VerifyUpgrade fails", + setup: func() { + upgradedClient = ibctm.NewClientState(newChainID, ibctm.DefaultTrustLevel, ibctesting.TrustingPeriod, ibctesting.UnbondingPeriod+ibctesting.TrustingPeriod, ibctesting.MaxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath) + // Call ZeroCustomFields on upgraded clients to clear any client-chosen parameters in test-case upgradedClient + upgradedClient = upgradedClient.ZeroCustomFields() + + upgradedConsState = &ibctm.ConsensusState{ + NextValidatorsHash: []byte("nextValsHash"), + } + + // last Height is at next block + lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + + upgradedClientBz, err := clienttypes.MarshalClientState(suite.chainA.App.AppCodec(), upgradedClient) + suite.Require().NoError(err) + upgradedConsStateBz, err := clienttypes.MarshalConsensusState(suite.chainA.App.AppCodec(), upgradedConsState) + suite.Require().NoError(err) + + // zero custom fields and store in upgrade store + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) //nolint:errcheck // ignore error for testing + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) //nolint:errcheck // ignore error for testing + + // commit upgrade store changes and update clients + suite.coordinator.CommitBlock(suite.chainB) + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + msg, err = clienttypes.NewMsgUpgradeClient(path.EndpointA.ClientID, upgradedClient, upgradedConsState, nil, nil, suite.chainA.SenderAccount.GetAddress().String()) + suite.Require().NoError(err) + }, + expErr: errors.New("invalid merkle proof"), + }, + } + + for _, tc := range cases { + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + var err error + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + revisionNumber := clienttypes.ParseChainID(clientState.ChainId) + + newChainID, err = clienttypes.SetRevisionNumber(clientState.ChainId, revisionNumber+1) + suite.Require().NoError(err) + + newClientHeight = clienttypes.NewHeight(revisionNumber+1, clientState.LatestHeight.GetRevisionHeight()+1) + + tc.setup() + + ctx := suite.chainA.GetContext() + _, err = suite.chainA.GetSimApp().GetIBCKeeper().UpgradeClient(ctx, msg) + + if tc.expErr == nil { + suite.Require().NoError(err, "upgrade handler failed on valid case: %s", tc.name) + newClient, ok := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(ok) + newChainSpecifiedClient := newClient.(*ibctm.ClientState).ZeroCustomFields() + suite.Require().Equal(upgradedClient, newChainSpecifiedClient) + + expectedEvents := sdk.Events{ + sdk.NewEvent( + clienttypes.EventTypeUpgradeClient, + sdk.NewAttribute(clienttypes.AttributeKeyClientID, ibctesting.FirstClientID), + sdk.NewAttribute(clienttypes.AttributeKeyClientType, path.EndpointA.GetClientState().ClientType()), + sdk.NewAttribute(clienttypes.AttributeKeyConsensusHeight, path.EndpointA.GetClientLatestHeight().String()), + ), + }.ToABCIEvents() + + expectedEvents = sdk.MarkEventsToIndex(expectedEvents, map[string]struct{}{}) + ibctesting.AssertEvents(&suite.Suite, expectedEvents, ctx.EventManager().Events().ToABCIEvents()) + } else { + suite.Require().Error(err, "upgrade handler passed on invalid case: %s", tc.name) + suite.Require().Contains(err.Error(), tc.expErr.Error()) + } + } +} + +// TestIBCSoftwareUpgrade tests the IBCSoftwareUpgrade rpc handler +func (suite *KeeperTestSuite) TestIBCSoftwareUpgrade() { + var msg *clienttypes.MsgIBCSoftwareUpgrade + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success: valid authority and client upgrade", + func() {}, + nil, + }, + { + "failure: invalid authority address", + func() { + msg.Signer = suite.chainA.SenderAccount.GetAddress().String() + }, + ibcerrors.ErrUnauthorized, + }, + { + "failure: invalid clientState", + func() { + msg.UpgradedClientState = nil + }, + clienttypes.ErrInvalidClientType, + }, + { + "failure: failed to schedule client upgrade", + func() { + msg.Plan.Height = 0 + }, + sdkerrors.ErrInvalidRequest, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + validAuthority := suite.chainA.App.GetIBCKeeper().GetAuthority() + plan := upgradetypes.Plan{ + Name: "upgrade IBC clients", + Height: 1000, + } + // update trusting period + clientState, ok := path.EndpointB.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + clientState.TrustingPeriod += 100 + + var err error + msg, err = clienttypes.NewMsgIBCSoftwareUpgrade( + validAuthority, + plan, + clientState, + ) + + suite.Require().NoError(err) + + tc.malleate() + + _, err = suite.chainA.App.GetIBCKeeper().IBCSoftwareUpgrade(suite.chainA.GetContext(), msg) + + if tc.expError == nil { + suite.Require().NoError(err) + // upgrade plan is stored + storedPlan, err := suite.chainA.GetSimApp().UpgradeKeeper.GetUpgradePlan(suite.chainA.GetContext()) + suite.Require().NoError(err) + suite.Require().Equal(plan, storedPlan) + + // upgraded client state is stored + bz, err := suite.chainA.GetSimApp().UpgradeKeeper.GetUpgradedClient(suite.chainA.GetContext(), plan.Height) + suite.Require().NoError(err) + upgradedClientState, err := clienttypes.UnmarshalClientState(suite.chainA.App.AppCodec(), bz) + suite.Require().NoError(err) + suite.Require().Equal(clientState.ZeroCustomFields(), upgradedClientState) + } else { + suite.Require().True(errors.Is(err, tc.expError)) + } + }) + } +} + +// TestUpdateClientParams tests the UpdateClientParams rpc handler +func (suite *KeeperTestSuite) TestUpdateClientParams() { + signer := suite.chainA.App.GetIBCKeeper().GetAuthority() + testCases := []struct { + name string + msg *clienttypes.MsgUpdateParams + expError error + }{ + { + "success: valid signer and default params", + clienttypes.NewMsgUpdateParams(signer, clienttypes.DefaultParams()), + nil, + }, + { + "failure: malformed signer address", + clienttypes.NewMsgUpdateParams(ibctesting.InvalidID, clienttypes.DefaultParams()), + errors.New("unauthorized"), + }, + { + "failure: empty signer address", + clienttypes.NewMsgUpdateParams("", clienttypes.DefaultParams()), + errors.New("unauthorized"), + }, + { + "failure: whitespace signer address", + clienttypes.NewMsgUpdateParams(" ", clienttypes.DefaultParams()), + errors.New("unauthorized"), + }, + { + "failure: unauthorized signer address", + clienttypes.NewMsgUpdateParams(ibctesting.TestAccAddress, clienttypes.DefaultParams()), + errors.New("unauthorized"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + _, err := suite.chainA.App.GetIBCKeeper().UpdateClientParams(suite.chainA.GetContext(), tc.msg) + if tc.expError == nil { + suite.Require().NoError(err) + p := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetParams(suite.chainA.GetContext()) + suite.Require().Equal(tc.msg.Params, p) + } else { + suite.Require().Error(err) + suite.Require().Contains(err.Error(), tc.expError.Error()) + } + }) + } +} + +// TestUpdateConnectionParams tests the UpdateConnectionParams rpc handler +func (suite *KeeperTestSuite) TestUpdateConnectionParams() { + signer := suite.chainA.App.GetIBCKeeper().GetAuthority() + testCases := []struct { + name string + msg *connectiontypes.MsgUpdateParams + expErr error + }{ + { + "success: valid signer and default params", + connectiontypes.NewMsgUpdateParams(signer, connectiontypes.DefaultParams()), + nil, + }, + { + "failure: malformed signer address", + connectiontypes.NewMsgUpdateParams(ibctesting.InvalidID, connectiontypes.DefaultParams()), + errors.New("unauthorized"), + }, + { + "failure: empty signer address", + connectiontypes.NewMsgUpdateParams("", connectiontypes.DefaultParams()), + errors.New("unauthorized"), + }, + { + "failure: whitespace signer address", + connectiontypes.NewMsgUpdateParams(" ", connectiontypes.DefaultParams()), + errors.New("unauthorized"), + }, + { + "failure: unauthorized signer address", + connectiontypes.NewMsgUpdateParams(ibctesting.TestAccAddress, connectiontypes.DefaultParams()), + errors.New("unauthorized"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + _, err := suite.chainA.App.GetIBCKeeper().UpdateConnectionParams(suite.chainA.GetContext(), tc.msg) + if tc.expErr == nil { + suite.Require().NoError(err) + p := suite.chainA.App.GetIBCKeeper().ConnectionKeeper.GetParams(suite.chainA.GetContext()) + suite.Require().Equal(tc.msg.Params, p) + } else { + suite.Require().Error(err) + suite.Require().Contains(err.Error(), tc.expErr.Error()) + } + }) + } +} + +func (suite *KeeperTestSuite) TestUpdateClientConfig() { + var ( + path *ibctesting.Path + signer string + config clientv2types.Config + ) + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success: valid authority and default config", + func() { + signer = suite.chainA.App.GetIBCKeeper().GetAuthority() + }, + nil, + }, + { + "success: valid creator and default config", + func() { + signer = suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientCreator(suite.chainA.GetContext(), path.EndpointA.ClientID).String() + }, + nil, + }, + { + "success: valid authority and custom config", + func() { + signer = suite.chainA.App.GetIBCKeeper().GetAuthority() + config = clientv2types.NewConfig(suite.chainB.SenderAccount.String(), suite.chainA.SenderAccount.String()) + }, + nil, + }, + { + "success: valid creator and default config", + func() { + signer = suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientCreator(suite.chainA.GetContext(), path.EndpointA.ClientID).String() + config = clientv2types.NewConfig(suite.chainB.SenderAccount.String(), suite.chainA.SenderAccount.String()) + }, + nil, + }, + { + "success: valid creator and setting config to empty after it has been set", + func() { + signer = suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientCreator(suite.chainA.GetContext(), path.EndpointA.ClientID).String() + config = clientv2types.NewConfig(suite.chainB.SenderAccount.String(), suite.chainA.SenderAccount.String()) + _, err := suite.chainA.App.GetIBCKeeper().UpdateClientConfig(suite.chainA.GetContext(), clientv2types.NewMsgUpdateClientConfig(path.EndpointA.ClientID, signer, config)) + suite.Require().NoError(err) + config = clientv2types.DefaultConfig() + }, + nil, + }, + { + "success: valid creator and setting config to different config after it has been set", + func() { + signer = suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientCreator(suite.chainA.GetContext(), path.EndpointA.ClientID).String() + config = clientv2types.NewConfig(suite.chainA.SenderAccount.String()) + _, err := suite.chainA.App.GetIBCKeeper().UpdateClientConfig(suite.chainA.GetContext(), clientv2types.NewMsgUpdateClientConfig(path.EndpointA.ClientID, signer, config)) + suite.Require().NoError(err) + config = clientv2types.NewConfig(suite.chainB.SenderAccount.String(), suite.chainA.SenderAccount.String()) + }, + nil, + }, + { + "failure: invalid signer", + func() { + signer = suite.chainB.SenderAccount.GetAddress().String() + config = clientv2types.NewConfig(suite.chainB.SenderAccount.String()) + }, + ibcerrors.ErrUnauthorized, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + config = clientv2types.DefaultConfig() + + tc.malleate() + + msg := clientv2types.NewMsgUpdateClientConfig(path.EndpointA.ClientID, signer, config) + _, err := suite.chainA.App.GetIBCKeeper().UpdateClientConfig(suite.chainA.GetContext(), msg) + if tc.expError == nil { + suite.Require().NoError(err) + c := suite.chainA.App.GetIBCKeeper().ClientV2Keeper.GetConfig(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().Equal(config, c) + } else { + suite.Require().Error(err) + suite.Require().Contains(err.Error(), tc.expError.Error()) + } + }) + } +} + +// TestDeleteClientCreator tests the DeleteClientCreator message handler +func (suite *KeeperTestSuite) TestDeleteClientCreator() { + var ( + path *ibctesting.Path + clientID string + msg *clienttypes.MsgDeleteClientCreator + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success: valid creator deletes itself", + func() { + creator := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientCreator(suite.chainA.GetContext(), clientID) + msg = clienttypes.NewMsgDeleteClientCreator(clientID, creator.String()) + }, + nil, + }, + { + "success: valid authority deletes client creator", + func() { + msg = clienttypes.NewMsgDeleteClientCreator(clientID, suite.chainA.App.GetIBCKeeper().GetAuthority()) + }, + nil, + }, + { + "failure: deleting a client creator that was already deleted", + func() { + // First delete the creator + authority := suite.chainA.App.GetIBCKeeper().GetAuthority() + deleteMsg := clienttypes.NewMsgDeleteClientCreator(clientID, authority) + _, err := suite.chainA.App.GetIBCKeeper().DeleteClientCreator(suite.chainA.GetContext(), deleteMsg) + suite.Require().NoError(err) + + // Now try to delete it again + msg = clienttypes.NewMsgDeleteClientCreator(clientID, authority) + }, + ibcerrors.ErrNotFound, // Now it should fail with not found + }, + { + "failure: unauthorized signer - not creator or authority", + func() { + msg = clienttypes.NewMsgDeleteClientCreator(clientID, suite.chainB.SenderAccount.GetAddress().String()) + }, + ibcerrors.ErrUnauthorized, + }, + { + "failure: client ID does not exist", + func() { + msg = clienttypes.NewMsgDeleteClientCreator("nonexistentclient", suite.chainA.App.GetIBCKeeper().GetAuthority()) + }, + ibcerrors.ErrNotFound, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + clientID = path.EndpointA.ClientID + + tc.malleate() + + _, err := suite.chainA.App.GetIBCKeeper().DeleteClientCreator(suite.chainA.GetContext(), msg) + + if tc.expError == nil { + suite.Require().NoError(err) + + // Verify creator has been deleted + creator := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientCreator(suite.chainA.GetContext(), clientID) + suite.Require().Nil(creator) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} diff --git a/modules/core/metrics/metrics.go b/modules/core/metrics/metrics.go new file mode 100644 index 0000000..2aa187e --- /dev/null +++ b/modules/core/metrics/metrics.go @@ -0,0 +1,21 @@ +package metrics + +// Prometheus metric labels. +const ( + // 02-client labels + + LabelClientType = "client_type" + LabelClientID = "client_id" + LabelUpdateType = "update_type" + LabelMsgType = "msg_type" + + // Message server labels + + LabelSourcePort = "source_port" + LabelSourceChannel = "source_channel" + LabelDestinationPort = "destination_port" + LabelDestinationChannel = "destination_channel" + LabelTimeoutType = "timeout_type" + LabelDenom = "denom" + LabelSource = "source" +) diff --git a/modules/core/migrations/v7/genesis.go b/modules/core/migrations/v7/genesis.go new file mode 100644 index 0000000..2809997 --- /dev/null +++ b/modules/core/migrations/v7/genesis.go @@ -0,0 +1,42 @@ +package v7 + +import ( + "github.com/cosmos/cosmos-sdk/codec" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + + clientv7 "github.com/cosmos/ibc-go/v10/modules/core/02-client/migrations/v7" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + "github.com/cosmos/ibc-go/v10/modules/core/types" +) + +// MigrateGenesis accepts an exported IBC client genesis file and migrates it to: +// +// - Update solo machine client state protobuf definition (v2 to v3) +// - Remove all solo machine consensus states +// - Remove any localhost clients +func MigrateGenesis(appState genutiltypes.AppMap, cdc codec.ProtoCodecMarshaler) (genutiltypes.AppMap, error) { + if appState[ibcexported.ModuleName] == nil { + return appState, nil + } + + // ensure legacy solo machines types are registered + clientv7.RegisterInterfaces(cdc.InterfaceRegistry()) + + // unmarshal old ibc genesis state + ibcGenState := &types.GenesisState{} + cdc.MustUnmarshalJSON(appState[ibcexported.ModuleName], ibcGenState) + + clientGenState, err := clientv7.MigrateGenesis(&ibcGenState.ClientGenesis, cdc) + if err != nil { + return nil, err + } + + ibcGenState.ClientGenesis = *clientGenState + + // delete old genesis state + delete(appState, ibcexported.ModuleName) + + // set new ibc genesis state + appState[ibcexported.ModuleName] = cdc.MustMarshalJSON(ibcGenState) + return appState, nil +} diff --git a/modules/core/migrations/v7/genesis_test.go b/modules/core/migrations/v7/genesis_test.go new file mode 100644 index 0000000..39289aa --- /dev/null +++ b/modules/core/migrations/v7/genesis_test.go @@ -0,0 +1,168 @@ +package v7_test + +import ( + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/runtime" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + + ibcclient "github.com/cosmos/ibc-go/v10/modules/core/02-client" + clientv7 "github.com/cosmos/ibc-go/v10/modules/core/02-client/migrations/v7" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + "github.com/cosmos/ibc-go/v10/modules/core/migrations/v7" + "github.com/cosmos/ibc-go/v10/modules/core/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +type MigrationsV7TestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains used for convenience and readability + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain +} + +// TestMigrationsV7TestSuite runs all the tests within this package. +func TestMigrationsV7TestSuite(t *testing.T) { + testifysuite.Run(t, new(MigrationsV7TestSuite)) +} + +// SetupTest creates a coordinator with 2 test chains. +func (suite *MigrationsV7TestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) +} + +// NOTE: this test is mainly copied from 02-client/migrations/v7/genesis_test.go +func (suite *MigrationsV7TestSuite) TestMigrateGenesisSolomachine() { + // create tendermint clients + for range 3 { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + + path.SetupClients() + + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + // update a second time to add more state + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + } + + // create multiple legacy solo machine clients + solomachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, ibctesting.DefaultSolomachineClientID, "testing", 1) + solomachineMulti := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "06-solomachine-1", "testing", 4) + + clientGenState := ibcclient.ExportGenesis(suite.chainA.GetContext(), suite.chainA.App.GetIBCKeeper().ClientKeeper) + + // manually generate old proto buf definitions and set in genesis + // NOTE: we cannot use 'ExportGenesis' for the solo machines since we are + // using client states and consensus states which do not implement the exported.ClientState + // and exported.ConsensusState interface + var clients []clienttypes.IdentifiedClientState + for _, sm := range []*ibctesting.Solomachine{solomachine, solomachineMulti} { + clientState := sm.ClientState() + + // generate old client state proto definition + legacyClientState := &clientv7.ClientState{ + Sequence: clientState.Sequence, + ConsensusState: &clientv7.ConsensusState{ + PublicKey: clientState.ConsensusState.PublicKey, + Diversifier: clientState.ConsensusState.Diversifier, + Timestamp: clientState.ConsensusState.Timestamp, + }, + AllowUpdateAfterProposal: true, + } + + // set client state + protoAny, err := codectypes.NewAnyWithValue(legacyClientState) + suite.Require().NoError(err) + suite.Require().NotNil(protoAny) + + clients = append(clients, clienttypes.IdentifiedClientState{ + ClientId: sm.ClientID, + ClientState: protoAny, + }) + + // set in store for ease of determining expected genesis + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), sm.ClientID) + cdc, ok := suite.chainA.App.AppCodec().(*codec.ProtoCodec) + suite.Require().True(ok) + clientv7.RegisterInterfaces(cdc.InterfaceRegistry()) + + bz, err := cdc.MarshalInterface(legacyClientState) + suite.Require().NoError(err) + clientStore.Set(host.ClientStateKey(), bz) + + protoAny, err = codectypes.NewAnyWithValue(legacyClientState.ConsensusState) + suite.Require().NoError(err) + suite.Require().NotNil(protoAny) + + // obtain marshalled bytes to set in client store + bz, err = cdc.MarshalInterface(legacyClientState.ConsensusState) + suite.Require().NoError(err) + + var consensusStates []clienttypes.ConsensusStateWithHeight + + // set consensus states in store and genesis + for i := uint64(0); i < 10; i++ { + height := clienttypes.NewHeight(1, i) + clientStore.Set(host.ConsensusStateKey(height), bz) + consensusStates = append(consensusStates, clienttypes.ConsensusStateWithHeight{ + Height: height, + ConsensusState: protoAny, + }) + } + + clientGenState.ClientsConsensus = append(clientGenState.ClientsConsensus, clienttypes.ClientConsensusStates{ + ClientId: sm.ClientID, + ConsensusStates: consensusStates, + }) + } + + // solo machine clients must come before tendermint in expected + clientGenState.Clients = append(clients, clientGenState.Clients...) + + // migrate store get expected genesis + // store migration and genesis migration should produce identical results + // NOTE: tendermint clients are not pruned in genesis so the test should not have expired tendermint clients + err := clientv7.MigrateStore(suite.chainA.GetContext(), runtime.NewKVStoreService(suite.chainA.GetSimApp().GetKey(ibcexported.StoreKey)), suite.chainA.App.AppCodec(), suite.chainA.GetSimApp().IBCKeeper.ClientKeeper) + suite.Require().NoError(err) + expectedClientGenState := ibcclient.ExportGenesis(suite.chainA.GetContext(), suite.chainA.App.GetIBCKeeper().ClientKeeper) + + cdc, ok := suite.chainA.App.AppCodec().(*codec.ProtoCodec) + suite.Require().True(ok) + + // NOTE: these lines are added in comparison to 02-client/migrations/v7/genesis_test.go + // generate appState with old ibc genesis state + appState := genutiltypes.AppMap{} + ibcGenState := types.DefaultGenesisState() + ibcGenState.ClientGenesis = clientGenState + + // ensure tests pass even if the legacy solo machine is already registered + clientv7.RegisterInterfaces(cdc.InterfaceRegistry()) + appState[ibcexported.ModuleName] = cdc.MustMarshalJSON(ibcGenState) + + // NOTE: genesis time isn't updated since we aren't testing for tendermint consensus state pruning + migrated, err := v7.MigrateGenesis(appState, cdc) + suite.Require().NoError(err) + + expectedAppState := genutiltypes.AppMap{} + expectedIBCGenState := types.DefaultGenesisState() + expectedIBCGenState.ClientGenesis = expectedClientGenState + + bz, err := cdc.MarshalJSON(expectedIBCGenState) + suite.Require().NoError(err) + expectedAppState[ibcexported.ModuleName] = bz + + suite.Require().Equal(expectedAppState, migrated) +} diff --git a/modules/core/module.go b/modules/core/module.go new file mode 100644 index 0000000..4893b01 --- /dev/null +++ b/modules/core/module.go @@ -0,0 +1,237 @@ +package ibc + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + + "cosmossdk.io/core/appmodule" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + ibcclient "github.com/cosmos/ibc-go/v10/modules/core/02-client" + clientkeeper "github.com/cosmos/ibc-go/v10/modules/core/02-client/keeper" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + clientv2keeper "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/keeper" + clientv2types "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" + connectionkeeper "github.com/cosmos/ibc-go/v10/modules/core/03-connection/keeper" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channelkeeper "github.com/cosmos/ibc-go/v10/modules/core/04-channel/keeper" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + channelkeeperv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/keeper" + channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + "github.com/cosmos/ibc-go/v10/modules/core/client/cli" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + "github.com/cosmos/ibc-go/v10/modules/core/keeper" + "github.com/cosmos/ibc-go/v10/modules/core/simulation" + "github.com/cosmos/ibc-go/v10/modules/core/types" +) + +var ( + _ module.AppModule = (*AppModule)(nil) + _ module.AppModuleBasic = (*AppModuleBasic)(nil) + _ module.AppModuleSimulation = (*AppModule)(nil) + _ module.HasGenesis = (*AppModule)(nil) + _ module.HasName = (*AppModule)(nil) + _ module.HasConsensusVersion = (*AppModule)(nil) + _ module.HasServices = (*AppModule)(nil) + _ module.HasProposalMsgs = (*AppModule)(nil) + _ appmodule.AppModule = (*AppModule)(nil) + _ appmodule.HasBeginBlocker = (*AppModule)(nil) +) + +// AppModuleBasic defines the basic application module used by the ibc module. +type AppModuleBasic struct{} + +// Name returns the ibc module's name. +func (AppModuleBasic) Name() string { + return exported.ModuleName +} + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (AppModule) IsOnePerModuleType() {} + +// IsAppModule implements the appmodule.AppModule interface. +func (AppModule) IsAppModule() {} + +// RegisterLegacyAminoCodec implements AppModuleBasic interface. +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + clienttypes.RegisterLegacyAminoCodec(cdc) +} + +// DefaultGenesis returns default genesis state as raw bytes for the ibc +// module. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(types.DefaultGenesisState()) +} + +// ValidateGenesis performs genesis state validation for the ibc module. +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { + var gs types.GenesisState + if err := cdc.UnmarshalJSON(bz, &gs); err != nil { + return fmt.Errorf("failed to unmarshal %s genesis state: %w", exported.ModuleName, err) + } + + return gs.Validate() +} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the ibc module. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { + err := clienttypes.RegisterQueryHandlerClient(context.Background(), mux, clienttypes.NewQueryClient(clientCtx)) + if err != nil { + panic(err) + } + err = connectiontypes.RegisterQueryHandlerClient(context.Background(), mux, connectiontypes.NewQueryClient(clientCtx)) + if err != nil { + panic(err) + } + err = channeltypes.RegisterQueryHandlerClient(context.Background(), mux, channeltypes.NewQueryClient(clientCtx)) + if err != nil { + panic(err) + } + err = channeltypesv2.RegisterQueryHandlerClient(context.Background(), mux, channeltypesv2.NewQueryClient(clientCtx)) + if err != nil { + panic(err) + } +} + +// GetTxCmd returns the root tx command for the ibc module. +func (AppModuleBasic) GetTxCmd() *cobra.Command { + return cli.GetTxCmd() +} + +// GetQueryCmd returns no root query command for the ibc module. +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd() +} + +// RegisterInterfaces registers module concrete types into protobuf Any. +func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) { + types.RegisterInterfaces(registry) +} + +// AppModule implements an application module for the ibc module. +type AppModule struct { + AppModuleBasic + keeper *keeper.Keeper +} + +// NewAppModule creates a new AppModule object +func NewAppModule(k *keeper.Keeper) AppModule { + return AppModule{ + keeper: k, + } +} + +// Name returns the ibc module's name. +func (AppModule) Name() string { + return exported.ModuleName +} + +// RegisterServices registers module services. +func (am AppModule) RegisterServices(cfg module.Configurator) { + clienttypes.RegisterMsgServer(cfg.MsgServer(), am.keeper) + clientv2types.RegisterMsgServer(cfg.MsgServer(), am.keeper) + connectiontypes.RegisterMsgServer(cfg.MsgServer(), am.keeper) + channeltypes.RegisterMsgServer(cfg.MsgServer(), am.keeper) + channeltypesv2.RegisterMsgServer(cfg.MsgServer(), am.keeper.ChannelKeeperV2) + + clienttypes.RegisterQueryServer(cfg.QueryServer(), clientkeeper.NewQueryServer(am.keeper.ClientKeeper)) + clientv2types.RegisterQueryServer(cfg.QueryServer(), clientv2keeper.NewQueryServer(am.keeper.ClientV2Keeper)) + connectiontypes.RegisterQueryServer(cfg.QueryServer(), connectionkeeper.NewQueryServer(am.keeper.ConnectionKeeper)) + channeltypes.RegisterQueryServer(cfg.QueryServer(), channelkeeper.NewQueryServer(am.keeper.ChannelKeeper)) + channeltypesv2.RegisterQueryServer(cfg.QueryServer(), channelkeeperv2.NewQueryServer(am.keeper.ChannelKeeperV2)) + + clientMigrator := clientkeeper.NewMigrator(am.keeper.ClientKeeper) + if err := cfg.RegisterMigration(exported.ModuleName, 2, clientMigrator.Migrate2to3); err != nil { + panic(err) + } + + connectionMigrator := connectionkeeper.NewMigrator(am.keeper.ConnectionKeeper) + if err := cfg.RegisterMigration(exported.ModuleName, 3, connectionMigrator.Migrate3to4); err != nil { + panic(err) + } + + if err := cfg.RegisterMigration(exported.ModuleName, 4, func(ctx sdk.Context) error { + if err := clientMigrator.MigrateParams(ctx); err != nil { + return err + } + + return connectionMigrator.MigrateParams(ctx) + }); err != nil { + panic(err) + } + + // This upgrade used to just add default params, since we have deleted it (in consensus version 8 - ibc-go v10), + // we just return directly to increment the ConsensusVersion as expected + if err := cfg.RegisterMigration(exported.ModuleName, 5, func(_ sdk.Context) error { + return nil + }); err != nil { + panic(err) + } + + if err := cfg.RegisterMigration(exported.ModuleName, 6, clientMigrator.MigrateToStatelessLocalhost); err != nil { + panic(err) + } + + channelMigrator := channelkeeper.NewMigrator(am.keeper.ChannelKeeper) + if err := cfg.RegisterMigration(exported.ModuleName, 7, channelMigrator.Migrate7To8); err != nil { + panic(err) + } +} + +// InitGenesis performs genesis initialization for the ibc module. It returns +// no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, bz json.RawMessage) { + var gs types.GenesisState + err := cdc.UnmarshalJSON(bz, &gs) + if err != nil { + panic(fmt.Errorf("failed to unmarshal %s genesis state: %s", exported.ModuleName, err)) + } + InitGenesis(ctx, *am.keeper, &gs) +} + +// ExportGenesis returns the exported genesis state as raw bytes for the ibc +// module. +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(ExportGenesis(ctx, *am.keeper)) +} + +// ConsensusVersion implements AppModule/ConsensusVersion. +func (AppModule) ConsensusVersion() uint64 { return 8 } + +// BeginBlock returns the begin blocker for the ibc module. +func (am AppModule) BeginBlock(goCtx context.Context) error { + ibcclient.BeginBlocker(sdk.UnwrapSDKContext(goCtx), am.keeper.ClientKeeper) + return nil +} + +// AppModuleSimulation functions + +// GenerateGenesisState creates a randomized GenState of the ibc module. +func (AppModule) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) +} + +// ProposalMsgs returns msgs used for governance proposals for simulations. +func (AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { + return simulation.ProposalMsgs() +} + +// RegisterStoreDecoder registers a decoder for ibc module's types +func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { + sdr[exported.StoreKey] = simulation.NewDecodeStore(*am.keeper) +} + +// WeightedOperations returns the all the ibc module operations with their respective weights. +func (AppModule) WeightedOperations(_ module.SimulationState) []simtypes.WeightedOperation { + return nil +} diff --git a/modules/core/simulation/decoder.go b/modules/core/simulation/decoder.go new file mode 100644 index 0000000..5be15fc --- /dev/null +++ b/modules/core/simulation/decoder.go @@ -0,0 +1,33 @@ +package simulation + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/types/kv" + + clientsim "github.com/cosmos/ibc-go/v10/modules/core/02-client/simulation" + connectionsim "github.com/cosmos/ibc-go/v10/modules/core/03-connection/simulation" + channelsim "github.com/cosmos/ibc-go/v10/modules/core/04-channel/simulation" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + "github.com/cosmos/ibc-go/v10/modules/core/keeper" +) + +// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's +// Value to the corresponding ibc type. +func NewDecodeStore(k keeper.Keeper) func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + if res, found := clientsim.NewDecodeStore(k.Codec(), kvA, kvB); found { + return res + } + + if res, found := connectionsim.NewDecodeStore(k.Codec(), kvA, kvB); found { + return res + } + + if res, found := channelsim.NewDecodeStore(k.Codec(), kvA, kvB); found { + return res + } + + panic(fmt.Errorf("invalid %s key prefix: %s", ibcexported.ModuleName, string(kvA.Key))) + } +} diff --git a/modules/core/simulation/decoder_test.go b/modules/core/simulation/decoder_test.go new file mode 100644 index 0000000..3c7c4d3 --- /dev/null +++ b/modules/core/simulation/decoder_test.go @@ -0,0 +1,80 @@ +package simulation_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/types/kv" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/simulation" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + "github.com/cosmos/ibc-go/v10/testing/simapp" +) + +func TestDecodeStore(t *testing.T) { + app := simapp.Setup(t, false) + dec := simulation.NewDecodeStore(*app.IBCKeeper) + + clientID := "clientidone" + connectionID := "connectionidone" + channelID := "channelidone" + portID := "portidone" + + clientState := &ibctm.ClientState{ + FrozenHeight: clienttypes.NewHeight(0, 10), + } + connection := connectiontypes.ConnectionEnd{ + ClientId: "clientidone", + Versions: []*connectiontypes.Version{connectiontypes.NewVersion("1", nil)}, + } + channel := channeltypes.Channel{ + State: channeltypes.OPEN, + Version: "1.0", + } + + kvPairs := kv.Pairs{ + Pairs: []kv.Pair{ + { + Key: host.FullClientStateKey(clientID), + Value: clienttypes.MustMarshalClientState(app.AppCodec(), clientState), + }, + { + Key: host.ConnectionKey(connectionID), + Value: app.IBCKeeper.Codec().MustMarshal(&connection), + }, + { + Key: host.ChannelKey(portID, channelID), + Value: app.IBCKeeper.Codec().MustMarshal(&channel), + }, + { + Key: []byte{0x99}, + Value: []byte{0x99}, + }, + }, + } + tests := []struct { + name string + expectedLog string + }{ + {"ClientState", fmt.Sprintf("ClientState A: %v\nClientState B: %v", clientState, clientState)}, + {"ConnectionEnd", fmt.Sprintf("ConnectionEnd A: %v\nConnectionEnd B: %v", connection, connection)}, + {"Channel", fmt.Sprintf("Channel A: %v\nChannel B: %v", channel, channel)}, + {"other", ""}, + } + + for i, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if i == len(tests)-1 { + require.Panics(t, func() { dec(kvPairs.Pairs[i], kvPairs.Pairs[i]) }, tt.name) + } else { + require.Equal(t, tt.expectedLog, dec(kvPairs.Pairs[i], kvPairs.Pairs[i]), tt.name) + } + }) + } +} diff --git a/modules/core/simulation/genesis.go b/modules/core/simulation/genesis.go new file mode 100644 index 0000000..a853919 --- /dev/null +++ b/modules/core/simulation/genesis.go @@ -0,0 +1,64 @@ +package simulation + +// DONTCOVER + +import ( + "encoding/json" + "fmt" + "math/rand" + + "github.com/cosmos/cosmos-sdk/types/module" + + clientsims "github.com/cosmos/ibc-go/v10/modules/core/02-client/simulation" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + connectionsims "github.com/cosmos/ibc-go/v10/modules/core/03-connection/simulation" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channelsims "github.com/cosmos/ibc-go/v10/modules/core/04-channel/simulation" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + "github.com/cosmos/ibc-go/v10/modules/core/types" +) + +// Simulation parameter constants +const ( + clientGenesis = "client_genesis" + connectionGenesis = "connection_genesis" + channelGenesis = "channel_genesis" +) + +// RandomizedGenState generates a random GenesisState for evidence +func RandomizedGenState(simState *module.SimulationState) { + var ( + clientGenesisState clienttypes.GenesisState + connectionGenesisState connectiontypes.GenesisState + channelGenesisState channeltypes.GenesisState + ) + + simState.AppParams.GetOrGenerate( + clientGenesis, &clientGenesisState, simState.Rand, + func(r *rand.Rand) { clientGenesisState = clientsims.GenClientGenesis(r, simState.Accounts) }, + ) + + simState.AppParams.GetOrGenerate( + connectionGenesis, &connectionGenesisState, simState.Rand, + func(r *rand.Rand) { connectionGenesisState = connectionsims.GenConnectionGenesis(r, simState.Accounts) }, + ) + + simState.AppParams.GetOrGenerate( + channelGenesis, &channelGenesisState, simState.Rand, + func(r *rand.Rand) { channelGenesisState = channelsims.GenChannelGenesis(r, simState.Accounts) }, + ) + + ibcGenesis := types.GenesisState{ + ClientGenesis: clientGenesisState, + ConnectionGenesis: connectionGenesisState, + ChannelGenesis: channelGenesisState, + } + + bz, err := json.MarshalIndent(&ibcGenesis, "", " ") + if err != nil { + panic(err) + } + fmt.Printf("Selected randomly generated %s parameters:\n%s\n", ibcexported.ModuleName, bz) + simState.GenState[ibcexported.ModuleName] = simState.Cdc.MustMarshalJSON(&ibcGenesis) +} diff --git a/modules/core/simulation/genesis_test.go b/modules/core/simulation/genesis_test.go new file mode 100644 index 0000000..d2a78cf --- /dev/null +++ b/modules/core/simulation/genesis_test.go @@ -0,0 +1,54 @@ +package simulation_test + +import ( + "encoding/json" + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + + sdkmath "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + "github.com/cosmos/ibc-go/v10/modules/core/simulation" + "github.com/cosmos/ibc-go/v10/modules/core/types" +) + +// TestRandomizedGenState tests the normal scenario of applying RandomizedGenState. +// Abonormal scenarios are not tested here. +func TestRandomizedGenState(t *testing.T) { + interfaceRegistry := codectypes.NewInterfaceRegistry() + cryptocodec.RegisterInterfaces(interfaceRegistry) + cdc := codec.NewProtoCodec(interfaceRegistry) + + s := rand.NewSource(1) + r := rand.New(s) + + simState := module.SimulationState{ + AppParams: make(simtypes.AppParams), + Cdc: cdc, + Rand: r, + NumBonded: 3, + Accounts: simtypes.RandomAccounts(r, 3), + InitialStake: sdkmath.NewInt(1000), + GenState: make(map[string]json.RawMessage), + } + + // Remark: the current RandomizedGenState function + // is actually not random as it does not utilize concretely the random value r. + // This tests will pass for any value of r. + simulation.RandomizedGenState(&simState) + + var ibcGenesis types.GenesisState + simState.Cdc.MustUnmarshalJSON(simState.GenState[ibcexported.ModuleName], &ibcGenesis) + + require.NotNil(t, ibcGenesis.ClientGenesis) + require.NotNil(t, ibcGenesis.ConnectionGenesis) + require.NotNil(t, ibcGenesis.ChannelGenesis) +} diff --git a/modules/core/simulation/proposals.go b/modules/core/simulation/proposals.go new file mode 100644 index 0000000..c0d9d5a --- /dev/null +++ b/modules/core/simulation/proposals.go @@ -0,0 +1,117 @@ +package simulation + +import ( + "math/rand" + "time" + + upgradetypes "cosmossdk.io/x/upgrade/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" +) + +// Simulation operation weights constants +const ( + DefaultWeight int = 100 + + OpWeightMsgUpdateParams = "op_weight_msg_update_params" // #nosec + OpWeightMsgRecoverClient = "op_weight_msg_recover_client" // #nosec + OpWeightMsgIBCSoftwareUpgrade = "op_weight_msg_schedule_ibc_software_upgrade" // #nosec +) + +// ProposalMsgs defines the module weighted proposals' contents +func ProposalMsgs() []simtypes.WeightedProposalMsg { + return []simtypes.WeightedProposalMsg{ + simulation.NewWeightedProposalMsg( + OpWeightMsgUpdateParams, + DefaultWeight, + SimulateClientMsgUpdateParams, + ), + simulation.NewWeightedProposalMsg( + OpWeightMsgUpdateParams, + DefaultWeight, + SimulateConnectionMsgUpdateParams, + ), + simulation.NewWeightedProposalMsg( + OpWeightMsgRecoverClient, + DefaultWeight, + SimulateClientMsgRecoverClient, + ), + simulation.NewWeightedProposalMsg( + OpWeightMsgIBCSoftwareUpgrade, + DefaultWeight, + SimulateClientMsgScheduleIBCSoftwareUpgrade, + ), + } +} + +// SimulateClientMsgUpdateParams returns a MsgUpdateParams for 02-client +func SimulateClientMsgUpdateParams(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) sdk.Msg { + var signer sdk.AccAddress = address.Module("gov") + params := types.DefaultParams() + params.AllowedClients = []string{"06-solomachine", "07-tendermint"} + + return &types.MsgUpdateParams{ + Signer: signer.String(), + Params: params, + } +} + +// SimulateClientMsgRecoverClient returns a MsgRecoverClient for 02-client +func SimulateClientMsgRecoverClient(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) sdk.Msg { + var signer sdk.AccAddress = address.Module("gov") + + return &types.MsgRecoverClient{ + Signer: signer.String(), + SubjectClientId: "07-tendermint-0", + SubstituteClientId: "07-tendermint-1", + } +} + +// SimulateClientMsgScheduleIBCSoftwareUpgrade returns a MsgScheduleIBCSoftwareUpgrade for 02-client +func SimulateClientMsgScheduleIBCSoftwareUpgrade(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) sdk.Msg { + var signer sdk.AccAddress = address.Module("gov") + + chainID := "chain-a-0" + ubdPeriod := time.Hour * 24 * 7 * 2 + upgradePath := []string{"upgrade", "upgradedIBCState"} + + upgradedClientState := &ibctm.ClientState{ + ChainId: chainID, + UnbondingPeriod: ubdPeriod, + ProofSpecs: commitmenttypes.GetSDKSpecs(), + UpgradePath: upgradePath, + } + anyClient, err := types.PackClientState(upgradedClientState) + if err != nil { + panic(err) + } + + return &types.MsgIBCSoftwareUpgrade{ + Signer: signer.String(), + Plan: upgradetypes.Plan{ + Name: "upgrade", + Height: 100, + }, + UpgradedClientState: anyClient, + } +} + +// SimulateConnectionMsgUpdateParams returns a MsgUpdateParams 03-connection +func SimulateConnectionMsgUpdateParams(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) sdk.Msg { + var signer sdk.AccAddress = address.Module("gov") + params := connectiontypes.DefaultParams() + params.MaxExpectedTimePerBlock = uint64(100) + + return &connectiontypes.MsgUpdateParams{ + Signer: signer.String(), + Params: params, + } +} diff --git a/modules/core/simulation/proposals_test.go b/modules/core/simulation/proposals_test.go new file mode 100644 index 0000000..fd4f442 --- /dev/null +++ b/modules/core/simulation/proposals_test.go @@ -0,0 +1,83 @@ +package simulation_test + +import ( + "math/rand" + "testing" + "time" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + "github.com/cosmos/ibc-go/v10/modules/core/simulation" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" +) + +func TestProposalMsgs(t *testing.T) { + // initialize parameters + s := rand.NewSource(1) + r := rand.New(s) + + ctx := sdk.NewContext(nil, cmtproto.Header{}, true, nil) + accounts := simtypes.RandomAccounts(r, 3) + + // execute ProposalMsgs function + weightedProposalMsgs := simulation.ProposalMsgs() + require.Equal(t, 4, len(weightedProposalMsgs)) + + // tests w0 interface: + w0 := weightedProposalMsgs[0] + require.Equal(t, simulation.OpWeightMsgUpdateParams, w0.AppParamsKey()) + require.Equal(t, simulation.DefaultWeight, w0.DefaultWeight()) + + msg := w0.MsgSimulatorFn()(r, ctx, accounts) + msgUpdateParams, ok := msg.(*clienttypes.MsgUpdateParams) + require.True(t, ok) + + require.Equal(t, sdk.AccAddress(address.Module("gov")).String(), msgUpdateParams.Signer) + require.EqualValues(t, []string{"06-solomachine", "07-tendermint"}, msgUpdateParams.Params.AllowedClients) + + // tests w1 interface: + w1 := weightedProposalMsgs[1] + require.Equal(t, simulation.OpWeightMsgUpdateParams, w1.AppParamsKey()) + require.Equal(t, simulation.DefaultWeight, w1.DefaultWeight()) + + msg1 := w1.MsgSimulatorFn()(r, ctx, accounts) + msgUpdateConnectionParams, ok := msg1.(*connectiontypes.MsgUpdateParams) + require.True(t, ok) + + require.Equal(t, sdk.AccAddress(address.Module("gov")).String(), msgUpdateParams.Signer) + require.EqualValues(t, uint64(100), msgUpdateConnectionParams.Params.MaxExpectedTimePerBlock) + + // tests w2 interface: + w2 := weightedProposalMsgs[2] + require.Equal(t, simulation.OpWeightMsgRecoverClient, w2.AppParamsKey()) + require.Equal(t, simulation.DefaultWeight, w2.DefaultWeight()) + + msg2 := w2.MsgSimulatorFn()(r, ctx, accounts) + msgRecoverClient, ok := msg2.(*clienttypes.MsgRecoverClient) + require.True(t, ok) + + require.Equal(t, sdk.AccAddress(address.Module("gov")).String(), msgRecoverClient.Signer) + require.EqualValues(t, "07-tendermint-1", msgRecoverClient.SubstituteClientId) + + // tests w3 interface: + w3 := weightedProposalMsgs[3] + require.Equal(t, simulation.OpWeightMsgIBCSoftwareUpgrade, w3.AppParamsKey()) + require.Equal(t, simulation.DefaultWeight, w3.DefaultWeight()) + + msg3 := w3.MsgSimulatorFn()(r, ctx, accounts) + msgIBCSoftwareUpgrade, ok := msg3.(*clienttypes.MsgIBCSoftwareUpgrade) + require.True(t, ok) + + require.Equal(t, sdk.AccAddress(address.Module("gov")).String(), msgIBCSoftwareUpgrade.Signer) + clientState, err := clienttypes.UnpackClientState(msgIBCSoftwareUpgrade.UpgradedClientState) + require.NoError(t, err) + require.EqualValues(t, time.Hour*24*7*2, clientState.(*ibctm.ClientState).UnbondingPeriod) +} diff --git a/modules/core/types/codec.go b/modules/core/types/codec.go new file mode 100644 index 0000000..6988607 --- /dev/null +++ b/modules/core/types/codec.go @@ -0,0 +1,23 @@ +package types + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + clientv2types "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" +) + +// RegisterInterfaces registers ibc types against interfaces using the global InterfaceRegistry. +// Note: The localhost client is created by ibc core and thus requires explicit type registration. +func RegisterInterfaces(registry codectypes.InterfaceRegistry) { + clienttypes.RegisterInterfaces(registry) + connectiontypes.RegisterInterfaces(registry) + channeltypes.RegisterInterfaces(registry) + commitmenttypes.RegisterInterfaces(registry) + clientv2types.RegisterInterfaces(registry) + channeltypesv2.RegisterInterfaces(registry) +} diff --git a/modules/core/types/events.go b/modules/core/types/events.go new file mode 100644 index 0000000..cec1cba --- /dev/null +++ b/modules/core/types/events.go @@ -0,0 +1,3 @@ +package types + +const ErrorAttributeKeyPrefix = "ibccallbackerror-" diff --git a/modules/core/types/expected_interfaces.go b/modules/core/types/expected_interfaces.go new file mode 100644 index 0000000..5c1e3ba --- /dev/null +++ b/modules/core/types/expected_interfaces.go @@ -0,0 +1,11 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" +) + +// ParamSubspace defines the expected Subspace interface for module parameters. +type ParamSubspace interface { + GetParamSet(ctx sdk.Context, ps paramtypes.ParamSet) +} diff --git a/modules/core/types/genesis.go b/modules/core/types/genesis.go new file mode 100644 index 0000000..503e7e6 --- /dev/null +++ b/modules/core/types/genesis.go @@ -0,0 +1,51 @@ +package types + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + clientv2types "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + channelv2types "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" +) + +var _ codectypes.UnpackInterfacesMessage = (*GenesisState)(nil) + +// DefaultGenesisState returns the ibc module's default genesis state. +func DefaultGenesisState() *GenesisState { + return &GenesisState{ + ClientGenesis: clienttypes.DefaultGenesisState(), + ClientV2Genesis: clientv2types.DefaultGenesisState(), + ConnectionGenesis: connectiontypes.DefaultGenesisState(), + ChannelGenesis: channeltypes.DefaultGenesisState(), + ChannelV2Genesis: channelv2types.DefaultGenesisState(), + } +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (gs GenesisState) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + return gs.ClientGenesis.UnpackInterfaces(unpacker) +} + +// Validate performs basic genesis state validation returning an error upon any +// failure. +func (gs *GenesisState) Validate() error { + if err := gs.ClientGenesis.Validate(); err != nil { + return err + } + + if err := gs.ClientV2Genesis.Validate(); err != nil { + return err + } + + if err := gs.ConnectionGenesis.Validate(); err != nil { + return err + } + + if err := gs.ChannelGenesis.Validate(); err != nil { + return err + } + + return gs.ChannelV2Genesis.Validate() +} diff --git a/modules/core/types/genesis.pb.go b/modules/core/types/genesis.pb.go new file mode 100644 index 0000000..f3c92ab --- /dev/null +++ b/modules/core/types/genesis.pb.go @@ -0,0 +1,552 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/core/types/v1/genesis.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + types "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + types3 "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" + types1 "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + types2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + types4 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// GenesisState defines the ibc module's genesis state. +type GenesisState struct { + // ICS002 - Clients genesis state + ClientGenesis types.GenesisState `protobuf:"bytes,1,opt,name=client_genesis,json=clientGenesis,proto3" json:"client_genesis"` + // ICS003 - Connections genesis state + ConnectionGenesis types1.GenesisState `protobuf:"bytes,2,opt,name=connection_genesis,json=connectionGenesis,proto3" json:"connection_genesis"` + // ICS004 - Channel genesis state + ChannelGenesis types2.GenesisState `protobuf:"bytes,3,opt,name=channel_genesis,json=channelGenesis,proto3" json:"channel_genesis"` + // ICS002 - Clients/v2 genesis state + ClientV2Genesis types3.GenesisState `protobuf:"bytes,4,opt,name=client_v2_genesis,json=clientV2Genesis,proto3" json:"client_v2_genesis"` + // ICS004 - Channel/v2 genesis state + ChannelV2Genesis types4.GenesisState `protobuf:"bytes,5,opt,name=channel_v2_genesis,json=channelV2Genesis,proto3" json:"channel_v2_genesis"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_b9a49c5663e6fc59, []int{0} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetClientGenesis() types.GenesisState { + if m != nil { + return m.ClientGenesis + } + return types.GenesisState{} +} + +func (m *GenesisState) GetConnectionGenesis() types1.GenesisState { + if m != nil { + return m.ConnectionGenesis + } + return types1.GenesisState{} +} + +func (m *GenesisState) GetChannelGenesis() types2.GenesisState { + if m != nil { + return m.ChannelGenesis + } + return types2.GenesisState{} +} + +func (m *GenesisState) GetClientV2Genesis() types3.GenesisState { + if m != nil { + return m.ClientV2Genesis + } + return types3.GenesisState{} +} + +func (m *GenesisState) GetChannelV2Genesis() types4.GenesisState { + if m != nil { + return m.ChannelV2Genesis + } + return types4.GenesisState{} +} + +func init() { + proto.RegisterType((*GenesisState)(nil), "ibc.core.types.v1.GenesisState") +} + +func init() { proto.RegisterFile("ibc/core/types/v1/genesis.proto", fileDescriptor_b9a49c5663e6fc59) } + +var fileDescriptor_b9a49c5663e6fc59 = []byte{ + // 341 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xc1, 0x4e, 0xfa, 0x30, + 0x1c, 0xc7, 0xb7, 0x3f, 0xfc, 0x3d, 0x54, 0x05, 0x59, 0x3c, 0x18, 0x0e, 0x03, 0x0c, 0x07, 0x2f, + 0xb6, 0x6e, 0xbe, 0x01, 0x17, 0xe3, 0xc1, 0xc4, 0x60, 0x34, 0xd1, 0x8b, 0x61, 0xb5, 0x19, 0x4d, + 0xa0, 0x3f, 0x42, 0xcb, 0x12, 0xdf, 0xc2, 0x97, 0xf1, 0x1d, 0x38, 0x72, 0xf4, 0x64, 0x0c, 0xbc, + 0x88, 0x61, 0xed, 0xba, 0x05, 0xb6, 0xc4, 0xdb, 0xb2, 0xef, 0xa7, 0x9f, 0xfe, 0xbe, 0xcd, 0x0f, + 0x75, 0x78, 0x44, 0x09, 0x85, 0x39, 0x23, 0xea, 0x7d, 0xc6, 0x24, 0x49, 0x02, 0x12, 0x33, 0xc1, + 0x24, 0x97, 0x78, 0x36, 0x07, 0x05, 0x5e, 0x8b, 0x47, 0x14, 0x6f, 0x01, 0x9c, 0x02, 0x38, 0x09, + 0xda, 0xa7, 0x31, 0xc4, 0x90, 0xa6, 0x64, 0xfb, 0xa5, 0xc1, 0x76, 0xd7, 0x9a, 0xe8, 0x84, 0x33, + 0xa1, 0xf6, 0x54, 0x25, 0x44, 0xb8, 0x43, 0xf4, 0x73, 0x02, 0x84, 0x60, 0x54, 0x71, 0x10, 0xfb, + 0x9e, 0x5e, 0x4e, 0x8d, 0x47, 0x42, 0xb0, 0xc9, 0x9f, 0x90, 0x9d, 0xbb, 0xce, 0x3f, 0x6b, 0xe8, + 0xe8, 0x46, 0xff, 0x79, 0x50, 0x23, 0xc5, 0xbc, 0x3b, 0xd4, 0xd0, 0x73, 0xbd, 0x1a, 0xf0, 0xcc, + 0xed, 0xba, 0x17, 0x87, 0x61, 0x17, 0xdb, 0x27, 0xd0, 0x39, 0x4e, 0x02, 0x5c, 0x3c, 0x39, 0xa8, + 0x2f, 0xbf, 0x3b, 0xce, 0xf0, 0x58, 0xa7, 0x26, 0xf1, 0x9e, 0x91, 0x97, 0x97, 0xb0, 0xca, 0x7f, + 0xa9, 0xb2, 0x5f, 0x50, 0x5a, 0xa6, 0x42, 0xdb, 0xca, 0x89, 0x4c, 0x7d, 0x8f, 0x9a, 0xa6, 0x96, + 0xf5, 0xd6, 0x52, 0x6f, 0xaf, 0xe0, 0xd5, 0x40, 0x85, 0xb4, 0x61, 0xe2, 0xcc, 0x38, 0x44, 0x2d, + 0xd3, 0x3d, 0x09, 0xad, 0xb3, 0x5e, 0x55, 0x3f, 0x2c, 0x53, 0x36, 0x75, 0xfa, 0x14, 0x66, 0xce, + 0x47, 0xe4, 0x65, 0x53, 0x16, 0xa4, 0xff, 0x2b, 0x07, 0x2d, 0xb5, 0x9e, 0x98, 0xd8, 0x6a, 0x07, + 0xb7, 0xcb, 0xb5, 0xef, 0xae, 0xd6, 0xbe, 0xfb, 0xb3, 0xf6, 0xdd, 0x8f, 0x8d, 0xef, 0xac, 0x36, + 0xbe, 0xf3, 0xb5, 0xf1, 0x9d, 0x17, 0x12, 0x73, 0x35, 0x5e, 0x44, 0x98, 0xc2, 0x94, 0x50, 0x90, + 0x53, 0x90, 0x84, 0x47, 0xf4, 0x32, 0x06, 0x92, 0x04, 0x57, 0x64, 0x0a, 0x6f, 0x8b, 0x09, 0x93, + 0x85, 0x65, 0x8f, 0x0e, 0xd2, 0x4d, 0xb8, 0xfe, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x80, 0x8d, 0xfa, + 0x2d, 0x05, 0x03, 0x00, 0x00, +} + +func (m *GenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.ChannelV2Genesis.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + { + size, err := m.ClientV2Genesis.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + { + size, err := m.ChannelGenesis.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + { + size, err := m.ConnectionGenesis.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.ClientGenesis.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.ClientGenesis.Size() + n += 1 + l + sovGenesis(uint64(l)) + l = m.ConnectionGenesis.Size() + n += 1 + l + sovGenesis(uint64(l)) + l = m.ChannelGenesis.Size() + n += 1 + l + sovGenesis(uint64(l)) + l = m.ClientV2Genesis.Size() + n += 1 + l + sovGenesis(uint64(l)) + l = m.ChannelV2Genesis.Size() + n += 1 + l + sovGenesis(uint64(l)) + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientGenesis", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ClientGenesis.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectionGenesis", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ConnectionGenesis.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelGenesis", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ChannelGenesis.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientV2Genesis", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ClientV2Genesis.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChannelV2Genesis", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ChannelV2Genesis.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/light-clients/06-solomachine/client_state.go b/modules/light-clients/06-solomachine/client_state.go new file mode 100644 index 0000000..6c2d8b3 --- /dev/null +++ b/modules/light-clients/06-solomachine/client_state.go @@ -0,0 +1,194 @@ +package solomachine + +import ( + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypesv2 "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types/v2" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var _ exported.ClientState = (*ClientState)(nil) + +// NewClientState creates a new ClientState instance. +func NewClientState(latestSequence uint64, consensusState *ConsensusState) *ClientState { + return &ClientState{ + Sequence: latestSequence, + IsFrozen: false, + ConsensusState: consensusState, + } +} + +// ClientType is Solo Machine. +func (ClientState) ClientType() string { + return exported.Solomachine +} + +// Validate performs basic validation of the client state fields. +func (cs ClientState) Validate() error { + if cs.Sequence == 0 { + return errorsmod.Wrap(clienttypes.ErrInvalidClient, "sequence cannot be 0") + } + if cs.ConsensusState == nil { + return errorsmod.Wrap(clienttypes.ErrInvalidConsensus, "consensus state cannot be nil") + } + return cs.ConsensusState.ValidateBasic() +} + +// verifyMembership is a generic proof verification method which verifies a proof of the existence of a value at a given CommitmentPath at the latest sequence. +// The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). +func (cs *ClientState) verifyMembership( + clientStore storetypes.KVStore, + cdc codec.BinaryCodec, + proof []byte, + path exported.Path, + value []byte, +) error { + publicKey, sigData, timestamp, sequence, err := produceVerificationArgs(cdc, cs, proof) + if err != nil { + return err + } + + merklePath, ok := path.(commitmenttypesv2.MerklePath) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", commitmenttypesv2.MerklePath{}, path) + } + + if len(merklePath.GetKeyPath()) != 2 { + return errorsmod.Wrapf(host.ErrInvalidPath, "path must be of length 2: %s", merklePath.GetKeyPath()) + } + + // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + key, err := merklePath.GetKey(1) + if err != nil { + return errorsmod.Wrapf(host.ErrInvalidPath, "key not found at index 1: %v", err) + } + + signBytes := &SignBytes{ + Sequence: sequence, + Timestamp: timestamp, + Diversifier: cs.ConsensusState.Diversifier, + Path: key, + Data: value, + } + + signBz, err := cdc.Marshal(signBytes) + if err != nil { + return err + } + + if err := VerifySignature(publicKey, signBz, sigData); err != nil { + return err + } + + cs.Sequence++ + cs.ConsensusState.Timestamp = timestamp + setClientState(clientStore, cdc, cs) + + return nil +} + +// verifyNonMembership is a generic proof verification method which verifies the absence of a given CommitmentPath at the latest sequence. +// The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). +func (cs *ClientState) verifyNonMembership( + clientStore storetypes.KVStore, + cdc codec.BinaryCodec, + proof []byte, + path exported.Path, +) error { + publicKey, sigData, timestamp, sequence, err := produceVerificationArgs(cdc, cs, proof) + if err != nil { + return err + } + + merklePath, ok := path.(commitmenttypesv2.MerklePath) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", commitmenttypesv2.MerklePath{}, path) + } + + if len(merklePath.GetKeyPath()) != 2 { + return errorsmod.Wrapf(host.ErrInvalidPath, "path must be of length 2: %s", merklePath.GetKeyPath()) + } + + // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + key, err := merklePath.GetKey(1) + if err != nil { + return errorsmod.Wrapf(host.ErrInvalidPath, "key not found at index 1: %v", err) + } + + signBytes := &SignBytes{ + Sequence: sequence, + Timestamp: timestamp, + Diversifier: cs.ConsensusState.Diversifier, + Path: key, + Data: nil, + } + + signBz, err := cdc.Marshal(signBytes) + if err != nil { + return err + } + + if err := VerifySignature(publicKey, signBz, sigData); err != nil { + return err + } + + cs.Sequence++ + cs.ConsensusState.Timestamp = timestamp + setClientState(clientStore, cdc, cs) + + return nil +} + +// produceVerificationArgs performs the basic checks on the arguments that are +// shared between the verification functions and returns the public key of the +// consensus state, the unmarshalled proof representing the signature and timestamp. +func produceVerificationArgs( + cdc codec.BinaryCodec, + cs *ClientState, + proof []byte, +) (cryptotypes.PubKey, signing.SignatureData, uint64, uint64, error) { + if proof == nil { + return nil, nil, 0, 0, errorsmod.Wrap(ErrInvalidProof, "proof cannot be empty") + } + + var timestampedSigData TimestampedSignatureData + if err := cdc.Unmarshal(proof, ×tampedSigData); err != nil { + return nil, nil, 0, 0, errorsmod.Wrapf(err, "failed to unmarshal proof into type %T", timestampedSigData) + } + + timestamp := timestampedSigData.Timestamp + if len(timestampedSigData.SignatureData) == 0 { + return nil, nil, 0, 0, errorsmod.Wrap(ErrInvalidProof, "signature data cannot be empty") + } + + sigData, err := UnmarshalSignatureData(cdc, timestampedSigData.SignatureData) + if err != nil { + return nil, nil, 0, 0, err + } + + if cs.ConsensusState.GetTimestamp() > timestamp { + return nil, nil, 0, 0, errorsmod.Wrapf(ErrInvalidProof, "the consensus state timestamp is greater than the signature timestamp (%d >= %d)", cs.ConsensusState.GetTimestamp(), timestamp) + } + + sequence := cs.Sequence + publicKey, err := cs.ConsensusState.GetPubKey() + if err != nil { + return nil, nil, 0, 0, err + } + + return publicKey, sigData, timestamp, sequence, nil +} + +// sets the client state to the store +func setClientState(store storetypes.KVStore, cdc codec.BinaryCodec, clientState exported.ClientState) { + bz := clienttypes.MustMarshalClientState(cdc, clientState) + store.Set(host.ClientStateKey(), bz) +} diff --git a/modules/light-clients/06-solomachine/client_state_test.go b/modules/light-clients/06-solomachine/client_state_test.go new file mode 100644 index 0000000..c7965cc --- /dev/null +++ b/modules/light-clients/06-solomachine/client_state_test.go @@ -0,0 +1,100 @@ +package solomachine_test + +import ( + "bytes" + "errors" + + solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +const ( + counterpartyClientIdentifier = "chainA" + testConnectionID = "connectionid" + testChannelID = "testchannelid" + testPortID = "testportid" +) + +func (suite *SoloMachineTestSuite) TestClientStateValidate() { + // test singlesig and multisig public keys + for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { + + testCases := []struct { + name string + clientState *solomachine.ClientState + expErr error + }{ + { + "valid client state", + sm.ClientState(), + nil, + }, + { + "empty ClientState", + &solomachine.ClientState{}, + errors.New("sequence cannot be 0: light client is invalid"), + }, + { + "sequence is zero", + solomachine.NewClientState(0, &solomachine.ConsensusState{sm.ConsensusState().PublicKey, sm.Diversifier, sm.Time}), + errors.New("sequence cannot be 0: light client is invalid"), + }, + { + "timestamp is zero", + solomachine.NewClientState(1, &solomachine.ConsensusState{sm.ConsensusState().PublicKey, sm.Diversifier, 0}), + errors.New("timestamp cannot be 0: invalid consensus state"), + }, + { + "diversifier is blank", + solomachine.NewClientState(1, &solomachine.ConsensusState{sm.ConsensusState().PublicKey, " ", 1}), + errors.New("diversifier cannot contain only spaces: invalid consensus state"), + }, + { + "pubkey is empty", + solomachine.NewClientState(1, &solomachine.ConsensusState{nil, sm.Diversifier, sm.Time}), + errors.New("public key cannot be empty: invalid consensus state"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := tc.clientState.Validate() + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } + } +} + +func (suite *SoloMachineTestSuite) TestSignBytesMarshalling() { + sm := suite.solomachine + path := []byte("solomachine") + signBytesNilData := solomachine.SignBytes{ + Sequence: sm.GetHeight().GetRevisionHeight(), + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: path, + Data: nil, + } + + signBytesEmptyArray := solomachine.SignBytes{ + Sequence: sm.GetHeight().GetRevisionHeight(), + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: path, + Data: []byte{}, + } + + signBzNil, err := suite.chainA.Codec.Marshal(&signBytesNilData) + suite.Require().NoError(err) + + signBzEmptyArray, err := suite.chainA.Codec.Marshal(&signBytesEmptyArray) + suite.Require().NoError(err) + + suite.Require().True(bytes.Equal(signBzNil, signBzEmptyArray)) +} diff --git a/modules/light-clients/06-solomachine/codec.go b/modules/light-clients/06-solomachine/codec.go new file mode 100644 index 0000000..32f3d2d --- /dev/null +++ b/modules/light-clients/06-solomachine/codec.go @@ -0,0 +1,43 @@ +package solomachine + +import ( + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// RegisterInterfaces register the ibc channel submodule interfaces to protobuf +// Any. +func RegisterInterfaces(registry codectypes.InterfaceRegistry) { + registry.RegisterImplementations( + (*exported.ClientState)(nil), + &ClientState{}, + ) + registry.RegisterImplementations( + (*exported.ConsensusState)(nil), + &ConsensusState{}, + ) + registry.RegisterImplementations( + (*exported.ClientMessage)(nil), + &Header{}, + ) + registry.RegisterImplementations( + (*exported.ClientMessage)(nil), + &Misbehaviour{}, + ) +} + +func UnmarshalSignatureData(cdc codec.BinaryCodec, data []byte) (signing.SignatureData, error) { + protoSigData := &signing.SignatureDescriptor_Data{} + if err := cdc.Unmarshal(data, protoSigData); err != nil { + return nil, errorsmod.Wrapf(err, "failed to unmarshal proof into type %T", protoSigData) + } + + sigData := signing.SignatureDataFromProto(protoSigData) + + return sigData, nil +} diff --git a/modules/light-clients/06-solomachine/codec_test.go b/modules/light-clients/06-solomachine/codec_test.go new file mode 100644 index 0000000..a8f38bd --- /dev/null +++ b/modules/light-clients/06-solomachine/codec_test.go @@ -0,0 +1,62 @@ +package solomachine_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + + solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine" +) + +func TestCodecTypeRegistration(t *testing.T) { + testCases := []struct { + name string + typeURL string + expError error + }{ + { + "success: ClientState", + sdk.MsgTypeURL(&solomachine.ClientState{}), + nil, + }, + { + "success: ConsensusState", + sdk.MsgTypeURL(&solomachine.ConsensusState{}), + nil, + }, + { + "success: Header", + sdk.MsgTypeURL(&solomachine.Header{}), + nil, + }, + { + "success: Misbehaviour", + sdk.MsgTypeURL(&solomachine.Misbehaviour{}), + nil, + }, + { + "type not registered on codec", + "ibc.invalid.MsgTypeURL", + errors.New("unable to resolve type URL ibc.invalid.MsgTypeURL"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + encodingCfg := moduletestutil.MakeTestEncodingConfig(solomachine.AppModuleBasic{}) + msg, err := encodingCfg.Codec.InterfaceRegistry().Resolve(tc.typeURL) + + if tc.expError == nil { + require.NotNil(t, msg) + require.NoError(t, err) + } else { + require.Nil(t, msg) + require.ErrorContains(t, err, tc.expError.Error()) + } + }) + } +} diff --git a/modules/light-clients/06-solomachine/consensus_state.go b/modules/light-clients/06-solomachine/consensus_state.go new file mode 100644 index 0000000..9f770d6 --- /dev/null +++ b/modules/light-clients/06-solomachine/consensus_state.go @@ -0,0 +1,57 @@ +package solomachine + +import ( + "strings" + + errorsmod "cosmossdk.io/errors" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var _ exported.ConsensusState = (*ConsensusState)(nil) + +// ClientType returns Solo Machine type. +func (ConsensusState) ClientType() string { + return exported.Solomachine +} + +// GetTimestamp returns zero. +func (cs ConsensusState) GetTimestamp() uint64 { + return cs.Timestamp +} + +// GetPubKey unmarshals the public key into a cryptotypes.PubKey type. +// An error is returned if the public key is nil or the cached value +// is not a PubKey. +func (cs ConsensusState) GetPubKey() (cryptotypes.PubKey, error) { + if cs.PublicKey == nil { + return nil, errorsmod.Wrap(clienttypes.ErrInvalidConsensus, "consensus state PublicKey cannot be nil") + } + + publicKey, ok := cs.PublicKey.GetCachedValue().(cryptotypes.PubKey) + if !ok { + return nil, errorsmod.Wrap(clienttypes.ErrInvalidConsensus, "consensus state PublicKey is not cryptotypes.PubKey") + } + + return publicKey, nil +} + +// ValidateBasic defines basic validation for the solo machine consensus state. +func (cs ConsensusState) ValidateBasic() error { + if cs.Timestamp == 0 { + return errorsmod.Wrap(clienttypes.ErrInvalidConsensus, "timestamp cannot be 0") + } + if cs.Diversifier != "" && strings.TrimSpace(cs.Diversifier) == "" { + return errorsmod.Wrap(clienttypes.ErrInvalidConsensus, "diversifier cannot contain only spaces") + } + + publicKey, err := cs.GetPubKey() + if err != nil || publicKey == nil || len(publicKey.Bytes()) == 0 { + return errorsmod.Wrap(clienttypes.ErrInvalidConsensus, "public key cannot be empty") + } + + return nil +} diff --git a/modules/light-clients/06-solomachine/consensus_state_test.go b/modules/light-clients/06-solomachine/consensus_state_test.go new file mode 100644 index 0000000..cef7b00 --- /dev/null +++ b/modules/light-clients/06-solomachine/consensus_state_test.go @@ -0,0 +1,74 @@ +package solomachine_test + +import ( + "errors" + + "github.com/cosmos/ibc-go/v10/modules/core/exported" + solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *SoloMachineTestSuite) TestConsensusState() { + consensusState := suite.solomachine.ConsensusState() + + suite.Require().Equal(exported.Solomachine, consensusState.ClientType()) + suite.Require().Equal(suite.solomachine.Time, consensusState.GetTimestamp()) +} + +func (suite *SoloMachineTestSuite) TestConsensusStateValidateBasic() { + // test singlesig and multisig public keys + for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { + + testCases := []struct { + name string + consensusState *solomachine.ConsensusState + expErr error + }{ + { + "valid consensus state", + sm.ConsensusState(), + nil, + }, + { + "timestamp is zero", + &solomachine.ConsensusState{ + PublicKey: sm.ConsensusState().PublicKey, + Timestamp: 0, + Diversifier: sm.Diversifier, + }, + errors.New("timestamp cannot be 0: invalid consensus state"), + }, + { + "diversifier is blank", + &solomachine.ConsensusState{ + PublicKey: sm.ConsensusState().PublicKey, + Timestamp: sm.Time, + Diversifier: " ", + }, + errors.New("diversifier cannot contain only spaces: invalid consensus state"), + }, + { + "pubkey is nil", + &solomachine.ConsensusState{ + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + PublicKey: nil, + }, + errors.New("public key cannot be empty: invalid consensus state"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := tc.consensusState.ValidateBasic() + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } + } +} diff --git a/modules/light-clients/06-solomachine/doc.go b/modules/light-clients/06-solomachine/doc.go new file mode 100644 index 0000000..fb75279 --- /dev/null +++ b/modules/light-clients/06-solomachine/doc.go @@ -0,0 +1,10 @@ +/* +Package solomachine implements a concrete LightClientModule, ClientState, ConsensusState, +Header and Misbehaviour types for the Solo Machine light client. +This implementation is based off the ICS 06 specification +(https://github.com/cosmos/ibc/tree/master/spec/client/ics-006-solo-machine-client) + +Note that client identifiers are expected to be in the form: 06-solomachine-{N}. +Client identifiers are generated and validated by core IBC, unexpected client identifiers will result in errors. +*/ +package solomachine diff --git a/modules/light-clients/06-solomachine/errors.go b/modules/light-clients/06-solomachine/errors.go new file mode 100644 index 0000000..a5523cb --- /dev/null +++ b/modules/light-clients/06-solomachine/errors.go @@ -0,0 +1,13 @@ +package solomachine + +import ( + errorsmod "cosmossdk.io/errors" +) + +var ( + ErrInvalidHeader = errorsmod.Register(ModuleName, 2, "invalid header") + ErrInvalidSequence = errorsmod.Register(ModuleName, 3, "invalid sequence") + ErrInvalidSignatureAndData = errorsmod.Register(ModuleName, 4, "invalid signature and data") + ErrSignatureVerificationFailed = errorsmod.Register(ModuleName, 5, "signature verification failed") + ErrInvalidProof = errorsmod.Register(ModuleName, 6, "invalid solo machine proof") +) diff --git a/modules/light-clients/06-solomachine/header.go b/modules/light-clients/06-solomachine/header.go new file mode 100644 index 0000000..8cf3db8 --- /dev/null +++ b/modules/light-clients/06-solomachine/header.go @@ -0,0 +1,61 @@ +package solomachine + +import ( + "strings" + + errorsmod "cosmossdk.io/errors" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// SentinelHeaderPath defines a placeholder path value used for headers in solomachine client updates +const SentinelHeaderPath = "solomachine:header" + +var _ exported.ClientMessage = (*Header)(nil) + +// ClientType defines that the Header is a Solo Machine. +func (Header) ClientType() string { + return exported.Solomachine +} + +// GetPubKey unmarshals the new public key into a cryptotypes.PubKey type. +// An error is returned if the new public key is nil or the cached value +// is not a PubKey. +func (h Header) GetPubKey() (cryptotypes.PubKey, error) { + if h.NewPublicKey == nil { + return nil, errorsmod.Wrap(ErrInvalidHeader, "header NewPublicKey cannot be nil") + } + + publicKey, ok := h.NewPublicKey.GetCachedValue().(cryptotypes.PubKey) + if !ok { + return nil, errorsmod.Wrap(ErrInvalidHeader, "header NewPublicKey is not cryptotypes.PubKey") + } + + return publicKey, nil +} + +// ValidateBasic ensures that the timestamp, signature and public key have all +// been initialized. +func (h Header) ValidateBasic() error { + if h.Timestamp == 0 { + return errorsmod.Wrap(clienttypes.ErrInvalidHeader, "timestamp cannot be zero") + } + + if h.NewDiversifier != "" && strings.TrimSpace(h.NewDiversifier) == "" { + return errorsmod.Wrap(clienttypes.ErrInvalidHeader, "diversifier cannot contain only spaces") + } + + if len(h.Signature) == 0 { + return errorsmod.Wrap(clienttypes.ErrInvalidHeader, "signature cannot be empty") + } + + newPublicKey, err := h.GetPubKey() + if err != nil || newPublicKey == nil || len(newPublicKey.Bytes()) == 0 { + return errorsmod.Wrap(clienttypes.ErrInvalidHeader, "new public key cannot be empty") + } + + return nil +} diff --git a/modules/light-clients/06-solomachine/header_test.go b/modules/light-clients/06-solomachine/header_test.go new file mode 100644 index 0000000..3f82eb5 --- /dev/null +++ b/modules/light-clients/06-solomachine/header_test.go @@ -0,0 +1,84 @@ +package solomachine_test + +import ( + "errors" + + "github.com/cosmos/ibc-go/v10/modules/core/exported" + solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *SoloMachineTestSuite) TestHeaderValidateBasic() { + // test singlesig and multisig public keys + for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { + + header := sm.CreateHeader(sm.Diversifier) + + cases := []struct { + name string + header *solomachine.Header + expErr error + }{ + { + "valid header", + header, + nil, + }, + { + "timestamp is zero", + &solomachine.Header{ + Timestamp: 0, + Signature: header.Signature, + NewPublicKey: header.NewPublicKey, + NewDiversifier: header.NewDiversifier, + }, + errors.New("timestamp cannot be zero: invalid client header"), + }, + { + "signature is empty", + &solomachine.Header{ + Timestamp: header.Timestamp, + Signature: []byte{}, + NewPublicKey: header.NewPublicKey, + NewDiversifier: header.NewDiversifier, + }, + errors.New("signature cannot be empty: invalid client header"), + }, + { + "diversifier contains only spaces", + &solomachine.Header{ + Timestamp: header.Timestamp, + Signature: header.Signature, + NewPublicKey: header.NewPublicKey, + NewDiversifier: " ", + }, + errors.New("diversifier cannot contain only spaces: invalid client header"), + }, + { + "public key is nil", + &solomachine.Header{ + Timestamp: header.Timestamp, + Signature: header.Signature, + NewPublicKey: nil, + NewDiversifier: header.NewDiversifier, + }, + errors.New("new public key cannot be empty: invalid client header"), + }, + } + + suite.Require().Equal(exported.Solomachine, header.ClientType()) + + for _, tc := range cases { + suite.Run(tc.name, func() { + err := tc.header.ValidateBasic() + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } + } +} diff --git a/modules/light-clients/06-solomachine/keys.go b/modules/light-clients/06-solomachine/keys.go new file mode 100644 index 0000000..0b31721 --- /dev/null +++ b/modules/light-clients/06-solomachine/keys.go @@ -0,0 +1,5 @@ +package solomachine + +const ( + ModuleName = "06-solomachine" +) diff --git a/modules/light-clients/06-solomachine/light_client_module.go b/modules/light-clients/06-solomachine/light_client_module.go new file mode 100644 index 0000000..946f595 --- /dev/null +++ b/modules/light-clients/06-solomachine/light_client_module.go @@ -0,0 +1,224 @@ +package solomachine + +import ( + "reflect" + + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var _ exported.LightClientModule = (*LightClientModule)(nil) + +// LightClientModule implements the core IBC api.LightClientModule interface +type LightClientModule struct { + cdc codec.BinaryCodec + storeProvider clienttypes.StoreProvider +} + +// NewLightClientModule creates and returns a new 06-solomachine LightClientModule. +func NewLightClientModule(cdc codec.BinaryCodec, storeProvider clienttypes.StoreProvider) LightClientModule { + return LightClientModule{ + cdc: cdc, + storeProvider: storeProvider, + } +} + +// Initialize unmarshals the provided client and consensus states and performs basic validation. It calls into the +// clientState.Initialize method. +func (l LightClientModule) Initialize(ctx sdk.Context, clientID string, clientStateBz, consensusStateBz []byte) error { + var clientState ClientState + if err := l.cdc.Unmarshal(clientStateBz, &clientState); err != nil { + return err + } + + if err := clientState.Validate(); err != nil { + return err + } + + var consensusState ConsensusState + if err := l.cdc.Unmarshal(consensusStateBz, &consensusState); err != nil { + return err + } + + if err := consensusState.ValidateBasic(); err != nil { + return err + } + + clientStore := l.storeProvider.ClientStore(ctx, clientID) + + if !reflect.DeepEqual(clientState.ConsensusState, &consensusState) { + return errorsmod.Wrapf(clienttypes.ErrInvalidConsensus, "consensus state in initial client does not equal initial consensus state. expected: %s, got: %s", + clientState.ConsensusState, &consensusState) + } + + setClientState(clientStore, l.cdc, &clientState) + + return nil +} + +// VerifyClientMessage obtains the client state associated with the client identifier and calls into the clientState.VerifyClientMessage method. +func (l LightClientModule) VerifyClientMessage(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) error { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + clientState, found := getClientState(clientStore, l.cdc) + if !found { + return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) + } + + return clientState.VerifyClientMessage(ctx, l.cdc, clientStore, clientMsg) +} + +// CheckForMisbehaviour obtains the client state associated with the client identifier and calls into the clientState.CheckForMisbehaviour method. +func (l LightClientModule) CheckForMisbehaviour(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) bool { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + clientState, found := getClientState(clientStore, l.cdc) + if !found { + panic(errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID)) + } + + return clientState.CheckForMisbehaviour(ctx, l.cdc, clientStore, clientMsg) +} + +// UpdateStateOnMisbehaviour updates state upon misbehaviour, freezing the ClientState. +// This method should only be called when misbehaviour is detected as it does not perform +// any misbehaviour checks. +func (l LightClientModule) UpdateStateOnMisbehaviour(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + clientState, found := getClientState(clientStore, l.cdc) + if !found { + panic(errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID)) + } + + clientState.IsFrozen = true + setClientState(clientStore, l.cdc, clientState) +} + +// UpdateState obtains the client state associated with the client identifier and calls into the clientState.UpdateState method. +func (l LightClientModule) UpdateState(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) []exported.Height { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + clientState, found := getClientState(clientStore, l.cdc) + if !found { + panic(errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID)) + } + + return clientState.UpdateState(ctx, l.cdc, clientStore, clientMsg) +} + +// VerifyMembership obtains the client state associated with the client identifier and calls into the clientState.verifyMembership method. +func (l LightClientModule) VerifyMembership( + ctx sdk.Context, + clientID string, + height exported.Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path exported.Path, + value []byte, +) error { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + clientState, found := getClientState(clientStore, l.cdc) + if !found { + return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) + } + + return clientState.verifyMembership(clientStore, l.cdc, proof, path, value) +} + +// VerifyNonMembership obtains the client state associated with the client identifier and calls into the clientState.verifyNonMembership method. +func (l LightClientModule) VerifyNonMembership( + ctx sdk.Context, + clientID string, + height exported.Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path exported.Path, +) error { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + clientState, found := getClientState(clientStore, l.cdc) + if !found { + return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) + } + + return clientState.verifyNonMembership(clientStore, l.cdc, proof, path) +} + +// Status returns the status of the solo machine client. +// The client may be: +// - Active: if `IsFrozen` is false. +// - Frozen: if `IsFrozen` is true. +// - Unknown: if the client state associated with the provided client identifier is not found. +func (l LightClientModule) Status(ctx sdk.Context, clientID string) exported.Status { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + clientState, found := getClientState(clientStore, l.cdc) + if !found { + return exported.Unknown + } + + if clientState.IsFrozen { + return exported.Frozen + } + + return exported.Active +} + +// LatestHeight returns the latest height for the client state for the given client identifier. +// If no client is present for the provided client identifier a zero value height is returned. +// NOTE: RevisionNumber is always 0 for solomachine client heights. +func (l LightClientModule) LatestHeight(ctx sdk.Context, clientID string) exported.Height { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + + clientState, found := getClientState(clientStore, l.cdc) + if !found { + return clienttypes.ZeroHeight() + } + + return clienttypes.NewHeight(0, clientState.Sequence) +} + +// TimestampAtHeight obtains the client state associated with the client identifier and returns the timestamp in nanoseconds of the consensus state at the given height. +func (l LightClientModule) TimestampAtHeight(ctx sdk.Context, clientID string, height exported.Height) (uint64, error) { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + clientState, found := getClientState(clientStore, l.cdc) + if !found { + return 0, errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) + } + + return clientState.ConsensusState.Timestamp, nil +} + +// RecoverClient asserts that the substitute client is a solo machine client. It obtains the client state associated with the +// subject client and calls into the subjectClientState.CheckSubstituteAndUpdateState method. +func (l LightClientModule) RecoverClient(ctx sdk.Context, clientID, substituteClientID string) error { + substituteClientType, _, err := clienttypes.ParseClientIdentifier(substituteClientID) + if err != nil { + return err + } + + if substituteClientType != exported.Solomachine { + return errorsmod.Wrapf(clienttypes.ErrInvalidClientType, "expected: %s, got: %s", exported.Solomachine, substituteClientType) + } + + clientStore := l.storeProvider.ClientStore(ctx, clientID) + clientState, found := getClientState(clientStore, l.cdc) + if !found { + return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) + } + + substituteClientStore := l.storeProvider.ClientStore(ctx, substituteClientID) + substituteClient, found := getClientState(substituteClientStore, l.cdc) + if !found { + return errorsmod.Wrap(clienttypes.ErrClientNotFound, substituteClientID) + } + + return clientState.CheckSubstituteAndUpdateState(ctx, l.cdc, clientStore, substituteClientStore, substituteClient) +} + +// VerifyUpgradeAndUpdateState returns an error since solomachine client does not support upgrades +func (LightClientModule) VerifyUpgradeAndUpdateState(ctx sdk.Context, clientID string, newClient, newConsState, upgradeClientProof, upgradeConsensusStateProof []byte) error { + return errorsmod.Wrap(clienttypes.ErrInvalidUpgradeClient, "cannot upgrade solomachine client") +} diff --git a/modules/light-clients/06-solomachine/light_client_module_test.go b/modules/light-clients/06-solomachine/light_client_module_test.go new file mode 100644 index 0000000..887c27c --- /dev/null +++ b/modules/light-clients/06-solomachine/light_client_module_test.go @@ -0,0 +1,1687 @@ +package solomachine_test + +import ( + "errors" + "fmt" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + commitmenttypesv2 "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types/v2" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + ibcmock "github.com/cosmos/ibc-go/v10/testing/mock" +) + +const ( + unusedSmClientID = "06-solomachine-999" + wasmClientID = "08-wasm-0" +) + +func (suite *SoloMachineTestSuite) TestStatus() { + var ( + clientState *solomachine.ClientState + clientID string + ) + + testCases := []struct { + name string + malleate func() + expStatus exported.Status + }{ + { + "client is active", + func() {}, + exported.Active, + }, + { + "client is frozen", + func() { + clientState = solomachine.NewClientState(0, &solomachine.ConsensusState{}) + clientState.IsFrozen = true + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + }, + exported.Frozen, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedSmClientID + }, + exported.Unknown, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + clientID = suite.solomachine.ClientID + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, suite.solomachine.ClientState()) + + tc.malleate() + + status := lightClientModule.Status(suite.chainA.GetContext(), clientID) + suite.Require().Equal(tc.expStatus, status) + }) + } +} + +func (suite *SoloMachineTestSuite) TestGetTimestampAtHeight() { + var ( + clientID string + height exported.Height + ) + + testCases := []struct { + name string + malleate func() + expValue uint64 + expErr error + }{ + { + "success: get timestamp at height exists", + func() {}, + suite.solomachine.ClientState().ConsensusState.Timestamp, + nil, + }, + { + "success: modified height", + func() { + height = clienttypes.ZeroHeight() + }, + // Timestamp should be the same. + suite.solomachine.ClientState().ConsensusState.Timestamp, + nil, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedSmClientID + }, + 0, + clienttypes.ErrClientNotFound, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + clientID = suite.solomachine.ClientID + clientState := suite.solomachine.ClientState() + height = clienttypes.NewHeight(0, suite.solomachine.ClientState().Sequence) + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + + tc.malleate() + + ts, err := lightClientModule.TimestampAtHeight(suite.chainA.GetContext(), clientID, height) + + suite.Require().Equal(tc.expValue, ts) + suite.Require().ErrorIs(err, tc.expErr) + }) + } +} + +func (suite *SoloMachineTestSuite) TestInitialize() { + // test singlesig and multisig public keys + for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { + malleatedConsensus := sm.ClientState().ConsensusState + malleatedConsensus.Timestamp += 10 + + testCases := []struct { + name string + consState exported.ConsensusState + clientState exported.ClientState + expErr error + }{ + { + "success: valid consensus state", + sm.ConsensusState(), + sm.ClientState(), + nil, + }, + { + "failure: nil consensus state", + nil, + sm.ClientState(), + clienttypes.ErrInvalidConsensus, + }, + { + "failure: invalid consensus state: Tendermint consensus state", + &ibctm.ConsensusState{}, + sm.ClientState(), + errors.New("proto: wrong wireType = 0 for field TypeUrl"), + }, + { + "failure: invalid consensus state: consensus state does not match consensus state in client", + malleatedConsensus, + sm.ClientState(), + clienttypes.ErrInvalidConsensus, + }, + { + "failure: invalid client state: sequence is zero", + sm.ConsensusState(), + solomachine.NewClientState(0, sm.ConsensusState()), + clienttypes.ErrInvalidClient, + }, + { + "failure: invalid client state: Tendermint client state", + sm.ConsensusState(), + &ibctm.ClientState{}, + errors.New("proto: wrong wireType = 2 for field IsFrozen"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + clientID := sm.ClientID + + clientStateBz := suite.chainA.Codec.MustMarshal(tc.clientState) + consStateBz := suite.chainA.Codec.MustMarshal(tc.consState) + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + err = lightClientModule.Initialize(suite.chainA.GetContext(), clientID, clientStateBz, consStateBz) + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), clientID) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().True(store.Has(host.ClientStateKey())) + } else { + suite.Require().ErrorContains(err, tc.expErr.Error()) + suite.Require().False(store.Has(host.ClientStateKey())) + } + }) + } + } +} + +func (suite *SoloMachineTestSuite) TestVerifyMembership() { + var ( + clientState *solomachine.ClientState + path exported.Path + proof []byte + testingPath *ibctesting.Path + signBytes solomachine.SignBytes + err error + clientID string + ) + + // test singlesig and multisig public keys + for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() {}, + nil, + }, + { + "success: client state verification", + func() { + clientState = sm.ClientState() + clientStateBz, err := suite.chainA.Codec.MarshalInterface(clientState) + suite.Require().NoError(err) + + path = sm.GetClientStatePath(counterpartyClientIdentifier) + merklePath, ok := path.(commitmenttypesv2.MerklePath) + suite.Require().True(ok) + key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + suite.Require().NoError(err) + signBytes = solomachine.SignBytes{ + Sequence: sm.GetHeight().GetRevisionHeight(), + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: key, + Data: clientStateBz, + } + + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(signBz) + + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + nil, + }, + { + "success: consensus state verification", + func() { + clientState = sm.ClientState() + consensusState := clientState.ConsensusState + consensusStateBz, err := suite.chainA.Codec.MarshalInterface(consensusState) + suite.Require().NoError(err) + + path = sm.GetConsensusStatePath(counterpartyClientIdentifier, clienttypes.NewHeight(0, 1)) + merklePath, ok := path.(commitmenttypesv2.MerklePath) + suite.Require().True(ok) + key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + suite.Require().NoError(err) + signBytes = solomachine.SignBytes{ + Sequence: sm.Sequence, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: key, + Data: consensusStateBz, + } + + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(signBz) + + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + nil, + }, + { + "success: connection state verification", + func() { + testingPath.SetupConnections() + + connectionEnd, found := suite.chainA.GetSimApp().IBCKeeper.ConnectionKeeper.GetConnection(suite.chainA.GetContext(), ibctesting.FirstConnectionID) + suite.Require().True(found) + + connectionEndBz, err := suite.chainA.Codec.Marshal(&connectionEnd) + suite.Require().NoError(err) + + path = sm.GetConnectionStatePath(ibctesting.FirstConnectionID) + merklePath, ok := path.(commitmenttypesv2.MerklePath) + suite.Require().True(ok) + key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + suite.Require().NoError(err) + signBytes = solomachine.SignBytes{ + Sequence: sm.Sequence, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: key, + Data: connectionEndBz, + } + + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(signBz) + + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + nil, + }, + { + "success: channel state verification", + func() { + testingPath.SetupConnections() + suite.coordinator.CreateMockChannels(testingPath) + + channelEnd, found := suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.GetChannel(suite.chainA.GetContext(), ibctesting.MockPort, testingPath.EndpointA.ChannelID) + suite.Require().True(found) + + channelEndBz, err := suite.chainA.Codec.Marshal(&channelEnd) + suite.Require().NoError(err) + + path = sm.GetChannelStatePath(ibctesting.MockPort, ibctesting.FirstChannelID) + merklePath, ok := path.(commitmenttypesv2.MerklePath) + suite.Require().True(ok) + key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + suite.Require().NoError(err) + signBytes = solomachine.SignBytes{ + Sequence: sm.Sequence, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: key, + Data: channelEndBz, + } + + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(signBz) + + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + nil, + }, + { + "success: next sequence recv verification", + func() { + testingPath.SetupConnections() + suite.coordinator.CreateMockChannels(testingPath) + + nextSeqRecv, found := suite.chainA.GetSimApp().IBCKeeper.ChannelKeeper.GetNextSequenceRecv(suite.chainA.GetContext(), ibctesting.MockPort, testingPath.EndpointA.ChannelID) + suite.Require().True(found) + + path = sm.GetNextSequenceRecvPath(ibctesting.MockPort, ibctesting.FirstChannelID) + merklePath, ok := path.(commitmenttypesv2.MerklePath) + suite.Require().True(ok) + key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + suite.Require().NoError(err) + signBytes = solomachine.SignBytes{ + Sequence: sm.Sequence, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: key, + Data: sdk.Uint64ToBigEndian(nextSeqRecv), + } + + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(signBz) + + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + nil, + }, + { + "success: packet commitment verification", + func() { + packet := channeltypes.NewPacket( + ibctesting.MockPacketData, + 1, + ibctesting.MockPort, + ibctesting.FirstChannelID, + ibctesting.MockPort, + ibctesting.FirstChannelID, + clienttypes.NewHeight(0, 10), + 0, + ) + + commitmentBz := channeltypes.CommitPacket(packet) + path = sm.GetPacketCommitmentPath(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + merklePath, ok := path.(commitmenttypesv2.MerklePath) + suite.Require().True(ok) + key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + suite.Require().NoError(err) + signBytes = solomachine.SignBytes{ + Sequence: sm.Sequence, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: key, + Data: commitmentBz, + } + + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(signBz) + + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + nil, + }, + { + "success: packet acknowledgement verification", + func() { + path = sm.GetPacketAcknowledgementPath(ibctesting.MockPort, ibctesting.FirstChannelID, 1) + merklePath, ok := path.(commitmenttypesv2.MerklePath) + suite.Require().True(ok) + key, err := merklePath.GetKey(1) // index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + suite.Require().NoError(err) + signBytes = solomachine.SignBytes{ + Sequence: sm.Sequence, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: key, + Data: ibctesting.MockAcknowledgement, + } + + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(signBz) + + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + nil, + }, + { + "success: packet receipt verification", + func() { + path = sm.GetPacketReceiptPath(ibctesting.MockPort, ibctesting.FirstChannelID, 1) + merklePath, ok := path.(commitmenttypesv2.MerklePath) + suite.Require().True(ok) + key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + suite.Require().NoError(err) + signBytes = solomachine.SignBytes{ + Sequence: sm.Sequence, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: key, + Data: []byte{byte(1)}, // packet receipt is stored as a single byte + } + + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(signBz) + + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + nil, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedSmClientID + }, + clienttypes.ErrClientNotFound, + }, + { + "failure: invalid path type - empty", + func() { + path = ibcmock.KeyPath{} + }, + ibcerrors.ErrInvalidType, + }, + { + "failure: malformed proof fails to unmarshal", + func() { + path = sm.GetClientStatePath(counterpartyClientIdentifier) + proof = []byte("invalid proof") + }, + errors.New("failed to unmarshal proof into type"), + }, + { + "failure: consensus state timestamp is greater than signature", + func() { + consensusState := &solomachine.ConsensusState{ + Timestamp: sm.Time + 1, + PublicKey: sm.ConsensusState().PublicKey, + } + + clientState = solomachine.NewClientState(sm.Sequence, consensusState) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + }, + fmt.Errorf("the consensus state timestamp is greater than the signature timestamp (11 >= 10): %s", solomachine.ErrInvalidProof), + }, + { + "failure: signature data is nil", + func() { + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: nil, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + fmt.Errorf("signature data cannot be empty: %s", solomachine.ErrInvalidProof), + }, + { + "failure: consensus state public key is nil", + func() { + clientState.ConsensusState.PublicKey = nil + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + }, + fmt.Errorf("consensus state PublicKey cannot be nil: %s", clienttypes.ErrInvalidConsensus), + }, + { + "failure: malformed signature data fails to unmarshal", + func() { + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: []byte("invalid signature data"), + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + errors.New("failed to unmarshal proof into type"), + }, + { + "failure: proof is nil", + func() { + proof = nil + }, + fmt.Errorf("proof cannot be empty: %s", solomachine.ErrInvalidProof), + }, + { + "failure: proof verification failed", + func() { + signBytes.Data = []byte("invalid membership data value") + }, + solomachine.ErrSignatureVerificationFailed, + }, + { + "failure: empty path", + func() { + path = commitmenttypesv2.MerklePath{} + }, + fmt.Errorf("path must be of length 2: []: %s", host.ErrInvalidPath), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + testingPath = ibctesting.NewPath(suite.chainA, suite.chainB) + + clientID = sm.ClientID + clientState = sm.ClientState() + + path = commitmenttypesv2.NewMerklePath([]byte("ibc"), []byte("solomachine")) + merklePath, ok := path.(commitmenttypesv2.MerklePath) + suite.Require().True(ok) + key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + suite.Require().NoError(err) + signBytes = solomachine.SignBytes{ + Sequence: sm.GetHeight().GetRevisionHeight(), + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: key, + Data: []byte("solomachine"), + } + + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(signBz) + + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + // Set the client state in the store for light client call to find. + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + + tc.malleate() + + var expSeq uint64 + if clientState.ConsensusState != nil { + expSeq = clientState.Sequence + 1 + } + + // Verify the membership proof + err = lightClientModule.VerifyMembership( + suite.chainA.GetContext(), clientID, clienttypes.ZeroHeight(), + 0, 0, proof, path, signBytes.Data, + ) + + if tc.expErr == nil { + // Grab fresh client state after updates. + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), clientID) + suite.Require().True(found) + clientState, ok = cs.(*solomachine.ClientState) + suite.Require().True(ok) + + suite.Require().NoError(err) + // clientState.Sequence is the most recent view of state. + suite.Require().Equal(expSeq, clientState.Sequence) + } else { + suite.Require().Error(err) + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } + } +} + +func (suite *SoloMachineTestSuite) TestVerifyNonMembership() { + var ( + clientState *solomachine.ClientState + path exported.Path + proof []byte + signBytes solomachine.SignBytes + err error + clientID string + ) + + // test singlesig and multisig public keys + for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() {}, + nil, + }, + { + "success: packet receipt absence verification", + func() { + path = sm.GetPacketReceiptPath(ibctesting.MockPort, ibctesting.FirstChannelID, 1) + merklePath, ok := path.(commitmenttypesv2.MerklePath) + suite.Require().True(ok) + key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + suite.Require().NoError(err) + signBytes = solomachine.SignBytes{ + Sequence: sm.GetHeight().GetRevisionHeight(), + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: key, + Data: nil, + } + + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(signBz) + + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + nil, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedSmClientID + }, + clienttypes.ErrClientNotFound, + }, + { + "failure: invalid path type", + func() { + path = ibcmock.KeyPath{} + }, + ibcerrors.ErrInvalidType, + }, + { + "failure: malformed proof fails to unmarshal", + func() { + path = sm.GetClientStatePath(counterpartyClientIdentifier) + proof = []byte("invalid proof") + }, + errors.New("failed to unmarshal proof into type"), + }, + { + "failure: consensus state timestamp is greater than signature", + func() { + consensusState := &solomachine.ConsensusState{ + Timestamp: sm.Time + 1, + PublicKey: sm.ConsensusState().PublicKey, + } + + clientState = solomachine.NewClientState(sm.Sequence, consensusState) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + }, + fmt.Errorf("the consensus state timestamp is greater than the signature timestamp (11 >= 10): %s", solomachine.ErrInvalidProof), + }, + { + "failure: signature data is nil", + func() { + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: nil, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + fmt.Errorf("signature data cannot be empty: %s", solomachine.ErrInvalidProof), + }, + { + "failure: consensus state public key is nil", + func() { + clientState.ConsensusState.PublicKey = nil + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + }, + fmt.Errorf("consensus state PublicKey cannot be nil: %s", clienttypes.ErrInvalidConsensus), + }, + { + "failure: malformed signature data fails to unmarshal", + func() { + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: []byte("invalid signature data"), + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + errors.New("failed to unmarshal proof into type"), + }, + { + "failure: proof is nil", + func() { + proof = nil + }, + fmt.Errorf("proof cannot be empty: %s", solomachine.ErrInvalidProof), + }, + { + "failure: proof verification failed", + func() { + signBytes.Data = []byte("invalid non-membership data value") + + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(signBz) + + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + }, + solomachine.ErrSignatureVerificationFailed, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + clientState = sm.ClientState() + clientID = sm.ClientID + + path = commitmenttypesv2.NewMerklePath([]byte("ibc"), []byte("solomachine")) + merklePath, ok := path.(commitmenttypesv2.MerklePath) + suite.Require().True(ok) + key, err := merklePath.GetKey(1) // in a multistore context: index 0 is the key for the IBC store in the multistore, index 1 is the key in the IBC store + suite.Require().NoError(err) + signBytes = solomachine.SignBytes{ + Sequence: sm.GetHeight().GetRevisionHeight(), + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: key, + Data: nil, + } + + signBz, err := suite.chainA.Codec.Marshal(&signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(signBz) + + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: sm.Time, + } + + proof, err = suite.chainA.Codec.Marshal(signatureDoc) + suite.Require().NoError(err) + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + // Set the client state in the store for light client call to find. + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + + tc.malleate() + + var expSeq uint64 + if clientState.ConsensusState != nil { + expSeq = clientState.Sequence + 1 + } + + // Verify the membership proof + err = lightClientModule.VerifyNonMembership( + suite.chainA.GetContext(), clientID, clienttypes.ZeroHeight(), + 0, 0, proof, path, + ) + + if tc.expErr == nil { + // Grab fresh client state after updates. + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), clientID) + suite.Require().True(found) + clientState, ok = cs.(*solomachine.ClientState) + suite.Require().True(ok) + + suite.Require().NoError(err) + suite.Require().Equal(expSeq, clientState.Sequence) + } else { + suite.Require().Error(err) + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } + } +} + +func (suite *SoloMachineTestSuite) TestRecoverClient() { + var ( + subjectClientID, substituteClientID string + subjectClientState, substituteClientState *solomachine.ClientState + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() { + }, + nil, + }, + { + "failure: cannot parse malformed substitute client ID", + func() { + substituteClientID = ibctesting.InvalidID + }, + host.ErrInvalidID, + }, + { + "failure: substitute client ID does not contain 06-solomachine prefix", + func() { + substituteClientID = wasmClientID + }, + clienttypes.ErrInvalidClientType, + }, + { + "failure: cannot find subject client state", + func() { + subjectClientID = unusedSmClientID + }, + clienttypes.ErrClientNotFound, + }, + { + "failure: cannot find substitute client state", + func() { + substituteClientID = unusedSmClientID + }, + clienttypes.ErrClientNotFound, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + ctx := suite.chainA.GetContext() + + subjectClientID = suite.chainA.App.GetIBCKeeper().ClientKeeper.GenerateClientIdentifier(ctx, exported.Solomachine) + subject := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, substituteClientID, "testing", 1) + subjectClientState = subject.ClientState() + + substituteClientID = suite.chainA.App.GetIBCKeeper().ClientKeeper.GenerateClientIdentifier(ctx, exported.Solomachine) + substitute := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, substituteClientID, "testing", 1) + substitute.Sequence++ // increase sequence so that latest height of substitute is > than subject's latest height + substituteClientState = substitute.ClientState() + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, substituteClientID) + clientStore.Get(host.ClientStateKey()) + bz := clienttypes.MustMarshalClientState(suite.chainA.Codec, substituteClientState) + clientStore.Set(host.ClientStateKey(), bz) + + subjectClientState.IsFrozen = true + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(ctx, subjectClientID, subjectClientState) + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), subjectClientID) + suite.Require().NoError(err) + + tc.malleate() + + err = lightClientModule.RecoverClient(ctx, subjectClientID, substituteClientID) + + if tc.expErr == nil { + suite.Require().NoError(err) + + // assert that status of subject client is now Active + clientStore = suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, subjectClientID) + bz = clientStore.Get(host.ClientStateKey()) + smClientState, ok := clienttypes.MustUnmarshalClientState(suite.chainA.Codec, bz).(*solomachine.ClientState) + suite.Require().True(ok) + + suite.Require().Equal(substituteClientState.ConsensusState, smClientState.ConsensusState) + suite.Require().Equal(substituteClientState.Sequence, smClientState.Sequence) + suite.Require().Equal(exported.Active, lightClientModule.Status(ctx, subjectClientID)) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *SoloMachineTestSuite) TestUpdateState() { + var ( + clientState *solomachine.ClientState + clientMsg exported.ClientMessage + clientID string + ) + + // test singlesig and multisig public keys + for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { + + testCases := []struct { + name string + malleate func() + expPanic error + }{ + { + "successful update", + func() {}, + nil, + }, + { + "invalid type misbehaviour no-ops", + func() { + clientState = sm.ClientState() + clientMsg = sm.CreateMisbehaviour() + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + }, + nil, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedSmClientID + }, + fmt.Errorf("%s: %s", unusedSmClientID, clienttypes.ErrClientNotFound), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + clientID = sm.ClientID + clientState = sm.ClientState() + clientMsg = sm.CreateHeader(sm.Diversifier) + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + + tc.malleate() // setup test + + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), clientID) + + var consensusHeights []exported.Height + updateStateFunc := func() { + consensusHeights = lightClientModule.UpdateState(suite.chainA.GetContext(), clientID, clientMsg) + } + + if tc.expPanic == nil { + updateStateFunc() + + clientStateBz := store.Get(host.ClientStateKey()) + suite.Require().NotEmpty(clientStateBz) + + newClientState := clienttypes.MustUnmarshalClientState(suite.chainA.Codec, clientStateBz) + + if len(consensusHeights) == 0 { + suite.Require().Equal(clientState, newClientState) + return + } + + suite.Require().Len(consensusHeights, 1) + suite.Require().Equal(uint64(0), consensusHeights[0].GetRevisionNumber()) + suite.Require().Equal(newClientState.(*solomachine.ClientState).Sequence, consensusHeights[0].GetRevisionHeight()) + + suite.Require().False(newClientState.(*solomachine.ClientState).IsFrozen) + suite.Require().Equal(clientMsg.(*solomachine.Header).NewPublicKey, newClientState.(*solomachine.ClientState).ConsensusState.PublicKey) + suite.Require().Equal(clientMsg.(*solomachine.Header).NewDiversifier, newClientState.(*solomachine.ClientState).ConsensusState.Diversifier) + suite.Require().Equal(clientMsg.(*solomachine.Header).Timestamp, newClientState.(*solomachine.ClientState).ConsensusState.Timestamp) + } else { + suite.Require().PanicsWithError(tc.expPanic.Error(), updateStateFunc) + } + }) + } + } +} + +func (suite *SoloMachineTestSuite) TestCheckForMisbehaviour() { + var ( + clientMsg exported.ClientMessage + clientID string + ) + + // test singlesig and multisig public keys + for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { + testCases := []struct { + name string + malleate func() + foundMisbehaviour bool + expPanic error + }{ + { + "success", + func() { + clientMsg = sm.CreateMisbehaviour() + }, + true, + nil, + }, + { + "failure: normal header returns false", + func() { + clientMsg = sm.CreateHeader(sm.Diversifier) + }, + false, + nil, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedSmClientID + }, + false, + fmt.Errorf("%s: %s", unusedSmClientID, clienttypes.ErrClientNotFound), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + clientID = sm.ClientID + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, sm.ClientState()) + + tc.malleate() + + var foundMisbehaviour bool + foundMisbehaviourFunc := func() { + foundMisbehaviour = lightClientModule.CheckForMisbehaviour(suite.chainA.GetContext(), clientID, clientMsg) + } + + if tc.expPanic == nil { + foundMisbehaviourFunc() + + suite.Require().Equal(tc.foundMisbehaviour, foundMisbehaviour) + } else { + suite.Require().PanicsWithError(tc.expPanic.Error(), foundMisbehaviourFunc) + suite.Require().False(foundMisbehaviour) + } + }) + } + } +} + +func (suite *SoloMachineTestSuite) TestUpdateStateOnMisbehaviour() { + var clientID string + + // test singlesig and multisig public keys + for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { + testCases := []struct { + name string + malleate func() + expPanic error + }{ + { + "success", + func() {}, + nil, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedSmClientID + }, + fmt.Errorf("%s: %s", unusedSmClientID, clienttypes.ErrClientNotFound), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + clientID = sm.ClientID + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, sm.ClientState()) + + tc.malleate() + + updateOnMisbehaviourFunc := func() { + lightClientModule.UpdateStateOnMisbehaviour(suite.chainA.GetContext(), clientID, nil) + } + + if tc.expPanic == nil { + updateOnMisbehaviourFunc() + + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), clientID) + + clientStateBz := store.Get(host.ClientStateKey()) + suite.Require().NotEmpty(clientStateBz) + + newClientState := clienttypes.MustUnmarshalClientState(suite.chainA.Codec, clientStateBz) + + suite.Require().True(newClientState.(*solomachine.ClientState).IsFrozen) + } else { + suite.Require().PanicsWithError(tc.expPanic.Error(), updateOnMisbehaviourFunc) + } + }) + } + } +} + +func (suite *SoloMachineTestSuite) TestVerifyClientMessageHeader() { + var ( + clientID string + clientMsg exported.ClientMessage + ) + + // test singlesig and multisig public keys + for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success: successful header", + func() { + clientMsg = sm.CreateHeader(sm.Diversifier) + }, + nil, + }, + { + "success: successful header with new diversifier", + func() { + clientMsg = sm.CreateHeader(sm.Diversifier + "0") + }, + nil, + }, + { + "success: successful misbehaviour", + func() { + clientMsg = sm.CreateMisbehaviour() + }, + nil, + }, + { + "failure: invalid client message type", + func() { + clientMsg = &ibctm.Header{} + }, + clienttypes.ErrInvalidClientType, + }, + { + "failure: invalid header Signature", + func() { + h := sm.CreateHeader(sm.Diversifier) + h.Signature = suite.GetInvalidProof() + clientMsg = h + }, errors.New("proto: wrong wireType = 0 for field Multi"), + }, + { + "failure: invalid timestamp in header", + func() { + h := sm.CreateHeader(sm.Diversifier) + h.Timestamp-- + clientMsg = h + }, clienttypes.ErrInvalidHeader, + }, + { + "failure: signature uses wrong sequence", + func() { + sm.Sequence++ + clientMsg = sm.CreateHeader(sm.Diversifier) + }, + solomachine.ErrSignatureVerificationFailed, + }, + { + "signature uses new pubkey to sign", + func() { + // store in temp before assigning to interface type + cs := sm.ClientState() + h := sm.CreateHeader(sm.Diversifier) + + publicKey, err := codectypes.NewAnyWithValue(sm.PublicKey) + suite.NoError(err) + + data := &solomachine.HeaderData{ + NewPubKey: publicKey, + NewDiversifier: h.NewDiversifier, + } + + dataBz, err := suite.chainA.Codec.Marshal(data) + suite.Require().NoError(err) + + // generate invalid signature + signBytes := &solomachine.SignBytes{ + Sequence: cs.Sequence, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: []byte("invalid signature data"), + Data: dataBz, + } + + signBz, err := suite.chainA.Codec.Marshal(signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(signBz) + suite.Require().NoError(err) + h.Signature = sig + + clientMsg = h + }, + solomachine.ErrSignatureVerificationFailed, + }, + { + "failure: signature signs over old pubkey", + func() { + // store in temp before assigning to interface type + cs := sm.ClientState() + + oldPubKey := sm.PublicKey + h := sm.CreateHeader(sm.Diversifier) + + // generate invalid signature + data := append(sdk.Uint64ToBigEndian(cs.Sequence), oldPubKey.Bytes()...) + sig := sm.GenerateSignature(data) + h.Signature = sig + + clientMsg = h + }, + solomachine.ErrSignatureVerificationFailed, + }, + { + "failure: consensus state public key is nil - header", + func() { + h := sm.CreateHeader(sm.Diversifier) + h.NewPublicKey = nil + clientMsg = h + }, + solomachine.ErrSignatureVerificationFailed, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedSmClientID + }, + fmt.Errorf("%s: %s", unusedSmClientID, clienttypes.ErrClientNotFound), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + clientID = sm.ClientID + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, sm.ClientState()) + + tc.malleate() + + err = lightClientModule.VerifyClientMessage(suite.chainA.GetContext(), clientID, clientMsg) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } + } +} + +func (suite *SoloMachineTestSuite) TestVerifyClientMessageMisbehaviour() { + var ( + clientMsg exported.ClientMessage + clientState *solomachine.ClientState + clientID string + ) + + // test singlesig and multisig public keys + for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success: successful misbehaviour", + func() { + clientMsg = sm.CreateMisbehaviour() + }, + nil, + }, + { + "success: old misbehaviour is successful (timestamp is less than current consensus state)", + func() { + clientState = sm.ClientState() + sm.Time -= 5 + clientMsg = sm.CreateMisbehaviour() + }, nil, + }, + { + "failure: invalid client message type", + func() { + clientMsg = &ibctm.Header{} + }, + clienttypes.ErrInvalidClientType, + }, + { + "failure: consensus state pubkey is nil", + func() { + clientState.ConsensusState.PublicKey = nil + clientMsg = sm.CreateMisbehaviour() + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + }, + clienttypes.ErrInvalidConsensus, + }, + { + "failure: invalid SignatureOne SignatureData", + func() { + m := sm.CreateMisbehaviour() + + m.SignatureOne.Signature = suite.GetInvalidProof() + clientMsg = m + }, errors.New("proto: wrong wireType = 0 for field Multi"), + }, + { + "failure: invalid SignatureTwo SignatureData", + func() { + m := sm.CreateMisbehaviour() + + m.SignatureTwo.Signature = suite.GetInvalidProof() + clientMsg = m + }, errors.New("proto: wrong wireType = 0 for field Multi"), + }, + { + "failure: invalid SignatureOne timestamp", + func() { + m := sm.CreateMisbehaviour() + + m.SignatureOne.Timestamp = 1000000000000 + clientMsg = m + }, solomachine.ErrSignatureVerificationFailed, + }, + { + "failure: invalid SignatureTwo timestamp", + func() { + m := sm.CreateMisbehaviour() + + m.SignatureTwo.Timestamp = 1000000000000 + clientMsg = m + }, solomachine.ErrSignatureVerificationFailed, + }, + { + "failure: invalid first signature data", + func() { + // store in temp before assigning to interface type + m := sm.CreateMisbehaviour() + + msg := []byte("DATA ONE") + signBytes := &solomachine.SignBytes{ + Sequence: sm.Sequence + 1, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: []byte("invalid signature data"), + Data: msg, + } + + data, err := suite.chainA.Codec.Marshal(signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(data) + + m.SignatureOne.Signature = sig + m.SignatureOne.Data = msg + clientMsg = m + }, + solomachine.ErrSignatureVerificationFailed, + }, + { + "failure: invalid second signature data", + func() { + // store in temp before assigning to interface type + m := sm.CreateMisbehaviour() + + msg := []byte("DATA TWO") + signBytes := &solomachine.SignBytes{ + Sequence: sm.Sequence + 1, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: []byte("invalid signature data"), + Data: msg, + } + + data, err := suite.chainA.Codec.Marshal(signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(data) + + m.SignatureTwo.Signature = sig + m.SignatureTwo.Data = msg + clientMsg = m + }, + solomachine.ErrSignatureVerificationFailed, + }, + { + "failure: wrong pubkey generates first signature", + func() { + badMisbehaviour := sm.CreateMisbehaviour() + + // update public key to a new one + sm.CreateHeader(sm.Diversifier) + m := sm.CreateMisbehaviour() + + // set SignatureOne to use the wrong signature + m.SignatureOne = badMisbehaviour.SignatureOne + clientMsg = m + }, solomachine.ErrSignatureVerificationFailed, + }, + { + "failure: wrong pubkey generates second signature", + func() { + badMisbehaviour := sm.CreateMisbehaviour() + + // update public key to a new one + sm.CreateHeader(sm.Diversifier) + m := sm.CreateMisbehaviour() + + // set SignatureTwo to use the wrong signature + m.SignatureTwo = badMisbehaviour.SignatureTwo + clientMsg = m + }, solomachine.ErrSignatureVerificationFailed, + }, + { + "failure: signatures sign over different sequence", + func() { + // store in temp before assigning to interface type + m := sm.CreateMisbehaviour() + + // Signature One + msg := []byte("DATA ONE") + // sequence used is plus 1 + signBytes := &solomachine.SignBytes{ + Sequence: sm.Sequence + 1, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: []byte("invalid signature data"), + Data: msg, + } + + data, err := suite.chainA.Codec.Marshal(signBytes) + suite.Require().NoError(err) + + sig := sm.GenerateSignature(data) + + m.SignatureOne.Signature = sig + m.SignatureOne.Data = msg + + // Signature Two + msg = []byte("DATA TWO") + // sequence used is minus 1 + + signBytes = &solomachine.SignBytes{ + Sequence: sm.Sequence - 1, + Timestamp: sm.Time, + Diversifier: sm.Diversifier, + Path: []byte("invalid signature data"), + Data: msg, + } + data, err = suite.chainA.Codec.Marshal(signBytes) + suite.Require().NoError(err) + + sig = sm.GenerateSignature(data) + + m.SignatureTwo.Signature = sig + m.SignatureTwo.Data = msg + + clientMsg = m + }, + solomachine.ErrSignatureVerificationFailed, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedSmClientID + }, + fmt.Errorf("%s: %s", unusedSmClientID, clienttypes.ErrClientNotFound), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + clientID = sm.ClientID + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, sm.ClientState()) + + tc.malleate() + + err = lightClientModule.VerifyClientMessage(suite.chainA.GetContext(), clientID, clientMsg) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } + } +} + +func (suite *SoloMachineTestSuite) TestVerifyUpgradeAndUpdateState() { + clientID := suite.solomachine.ClientID + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + err = lightClientModule.VerifyUpgradeAndUpdateState(suite.chainA.GetContext(), clientID, nil, nil, nil, nil) + suite.Require().Error(err) +} + +func (suite *SoloMachineTestSuite) TestLatestHeight() { + var clientID string + + testCases := []struct { + name string + malleate func() + expHeight clienttypes.Height + }{ + { + "success", + func() {}, + // Default as returned by solomachine.ClientState() + clienttypes.NewHeight(0, 1), + }, + { + "failure: cannot find client state", + func() { + clientID = unusedSmClientID + }, + clienttypes.ZeroHeight(), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + clientID = suite.solomachine.ClientID + clientState := suite.solomachine.ClientState() + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), clientID, clientState) + + tc.malleate() + + height := lightClientModule.LatestHeight(suite.chainA.GetContext(), clientID) + + suite.Require().Equal(tc.expHeight, height) + }) + } +} diff --git a/modules/light-clients/06-solomachine/misbehaviour.go b/modules/light-clients/06-solomachine/misbehaviour.go new file mode 100644 index 0000000..4035dbf --- /dev/null +++ b/modules/light-clients/06-solomachine/misbehaviour.go @@ -0,0 +1,63 @@ +package solomachine + +import ( + "bytes" + + errorsmod "cosmossdk.io/errors" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var _ exported.ClientMessage = (*Misbehaviour)(nil) + +// ClientType is a Solo Machine light client. +func (Misbehaviour) ClientType() string { + return exported.Solomachine +} + +// ValidateBasic implements Misbehaviour interface. +func (misbehaviour Misbehaviour) ValidateBasic() error { + if misbehaviour.Sequence == 0 { + return errorsmod.Wrap(clienttypes.ErrInvalidMisbehaviour, "sequence cannot be 0") + } + + if err := misbehaviour.SignatureOne.ValidateBasic(); err != nil { + return errorsmod.Wrap(err, "signature one failed basic validation") + } + + if err := misbehaviour.SignatureTwo.ValidateBasic(); err != nil { + return errorsmod.Wrap(err, "signature two failed basic validation") + } + + // misbehaviour signatures cannot be identical. + if bytes.Equal(misbehaviour.SignatureOne.Signature, misbehaviour.SignatureTwo.Signature) { + return errorsmod.Wrap(clienttypes.ErrInvalidMisbehaviour, "misbehaviour signatures cannot be equal") + } + + // message data signed cannot be identical if both paths are the same. + if bytes.Equal(misbehaviour.SignatureOne.Path, misbehaviour.SignatureTwo.Path) && + bytes.Equal(misbehaviour.SignatureOne.Data, misbehaviour.SignatureTwo.Data) { + return errorsmod.Wrap(clienttypes.ErrInvalidMisbehaviour, "misbehaviour signature data must be signed over different messages") + } + + return nil +} + +// ValidateBasic ensures that the signature and data fields are non-empty. +func (sd SignatureAndData) ValidateBasic() error { + if len(sd.Signature) == 0 { + return errorsmod.Wrap(ErrInvalidSignatureAndData, "signature cannot be empty") + } + if len(sd.Data) == 0 { + return errorsmod.Wrap(ErrInvalidSignatureAndData, "data for signature cannot be empty") + } + if len(sd.Path) == 0 { + return errorsmod.Wrap(ErrInvalidSignatureAndData, "path for signature cannot be empty") + } + if sd.Timestamp == 0 { + return errorsmod.Wrap(ErrInvalidSignatureAndData, "timestamp cannot be 0") + } + + return nil +} diff --git a/modules/light-clients/06-solomachine/misbehaviour_handle.go b/modules/light-clients/06-solomachine/misbehaviour_handle.go new file mode 100644 index 0000000..f16bfc7 --- /dev/null +++ b/modules/light-clients/06-solomachine/misbehaviour_handle.go @@ -0,0 +1,72 @@ +package solomachine + +import ( + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + commitmenttypesv2 "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types/v2" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// CheckForMisbehaviour returns true for type Misbehaviour (passed VerifyClientMessage check), otherwise returns false +func (ClientState) CheckForMisbehaviour(_ sdk.Context, _ codec.BinaryCodec, _ storetypes.KVStore, clientMsg exported.ClientMessage) bool { + if _, ok := clientMsg.(*Misbehaviour); ok { + return true + } + + return false +} + +func (cs ClientState) verifyMisbehaviour(cdc codec.BinaryCodec, misbehaviour *Misbehaviour) error { + // NOTE: a check that the misbehaviour message data are not equal is done by + // misbehaviour.ValidateBasic which is called by the 02-client keeper. + // verify first signature + if err := cs.verifySignatureAndData(cdc, misbehaviour, misbehaviour.SignatureOne); err != nil { + return errorsmod.Wrap(err, "failed to verify signature one") + } + + // verify second signature + if err := cs.verifySignatureAndData(cdc, misbehaviour, misbehaviour.SignatureTwo); err != nil { + return errorsmod.Wrap(err, "failed to verify signature two") + } + + return nil +} + +// verifySignatureAndData verifies that the currently registered public key has signed +// over the provided data and that the data is valid. The data is valid if it can be +// unmarshaled into the specified data type. +func (cs ClientState) verifySignatureAndData(cdc codec.BinaryCodec, misbehaviour *Misbehaviour, sigAndData *SignatureAndData) error { + // do not check misbehaviour timestamp since we want to allow processing of past misbehaviour + if err := cdc.Unmarshal(sigAndData.Path, new(commitmenttypesv2.MerklePath)); err != nil { + return err + } + + signBytes := SignBytes{ + Sequence: misbehaviour.Sequence, + Timestamp: sigAndData.Timestamp, + Diversifier: cs.ConsensusState.Diversifier, + Path: sigAndData.Path, + Data: sigAndData.Data, + } + + data, err := cdc.Marshal(&signBytes) + if err != nil { + return err + } + + sigData, err := UnmarshalSignatureData(cdc, sigAndData.Signature) + if err != nil { + return err + } + + publicKey, err := cs.ConsensusState.GetPubKey() + if err != nil { + return err + } + + return VerifySignature(publicKey, data, sigData) +} diff --git a/modules/light-clients/06-solomachine/misbehaviour_test.go b/modules/light-clients/06-solomachine/misbehaviour_test.go new file mode 100644 index 0000000..1fc1982 --- /dev/null +++ b/modules/light-clients/06-solomachine/misbehaviour_test.go @@ -0,0 +1,134 @@ +package solomachine_test + +import ( + "errors" + + "github.com/cosmos/ibc-go/v10/modules/core/exported" + solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *SoloMachineTestSuite) TestMisbehaviour() { + misbehaviour := suite.solomachine.CreateMisbehaviour() + + suite.Require().Equal(exported.Solomachine, misbehaviour.ClientType()) +} + +func (suite *SoloMachineTestSuite) TestMisbehaviourValidateBasic() { + // test singlesig and multisig public keys + for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { + + testCases := []struct { + name string + malleateMisbehaviour func(misbehaviour *solomachine.Misbehaviour) + expErr error + }{ + { + "valid misbehaviour", + func(*solomachine.Misbehaviour) {}, + nil, + }, + { + "sequence is zero", + func(misbehaviour *solomachine.Misbehaviour) { + misbehaviour.Sequence = 0 + }, + errors.New("sequence cannot be 0: invalid light client misbehaviour"), + }, + { + "signature one sig is empty", + func(misbehaviour *solomachine.Misbehaviour) { + misbehaviour.SignatureOne.Signature = []byte{} + }, + errors.New("signature one failed basic validation: signature cannot be empty: invalid signature and data"), + }, + { + "signature two sig is empty", + func(misbehaviour *solomachine.Misbehaviour) { + misbehaviour.SignatureTwo.Signature = []byte{} + }, + errors.New("signature two failed basic validation: signature cannot be empty: invalid signature and data"), + }, + { + "signature one data is empty", + func(misbehaviour *solomachine.Misbehaviour) { + misbehaviour.SignatureOne.Data = nil + }, + errors.New("signature one failed basic validation: data for signature cannot be empty: invalid signature and data"), + }, + { + "signature two data is empty", + func(misbehaviour *solomachine.Misbehaviour) { + misbehaviour.SignatureTwo.Data = []byte{} + }, + errors.New("signature two failed basic validation: data for signature cannot be empty: invalid signature and data"), + }, + { + "signatures are identical", + func(misbehaviour *solomachine.Misbehaviour) { + misbehaviour.SignatureTwo.Signature = misbehaviour.SignatureOne.Signature + }, + errors.New("misbehaviour signatures cannot be equal: invalid light client misbehaviour"), + }, + { + "data signed is identical but path differs", + func(misbehaviour *solomachine.Misbehaviour) { + misbehaviour.SignatureTwo.Data = misbehaviour.SignatureOne.Data + }, + nil, + }, + { + "data signed and path are identical", + func(misbehaviour *solomachine.Misbehaviour) { + misbehaviour.SignatureTwo.Path = misbehaviour.SignatureOne.Path + misbehaviour.SignatureTwo.Data = misbehaviour.SignatureOne.Data + }, + errors.New("misbehaviour signature data must be signed over different messages: invalid light client misbehaviour"), + }, + { + "data path for SignatureOne is unspecified", + func(misbehaviour *solomachine.Misbehaviour) { + misbehaviour.SignatureOne.Path = []byte{} + }, + errors.New("signature one failed basic validation: path for signature cannot be empty: invalid signature and data"), + }, + { + "data path for SignatureTwo is unspecified", + func(misbehaviour *solomachine.Misbehaviour) { + misbehaviour.SignatureTwo.Path = []byte{} + }, + errors.New("signature two failed basic validation: path for signature cannot be empty: invalid signature and data"), + }, + { + "timestamp for SignatureOne is zero", + func(misbehaviour *solomachine.Misbehaviour) { + misbehaviour.SignatureOne.Timestamp = 0 + }, + errors.New("signature one failed basic validation: timestamp cannot be 0: invalid signature and data"), + }, + { + "timestamp for SignatureTwo is zero", + func(misbehaviour *solomachine.Misbehaviour) { + misbehaviour.SignatureTwo.Timestamp = 0 + }, + errors.New("signature two failed basic validation: timestamp cannot be 0: invalid signature and data"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + misbehaviour := sm.CreateMisbehaviour() + tc.malleateMisbehaviour(misbehaviour) + + err := misbehaviour.ValidateBasic() + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } + } +} diff --git a/modules/light-clients/06-solomachine/module.go b/modules/light-clients/06-solomachine/module.go new file mode 100644 index 0000000..d5c4f15 --- /dev/null +++ b/modules/light-clients/06-solomachine/module.go @@ -0,0 +1,87 @@ +package solomachine + +import ( + "encoding/json" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + + "cosmossdk.io/core/appmodule" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/types/module" +) + +var ( + _ module.AppModuleBasic = (*AppModuleBasic)(nil) + _ appmodule.AppModule = (*AppModule)(nil) +) + +// AppModuleBasic defines the basic application module used by the solo machine light client. +// Only the RegisterInterfaces function needs to be implemented. All other function perform +// a no-op. +type AppModuleBasic struct{} + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (AppModuleBasic) IsOnePerModuleType() {} + +// IsAppModule implements the appmodule.AppModule interface. +func (AppModuleBasic) IsAppModule() {} + +// Name returns the solo machine module name. +func (AppModuleBasic) Name() string { + return ModuleName +} + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (AppModule) IsOnePerModuleType() {} + +// IsAppModule implements the appmodule.AppModule interface. +func (AppModule) IsAppModule() {} + +// RegisterLegacyAminoCodec performs a no-op. The solo machine client does not support amino. +func (AppModuleBasic) RegisterLegacyAminoCodec(*codec.LegacyAmino) {} + +// RegisterInterfaces registers module concrete types into protobuf Any. This allows core IBC +// to unmarshal solo machine types. +func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) { + RegisterInterfaces(registry) +} + +// DefaultGenesis performs a no-op. Genesis is not supported for solo machine. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return nil +} + +// ValidateGenesis performs a no-op. Genesis is not supported for solo machine. +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { + return nil +} + +// RegisterGRPCGatewayRoutes performs a no-op. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {} + +// GetTxCmd performs a no-op. Please see the 02-client cli commands. +func (AppModuleBasic) GetTxCmd() *cobra.Command { + return nil +} + +// GetQueryCmd performs a no-op. Please see the 02-client cli commands. +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return nil +} + +// AppModule is the application module for the Solomachine client module +type AppModule struct { + AppModuleBasic + lightClientModule LightClientModule +} + +// NewAppModule creates a new Solomachine client module +func NewAppModule(lightClientModule LightClientModule) AppModule { + return AppModule{ + lightClientModule: lightClientModule, + } +} diff --git a/modules/light-clients/06-solomachine/proof.go b/modules/light-clients/06-solomachine/proof.go new file mode 100644 index 0000000..70c58b5 --- /dev/null +++ b/modules/light-clients/06-solomachine/proof.go @@ -0,0 +1,44 @@ +package solomachine + +import ( + errorsmod "cosmossdk.io/errors" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/crypto/types/multisig" + "github.com/cosmos/cosmos-sdk/types/tx/signing" +) + +// VerifySignature verifies if the provided public key generated the signature +// over the given data. Single and Multi signature public keys are supported. +// The signature data type must correspond to the public key type. An error is +// returned if signature verification fails or an invalid SignatureData type is +// provided. +func VerifySignature(pubKey cryptotypes.PubKey, signBytes []byte, sigData signing.SignatureData) error { + switch pubKey := pubKey.(type) { + case multisig.PubKey: + data, ok := sigData.(*signing.MultiSignatureData) + if !ok { + return errorsmod.Wrapf(ErrSignatureVerificationFailed, "invalid signature data type, expected %T, got %T", (*signing.MultiSignatureData)(nil), data) + } + + // The function supplied fulfills the VerifyMultisignature interface. No special + // adjustments need to be made to the sign bytes based on the sign mode. + if err := pubKey.VerifyMultisignature(func(signing.SignMode) ([]byte, error) { + return signBytes, nil + }, data); err != nil { + return errorsmod.Wrapf(ErrSignatureVerificationFailed, "failed to verify multisignature: %s", err.Error()) + } + + default: + data, ok := sigData.(*signing.SingleSignatureData) + if !ok { + return errorsmod.Wrapf(ErrSignatureVerificationFailed, "invalid signature data type, expected %T, got %T", (*signing.SingleSignatureData)(nil), data) + } + + if !pubKey.VerifySignature(signBytes, data.Signature) { + return ErrSignatureVerificationFailed + } + } + + return nil +} diff --git a/modules/light-clients/06-solomachine/proof_test.go b/modules/light-clients/06-solomachine/proof_test.go new file mode 100644 index 0000000..eb07b69 --- /dev/null +++ b/modules/light-clients/06-solomachine/proof_test.go @@ -0,0 +1,68 @@ +package solomachine_test + +import ( + "errors" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + + solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine" +) + +func (suite *SoloMachineTestSuite) TestVerifySignature() { + cdc := suite.chainA.App.AppCodec() + signBytes := []byte("sign bytes") + + singleSignature := suite.solomachine.GenerateSignature(signBytes) + singleSigData, err := solomachine.UnmarshalSignatureData(cdc, singleSignature) + suite.Require().NoError(err) + + multiSignature := suite.solomachineMulti.GenerateSignature(signBytes) + multiSigData, err := solomachine.UnmarshalSignatureData(cdc, multiSignature) + suite.Require().NoError(err) + + testCases := []struct { + name string + publicKey cryptotypes.PubKey + sigData signing.SignatureData + expErr error + }{ + { + "single signature with regular public key", + suite.solomachine.PublicKey, + singleSigData, + nil, + }, + { + "multi signature with multisig public key", + suite.solomachineMulti.PublicKey, + multiSigData, + nil, + }, + { + "single signature with multisig public key", + suite.solomachineMulti.PublicKey, + singleSigData, + errors.New("invalid signature data type, expected *signing.MultiSignatureData, got *signing.MultiSignatureData: signature verification failed"), + }, + { + "multi signature with regular public key", + suite.solomachine.PublicKey, + multiSigData, + errors.New("invalid signature data type, expected *signing.SingleSignatureData, got *signing.SingleSignatureData: signature verification failed"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := solomachine.VerifySignature(tc.publicKey, signBytes, tc.sigData) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } +} diff --git a/modules/light-clients/06-solomachine/proposal_handle.go b/modules/light-clients/06-solomachine/proposal_handle.go new file mode 100644 index 0000000..71b5512 --- /dev/null +++ b/modules/light-clients/06-solomachine/proposal_handle.go @@ -0,0 +1,54 @@ +package solomachine + +import ( + "reflect" + + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// CheckSubstituteAndUpdateState verifies that the subject is allowed to be updated by +// a governance proposal and that the substitute client is a solo machine. +// It will update the consensus state to the substitute's consensus state and +// the sequence to the substitute's current sequence. An error is returned if +// the client has been disallowed to be updated by a governance proposal, +// the substitute is not a solo machine, or the current public key equals +// the new public key. +func (cs ClientState) CheckSubstituteAndUpdateState( + ctx sdk.Context, cdc codec.BinaryCodec, subjectClientStore, + _ storetypes.KVStore, substituteClient exported.ClientState, +) error { + substituteClientState, ok := substituteClient.(*ClientState) + if !ok { + return errorsmod.Wrapf(clienttypes.ErrInvalidClientType, "substitute client state type %T, expected %T", substituteClient, &ClientState{}) + } + + subjectPublicKey, err := cs.ConsensusState.GetPubKey() + if err != nil { + return errorsmod.Wrap(err, "failed to get consensus public key") + } + + substitutePublicKey, err := substituteClientState.ConsensusState.GetPubKey() + if err != nil { + return errorsmod.Wrap(err, "failed to get substitute client public key") + } + + if reflect.DeepEqual(subjectPublicKey, substitutePublicKey) { + return errorsmod.Wrapf(clienttypes.ErrInvalidHeader, "subject and substitute have the same public key") + } + + // update to substitute parameters + cs.Sequence = substituteClientState.Sequence + cs.ConsensusState = substituteClientState.ConsensusState + cs.IsFrozen = false + + setClientState(subjectClientStore, cdc, &cs) + + return nil +} diff --git a/modules/light-clients/06-solomachine/solomachine.go b/modules/light-clients/06-solomachine/solomachine.go new file mode 100644 index 0000000..c4a9802 --- /dev/null +++ b/modules/light-clients/06-solomachine/solomachine.go @@ -0,0 +1,32 @@ +package solomachine + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" +) + +// Interface implementation checks. +var _, _, _, _ codectypes.UnpackInterfacesMessage = (*ClientState)(nil), (*ConsensusState)(nil), (*Header)(nil), (*HeaderData)(nil) + +// Data is an interface used for all the signature data bytes proto definitions. +type Data any + +// UnpackInterfaces implements the UnpackInterfaceMessages.UnpackInterfaces method +func (cs ClientState) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + return cs.ConsensusState.UnpackInterfaces(unpacker) +} + +// UnpackInterfaces implements the UnpackInterfaceMessages.UnpackInterfaces method +func (cs ConsensusState) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + return unpacker.UnpackAny(cs.PublicKey, new(cryptotypes.PubKey)) +} + +// UnpackInterfaces implements the UnpackInterfaceMessages.UnpackInterfaces method +func (h Header) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + return unpacker.UnpackAny(h.NewPublicKey, new(cryptotypes.PubKey)) +} + +// UnpackInterfaces implements the UnpackInterfaceMessages.UnpackInterfaces method +func (hd HeaderData) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + return unpacker.UnpackAny(hd.NewPubKey, new(cryptotypes.PubKey)) +} diff --git a/modules/light-clients/06-solomachine/solomachine.pb.go b/modules/light-clients/06-solomachine/solomachine.pb.go new file mode 100644 index 0000000..31a3c81 --- /dev/null +++ b/modules/light-clients/06-solomachine/solomachine.pb.go @@ -0,0 +1,2230 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/lightclients/solomachine/v3/solomachine.proto + +package solomachine + +import ( + fmt "fmt" + types "github.com/cosmos/cosmos-sdk/codec/types" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// ClientState defines a solo machine client that tracks the current consensus +// state and if the client is frozen. +type ClientState struct { + // latest sequence of the client state + Sequence uint64 `protobuf:"varint,1,opt,name=sequence,proto3" json:"sequence,omitempty"` + // frozen sequence of the solo machine + IsFrozen bool `protobuf:"varint,2,opt,name=is_frozen,json=isFrozen,proto3" json:"is_frozen,omitempty"` + ConsensusState *ConsensusState `protobuf:"bytes,3,opt,name=consensus_state,json=consensusState,proto3" json:"consensus_state,omitempty"` +} + +func (m *ClientState) Reset() { *m = ClientState{} } +func (m *ClientState) String() string { return proto.CompactTextString(m) } +func (*ClientState) ProtoMessage() {} +func (*ClientState) Descriptor() ([]byte, []int) { + return fileDescriptor_264187157b9220a4, []int{0} +} +func (m *ClientState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ClientState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ClientState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ClientState) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClientState.Merge(m, src) +} +func (m *ClientState) XXX_Size() int { + return m.Size() +} +func (m *ClientState) XXX_DiscardUnknown() { + xxx_messageInfo_ClientState.DiscardUnknown(m) +} + +var xxx_messageInfo_ClientState proto.InternalMessageInfo + +// ConsensusState defines a solo machine consensus state. The sequence of a +// consensus state is contained in the "height" key used in storing the +// consensus state. +type ConsensusState struct { + // public key of the solo machine + PublicKey *types.Any `protobuf:"bytes,1,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` + // diversifier allows the same public key to be reused across different solo + // machine clients (potentially on different chains) without being considered + // misbehaviour. + Diversifier string `protobuf:"bytes,2,opt,name=diversifier,proto3" json:"diversifier,omitempty"` + Timestamp uint64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` +} + +func (m *ConsensusState) Reset() { *m = ConsensusState{} } +func (m *ConsensusState) String() string { return proto.CompactTextString(m) } +func (*ConsensusState) ProtoMessage() {} +func (*ConsensusState) Descriptor() ([]byte, []int) { + return fileDescriptor_264187157b9220a4, []int{1} +} +func (m *ConsensusState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ConsensusState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ConsensusState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ConsensusState) XXX_Merge(src proto.Message) { + xxx_messageInfo_ConsensusState.Merge(m, src) +} +func (m *ConsensusState) XXX_Size() int { + return m.Size() +} +func (m *ConsensusState) XXX_DiscardUnknown() { + xxx_messageInfo_ConsensusState.DiscardUnknown(m) +} + +var xxx_messageInfo_ConsensusState proto.InternalMessageInfo + +// Header defines a solo machine consensus header +type Header struct { + Timestamp uint64 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` + NewPublicKey *types.Any `protobuf:"bytes,3,opt,name=new_public_key,json=newPublicKey,proto3" json:"new_public_key,omitempty"` + NewDiversifier string `protobuf:"bytes,4,opt,name=new_diversifier,json=newDiversifier,proto3" json:"new_diversifier,omitempty"` +} + +func (m *Header) Reset() { *m = Header{} } +func (m *Header) String() string { return proto.CompactTextString(m) } +func (*Header) ProtoMessage() {} +func (*Header) Descriptor() ([]byte, []int) { + return fileDescriptor_264187157b9220a4, []int{2} +} +func (m *Header) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Header) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Header.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Header) XXX_Merge(src proto.Message) { + xxx_messageInfo_Header.Merge(m, src) +} +func (m *Header) XXX_Size() int { + return m.Size() +} +func (m *Header) XXX_DiscardUnknown() { + xxx_messageInfo_Header.DiscardUnknown(m) +} + +var xxx_messageInfo_Header proto.InternalMessageInfo + +// Misbehaviour defines misbehaviour for a solo machine which consists +// of a sequence and two signatures over different messages at that sequence. +type Misbehaviour struct { + Sequence uint64 `protobuf:"varint,1,opt,name=sequence,proto3" json:"sequence,omitempty"` + SignatureOne *SignatureAndData `protobuf:"bytes,2,opt,name=signature_one,json=signatureOne,proto3" json:"signature_one,omitempty"` + SignatureTwo *SignatureAndData `protobuf:"bytes,3,opt,name=signature_two,json=signatureTwo,proto3" json:"signature_two,omitempty"` +} + +func (m *Misbehaviour) Reset() { *m = Misbehaviour{} } +func (m *Misbehaviour) String() string { return proto.CompactTextString(m) } +func (*Misbehaviour) ProtoMessage() {} +func (*Misbehaviour) Descriptor() ([]byte, []int) { + return fileDescriptor_264187157b9220a4, []int{3} +} +func (m *Misbehaviour) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Misbehaviour) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Misbehaviour.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Misbehaviour) XXX_Merge(src proto.Message) { + xxx_messageInfo_Misbehaviour.Merge(m, src) +} +func (m *Misbehaviour) XXX_Size() int { + return m.Size() +} +func (m *Misbehaviour) XXX_DiscardUnknown() { + xxx_messageInfo_Misbehaviour.DiscardUnknown(m) +} + +var xxx_messageInfo_Misbehaviour proto.InternalMessageInfo + +// SignatureAndData contains a signature and the data signed over to create that +// signature. +type SignatureAndData struct { + Signature []byte `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"` + Path []byte `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` + Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` + Timestamp uint64 `protobuf:"varint,4,opt,name=timestamp,proto3" json:"timestamp,omitempty"` +} + +func (m *SignatureAndData) Reset() { *m = SignatureAndData{} } +func (m *SignatureAndData) String() string { return proto.CompactTextString(m) } +func (*SignatureAndData) ProtoMessage() {} +func (*SignatureAndData) Descriptor() ([]byte, []int) { + return fileDescriptor_264187157b9220a4, []int{4} +} +func (m *SignatureAndData) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *SignatureAndData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_SignatureAndData.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *SignatureAndData) XXX_Merge(src proto.Message) { + xxx_messageInfo_SignatureAndData.Merge(m, src) +} +func (m *SignatureAndData) XXX_Size() int { + return m.Size() +} +func (m *SignatureAndData) XXX_DiscardUnknown() { + xxx_messageInfo_SignatureAndData.DiscardUnknown(m) +} + +var xxx_messageInfo_SignatureAndData proto.InternalMessageInfo + +// TimestampedSignatureData contains the signature data and the timestamp of the +// signature. +type TimestampedSignatureData struct { + SignatureData []byte `protobuf:"bytes,1,opt,name=signature_data,json=signatureData,proto3" json:"signature_data,omitempty"` + Timestamp uint64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` +} + +func (m *TimestampedSignatureData) Reset() { *m = TimestampedSignatureData{} } +func (m *TimestampedSignatureData) String() string { return proto.CompactTextString(m) } +func (*TimestampedSignatureData) ProtoMessage() {} +func (*TimestampedSignatureData) Descriptor() ([]byte, []int) { + return fileDescriptor_264187157b9220a4, []int{5} +} +func (m *TimestampedSignatureData) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TimestampedSignatureData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TimestampedSignatureData.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TimestampedSignatureData) XXX_Merge(src proto.Message) { + xxx_messageInfo_TimestampedSignatureData.Merge(m, src) +} +func (m *TimestampedSignatureData) XXX_Size() int { + return m.Size() +} +func (m *TimestampedSignatureData) XXX_DiscardUnknown() { + xxx_messageInfo_TimestampedSignatureData.DiscardUnknown(m) +} + +var xxx_messageInfo_TimestampedSignatureData proto.InternalMessageInfo + +// SignBytes defines the signed bytes used for signature verification. +type SignBytes struct { + // the sequence number + Sequence uint64 `protobuf:"varint,1,opt,name=sequence,proto3" json:"sequence,omitempty"` + // the proof timestamp + Timestamp uint64 `protobuf:"varint,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + // the public key diversifier + Diversifier string `protobuf:"bytes,3,opt,name=diversifier,proto3" json:"diversifier,omitempty"` + // the standardised path bytes + Path []byte `protobuf:"bytes,4,opt,name=path,proto3" json:"path,omitempty"` + // the marshaled data bytes + Data []byte `protobuf:"bytes,5,opt,name=data,proto3" json:"data,omitempty"` +} + +func (m *SignBytes) Reset() { *m = SignBytes{} } +func (m *SignBytes) String() string { return proto.CompactTextString(m) } +func (*SignBytes) ProtoMessage() {} +func (*SignBytes) Descriptor() ([]byte, []int) { + return fileDescriptor_264187157b9220a4, []int{6} +} +func (m *SignBytes) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *SignBytes) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_SignBytes.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *SignBytes) XXX_Merge(src proto.Message) { + xxx_messageInfo_SignBytes.Merge(m, src) +} +func (m *SignBytes) XXX_Size() int { + return m.Size() +} +func (m *SignBytes) XXX_DiscardUnknown() { + xxx_messageInfo_SignBytes.DiscardUnknown(m) +} + +var xxx_messageInfo_SignBytes proto.InternalMessageInfo + +// HeaderData returns the SignBytes data for update verification. +type HeaderData struct { + // header public key + NewPubKey *types.Any `protobuf:"bytes,1,opt,name=new_pub_key,json=newPubKey,proto3" json:"new_pub_key,omitempty"` + // header diversifier + NewDiversifier string `protobuf:"bytes,2,opt,name=new_diversifier,json=newDiversifier,proto3" json:"new_diversifier,omitempty"` +} + +func (m *HeaderData) Reset() { *m = HeaderData{} } +func (m *HeaderData) String() string { return proto.CompactTextString(m) } +func (*HeaderData) ProtoMessage() {} +func (*HeaderData) Descriptor() ([]byte, []int) { + return fileDescriptor_264187157b9220a4, []int{7} +} +func (m *HeaderData) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *HeaderData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_HeaderData.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *HeaderData) XXX_Merge(src proto.Message) { + xxx_messageInfo_HeaderData.Merge(m, src) +} +func (m *HeaderData) XXX_Size() int { + return m.Size() +} +func (m *HeaderData) XXX_DiscardUnknown() { + xxx_messageInfo_HeaderData.DiscardUnknown(m) +} + +var xxx_messageInfo_HeaderData proto.InternalMessageInfo + +func init() { + proto.RegisterType((*ClientState)(nil), "ibc.lightclients.solomachine.v3.ClientState") + proto.RegisterType((*ConsensusState)(nil), "ibc.lightclients.solomachine.v3.ConsensusState") + proto.RegisterType((*Header)(nil), "ibc.lightclients.solomachine.v3.Header") + proto.RegisterType((*Misbehaviour)(nil), "ibc.lightclients.solomachine.v3.Misbehaviour") + proto.RegisterType((*SignatureAndData)(nil), "ibc.lightclients.solomachine.v3.SignatureAndData") + proto.RegisterType((*TimestampedSignatureData)(nil), "ibc.lightclients.solomachine.v3.TimestampedSignatureData") + proto.RegisterType((*SignBytes)(nil), "ibc.lightclients.solomachine.v3.SignBytes") + proto.RegisterType((*HeaderData)(nil), "ibc.lightclients.solomachine.v3.HeaderData") +} + +func init() { + proto.RegisterFile("ibc/lightclients/solomachine/v3/solomachine.proto", fileDescriptor_264187157b9220a4) +} + +var fileDescriptor_264187157b9220a4 = []byte{ + // 620 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x54, 0xbf, 0x6f, 0xd3, 0x4e, + 0x14, 0xcf, 0xb5, 0xfe, 0x56, 0xcd, 0x39, 0xdf, 0x14, 0x59, 0x1d, 0x42, 0x41, 0x6e, 0x54, 0x09, + 0xd1, 0xa5, 0x76, 0x93, 0x20, 0x86, 0x32, 0xb5, 0x8d, 0x10, 0x12, 0xaa, 0x40, 0x6e, 0x85, 0x10, + 0x4b, 0x74, 0xb6, 0x2f, 0xce, 0x09, 0xfb, 0x2e, 0xf5, 0x9d, 0x13, 0x05, 0xf1, 0x07, 0x20, 0xb1, + 0xb0, 0xb0, 0x33, 0xb1, 0xf2, 0x6f, 0x30, 0x76, 0x64, 0xac, 0x92, 0x7f, 0x04, 0xf9, 0x6c, 0xc7, + 0x8e, 0x9b, 0x26, 0x03, 0xdb, 0x7b, 0xcf, 0xef, 0x7d, 0xee, 0xf3, 0x79, 0x3f, 0x0c, 0x5b, 0xc4, + 0x76, 0x4c, 0x9f, 0x78, 0x03, 0xe1, 0xf8, 0x04, 0x53, 0xc1, 0x4d, 0xce, 0x7c, 0x16, 0x20, 0x67, + 0x40, 0x28, 0x36, 0x47, 0x9d, 0xa2, 0x6b, 0x0c, 0x43, 0x26, 0x98, 0xb6, 0x4f, 0x6c, 0xc7, 0x28, + 0x96, 0x18, 0xc5, 0x9c, 0x51, 0x67, 0x6f, 0xd7, 0x63, 0x1e, 0x93, 0xb9, 0x66, 0x6c, 0x25, 0x65, + 0x7b, 0x0f, 0x3d, 0xc6, 0x3c, 0x1f, 0x9b, 0xd2, 0xb3, 0xa3, 0xbe, 0x89, 0xe8, 0x24, 0xf9, 0x74, + 0xf0, 0x13, 0x40, 0xf5, 0x5c, 0x62, 0x5d, 0x0a, 0x24, 0xb0, 0xb6, 0x07, 0xb7, 0x39, 0xbe, 0x8e, + 0x30, 0x75, 0x70, 0x03, 0x34, 0xc1, 0xa1, 0x62, 0xcd, 0x7d, 0xed, 0x11, 0xac, 0x12, 0xde, 0xeb, + 0x87, 0xec, 0x13, 0xa6, 0x8d, 0x8d, 0x26, 0x38, 0xdc, 0xb6, 0xb6, 0x09, 0x7f, 0x29, 0x7d, 0xed, + 0x3d, 0xdc, 0x71, 0x18, 0xe5, 0x98, 0xf2, 0x88, 0xf7, 0x78, 0x8c, 0xd5, 0xd8, 0x6c, 0x82, 0x43, + 0xb5, 0x6d, 0x1a, 0x6b, 0x48, 0x1b, 0xe7, 0x59, 0x9d, 0xa4, 0x60, 0xd5, 0x9d, 0x05, 0xff, 0x44, + 0xf9, 0xf2, 0x63, 0xbf, 0x72, 0xf0, 0x15, 0xc0, 0xfa, 0x62, 0xa2, 0xd6, 0x81, 0x70, 0x18, 0xd9, + 0x3e, 0x71, 0x7a, 0x1f, 0xf1, 0x44, 0xb2, 0x55, 0xdb, 0xbb, 0x46, 0xa2, 0xd5, 0xc8, 0xb4, 0x1a, + 0xa7, 0x74, 0x62, 0x55, 0x93, 0xbc, 0xd7, 0x78, 0xa2, 0x35, 0xa1, 0xea, 0x92, 0x11, 0x0e, 0x39, + 0xe9, 0x13, 0x1c, 0x4a, 0x19, 0x55, 0xab, 0x18, 0xd2, 0x1e, 0xc3, 0xaa, 0x20, 0x01, 0xe6, 0x02, + 0x05, 0x43, 0xa9, 0x41, 0xb1, 0xf2, 0x40, 0xca, 0xe6, 0x17, 0x80, 0x5b, 0xaf, 0x30, 0x72, 0xcb, + 0xe9, 0xa0, 0x94, 0x1e, 0x7f, 0xe5, 0xc4, 0xa3, 0x48, 0x44, 0x21, 0x96, 0x8f, 0xd5, 0xac, 0x3c, + 0xa0, 0x9d, 0xc0, 0x3a, 0xc5, 0xe3, 0x5e, 0x41, 0xc5, 0xe6, 0x0a, 0x15, 0x35, 0x8a, 0xc7, 0x6f, + 0xe7, 0x42, 0x9e, 0xc2, 0x9d, 0xb8, 0xb6, 0x28, 0x46, 0x91, 0x62, 0x62, 0xc8, 0x6e, 0x1e, 0x4d, + 0x19, 0xdf, 0x02, 0x58, 0xbb, 0x20, 0xdc, 0xc6, 0x03, 0x34, 0x22, 0x2c, 0x0a, 0x57, 0x4e, 0xfa, + 0x1d, 0xfc, 0x7f, 0x4e, 0xb2, 0xc7, 0x68, 0xc2, 0x5c, 0x6d, 0xb7, 0xd6, 0x8e, 0xf2, 0x32, 0xab, + 0x3a, 0xa5, 0x6e, 0x17, 0x09, 0x64, 0xd5, 0xe6, 0x38, 0x6f, 0x68, 0x09, 0x57, 0x8c, 0x59, 0x2a, + 0xf7, 0x9f, 0x70, 0xaf, 0xc6, 0x2c, 0x95, 0xf8, 0x19, 0x3e, 0x28, 0xe7, 0x2d, 0xf6, 0x1f, 0x94, + 0xfb, 0xaf, 0x41, 0x65, 0x88, 0xc4, 0x20, 0x1d, 0x8c, 0xb4, 0xe3, 0x98, 0x8b, 0x04, 0x92, 0xd4, + 0x6a, 0x96, 0xb4, 0x17, 0x67, 0xac, 0x2c, 0x5f, 0x09, 0x0c, 0x1b, 0x57, 0x59, 0x08, 0xbb, 0x73, + 0x22, 0x92, 0xc5, 0x13, 0x58, 0xcf, 0x75, 0x4b, 0xf4, 0x84, 0x4a, 0xde, 0x8d, 0xee, 0x9d, 0x67, + 0x36, 0x96, 0x3f, 0xf3, 0x1d, 0xc0, 0x6a, 0x0c, 0x7e, 0x36, 0x11, 0x98, 0xaf, 0x1c, 0xe2, 0x4a, + 0xb4, 0xf2, 0x1d, 0x6c, 0xde, 0xbd, 0x83, 0xac, 0x39, 0xca, 0x92, 0xe6, 0xfc, 0x97, 0x37, 0x27, + 0xe5, 0x75, 0x0d, 0x61, 0x72, 0x10, 0x52, 0xc9, 0x33, 0xa8, 0xa6, 0x8b, 0xbd, 0xfe, 0x36, 0x93, + 0xad, 0xbe, 0x67, 0xa5, 0x37, 0xee, 0x5f, 0xe9, 0x33, 0xef, 0xf7, 0x54, 0x07, 0x37, 0x53, 0x1d, + 0xdc, 0x4e, 0x75, 0xf0, 0x6d, 0xa6, 0x57, 0x6e, 0x66, 0x7a, 0xe5, 0xcf, 0x4c, 0xaf, 0x7c, 0xb8, + 0xf0, 0x88, 0x18, 0x44, 0xb6, 0xe1, 0xb0, 0xc0, 0x74, 0x18, 0x0f, 0x18, 0x37, 0x89, 0xed, 0x1c, + 0x79, 0xcc, 0x1c, 0xb5, 0x8e, 0xcd, 0x80, 0xb9, 0x91, 0x8f, 0x79, 0xf2, 0xef, 0x3d, 0xca, 0x7e, + 0xbe, 0xc7, 0xcf, 0x8f, 0x0a, 0x4b, 0xf7, 0xa2, 0x60, 0xdb, 0x5b, 0x92, 0x70, 0xe7, 0x6f, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x8f, 0xab, 0xf8, 0x82, 0xb2, 0x05, 0x00, 0x00, +} + +func (m *ClientState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ClientState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ClientState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.ConsensusState != nil { + { + size, err := m.ConsensusState.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintSolomachine(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.IsFrozen { + i-- + if m.IsFrozen { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x10 + } + if m.Sequence != 0 { + i = encodeVarintSolomachine(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *ConsensusState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ConsensusState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ConsensusState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Timestamp != 0 { + i = encodeVarintSolomachine(dAtA, i, uint64(m.Timestamp)) + i-- + dAtA[i] = 0x18 + } + if len(m.Diversifier) > 0 { + i -= len(m.Diversifier) + copy(dAtA[i:], m.Diversifier) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Diversifier))) + i-- + dAtA[i] = 0x12 + } + if m.PublicKey != nil { + { + size, err := m.PublicKey.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintSolomachine(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Header) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Header) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Header) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.NewDiversifier) > 0 { + i -= len(m.NewDiversifier) + copy(dAtA[i:], m.NewDiversifier) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.NewDiversifier))) + i-- + dAtA[i] = 0x22 + } + if m.NewPublicKey != nil { + { + size, err := m.NewPublicKey.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintSolomachine(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if len(m.Signature) > 0 { + i -= len(m.Signature) + copy(dAtA[i:], m.Signature) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Signature))) + i-- + dAtA[i] = 0x12 + } + if m.Timestamp != 0 { + i = encodeVarintSolomachine(dAtA, i, uint64(m.Timestamp)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *Misbehaviour) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Misbehaviour) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Misbehaviour) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.SignatureTwo != nil { + { + size, err := m.SignatureTwo.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintSolomachine(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.SignatureOne != nil { + { + size, err := m.SignatureOne.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintSolomachine(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.Sequence != 0 { + i = encodeVarintSolomachine(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *SignatureAndData) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SignatureAndData) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *SignatureAndData) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Timestamp != 0 { + i = encodeVarintSolomachine(dAtA, i, uint64(m.Timestamp)) + i-- + dAtA[i] = 0x20 + } + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0x1a + } + if len(m.Path) > 0 { + i -= len(m.Path) + copy(dAtA[i:], m.Path) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Path))) + i-- + dAtA[i] = 0x12 + } + if len(m.Signature) > 0 { + i -= len(m.Signature) + copy(dAtA[i:], m.Signature) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Signature))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *TimestampedSignatureData) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TimestampedSignatureData) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TimestampedSignatureData) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Timestamp != 0 { + i = encodeVarintSolomachine(dAtA, i, uint64(m.Timestamp)) + i-- + dAtA[i] = 0x10 + } + if len(m.SignatureData) > 0 { + i -= len(m.SignatureData) + copy(dAtA[i:], m.SignatureData) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.SignatureData))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *SignBytes) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SignBytes) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *SignBytes) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0x2a + } + if len(m.Path) > 0 { + i -= len(m.Path) + copy(dAtA[i:], m.Path) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Path))) + i-- + dAtA[i] = 0x22 + } + if len(m.Diversifier) > 0 { + i -= len(m.Diversifier) + copy(dAtA[i:], m.Diversifier) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.Diversifier))) + i-- + dAtA[i] = 0x1a + } + if m.Timestamp != 0 { + i = encodeVarintSolomachine(dAtA, i, uint64(m.Timestamp)) + i-- + dAtA[i] = 0x10 + } + if m.Sequence != 0 { + i = encodeVarintSolomachine(dAtA, i, uint64(m.Sequence)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *HeaderData) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *HeaderData) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *HeaderData) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.NewDiversifier) > 0 { + i -= len(m.NewDiversifier) + copy(dAtA[i:], m.NewDiversifier) + i = encodeVarintSolomachine(dAtA, i, uint64(len(m.NewDiversifier))) + i-- + dAtA[i] = 0x12 + } + if m.NewPubKey != nil { + { + size, err := m.NewPubKey.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintSolomachine(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintSolomachine(dAtA []byte, offset int, v uint64) int { + offset -= sovSolomachine(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *ClientState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Sequence != 0 { + n += 1 + sovSolomachine(uint64(m.Sequence)) + } + if m.IsFrozen { + n += 2 + } + if m.ConsensusState != nil { + l = m.ConsensusState.Size() + n += 1 + l + sovSolomachine(uint64(l)) + } + return n +} + +func (m *ConsensusState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.PublicKey != nil { + l = m.PublicKey.Size() + n += 1 + l + sovSolomachine(uint64(l)) + } + l = len(m.Diversifier) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + if m.Timestamp != 0 { + n += 1 + sovSolomachine(uint64(m.Timestamp)) + } + return n +} + +func (m *Header) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Timestamp != 0 { + n += 1 + sovSolomachine(uint64(m.Timestamp)) + } + l = len(m.Signature) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + if m.NewPublicKey != nil { + l = m.NewPublicKey.Size() + n += 1 + l + sovSolomachine(uint64(l)) + } + l = len(m.NewDiversifier) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + return n +} + +func (m *Misbehaviour) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Sequence != 0 { + n += 1 + sovSolomachine(uint64(m.Sequence)) + } + if m.SignatureOne != nil { + l = m.SignatureOne.Size() + n += 1 + l + sovSolomachine(uint64(l)) + } + if m.SignatureTwo != nil { + l = m.SignatureTwo.Size() + n += 1 + l + sovSolomachine(uint64(l)) + } + return n +} + +func (m *SignatureAndData) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signature) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + l = len(m.Path) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + l = len(m.Data) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + if m.Timestamp != 0 { + n += 1 + sovSolomachine(uint64(m.Timestamp)) + } + return n +} + +func (m *TimestampedSignatureData) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.SignatureData) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + if m.Timestamp != 0 { + n += 1 + sovSolomachine(uint64(m.Timestamp)) + } + return n +} + +func (m *SignBytes) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Sequence != 0 { + n += 1 + sovSolomachine(uint64(m.Sequence)) + } + if m.Timestamp != 0 { + n += 1 + sovSolomachine(uint64(m.Timestamp)) + } + l = len(m.Diversifier) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + l = len(m.Path) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + l = len(m.Data) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + return n +} + +func (m *HeaderData) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.NewPubKey != nil { + l = m.NewPubKey.Size() + n += 1 + l + sovSolomachine(uint64(l)) + } + l = len(m.NewDiversifier) + if l > 0 { + n += 1 + l + sovSolomachine(uint64(l)) + } + return n +} + +func sovSolomachine(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozSolomachine(x uint64) (n int) { + return sovSolomachine(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *ClientState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ClientState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ClientState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IsFrozen", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.IsFrozen = bool(v != 0) + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ConsensusState", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ConsensusState == nil { + m.ConsensusState = &ConsensusState{} + } + if err := m.ConsensusState.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ConsensusState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ConsensusState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ConsensusState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PublicKey", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.PublicKey == nil { + m.PublicKey = &types.Any{} + } + if err := m.PublicKey.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Diversifier", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Diversifier = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Timestamp |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Header) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Header: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Header: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Timestamp |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signature", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signature = append(m.Signature[:0], dAtA[iNdEx:postIndex]...) + if m.Signature == nil { + m.Signature = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NewPublicKey", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.NewPublicKey == nil { + m.NewPublicKey = &types.Any{} + } + if err := m.NewPublicKey.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NewDiversifier", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NewDiversifier = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Misbehaviour) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Misbehaviour: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Misbehaviour: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SignatureOne", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.SignatureOne == nil { + m.SignatureOne = &SignatureAndData{} + } + if err := m.SignatureOne.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SignatureTwo", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.SignatureTwo == nil { + m.SignatureTwo = &SignatureAndData{} + } + if err := m.SignatureTwo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SignatureAndData) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SignatureAndData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SignatureAndData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signature", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signature = append(m.Signature[:0], dAtA[iNdEx:postIndex]...) + if m.Signature == nil { + m.Signature = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Path", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Path = append(m.Path[:0], dAtA[iNdEx:postIndex]...) + if m.Path == nil { + m.Path = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Timestamp |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TimestampedSignatureData) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TimestampedSignatureData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TimestampedSignatureData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SignatureData", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SignatureData = append(m.SignatureData[:0], dAtA[iNdEx:postIndex]...) + if m.SignatureData == nil { + m.SignatureData = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Timestamp |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SignBytes) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SignBytes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SignBytes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sequence", wireType) + } + m.Sequence = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sequence |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Timestamp |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Diversifier", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Diversifier = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Path", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Path = append(m.Path[:0], dAtA[iNdEx:postIndex]...) + if m.Path == nil { + m.Path = []byte{} + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *HeaderData) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: HeaderData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: HeaderData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NewPubKey", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.NewPubKey == nil { + m.NewPubKey = &types.Any{} + } + if err := m.NewPubKey.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NewDiversifier", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSolomachine + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthSolomachine + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthSolomachine + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NewDiversifier = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipSolomachine(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSolomachine + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipSolomachine(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowSolomachine + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowSolomachine + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowSolomachine + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthSolomachine + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupSolomachine + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthSolomachine + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthSolomachine = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowSolomachine = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupSolomachine = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/light-clients/06-solomachine/solomachine_test.go b/modules/light-clients/06-solomachine/solomachine_test.go new file mode 100644 index 0000000..75d2e62 --- /dev/null +++ b/modules/light-clients/06-solomachine/solomachine_test.go @@ -0,0 +1,207 @@ +package solomachine_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + testifysuite "github.com/stretchr/testify/suite" + + storetypes "cosmossdk.io/store/types" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + "github.com/cosmos/ibc-go/v10/testing/mock" +) + +var channelIDSolomachine = "channel-on-solomachine" // channelID generated on solo machine side + +type SoloMachineTestSuite struct { + testifysuite.Suite + + solomachine *ibctesting.Solomachine // singlesig public key + solomachineMulti *ibctesting.Solomachine // multisig public key + coordinator *ibctesting.Coordinator + + // testing chain used for convenience and readability + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain + + store storetypes.KVStore +} + +func (suite *SoloMachineTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) + + suite.solomachine = ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "06-solomachine-0", "testing", 1) + suite.solomachineMulti = ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "06-solomachine-1", "testing", 4) + + suite.store = suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), exported.Solomachine) +} + +func TestSoloMachineTestSuite(t *testing.T) { + testifysuite.Run(t, new(SoloMachineTestSuite)) +} + +func (suite *SoloMachineTestSuite) SetupSolomachine() string { + clientID := suite.solomachine.CreateClient(suite.chainA) + + connectionID := suite.solomachine.ConnOpenInit(suite.chainA, clientID) + + // open try is not necessary as the solo machine implementation is mocked + + suite.solomachine.ConnOpenAck(suite.chainA, clientID, connectionID) + + // open confirm is not necessary as the solo machine implementation is mocked + + channelID := suite.solomachine.ChanOpenInit(suite.chainA, connectionID) + + // open try is not necessary as the solo machine implementation is mocked + + suite.solomachine.ChanOpenAck(suite.chainA, channelID) + + // open confirm is not necessary as the solo machine implementation is mocked + + return channelID +} + +func (suite *SoloMachineTestSuite) TestRecvPacket() { + channelID := suite.SetupSolomachine() + packet := channeltypes.NewPacket( + mock.MockPacketData, + 1, + transfertypes.PortID, + channelIDSolomachine, + transfertypes.PortID, + channelID, + clienttypes.ZeroHeight(), + uint64(suite.chainA.GetContext().BlockTime().Add(time.Hour).UnixNano()), + ) + + // send packet is not necessary as the solo machine implementation is mocked + + suite.solomachine.RecvPacket(suite.chainA, packet) + + // close init is not necessary as the solomachine implementation is mocked + + suite.solomachine.ChanCloseConfirm(suite.chainA, transfertypes.PortID, channelID) +} + +func (suite *SoloMachineTestSuite) TestAcknowledgePacket() { + channelID := suite.SetupSolomachine() + + packet := suite.solomachine.SendTransfer(suite.chainA, transfertypes.PortID, channelID) + + // recv packet is not necessary as the solo machine implementation is mocked + + suite.solomachine.AcknowledgePacket(suite.chainA, packet) + + // close init is not necessary as the solomachine implementation is mocked + + suite.solomachine.ChanCloseConfirm(suite.chainA, transfertypes.PortID, channelID) +} + +func (suite *SoloMachineTestSuite) TestTimeout() { + channelID := suite.SetupSolomachine() + packet := suite.solomachine.SendTransfer(suite.chainA, transfertypes.PortID, channelID, func(msg *transfertypes.MsgTransfer) { + msg.TimeoutTimestamp = suite.solomachine.Time + 1 + }) + + // simulate solomachine time increment + suite.solomachine.Time++ + + suite.solomachine.UpdateClient(suite.chainA, ibctesting.DefaultSolomachineClientID) + + suite.solomachine.TimeoutPacket(suite.chainA, packet) + + suite.solomachine.ChanCloseConfirm(suite.chainA, transfertypes.PortID, channelID) +} + +func (suite *SoloMachineTestSuite) TestTimeoutOnClose() { + channelID := suite.SetupSolomachine() + + packet := suite.solomachine.SendTransfer(suite.chainA, transfertypes.PortID, channelID) + + suite.solomachine.TimeoutPacketOnClose(suite.chainA, packet, channelID) +} + +func (suite *SoloMachineTestSuite) GetSequenceFromStore() uint64 { + bz := suite.store.Get(host.ClientStateKey()) + suite.Require().NotNil(bz) + + var clientState exported.ClientState + err := suite.chainA.Codec.UnmarshalInterface(bz, &clientState) + suite.Require().NoError(err) + + smClientState, ok := clientState.(*solomachine.ClientState) + suite.Require().True(ok) + + return smClientState.Sequence +} + +func (suite *SoloMachineTestSuite) GetInvalidProof() []byte { + invalidProof, err := suite.chainA.Codec.Marshal(&solomachine.TimestampedSignatureData{Timestamp: suite.solomachine.Time}) + suite.Require().NoError(err) + + return invalidProof +} + +func TestUnpackInterfaces_Header(t *testing.T) { + registry := testdata.NewTestInterfaceRegistry() + cryptocodec.RegisterInterfaces(registry) + + pk := secp256k1.GenPrivKey().PubKey() + protoAny, err := codectypes.NewAnyWithValue(pk) + require.NoError(t, err) + + header := solomachine.Header{ + NewPublicKey: protoAny, + } + bz, err := header.Marshal() + require.NoError(t, err) + + var header2 solomachine.Header + err = header2.Unmarshal(bz) + require.NoError(t, err) + + err = codectypes.UnpackInterfaces(header2, registry) + require.NoError(t, err) + + require.Equal(t, pk, header2.NewPublicKey.GetCachedValue()) +} + +func TestUnpackInterfaces_HeaderData(t *testing.T) { + registry := testdata.NewTestInterfaceRegistry() + cryptocodec.RegisterInterfaces(registry) + + pk := secp256k1.GenPrivKey().PubKey() + protoAny, err := codectypes.NewAnyWithValue(pk) + require.NoError(t, err) + + hd := solomachine.HeaderData{ + NewPubKey: protoAny, + } + bz, err := hd.Marshal() + require.NoError(t, err) + + var hd2 solomachine.HeaderData + err = hd2.Unmarshal(bz) + require.NoError(t, err) + + err = codectypes.UnpackInterfaces(hd2, registry) + require.NoError(t, err) + + require.Equal(t, pk, hd2.NewPubKey.GetCachedValue()) +} diff --git a/modules/light-clients/06-solomachine/store.go b/modules/light-clients/06-solomachine/store.go new file mode 100644 index 0000000..d3f6469 --- /dev/null +++ b/modules/light-clients/06-solomachine/store.go @@ -0,0 +1,30 @@ +package solomachine + +import ( + "fmt" + + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +// getClientState retrieves the client state from the store using the provided KVStore and codec. +// It returns the unmarshaled ClientState and a boolean indicating if the state was found. +func getClientState(store storetypes.KVStore, cdc codec.BinaryCodec) (*ClientState, bool) { + bz := store.Get(host.ClientStateKey()) + if len(bz) == 0 { + return nil, false + } + + clientStateI := clienttypes.MustUnmarshalClientState(cdc, bz) + var clientState *ClientState + clientState, ok := clientStateI.(*ClientState) + if !ok { + panic(fmt.Errorf("cannot convert %T to %T", clientStateI, clientState)) + } + + return clientState, true +} diff --git a/modules/light-clients/06-solomachine/update.go b/modules/light-clients/06-solomachine/update.go new file mode 100644 index 0000000..c34c3e7 --- /dev/null +++ b/modules/light-clients/06-solomachine/update.go @@ -0,0 +1,101 @@ +package solomachine + +import ( + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// VerifyClientMessage introspects the provided ClientMessage and checks its validity +// A Solomachine Header is considered valid if the currently registered public key has signed over the new public key with the correct sequence +// A Solomachine Misbehaviour is considered valid if duplicate signatures of the current public key are found on two different messages at a given sequence +func (cs ClientState) VerifyClientMessage(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, clientMsg exported.ClientMessage) error { + switch msg := clientMsg.(type) { + case *Header: + return cs.verifyHeader(cdc, msg) + case *Misbehaviour: + return cs.verifyMisbehaviour(cdc, msg) + default: + return errorsmod.Wrapf(clienttypes.ErrInvalidClientType, "expected type of %T or %T, got type %T", Header{}, Misbehaviour{}, msg) + } +} + +func (cs ClientState) verifyHeader(cdc codec.BinaryCodec, header *Header) error { + // assert update timestamp is not less than current consensus state timestamp + if header.Timestamp < cs.ConsensusState.Timestamp { + return errorsmod.Wrapf( + clienttypes.ErrInvalidHeader, + "header timestamp is less than to the consensus state timestamp (%d < %d)", header.Timestamp, cs.ConsensusState.Timestamp, + ) + } + + // assert currently registered public key signed over the new public key with correct sequence + headerData := &HeaderData{ + NewPubKey: header.NewPublicKey, + NewDiversifier: header.NewDiversifier, + } + + dataBz, err := cdc.Marshal(headerData) + if err != nil { + return err + } + + signBytes := &SignBytes{ + Sequence: cs.Sequence, + Timestamp: header.Timestamp, + Diversifier: cs.ConsensusState.Diversifier, + Path: []byte(SentinelHeaderPath), + Data: dataBz, + } + + data, err := cdc.Marshal(signBytes) + if err != nil { + return err + } + + sigData, err := UnmarshalSignatureData(cdc, header.Signature) + if err != nil { + return err + } + + publicKey, err := cs.ConsensusState.GetPubKey() + if err != nil { + return err + } + + if err := VerifySignature(publicKey, data, sigData); err != nil { + return errorsmod.Wrap(ErrInvalidHeader, err.Error()) + } + + return nil +} + +// UpdateState updates the consensus state to the new public key and an incremented sequence. +// A list containing the updated consensus height is returned. +// If the provided clientMsg is not of type Header, the handler will no-op and return an empty slice. +func (cs ClientState) UpdateState(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, clientMsg exported.ClientMessage) []exported.Height { + smHeader, ok := clientMsg.(*Header) + if !ok { + // clientMsg is invalid Misbehaviour, no update necessary + return []exported.Height{} + } + + // create new solomachine ConsensusState + consensusState := &ConsensusState{ + PublicKey: smHeader.NewPublicKey, + Diversifier: smHeader.NewDiversifier, + Timestamp: smHeader.Timestamp, + } + + cs.Sequence++ + cs.ConsensusState = consensusState + + setClientState(clientStore, cdc, &cs) + + return []exported.Height{clienttypes.NewHeight(0, cs.Sequence)} +} diff --git a/modules/light-clients/07-tendermint/client_state.go b/modules/light-clients/07-tendermint/client_state.go new file mode 100644 index 0000000..91e5c36 --- /dev/null +++ b/modules/light-clients/07-tendermint/client_state.go @@ -0,0 +1,326 @@ +package tendermint + +import ( + "strings" + "time" + + ics23 "github.com/cosmos/ics23/go" + + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cometbft/cometbft/light" + cmttypes "github.com/cometbft/cometbft/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + commitmenttypesv2 "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types/v2" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var _ exported.ClientState = (*ClientState)(nil) + +// NewClientState creates a new ClientState instance +func NewClientState( + chainID string, trustLevel Fraction, + trustingPeriod, ubdPeriod, maxClockDrift time.Duration, + latestHeight clienttypes.Height, specs []*ics23.ProofSpec, + upgradePath []string, +) *ClientState { + return &ClientState{ + ChainId: chainID, + TrustLevel: trustLevel, + TrustingPeriod: trustingPeriod, + UnbondingPeriod: ubdPeriod, + MaxClockDrift: maxClockDrift, + LatestHeight: latestHeight, + FrozenHeight: clienttypes.ZeroHeight(), + ProofSpecs: specs, + UpgradePath: upgradePath, + } +} + +// GetChainID returns the chain-id +func (cs ClientState) GetChainID() string { + return cs.ChainId +} + +// ClientType is tendermint. +func (ClientState) ClientType() string { + return exported.Tendermint +} + +// getTimestampAtHeight returns the timestamp in nanoseconds of the consensus state at the given height. +func (ClientState) getTimestampAtHeight( + clientStore storetypes.KVStore, + cdc codec.BinaryCodec, + height exported.Height, +) (uint64, error) { + // get consensus state at height from clientStore to check for expiry + consState, found := GetConsensusState(clientStore, cdc, height) + if !found { + return 0, errorsmod.Wrapf(clienttypes.ErrConsensusStateNotFound, "height (%s)", height) + } + return consState.GetTimestamp(), nil +} + +// status returns the status of the tendermint client. +// The client may be: +// - Active: FrozenHeight is zero and client is not expired +// - Frozen: Frozen Height is not zero +// - Expired: the latest consensus state timestamp + trusting period <= current time +// +// A frozen client will become expired, so the Frozen status +// has higher precedence. +func (cs ClientState) status( + ctx sdk.Context, + clientStore storetypes.KVStore, + cdc codec.BinaryCodec, +) exported.Status { + if !cs.FrozenHeight.IsZero() { + return exported.Frozen + } + + // get latest consensus state from clientStore to check for expiry + consState, found := GetConsensusState(clientStore, cdc, cs.LatestHeight) + if !found { + // if the client state does not have an associated consensus state for its latest height + // then it must be expired + return exported.Expired + } + + if cs.IsExpired(consState.Timestamp, ctx.BlockTime()) { + return exported.Expired + } + + return exported.Active +} + +// IsExpired returns whether or not the client has passed the trusting period since the last +// update (in which case no headers are considered valid). +func (cs ClientState) IsExpired(latestTimestamp, now time.Time) bool { + expirationTime := latestTimestamp.Add(cs.TrustingPeriod) + return !expirationTime.After(now) +} + +// Validate performs a basic validation of the client state fields. +func (cs ClientState) Validate() error { + if strings.TrimSpace(cs.ChainId) == "" { + return errorsmod.Wrap(ErrInvalidChainID, "chain id cannot be empty string") + } + + // NOTE: the value of cmttypes.MaxChainIDLen may change in the future. + // If this occurs, the code here must account for potential difference + // between the tendermint version being run by the counterparty chain + // and the tendermint version used by this light client. + // https://github.com/cosmos/ibc-go/issues/177 + if len(cs.ChainId) > cmttypes.MaxChainIDLen { + return errorsmod.Wrapf(ErrInvalidChainID, "chainID is too long; got: %d, max: %d", len(cs.ChainId), cmttypes.MaxChainIDLen) + } + + if err := light.ValidateTrustLevel(cs.TrustLevel.ToTendermint()); err != nil { + return errorsmod.Wrap(ErrInvalidTrustLevel, err.Error()) + } + if cs.TrustingPeriod <= 0 { + return errorsmod.Wrap(ErrInvalidTrustingPeriod, "trusting period must be greater than zero") + } + if cs.UnbondingPeriod <= 0 { + return errorsmod.Wrap(ErrInvalidUnbondingPeriod, "unbonding period must be greater than zero") + } + if cs.MaxClockDrift <= 0 { + return errorsmod.Wrap(ErrInvalidMaxClockDrift, "max clock drift must be greater than zero") + } + + // the latest height revision number must match the chain id revision number + if cs.LatestHeight.RevisionNumber != clienttypes.ParseChainID(cs.ChainId) { + return errorsmod.Wrapf(ErrInvalidHeaderHeight, + "latest height revision number must match chain id revision number (%d != %d)", cs.LatestHeight.RevisionNumber, clienttypes.ParseChainID(cs.ChainId)) + } + if cs.LatestHeight.RevisionHeight == 0 { + return errorsmod.Wrap(ErrInvalidHeaderHeight, "tendermint client's latest height revision height cannot be zero") + } + if cs.TrustingPeriod >= cs.UnbondingPeriod { + return errorsmod.Wrapf( + ErrInvalidTrustingPeriod, + "trusting period (%s) should be < unbonding period (%s)", cs.TrustingPeriod, cs.UnbondingPeriod, + ) + } + + if cs.ProofSpecs == nil { + return errorsmod.Wrap(ErrInvalidProofSpecs, "proof specs cannot be nil for tm client") + } + for i, spec := range cs.ProofSpecs { + if spec == nil { + return errorsmod.Wrapf(ErrInvalidProofSpecs, "proof spec cannot be nil at index: %d", i) + } + } + // UpgradePath may be empty, but if it isn't, each key must be non-empty + for i, k := range cs.UpgradePath { + if strings.TrimSpace(k) == "" { + return errorsmod.Wrapf(clienttypes.ErrInvalidClient, "key in upgrade path at index %d cannot be empty", i) + } + } + + return nil +} + +// ZeroCustomFields returns a ClientState that is a copy of the current ClientState +// with all client customizable fields zeroed out. All chain specific fields must +// remain unchanged. This client state will be used to verify chain upgrades when a +// chain breaks a light client verification parameter such as chainID. +func (cs ClientState) ZeroCustomFields() *ClientState { + // copy over all chain-specified fields + // and leave custom fields empty + return &ClientState{ + ChainId: cs.ChainId, + UnbondingPeriod: cs.UnbondingPeriod, + LatestHeight: cs.LatestHeight, + ProofSpecs: cs.ProofSpecs, + UpgradePath: cs.UpgradePath, + } +} + +// initialize checks that the initial consensus state is an 07-tendermint consensus state and +// sets the client state, consensus state and associated metadata in the provided client store. +func (cs ClientState) initialize(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, consState exported.ConsensusState) error { + consensusState, ok := consState.(*ConsensusState) + if !ok { + return errorsmod.Wrapf(clienttypes.ErrInvalidConsensus, "invalid initial consensus state. expected type: %T, got: %T", + &ConsensusState{}, consState) + } + + setClientState(clientStore, cdc, &cs) + setConsensusState(clientStore, cdc, consensusState, cs.LatestHeight) + setConsensusMetadata(ctx, clientStore, cs.LatestHeight) + + return nil +} + +// verifyMembership is a generic proof verification method which verifies a proof of the existence of a value at a given CommitmentPath at the specified height. +// The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). +// If a zero proof height is passed in, it will fail to retrieve the associated consensus state. +func (cs ClientState) verifyMembership( + ctx sdk.Context, + clientStore storetypes.KVStore, + cdc codec.BinaryCodec, + height exported.Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path exported.Path, + value []byte, +) error { + if cs.LatestHeight.LT(height) { + return errorsmod.Wrapf( + ibcerrors.ErrInvalidHeight, + "client state height < proof height (%d < %d), please ensure the client has been updated", cs.LatestHeight, height, + ) + } + + if err := verifyDelayPeriodPassed(ctx, clientStore, height, delayTimePeriod, delayBlockPeriod); err != nil { + return err + } + + var merkleProof commitmenttypes.MerkleProof + if err := cdc.Unmarshal(proof, &merkleProof); err != nil { + return errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "failed to unmarshal proof into ICS 23 commitment merkle proof") + } + + merklePath, ok := path.(commitmenttypesv2.MerklePath) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", commitmenttypesv2.MerklePath{}, path) + } + + consensusState, found := GetConsensusState(clientStore, cdc, height) + if !found { + return errorsmod.Wrap(clienttypes.ErrConsensusStateNotFound, "please ensure the proof was constructed against a height that exists on the client") + } + + return merkleProof.VerifyMembership(cs.ProofSpecs, consensusState.GetRoot(), merklePath, value) +} + +// verifyNonMembership is a generic proof verification method which verifies the absence of a given CommitmentPath at a specified height. +// The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). +// If a zero proof height is passed in, it will fail to retrieve the associated consensus state. +func (cs ClientState) verifyNonMembership( + ctx sdk.Context, + clientStore storetypes.KVStore, + cdc codec.BinaryCodec, + height exported.Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path exported.Path, +) error { + if cs.LatestHeight.LT(height) { + return errorsmod.Wrapf( + ibcerrors.ErrInvalidHeight, + "client state height < proof height (%d < %d), please ensure the client has been updated", cs.LatestHeight, height, + ) + } + + if err := verifyDelayPeriodPassed(ctx, clientStore, height, delayTimePeriod, delayBlockPeriod); err != nil { + return err + } + + var merkleProof commitmenttypes.MerkleProof + if err := cdc.Unmarshal(proof, &merkleProof); err != nil { + return errorsmod.Wrap(commitmenttypes.ErrInvalidProof, "failed to unmarshal proof into ICS 23 commitment merkle proof") + } + + merklePath, ok := path.(commitmenttypesv2.MerklePath) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", commitmenttypesv2.MerklePath{}, path) + } + + consensusState, found := GetConsensusState(clientStore, cdc, height) + if !found { + return errorsmod.Wrap(clienttypes.ErrConsensusStateNotFound, "please ensure the proof was constructed against a height that exists on the client") + } + + return merkleProof.VerifyNonMembership(cs.ProofSpecs, consensusState.GetRoot(), merklePath) +} + +// verifyDelayPeriodPassed will ensure that at least delayTimePeriod amount of time and delayBlockPeriod number of blocks have passed +// since consensus state was submitted before allowing verification to continue. +func verifyDelayPeriodPassed(ctx sdk.Context, store storetypes.KVStore, proofHeight exported.Height, delayTimePeriod, delayBlockPeriod uint64) error { + if delayTimePeriod != 0 { + // check that executing chain's timestamp has passed consensusState's processed time + delay time period + processedTime, ok := GetProcessedTime(store, proofHeight) + if !ok { + return errorsmod.Wrapf(ErrProcessedTimeNotFound, "processed time not found for height: %s", proofHeight) + } + + currentTimestamp := uint64(ctx.BlockTime().UnixNano()) + validTime := processedTime + delayTimePeriod + + // NOTE: delay time period is inclusive, so if currentTimestamp is validTime, then we return no error + if currentTimestamp < validTime { + return errorsmod.Wrapf(ErrDelayPeriodNotPassed, "cannot verify packet until time: %d, current time: %d", + validTime, currentTimestamp) + } + } + + if delayBlockPeriod != 0 { + // check that executing chain's height has passed consensusState's processed height + delay block period + processedHeight, ok := GetProcessedHeight(store, proofHeight) + if !ok { + return errorsmod.Wrapf(ErrProcessedHeightNotFound, "processed height not found for height: %s", proofHeight) + } + + currentHeight := clienttypes.GetSelfHeight(ctx) + validHeight := clienttypes.NewHeight(processedHeight.GetRevisionNumber(), processedHeight.GetRevisionHeight()+delayBlockPeriod) + + // NOTE: delay block period is inclusive, so if currentHeight is validHeight, then we return no error + if currentHeight.LT(validHeight) { + return errorsmod.Wrapf(ErrDelayPeriodNotPassed, "cannot verify packet until height: %s, current height: %s", + validHeight, currentHeight) + } + } + + return nil +} diff --git a/modules/light-clients/07-tendermint/client_state_test.go b/modules/light-clients/07-tendermint/client_state_test.go new file mode 100644 index 0000000..16bf0d5 --- /dev/null +++ b/modules/light-clients/07-tendermint/client_state_test.go @@ -0,0 +1,134 @@ +package tendermint_test + +import ( + ics23 "github.com/cosmos/ics23/go" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" +) + +const ( + // Do not change the length of these variables + fiftyCharChainID = "12345678901234567890123456789012345678901234567890" + fiftyOneCharChainID = "123456789012345678901234567890123456789012345678901" +) + +var invalidProof = []byte("invalid proof") + +func (suite *TendermintTestSuite) TestValidate() { + testCases := []struct { + name string + clientState *ibctm.ClientState + expErr error + }{ + { + name: "valid client", + clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), + expErr: nil, + }, + { + name: "valid client with nil upgrade path", + clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), nil), + expErr: nil, + }, + { + name: "invalid chainID", + clientState: ibctm.NewClientState(" ", ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), + expErr: ibctm.ErrInvalidChainID, + }, + { + // NOTE: if this test fails, the code must account for the change in chainID length across tendermint versions! + // Do not only fix the test, fix the code! + // https://github.com/cosmos/ibc-go/issues/177 + name: "valid chainID - chainID validation did not fail for chainID of length 50! ", + clientState: ibctm.NewClientState(fiftyCharChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), + expErr: nil, + }, + { + // NOTE: if this test fails, the code must account for the change in chainID length across tendermint versions! + // Do not only fix the test, fix the code! + // https://github.com/cosmos/ibc-go/issues/177 + name: "invalid chainID - chainID validation failed for chainID of length 51! ", + clientState: ibctm.NewClientState(fiftyOneCharChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), + expErr: ibctm.ErrInvalidChainID, + }, + { + name: "invalid trust level", + clientState: ibctm.NewClientState(chainID, ibctm.Fraction{Numerator: 0, Denominator: 1}, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), + expErr: ibctm.ErrInvalidTrustLevel, + }, + { + name: "invalid zero trusting period", + clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, 0, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), + expErr: ibctm.ErrInvalidTrustingPeriod, + }, + { + name: "invalid negative trusting period", + clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, -1, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), + expErr: ibctm.ErrInvalidTrustingPeriod, + }, + { + name: "invalid zero unbonding period", + clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, trustingPeriod, 0, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), + expErr: ibctm.ErrInvalidUnbondingPeriod, + }, + { + name: "invalid negative unbonding period", + clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, trustingPeriod, -1, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), + expErr: ibctm.ErrInvalidUnbondingPeriod, + }, + { + name: "invalid zero max clock drift", + clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, 0, height, commitmenttypes.GetSDKSpecs(), upgradePath), + expErr: ibctm.ErrInvalidMaxClockDrift, + }, + { + name: "invalid negative max clock drift", + clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, -1, height, commitmenttypes.GetSDKSpecs(), upgradePath), + expErr: ibctm.ErrInvalidMaxClockDrift, + }, + { + name: "invalid revision number", + clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.NewHeight(1, 1), commitmenttypes.GetSDKSpecs(), upgradePath), + expErr: ibctm.ErrInvalidHeaderHeight, + }, + { + name: "invalid revision height", + clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, clienttypes.ZeroHeight(), commitmenttypes.GetSDKSpecs(), upgradePath), + expErr: ibctm.ErrInvalidHeaderHeight, + }, + { + name: "trusting period not less than unbonding period", + clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, ubdPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), upgradePath), + expErr: ibctm.ErrInvalidTrustingPeriod, + }, + { + name: "proof specs is nil", + clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, nil, upgradePath), + expErr: ibctm.ErrInvalidProofSpecs, + }, + { + name: "proof specs contains nil", + clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, []*ics23.ProofSpec{ics23.TendermintSpec, nil}, upgradePath), + expErr: ibctm.ErrInvalidProofSpecs, + }, + { + name: "invalid upgrade path", + clientState: ibctm.NewClientState(chainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, height, commitmenttypes.GetSDKSpecs(), invalidUpgradePath), + expErr: clienttypes.ErrInvalidClient, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := tc.clientState.Validate() + + if tc.expErr == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } +} diff --git a/modules/light-clients/07-tendermint/codec.go b/modules/light-clients/07-tendermint/codec.go new file mode 100644 index 0000000..3d70484 --- /dev/null +++ b/modules/light-clients/07-tendermint/codec.go @@ -0,0 +1,28 @@ +package tendermint + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// RegisterInterfaces registers the tendermint concrete client-related +// implementations and interfaces. +func RegisterInterfaces(registry codectypes.InterfaceRegistry) { + registry.RegisterImplementations( + (*exported.ClientState)(nil), + &ClientState{}, + ) + registry.RegisterImplementations( + (*exported.ConsensusState)(nil), + &ConsensusState{}, + ) + registry.RegisterImplementations( + (*exported.ClientMessage)(nil), + &Header{}, + ) + registry.RegisterImplementations( + (*exported.ClientMessage)(nil), + &Misbehaviour{}, + ) +} diff --git a/modules/light-clients/07-tendermint/codec_test.go b/modules/light-clients/07-tendermint/codec_test.go new file mode 100644 index 0000000..7200f2b --- /dev/null +++ b/modules/light-clients/07-tendermint/codec_test.go @@ -0,0 +1,62 @@ +package tendermint_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + + tendermint "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" +) + +func TestCodecTypeRegistration(t *testing.T) { + testCases := []struct { + name string + typeURL string + expError error + }{ + { + "success: ClientState", + sdk.MsgTypeURL(&tendermint.ClientState{}), + nil, + }, + { + "success: ConsensusState", + sdk.MsgTypeURL(&tendermint.ConsensusState{}), + nil, + }, + { + "success: Header", + sdk.MsgTypeURL(&tendermint.Header{}), + nil, + }, + { + "success: Misbehaviour", + sdk.MsgTypeURL(&tendermint.Misbehaviour{}), + nil, + }, + { + "type not registered on codec", + "ibc.invalid.MsgTypeURL", + errors.New("unable to resolve type URL ibc.invalid.MsgTypeURL"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + encodingCfg := moduletestutil.MakeTestEncodingConfig(tendermint.AppModuleBasic{}) + msg, err := encodingCfg.Codec.InterfaceRegistry().Resolve(tc.typeURL) + + if tc.expError == nil { + require.NotNil(t, msg) + require.NoError(t, err) + } else { + require.Nil(t, msg) + require.ErrorContains(t, err, tc.expError.Error()) + } + }) + } +} diff --git a/modules/light-clients/07-tendermint/consensus_state.go b/modules/light-clients/07-tendermint/consensus_state.go new file mode 100644 index 0000000..326bed0 --- /dev/null +++ b/modules/light-clients/07-tendermint/consensus_state.go @@ -0,0 +1,61 @@ +package tendermint + +import ( + "time" + + errorsmod "cosmossdk.io/errors" + + cmtbytes "github.com/cometbft/cometbft/libs/bytes" + cmttypes "github.com/cometbft/cometbft/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var _ exported.ConsensusState = (*ConsensusState)(nil) + +// SentinelRoot is used as a stand-in root value for the consensus state set at the upgrade height +const SentinelRoot = "sentinel_root" + +// NewConsensusState creates a new ConsensusState instance. +func NewConsensusState( + timestamp time.Time, root commitmenttypes.MerkleRoot, nextValsHash cmtbytes.HexBytes, +) *ConsensusState { + return &ConsensusState{ + Timestamp: timestamp, + Root: root, + NextValidatorsHash: nextValsHash, + } +} + +// ClientType returns Tendermint +func (ConsensusState) ClientType() string { + return exported.Tendermint +} + +// GetRoot returns the commitment Root for the specific +func (cs ConsensusState) GetRoot() exported.Root { + return cs.Root +} + +// GetTimestamp returns block time in nanoseconds of the header that created consensus state +func (cs ConsensusState) GetTimestamp() uint64 { + return uint64(cs.Timestamp.UnixNano()) +} + +// ValidateBasic defines a basic validation for the tendermint consensus state. +// NOTE: ProcessedTimestamp may be zero if this is an initial consensus state passed in by relayer +// as opposed to a consensus state constructed by the chain. +func (cs ConsensusState) ValidateBasic() error { + if cs.Root.Empty() { + return errorsmod.Wrap(clienttypes.ErrInvalidConsensus, "root cannot be empty") + } + if err := cmttypes.ValidateHash(cs.NextValidatorsHash); err != nil { + return errorsmod.Wrap(err, "next validators hash is invalid") + } + if cs.Timestamp.Unix() <= 0 { + return errorsmod.Wrap(clienttypes.ErrInvalidConsensus, "timestamp must be a positive Unix time") + } + return nil +} diff --git a/modules/light-clients/07-tendermint/consensus_state_test.go b/modules/light-clients/07-tendermint/consensus_state_test.go new file mode 100644 index 0000000..73e3082 --- /dev/null +++ b/modules/light-clients/07-tendermint/consensus_state_test.go @@ -0,0 +1,88 @@ +package tendermint_test + +import ( + "time" + + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" +) + +func (suite *TendermintTestSuite) TestConsensusStateValidateBasic() { + testCases := []struct { + msg string + consensusState *ibctm.ConsensusState + expectPass bool + }{ + { + "success", + &ibctm.ConsensusState{ + Timestamp: suite.now, + Root: commitmenttypes.NewMerkleRoot([]byte("app_hash")), + NextValidatorsHash: suite.valsHash, + }, + true, + }, + { + "success with sentinel", + &ibctm.ConsensusState{ + Timestamp: suite.now, + Root: commitmenttypes.NewMerkleRoot([]byte(ibctm.SentinelRoot)), + NextValidatorsHash: suite.valsHash, + }, + true, + }, + { + "root is nil", + &ibctm.ConsensusState{ + Timestamp: suite.now, + Root: commitmenttypes.MerkleRoot{}, + NextValidatorsHash: suite.valsHash, + }, + false, + }, + { + "root is empty", + &ibctm.ConsensusState{ + Timestamp: suite.now, + Root: commitmenttypes.MerkleRoot{}, + NextValidatorsHash: suite.valsHash, + }, + false, + }, + { + "nextvalshash is invalid", + &ibctm.ConsensusState{ + Timestamp: suite.now, + Root: commitmenttypes.NewMerkleRoot([]byte("app_hash")), + NextValidatorsHash: []byte("hi"), + }, + false, + }, + + { + "timestamp is zero", + &ibctm.ConsensusState{ + Timestamp: time.Time{}, + Root: commitmenttypes.NewMerkleRoot([]byte("app_hash")), + NextValidatorsHash: suite.valsHash, + }, + false, + }, + } + + for i, tc := range testCases { + suite.Run(tc.msg, func() { + // check just to increase coverage + suite.Require().Equal(exported.Tendermint, tc.consensusState.ClientType()) + suite.Require().Equal(tc.consensusState.GetRoot(), tc.consensusState.Root) + + err := tc.consensusState.ValidateBasic() + if tc.expectPass { + suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.msg) + } else { + suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.msg) + } + }) + } +} diff --git a/modules/light-clients/07-tendermint/doc.go b/modules/light-clients/07-tendermint/doc.go new file mode 100644 index 0000000..cdf9611 --- /dev/null +++ b/modules/light-clients/07-tendermint/doc.go @@ -0,0 +1,10 @@ +/* +Package tendermint implements a concrete LightClientModule, ClientState, ConsensusState, +Header, Misbehaviour and types for the Tendermint consensus light client. +This implementation is based off the ICS 07 specification +(https://github.com/cosmos/ibc/tree/main/spec/client/ics-007-tendermint-client) + +Note that client identifiers are expected to be in the form: 07-tendermint-{N}. +Client identifiers are generated and validated by core IBC, unexpected client identifiers will result in errors. +*/ +package tendermint diff --git a/modules/light-clients/07-tendermint/errors.go b/modules/light-clients/07-tendermint/errors.go new file mode 100644 index 0000000..90f89f3 --- /dev/null +++ b/modules/light-clients/07-tendermint/errors.go @@ -0,0 +1,23 @@ +package tendermint + +import ( + errorsmod "cosmossdk.io/errors" +) + +// IBC tendermint client sentinel errors +var ( + ErrInvalidChainID = errorsmod.Register(ModuleName, 2, "invalid chain-id") + ErrInvalidTrustingPeriod = errorsmod.Register(ModuleName, 3, "invalid trusting period") + ErrInvalidUnbondingPeriod = errorsmod.Register(ModuleName, 4, "invalid unbonding period") + ErrInvalidHeaderHeight = errorsmod.Register(ModuleName, 5, "invalid header height") + ErrInvalidHeader = errorsmod.Register(ModuleName, 6, "invalid header") + ErrInvalidMaxClockDrift = errorsmod.Register(ModuleName, 7, "invalid max clock drift") + ErrProcessedTimeNotFound = errorsmod.Register(ModuleName, 8, "processed time not found") + ErrProcessedHeightNotFound = errorsmod.Register(ModuleName, 9, "processed height not found") + ErrDelayPeriodNotPassed = errorsmod.Register(ModuleName, 10, "packet-specified delay period has not been reached") + ErrTrustingPeriodExpired = errorsmod.Register(ModuleName, 11, "time since latest trusted state has passed the trusting period") + ErrUnbondingPeriodExpired = errorsmod.Register(ModuleName, 12, "time since latest trusted state has passed the unbonding period") + ErrInvalidProofSpecs = errorsmod.Register(ModuleName, 13, "invalid proof specs") + ErrInvalidValidatorSet = errorsmod.Register(ModuleName, 14, "invalid validator set") + ErrInvalidTrustLevel = errorsmod.Register(ModuleName, 15, "invalid trust level") +) diff --git a/modules/light-clients/07-tendermint/fraction.go b/modules/light-clients/07-tendermint/fraction.go new file mode 100644 index 0000000..9e34092 --- /dev/null +++ b/modules/light-clients/07-tendermint/fraction.go @@ -0,0 +1,25 @@ +package tendermint + +import ( + cmtmath "github.com/cometbft/cometbft/libs/math" + "github.com/cometbft/cometbft/light" +) + +// DefaultTrustLevel is the tendermint light client default trust level +var DefaultTrustLevel = NewFractionFromTm(light.DefaultTrustLevel) + +// NewFractionFromTm returns a new Fraction instance from a tmmath.Fraction +func NewFractionFromTm(f cmtmath.Fraction) Fraction { + return Fraction{ + Numerator: f.Numerator, + Denominator: f.Denominator, + } +} + +// ToTendermint converts Fraction to tmmath.Fraction +func (f Fraction) ToTendermint() cmtmath.Fraction { + return cmtmath.Fraction{ + Numerator: f.Numerator, + Denominator: f.Denominator, + } +} diff --git a/modules/light-clients/07-tendermint/header.go b/modules/light-clients/07-tendermint/header.go new file mode 100644 index 0000000..86d0c89 --- /dev/null +++ b/modules/light-clients/07-tendermint/header.go @@ -0,0 +1,83 @@ +package tendermint + +import ( + "bytes" + "time" + + errorsmod "cosmossdk.io/errors" + + cmttypes "github.com/cometbft/cometbft/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var _ exported.ClientMessage = (*Header)(nil) + +// ConsensusState returns the updated consensus state associated with the header +func (h Header) ConsensusState() *ConsensusState { + return &ConsensusState{ + Timestamp: h.GetTime(), + Root: commitmenttypes.NewMerkleRoot(h.Header.GetAppHash()), + NextValidatorsHash: h.Header.NextValidatorsHash, + } +} + +// ClientType defines that the Header is a Tendermint consensus algorithm +func (Header) ClientType() string { + return exported.Tendermint +} + +// GetHeight returns the current height. It returns 0 if the tendermint +// header is nil. +// NOTE: the header.Header is checked to be non nil in ValidateBasic. +func (h Header) GetHeight() exported.Height { + revision := clienttypes.ParseChainID(h.Header.ChainID) + return clienttypes.NewHeight(revision, uint64(h.Header.Height)) +} + +// GetTime returns the current block timestamp. It returns a zero time if +// the tendermint header is nil. +// NOTE: the header.Header is checked to be non nil in ValidateBasic. +func (h Header) GetTime() time.Time { + return h.Header.Time +} + +// ValidateBasic calls the SignedHeader ValidateBasic function and checks +// that validatorsets are not nil. +// NOTE: TrustedHeight and TrustedValidators may be empty when creating client +// with MsgCreateClient +func (h Header) ValidateBasic() error { + if h.SignedHeader == nil { + return errorsmod.Wrap(clienttypes.ErrInvalidHeader, "tendermint signed header cannot be nil") + } + if h.Header == nil { + return errorsmod.Wrap(clienttypes.ErrInvalidHeader, "tendermint header cannot be nil") + } + tmSignedHeader, err := cmttypes.SignedHeaderFromProto(h.SignedHeader) + if err != nil { + return errorsmod.Wrap(err, "header is not a tendermint header") + } + if err := tmSignedHeader.ValidateBasic(h.Header.GetChainID()); err != nil { + return errorsmod.Wrap(err, "header failed basic validation") + } + + // TrustedHeight is less than Header for updates and misbehaviour + if h.TrustedHeight.GTE(h.GetHeight()) { + return errorsmod.Wrapf(ErrInvalidHeaderHeight, "TrustedHeight %d must be less than header height %d", + h.TrustedHeight, h.GetHeight()) + } + + if h.ValidatorSet == nil { + return errorsmod.Wrap(clienttypes.ErrInvalidHeader, "validator set is nil") + } + tmValset, err := cmttypes.ValidatorSetFromProto(h.ValidatorSet) + if err != nil { + return errorsmod.Wrap(err, "validator set is not tendermint validator set") + } + if !bytes.Equal(h.Header.ValidatorsHash, tmValset.Hash()) { + return errorsmod.Wrap(clienttypes.ErrInvalidHeader, "validator set does not match hash") + } + return nil +} diff --git a/modules/light-clients/07-tendermint/header_test.go b/modules/light-clients/07-tendermint/header_test.go new file mode 100644 index 0000000..1b36fbe --- /dev/null +++ b/modules/light-clients/07-tendermint/header_test.go @@ -0,0 +1,82 @@ +package tendermint_test + +import ( + "errors" + "time" + + cmtprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" +) + +func (suite *TendermintTestSuite) TestGetHeight() { + header := suite.chainA.LatestCommittedHeader + suite.Require().NotEqual(uint64(0), header.GetHeight()) +} + +func (suite *TendermintTestSuite) TestGetTime() { + header := suite.chainA.LatestCommittedHeader + suite.Require().NotEqual(time.Time{}, header.GetTime()) +} + +func (suite *TendermintTestSuite) TestHeaderValidateBasic() { + var header *ibctm.Header + testCases := []struct { + name string + malleate func() + expErr error + }{ + {"valid header", func() {}, nil}, + {"header is nil", func() { + header.Header = nil + }, errors.New("tendermint header cannot be nil")}, + {"signed header is nil", func() { + header.SignedHeader = nil + }, errors.New("tendermint signed header cannot be nil")}, + {"SignedHeaderFromProto failed", func() { + header.Commit.Height = -1 + }, errors.New("header is not a tendermint header")}, + {"signed header failed tendermint ValidateBasic", func() { + header = suite.chainA.LatestCommittedHeader + header.Commit = nil + }, errors.New("header failed basic validation")}, + {"trusted height is equal to header height", func() { + var ok bool + header.TrustedHeight, ok = header.GetHeight().(clienttypes.Height) + suite.Require().True(ok) + }, errors.New("invalid header height")}, + {"validator set nil", func() { + header.ValidatorSet = nil + }, errors.New("invalid client header")}, + {"ValidatorSetFromProto failed", func() { + header.ValidatorSet.Validators[0].PubKey = cmtprotocrypto.PublicKey{} + }, errors.New("validator set is not tendermint validator set")}, + {"header validator hash does not equal hash of validator set", func() { + // use chainB's randomly generated validator set + header.ValidatorSet = suite.chainB.LatestCommittedHeader.ValidatorSet + }, errors.New("validator set does not match hash")}, + } + + suite.Require().Equal(exported.Tendermint, suite.header.ClientType()) + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + header = suite.chainA.LatestCommittedHeader // must be explicitly changed in malleate + + tc.malleate() + + err := header.ValidateBasic() + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } +} diff --git a/modules/light-clients/07-tendermint/keys.go b/modules/light-clients/07-tendermint/keys.go new file mode 100644 index 0000000..a469d80 --- /dev/null +++ b/modules/light-clients/07-tendermint/keys.go @@ -0,0 +1,5 @@ +package tendermint + +const ( + ModuleName = "07-tendermint" +) diff --git a/modules/light-clients/07-tendermint/light_client_module.go b/modules/light-clients/07-tendermint/light_client_module.go new file mode 100644 index 0000000..771a967 --- /dev/null +++ b/modules/light-clients/07-tendermint/light_client_module.go @@ -0,0 +1,240 @@ +package tendermint + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var _ exported.LightClientModule = (*LightClientModule)(nil) + +// LightClientModule implements the core IBC api.LightClientModule interface. +type LightClientModule struct { + cdc codec.BinaryCodec + storeProvider clienttypes.StoreProvider +} + +// NewLightClientModule creates and returns a new 07-tendermint LightClientModule. +func NewLightClientModule(cdc codec.BinaryCodec, storeProvider clienttypes.StoreProvider) LightClientModule { + return LightClientModule{ + cdc: cdc, + storeProvider: storeProvider, + } +} + +// Initialize unmarshals the provided client and consensus states and performs basic validation. It calls into the +// clientState.initialize method. +func (l LightClientModule) Initialize(ctx sdk.Context, clientID string, clientStateBz, consensusStateBz []byte) error { + var clientState ClientState + if err := l.cdc.Unmarshal(clientStateBz, &clientState); err != nil { + return fmt.Errorf("failed to unmarshal client state bytes into client state: %w", err) + } + + if err := clientState.Validate(); err != nil { + return err + } + + var consensusState ConsensusState + if err := l.cdc.Unmarshal(consensusStateBz, &consensusState); err != nil { + return fmt.Errorf("failed to unmarshal consensus state bytes into consensus state: %w", err) + } + + if err := consensusState.ValidateBasic(); err != nil { + return err + } + + clientStore := l.storeProvider.ClientStore(ctx, clientID) + + return clientState.initialize(ctx, l.cdc, clientStore, &consensusState) +} + +// VerifyClientMessage obtains the client state associated with the client identifier and calls into the clientState.VerifyClientMessage method. +func (l LightClientModule) VerifyClientMessage(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) error { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + clientState, found := getClientState(clientStore, l.cdc) + if !found { + return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) + } + + return clientState.VerifyClientMessage(ctx, l.cdc, clientStore, clientMsg) +} + +// CheckForMisbehaviour obtains the client state associated with the client identifier and calls into the clientState.CheckForMisbehaviour method. +func (l LightClientModule) CheckForMisbehaviour(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) bool { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + clientState, found := getClientState(clientStore, l.cdc) + if !found { + panic(errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID)) + } + + return clientState.CheckForMisbehaviour(ctx, l.cdc, clientStore, clientMsg) +} + +// UpdateStateOnMisbehaviour obtains the client state associated with the client identifier and calls into the clientState.UpdateStateOnMisbehaviour method. +func (l LightClientModule) UpdateStateOnMisbehaviour(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + clientState, found := getClientState(clientStore, l.cdc) + if !found { + panic(errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID)) + } + + clientState.UpdateStateOnMisbehaviour(ctx, l.cdc, clientStore, clientMsg) +} + +// UpdateState obtains the client state associated with the client identifier and calls into the clientState.UpdateState method. +func (l LightClientModule) UpdateState(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) []exported.Height { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + clientState, found := getClientState(clientStore, l.cdc) + if !found { + panic(errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID)) + } + + return clientState.UpdateState(ctx, l.cdc, clientStore, clientMsg) +} + +// VerifyMembership obtains the client state associated with the client identifier and calls into the clientState.verifyMembership method. +func (l LightClientModule) VerifyMembership( + ctx sdk.Context, + clientID string, + height exported.Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path exported.Path, + value []byte, +) error { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + clientState, found := getClientState(clientStore, l.cdc) + if !found { + return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) + } + + return clientState.verifyMembership(ctx, clientStore, l.cdc, height, delayTimePeriod, delayBlockPeriod, proof, path, value) +} + +// VerifyNonMembership obtains the client state associated with the client identifier and calls into the clientState.verifyNonMembership method. +func (l LightClientModule) VerifyNonMembership( + ctx sdk.Context, + clientID string, + height exported.Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path exported.Path, +) error { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + clientState, found := getClientState(clientStore, l.cdc) + if !found { + return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) + } + + return clientState.verifyNonMembership(ctx, clientStore, l.cdc, height, delayTimePeriod, delayBlockPeriod, proof, path) +} + +// Status obtains the client state associated with the client identifier and calls into the clientState.status method. +func (l LightClientModule) Status(ctx sdk.Context, clientID string) exported.Status { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + clientState, found := getClientState(clientStore, l.cdc) + if !found { + return exported.Unknown + } + + return clientState.status(ctx, clientStore, l.cdc) +} + +// LatestHeight returns the latest height for the client state for the given client identifier. +// If no client is present for the provided client identifier a zero value height is returned. +func (l LightClientModule) LatestHeight(ctx sdk.Context, clientID string) exported.Height { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + clientState, found := getClientState(clientStore, l.cdc) + if !found { + return clienttypes.ZeroHeight() + } + + return clientState.LatestHeight +} + +// TimestampAtHeight obtains the client state associated with the client identifier and calls into the clientState.getTimestampAtHeight method. +func (l LightClientModule) TimestampAtHeight( + ctx sdk.Context, + clientID string, + height exported.Height, +) (uint64, error) { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + clientState, found := getClientState(clientStore, l.cdc) + if !found { + return 0, errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) + } + + return clientState.getTimestampAtHeight(clientStore, l.cdc, height) +} + +// RecoverClient asserts that the substitute client is a tendermint client. It obtains the client state associated with the +// subject client and calls into the subjectClientState.CheckSubstituteAndUpdateState method. +func (l LightClientModule) RecoverClient(ctx sdk.Context, clientID, substituteClientID string) error { + substituteClientType, _, err := clienttypes.ParseClientIdentifier(substituteClientID) + if err != nil { + return err + } + + if substituteClientType != exported.Tendermint { + return errorsmod.Wrapf(clienttypes.ErrInvalidClientType, "expected: %s, got: %s", exported.Tendermint, substituteClientType) + } + + clientStore := l.storeProvider.ClientStore(ctx, clientID) + clientState, found := getClientState(clientStore, l.cdc) + if !found { + return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) + } + + substituteClientStore := l.storeProvider.ClientStore(ctx, substituteClientID) + substituteClient, found := getClientState(substituteClientStore, l.cdc) + if !found { + return errorsmod.Wrap(clienttypes.ErrClientNotFound, substituteClientID) + } + + return clientState.CheckSubstituteAndUpdateState(ctx, l.cdc, clientStore, substituteClientStore, substituteClient) +} + +// VerifyUpgradeAndUpdateState obtains the client state associated with the client identifier and calls into the clientState.VerifyUpgradeAndUpdateState method. +// The new client and consensus states will be unmarshaled and an error is returned if the new client state is not at a height greater +// than the existing client. +func (l LightClientModule) VerifyUpgradeAndUpdateState( + ctx sdk.Context, + clientID string, + newClient []byte, + newConsState []byte, + upgradeClientProof, + upgradeConsensusStateProof []byte, +) error { + var newClientState ClientState + if err := l.cdc.Unmarshal(newClient, &newClientState); err != nil { + return errorsmod.Wrap(clienttypes.ErrInvalidClient, err.Error()) + } + + var newConsensusState ConsensusState + if err := l.cdc.Unmarshal(newConsState, &newConsensusState); err != nil { + return errorsmod.Wrap(clienttypes.ErrInvalidConsensus, err.Error()) + } + + clientStore := l.storeProvider.ClientStore(ctx, clientID) + clientState, found := getClientState(clientStore, l.cdc) + if !found { + return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) + } + + // last height of current counterparty chain must be client's latest height + lastHeight := clientState.LatestHeight + if !newClientState.LatestHeight.GT(lastHeight) { + return errorsmod.Wrapf(ibcerrors.ErrInvalidHeight, "upgraded client height %s must be at greater than current client height %s", newClientState.LatestHeight, lastHeight) + } + + return clientState.VerifyUpgradeAndUpdateState(ctx, l.cdc, clientStore, &newClientState, &newConsensusState, upgradeClientProof, upgradeConsensusStateProof) +} diff --git a/modules/light-clients/07-tendermint/light_client_module_test.go b/modules/light-clients/07-tendermint/light_client_module_test.go new file mode 100644 index 0000000..4601748 --- /dev/null +++ b/modules/light-clients/07-tendermint/light_client_module_test.go @@ -0,0 +1,1187 @@ +package tendermint_test + +import ( + "crypto/sha256" + "errors" + "time" + + errorsmod "cosmossdk.io/errors" + upgradetypes "cosmossdk.io/x/upgrade/types" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + ibcmock "github.com/cosmos/ibc-go/v10/testing/mock" +) + +var ( + tmClientID = clienttypes.FormatClientIdentifier(exported.Tendermint, 100) + solomachineClientID = clienttypes.FormatClientIdentifier(exported.Solomachine, 0) +) + +func (suite *TendermintTestSuite) TestInitialize() { + var consensusState exported.ConsensusState + var clientState exported.ClientState + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "valid consensus & client states", + func() {}, + nil, + }, + { + "invalid client state", + func() { + clientState.(*ibctm.ClientState).ChainId = "" + }, + ibctm.ErrInvalidChainID, + }, + { + "invalid client state: solomachine client state", + func() { + clientState = ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2).ClientState() + }, + errors.New("failed to unmarshal client state bytes into client state"), + }, + { + "invalid consensus: consensus state is solomachine consensus", + func() { + consensusState = ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2).ConsensusState() + }, + errors.New("failed to unmarshal consensus state bytes into consensus state"), + }, + { + "invalid consensus state", + func() { + consensusState = ibctm.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte(ibctm.SentinelRoot)), []byte("invalidNextValsHash")) + }, + errors.New("next validators hash is invalid"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + path := ibctesting.NewPath(suite.chainA, suite.chainB) + + tmConfig, ok := path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig) + suite.Require().True(ok) + + clientState = ibctm.NewClientState( + path.EndpointA.Chain.ChainID, + tmConfig.TrustLevel, tmConfig.TrustingPeriod, tmConfig.UnbondingPeriod, tmConfig.MaxClockDrift, + suite.chainA.LatestCommittedHeader.GetHeight().(clienttypes.Height), commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath, + ) + + consensusState = ibctm.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte(ibctm.SentinelRoot)), suite.chainA.ProposedHeader.ValidatorsHash) + + clientID := suite.chainA.App.GetIBCKeeper().ClientKeeper.GenerateClientIdentifier(suite.chainA.GetContext(), clientState.ClientType()) + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + tc.malleate() + + clientStateBz := suite.chainA.Codec.MustMarshal(clientState) + consStateBz := suite.chainA.Codec.MustMarshal(consensusState) + + err = lightClientModule.Initialize(suite.chainA.GetContext(), path.EndpointA.ClientID, clientStateBz, consStateBz) + + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + + if tc.expErr == nil { + suite.Require().NoError(err, "valid case returned an error") + suite.Require().True(store.Has(host.ClientStateKey())) + suite.Require().True(store.Has(host.ConsensusStateKey(suite.chainB.LatestCommittedHeader.GetHeight()))) + } else { + suite.Require().ErrorContains(err, tc.expErr.Error()) + suite.Require().False(store.Has(host.ClientStateKey())) + suite.Require().False(store.Has(host.ConsensusStateKey(suite.chainB.LatestCommittedHeader.GetHeight()))) + } + }) + } +} + +func (suite *TendermintTestSuite) TestVerifyClientMessage() { + var path *ibctesting.Path + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() {}, + nil, + }, + { + "failure: client state not found", + func() { + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + store.Delete(host.ClientStateKey()) + }, + clienttypes.ErrClientNotFound, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().NoError(err) + + // ensure counterparty state is committed + suite.coordinator.CommitBlock(suite.chainB) + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + header, err := path.EndpointA.Counterparty.Chain.IBCClientHeader(path.EndpointA.Counterparty.Chain.LatestCommittedHeader, trustedHeight) + suite.Require().NoError(err) + + tc.malleate() + + err = lightClientModule.VerifyClientMessage(suite.chainA.GetContext(), path.EndpointA.ClientID, header) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *TendermintTestSuite) TestCheckForMisbehaviourPanicsOnClientStateNotFound() { + suite.SetupTest() + + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().NoError(err) + + // ensure counterparty state is committed + suite.coordinator.CommitBlock(suite.chainB) + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + header, err := path.EndpointA.Counterparty.Chain.IBCClientHeader(path.EndpointA.Counterparty.Chain.LatestCommittedHeader, trustedHeight) + suite.Require().NoError(err) + + // delete client state + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + store.Delete(host.ClientStateKey()) + + suite.Require().PanicsWithError(errorsmod.Wrap(clienttypes.ErrClientNotFound, path.EndpointA.ClientID).Error(), + func() { + lightClientModule.CheckForMisbehaviour(suite.chainA.GetContext(), path.EndpointA.ClientID, header) + }, + ) +} + +func (suite *TendermintTestSuite) TestUpdateStateOnMisbehaviourPanicsOnClientStateNotFound() { + suite.SetupTest() + + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().NoError(err) + + // ensure counterparty state is committed + suite.coordinator.CommitBlock(suite.chainB) + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + header, err := path.EndpointA.Counterparty.Chain.IBCClientHeader(path.EndpointA.Counterparty.Chain.LatestCommittedHeader, trustedHeight) + suite.Require().NoError(err) + + // delete client state + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + store.Delete(host.ClientStateKey()) + + suite.Require().PanicsWithError( + errorsmod.Wrap(clienttypes.ErrClientNotFound, path.EndpointA.ClientID).Error(), + func() { + lightClientModule.UpdateStateOnMisbehaviour(suite.chainA.GetContext(), path.EndpointA.ClientID, header) + }, + ) +} + +func (suite *TendermintTestSuite) TestUpdateStatePanicsOnClientStateNotFound() { + suite.SetupTest() + + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().NoError(err) + + // ensure counterparty state is committed + suite.coordinator.CommitBlock(suite.chainB) + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + header, err := path.EndpointA.Counterparty.Chain.IBCClientHeader(path.EndpointA.Counterparty.Chain.LatestCommittedHeader, trustedHeight) + suite.Require().NoError(err) + + // delete client state + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + store.Delete(host.ClientStateKey()) + + suite.Require().PanicsWithError( + errorsmod.Wrap(clienttypes.ErrClientNotFound, path.EndpointA.ClientID).Error(), + func() { + lightClientModule.UpdateState(suite.chainA.GetContext(), path.EndpointA.ClientID, header) + }, + ) +} + +func (suite *TendermintTestSuite) TestVerifyMembership() { + var ( + testingpath *ibctesting.Path + delayTimePeriod uint64 + delayBlockPeriod uint64 + err error + proofHeight exported.Height + proof []byte + path exported.Path + value []byte + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "successful ClientState verification", + func() { + // default proof construction uses ClientState + }, + nil, + }, + { + "successful ConsensusState verification", func() { + latestHeight := testingpath.EndpointB.GetClientLatestHeight() + + key := host.FullConsensusStateKey(testingpath.EndpointB.ClientID, latestHeight) + merklePath := commitmenttypes.NewMerklePath(key) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = suite.chainB.QueryProof(key) + + consensusState, ok := testingpath.EndpointB.GetConsensusState(latestHeight).(*ibctm.ConsensusState) + suite.Require().True(ok) + value, err = suite.chainB.Codec.MarshalInterface(consensusState) + suite.Require().NoError(err) + }, + nil, + }, + { + "successful Connection verification", func() { + key := host.ConnectionKey(testingpath.EndpointB.ConnectionID) + merklePath := commitmenttypes.NewMerklePath(key) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = suite.chainB.QueryProof(key) + + connection := testingpath.EndpointB.GetConnection() + value, err = suite.chainB.Codec.Marshal(&connection) + suite.Require().NoError(err) + }, + nil, + }, + { + "successful Channel verification", func() { + key := host.ChannelKey(testingpath.EndpointB.ChannelConfig.PortID, testingpath.EndpointB.ChannelID) + merklePath := commitmenttypes.NewMerklePath(key) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = suite.chainB.QueryProof(key) + + channel := testingpath.EndpointB.GetChannel() + value, err = suite.chainB.Codec.Marshal(&channel) + suite.Require().NoError(err) + }, + nil, + }, + { + "successful PacketCommitment verification", func() { + // send from chainB to chainA since we are proving chainB sent a packet + sequence, err := testingpath.EndpointB.SendPacket(clienttypes.NewHeight(1, 100), 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + // make packet commitment proof + packet := channeltypes.NewPacket(ibctesting.MockPacketData, sequence, testingpath.EndpointB.ChannelConfig.PortID, testingpath.EndpointB.ChannelID, testingpath.EndpointA.ChannelConfig.PortID, testingpath.EndpointA.ChannelID, clienttypes.NewHeight(1, 100), 0) + key := host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + merklePath := commitmenttypes.NewMerklePath(key) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = testingpath.EndpointB.QueryProof(key) + + value = channeltypes.CommitPacket(packet) + }, nil, + }, + { + "successful Acknowledgement verification", func() { + // send from chainA to chainB since we are proving chainB wrote an acknowledgement + sequence, err := testingpath.EndpointA.SendPacket(clienttypes.NewHeight(1, 100), 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + // write receipt and ack + packet := channeltypes.NewPacket(ibctesting.MockPacketData, sequence, testingpath.EndpointA.ChannelConfig.PortID, testingpath.EndpointA.ChannelID, testingpath.EndpointB.ChannelConfig.PortID, testingpath.EndpointB.ChannelID, clienttypes.NewHeight(1, 100), 0) + err = testingpath.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + + key := host.PacketAcknowledgementKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + merklePath := commitmenttypes.NewMerklePath(key) + + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = testingpath.EndpointB.QueryProof(key) + + value = channeltypes.CommitAcknowledgement(ibcmock.MockAcknowledgement.Acknowledgement()) + }, + nil, + }, + { + "successful NextSequenceRecv verification", func() { + // send from chainA to chainB since we are proving chainB incremented the sequence recv + + // send packet + sequence, err := testingpath.EndpointA.SendPacket(clienttypes.NewHeight(1, 100), 0, ibctesting.MockPacketData) + suite.Require().NoError(err) + + // next seq recv incremented + packet := channeltypes.NewPacket(ibctesting.MockPacketData, sequence, testingpath.EndpointA.ChannelConfig.PortID, testingpath.EndpointA.ChannelID, testingpath.EndpointB.ChannelConfig.PortID, testingpath.EndpointB.ChannelID, clienttypes.NewHeight(1, 100), 0) + err = testingpath.EndpointB.RecvPacket(packet) + suite.Require().NoError(err) + key := host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) + merklePath := commitmenttypes.NewMerklePath(key) + + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = testingpath.EndpointB.QueryProof(key) + + value = sdk.Uint64ToBigEndian(packet.GetSequence() + 1) + }, + nil, + }, + { + "successful verification outside IBC store", func() { + key := transfertypes.PortKey + merklePath := commitmenttypes.NewMerklePath(key) + path, err = commitmenttypes.ApplyPrefix(commitmenttypes.NewMerklePrefix([]byte(transfertypes.StoreKey)), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = suite.chainB.QueryProofForStore(transfertypes.StoreKey, key, int64(testingpath.EndpointA.GetClientLatestHeight().GetRevisionHeight())) + + value = []byte(suite.chainB.GetSimApp().TransferKeeper.GetPort(suite.chainB.GetContext())) + suite.Require().NoError(err) + }, + nil, + }, + { + "delay time period has passed", func() { + delayTimePeriod = uint64(time.Second.Nanoseconds()) + }, + nil, + }, + { + "delay time period has not passed", func() { + delayTimePeriod = uint64(time.Hour.Nanoseconds()) + }, + ibctm.ErrDelayPeriodNotPassed, + }, + { + "delay block period has passed", func() { + delayBlockPeriod = 1 + }, + nil, + }, + { + "delay block period has not passed", func() { + delayBlockPeriod = 1000 + }, + ibctm.ErrDelayPeriodNotPassed, + }, + { + "latest client height < height", func() { + proofHeight = testingpath.EndpointA.GetClientLatestHeight().Increment() + }, + ibcerrors.ErrInvalidHeight, + }, + { + "invalid path type", + func() { + path = ibcmock.KeyPath{} + }, + ibcerrors.ErrInvalidType, + }, + { + "failed to unmarshal merkle proof", func() { + proof = invalidProof + }, + commitmenttypes.ErrInvalidProof, + }, + { + "consensus state not found", func() { + proofHeight = clienttypes.ZeroHeight() + }, + clienttypes.ErrConsensusStateNotFound, + }, + { + "proof verification failed", func() { + // change the value being proved + value = []byte("invalid value") + }, + commitmenttypes.ErrInvalidProof, + }, + { + "proof is empty", func() { + // change the inserted proof + proof = []byte{} + }, + commitmenttypes.ErrInvalidMerkleProof, + }, + { + "client state not found", + func() { + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), testingpath.EndpointA.ClientID) + store.Delete(host.ClientStateKey()) + }, + clienttypes.ErrClientNotFound, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + testingpath = ibctesting.NewPath(suite.chainA, suite.chainB) + testingpath.SetChannelOrdered() + testingpath.Setup() + + // reset time and block delays to 0, malleate may change to a specific non-zero value. + delayTimePeriod = 0 + delayBlockPeriod = 0 + + // create default proof, merklePath, and value which passes + // may be overwritten by malleate() + key := host.FullClientStateKey(testingpath.EndpointB.ClientID) + merklePath := commitmenttypes.NewMerklePath(key) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = suite.chainB.QueryProof(key) + + clientState, ok := testingpath.EndpointB.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + value, err = suite.chainB.Codec.MarshalInterface(clientState) + suite.Require().NoError(err) + + tc.malleate() // make changes as necessary + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), testingpath.EndpointA.ClientID) + suite.Require().NoError(err) + + err = lightClientModule.VerifyMembership( + suite.chainA.GetContext(), testingpath.EndpointA.ClientID, proofHeight, delayTimePeriod, delayBlockPeriod, + proof, path, value, + ) + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } +} + +func (suite *TendermintTestSuite) TestVerifyNonMembership() { + var ( + testingpath *ibctesting.Path + delayTimePeriod uint64 + delayBlockPeriod uint64 + err error + proofHeight exported.Height + path exported.Path + proof []byte + invalidClientID = "09-tendermint" + invalidConnectionID = "connection-100" + invalidChannelID = "channel-800" + invalidPortID = "invalid-port" + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "successful ClientState verification of non membership", + func() { + // default proof construction uses ClientState + }, + nil, + }, + { + "successful ConsensusState verification of non membership", func() { + key := host.FullConsensusStateKey(invalidClientID, testingpath.EndpointB.GetClientLatestHeight()) + merklePath := commitmenttypes.NewMerklePath(key) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = suite.chainB.QueryProof(key) + }, + nil, + }, + { + "successful Connection verification of non membership", func() { + key := host.ConnectionKey(invalidConnectionID) + merklePath := commitmenttypes.NewMerklePath(key) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = suite.chainB.QueryProof(key) + }, + nil, + }, + { + "successful Channel verification of non membership", func() { + key := host.ChannelKey(testingpath.EndpointB.ChannelConfig.PortID, invalidChannelID) + merklePath := commitmenttypes.NewMerklePath(key) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = suite.chainB.QueryProof(key) + }, + nil, + }, + { + "successful PacketCommitment verification of non membership", func() { + // make packet commitment proof + key := host.PacketCommitmentKey(invalidPortID, invalidChannelID, 1) + merklePath := commitmenttypes.NewMerklePath(key) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = testingpath.EndpointB.QueryProof(key) + }, + nil, + }, + { + "successful Acknowledgement verification of non membership", func() { + key := host.PacketAcknowledgementKey(invalidPortID, invalidChannelID, 1) + merklePath := commitmenttypes.NewMerklePath(key) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = testingpath.EndpointB.QueryProof(key) + }, + nil, + }, + { + "successful NextSequenceRecv verification of non membership", func() { + key := host.NextSequenceRecvKey(invalidPortID, invalidChannelID) + merklePath := commitmenttypes.NewMerklePath(key) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = testingpath.EndpointB.QueryProof(key) + }, + nil, + }, + { + "successful verification of non membership outside IBC store", func() { + key := []byte{0x08} + merklePath := commitmenttypes.NewMerklePath(key) + path, err = commitmenttypes.ApplyPrefix(commitmenttypes.NewMerklePrefix([]byte(transfertypes.StoreKey)), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = suite.chainB.QueryProofForStore(transfertypes.StoreKey, key, int64(testingpath.EndpointA.GetClientLatestHeight().GetRevisionHeight())) + }, + nil, + }, + { + "delay time period has passed", func() { + delayTimePeriod = uint64(time.Second.Nanoseconds()) + }, + nil, + }, + { + "delay time period has not passed", func() { + delayTimePeriod = uint64(time.Hour.Nanoseconds()) + }, + ibctm.ErrDelayPeriodNotPassed, + }, + { + "delay block period has passed", func() { + delayBlockPeriod = 1 + }, + nil, + }, + { + "delay block period has not passed", func() { + delayBlockPeriod = 1000 + }, + ibctm.ErrDelayPeriodNotPassed, + }, + { + "latest client height < height", func() { + proofHeight = testingpath.EndpointA.GetClientLatestHeight().Increment() + }, + ibcerrors.ErrInvalidHeight, + }, + { + "invalid path type", + func() { + path = ibcmock.KeyPath{} + }, + ibcerrors.ErrInvalidType, + }, + { + "failed to unmarshal merkle proof", func() { + proof = invalidProof + }, + commitmenttypes.ErrInvalidProof, + }, + { + "consensus state not found", func() { + proofHeight = clienttypes.ZeroHeight() + }, + clienttypes.ErrConsensusStateNotFound, + }, + { + "verify non membership fails as path exists", func() { + // change the value being proved + key := host.FullClientStateKey(testingpath.EndpointB.ClientID) + merklePath := commitmenttypes.NewMerklePath(key) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = suite.chainB.QueryProof(key) + }, + commitmenttypes.ErrInvalidProof, + }, + { + "proof is empty", func() { + // change the inserted proof + proof = []byte{} + }, + commitmenttypes.ErrInvalidMerkleProof, + }, + { + "client state not found", + func() { + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), testingpath.EndpointA.ClientID) + store.Delete(host.ClientStateKey()) + }, + clienttypes.ErrClientNotFound, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + testingpath = ibctesting.NewPath(suite.chainA, suite.chainB) + testingpath.SetChannelOrdered() + testingpath.Setup() + + // reset time and block delays to 0, malleate may change to a specific non-zero value. + delayTimePeriod = 0 + delayBlockPeriod = 0 + + // create default proof, merklePath, and value which passes + // may be overwritten by malleate() + key := host.FullClientStateKey("invalid-client-id") + + merklePath := commitmenttypes.NewMerklePath(key) + path, err = commitmenttypes.ApplyPrefix(suite.chainB.GetPrefix(), merklePath) + suite.Require().NoError(err) + + proof, proofHeight = suite.chainB.QueryProof(key) + + tc.malleate() // make changes as necessary + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), testingpath.EndpointA.ClientID) + suite.Require().NoError(err) + + err = lightClientModule.VerifyNonMembership( + suite.chainA.GetContext(), testingpath.EndpointA.ClientID, proofHeight, delayTimePeriod, delayBlockPeriod, + proof, path, + ) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } +} + +func (suite *TendermintTestSuite) TestStatus() { + var ( + path *ibctesting.Path + clientState *ibctm.ClientState + ) + + testCases := []struct { + name string + malleate func() + expStatus exported.Status + }{ + { + "client is active", + func() {}, + exported.Active, + }, + { + "client is frozen", + func() { + clientState.FrozenHeight = clienttypes.NewHeight(0, 1) + path.EndpointA.SetClientState(clientState) + }, + exported.Frozen, + }, + { + "client status without consensus state", + func() { + newLatestHeight, ok := clientState.LatestHeight.Increment().(clienttypes.Height) + suite.Require().True(ok) + clientState.LatestHeight = newLatestHeight + path.EndpointA.SetClientState(clientState) + }, + exported.Expired, + }, + { + "client status is expired", + func() { + suite.coordinator.IncrementTimeBy(clientState.TrustingPeriod) + }, + exported.Expired, + }, + { + "client state not found", + func() { + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + store.Delete(host.ClientStateKey()) + }, + exported.Unknown, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().NoError(err) + + var ok bool + clientState, ok = path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + + tc.malleate() + + status := lightClientModule.Status(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().Equal(tc.expStatus, status) + }) + } +} + +func (suite *TendermintTestSuite) TestLatestHeight() { + var ( + path *ibctesting.Path + height exported.Height + ) + + testCases := []struct { + name string + malleate func() + expHeight exported.Height + }{ + { + "success", + func() {}, + clienttypes.Height{RevisionNumber: 0x1, RevisionHeight: 0x4}, + }, + { + "client state not found", + func() { + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + store.Delete(host.ClientStateKey()) + }, + clienttypes.ZeroHeight(), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().NoError(err) + + tc.malleate() + + height = lightClientModule.LatestHeight(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().Equal(tc.expHeight, height) + }) + } +} + +func (suite *TendermintTestSuite) TestGetTimestampAtHeight() { + var ( + path *ibctesting.Path + height exported.Height + ) + expectedTimestamp := time.Unix(1, 0) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() {}, + nil, + }, + { + "failure: client state not found", + func() { + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + store.Delete(host.ClientStateKey()) + }, + clienttypes.ErrClientNotFound, + }, + { + "failure: consensus state not found for height", + func() { + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + height = clientState.LatestHeight.Increment() + }, + clienttypes.ErrConsensusStateNotFound, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + height = clientState.LatestHeight + + // grab consensusState from store and update with a predefined timestamp + consensusState := path.EndpointA.GetConsensusState(height) + tmConsensusState, ok := consensusState.(*ibctm.ConsensusState) + suite.Require().True(ok) + + tmConsensusState.Timestamp = expectedTimestamp + path.EndpointA.SetConsensusState(tmConsensusState, height) + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().NoError(err) + + tc.malleate() + + timestamp, err := lightClientModule.TimestampAtHeight(suite.chainA.GetContext(), path.EndpointA.ClientID, height) + + if tc.expErr == nil { + suite.Require().NoError(err) + + expectedTimestamp := uint64(expectedTimestamp.UnixNano()) + suite.Require().Equal(expectedTimestamp, timestamp) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *TendermintTestSuite) TestRecoverClient() { + var ( + subjectClientID, substituteClientID string + subjectClientState exported.ClientState + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() { + }, + nil, + }, + { + "cannot parse malformed substitute client ID", + func() { + substituteClientID = ibctesting.InvalidID + }, + host.ErrInvalidID, + }, + { + "substitute client ID does not contain 07-tendermint prefix", + func() { + substituteClientID = solomachineClientID + }, + clienttypes.ErrInvalidClientType, + }, + { + "cannot find subject client state", + func() { + subjectClientID = tmClientID + }, + clienttypes.ErrClientNotFound, + }, + { + "cannot find substitute client state", + func() { + substituteClientID = tmClientID + }, + clienttypes.ErrClientNotFound, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + ctx := suite.chainA.GetContext() + + subjectPath := ibctesting.NewPath(suite.chainA, suite.chainB) + subjectPath.SetupClients() + subjectClientID = subjectPath.EndpointA.ClientID + subjectClientState = suite.chainA.GetClientState(subjectClientID) + + substitutePath := ibctesting.NewPath(suite.chainA, suite.chainB) + substitutePath.SetupClients() + substituteClientID = substitutePath.EndpointA.ClientID + + tmClientState, ok := subjectClientState.(*ibctm.ClientState) + suite.Require().True(ok) + tmClientState.FrozenHeight = tmClientState.LatestHeight + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(ctx, subjectPath.EndpointA.ClientID, tmClientState) + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), subjectClientID) + suite.Require().NoError(err) + + tc.malleate() + + err = lightClientModule.RecoverClient(ctx, subjectClientID, substituteClientID) + + if tc.expErr == nil { + suite.Require().NoError(err) + + // assert that status of subject client is now Active + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), subjectClientID) + suite.Require().NoError(err) + suite.Require().Equal(lightClientModule.Status(suite.chainA.GetContext(), subjectClientID), exported.Active) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *TendermintTestSuite) TestVerifyUpgradeAndUpdateState() { + var ( + clientID string + path *ibctesting.Path + upgradedClientState exported.ClientState + upgradedClientStateAny, upgradedConsensusStateAny *codectypes.Any + upgradedClientStateProof, upgradedConsensusStateProof []byte + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() { + // upgrade height is at next block + upgradeHeight := clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + + // zero custom fields and store in upgrade store + zeroedUpgradedClient := upgradedClientState.(*ibctm.ClientState).ZeroCustomFields() + zeroedUpgradedClientAny, err := codectypes.NewAnyWithValue(zeroedUpgradedClient) + suite.Require().NoError(err) + + err = suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(upgradeHeight.GetRevisionHeight()), suite.chainB.Codec.MustMarshal(zeroedUpgradedClientAny)) + suite.Require().NoError(err) + + err = suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(upgradeHeight.GetRevisionHeight()), suite.chainB.Codec.MustMarshal(upgradedConsensusStateAny)) + suite.Require().NoError(err) + + // commit upgrade store changes and update clients + suite.coordinator.CommitBlock(suite.chainB) + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + upgradedClientStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(upgradeHeight.GetRevisionHeight())), path.EndpointA.GetClientLatestHeight().GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(upgradeHeight.GetRevisionHeight())), path.EndpointA.GetClientLatestHeight().GetRevisionHeight()) + }, + nil, + }, + { + "cannot find client state", + func() { + clientID = tmClientID + }, + clienttypes.ErrClientNotFound, + }, + { + "upgraded client state is not for tendermint client state", + func() { + upgradedClientStateAny = &codectypes.Any{ + Value: []byte("invalid client state bytes"), + } + }, + clienttypes.ErrInvalidClient, + }, + { + "upgraded consensus state is not tendermint consensus state", + func() { + upgradedConsensusStateAny = &codectypes.Any{ + Value: []byte("invalid consensus state bytes"), + } + }, + clienttypes.ErrInvalidConsensus, + }, + { + "upgraded client state height is not greater than current height", + func() { + // upgrade height is at next block + upgradeHeight := clienttypes.NewHeight(1, uint64(suite.chainB.GetContext().BlockHeight()+1)) + + // zero custom fields and store in upgrade store + zeroedUpgradedClient := upgradedClientState.(*ibctm.ClientState).ZeroCustomFields() + zeroedUpgradedClientAny, err := codectypes.NewAnyWithValue(zeroedUpgradedClient) + suite.Require().NoError(err) + + err = suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(upgradeHeight.GetRevisionHeight()), suite.chainB.Codec.MustMarshal(zeroedUpgradedClientAny)) + suite.Require().NoError(err) + + err = suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(upgradeHeight.GetRevisionHeight()), suite.chainB.Codec.MustMarshal(upgradedConsensusStateAny)) + suite.Require().NoError(err) + + // change upgraded client state height to be lower than current client state height + tmClient, ok := upgradedClientState.(*ibctm.ClientState) + suite.Require().True(ok) + + newLatestheight, ok := path.EndpointA.GetClientLatestHeight().Decrement() + suite.Require().True(ok) + + tmClient.LatestHeight, ok = newLatestheight.(clienttypes.Height) + suite.Require().True(ok) + upgradedClientStateAny, err = codectypes.NewAnyWithValue(tmClient) + suite.Require().NoError(err) + + suite.coordinator.CommitBlock(suite.chainB) + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + upgradedClientStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(upgradeHeight.GetRevisionHeight())), path.EndpointA.GetClientLatestHeight().GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(upgradeHeight.GetRevisionHeight())), path.EndpointA.GetClientLatestHeight().GetRevisionHeight()) + }, + ibcerrors.ErrInvalidHeight, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + path = ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + clientID = path.EndpointA.ClientID + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + revisionNumber := clienttypes.ParseChainID(clientState.ChainId) + + newUnbondindPeriod := ubdPeriod + trustingPeriod + newChainID, err := clienttypes.SetRevisionNumber(clientState.ChainId, revisionNumber+1) + suite.Require().NoError(err) + + upgradedClientState = ibctm.NewClientState(newChainID, ibctm.DefaultTrustLevel, trustingPeriod, newUnbondindPeriod, maxClockDrift, clienttypes.NewHeight(revisionNumber+1, clientState.LatestHeight.GetRevisionHeight()+1), commitmenttypes.GetSDKSpecs(), upgradePath) + upgradedClientStateAny, err = codectypes.NewAnyWithValue(upgradedClientState) + suite.Require().NoError(err) + + nextValsHash := sha256.Sum256([]byte("new-nextValsHash")) + upgradedConsensusState := ibctm.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte("new-hash")), nextValsHash[:]) + + upgradedConsensusStateAny, err = codectypes.NewAnyWithValue(upgradedConsensusState) + suite.Require().NoError(err) + + tc.malleate() + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + err = lightClientModule.VerifyUpgradeAndUpdateState( + suite.chainA.GetContext(), + clientID, + upgradedClientStateAny.Value, + upgradedConsensusStateAny.Value, + upgradedClientStateProof, + upgradedConsensusStateProof, + ) + + if tc.expErr == nil { + suite.Require().NoError(err) + + expClientState := path.EndpointA.GetClientState() + expClientStateBz := suite.chainA.Codec.MustMarshal(expClientState) + suite.Require().Equal(upgradedClientStateAny.Value, expClientStateBz) + + expConsensusState := ibctm.NewConsensusState(upgradedConsensusState.Timestamp, commitmenttypes.NewMerkleRoot([]byte(ibctm.SentinelRoot)), upgradedConsensusState.NextValidatorsHash) + expConsensusStateBz := suite.chainA.Codec.MustMarshal(expConsensusState) + + consensusStateBz := suite.chainA.Codec.MustMarshal(path.EndpointA.GetConsensusState(path.EndpointA.GetClientLatestHeight())) + suite.Require().Equal(expConsensusStateBz, consensusStateBz) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} diff --git a/modules/light-clients/07-tendermint/migrations/expected_keepers.go b/modules/light-clients/07-tendermint/migrations/expected_keepers.go new file mode 100644 index 0000000..93ffbe5 --- /dev/null +++ b/modules/light-clients/07-tendermint/migrations/expected_keepers.go @@ -0,0 +1,18 @@ +package migrations + +import ( + "cosmossdk.io/log" + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// ClientKeeper expected account IBC client keeper +type ClientKeeper interface { + GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool) + IterateClientStates(ctx sdk.Context, prefix []byte, cb func(string, exported.ClientState) bool) + ClientStore(ctx sdk.Context, clientID string) storetypes.KVStore + Logger(ctx sdk.Context) log.Logger +} diff --git a/modules/light-clients/07-tendermint/migrations/migrations.go b/modules/light-clients/07-tendermint/migrations/migrations.go new file mode 100644 index 0000000..848b4c1 --- /dev/null +++ b/modules/light-clients/07-tendermint/migrations/migrations.go @@ -0,0 +1,46 @@ +package migrations + +import ( + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" +) + +// PruneExpiredConsensusStates prunes all expired tendermint consensus states. This function +// may optionally be called during in-place store migrations. The ibc store key must be provided. +func PruneExpiredConsensusStates(ctx sdk.Context, cdc codec.BinaryCodec, clientKeeper ClientKeeper) (int, error) { + var clientIDs []string + clientKeeper.IterateClientStates(ctx, []byte(exported.Tendermint), func(clientID string, _ exported.ClientState) bool { + clientIDs = append(clientIDs, clientID) + return false + }) + + // keep track of the total consensus states pruned so chains can + // understand how much space is saved when the migration is run + var totalPruned int + + for _, clientID := range clientIDs { + clientStore := clientKeeper.ClientStore(ctx, clientID) + + clientState, ok := clientKeeper.GetClientState(ctx, clientID) + if !ok { + return 0, errorsmod.Wrapf(clienttypes.ErrClientNotFound, "clientID %s", clientID) + } + + tmClientState, ok := clientState.(*ibctm.ClientState) + if !ok { + return 0, errorsmod.Wrap(clienttypes.ErrInvalidClient, "client state is not tendermint even though client id contains 07-tendermint") + } + + totalPruned += ibctm.PruneAllExpiredConsensusStates(ctx, clientStore, cdc, tmClientState) + } + + clientKeeper.Logger(ctx).Info("pruned expired tendermint consensus states", "total", totalPruned) + + return totalPruned, nil +} diff --git a/modules/light-clients/07-tendermint/migrations/migrations_test.go b/modules/light-clients/07-tendermint/migrations/migrations_test.go new file mode 100644 index 0000000..648e9e2 --- /dev/null +++ b/modules/light-clients/07-tendermint/migrations/migrations_test.go @@ -0,0 +1,178 @@ +package migrations_test + +import ( + "testing" + "time" + + testifysuite "github.com/stretchr/testify/suite" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctmmigrations "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint/migrations" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +type MigrationsTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains used for convenience and readability + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain +} + +func (suite *MigrationsTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) +} + +func TestTendermintTestSuite(t *testing.T) { + testifysuite.Run(t, new(MigrationsTestSuite)) +} + +// test pruning of multiple expired tendermint consensus states +func (suite *MigrationsTestSuite) TestPruneExpiredConsensusStates() { + // create multiple tendermint clients and a solo machine client + // the solo machine is used to verify this pruning function only modifies + // the tendermint store. + + numTMClients := 3 + paths := make([]*ibctesting.Path, numTMClients) + + for i := range numTMClients { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + paths[i] = path + } + + solomachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, ibctesting.DefaultSolomachineClientID, "testing", 1) + smClientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), solomachine.ClientID) + + // set client state + bz, err := suite.chainA.App.AppCodec().MarshalInterface(solomachine.ClientState()) + suite.Require().NoError(err) + smClientStore.Set(host.ClientStateKey(), bz) + + bz, err = suite.chainA.App.AppCodec().MarshalInterface(solomachine.ConsensusState()) + suite.Require().NoError(err) + smHeight := clienttypes.NewHeight(0, 1) + smClientStore.Set(host.ConsensusStateKey(smHeight), bz) + + pruneHeightMap := make(map[*ibctesting.Path][]exported.Height) + unexpiredHeightMap := make(map[*ibctesting.Path][]exported.Height) + + for _, path := range paths { + // collect all heights expected to be pruned + var pruneHeights []exported.Height + pruneHeights = append(pruneHeights, path.EndpointA.GetClientLatestHeight()) + + // these heights will be expired and also pruned + for range 3 { + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + pruneHeights = append(pruneHeights, path.EndpointA.GetClientLatestHeight()) + } + + // double chedck all information is currently stored + for _, pruneHeight := range pruneHeights { + consState, ok := suite.chainA.GetConsensusState(path.EndpointA.ClientID, pruneHeight) + suite.Require().True(ok) + suite.Require().NotNil(consState) + + ctx := suite.chainA.GetContext() + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, path.EndpointA.ClientID) + + processedTime, ok := ibctm.GetProcessedTime(clientStore, pruneHeight) + suite.Require().True(ok) + suite.Require().NotNil(processedTime) + + processedHeight, ok := ibctm.GetProcessedHeight(clientStore, pruneHeight) + suite.Require().True(ok) + suite.Require().NotNil(processedHeight) + + expectedConsKey := ibctm.GetIterationKey(clientStore, pruneHeight) + suite.Require().NotNil(expectedConsKey) + } + pruneHeightMap[path] = pruneHeights + } + + // Increment the time by a week + suite.coordinator.IncrementTimeBy(7 * 24 * time.Hour) + + for _, path := range paths { + // create the consensus state that can be used as trusted height for next update + var unexpiredHeights []exported.Height + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + unexpiredHeights = append(unexpiredHeights, path.EndpointA.GetClientLatestHeight()) + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + unexpiredHeights = append(unexpiredHeights, path.EndpointA.GetClientLatestHeight()) + + unexpiredHeightMap[path] = unexpiredHeights + } + + // Increment the time by another week, then update the client. + // This will cause the consensus states created before the first time increment + // to be expired + suite.coordinator.IncrementTimeBy(7 * 24 * time.Hour) + totalPruned, err := ibctmmigrations.PruneExpiredConsensusStates(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), suite.chainA.GetSimApp().IBCKeeper.ClientKeeper) + suite.Require().NoError(err) + suite.Require().NotZero(totalPruned) + + for _, path := range paths { + ctx := suite.chainA.GetContext() + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, path.EndpointA.ClientID) + + // ensure everything has been pruned + for i, pruneHeight := range pruneHeightMap[path] { + consState, ok := suite.chainA.GetConsensusState(path.EndpointA.ClientID, pruneHeight) + suite.Require().False(ok, i) + suite.Require().Nil(consState, i) + + processedTime, ok := ibctm.GetProcessedTime(clientStore, pruneHeight) + suite.Require().False(ok, i) + suite.Require().Equal(uint64(0), processedTime, i) + + processedHeight, ok := ibctm.GetProcessedHeight(clientStore, pruneHeight) + suite.Require().False(ok, i) + suite.Require().Nil(processedHeight, i) + + expectedConsKey := ibctm.GetIterationKey(clientStore, pruneHeight) + suite.Require().Nil(expectedConsKey, i) + } + + // ensure metadata is set for unexpired consensus state + for _, height := range unexpiredHeightMap[path] { + consState, ok := suite.chainA.GetConsensusState(path.EndpointA.ClientID, height) + suite.Require().True(ok) + suite.Require().NotNil(consState) + + processedTime, ok := ibctm.GetProcessedTime(clientStore, height) + suite.Require().True(ok) + suite.Require().NotEqual(uint64(0), processedTime) + + processedHeight, ok := ibctm.GetProcessedHeight(clientStore, height) + suite.Require().True(ok) + suite.Require().NotEqual(clienttypes.ZeroHeight(), processedHeight) + + consKey := ibctm.GetIterationKey(clientStore, height) + suite.Require().Equal(host.ConsensusStateKey(height), consKey) + } + } + + // verify that solomachine client and consensus state were not removed + smClientStore = suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), solomachine.ClientID) + bz = smClientStore.Get(host.ClientStateKey()) + suite.Require().NotEmpty(bz) + + bz = smClientStore.Get(host.ConsensusStateKey(smHeight)) + suite.Require().NotEmpty(bz) +} diff --git a/modules/light-clients/07-tendermint/misbehaviour.go b/modules/light-clients/07-tendermint/misbehaviour.go new file mode 100644 index 0000000..e663c63 --- /dev/null +++ b/modules/light-clients/07-tendermint/misbehaviour.go @@ -0,0 +1,125 @@ +package tendermint + +import ( + "time" + + errorsmod "cosmossdk.io/errors" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + cmttypes "github.com/cometbft/cometbft/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var _ exported.ClientMessage = (*Misbehaviour)(nil) + +// FrozenHeight is same for all misbehaviour +var FrozenHeight = clienttypes.NewHeight(0, 1) + +// NewMisbehaviour creates a new Misbehaviour instance. +func NewMisbehaviour(clientID string, header1, header2 *Header) *Misbehaviour { + return &Misbehaviour{ + ClientId: clientID, + Header1: header1, + Header2: header2, + } +} + +// ClientType is Tendermint light client +func (Misbehaviour) ClientType() string { + return exported.Tendermint +} + +// GetTime returns the timestamp at which misbehaviour occurred. It uses the +// maximum value from both headers to prevent producing an invalid header outside +// of the misbehaviour age range. +func (misbehaviour Misbehaviour) GetTime() time.Time { + t1, t2 := misbehaviour.Header1.GetTime(), misbehaviour.Header2.GetTime() + if t1.After(t2) { + return t1 + } + return t2 +} + +// ValidateBasic implements Misbehaviour interface +func (misbehaviour Misbehaviour) ValidateBasic() error { + if misbehaviour.Header1 == nil { + return errorsmod.Wrap(ErrInvalidHeader, "misbehaviour Header1 cannot be nil") + } + if misbehaviour.Header2 == nil { + return errorsmod.Wrap(ErrInvalidHeader, "misbehaviour Header2 cannot be nil") + } + if misbehaviour.Header1.TrustedHeight.RevisionHeight == 0 { + return errorsmod.Wrapf(ErrInvalidHeaderHeight, "misbehaviour Header1 cannot have zero revision height") + } + if misbehaviour.Header2.TrustedHeight.RevisionHeight == 0 { + return errorsmod.Wrapf(ErrInvalidHeaderHeight, "misbehaviour Header2 cannot have zero revision height") + } + if misbehaviour.Header1.TrustedValidators == nil { + return errorsmod.Wrap(ErrInvalidValidatorSet, "trusted validator set in Header1 cannot be empty") + } + if misbehaviour.Header2.TrustedValidators == nil { + return errorsmod.Wrap(ErrInvalidValidatorSet, "trusted validator set in Header2 cannot be empty") + } + if misbehaviour.Header1.Header.ChainID != misbehaviour.Header2.Header.ChainID { + return errorsmod.Wrap(clienttypes.ErrInvalidMisbehaviour, "headers must have identical chainIDs") + } + + if err := host.ClientIdentifierValidator(misbehaviour.ClientId); err != nil { + return errorsmod.Wrap(err, "misbehaviour client ID is invalid") + } + + // ValidateBasic on both validators + if err := misbehaviour.Header1.ValidateBasic(); err != nil { + return errorsmod.Wrap( + clienttypes.ErrInvalidMisbehaviour, + errorsmod.Wrap(err, "header 1 failed validation").Error(), + ) + } + if err := misbehaviour.Header2.ValidateBasic(); err != nil { + return errorsmod.Wrap( + clienttypes.ErrInvalidMisbehaviour, + errorsmod.Wrap(err, "header 2 failed validation").Error(), + ) + } + // Ensure that Height1 is greater than or equal to Height2 + if misbehaviour.Header1.GetHeight().LT(misbehaviour.Header2.GetHeight()) { + return errorsmod.Wrapf(clienttypes.ErrInvalidMisbehaviour, "Header1 height is less than Header2 height (%s < %s)", misbehaviour.Header1.GetHeight(), misbehaviour.Header2.GetHeight()) + } + + blockID1, err := cmttypes.BlockIDFromProto(&misbehaviour.Header1.Commit.BlockID) + if err != nil { + return errorsmod.Wrap(err, "invalid block ID from header 1 in misbehaviour") + } + blockID2, err := cmttypes.BlockIDFromProto(&misbehaviour.Header2.Commit.BlockID) + if err != nil { + return errorsmod.Wrap(err, "invalid block ID from header 2 in misbehaviour") + } + + if err := validCommit(misbehaviour.Header1.Header.ChainID, *blockID1, + misbehaviour.Header1.Commit, misbehaviour.Header1.ValidatorSet); err != nil { + return err + } + return validCommit(misbehaviour.Header2.Header.ChainID, *blockID2, + misbehaviour.Header2.Commit, misbehaviour.Header2.ValidatorSet) +} + +// validCommit checks if the given commit is a valid commit from the passed-in validatorset +func validCommit(chainID string, blockID cmttypes.BlockID, commit *cmtproto.Commit, valSet *cmtproto.ValidatorSet) (err error) { + tmCommit, err := cmttypes.CommitFromProto(commit) + if err != nil { + return errorsmod.Wrap(err, "commit is not tendermint commit type") + } + tmValset, err := cmttypes.ValidatorSetFromProto(valSet) + if err != nil { + return errorsmod.Wrap(err, "validator set is not tendermint validator set type") + } + + if err := tmValset.VerifyCommitLight(chainID, blockID, tmCommit.Height, tmCommit); err != nil { + return errorsmod.Wrap(clienttypes.ErrInvalidMisbehaviour, "validator set did not commit to header") + } + + return nil +} diff --git a/modules/light-clients/07-tendermint/misbehaviour_handle.go b/modules/light-clients/07-tendermint/misbehaviour_handle.go new file mode 100644 index 0000000..8bdd485 --- /dev/null +++ b/modules/light-clients/07-tendermint/misbehaviour_handle.go @@ -0,0 +1,172 @@ +package tendermint + +import ( + "bytes" + "reflect" + "time" + + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + cmttypes "github.com/cometbft/cometbft/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// CheckForMisbehaviour detects duplicate height misbehaviour and BFT time violation misbehaviour +// in a submitted Header message and verifies the correctness of a submitted Misbehaviour ClientMessage +func (ClientState) CheckForMisbehaviour(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, msg exported.ClientMessage) bool { + switch msg := msg.(type) { + case *Header: + tmHeader := msg + consState := tmHeader.ConsensusState() + + // Check if the Client store already has a consensus state for the header's height + // If the consensus state exists, and it matches the header then we return early + // since header has already been submitted in a previous UpdateClient. + if existingConsState, found := GetConsensusState(clientStore, cdc, tmHeader.GetHeight()); found { + // This header has already been submitted and the necessary state is already stored + // in client store, thus we can return early without further validation. + if reflect.DeepEqual(existingConsState, tmHeader.ConsensusState()) { //nolint:gosimple + return false + } + + // A consensus state already exists for this height, but it does not match the provided header. + // The assumption is that Header has already been validated. Thus we can return true as misbehaviour is present + return true + } + + // Check that consensus state timestamps are monotonic + prevCons, prevOk := GetPreviousConsensusState(clientStore, cdc, tmHeader.GetHeight()) + nextCons, nextOk := GetNextConsensusState(clientStore, cdc, tmHeader.GetHeight()) + // if previous consensus state exists, check consensus state time is greater than previous consensus state time + // if previous consensus state is not before current consensus state return true + if prevOk && !prevCons.Timestamp.Before(consState.Timestamp) { + return true + } + // if next consensus state exists, check consensus state time is less than next consensus state time + // if next consensus state is not after current consensus state return true + if nextOk && !nextCons.Timestamp.After(consState.Timestamp) { + return true + } + case *Misbehaviour: + // if heights are equal check that this is valid misbehaviour of a fork + // otherwise if heights are unequal check that this is valid misbehavior of BFT time violation + if msg.Header1.GetHeight().EQ(msg.Header2.GetHeight()) { + blockID1, err := cmttypes.BlockIDFromProto(&msg.Header1.Commit.BlockID) + if err != nil { + return false + } + + blockID2, err := cmttypes.BlockIDFromProto(&msg.Header2.Commit.BlockID) + if err != nil { + return false + } + + // Ensure that Commit Hashes are different + if !bytes.Equal(blockID1.Hash, blockID2.Hash) { + return true + } + + } else if !msg.Header1.Header.Time.After(msg.Header2.Header.Time) { + // Header1 is at greater height than Header2, therefore Header1 time must be less than or equal to + // Header2 time in order to be valid misbehaviour (violation of monotonic time). + return true + } + } + + return false +} + +// verifyMisbehaviour determines whether or not two conflicting +// headers at the same height would have convinced the light client. +// +// NOTE: consensusState1 is the trusted consensus state that corresponds to the TrustedHeight +// of misbehaviour.Header1 +// Similarly, consensusState2 is the trusted consensus state that corresponds +// to misbehaviour.Header2 +// Misbehaviour sets frozen height to {0, 1} since it is only used as a boolean value (zero or non-zero). +func (cs *ClientState) verifyMisbehaviour(ctx sdk.Context, clientStore storetypes.KVStore, cdc codec.BinaryCodec, misbehaviour *Misbehaviour) error { + // Regardless of the type of misbehaviour, ensure that both headers are valid and would have been accepted by light-client + + // Retrieve trusted consensus states for each Header in misbehaviour + tmConsensusState1, found := GetConsensusState(clientStore, cdc, misbehaviour.Header1.TrustedHeight) + if !found { + return errorsmod.Wrapf(clienttypes.ErrConsensusStateNotFound, "could not get trusted consensus state from clientStore for Header1 at TrustedHeight: %s", misbehaviour.Header1.TrustedHeight) + } + + tmConsensusState2, found := GetConsensusState(clientStore, cdc, misbehaviour.Header2.TrustedHeight) + if !found { + return errorsmod.Wrapf(clienttypes.ErrConsensusStateNotFound, "could not get trusted consensus state from clientStore for Header2 at TrustedHeight: %s", misbehaviour.Header2.TrustedHeight) + } + + // Check the validity of the two conflicting headers against their respective + // trusted consensus states + // NOTE: header height and commitment root assertions are checked in + // misbehaviour.ValidateBasic by the client keeper and msg.ValidateBasic + // by the base application. + if err := checkMisbehaviourHeader( + cs, tmConsensusState1, misbehaviour.Header1, ctx.BlockTime(), + ); err != nil { + return errorsmod.Wrap(err, "verifying Header1 in Misbehaviour failed") + } + if err := checkMisbehaviourHeader( + cs, tmConsensusState2, misbehaviour.Header2, ctx.BlockTime(), + ); err != nil { + return errorsmod.Wrap(err, "verifying Header2 in Misbehaviour failed") + } + + return nil +} + +// checkMisbehaviourHeader checks that a Header in Misbehaviour is valid misbehaviour given +// a trusted ConsensusState +func checkMisbehaviourHeader( + clientState *ClientState, consState *ConsensusState, header *Header, currentTimestamp time.Time, +) error { + tmTrustedValset, err := cmttypes.ValidatorSetFromProto(header.TrustedValidators) + if err != nil { + return errorsmod.Wrap(err, "trusted validator set is not tendermint validator set type") + } + + tmCommit, err := cmttypes.CommitFromProto(header.Commit) + if err != nil { + return errorsmod.Wrap(err, "commit is not tendermint commit type") + } + + // check the trusted fields for the header against ConsensusState + if err := checkTrustedHeader(header, consState); err != nil { + return err + } + + // assert that the age of the trusted consensus state is not older than the trusting period + if currentTimestamp.Sub(consState.Timestamp) >= clientState.TrustingPeriod { + return errorsmod.Wrapf( + ErrTrustingPeriodExpired, + "current timestamp minus the latest consensus state timestamp is greater than or equal to the trusting period (%d >= %d)", + currentTimestamp.Sub(consState.Timestamp), clientState.TrustingPeriod, + ) + } + + chainID := clientState.GetChainID() + // If chainID is in revision format, then set revision number of chainID with the revision number + // of the misbehaviour header + // NOTE: misbehaviour verification is not supported for chains which upgrade to a new chainID without + // strictly following the chainID revision format + if clienttypes.IsRevisionFormat(chainID) { + chainID, _ = clienttypes.SetRevisionNumber(chainID, header.GetHeight().GetRevisionNumber()) + } + + // - ValidatorSet must have TrustLevel similarity with trusted FromValidatorSet + // - ValidatorSets on both headers are valid given the last trusted ValidatorSet + if err := tmTrustedValset.VerifyCommitLightTrusting( + chainID, tmCommit, clientState.TrustLevel.ToTendermint(), + ); err != nil { + return errorsmod.Wrapf(clienttypes.ErrInvalidMisbehaviour, "validator set in header has too much change from trusted validator set: %v", err) + } + return nil +} diff --git a/modules/light-clients/07-tendermint/misbehaviour_handle_test.go b/modules/light-clients/07-tendermint/misbehaviour_handle_test.go new file mode 100644 index 0000000..a49ae49 --- /dev/null +++ b/modules/light-clients/07-tendermint/misbehaviour_handle_test.go @@ -0,0 +1,696 @@ +package tendermint_test + +import ( + "errors" + "fmt" + "strings" + "time" + + cmttypes "github.com/cometbft/cometbft/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *TendermintTestSuite) TestVerifyMisbehaviour() { + // Setup different validators and signers for testing different types of updates + altPrivVal := cmttypes.NewMockPV() + altPubKey, err := altPrivVal.GetPubKey() + suite.Require().NoError(err) + + // create modified heights to use for test-cases + altVal := cmttypes.NewValidator(altPubKey, 100) + + // Create alternative validator set with only altVal, invalid update (too much change in valSet) + altValSet := cmttypes.NewValidatorSet([]*cmttypes.Validator{altVal}) + altSigners := getAltSigners(altVal, altPrivVal) + + var ( + path *ibctesting.Path + misbehaviour exported.ClientMessage + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "valid fork misbehaviour", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, + nil, + }, + { + "valid time misbehaviour", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+3, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, + nil, + }, + { + "valid time misbehaviour, header 1 time strictly less than header 2 time", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+3, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Hour), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, + nil, + }, + { + "valid misbehavior at height greater than last consensusState", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+1, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+1, trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, nil, + }, + { + "valid misbehaviour with different trusted heights", func() { + trustedHeight1, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals1, ok := suite.chainB.TrustedValidators[trustedHeight1.RevisionHeight] + suite.Require().True(ok) + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + trustedHeight2, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals2, ok := suite.chainB.TrustedValidators[trustedHeight2.RevisionHeight] + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight1, suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals1, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight2, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals2, suite.chainB.Signers), + } + }, + nil, + }, + { + "valid misbehaviour at a previous revision", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + + // increment revision number + err = path.EndpointB.UpgradeChain() + suite.Require().NoError(err) + }, + nil, + }, + { + "valid misbehaviour at a future revision", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + height, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + futureRevision := fmt.Sprintf("%s-%d", strings.TrimSuffix(suite.chainB.ChainID, fmt.Sprintf("-%d", clienttypes.ParseChainID(suite.chainB.ChainID))), height.GetRevisionNumber()+1) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(futureRevision, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(futureRevision, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, + nil, + }, + { + "valid misbehaviour with trusted heights at a previous revision", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + // increment revision of chainID + err = path.EndpointB.UpgradeChain() + suite.Require().NoError(err) + + height, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, + nil, + }, + { + "consensus state's valset hash different from misbehaviour should still pass", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + // Create bothValSet with both suite validator and altVal + bothValSet := cmttypes.NewValidatorSet(append(suite.chainB.Vals.Validators, altValSet.Proposer)) + bothSigners := suite.chainB.Signers + bothSigners[altValSet.Proposer.Address.String()] = altPrivVal + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), bothValSet, suite.chainB.NextVals, trustedVals, bothSigners), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time, bothValSet, suite.chainB.NextVals, trustedVals, bothSigners), + } + }, nil, + }, + { + "invalid misbehaviour: misbehaviour from different chain", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader("evmos", int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader("evmos", int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, errors.New("invalid light client misbehaviour"), + }, + { + "misbehaviour trusted validators does not match validator hash in trusted consensus state", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, altValSet, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, altValSet, suite.chainB.Signers), + } + }, errors.New("invalid validator set"), + }, + { + "trusted consensus state does not exist", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight.Increment().(clienttypes.Height), suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, errors.New("consensus state not found"), + }, + { + "invalid tendermint misbehaviour", func() { + misbehaviour = &solomachine.Misbehaviour{} + }, errors.New("invalid client type"), + }, + { + "trusting period expired", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + suite.chainA.ExpireClient(path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, errors.New("time since latest trusted state has passed the trusting period"), + }, + { + "header 1 valset has too much change", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), altValSet, suite.chainB.NextVals, trustedVals, altSigners), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, errors.New("validator set in header has too much change from trusted validator set"), + }, + { + "header 2 valset has too much change", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time, altValSet, suite.chainB.NextVals, trustedVals, altSigners), + } + }, errors.New("validator set in header has too much change from trusted validator set"), + }, + { + "both header 1 and header 2 valsets have too much change", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), altValSet, suite.chainB.NextVals, trustedVals, altSigners), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time, altValSet, suite.chainB.NextVals, trustedVals, altSigners), + } + }, errors.New("validator set in header has too much change from trusted validator set"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + err := path.EndpointA.CreateClient() + suite.Require().NoError(err) + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().NoError(err) + + tc.malleate() + + err = lightClientModule.VerifyClientMessage(suite.chainA.GetContext(), path.EndpointA.ClientID, misbehaviour) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } +} + +// test both fork and time misbehaviour for chainIDs not in the revision format +// this function is separate as it must use a global variable in the testing package +// to initialize chains not in the revision format +func (suite *TendermintTestSuite) TestVerifyMisbehaviourNonRevisionChainID() { + // NOTE: chains set to non revision format + ibctesting.ChainIDSuffix = "" + + // Setup different validators and signers for testing different types of updates + altPrivVal := cmttypes.NewMockPV() + altPubKey, err := altPrivVal.GetPubKey() + suite.Require().NoError(err) + + // create modified heights to use for test-cases + altVal := cmttypes.NewValidator(altPubKey, 100) + + // Create alternative validator set with only altVal, invalid update (too much change in valSet) + altValSet := cmttypes.NewValidatorSet([]*cmttypes.Validator{altVal}) + altSigners := getAltSigners(altVal, altPrivVal) + + var ( + path *ibctesting.Path + misbehaviour exported.ClientMessage + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "valid fork misbehaviour", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, + nil, + }, + { + "valid time misbehaviour", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+3, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, + nil, + }, + { + "valid time misbehaviour, header 1 time strictly less than header 2 time", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+3, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Hour), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, + nil, + }, + { + "valid misbehavior at height greater than last consensusState", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+1, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+1, trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, nil, + }, + { + "valid misbehaviour with different trusted heights", func() { + trustedHeight1, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals1, ok := suite.chainB.TrustedValidators[trustedHeight1.RevisionHeight] + suite.Require().True(ok) + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + trustedHeight2, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals2, ok := suite.chainB.TrustedValidators[trustedHeight2.RevisionHeight] + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight1, suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals1, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight2, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals2, suite.chainB.Signers), + } + }, + nil, + }, + { + "consensus state's valset hash different from misbehaviour should still pass", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + // Create bothValSet with both suite validator and altVal + bothValSet := cmttypes.NewValidatorSet(append(suite.chainB.Vals.Validators, altValSet.Proposer)) + bothSigners := suite.chainB.Signers + bothSigners[altValSet.Proposer.Address.String()] = altPrivVal + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), bothValSet, suite.chainB.NextVals, trustedVals, bothSigners), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time, bothValSet, suite.chainB.NextVals, trustedVals, bothSigners), + } + }, nil, + }, + { + "invalid misbehaviour: misbehaviour from different chain", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader("evmos", int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader("evmos", int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, errors.New("validator set in header has too much change from trusted validator set"), + }, + { + "misbehaviour trusted validators does not match validator hash in trusted consensus state", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, altValSet, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, altValSet, suite.chainB.Signers), + } + }, errors.New("invalid validator set"), + }, + { + "trusted consensus state does not exist", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight.Increment().(clienttypes.Height), suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, errors.New("consensus state not found"), + }, + { + "invalid tendermint misbehaviour", func() { + misbehaviour = &solomachine.Misbehaviour{} + }, errors.New("nvalid client type"), + }, + { + "trusting period expired", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + suite.chainA.ExpireClient(path.EndpointA.ClientConfig.(*ibctesting.TendermintConfig).TrustingPeriod) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, errors.New("time since latest trusted state has passed the trusting period"), + }, + { + "header 1 valset has too much change", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), altValSet, suite.chainB.NextVals, trustedVals, altSigners), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, errors.New("validator set in header has too much change from trusted validator set"), + }, + { + "header 2 valset has too much change", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time, altValSet, suite.chainB.NextVals, trustedVals, altSigners), + } + }, errors.New("validator set in header has too much change from trusted validator set"), + }, + { + "both header 1 and header 2 valsets have too much change", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + misbehaviour = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), altValSet, suite.chainB.NextVals, trustedVals, altSigners), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time, altValSet, suite.chainB.NextVals, trustedVals, altSigners), + } + }, errors.New("validator set in header has too much change from trusted validator set"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + err := path.EndpointA.CreateClient() + suite.Require().NoError(err) + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().NoError(err) + + tc.malleate() + + err = lightClientModule.VerifyClientMessage(suite.chainA.GetContext(), path.EndpointA.ClientID, misbehaviour) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } + + // NOTE: reset chain creation to revision format + ibctesting.ChainIDSuffix = "-1" +} diff --git a/modules/light-clients/07-tendermint/misbehaviour_test.go b/modules/light-clients/07-tendermint/misbehaviour_test.go new file mode 100644 index 0000000..04dabe2 --- /dev/null +++ b/modules/light-clients/07-tendermint/misbehaviour_test.go @@ -0,0 +1,240 @@ +package tendermint_test + +import ( + "errors" + "time" + + errorsmod "cosmossdk.io/errors" + + "github.com/cometbft/cometbft/crypto/tmhash" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + cmttypes "github.com/cometbft/cometbft/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *TendermintTestSuite) TestMisbehaviour() { + heightMinus1 := clienttypes.NewHeight(0, height.RevisionHeight-1) + + misbehaviour := &ibctm.Misbehaviour{ + Header1: suite.header, + Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, suite.valSet, suite.signers), + ClientId: clientID, + } + + suite.Require().Equal(exported.Tendermint, misbehaviour.ClientType()) +} + +func (suite *TendermintTestSuite) TestMisbehaviourValidateBasic() { + altPrivVal := cmttypes.NewMockPV() + altPubKey, err := altPrivVal.GetPubKey() + suite.Require().NoError(err) + + revisionHeight := int64(height.RevisionHeight) + + altVal := cmttypes.NewValidator(altPubKey, revisionHeight) + + // Create alternative validator set with only altVal + altValSet := cmttypes.NewValidatorSet([]*cmttypes.Validator{altVal}) + + // Create signer array and ensure it is in same order as bothValSet + bothValSet, bothSigners := getBothSigners(suite, altVal, altPrivVal) + + altSignerArr := []cmttypes.PrivValidator{altPrivVal} + + heightMinus1 := clienttypes.NewHeight(0, height.RevisionHeight-1) + + testCases := []struct { + name string + misbehaviour *ibctm.Misbehaviour + malleateMisbehaviour func(misbehaviour *ibctm.Misbehaviour) error + expErr error + }{ + { + "valid fork misbehaviour, two headers at same height have different time", + &ibctm.Misbehaviour{ + Header1: suite.header, + Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now.Add(time.Minute), suite.valSet, suite.valSet, suite.valSet, suite.signers), + ClientId: clientID, + }, + func(misbehaviour *ibctm.Misbehaviour) error { return nil }, + nil, + }, + { + "valid time misbehaviour, both headers at different heights are at same time", + &ibctm.Misbehaviour{ + Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight+5), heightMinus1, suite.now, suite.valSet, suite.valSet, suite.valSet, suite.signers), + Header2: suite.header, + ClientId: clientID, + }, + func(misbehaviour *ibctm.Misbehaviour) error { return nil }, + nil, + }, + { + "misbehaviour Header1 is nil", + ibctm.NewMisbehaviour(clientID, nil, suite.header), + func(m *ibctm.Misbehaviour) error { return nil }, + errorsmod.Wrap(ibctm.ErrInvalidHeader, "misbehaviour Header1 cannot be nil"), + }, + { + "misbehaviour Header2 is nil", + ibctm.NewMisbehaviour(clientID, suite.header, nil), + func(m *ibctm.Misbehaviour) error { return nil }, + errorsmod.Wrap(ibctm.ErrInvalidHeader, "misbehaviour Header2 cannot be nil"), + }, + { + "valid misbehaviour with different trusted headers", + &ibctm.Misbehaviour{ + Header1: suite.header, + Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight), clienttypes.NewHeight(0, height.RevisionHeight-3), suite.now.Add(time.Minute), suite.valSet, suite.valSet, bothValSet, suite.signers), + ClientId: clientID, + }, + func(misbehaviour *ibctm.Misbehaviour) error { return nil }, + nil, + }, + { + "trusted height is 0 in Header1", + &ibctm.Misbehaviour{ + Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight), clienttypes.ZeroHeight(), suite.now.Add(time.Minute), suite.valSet, suite.valSet, suite.valSet, suite.signers), + Header2: suite.header, + ClientId: clientID, + }, + func(misbehaviour *ibctm.Misbehaviour) error { return nil }, + errorsmod.Wrap(ibctm.ErrInvalidHeaderHeight, "misbehaviour Header1 cannot have zero revision height"), + }, + { + "trusted height is 0 in Header2", + &ibctm.Misbehaviour{ + Header1: suite.header, + Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight), clienttypes.ZeroHeight(), suite.now.Add(time.Minute), suite.valSet, suite.valSet, suite.valSet, suite.signers), + ClientId: clientID, + }, + func(misbehaviour *ibctm.Misbehaviour) error { return nil }, + errorsmod.Wrap(ibctm.ErrInvalidHeaderHeight, "misbehaviour Header2 cannot have zero revision height"), + }, + { + "trusted valset is nil in Header1", + &ibctm.Misbehaviour{ + Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now.Add(time.Minute), suite.valSet, suite.valSet, nil, suite.signers), + Header2: suite.header, + ClientId: clientID, + }, + func(misbehaviour *ibctm.Misbehaviour) error { return nil }, + errorsmod.Wrap(ibctm.ErrInvalidValidatorSet, "trusted validator set in Header1 cannot be empty"), + }, + { + "trusted valset is nil in Header2", + &ibctm.Misbehaviour{ + Header1: suite.header, + Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now.Add(time.Minute), suite.valSet, suite.valSet, nil, suite.signers), + ClientId: clientID, + }, + func(misbehaviour *ibctm.Misbehaviour) error { return nil }, + errorsmod.Wrap(ibctm.ErrInvalidValidatorSet, "trusted validator set in Header2 cannot be empty"), + }, + { + "invalid client ID ", + &ibctm.Misbehaviour{ + Header1: suite.header, + Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, suite.valSet, suite.signers), + ClientId: "GAI", + }, + func(misbehaviour *ibctm.Misbehaviour) error { return nil }, + errors.New("identifier GAI has invalid length"), + }, + { + "chainIDs do not match", + &ibctm.Misbehaviour{ + Header1: suite.header, + Header2: suite.chainA.CreateTMClientHeader("ethermint", int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, suite.valSet, suite.signers), + ClientId: clientID, + }, + func(misbehaviour *ibctm.Misbehaviour) error { return nil }, + errorsmod.Wrap(clienttypes.ErrInvalidMisbehaviour, "headers must have identical chainIDs"), + }, + { + "header2 height is greater", + &ibctm.Misbehaviour{ + Header1: suite.header, + Header2: suite.chainA.CreateTMClientHeader(chainID, 6, clienttypes.NewHeight(0, height.RevisionHeight+1), suite.now, suite.valSet, suite.valSet, suite.valSet, suite.signers), + ClientId: clientID, + }, + func(misbehaviour *ibctm.Misbehaviour) error { return nil }, + errors.New("Header1 height is less than Header2 height"), + }, + { + "header 1 doesn't have 2/3 majority", + &ibctm.Misbehaviour{ + Header1: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, bothValSet, bothValSet, suite.valSet, bothSigners), + Header2: suite.header, + ClientId: clientID, + }, + func(misbehaviour *ibctm.Misbehaviour) error { + // voteSet contains only altVal which is less than 2/3 of total power (height/1height) + wrongVoteSet := cmttypes.NewVoteSet(chainID, int64(misbehaviour.Header1.GetHeight().GetRevisionHeight()), 1, cmtproto.PrecommitType, altValSet) + blockID, err := cmttypes.BlockIDFromProto(&misbehaviour.Header1.Commit.BlockID) + if err != nil { + return err + } + + extCommit, err := cmttypes.MakeExtCommit(*blockID, int64(misbehaviour.Header2.GetHeight().GetRevisionHeight()), misbehaviour.Header1.Commit.Round, wrongVoteSet, altSignerArr, suite.now, false) + misbehaviour.Header1.Commit = extCommit.ToCommit().ToProto() + return err + }, + errors.New("validator set did not commit to header"), + }, + { + "header 2 doesn't have 2/3 majority", + &ibctm.Misbehaviour{ + Header1: suite.header, + Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, bothValSet, bothValSet, suite.valSet, bothSigners), + ClientId: clientID, + }, + func(misbehaviour *ibctm.Misbehaviour) error { + // voteSet contains only altVal which is less than 2/3 of total power (height/1height) + wrongVoteSet := cmttypes.NewVoteSet(chainID, int64(misbehaviour.Header2.GetHeight().GetRevisionHeight()), 1, cmtproto.PrecommitType, altValSet) + blockID, err := cmttypes.BlockIDFromProto(&misbehaviour.Header2.Commit.BlockID) + if err != nil { + return err + } + + extCommit, err := cmttypes.MakeExtCommit(*blockID, int64(misbehaviour.Header2.GetHeight().GetRevisionHeight()), misbehaviour.Header2.Commit.Round, wrongVoteSet, altSignerArr, suite.now, false) + misbehaviour.Header2.Commit = extCommit.ToCommit().ToProto() + return err + }, + errors.New("validator set did not commit to header"), + }, + { + "validators sign off on wrong commit", + &ibctm.Misbehaviour{ + Header1: suite.header, + Header2: suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, bothValSet, bothValSet, suite.valSet, bothSigners), + ClientId: clientID, + }, + func(misbehaviour *ibctm.Misbehaviour) error { + tmBlockID := ibctesting.MakeBlockID(tmhash.Sum([]byte("other_hash")), 3, tmhash.Sum([]byte("other_partset"))) + misbehaviour.Header2.Commit.BlockID = tmBlockID.ToProto() + return nil + }, + errors.New("header 2 failed validation"), + }, + } + + for i, tc := range testCases { + suite.Run(tc.name, func() { + err := tc.malleateMisbehaviour(tc.misbehaviour) + suite.Require().NoError(err) + err = tc.misbehaviour.ValidateBasic() + + if tc.expErr == nil { + suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) + } else { + suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } +} diff --git a/modules/light-clients/07-tendermint/module.go b/modules/light-clients/07-tendermint/module.go new file mode 100644 index 0000000..869eccc --- /dev/null +++ b/modules/light-clients/07-tendermint/module.go @@ -0,0 +1,87 @@ +package tendermint + +import ( + "encoding/json" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + + "cosmossdk.io/core/appmodule" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/types/module" +) + +var ( + _ module.AppModuleBasic = (*AppModuleBasic)(nil) + _ appmodule.AppModule = (*AppModule)(nil) +) + +// AppModuleBasic defines the basic application module used by the tendermint light client. +// Only the RegisterInterfaces function needs to be implemented. All other function perform +// a no-op. +type AppModuleBasic struct{} + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (AppModuleBasic) IsOnePerModuleType() {} + +// IsAppModule implements the appmodule.AppModule interface. +func (AppModuleBasic) IsAppModule() {} + +// Name returns the tendermint module name. +func (AppModuleBasic) Name() string { + return ModuleName +} + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (AppModule) IsOnePerModuleType() {} + +// IsAppModule implements the appmodule.AppModule interface. +func (AppModule) IsAppModule() {} + +// RegisterLegacyAminoCodec performs a no-op. The Tendermint client does not support amino. +func (AppModuleBasic) RegisterLegacyAminoCodec(*codec.LegacyAmino) {} + +// RegisterInterfaces registers module concrete types into protobuf Any. This allows core IBC +// to unmarshal tendermint light client types. +func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) { + RegisterInterfaces(registry) +} + +// DefaultGenesis performs a no-op. Genesis is not supported for the tendermint light client. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return nil +} + +// ValidateGenesis performs a no-op. Genesis is not supported for the tendermint light client. +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { + return nil +} + +// RegisterGRPCGatewayRoutes performs a no-op. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) {} + +// GetTxCmd performs a no-op. Please see the 02-client cli commands. +func (AppModuleBasic) GetTxCmd() *cobra.Command { + return nil +} + +// GetQueryCmd performs a no-op. Please see the 02-client cli commands. +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return nil +} + +// AppModule is the application module for the Tendermint client module +type AppModule struct { + AppModuleBasic + lightClientModule LightClientModule +} + +// NewAppModule creates a new Tendermint client module +func NewAppModule(lightClientModule LightClientModule) AppModule { + return AppModule{ + lightClientModule: lightClientModule, + } +} diff --git a/modules/light-clients/07-tendermint/proposal_handle.go b/modules/light-clients/07-tendermint/proposal_handle.go new file mode 100644 index 0000000..2731205 --- /dev/null +++ b/modules/light-clients/07-tendermint/proposal_handle.go @@ -0,0 +1,103 @@ +package tendermint + +import ( + "reflect" + "time" + + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// CheckSubstituteAndUpdateState will try to update the client with the state of the +// substitute. +// +// AllowUpdateAfterMisbehaviour and AllowUpdateAfterExpiry have been deprecated. +// Please see ADR 026 for more information. +// +// The following must always be true: +// - The substitute client is the same type as the subject client +// - The subject and substitute client states match in all parameters (expect frozen height, latest height, and chain-id) +// +// In case 1) before updating the client, the client will be unfrozen by resetting +// the FrozenHeight to the zero Height. +func (cs ClientState) CheckSubstituteAndUpdateState( + ctx sdk.Context, cdc codec.BinaryCodec, subjectClientStore, + substituteClientStore storetypes.KVStore, substituteClient exported.ClientState, +) error { + substituteClientState, ok := substituteClient.(*ClientState) + if !ok { + return errorsmod.Wrapf(clienttypes.ErrInvalidClient, "expected type %T, got %T", &ClientState{}, substituteClient) + } + + if !IsMatchingClientState(cs, *substituteClientState) { + return errorsmod.Wrap(clienttypes.ErrInvalidSubstitute, "subject client state does not match substitute client state") + } + + if cs.status(ctx, subjectClientStore, cdc) == exported.Frozen { + // unfreeze the client + cs.FrozenHeight = clienttypes.ZeroHeight() + } + + // copy consensus states and processed time from substitute to subject + // starting from initial height and ending on the latest height (inclusive) + height := substituteClientState.LatestHeight + + consensusState, found := GetConsensusState(substituteClientStore, cdc, height) + if !found { + return errorsmod.Wrap(clienttypes.ErrConsensusStateNotFound, "unable to retrieve latest consensus state for substitute client") + } + + setConsensusState(subjectClientStore, cdc, consensusState, height) + + // set metadata stored for the substitute consensus state + processedHeight, found := GetProcessedHeight(substituteClientStore, height) + if !found { + return errorsmod.Wrap(clienttypes.ErrUpdateClientFailed, "unable to retrieve processed height for substitute client latest height") + } + + processedTime, found := GetProcessedTime(substituteClientStore, height) + if !found { + return errorsmod.Wrap(clienttypes.ErrUpdateClientFailed, "unable to retrieve processed time for substitute client latest height") + } + + setConsensusMetadataWithValues(subjectClientStore, height, processedHeight, processedTime) + + cs.LatestHeight = substituteClientState.LatestHeight + cs.ChainId = substituteClientState.ChainId + + // set new trusting period based on the substitute client state + cs.TrustingPeriod = substituteClientState.TrustingPeriod + + // no validation is necessary since the substitute is verified to be Active + // in 02-client. + setClientState(subjectClientStore, cdc, &cs) + + return nil +} + +// IsMatchingClientState returns true if all the client state parameters match +// except for frozen height, latest height, trusting period, chain-id. +func IsMatchingClientState(subject, substitute ClientState) bool { + // zero out parameters which do not need to match + subject.LatestHeight = clienttypes.ZeroHeight() + subject.FrozenHeight = clienttypes.ZeroHeight() + subject.TrustingPeriod = time.Duration(0) + substitute.LatestHeight = clienttypes.ZeroHeight() + substitute.FrozenHeight = clienttypes.ZeroHeight() + substitute.TrustingPeriod = time.Duration(0) + subject.ChainId = "" + substitute.ChainId = "" + // sets both sets of flags to true as these flags have been DEPRECATED, see ADR-026 for more information + subject.AllowUpdateAfterExpiry = true + substitute.AllowUpdateAfterExpiry = true + subject.AllowUpdateAfterMisbehaviour = true + substitute.AllowUpdateAfterMisbehaviour = true + + return reflect.DeepEqual(subject, substitute) +} diff --git a/modules/light-clients/07-tendermint/proposal_handle_test.go b/modules/light-clients/07-tendermint/proposal_handle_test.go new file mode 100644 index 0000000..04d0652 --- /dev/null +++ b/modules/light-clients/07-tendermint/proposal_handle_test.go @@ -0,0 +1,236 @@ +package tendermint_test + +import ( + "time" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +var frozenHeight = clienttypes.NewHeight(0, 1) + +func (suite *TendermintTestSuite) TestCheckSubstituteUpdateStateBasic() { + var ( + substituteClientState exported.ClientState + substitutePath *ibctesting.Path + ) + testCases := []struct { + name string + malleate func() + }{ + { + "solo machine used for substitute", func() { + substituteClientState = ibctesting.NewSolomachine(suite.T(), suite.cdc, "solo machine", "", 1).ClientState() + }, + }, + { + "non-matching substitute", func() { + substitutePath.SetupClients() + substituteClientState, ok := suite.chainA.GetClientState(substitutePath.EndpointA.ClientID).(*ibctm.ClientState) + suite.Require().True(ok) + // change trusting period so that test should fail + substituteClientState.TrustingPeriod = time.Hour * 24 * 7 + + tmClientState := substituteClientState + tmClientState.ChainId += "different chain" + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + subjectPath := ibctesting.NewPath(suite.chainA, suite.chainB) + substitutePath = ibctesting.NewPath(suite.chainA, suite.chainB) + + subjectPath.SetupClients() + subjectClientState, ok := suite.chainA.GetClientState(subjectPath.EndpointA.ClientID).(*ibctm.ClientState) + suite.Require().True(ok) + + // expire subject client + suite.coordinator.IncrementTimeBy(subjectClientState.TrustingPeriod) + suite.coordinator.CommitBlock(suite.chainA, suite.chainB) + + tc.malleate() + + subjectClientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), subjectPath.EndpointA.ClientID) + substituteClientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), substitutePath.EndpointA.ClientID) + + err := subjectClientState.CheckSubstituteAndUpdateState(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), subjectClientStore, substituteClientStore, substituteClientState) + suite.Require().Error(err) + }) + } +} + +func (suite *TendermintTestSuite) TestCheckSubstituteAndUpdateState() { + testCases := []struct { + name string + FreezeClient bool + expError error + }{ + { + name: "PASS: update checks are deprecated, client is not frozen", + FreezeClient: false, + expError: nil, + }, + { + name: "PASS: update checks are deprecated, client is frozen", + FreezeClient: true, + expError: nil, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + // construct subject using test case parameters + subjectPath := ibctesting.NewPath(suite.chainA, suite.chainB) + subjectPath.SetupClients() + subjectClientState, ok := suite.chainA.GetClientState(subjectPath.EndpointA.ClientID).(*ibctm.ClientState) + suite.Require().True(ok) + + if tc.FreezeClient { + subjectClientState.FrozenHeight = frozenHeight + } + + // construct the substitute to match the subject client + + substitutePath := ibctesting.NewPath(suite.chainA, suite.chainB) + substitutePath.SetupClients() + substituteClientState, ok := suite.chainA.GetClientState(substitutePath.EndpointA.ClientID).(*ibctm.ClientState) + suite.Require().True(ok) + // update trusting period of substitute client state + substituteClientState.TrustingPeriod = time.Hour * 24 * 7 + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), substitutePath.EndpointA.ClientID, substituteClientState) + + // update substitute a few times + for range 3 { + err := substitutePath.EndpointA.UpdateClient() + suite.Require().NoError(err) + // skip a block + suite.coordinator.CommitBlock(suite.chainA, suite.chainB) + } + + // get updated substitute + substituteClientState, ok = suite.chainA.GetClientState(substitutePath.EndpointA.ClientID).(*ibctm.ClientState) + suite.Require().True(ok) + + // test that subject gets updated chain-id + newChainID := "new-chain-id" + substituteClientState.ChainId = newChainID + + subjectClientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), subjectPath.EndpointA.ClientID) + substituteClientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), substitutePath.EndpointA.ClientID) + + expectedConsState := substitutePath.EndpointA.GetConsensusState(substituteClientState.LatestHeight) + expectedProcessedTime, found := ibctm.GetProcessedTime(substituteClientStore, substituteClientState.LatestHeight) + suite.Require().True(found) + expectedProcessedHeight, found := ibctm.GetProcessedTime(substituteClientStore, substituteClientState.LatestHeight) + suite.Require().True(found) + expectedIterationKey := ibctm.GetIterationKey(substituteClientStore, substituteClientState.LatestHeight) + + err := subjectClientState.CheckSubstituteAndUpdateState(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), subjectClientStore, substituteClientStore, substituteClientState) + + if tc.expError == nil { + suite.Require().NoError(err) + + updatedClient, ok := subjectPath.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + suite.Require().Equal(clienttypes.ZeroHeight(), updatedClient.FrozenHeight) + + subjectClientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), subjectPath.EndpointA.ClientID) + + // check that the correct consensus state was copied over + suite.Require().Equal(substituteClientState.LatestHeight, updatedClient.LatestHeight) + subjectConsState := subjectPath.EndpointA.GetConsensusState(updatedClient.LatestHeight) + subjectProcessedTime, found := ibctm.GetProcessedTime(subjectClientStore, updatedClient.LatestHeight) + suite.Require().True(found) + subjectProcessedHeight, found := ibctm.GetProcessedTime(substituteClientStore, updatedClient.LatestHeight) + suite.Require().True(found) + subjectIterationKey := ibctm.GetIterationKey(substituteClientStore, updatedClient.LatestHeight) + + suite.Require().Equal(expectedConsState, subjectConsState) + suite.Require().Equal(expectedProcessedTime, subjectProcessedTime) + suite.Require().Equal(expectedProcessedHeight, subjectProcessedHeight) + suite.Require().Equal(expectedIterationKey, subjectIterationKey) + + suite.Require().Equal(newChainID, updatedClient.ChainId) + suite.Require().Equal(time.Hour*24*7, updatedClient.TrustingPeriod) + } else { + suite.Require().Error(err) + suite.Require().ErrorContains(err, tc.expError.Error()) + } + }) + } +} + +func (suite *TendermintTestSuite) TestIsMatchingClientState() { + var ( + subjectPath, substitutePath *ibctesting.Path + subjectClientState, substituteClientState *ibctm.ClientState + ) + + testCases := []struct { + name string + malleate func() + isMatch bool + }{ + { + "matching clients", func() { + var ok bool + subjectClientState, ok = suite.chainA.GetClientState(subjectPath.EndpointA.ClientID).(*ibctm.ClientState) + suite.Require().True(ok) + substituteClientState, ok = suite.chainA.GetClientState(substitutePath.EndpointA.ClientID).(*ibctm.ClientState) + suite.Require().True(ok) + }, true, + }, + { + "matching, frozen height is not used in check for equality", func() { + subjectClientState.FrozenHeight = frozenHeight + substituteClientState.FrozenHeight = clienttypes.ZeroHeight() + }, true, + }, + { + "matching, latest height is not used in check for equality", func() { + subjectClientState.LatestHeight = clienttypes.NewHeight(0, 10) + substituteClientState.FrozenHeight = clienttypes.ZeroHeight() + }, true, + }, + { + "matching, chain id is different", func() { + subjectClientState.ChainId = "bitcoin" + substituteClientState.ChainId = "ethereum" + }, true, + }, + { + "matching, trusting period is different", func() { + subjectClientState.TrustingPeriod = time.Hour * 10 + substituteClientState.TrustingPeriod = time.Hour * 1 + }, true, + }, + { + "not matching, trust level is different", func() { + subjectClientState.TrustLevel = ibctm.Fraction{2, 3} + substituteClientState.TrustLevel = ibctm.Fraction{1, 3} + }, false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + + subjectPath = ibctesting.NewPath(suite.chainA, suite.chainB) + substitutePath = ibctesting.NewPath(suite.chainA, suite.chainB) + subjectPath.SetupClients() + substitutePath.SetupClients() + + tc.malleate() + + suite.Require().Equal(tc.isMatch, ibctm.IsMatchingClientState(*subjectClientState, *substituteClientState)) + }) + } +} diff --git a/modules/light-clients/07-tendermint/store.go b/modules/light-clients/07-tendermint/store.go new file mode 100644 index 0000000..4fdbfbb --- /dev/null +++ b/modules/light-clients/07-tendermint/store.go @@ -0,0 +1,344 @@ +package tendermint + +import ( + "bytes" + "encoding/binary" + "fmt" + + "cosmossdk.io/store/prefix" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +/* +This file contains the logic for storage and iteration over `IterationKey` metadata that is stored +for each consensus state. The consensus state key specified in ICS-24 and expected by counterparty chains +stores the consensus state under the key: `consensusStates/{revision_number}-{revision_height}`, with each number +represented as a string. +While this works fine for IBC proof verification, it makes efficient iteration difficult since the lexicographic order +of the consensus state keys do not match the height order of consensus states. This makes consensus state pruning and +monotonic time enforcement difficult since it is inefficient to find the earliest consensus state or to find the neighboring +consensus states given a consensus state height. +Changing the ICS-24 representation will be a major breaking change that requires counterparty chains to accept a new key format. +Thus to avoid breaking IBC, we can store a lookup from a more efficiently formatted key: `iterationKey` to the consensus state key which +stores the underlying consensus state. This efficient iteration key will be formatted like so: `iterateConsensusStates{BigEndianRevisionBytes}{BigEndianHeightBytes}`. +This ensures that the lexicographic order of iteration keys match the height order of the consensus states. Thus, we can use the SDK store's +Iterators to iterate over the consensus states in ascending/descending order by providing a mapping from `iterationKey -> consensusStateKey -> ConsensusState`. +A future version of IBC may choose to replace the ICS24 ConsensusState path with the more efficient format and make this indirection unnecessary. +*/ + +const KeyIterateConsensusStatePrefix = "iterateConsensusStates" + +var ( + // KeyProcessedTime is appended to consensus state key to store the processed time + KeyProcessedTime = []byte("/processedTime") + // KeyProcessedHeight is appended to consensus state key to store the processed height + KeyProcessedHeight = []byte("/processedHeight") + // KeyIteration stores the key mapping to consensus state key for efficient iteration + KeyIteration = []byte("/iterationKey") +) + +// setClientState stores the client state +func setClientState(clientStore storetypes.KVStore, cdc codec.BinaryCodec, clientState *ClientState) { + key := host.ClientStateKey() + val := clienttypes.MustMarshalClientState(cdc, clientState) + clientStore.Set(key, val) +} + +// getClientState retrieves the client state from the store using the provided KVStore and codec. +// It returns the unmarshaled ClientState and a boolean indicating if the state was found. +func getClientState(store storetypes.KVStore, cdc codec.BinaryCodec) (*ClientState, bool) { + bz := store.Get(host.ClientStateKey()) + if len(bz) == 0 { + return nil, false + } + + clientStateI := clienttypes.MustUnmarshalClientState(cdc, bz) + var clientState *ClientState + clientState, ok := clientStateI.(*ClientState) + if !ok { + panic(fmt.Errorf("cannot convert %T into %T", clientStateI, clientState)) + } + return clientState, true +} + +// setConsensusState stores the consensus state at the given height. +func setConsensusState(clientStore storetypes.KVStore, cdc codec.BinaryCodec, consensusState *ConsensusState, height exported.Height) { + key := host.ConsensusStateKey(height) + val := clienttypes.MustMarshalConsensusState(cdc, consensusState) + clientStore.Set(key, val) +} + +// GetConsensusState retrieves the consensus state from the client prefixed store. +// If the ConsensusState does not exist in state for the provided height a nil value and false boolean flag is returned +func GetConsensusState(store storetypes.KVStore, cdc codec.BinaryCodec, height exported.Height) (*ConsensusState, bool) { + bz := store.Get(host.ConsensusStateKey(height)) + if len(bz) == 0 { + return nil, false + } + + consensusStateI := clienttypes.MustUnmarshalConsensusState(cdc, bz) + var consensusState *ConsensusState + consensusState, ok := consensusStateI.(*ConsensusState) + if !ok { + panic(fmt.Errorf("cannot convert %T into %T", consensusStateI, consensusState)) + } + + return consensusState, true +} + +// deleteConsensusState deletes the consensus state at the given height +func deleteConsensusState(clientStore storetypes.KVStore, height exported.Height) { + key := host.ConsensusStateKey(height) + clientStore.Delete(key) +} + +// ProcessedTimeKey returns the key under which the processed time will be stored in the client store. +func ProcessedTimeKey(height exported.Height) []byte { + return append(host.ConsensusStateKey(height), KeyProcessedTime...) +} + +// SetProcessedTime stores the time at which a header was processed and the corresponding consensus state was created. +// This is useful when validating whether a packet has reached the time specified delay period in the tendermint client's +// verification functions +func SetProcessedTime(clientStore storetypes.KVStore, height exported.Height, timeNs uint64) { + key := ProcessedTimeKey(height) + val := sdk.Uint64ToBigEndian(timeNs) + clientStore.Set(key, val) +} + +// GetProcessedTime gets the time (in nanoseconds) at which this chain received and processed a tendermint header. +// This is used to validate that a received packet has passed the time delay period. +func GetProcessedTime(clientStore storetypes.KVStore, height exported.Height) (uint64, bool) { + key := ProcessedTimeKey(height) + bz := clientStore.Get(key) + if len(bz) == 0 { + return 0, false + } + return sdk.BigEndianToUint64(bz), true +} + +// deleteProcessedTime deletes the processedTime for a given height +func deleteProcessedTime(clientStore storetypes.KVStore, height exported.Height) { + key := ProcessedTimeKey(height) + clientStore.Delete(key) +} + +// ProcessedHeightKey returns the key under which the processed height will be stored in the client store. +func ProcessedHeightKey(height exported.Height) []byte { + return append(host.ConsensusStateKey(height), KeyProcessedHeight...) +} + +// SetProcessedHeight stores the height at which a header was processed and the corresponding consensus state was created. +// This is useful when validating whether a packet has reached the specified block delay period in the tendermint client's +// verification functions +func SetProcessedHeight(clientStore storetypes.KVStore, consHeight, processedHeight exported.Height) { + key := ProcessedHeightKey(consHeight) + val := []byte(processedHeight.String()) + clientStore.Set(key, val) +} + +// GetProcessedHeight gets the height at which this chain received and processed a tendermint header. +// This is used to validate that a received packet has passed the block delay period. +func GetProcessedHeight(clientStore storetypes.KVStore, height exported.Height) (exported.Height, bool) { + key := ProcessedHeightKey(height) + bz := clientStore.Get(key) + if len(bz) == 0 { + return nil, false + } + processedHeight, err := clienttypes.ParseHeight(string(bz)) + if err != nil { + return nil, false + } + return processedHeight, true +} + +// deleteProcessedHeight deletes the processedHeight for a given height +func deleteProcessedHeight(clientStore storetypes.KVStore, height exported.Height) { + key := ProcessedHeightKey(height) + clientStore.Delete(key) +} + +// IterationKey returns the key under which the consensus state key will be stored. +// The iteration key is a BigEndian representation of the consensus state key to support efficient iteration. +func IterationKey(height exported.Height) []byte { + heightBytes := bigEndianHeightBytes(height) + return append([]byte(KeyIterateConsensusStatePrefix), heightBytes...) +} + +// SetIterationKey stores the consensus state key under a key that is more efficient for ordered iteration +func SetIterationKey(clientStore storetypes.KVStore, height exported.Height) { + key := IterationKey(height) + val := host.ConsensusStateKey(height) + clientStore.Set(key, val) +} + +// GetIterationKey returns the consensus state key stored under the efficient iteration key. +// NOTE: This function is currently only used for testing purposes +func GetIterationKey(clientStore storetypes.KVStore, height exported.Height) []byte { + key := IterationKey(height) + return clientStore.Get(key) +} + +// deleteIterationKey deletes the iteration key for a given height +func deleteIterationKey(clientStore storetypes.KVStore, height exported.Height) { + key := IterationKey(height) + clientStore.Delete(key) +} + +// GetHeightFromIterationKey takes an iteration key and returns the height that it references +func GetHeightFromIterationKey(iterKey []byte) exported.Height { + bigEndianBytes := iterKey[len([]byte(KeyIterateConsensusStatePrefix)):] + revisionBytes := bigEndianBytes[0:8] + heightBytes := bigEndianBytes[8:] + revision := binary.BigEndian.Uint64(revisionBytes) + height := binary.BigEndian.Uint64(heightBytes) + return clienttypes.NewHeight(revision, height) +} + +// IterateConsensusStateAscending iterates through the consensus states in ascending order. It calls the provided +// callback on each height, until stop=true is returned. +func IterateConsensusStateAscending(clientStore storetypes.KVStore, cb func(height exported.Height) (stop bool)) { + iterator := storetypes.KVStorePrefixIterator(clientStore, []byte(KeyIterateConsensusStatePrefix)) + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + iterKey := iterator.Key() + height := GetHeightFromIterationKey(iterKey) + if cb(height) { + break + } + } +} + +// GetNextConsensusState returns the lowest consensus state that is larger than the given height. +// The Iterator returns a storetypes.Iterator which iterates from start (inclusive) to end (exclusive). +// If the starting height exists in store, we need to call iterator.Next() to get the next consensus state. +// Otherwise, the iterator is already at the next consensus state so we can call iterator.Value() immediately. +func GetNextConsensusState(clientStore storetypes.KVStore, cdc codec.BinaryCodec, height exported.Height) (*ConsensusState, bool) { + iterateStore := prefix.NewStore(clientStore, []byte(KeyIterateConsensusStatePrefix)) + iterator := iterateStore.Iterator(bigEndianHeightBytes(height), nil) + defer iterator.Close() + if !iterator.Valid() { + return nil, false + } + + // if iterator is at current height, ignore the consensus state at current height and get next height + // if iterator value is not at current height, it is already at next height. + if bytes.Equal(iterator.Value(), host.ConsensusStateKey(height)) { + iterator.Next() + if !iterator.Valid() { + return nil, false + } + } + + csKey := iterator.Value() + + return getTmConsensusState(clientStore, cdc, csKey) +} + +// GetPreviousConsensusState returns the highest consensus state that is lower than the given height. +// The Iterator returns a storetypes.Iterator which iterates from the end (exclusive) to start (inclusive). +// Thus to get previous consensus state we call iterator.Value() immediately. +func GetPreviousConsensusState(clientStore storetypes.KVStore, cdc codec.BinaryCodec, height exported.Height) (*ConsensusState, bool) { + iterateStore := prefix.NewStore(clientStore, []byte(KeyIterateConsensusStatePrefix)) + iterator := iterateStore.ReverseIterator(nil, bigEndianHeightBytes(height)) + defer iterator.Close() + + if !iterator.Valid() { + return nil, false + } + + csKey := iterator.Value() + + return getTmConsensusState(clientStore, cdc, csKey) +} + +// PruneAllExpiredConsensusStates iterates over all consensus states for a given +// client store. If a consensus state is expired, it is deleted and its metadata +// is deleted. The number of consensus states pruned is returned. +func PruneAllExpiredConsensusStates( + ctx sdk.Context, clientStore storetypes.KVStore, + cdc codec.BinaryCodec, clientState *ClientState, +) int { + var heights []exported.Height + + pruneCb := func(height exported.Height) bool { + consState, found := GetConsensusState(clientStore, cdc, height) + if !found { // consensus state should always be found + return true + } + if clientState.IsExpired(consState.Timestamp, ctx.BlockTime()) { + heights = append(heights, height) + } + + return false + } + + IterateConsensusStateAscending(clientStore, pruneCb) + + for _, height := range heights { + deleteConsensusState(clientStore, height) + deleteConsensusMetadata(clientStore, height) + } + + return len(heights) +} + +// Helper function for GetNextConsensusState and GetPreviousConsensusState +func getTmConsensusState(clientStore storetypes.KVStore, cdc codec.BinaryCodec, key []byte) (*ConsensusState, bool) { + bz := clientStore.Get(key) + if len(bz) == 0 { + return nil, false + } + + consensusStateI, err := clienttypes.UnmarshalConsensusState(cdc, bz) + if err != nil { + return nil, false + } + + consensusState, ok := consensusStateI.(*ConsensusState) + if !ok { + return nil, false + } + return consensusState, true +} + +func bigEndianHeightBytes(height exported.Height) []byte { + heightBytes := make([]byte, 16) + binary.BigEndian.PutUint64(heightBytes, height.GetRevisionNumber()) + binary.BigEndian.PutUint64(heightBytes[8:], height.GetRevisionHeight()) + return heightBytes +} + +// setConsensusMetadata sets context time as processed time and set context height as processed height +// as this is internal tendermint light client logic. +// client state and consensus state will be set by client keeper +// set iteration key to provide ability for efficient ordered iteration of consensus states. +func setConsensusMetadata(ctx sdk.Context, clientStore storetypes.KVStore, height exported.Height) { + setConsensusMetadataWithValues(clientStore, height, clienttypes.GetSelfHeight(ctx), uint64(ctx.BlockTime().UnixNano())) +} + +// setConsensusMetadataWithValues sets the consensus metadata with the provided values +func setConsensusMetadataWithValues( + clientStore storetypes.KVStore, height, + processedHeight exported.Height, + processedTime uint64, +) { + SetProcessedTime(clientStore, height, processedTime) + SetProcessedHeight(clientStore, height, processedHeight) + SetIterationKey(clientStore, height) +} + +// deleteConsensusMetadata deletes the metadata stored for a particular consensus state. +func deleteConsensusMetadata(clientStore storetypes.KVStore, height exported.Height) { + deleteProcessedTime(clientStore, height) + deleteProcessedHeight(clientStore, height) + deleteIterationKey(clientStore, height) +} diff --git a/modules/light-clients/07-tendermint/store_test.go b/modules/light-clients/07-tendermint/store_test.go new file mode 100644 index 0000000..03bad98 --- /dev/null +++ b/modules/light-clients/07-tendermint/store_test.go @@ -0,0 +1,199 @@ +package tendermint_test + +import ( + "math" + "time" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine" + tendermint "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *TendermintTestSuite) TestGetConsensusState() { + var ( + height exported.Height + path *ibctesting.Path + ) + + testCases := []struct { + name string + malleate func() + expPass bool + expPanic bool + }{ + { + "success", func() {}, true, false, + }, + { + "consensus state not found", func() { + // use height with no consensus state set + height = height.Increment() + }, false, false, + }, + { + "not a consensus state interface", func() { + // marshal an empty client state and set as consensus state + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + clientStateBz := clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), &tendermint.ClientState{}) + store.Set(host.ConsensusStateKey(height), clientStateBz) + }, false, true, + }, + { + "invalid consensus state (solomachine)", func() { + // marshal and set solomachine consensus state + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + consensusStateBz := clienttypes.MustMarshalConsensusState(suite.chainA.App.AppCodec(), &solomachine.ConsensusState{}) + store.Set(host.ConsensusStateKey(height), consensusStateBz) + }, false, true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + path.Setup() + + height = path.EndpointA.GetClientLatestHeight() + + tc.malleate() // change vars as necessary + + if tc.expPanic { + suite.Require().Panics(func() { + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + tendermint.GetConsensusState(store, suite.chainA.Codec, height) + }) + + return + } + + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + consensusState, found := tendermint.GetConsensusState(store, suite.chainA.Codec, height) + + if tc.expPass { + suite.Require().True(found) + + expConsensusState, found := suite.chainA.GetConsensusState(path.EndpointA.ClientID, height) + suite.Require().True(found) + suite.Require().Equal(expConsensusState, consensusState) + } else { + suite.Require().False(found) + suite.Require().Nil(consensusState) + } + }) + } +} + +func (suite *TendermintTestSuite) TestGetProcessedTime() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + suite.coordinator.UpdateTime() + + expectedTime := suite.chainA.ProposedHeader.Time + + // Verify ProcessedTime on CreateClient + err := path.EndpointA.CreateClient() + suite.Require().NoError(err) + + height := path.EndpointA.GetClientLatestHeight() + + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + actualTime, ok := tendermint.GetProcessedTime(store, height) + suite.Require().True(ok, "could not retrieve processed time for stored consensus state") + suite.Require().Equal(uint64(expectedTime.UnixNano()), actualTime, "retrieved processed time is not expected value") + + suite.coordinator.UpdateTime() + // coordinator increments time before updating client + expectedTime = suite.chainA.ProposedHeader.Time.Add(ibctesting.TimeIncrement) + + // Verify ProcessedTime on UpdateClient + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height = path.EndpointA.GetClientLatestHeight() + + store = suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + actualTime, ok = tendermint.GetProcessedTime(store, height) + suite.Require().True(ok, "could not retrieve processed time for stored consensus state") + suite.Require().Equal(uint64(expectedTime.UnixNano()), actualTime, "retrieved processed time is not expected value") + + // try to get processed time for height that doesn't exist in store + _, ok = tendermint.GetProcessedTime(store, clienttypes.NewHeight(1, 1)) + suite.Require().False(ok, "retrieved processed time for a non-existent consensus state") +} + +func (suite *TendermintTestSuite) TestIterationKey() { + testHeights := []exported.Height{ + clienttypes.NewHeight(0, 1), + clienttypes.NewHeight(0, 1234), + clienttypes.NewHeight(7890, 4321), + clienttypes.NewHeight(math.MaxUint64, math.MaxUint64), + } + for _, h := range testHeights { + k := tendermint.IterationKey(h) + retrievedHeight := tendermint.GetHeightFromIterationKey(k) + suite.Require().Equal(h, retrievedHeight, "retrieving height from iteration key failed") + } +} + +func (suite *TendermintTestSuite) TestIterateConsensusStates() { + nextValsHash := []byte("nextVals") + + // Set iteration keys and consensus states + tendermint.SetIterationKey(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), "testClient"), clienttypes.NewHeight(0, 1)) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), "testClient", clienttypes.NewHeight(0, 1), tendermint.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte("hash0-1")), nextValsHash)) + tendermint.SetIterationKey(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), "testClient"), clienttypes.NewHeight(4, 9)) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), "testClient", clienttypes.NewHeight(4, 9), tendermint.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte("hash4-9")), nextValsHash)) + tendermint.SetIterationKey(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), "testClient"), clienttypes.NewHeight(0, 10)) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), "testClient", clienttypes.NewHeight(0, 10), tendermint.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte("hash0-10")), nextValsHash)) + tendermint.SetIterationKey(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), "testClient"), clienttypes.NewHeight(0, 4)) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), "testClient", clienttypes.NewHeight(0, 4), tendermint.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte("hash0-4")), nextValsHash)) + tendermint.SetIterationKey(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), "testClient"), clienttypes.NewHeight(40, 1)) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), "testClient", clienttypes.NewHeight(40, 1), tendermint.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte("hash40-1")), nextValsHash)) + + var testArr []string + cb := func(height exported.Height) bool { + testArr = append(testArr, height.String()) + return false + } + + tendermint.IterateConsensusStateAscending(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), "testClient"), cb) + expectedArr := []string{"0-1", "0-4", "0-10", "4-9", "40-1"} + suite.Require().Equal(expectedArr, testArr) +} + +func (suite *TendermintTestSuite) TestGetNeighboringConsensusStates() { + nextValsHash := []byte("nextVals") + cs01 := tendermint.NewConsensusState(time.Now().UTC(), commitmenttypes.NewMerkleRoot([]byte("hash0-1")), nextValsHash) + cs04 := tendermint.NewConsensusState(time.Now().UTC(), commitmenttypes.NewMerkleRoot([]byte("hash0-4")), nextValsHash) + cs49 := tendermint.NewConsensusState(time.Now().UTC(), commitmenttypes.NewMerkleRoot([]byte("hash4-9")), nextValsHash) + height01 := clienttypes.NewHeight(0, 1) + height04 := clienttypes.NewHeight(0, 4) + height49 := clienttypes.NewHeight(4, 9) + + // Set iteration keys and consensus states + tendermint.SetIterationKey(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), "testClient"), height01) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), "testClient", height01, cs01) + tendermint.SetIterationKey(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), "testClient"), height04) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), "testClient", height04, cs04) + tendermint.SetIterationKey(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), "testClient"), height49) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), "testClient", height49, cs49) + + prevCs01, ok := tendermint.GetPreviousConsensusState(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), "testClient"), suite.chainA.Codec, height01) + suite.Require().Nil(prevCs01, "consensus state exists before lowest consensus state") + suite.Require().False(ok) + prevCs49, ok := tendermint.GetPreviousConsensusState(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), "testClient"), suite.chainA.Codec, height49) + suite.Require().Equal(cs04, prevCs49, "previous consensus state is not returned correctly") + suite.Require().True(ok) + + nextCs01, ok := tendermint.GetNextConsensusState(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), "testClient"), suite.chainA.Codec, height01) + suite.Require().Equal(cs04, nextCs01, "next consensus state not returned correctly") + suite.Require().True(ok) + nextCs49, ok := tendermint.GetNextConsensusState(suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), "testClient"), suite.chainA.Codec, height49) + suite.Require().Nil(nextCs49, "next consensus state exists after highest consensus state") + suite.Require().False(ok) +} diff --git a/modules/light-clients/07-tendermint/tendermint.pb.go b/modules/light-clients/07-tendermint/tendermint.pb.go new file mode 100644 index 0000000..97a9dce --- /dev/null +++ b/modules/light-clients/07-tendermint/tendermint.pb.go @@ -0,0 +1,1907 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/lightclients/tendermint/v1/tendermint.proto + +package tendermint + +import ( + fmt "fmt" + github_com_cometbft_cometbft_libs_bytes "github.com/cometbft/cometbft/libs/bytes" + types2 "github.com/cometbft/cometbft/proto/tendermint/types" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + github_com_cosmos_gogoproto_types "github.com/cosmos/gogoproto/types" + types "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + types1 "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + _go "github.com/cosmos/ics23/go" + _ "google.golang.org/protobuf/types/known/durationpb" + _ "google.golang.org/protobuf/types/known/timestamppb" + io "io" + math "math" + math_bits "math/bits" + time "time" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf +var _ = time.Kitchen + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// ClientState from Tendermint tracks the current validator set, latest height, +// and a possible frozen height. +type ClientState struct { + ChainId string `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` + TrustLevel Fraction `protobuf:"bytes,2,opt,name=trust_level,json=trustLevel,proto3" json:"trust_level"` + // duration of the period since the LatestTimestamp during which the + // submitted headers are valid for upgrade + TrustingPeriod time.Duration `protobuf:"bytes,3,opt,name=trusting_period,json=trustingPeriod,proto3,stdduration" json:"trusting_period"` + // duration of the staking unbonding period + UnbondingPeriod time.Duration `protobuf:"bytes,4,opt,name=unbonding_period,json=unbondingPeriod,proto3,stdduration" json:"unbonding_period"` + // defines how much new (untrusted) header's Time can drift into the future. + MaxClockDrift time.Duration `protobuf:"bytes,5,opt,name=max_clock_drift,json=maxClockDrift,proto3,stdduration" json:"max_clock_drift"` + // Block height when the client was frozen due to a misbehaviour + FrozenHeight types.Height `protobuf:"bytes,6,opt,name=frozen_height,json=frozenHeight,proto3" json:"frozen_height"` + // Latest height the client was updated to + LatestHeight types.Height `protobuf:"bytes,7,opt,name=latest_height,json=latestHeight,proto3" json:"latest_height"` + // Proof specifications used in verifying counterparty state + ProofSpecs []*_go.ProofSpec `protobuf:"bytes,8,rep,name=proof_specs,json=proofSpecs,proto3" json:"proof_specs,omitempty"` + // Path at which next upgraded client will be committed. + // Each element corresponds to the key for a single CommitmentProof in the + // chained proof. NOTE: ClientState must stored under + // `{upgradePath}/{upgradeHeight}/clientState` ConsensusState must be stored + // under `{upgradepath}/{upgradeHeight}/consensusState` For SDK chains using + // the default upgrade module, upgrade_path should be []string{"upgrade", + // "upgradedIBCState"}` + UpgradePath []string `protobuf:"bytes,9,rep,name=upgrade_path,json=upgradePath,proto3" json:"upgrade_path,omitempty"` + // allow_update_after_expiry is deprecated + AllowUpdateAfterExpiry bool `protobuf:"varint,10,opt,name=allow_update_after_expiry,json=allowUpdateAfterExpiry,proto3" json:"allow_update_after_expiry,omitempty"` // Deprecated: Do not use. + // allow_update_after_misbehaviour is deprecated + AllowUpdateAfterMisbehaviour bool `protobuf:"varint,11,opt,name=allow_update_after_misbehaviour,json=allowUpdateAfterMisbehaviour,proto3" json:"allow_update_after_misbehaviour,omitempty"` // Deprecated: Do not use. +} + +func (m *ClientState) Reset() { *m = ClientState{} } +func (m *ClientState) String() string { return proto.CompactTextString(m) } +func (*ClientState) ProtoMessage() {} +func (*ClientState) Descriptor() ([]byte, []int) { + return fileDescriptor_c6d6cf2b288949be, []int{0} +} +func (m *ClientState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ClientState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ClientState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ClientState) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClientState.Merge(m, src) +} +func (m *ClientState) XXX_Size() int { + return m.Size() +} +func (m *ClientState) XXX_DiscardUnknown() { + xxx_messageInfo_ClientState.DiscardUnknown(m) +} + +var xxx_messageInfo_ClientState proto.InternalMessageInfo + +// ConsensusState defines the consensus state from Tendermint. +type ConsensusState struct { + // timestamp that corresponds to the block height in which the ConsensusState + // was stored. + Timestamp time.Time `protobuf:"bytes,1,opt,name=timestamp,proto3,stdtime" json:"timestamp"` + // commitment root (i.e app hash) + Root types1.MerkleRoot `protobuf:"bytes,2,opt,name=root,proto3" json:"root"` + NextValidatorsHash github_com_cometbft_cometbft_libs_bytes.HexBytes `protobuf:"bytes,3,opt,name=next_validators_hash,json=nextValidatorsHash,proto3,casttype=github.com/cometbft/cometbft/libs/bytes.HexBytes" json:"next_validators_hash,omitempty"` +} + +func (m *ConsensusState) Reset() { *m = ConsensusState{} } +func (m *ConsensusState) String() string { return proto.CompactTextString(m) } +func (*ConsensusState) ProtoMessage() {} +func (*ConsensusState) Descriptor() ([]byte, []int) { + return fileDescriptor_c6d6cf2b288949be, []int{1} +} +func (m *ConsensusState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ConsensusState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ConsensusState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ConsensusState) XXX_Merge(src proto.Message) { + xxx_messageInfo_ConsensusState.Merge(m, src) +} +func (m *ConsensusState) XXX_Size() int { + return m.Size() +} +func (m *ConsensusState) XXX_DiscardUnknown() { + xxx_messageInfo_ConsensusState.DiscardUnknown(m) +} + +var xxx_messageInfo_ConsensusState proto.InternalMessageInfo + +// Misbehaviour is a wrapper over two conflicting Headers +// that implements Misbehaviour interface expected by ICS-02 +type Misbehaviour struct { + // ClientID is deprecated + ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` // Deprecated: Do not use. + Header1 *Header `protobuf:"bytes,2,opt,name=header_1,json=header1,proto3" json:"header_1,omitempty"` + Header2 *Header `protobuf:"bytes,3,opt,name=header_2,json=header2,proto3" json:"header_2,omitempty"` +} + +func (m *Misbehaviour) Reset() { *m = Misbehaviour{} } +func (m *Misbehaviour) String() string { return proto.CompactTextString(m) } +func (*Misbehaviour) ProtoMessage() {} +func (*Misbehaviour) Descriptor() ([]byte, []int) { + return fileDescriptor_c6d6cf2b288949be, []int{2} +} +func (m *Misbehaviour) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Misbehaviour) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Misbehaviour.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Misbehaviour) XXX_Merge(src proto.Message) { + xxx_messageInfo_Misbehaviour.Merge(m, src) +} +func (m *Misbehaviour) XXX_Size() int { + return m.Size() +} +func (m *Misbehaviour) XXX_DiscardUnknown() { + xxx_messageInfo_Misbehaviour.DiscardUnknown(m) +} + +var xxx_messageInfo_Misbehaviour proto.InternalMessageInfo + +// Header defines the Tendermint client consensus Header. +// It encapsulates all the information necessary to update from a trusted +// Tendermint ConsensusState. The inclusion of TrustedHeight and +// TrustedValidators allows this update to process correctly, so long as the +// ConsensusState for the TrustedHeight exists, this removes race conditions +// among relayers The SignedHeader and ValidatorSet are the new untrusted update +// fields for the client. The TrustedHeight is the height of a stored +// ConsensusState on the client that will be used to verify the new untrusted +// header. The Trusted ConsensusState must be within the unbonding period of +// current time in order to correctly verify, and the TrustedValidators must +// hash to TrustedConsensusState.NextValidatorsHash since that is the last +// trusted validator set at the TrustedHeight. +type Header struct { + *types2.SignedHeader `protobuf:"bytes,1,opt,name=signed_header,json=signedHeader,proto3,embedded=signed_header" json:"signed_header,omitempty"` + ValidatorSet *types2.ValidatorSet `protobuf:"bytes,2,opt,name=validator_set,json=validatorSet,proto3" json:"validator_set,omitempty"` + TrustedHeight types.Height `protobuf:"bytes,3,opt,name=trusted_height,json=trustedHeight,proto3" json:"trusted_height"` + TrustedValidators *types2.ValidatorSet `protobuf:"bytes,4,opt,name=trusted_validators,json=trustedValidators,proto3" json:"trusted_validators,omitempty"` +} + +func (m *Header) Reset() { *m = Header{} } +func (m *Header) String() string { return proto.CompactTextString(m) } +func (*Header) ProtoMessage() {} +func (*Header) Descriptor() ([]byte, []int) { + return fileDescriptor_c6d6cf2b288949be, []int{3} +} +func (m *Header) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Header) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Header.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Header) XXX_Merge(src proto.Message) { + xxx_messageInfo_Header.Merge(m, src) +} +func (m *Header) XXX_Size() int { + return m.Size() +} +func (m *Header) XXX_DiscardUnknown() { + xxx_messageInfo_Header.DiscardUnknown(m) +} + +var xxx_messageInfo_Header proto.InternalMessageInfo + +func (m *Header) GetValidatorSet() *types2.ValidatorSet { + if m != nil { + return m.ValidatorSet + } + return nil +} + +func (m *Header) GetTrustedHeight() types.Height { + if m != nil { + return m.TrustedHeight + } + return types.Height{} +} + +func (m *Header) GetTrustedValidators() *types2.ValidatorSet { + if m != nil { + return m.TrustedValidators + } + return nil +} + +// Fraction defines the protobuf message type for tmmath.Fraction that only +// supports positive values. +type Fraction struct { + Numerator uint64 `protobuf:"varint,1,opt,name=numerator,proto3" json:"numerator,omitempty"` + Denominator uint64 `protobuf:"varint,2,opt,name=denominator,proto3" json:"denominator,omitempty"` +} + +func (m *Fraction) Reset() { *m = Fraction{} } +func (m *Fraction) String() string { return proto.CompactTextString(m) } +func (*Fraction) ProtoMessage() {} +func (*Fraction) Descriptor() ([]byte, []int) { + return fileDescriptor_c6d6cf2b288949be, []int{4} +} +func (m *Fraction) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Fraction) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Fraction.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Fraction) XXX_Merge(src proto.Message) { + xxx_messageInfo_Fraction.Merge(m, src) +} +func (m *Fraction) XXX_Size() int { + return m.Size() +} +func (m *Fraction) XXX_DiscardUnknown() { + xxx_messageInfo_Fraction.DiscardUnknown(m) +} + +var xxx_messageInfo_Fraction proto.InternalMessageInfo + +func (m *Fraction) GetNumerator() uint64 { + if m != nil { + return m.Numerator + } + return 0 +} + +func (m *Fraction) GetDenominator() uint64 { + if m != nil { + return m.Denominator + } + return 0 +} + +func init() { + proto.RegisterType((*ClientState)(nil), "ibc.lightclients.tendermint.v1.ClientState") + proto.RegisterType((*ConsensusState)(nil), "ibc.lightclients.tendermint.v1.ConsensusState") + proto.RegisterType((*Misbehaviour)(nil), "ibc.lightclients.tendermint.v1.Misbehaviour") + proto.RegisterType((*Header)(nil), "ibc.lightclients.tendermint.v1.Header") + proto.RegisterType((*Fraction)(nil), "ibc.lightclients.tendermint.v1.Fraction") +} + +func init() { + proto.RegisterFile("ibc/lightclients/tendermint/v1/tendermint.proto", fileDescriptor_c6d6cf2b288949be) +} + +var fileDescriptor_c6d6cf2b288949be = []byte{ + // 942 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x95, 0x4d, 0x6f, 0xe3, 0x44, + 0x18, 0xc7, 0xe3, 0x34, 0xbb, 0x4d, 0x26, 0xc9, 0x16, 0x46, 0x2b, 0xe4, 0x56, 0x55, 0x12, 0x7a, + 0x80, 0x5c, 0x6a, 0x37, 0x59, 0x24, 0x24, 0x16, 0x0e, 0xa4, 0xbb, 0xd0, 0xb2, 0x5b, 0xa8, 0x5c, + 0xe0, 0xc0, 0xc5, 0x1a, 0xdb, 0x13, 0x7b, 0xb4, 0xb6, 0xc7, 0x9a, 0x19, 0x87, 0x94, 0x13, 0x47, + 0x8e, 0x7b, 0xe4, 0xc8, 0x47, 0xe0, 0x63, 0xec, 0xb1, 0x17, 0x24, 0x4e, 0x05, 0xa5, 0xdf, 0x82, + 0x13, 0x9a, 0x17, 0x27, 0xa6, 0xac, 0xd8, 0x8a, 0x4b, 0xf4, 0xcc, 0x3c, 0xff, 0xe7, 0x97, 0x99, + 0xe7, 0x65, 0x0c, 0x5c, 0x12, 0x84, 0x6e, 0x4a, 0xe2, 0x44, 0x84, 0x29, 0xc1, 0xb9, 0xe0, 0xae, + 0xc0, 0x79, 0x84, 0x59, 0x46, 0x72, 0xe1, 0x2e, 0x26, 0xb5, 0x95, 0x53, 0x30, 0x2a, 0x28, 0x1c, + 0x90, 0x20, 0x74, 0xea, 0x01, 0x4e, 0x4d, 0xb2, 0x98, 0xec, 0x8d, 0x6a, 0xf1, 0xe2, 0xb2, 0xc0, + 0xdc, 0x5d, 0xa0, 0x94, 0x44, 0x48, 0x50, 0xa6, 0x09, 0x7b, 0xfb, 0xff, 0x52, 0xa8, 0xdf, 0xca, + 0x1b, 0x52, 0x9e, 0x51, 0xee, 0x92, 0x90, 0x4f, 0x1f, 0xc9, 0x13, 0x14, 0x8c, 0xd2, 0x79, 0xe5, + 0x1d, 0xc4, 0x94, 0xc6, 0x29, 0x76, 0xd5, 0x2a, 0x28, 0xe7, 0x6e, 0x54, 0x32, 0x24, 0x08, 0xcd, + 0x8d, 0x7f, 0x78, 0xdb, 0x2f, 0x48, 0x86, 0xb9, 0x40, 0x59, 0x51, 0x09, 0xe4, 0x7d, 0x43, 0xca, + 0xb0, 0xab, 0x8f, 0x2f, 0xff, 0x41, 0x5b, 0x46, 0xf0, 0xfe, 0x46, 0x40, 0xb3, 0x8c, 0x88, 0xac, + 0x12, 0xad, 0x57, 0x46, 0xf8, 0x30, 0xa6, 0x31, 0x55, 0xa6, 0x2b, 0x2d, 0xbd, 0x7b, 0xb0, 0xba, + 0x07, 0xba, 0xc7, 0x8a, 0x77, 0x21, 0x90, 0xc0, 0x70, 0x17, 0xb4, 0xc3, 0x04, 0x91, 0xdc, 0x27, + 0x91, 0x6d, 0x8d, 0xac, 0x71, 0xc7, 0xdb, 0x56, 0xeb, 0xd3, 0x08, 0x7e, 0x05, 0xba, 0x82, 0x95, + 0x5c, 0xf8, 0x29, 0x5e, 0xe0, 0xd4, 0x6e, 0x8e, 0xac, 0x71, 0x77, 0x3a, 0x76, 0xfe, 0x3b, 0xbf, + 0xce, 0x67, 0x0c, 0x85, 0xf2, 0xc2, 0xb3, 0xd6, 0xab, 0xeb, 0x61, 0xc3, 0x03, 0x0a, 0xf1, 0x5c, + 0x12, 0xe0, 0x73, 0xb0, 0xa3, 0x56, 0x24, 0x8f, 0xfd, 0x02, 0x33, 0x42, 0x23, 0x7b, 0x4b, 0x41, + 0x77, 0x1d, 0x9d, 0x16, 0xa7, 0x4a, 0x8b, 0xf3, 0xc4, 0xa4, 0x6d, 0xd6, 0x96, 0x94, 0x9f, 0xff, + 0x18, 0x5a, 0xde, 0x83, 0x2a, 0xf6, 0x5c, 0x85, 0xc2, 0x2f, 0xc1, 0x5b, 0x65, 0x1e, 0xd0, 0x3c, + 0xaa, 0xe1, 0x5a, 0x77, 0xc7, 0xed, 0xac, 0x83, 0x0d, 0xef, 0x19, 0xd8, 0xc9, 0xd0, 0xd2, 0x0f, + 0x53, 0x1a, 0xbe, 0xf0, 0x23, 0x46, 0xe6, 0xc2, 0xbe, 0x77, 0x77, 0x5c, 0x3f, 0x43, 0xcb, 0x63, + 0x19, 0xfa, 0x44, 0x46, 0xc2, 0xa7, 0xa0, 0x3f, 0x67, 0xf4, 0x07, 0x9c, 0xfb, 0x09, 0x96, 0xb9, + 0xb2, 0xef, 0x2b, 0xd4, 0x9e, 0xca, 0x9e, 0xac, 0x9e, 0x63, 0x8a, 0xba, 0x98, 0x38, 0x27, 0x4a, + 0x61, 0xf2, 0xd5, 0xd3, 0x61, 0x7a, 0x4f, 0x62, 0x52, 0x24, 0x30, 0x17, 0x15, 0x66, 0xfb, 0xae, + 0x18, 0x1d, 0x66, 0x30, 0x8f, 0x41, 0x57, 0x75, 0xa9, 0xcf, 0x0b, 0x1c, 0x72, 0xbb, 0x3d, 0xda, + 0x52, 0x10, 0xdd, 0xc9, 0x8e, 0xea, 0x64, 0x49, 0x38, 0x97, 0x9a, 0x8b, 0x02, 0x87, 0x1e, 0x28, + 0x2a, 0x93, 0xc3, 0x77, 0x41, 0xaf, 0x2c, 0x62, 0x86, 0x22, 0xec, 0x17, 0x48, 0x24, 0x76, 0x67, + 0xb4, 0x35, 0xee, 0x78, 0x5d, 0xb3, 0x77, 0x8e, 0x44, 0x02, 0x3f, 0x01, 0xbb, 0x28, 0x4d, 0xe9, + 0xf7, 0x7e, 0x59, 0x44, 0x48, 0x60, 0x1f, 0xcd, 0x05, 0x66, 0x3e, 0x5e, 0x16, 0x84, 0x5d, 0xda, + 0x60, 0x64, 0x8d, 0xdb, 0xb3, 0xa6, 0x6d, 0x79, 0xef, 0x28, 0xd1, 0x37, 0x4a, 0xf3, 0xa9, 0x94, + 0x3c, 0x55, 0x0a, 0x78, 0x0a, 0x86, 0xaf, 0x09, 0xcf, 0x08, 0x0f, 0x70, 0x82, 0x16, 0x84, 0x96, + 0xcc, 0xee, 0xae, 0x21, 0xfb, 0xb7, 0x21, 0x67, 0x35, 0xdd, 0x47, 0xad, 0x9f, 0x7e, 0x19, 0x36, + 0x0e, 0x7e, 0x6c, 0x82, 0x07, 0xc7, 0x34, 0xe7, 0x38, 0xe7, 0x25, 0xd7, 0x7d, 0x3e, 0x03, 0x9d, + 0xf5, 0xa8, 0xa9, 0x46, 0x97, 0x09, 0xb8, 0x5d, 0xd7, 0xaf, 0x2b, 0x85, 0x2e, 0xec, 0x4b, 0x59, + 0xd8, 0x4d, 0x18, 0xfc, 0x18, 0xb4, 0x18, 0xa5, 0xc2, 0x4c, 0xc2, 0x41, 0xad, 0x08, 0x9b, 0xd9, + 0x5b, 0x4c, 0x9c, 0x33, 0xcc, 0x5e, 0xa4, 0xd8, 0xa3, 0xb4, 0x2a, 0x86, 0x8a, 0x82, 0x73, 0xf0, + 0x30, 0xc7, 0x4b, 0xe1, 0xaf, 0x9f, 0x1b, 0xee, 0x27, 0x88, 0x27, 0x6a, 0x04, 0x7a, 0xb3, 0x0f, + 0xfe, 0xba, 0x1e, 0x1e, 0xc5, 0x44, 0x24, 0x65, 0x20, 0x71, 0x72, 0x9c, 0xb1, 0x08, 0xe6, 0x62, + 0x63, 0xa4, 0x24, 0xe0, 0x6e, 0x70, 0x29, 0x30, 0x77, 0x4e, 0xf0, 0x72, 0x26, 0x0d, 0x0f, 0x4a, + 0xe2, 0xb7, 0x6b, 0xe0, 0x09, 0xe2, 0x89, 0x49, 0xc1, 0x6f, 0x16, 0xe8, 0xd5, 0x33, 0x03, 0x87, + 0xa0, 0xa3, 0x7b, 0x65, 0x3d, 0xe9, 0x2a, 0x9d, 0x6d, 0xbd, 0x79, 0x2a, 0xe7, 0xa9, 0x9d, 0x60, + 0x14, 0x61, 0xe6, 0x4f, 0xcc, 0x0d, 0xdf, 0x7b, 0xd3, 0xac, 0x9f, 0x28, 0xfd, 0xac, 0xbb, 0xba, + 0x1e, 0x6e, 0x6b, 0x7b, 0xe2, 0x6d, 0x6b, 0xc8, 0xa4, 0xc6, 0x9b, 0x9a, 0x31, 0xff, 0x1f, 0xbc, + 0x69, 0xc5, 0x9b, 0x9a, 0x7b, 0xfd, 0xda, 0x04, 0xf7, 0xb5, 0x0b, 0x9e, 0x82, 0x3e, 0x27, 0x71, + 0x8e, 0x23, 0x5f, 0x4b, 0x4c, 0x59, 0x07, 0x75, 0xa8, 0x7e, 0xb9, 0x2f, 0x94, 0xcc, 0xd0, 0x5b, + 0x57, 0xd7, 0x43, 0xcb, 0xeb, 0xf1, 0xda, 0x1e, 0x3c, 0x06, 0xfd, 0x75, 0x59, 0x7c, 0x8e, 0xab, + 0x12, 0xbf, 0x06, 0xb5, 0x4e, 0xf6, 0x05, 0x16, 0x5e, 0x6f, 0x51, 0x5b, 0xc1, 0xcf, 0x81, 0x7e, + 0xa2, 0xd4, 0x81, 0xd4, 0xb4, 0x6e, 0xdd, 0x71, 0x5a, 0xfb, 0x26, 0xce, 0x8c, 0xeb, 0x19, 0x80, + 0x15, 0x68, 0xd3, 0x2c, 0xe6, 0x6d, 0x7b, 0xd3, 0x91, 0xde, 0x36, 0x91, 0x9b, 0xa6, 0x38, 0xf8, + 0x02, 0xb4, 0xab, 0x47, 0x19, 0xee, 0x83, 0x4e, 0x5e, 0x66, 0x98, 0x49, 0x8f, 0xca, 0x57, 0xcb, + 0xdb, 0x6c, 0xc0, 0x11, 0xe8, 0x46, 0x38, 0xa7, 0x19, 0xc9, 0x95, 0xbf, 0xa9, 0xfc, 0xf5, 0xad, + 0x19, 0x7e, 0xb5, 0x1a, 0x58, 0x57, 0xab, 0x81, 0xf5, 0xe7, 0x6a, 0x60, 0xbd, 0xbc, 0x19, 0x34, + 0xae, 0x6e, 0x06, 0x8d, 0xdf, 0x6f, 0x06, 0x8d, 0xef, 0x9e, 0xfd, 0xa3, 0x79, 0xf5, 0x27, 0x32, + 0x08, 0x0f, 0x63, 0xea, 0x2e, 0x26, 0x47, 0x6e, 0x46, 0xa3, 0x32, 0xc5, 0x5c, 0x7f, 0xc9, 0x0f, + 0xab, 0x4f, 0xf9, 0xd1, 0x87, 0x87, 0x9b, 0xdb, 0x3c, 0xde, 0x98, 0xc1, 0x7d, 0x35, 0x92, 0x8f, + 0xfe, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x38, 0x68, 0x4c, 0x64, 0xfe, 0x07, 0x00, 0x00, +} + +func (m *ClientState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ClientState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ClientState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.AllowUpdateAfterMisbehaviour { + i-- + if m.AllowUpdateAfterMisbehaviour { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x58 + } + if m.AllowUpdateAfterExpiry { + i-- + if m.AllowUpdateAfterExpiry { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x50 + } + if len(m.UpgradePath) > 0 { + for iNdEx := len(m.UpgradePath) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.UpgradePath[iNdEx]) + copy(dAtA[i:], m.UpgradePath[iNdEx]) + i = encodeVarintTendermint(dAtA, i, uint64(len(m.UpgradePath[iNdEx]))) + i-- + dAtA[i] = 0x4a + } + } + if len(m.ProofSpecs) > 0 { + for iNdEx := len(m.ProofSpecs) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ProofSpecs[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTendermint(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x42 + } + } + { + size, err := m.LatestHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTendermint(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + { + size, err := m.FrozenHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTendermint(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + n3, err3 := github_com_cosmos_gogoproto_types.StdDurationMarshalTo(m.MaxClockDrift, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.MaxClockDrift):]) + if err3 != nil { + return 0, err3 + } + i -= n3 + i = encodeVarintTendermint(dAtA, i, uint64(n3)) + i-- + dAtA[i] = 0x2a + n4, err4 := github_com_cosmos_gogoproto_types.StdDurationMarshalTo(m.UnbondingPeriod, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.UnbondingPeriod):]) + if err4 != nil { + return 0, err4 + } + i -= n4 + i = encodeVarintTendermint(dAtA, i, uint64(n4)) + i-- + dAtA[i] = 0x22 + n5, err5 := github_com_cosmos_gogoproto_types.StdDurationMarshalTo(m.TrustingPeriod, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.TrustingPeriod):]) + if err5 != nil { + return 0, err5 + } + i -= n5 + i = encodeVarintTendermint(dAtA, i, uint64(n5)) + i-- + dAtA[i] = 0x1a + { + size, err := m.TrustLevel.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTendermint(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.ChainId) > 0 { + i -= len(m.ChainId) + copy(dAtA[i:], m.ChainId) + i = encodeVarintTendermint(dAtA, i, uint64(len(m.ChainId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ConsensusState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ConsensusState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ConsensusState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.NextValidatorsHash) > 0 { + i -= len(m.NextValidatorsHash) + copy(dAtA[i:], m.NextValidatorsHash) + i = encodeVarintTendermint(dAtA, i, uint64(len(m.NextValidatorsHash))) + i-- + dAtA[i] = 0x1a + } + { + size, err := m.Root.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTendermint(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + n8, err8 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(m.Timestamp, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(m.Timestamp):]) + if err8 != nil { + return 0, err8 + } + i -= n8 + i = encodeVarintTendermint(dAtA, i, uint64(n8)) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *Misbehaviour) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Misbehaviour) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Misbehaviour) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Header2 != nil { + { + size, err := m.Header2.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTendermint(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.Header1 != nil { + { + size, err := m.Header1.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTendermint(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintTendermint(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Header) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Header) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Header) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.TrustedValidators != nil { + { + size, err := m.TrustedValidators.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTendermint(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + { + size, err := m.TrustedHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTendermint(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if m.ValidatorSet != nil { + { + size, err := m.ValidatorSet.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTendermint(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.SignedHeader != nil { + { + size, err := m.SignedHeader.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTendermint(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Fraction) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Fraction) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Fraction) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Denominator != 0 { + i = encodeVarintTendermint(dAtA, i, uint64(m.Denominator)) + i-- + dAtA[i] = 0x10 + } + if m.Numerator != 0 { + i = encodeVarintTendermint(dAtA, i, uint64(m.Numerator)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintTendermint(dAtA []byte, offset int, v uint64) int { + offset -= sovTendermint(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *ClientState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ChainId) + if l > 0 { + n += 1 + l + sovTendermint(uint64(l)) + } + l = m.TrustLevel.Size() + n += 1 + l + sovTendermint(uint64(l)) + l = github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.TrustingPeriod) + n += 1 + l + sovTendermint(uint64(l)) + l = github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.UnbondingPeriod) + n += 1 + l + sovTendermint(uint64(l)) + l = github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.MaxClockDrift) + n += 1 + l + sovTendermint(uint64(l)) + l = m.FrozenHeight.Size() + n += 1 + l + sovTendermint(uint64(l)) + l = m.LatestHeight.Size() + n += 1 + l + sovTendermint(uint64(l)) + if len(m.ProofSpecs) > 0 { + for _, e := range m.ProofSpecs { + l = e.Size() + n += 1 + l + sovTendermint(uint64(l)) + } + } + if len(m.UpgradePath) > 0 { + for _, s := range m.UpgradePath { + l = len(s) + n += 1 + l + sovTendermint(uint64(l)) + } + } + if m.AllowUpdateAfterExpiry { + n += 2 + } + if m.AllowUpdateAfterMisbehaviour { + n += 2 + } + return n +} + +func (m *ConsensusState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = github_com_cosmos_gogoproto_types.SizeOfStdTime(m.Timestamp) + n += 1 + l + sovTendermint(uint64(l)) + l = m.Root.Size() + n += 1 + l + sovTendermint(uint64(l)) + l = len(m.NextValidatorsHash) + if l > 0 { + n += 1 + l + sovTendermint(uint64(l)) + } + return n +} + +func (m *Misbehaviour) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovTendermint(uint64(l)) + } + if m.Header1 != nil { + l = m.Header1.Size() + n += 1 + l + sovTendermint(uint64(l)) + } + if m.Header2 != nil { + l = m.Header2.Size() + n += 1 + l + sovTendermint(uint64(l)) + } + return n +} + +func (m *Header) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SignedHeader != nil { + l = m.SignedHeader.Size() + n += 1 + l + sovTendermint(uint64(l)) + } + if m.ValidatorSet != nil { + l = m.ValidatorSet.Size() + n += 1 + l + sovTendermint(uint64(l)) + } + l = m.TrustedHeight.Size() + n += 1 + l + sovTendermint(uint64(l)) + if m.TrustedValidators != nil { + l = m.TrustedValidators.Size() + n += 1 + l + sovTendermint(uint64(l)) + } + return n +} + +func (m *Fraction) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Numerator != 0 { + n += 1 + sovTendermint(uint64(m.Numerator)) + } + if m.Denominator != 0 { + n += 1 + sovTendermint(uint64(m.Denominator)) + } + return n +} + +func sovTendermint(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTendermint(x uint64) (n int) { + return sovTendermint(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *ClientState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ClientState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ClientState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChainId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTendermint + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTendermint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChainId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TrustLevel", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTendermint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTendermint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.TrustLevel.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TrustingPeriod", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTendermint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTendermint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_cosmos_gogoproto_types.StdDurationUnmarshal(&m.TrustingPeriod, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UnbondingPeriod", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTendermint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTendermint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_cosmos_gogoproto_types.StdDurationUnmarshal(&m.UnbondingPeriod, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxClockDrift", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTendermint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTendermint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_cosmos_gogoproto_types.StdDurationUnmarshal(&m.MaxClockDrift, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FrozenHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTendermint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTendermint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.FrozenHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LatestHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTendermint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTendermint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.LatestHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProofSpecs", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTendermint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTendermint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ProofSpecs = append(m.ProofSpecs, &_go.ProofSpec{}) + if err := m.ProofSpecs[len(m.ProofSpecs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UpgradePath", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTendermint + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTendermint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.UpgradePath = append(m.UpgradePath, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowUpdateAfterExpiry", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.AllowUpdateAfterExpiry = bool(v != 0) + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowUpdateAfterMisbehaviour", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.AllowUpdateAfterMisbehaviour = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipTendermint(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTendermint + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ConsensusState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ConsensusState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ConsensusState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTendermint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTendermint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_cosmos_gogoproto_types.StdTimeUnmarshal(&m.Timestamp, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Root", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTendermint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTendermint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Root.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NextValidatorsHash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTendermint + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTendermint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NextValidatorsHash = append(m.NextValidatorsHash[:0], dAtA[iNdEx:postIndex]...) + if m.NextValidatorsHash == nil { + m.NextValidatorsHash = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTendermint(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTendermint + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Misbehaviour) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Misbehaviour: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Misbehaviour: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTendermint + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTendermint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Header1", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTendermint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTendermint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Header1 == nil { + m.Header1 = &Header{} + } + if err := m.Header1.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Header2", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTendermint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTendermint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Header2 == nil { + m.Header2 = &Header{} + } + if err := m.Header2.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTendermint(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTendermint + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Header) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Header: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Header: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SignedHeader", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTendermint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTendermint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.SignedHeader == nil { + m.SignedHeader = &types2.SignedHeader{} + } + if err := m.SignedHeader.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorSet", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTendermint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTendermint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ValidatorSet == nil { + m.ValidatorSet = &types2.ValidatorSet{} + } + if err := m.ValidatorSet.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TrustedHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTendermint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTendermint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.TrustedHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TrustedValidators", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTendermint + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTendermint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.TrustedValidators == nil { + m.TrustedValidators = &types2.ValidatorSet{} + } + if err := m.TrustedValidators.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTendermint(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTendermint + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Fraction) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Fraction: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Fraction: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Numerator", wireType) + } + m.Numerator = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Numerator |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Denominator", wireType) + } + m.Denominator = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTendermint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Denominator |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTendermint(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTendermint + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTendermint(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTendermint + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTendermint + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTendermint + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTendermint + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTendermint + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTendermint + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTendermint = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTendermint = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTendermint = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/light-clients/07-tendermint/tendermint_test.go b/modules/light-clients/07-tendermint/tendermint_test.go new file mode 100644 index 0000000..979bbf1 --- /dev/null +++ b/modules/light-clients/07-tendermint/tendermint_test.go @@ -0,0 +1,115 @@ +package tendermint_test + +import ( + "testing" + "time" + + testifysuite "github.com/stretchr/testify/suite" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + cmtbytes "github.com/cometbft/cometbft/libs/bytes" + cmttypes "github.com/cometbft/cometbft/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + "github.com/cosmos/ibc-go/v10/testing/simapp" +) + +const ( + chainID = "gaia" + chainIDRevision0 = "gaia-revision-0" + chainIDRevision1 = "gaia-revision-1" + clientID = "gaiamainnet" + trustingPeriod time.Duration = time.Hour * 24 * 7 * 2 + ubdPeriod time.Duration = time.Hour * 24 * 7 * 3 + maxClockDrift time.Duration = time.Second * 10 +) + +var ( + height = clienttypes.NewHeight(0, 4) + newClientHeight = clienttypes.NewHeight(1, 1) + upgradePath = []string{"upgrade", "upgradedIBCState"} + invalidUpgradePath = []string{"upgrade", ""} +) + +type TendermintTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains used for convenience and readability + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain + + // TODO: deprecate usage in favor of testing package + ctx sdk.Context + cdc codec.Codec + privVal cmttypes.PrivValidator + valSet *cmttypes.ValidatorSet + signers map[string]cmttypes.PrivValidator + valsHash cmtbytes.HexBytes + header *ibctm.Header + now time.Time + headerTime time.Time + clientTime time.Time +} + +func (suite *TendermintTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) + // commit some blocks so that QueryProof returns valid proof (cannot return valid query if height <= 1) + suite.coordinator.CommitNBlocks(suite.chainA, 2) + suite.coordinator.CommitNBlocks(suite.chainB, 2) + + // TODO: deprecate usage in favor of testing package + checkTx := false + app := simapp.Setup(suite.T(), checkTx) + + suite.cdc = app.AppCodec() + + // now is the time of the current chain, must be after the updating header + // mocks ctx.BlockTime() + suite.now = time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC) + suite.clientTime = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) + // Header time is intended to be time for any new header used for updates + suite.headerTime = time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC) + + suite.privVal = cmttypes.NewMockPV() + + pubKey, err := suite.privVal.GetPubKey() + suite.Require().NoError(err) + + heightMinus1 := clienttypes.NewHeight(0, height.RevisionHeight-1) + + val := cmttypes.NewValidator(pubKey, 10) + suite.signers = make(map[string]cmttypes.PrivValidator) + suite.signers[val.Address.String()] = suite.privVal + suite.valSet = cmttypes.NewValidatorSet([]*cmttypes.Validator{val}) + suite.valsHash = suite.valSet.Hash() + suite.header = suite.chainA.CreateTMClientHeader(chainID, int64(height.RevisionHeight), heightMinus1, suite.now, suite.valSet, suite.valSet, suite.valSet, suite.signers) + suite.ctx = app.NewContext(checkTx) +} + +func getAltSigners(altVal *cmttypes.Validator, altPrivVal cmttypes.PrivValidator) map[string]cmttypes.PrivValidator { + return map[string]cmttypes.PrivValidator{altVal.Address.String(): altPrivVal} +} + +func getBothSigners(suite *TendermintTestSuite, altVal *cmttypes.Validator, altPrivVal cmttypes.PrivValidator) (*cmttypes.ValidatorSet, map[string]cmttypes.PrivValidator) { + // Create bothValSet with both suite validator and altVal. Would be valid update + bothValSet := cmttypes.NewValidatorSet(append(suite.valSet.Validators, altVal)) + // Create signer array and ensure it is in same order as bothValSet + _, suiteVal := suite.valSet.GetByIndex(0) + bothSigners := map[string]cmttypes.PrivValidator{ + suiteVal.Address.String(): suite.privVal, + altVal.Address.String(): altPrivVal, + } + return bothValSet, bothSigners +} + +func TestTendermintTestSuite(t *testing.T) { + testifysuite.Run(t, new(TendermintTestSuite)) +} diff --git a/modules/light-clients/07-tendermint/update.go b/modules/light-clients/07-tendermint/update.go new file mode 100644 index 0000000..979c8e0 --- /dev/null +++ b/modules/light-clients/07-tendermint/update.go @@ -0,0 +1,235 @@ +package tendermint + +import ( + "bytes" + "fmt" + + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cometbft/cometbft/light" + cmttypes "github.com/cometbft/cometbft/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// VerifyClientMessage checks if the clientMessage is of type Header or Misbehaviour and verifies the message +func (cs *ClientState) VerifyClientMessage( + ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, + clientMsg exported.ClientMessage, +) error { + switch msg := clientMsg.(type) { + case *Header: + return cs.verifyHeader(ctx, clientStore, cdc, msg) + case *Misbehaviour: + return cs.verifyMisbehaviour(ctx, clientStore, cdc, msg) + default: + return clienttypes.ErrInvalidClientType + } +} + +// verifyHeader returns an error if: +// - the client or header provided are not parseable to tendermint types +// - the header is invalid +// - header height is less than or equal to the trusted header height +// - header revision is not equal to trusted header revision +// - header valset commit verification fails +// - header timestamp is past the trusting period in relation to the consensus state +// - header timestamp is less than or equal to the consensus state timestamp +func (cs *ClientState) verifyHeader( + ctx sdk.Context, clientStore storetypes.KVStore, cdc codec.BinaryCodec, + header *Header, +) error { + currentTimestamp := ctx.BlockTime() + + // Retrieve trusted consensus states for each Header in misbehaviour + consState, found := GetConsensusState(clientStore, cdc, header.TrustedHeight) + if !found { + return errorsmod.Wrapf(clienttypes.ErrConsensusStateNotFound, "could not get trusted consensus state from clientStore for Header at TrustedHeight: %s", header.TrustedHeight) + } + + if err := checkTrustedHeader(header, consState); err != nil { + return err + } + + // UpdateClient only accepts updates with a header at the same revision + // as the trusted consensus state + if header.GetHeight().GetRevisionNumber() != header.TrustedHeight.RevisionNumber { + return errorsmod.Wrapf( + ErrInvalidHeaderHeight, + "header height revision %d does not match trusted header revision %d", + header.GetHeight().GetRevisionNumber(), header.TrustedHeight.RevisionNumber, + ) + } + + tmTrustedValidators, err := cmttypes.ValidatorSetFromProto(header.TrustedValidators) + if err != nil { + return errorsmod.Wrap(err, "trusted validator set in not tendermint validator set type") + } + + tmSignedHeader, err := cmttypes.SignedHeaderFromProto(header.SignedHeader) + if err != nil { + return errorsmod.Wrap(err, "signed header in not tendermint signed header type") + } + + tmValidatorSet, err := cmttypes.ValidatorSetFromProto(header.ValidatorSet) + if err != nil { + return errorsmod.Wrap(err, "validator set in not tendermint validator set type") + } + + // assert header height is newer than consensus state + if header.GetHeight().LTE(header.TrustedHeight) { + return errorsmod.Wrapf( + clienttypes.ErrInvalidHeader, + "header height ≤ consensus state height (%s ≤ %s)", header.GetHeight(), header.TrustedHeight, + ) + } + + // Construct a trusted header using the fields in consensus state + // Only Height, Time, and NextValidatorsHash are necessary for verification + // NOTE: updates must be within the same revision + trustedHeader := cmttypes.Header{ + ChainID: cs.GetChainID(), + Height: int64(header.TrustedHeight.RevisionHeight), + Time: consState.Timestamp, + NextValidatorsHash: consState.NextValidatorsHash, + } + signedHeader := cmttypes.SignedHeader{ + Header: &trustedHeader, + } + + // Verify next header with the passed-in trustedVals + // - asserts trusting period not passed + // - assert header timestamp is not past the trusting period + // - assert header timestamp is past latest stored consensus state timestamp + // - assert that a TrustLevel proportion of TrustedValidators signed new Commit + err = light.Verify( + &signedHeader, + tmTrustedValidators, tmSignedHeader, tmValidatorSet, + cs.TrustingPeriod, currentTimestamp, cs.MaxClockDrift, cs.TrustLevel.ToTendermint(), + ) + if err != nil { + return errorsmod.Wrap(err, "failed to verify header") + } + + return nil +} + +// UpdateState may be used to either create a consensus state for: +// - a future height greater than the latest client state height +// - a past height that was skipped during bisection +// If we are updating to a past height, a consensus state is created for that height to be persisted in client store +// If we are updating to a future height, the consensus state is created and the client state is updated to reflect +// the new latest height +// A list containing the updated consensus height is returned. +// UpdateState must only be used to update within a single revision, thus header revision number and trusted height's revision +// number must be the same. To update to a new revision, use a separate upgrade path +// UpdateState will prune the oldest consensus state if it is expired. +// If the provided clientMsg is not of type of Header then the handler will noop and empty slice is returned. +func (cs ClientState) UpdateState(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, clientMsg exported.ClientMessage) []exported.Height { + header, ok := clientMsg.(*Header) + if !ok { + // clientMsg is invalid Misbehaviour, no update necessary + return []exported.Height{} + } + + // performance: do not prune in checkTx + // simulation must prune for accurate gas estimation + if (!ctx.IsCheckTx() && !ctx.IsReCheckTx()) || ctx.ExecMode() == sdk.ExecModeSimulate { + cs.pruneOldestConsensusState(ctx, cdc, clientStore) + } + + // check for duplicate update + if _, found := GetConsensusState(clientStore, cdc, header.GetHeight()); found { + // perform no-op + return []exported.Height{header.GetHeight()} + } + + height, ok := header.GetHeight().(clienttypes.Height) + if !ok { + panic(fmt.Errorf("cannot convert %T to %T", header.GetHeight(), &clienttypes.Height{})) + } + if height.GT(cs.LatestHeight) { + cs.LatestHeight = height + } + + consensusState := &ConsensusState{ + Timestamp: header.GetTime(), + Root: commitmenttypes.NewMerkleRoot(header.Header.GetAppHash()), + NextValidatorsHash: header.Header.NextValidatorsHash, + } + + // set client state, consensus state and associated metadata + setClientState(clientStore, cdc, &cs) + setConsensusState(clientStore, cdc, consensusState, header.GetHeight()) + setConsensusMetadata(ctx, clientStore, header.GetHeight()) + + return []exported.Height{height} +} + +// pruneOldestConsensusState will retrieve the earliest consensus state for this clientID and check if it is expired. If it is, +// that consensus state will be pruned from store along with all associated metadata. This will prevent the client store from +// becoming bloated with expired consensus states that can no longer be used for updates and packet verification. +func (cs ClientState) pruneOldestConsensusState(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore) { + // Check the earliest consensus state to see if it is expired, if so then set the prune height + // so that we can delete consensus state and all associated metadata. + var ( + pruneHeight exported.Height + ) + + pruneCb := func(height exported.Height) bool { + consState, found := GetConsensusState(clientStore, cdc, height) + // this error should never occur + if !found { + panic(errorsmod.Wrapf(clienttypes.ErrConsensusStateNotFound, "failed to retrieve consensus state at height: %s", height)) + } + + if cs.IsExpired(consState.Timestamp, ctx.BlockTime()) { + pruneHeight = height + } + + return true + } + + IterateConsensusStateAscending(clientStore, pruneCb) + + // if pruneHeight is set, delete consensus state and metadata + if pruneHeight != nil { + deleteConsensusState(clientStore, pruneHeight) + deleteConsensusMetadata(clientStore, pruneHeight) + } +} + +// UpdateStateOnMisbehaviour updates state upon misbehaviour, freezing the ClientState. This method should only be called when misbehaviour is detected +// as it does not perform any misbehaviour checks. +func (cs ClientState) UpdateStateOnMisbehaviour(ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, _ exported.ClientMessage) { + cs.FrozenHeight = FrozenHeight + + clientStore.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(cdc, &cs)) +} + +// checkTrustedHeader checks that consensus state matches trusted fields of Header +func checkTrustedHeader(header *Header, consState *ConsensusState) error { + tmTrustedValidators, err := cmttypes.ValidatorSetFromProto(header.TrustedValidators) + if err != nil { + return errorsmod.Wrap(err, "trusted validator set in not tendermint validator set type") + } + + // assert that trustedVals is NextValidators of last trusted header + // to do this, we check that trustedVals.Hash() == consState.NextValidatorsHash + tvalHash := tmTrustedValidators.Hash() + if !bytes.Equal(consState.NextValidatorsHash, tvalHash) { + return errorsmod.Wrapf( + ErrInvalidValidatorSet, + "trusted validators %s, does not hash to latest trusted validators. Expected: %X, got: %X", + header.TrustedValidators, consState.NextValidatorsHash, tvalHash, + ) + } + return nil +} diff --git a/modules/light-clients/07-tendermint/update_test.go b/modules/light-clients/07-tendermint/update_test.go new file mode 100644 index 0000000..1f78f72 --- /dev/null +++ b/modules/light-clients/07-tendermint/update_test.go @@ -0,0 +1,958 @@ +package tendermint_test + +import ( + "errors" + "time" + + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + cmttypes "github.com/cometbft/cometbft/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *TendermintTestSuite) TestVerifyHeader() { + var ( + path *ibctesting.Path + header *ibctm.Header + ) + + // Setup different validators and signers for testing different types of updates + altPrivVal := cmttypes.NewMockPV() + altPubKey, err := altPrivVal.GetPubKey() + suite.Require().NoError(err) + + revisionHeight := int64(height.RevisionHeight) + + // create modified heights to use for test-cases + altVal := cmttypes.NewValidator(altPubKey, 100) + // Create alternative validator set with only altVal, invalid update (too much change in valSet) + altValSet := cmttypes.NewValidatorSet([]*cmttypes.Validator{altVal}) + altSigners := getAltSigners(altVal, altPrivVal) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + name: "success", + malleate: func() {}, + expErr: nil, + }, + { + name: "successful verify header for header with a previous height", + malleate: func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1] + suite.Require().True(ok) + + // passing the ProposedHeader.Height as the block height as it will become a previous height once we commit N blocks + header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers) + + // commit some blocks so that the created Header now has a previous height as the BlockHeight + suite.coordinator.CommitNBlocks(suite.chainB, 5) + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + }, + expErr: nil, + }, + { + name: "successful verify header: header with future height and different validator set", + malleate: func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1] + suite.Require().True(ok) + + // Create bothValSet with both suite validator and altVal + bothValSet := cmttypes.NewValidatorSet(append(suite.chainB.Vals.Validators, altVal)) + bothSigners := suite.chainB.Signers + bothSigners[altVal.Address.String()] = altPrivVal + + header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+5, trustedHeight, suite.chainB.ProposedHeader.Time, bothValSet, suite.chainB.NextVals, trustedVals, bothSigners) + }, + expErr: nil, + }, + { + name: "successful verify header: header with next height and different validator set", + malleate: func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1] + suite.Require().True(ok) + + // Create bothValSet with both suite validator and altVal + bothValSet := cmttypes.NewValidatorSet(append(suite.chainB.Vals.Validators, altVal)) + bothSigners := suite.chainB.Signers + bothSigners[altVal.Address.String()] = altPrivVal + + header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight, suite.chainB.ProposedHeader.Time, bothValSet, suite.chainB.NextVals, trustedVals, bothSigners) + }, + expErr: nil, + }, + { + name: "unsuccessful updates, passed in incorrect trusted validators for given consensus state", + malleate: func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + // Create bothValSet with both suite validator and altVal + bothValSet := cmttypes.NewValidatorSet(append(suite.chainB.Vals.Validators, altVal)) + bothSigners := suite.chainB.Signers + bothSigners[altVal.Address.String()] = altPrivVal + + header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+1, trustedHeight, suite.chainB.ProposedHeader.Time, bothValSet, bothValSet, bothValSet, bothSigners) + }, + expErr: errors.New("invalid validator set"), + }, + { + name: "unsuccessful verify header with next height: update header mismatches nextValSetHash", + malleate: func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1] + suite.Require().True(ok) + + // this will err as altValSet.Hash() != consState.NextValidatorsHash + header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+1, trustedHeight, suite.chainB.ProposedHeader.Time, altValSet, altValSet, trustedVals, altSigners) + }, + expErr: errors.New("failed to verify header"), + }, + { + name: "unsuccessful update with future height: too much change in validator set", + malleate: func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1] + suite.Require().True(ok) + + header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+1, trustedHeight, suite.chainB.ProposedHeader.Time, altValSet, altValSet, trustedVals, altSigners) + }, + expErr: errors.New("failed to verify header: cant trust new val set"), + }, + { + name: "unsuccessful verify header: header height revision and trusted height revision mismatch", + malleate: func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1] + suite.Require().True(ok) + + header = suite.chainB.CreateTMClientHeader(chainIDRevision1, 3, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers) + }, + expErr: errors.New("invalid client header"), + }, + { + name: "unsuccessful verify header: header height < consensus height", + malleate: func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1] + suite.Require().True(ok) + + heightMinus1 := clienttypes.NewHeight(trustedHeight.RevisionNumber, trustedHeight.RevisionHeight-1) + + // Make new header at height less than latest client state + header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(heightMinus1.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers) + }, + expErr: errors.New("invalid client header"), + }, + { + name: "unsuccessful verify header: header basic validation failed", + malleate: func() { + // cause header to fail validatebasic by changing commit height to mismatch header height + header.Commit.Height = revisionHeight - 1 + }, + expErr: errors.New("header and commit height mismatch"), + }, + { + name: "unsuccessful verify header: header timestamp is not past last client timestamp", + malleate: func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+1, trustedHeight, suite.chainB.ProposedHeader.Time.Add(-time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers) + }, + expErr: errors.New("failed to verify header"), + }, + { + name: "unsuccessful verify header: header with incorrect header chain-id", + malleate: func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + header = suite.chainB.CreateTMClientHeader(chainID, suite.chainB.ProposedHeader.Height+1, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers) + }, + expErr: errors.New("header height revision 0 does not match trusted header revision 1"), + }, + { + name: "unsuccessful update: trusting period has passed since last client timestamp", + malleate: func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight] + suite.Require().True(ok) + + header = suite.chainA.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+1, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers) + + suite.chainB.ExpireClient(ibctesting.TrustingPeriod) + }, + expErr: errors.New("failed to verify header"), + }, + { + name: "unsuccessful update for a previous revision", + malleate: func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1] + suite.Require().True(ok) + + // passing the ProposedHeader.Height as the block height as it will become an update to previous revision once we upgrade the client + header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers) + + // increment the revision of the chain + err = path.EndpointB.UpgradeChain() + suite.Require().NoError(err) + }, + expErr: errors.New("failed to verify header"), + }, + { + name: "successful update with identical header to a previous update", + malleate: func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1] + suite.Require().True(ok) + + // passing the ProposedHeader.Height as the block height as it will become a previous height once we commit N blocks + header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers) + + // update client so the header constructed becomes a duplicate + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + }, + expErr: nil, + }, + + { + name: "unsuccessful update to a future revision", + malleate: func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1] + suite.Require().True(ok) + + header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID+"-1", suite.chainB.ProposedHeader.Height+5, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers) + }, + expErr: errors.New("failed to verify header"), + }, + + { + name: "unsuccessful update: header height revision and trusted height revision mismatch", + malleate: func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1] + suite.Require().True(ok) + + // increment the revision of the chain + err = path.EndpointB.UpgradeChain() + suite.Require().NoError(err) + + header = suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers) + }, + expErr: errors.New("header height revision 2 does not match trusted header revision 1"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + err := path.EndpointA.CreateClient() + suite.Require().NoError(err) + + // ensure counterparty state is committed + suite.coordinator.CommitBlock(suite.chainB) + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + header, err = path.EndpointA.Counterparty.Chain.IBCClientHeader(path.EndpointA.Counterparty.Chain.LatestCommittedHeader, trustedHeight) + suite.Require().NoError(err) + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().NoError(err) + + tc.malleate() + + err = lightClientModule.VerifyClientMessage(suite.chainA.GetContext(), path.EndpointA.ClientID, header) + + if tc.expErr == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().Error(err) + suite.Require().ErrorContains(err, tc.expErr.Error()) + } + }) + } +} + +func (suite *TendermintTestSuite) TestUpdateState() { + var ( + path *ibctesting.Path + clientMessage exported.ClientMessage + clientStore storetypes.KVStore + consensusHeights []exported.Height + pruneHeight clienttypes.Height + prevClientState exported.ClientState + prevConsensusState exported.ConsensusState + ) + + testCases := []struct { + name string + malleate func() + expResult func() + expPass bool + }{ + { + "success with height later than latest height", func() { + tmHeader, ok := clientMessage.(*ibctm.Header) + suite.Require().True(ok) + suite.Require().True(path.EndpointA.GetClientLatestHeight().(clienttypes.Height).LT(tmHeader.GetHeight())) + }, + func() { + tmHeader, ok := clientMessage.(*ibctm.Header) + suite.Require().True(ok) + + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + suite.Require().True(clientState.LatestHeight.EQ(tmHeader.GetHeight())) // new update, updated client state should have changed + suite.Require().True(clientState.LatestHeight.EQ(consensusHeights[0])) + }, true, + }, + { + "success with height earlier than latest height", func() { + // commit a block so the pre-created ClientMessage + // isn't used to update the client to a newer height + suite.coordinator.CommitBlock(suite.chainB) + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + tmHeader, ok := clientMessage.(*ibctm.Header) + suite.Require().True(ok) + suite.Require().True(path.EndpointA.GetClientLatestHeight().(clienttypes.Height).GT(tmHeader.GetHeight())) + + prevClientState = path.EndpointA.GetClientState() + }, + func() { + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + suite.Require().Equal(clientState, prevClientState) // fill in height, no change to client state + suite.Require().True(clientState.LatestHeight.GT(consensusHeights[0])) + }, true, + }, + { + "success with duplicate header", func() { + // update client in advance + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + // use the same header which just updated the client + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + clientMessage, err = path.EndpointA.Counterparty.Chain.IBCClientHeader(path.EndpointA.Counterparty.Chain.LatestCommittedHeader, trustedHeight) + suite.Require().NoError(err) + + tmHeader, ok := clientMessage.(*ibctm.Header) + suite.Require().True(ok) + suite.Require().Equal(path.EndpointA.GetClientLatestHeight().(clienttypes.Height), tmHeader.GetHeight()) + + prevClientState = path.EndpointA.GetClientState() + prevConsensusState = path.EndpointA.GetConsensusState(tmHeader.GetHeight()) + }, + func() { + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + suite.Require().Equal(clientState, prevClientState) + suite.Require().True(clientState.LatestHeight.EQ(consensusHeights[0])) + + tmHeader, ok := clientMessage.(*ibctm.Header) + suite.Require().True(ok) + suite.Require().Equal(path.EndpointA.GetConsensusState(tmHeader.GetHeight()), prevConsensusState) + }, true, + }, + { + "success with pruned consensus state", func() { + // this height will be expired and pruned + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + var ok bool + pruneHeight, ok = path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + // Increment the time by a week + suite.coordinator.IncrementTimeBy(7 * 24 * time.Hour) + + // create the consensus state that can be used as trusted height for next update + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + // Increment the time by another week, then update the client. + // This will cause the first two consensus states to become expired. + suite.coordinator.IncrementTimeBy(7 * 24 * time.Hour) + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + // ensure counterparty state is committed + suite.coordinator.CommitBlock(suite.chainB) + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + clientMessage, err = path.EndpointA.Counterparty.Chain.IBCClientHeader(path.EndpointA.Counterparty.Chain.LatestCommittedHeader, trustedHeight) + suite.Require().NoError(err) + }, + func() { + tmHeader, ok := clientMessage.(*ibctm.Header) + suite.Require().True(ok) + + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + suite.Require().True(clientState.LatestHeight.EQ(tmHeader.GetHeight())) // new update, updated client state should have changed + suite.Require().True(clientState.LatestHeight.EQ(consensusHeights[0])) + + // ensure consensus state was pruned + _, found := path.EndpointA.Chain.GetConsensusState(path.EndpointA.ClientID, pruneHeight) + suite.Require().False(found) + }, true, + }, + { + "success with pruned consensus state using duplicate header", func() { + // this height will be expired and pruned + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + var ok bool + pruneHeight, ok = path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + // assert that a consensus state exists at the prune height + consensusState, found := path.EndpointA.Chain.GetConsensusState(path.EndpointA.ClientID, pruneHeight) + suite.Require().True(found) + suite.Require().NotNil(consensusState) + + // Increment the time by a week + suite.coordinator.IncrementTimeBy(7 * 24 * time.Hour) + + // create the consensus state that can be used as trusted height for next update + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + // Increment the time by another week, then update the client. + // This will cause the first two consensus states to become expired. + suite.coordinator.IncrementTimeBy(7 * 24 * time.Hour) + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + // use the same header which just updated the client + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + clientMessage, err = path.EndpointA.Counterparty.Chain.IBCClientHeader(path.EndpointA.Counterparty.Chain.LatestCommittedHeader, trustedHeight) + suite.Require().NoError(err) + }, + func() { + tmHeader, ok := clientMessage.(*ibctm.Header) + suite.Require().True(ok) + + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + suite.Require().True(clientState.LatestHeight.EQ(tmHeader.GetHeight())) // new update, updated client state should have changed + suite.Require().True(clientState.LatestHeight.EQ(consensusHeights[0])) + + // ensure consensus state was pruned + _, found := path.EndpointA.Chain.GetConsensusState(path.EndpointA.ClientID, pruneHeight) + suite.Require().False(found) + }, true, + }, + { + "invalid ClientMessage type", func() { + clientMessage = &ibctm.Misbehaviour{} + }, + func() {}, + false, + }, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() // reset + pruneHeight = clienttypes.ZeroHeight() + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + err := path.EndpointA.CreateClient() + suite.Require().NoError(err) + + // ensure counterparty state is committed + suite.coordinator.CommitBlock(suite.chainB) + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + clientMessage, err = path.EndpointA.Counterparty.Chain.IBCClientHeader(path.EndpointA.Counterparty.Chain.LatestCommittedHeader, trustedHeight) + suite.Require().NoError(err) + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().NoError(err) + + tc.malleate() + + clientStore = suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + + if tc.expPass { + consensusHeights = lightClientModule.UpdateState(suite.chainA.GetContext(), path.EndpointA.ClientID, clientMessage) + + header, ok := clientMessage.(*ibctm.Header) + suite.Require().True(ok) + + expConsensusState := &ibctm.ConsensusState{ + Timestamp: header.GetTime(), + Root: commitmenttypes.NewMerkleRoot(header.Header.GetAppHash()), + NextValidatorsHash: header.Header.NextValidatorsHash, + } + + bz := clientStore.Get(host.ConsensusStateKey(header.GetHeight())) + updatedConsensusState := clienttypes.MustUnmarshalConsensusState(suite.chainA.App.AppCodec(), bz) + + suite.Require().Equal(expConsensusState, updatedConsensusState) + + } else { + consensusHeights = lightClientModule.UpdateState(suite.chainA.GetContext(), path.EndpointA.ClientID, clientMessage) + suite.Require().Empty(consensusHeights) + + consensusState, found := suite.chainA.GetSimApp().GetIBCKeeper().ClientKeeper.GetClientConsensusState(suite.chainA.GetContext(), path.EndpointA.ClientID, clienttypes.NewHeight(1, uint64(suite.chainB.GetContext().BlockHeight()))) + suite.Require().False(found) + suite.Require().Nil(consensusState) + } + + // perform custom checks + tc.expResult() + }) + } +} + +func (suite *TendermintTestSuite) TestUpdateStateCheckTx() { + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + createClientMessage := func() exported.ClientMessage { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + header, err := path.EndpointB.Chain.IBCClientHeader(path.EndpointB.Chain.LatestCommittedHeader, trustedHeight) + suite.Require().NoError(err) + return header + } + + // get the first height as it will be pruned first. + var pruneHeight exported.Height + getFirstHeightCb := func(height exported.Height) bool { + pruneHeight = height + return true + } + ctx := path.EndpointA.Chain.GetContext() + clientStore := path.EndpointA.Chain.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, path.EndpointA.ClientID) + ibctm.IterateConsensusStateAscending(clientStore, getFirstHeightCb) + + // Increment the time by a week + suite.coordinator.IncrementTimeBy(7 * 24 * time.Hour) + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().NoError(err) + + ctx = path.EndpointA.Chain.GetContext().WithIsCheckTx(true) + lightClientModule.UpdateState(ctx, path.EndpointA.ClientID, createClientMessage()) + + // Increment the time by another week, then update the client. + // This will cause the first two consensus states to become expired. + suite.coordinator.IncrementTimeBy(7 * 24 * time.Hour) + ctx = path.EndpointA.Chain.GetContext().WithIsCheckTx(true) + lightClientModule.UpdateState(ctx, path.EndpointA.ClientID, createClientMessage()) + + assertPrune := func(pruned bool) { + // check consensus states and associated metadata + consState, ok := path.EndpointA.Chain.GetConsensusState(path.EndpointA.ClientID, pruneHeight) + suite.Require().Equal(!pruned, ok) + + processTime, ok := ibctm.GetProcessedTime(clientStore, pruneHeight) + suite.Require().Equal(!pruned, ok) + + processHeight, ok := ibctm.GetProcessedHeight(clientStore, pruneHeight) + suite.Require().Equal(!pruned, ok) + + consKey := ibctm.GetIterationKey(clientStore, pruneHeight) + + if pruned { + suite.Require().Nil(consState, "expired consensus state not pruned") + suite.Require().Empty(processTime, "processed time metadata not pruned") + suite.Require().Nil(processHeight, "processed height metadata not pruned") + suite.Require().Nil(consKey, "iteration key not pruned") + } else { + suite.Require().NotNil(consState, "expired consensus state pruned") + suite.Require().NotEqual(uint64(0), processTime, "processed time metadata pruned") + suite.Require().NotNil(processHeight, "processed height metadata pruned") + suite.Require().NotNil(consKey, "iteration key pruned") + } + } + + assertPrune(false) + + // simulation mode must prune to calculate gas correctly + ctx = ctx.WithExecMode(sdk.ExecModeSimulate) + lightClientModule.UpdateState(ctx, path.EndpointA.ClientID, createClientMessage()) + + assertPrune(true) +} + +func (suite *TendermintTestSuite) TestPruneConsensusState() { + // create path and setup clients + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + // get the first height as it will be pruned first. + var pruneHeight exported.Height + getFirstHeightCb := func(height exported.Height) bool { + pruneHeight = height + return true + } + ctx := path.EndpointA.Chain.GetContext() + clientStore := path.EndpointA.Chain.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, path.EndpointA.ClientID) + ibctm.IterateConsensusStateAscending(clientStore, getFirstHeightCb) + + // this height will be expired but not pruned + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + expiredHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + // expected values that must still remain in store after pruning + expectedConsState, ok := path.EndpointA.Chain.GetConsensusState(path.EndpointA.ClientID, expiredHeight) + suite.Require().True(ok) + ctx = path.EndpointA.Chain.GetContext() + clientStore = path.EndpointA.Chain.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, path.EndpointA.ClientID) + expectedProcessTime, ok := ibctm.GetProcessedTime(clientStore, expiredHeight) + suite.Require().True(ok) + expectedProcessHeight, ok := ibctm.GetProcessedHeight(clientStore, expiredHeight) + suite.Require().True(ok) + expectedConsKey := ibctm.GetIterationKey(clientStore, expiredHeight) + suite.Require().NotNil(expectedConsKey) + + // Increment the time by a week + suite.coordinator.IncrementTimeBy(7 * 24 * time.Hour) + + // create the consensus state that can be used as trusted height for next update + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + // Increment the time by another week, then update the client. + // This will cause the first two consensus states to become expired. + suite.coordinator.IncrementTimeBy(7 * 24 * time.Hour) + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + ctx = path.EndpointA.Chain.GetContext() + clientStore = path.EndpointA.Chain.App.GetIBCKeeper().ClientKeeper.ClientStore(ctx, path.EndpointA.ClientID) + + // check that the first expired consensus state got deleted along with all associated metadata + consState, ok := path.EndpointA.Chain.GetConsensusState(path.EndpointA.ClientID, pruneHeight) + suite.Require().Nil(consState, "expired consensus state not pruned") + suite.Require().False(ok) + // check processed time metadata is pruned + processTime, ok := ibctm.GetProcessedTime(clientStore, pruneHeight) + suite.Require().Equal(uint64(0), processTime, "processed time metadata not pruned") + suite.Require().False(ok) + processHeight, ok := ibctm.GetProcessedHeight(clientStore, pruneHeight) + suite.Require().Nil(processHeight, "processed height metadata not pruned") + suite.Require().False(ok) + + // check iteration key metadata is pruned + consKey := ibctm.GetIterationKey(clientStore, pruneHeight) + suite.Require().Nil(consKey, "iteration key not pruned") + + // check that second expired consensus state doesn't get deleted + // this ensures that there is a cap on gas cost of UpdateClient + consState, ok = path.EndpointA.Chain.GetConsensusState(path.EndpointA.ClientID, expiredHeight) + suite.Require().Equal(expectedConsState, consState, "consensus state incorrectly pruned") + suite.Require().True(ok) + // check processed time metadata is not pruned + processTime, ok = ibctm.GetProcessedTime(clientStore, expiredHeight) + suite.Require().Equal(expectedProcessTime, processTime, "processed time metadata incorrectly pruned") + suite.Require().True(ok) + + // check processed height metadata is not pruned + processHeight, ok = ibctm.GetProcessedHeight(clientStore, expiredHeight) + suite.Require().Equal(expectedProcessHeight, processHeight, "processed height metadata incorrectly pruned") + suite.Require().True(ok) + + // check iteration key metadata is not pruned + consKey = ibctm.GetIterationKey(clientStore, expiredHeight) + suite.Require().Equal(expectedConsKey, consKey, "iteration key incorrectly pruned") +} + +func (suite *TendermintTestSuite) TestCheckForMisbehaviour() { + var ( + path *ibctesting.Path + clientMessage exported.ClientMessage + ) + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "valid update no misbehaviour", + func() {}, + false, + }, + { + "consensus state already exists, already updated", + func() { + header, ok := clientMessage.(*ibctm.Header) + suite.Require().True(ok) + + consensusState := &ibctm.ConsensusState{ + Timestamp: header.GetTime(), + Root: commitmenttypes.NewMerkleRoot(header.Header.GetAppHash()), + NextValidatorsHash: header.Header.NextValidatorsHash, + } + + tmHeader, ok := clientMessage.(*ibctm.Header) + suite.Require().True(ok) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), path.EndpointA.ClientID, tmHeader.GetHeight(), consensusState) + }, + false, + }, + { + "invalid fork misbehaviour: identical headers", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1] + suite.Require().True(ok) + + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + height, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + misbehaviourHeader := suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, int64(height.RevisionHeight), trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers) + clientMessage = &ibctm.Misbehaviour{ + Header1: misbehaviourHeader, + Header2: misbehaviourHeader, + } + }, false, + }, + { + "invalid time misbehaviour: monotonically increasing time", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1] + suite.Require().True(ok) + + clientMessage = &ibctm.Misbehaviour{ + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+3, trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, false, + }, + { + "consensus state already exists, app hash mismatch", + func() { + header, ok := clientMessage.(*ibctm.Header) + suite.Require().True(ok) + + consensusState := &ibctm.ConsensusState{ + Timestamp: header.GetTime(), + Root: commitmenttypes.NewMerkleRoot([]byte{}), // empty bytes + NextValidatorsHash: header.Header.NextValidatorsHash, + } + + tmHeader, ok := clientMessage.(*ibctm.Header) + suite.Require().True(ok) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(suite.chainA.GetContext(), path.EndpointA.ClientID, tmHeader.GetHeight(), consensusState) + }, + true, + }, + { + "previous consensus state exists and header time is before previous consensus state time", + func() { + header, ok := clientMessage.(*ibctm.Header) + suite.Require().True(ok) + + // offset header timestamp before previous consensus state timestamp + header.Header.Time = header.GetTime().Add(-time.Hour) + }, + true, + }, + { + "next consensus state exists and header time is after next consensus state time", + func() { + header, ok := clientMessage.(*ibctm.Header) + suite.Require().True(ok) + + // commit block and update client, adding a new consensus state + suite.coordinator.CommitBlock(suite.chainB) + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + // increase timestamp of current header + header.Header.Time = header.Header.Time.Add(time.Hour) + }, + true, + }, + { + "valid fork misbehaviour returns true", + func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + header1, err := path.EndpointA.Counterparty.Chain.IBCClientHeader(path.EndpointA.Counterparty.Chain.LatestCommittedHeader, trustedHeight) + suite.Require().NoError(err) + + // commit block and update client + suite.coordinator.CommitBlock(suite.chainB) + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + trustedHeight, ok = path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + header2, err := path.EndpointA.Counterparty.Chain.IBCClientHeader(path.EndpointA.Counterparty.Chain.LatestCommittedHeader, trustedHeight) + suite.Require().NoError(err) + + // assign the same height, each header will have a different commit hash + header1.Header.Height = header2.Header.Height + + clientMessage = &ibctm.Misbehaviour{ + Header1: header1, + Header2: header2, + ClientId: path.EndpointA.ClientID, + } + }, + true, + }, + { + "valid time misbehaviour: not monotonically increasing time", func() { + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + + trustedVals, ok := suite.chainB.TrustedValidators[trustedHeight.RevisionHeight+1] + suite.Require().True(ok) + + clientMessage = &ibctm.Misbehaviour{ + Header2: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height+3, trustedHeight, suite.chainB.ProposedHeader.Time.Add(time.Minute), suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + Header1: suite.chainB.CreateTMClientHeader(suite.chainB.ChainID, suite.chainB.ProposedHeader.Height, trustedHeight, suite.chainB.ProposedHeader.Time, suite.chainB.Vals, suite.chainB.NextVals, trustedVals, suite.chainB.Signers), + } + }, true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + // reset suite to create fresh application state + suite.SetupTest() + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + err := path.EndpointA.CreateClient() + suite.Require().NoError(err) + + // ensure counterparty state is committed + suite.coordinator.CommitBlock(suite.chainB) + trustedHeight, ok := path.EndpointA.GetClientLatestHeight().(clienttypes.Height) + suite.Require().True(ok) + clientMessage, err = path.EndpointA.Counterparty.Chain.IBCClientHeader(path.EndpointA.Counterparty.Chain.LatestCommittedHeader, trustedHeight) + suite.Require().NoError(err) + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().NoError(err) + + tc.malleate() + + foundMisbehaviour := lightClientModule.CheckForMisbehaviour( + suite.chainA.GetContext(), + path.EndpointA.ClientID, + clientMessage, + ) + + if tc.expPass { + suite.Require().True(foundMisbehaviour) + } else { + suite.Require().False(foundMisbehaviour) + } + }) + } +} + +func (suite *TendermintTestSuite) TestUpdateStateOnMisbehaviour() { + var path *ibctesting.Path + + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "success", + func() {}, + true, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + // reset suite to create fresh application state + suite.SetupTest() + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + err := path.EndpointA.CreateClient() + suite.Require().NoError(err) + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().NoError(err) + + tc.malleate() + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + + lightClientModule.UpdateStateOnMisbehaviour(suite.chainA.GetContext(), path.EndpointA.ClientID, nil) + + if tc.expPass { + clientStateBz := clientStore.Get(host.ClientStateKey()) + suite.Require().NotEmpty(clientStateBz) + + newClientState := clienttypes.MustUnmarshalClientState(suite.chainA.Codec, clientStateBz) + suite.Require().Equal(frozenHeight, newClientState.(*ibctm.ClientState).FrozenHeight) + } + }) + } +} diff --git a/modules/light-clients/07-tendermint/upgrade.go b/modules/light-clients/07-tendermint/upgrade.go new file mode 100644 index 0000000..440a152 --- /dev/null +++ b/modules/light-clients/07-tendermint/upgrade.go @@ -0,0 +1,188 @@ +package tendermint + +import ( + "fmt" + "time" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + storetypes "cosmossdk.io/store/types" + upgradetypes "cosmossdk.io/x/upgrade/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + commitmenttypesv2 "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types/v2" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// VerifyUpgradeAndUpdateState checks if the upgraded client has been committed by the current client +// It will zero out all client-specific fields and verify all data in client state that must +// be the same across all valid Tendermint clients for the new chain. +// Note, if there is a decrease in the UnbondingPeriod, then the TrustingPeriod, despite being a client-specific field +// is scaled down by the same ratio. +// VerifyUpgrade will return an error if: +// - the upgradedClient is not a Tendermint ClientState +// - the latest height of the client state does not have the same revision number or has a greater +// height than the committed client. +// - the height of upgraded client is not greater than that of current client +// - the latest height of the new client does not match or is greater than the height in committed client +// - any Tendermint chain specified parameter in upgraded client such as ChainID, UnbondingPeriod, +// and ProofSpecs do not match parameters set by committed client +func (cs ClientState) VerifyUpgradeAndUpdateState( + ctx sdk.Context, cdc codec.BinaryCodec, clientStore storetypes.KVStore, + upgradedClient exported.ClientState, upgradedConsState exported.ConsensusState, + upgradeClientProof, upgradeConsStateProof []byte, +) error { + if len(cs.UpgradePath) == 0 { + return errorsmod.Wrap(clienttypes.ErrInvalidUpgradeClient, "cannot upgrade client, no upgrade path set") + } + + // upgraded client state and consensus state must be IBC tendermint client state and consensus state + // this may be modified in the future to upgrade to a new IBC tendermint type + // counterparty must also commit to the upgraded consensus state at a sub-path under the upgrade path specified + tmUpgradeClient, ok := upgradedClient.(*ClientState) + if !ok { + return errorsmod.Wrapf(clienttypes.ErrInvalidClientType, "upgraded client must be Tendermint client. expected: %T got: %T", + &ClientState{}, upgradedClient) + } + + tmUpgradeConsState, ok := upgradedConsState.(*ConsensusState) + if !ok { + return errorsmod.Wrapf(clienttypes.ErrInvalidConsensus, "upgraded consensus state must be Tendermint consensus state. expected %T, got: %T", + &ConsensusState{}, upgradedConsState) + } + + // unmarshal proofs + var merkleProofClient, merkleProofConsState commitmenttypes.MerkleProof + if err := cdc.Unmarshal(upgradeClientProof, &merkleProofClient); err != nil { + return errorsmod.Wrapf(commitmenttypes.ErrInvalidProof, "could not unmarshal client merkle proof: %v", err) + } + if err := cdc.Unmarshal(upgradeConsStateProof, &merkleProofConsState); err != nil { + return errorsmod.Wrapf(commitmenttypes.ErrInvalidProof, "could not unmarshal consensus state merkle proof: %v", err) + } + + // last height of current counterparty chain must be client's latest height + lastHeight := cs.LatestHeight + + // Must prove against latest consensus state to ensure we are verifying against latest upgrade plan + // This verifies that upgrade is intended for the provided revision, since committed client must exist + // at this consensus state + consState, found := GetConsensusState(clientStore, cdc, lastHeight) + if !found { + return errorsmod.Wrap(clienttypes.ErrConsensusStateNotFound, "could not retrieve consensus state for lastHeight") + } + + // Verify client proof + bz, err := cdc.MarshalInterface(tmUpgradeClient.ZeroCustomFields()) + if err != nil { + return errorsmod.Wrapf(clienttypes.ErrInvalidClient, "could not marshal client state: %v", err) + } + // construct clientState Merkle path + upgradeClientPath := constructUpgradeClientMerklePath(cs.UpgradePath, lastHeight) + if err := merkleProofClient.VerifyMembership(cs.ProofSpecs, consState.GetRoot(), upgradeClientPath, bz); err != nil { + return errorsmod.Wrapf(err, "client state proof failed. Path: %s", upgradeClientPath.GetKeyPath()) + } + + // Verify consensus state proof + bz, err = cdc.MarshalInterface(upgradedConsState) + if err != nil { + return errorsmod.Wrapf(clienttypes.ErrInvalidConsensus, "could not marshal consensus state: %v", err) + } + // construct consensus state Merkle path + upgradeConsStatePath := constructUpgradeConsStateMerklePath(cs.UpgradePath, lastHeight) + if err := merkleProofConsState.VerifyMembership(cs.ProofSpecs, consState.GetRoot(), upgradeConsStatePath, bz); err != nil { + return errorsmod.Wrapf(err, "consensus state proof failed. Path: %s", upgradeConsStatePath.GetKeyPath()) + } + + trustingPeriod := cs.TrustingPeriod + if tmUpgradeClient.UnbondingPeriod < cs.UnbondingPeriod { + trustingPeriod = calculateNewTrustingPeriod(trustingPeriod, cs.UnbondingPeriod, tmUpgradeClient.UnbondingPeriod) + } + + // Construct new client state and consensus state + // Relayer chosen client parameters are ignored. + // All chain-chosen parameters come from committed client, all client-chosen parameters + // come from current client. + newClientState := NewClientState( + tmUpgradeClient.ChainId, cs.TrustLevel, trustingPeriod, tmUpgradeClient.UnbondingPeriod, + cs.MaxClockDrift, tmUpgradeClient.LatestHeight, tmUpgradeClient.ProofSpecs, tmUpgradeClient.UpgradePath, + ) + + if err := newClientState.Validate(); err != nil { + return errorsmod.Wrap(err, "updated client state failed basic validation") + } + + // The new consensus state is merely used as a trusted kernel against which headers on the new + // chain can be verified. The root is just a stand-in sentinel value as it cannot be known in advance, thus no proof verification will pass. + // The timestamp and the NextValidatorsHash of the consensus state is the blocktime and NextValidatorsHash + // of the last block committed by the old chain. This will allow the first block of the new chain to be verified against + // the last validators of the old chain so long as it is submitted within the TrustingPeriod of this client. + // NOTE: We do not set processed time for this consensus state since this consensus state should not be used for packet verification + // as the root is empty. The next consensus state submitted using update will be usable for packet-verification. + newConsState := NewConsensusState( + tmUpgradeConsState.Timestamp, commitmenttypes.NewMerkleRoot([]byte(SentinelRoot)), tmUpgradeConsState.NextValidatorsHash, + ) + + setClientState(clientStore, cdc, newClientState) + setConsensusState(clientStore, cdc, newConsState, newClientState.LatestHeight) + setConsensusMetadata(ctx, clientStore, tmUpgradeClient.LatestHeight) + + return nil +} + +// construct MerklePath for the committed client from upgradePath +func constructUpgradeClientMerklePath(upgradePath []string, lastHeight exported.Height) commitmenttypesv2.MerklePath { + // copy all elements from upgradePath except final element + clientPath := make([]string, len(upgradePath)-1) + copy(clientPath, upgradePath) + + // append lastHeight and `upgradedClient` to last key of upgradePath and use as lastKey of clientPath + // this will create the IAVL key that is used to store client in upgrade store + lastKey := upgradePath[len(upgradePath)-1] + appendedKey := fmt.Sprintf("%s/%d/%s", lastKey, lastHeight.GetRevisionHeight(), upgradetypes.KeyUpgradedClient) + + clientPath = append(clientPath, appendedKey) + + var clientKey [][]byte + for _, part := range clientPath { + clientKey = append(clientKey, []byte(part)) + } + + return commitmenttypes.NewMerklePath(clientKey...) +} + +// construct MerklePath for the committed consensus state from upgradePath +func constructUpgradeConsStateMerklePath(upgradePath []string, lastHeight exported.Height) commitmenttypesv2.MerklePath { + // copy all elements from upgradePath except final element + consPath := make([]string, len(upgradePath)-1) + copy(consPath, upgradePath) + + // append lastHeight and `upgradedClient` to last key of upgradePath and use as lastKey of clientPath + // this will create the IAVL key that is used to store client in upgrade store + lastKey := upgradePath[len(upgradePath)-1] + appendedKey := fmt.Sprintf("%s/%d/%s", lastKey, lastHeight.GetRevisionHeight(), upgradetypes.KeyUpgradedConsState) + + consPath = append(consPath, appendedKey) + + var consStateKey [][]byte + for _, part := range consPath { + consStateKey = append(consStateKey, []byte(part)) + } + + return commitmenttypes.NewMerklePath(consStateKey...) +} + +// calculateNewTrustingPeriod converts the provided durations to decimal representation to avoid floating-point precision issues +// and calculates the new trusting period, decreasing it by the ratio between the original and new unbonding period. +func calculateNewTrustingPeriod(trustingPeriod, originalUnbonding, newUnbonding time.Duration) time.Duration { + origUnbondingDec := sdkmath.LegacyNewDec(originalUnbonding.Nanoseconds()) + newUnbondingDec := sdkmath.LegacyNewDec(newUnbonding.Nanoseconds()) + trustingPeriodDec := sdkmath.LegacyNewDec(trustingPeriod.Nanoseconds()) + + // compute new trusting period: trustingPeriod * newUnbonding / originalUnbonding + newTrustingPeriodDec := trustingPeriodDec.Mul(newUnbondingDec).Quo(origUnbondingDec) + return time.Duration(newTrustingPeriodDec.TruncateInt64()) +} diff --git a/modules/light-clients/07-tendermint/upgrade_test.go b/modules/light-clients/07-tendermint/upgrade_test.go new file mode 100644 index 0000000..107bf54 --- /dev/null +++ b/modules/light-clients/07-tendermint/upgrade_test.go @@ -0,0 +1,697 @@ +package tendermint_test + +import ( + "errors" + "time" + + sdkmath "cosmossdk.io/math" + upgradetypes "cosmossdk.io/x/upgrade/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *TendermintTestSuite) TestVerifyUpgrade() { + var ( + newChainID string + upgradedClient exported.ClientState + upgradedConsState exported.ConsensusState + lastHeight clienttypes.Height + path *ibctesting.Path + upgradedClientProof, upgradedConsensusStateProof []byte + upgradedClientBz, upgradedConsStateBz []byte + err error + ) + + testCases := []struct { + name string + setup func() + expErr error + }{ + { + name: "successful upgrade", + setup: func() { + // upgrade Height is at next block + lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + + // zero custom fields and store in upgrade store + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) //nolint:errcheck // ignore error for test + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) //nolint:errcheck // ignore error for test + + // commit upgrade store changes and update clients + suite.coordinator.CommitBlock(suite.chainB) + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + }, + expErr: nil, + }, + { + name: "successful upgrade to same revision", + setup: func() { + upgradedClient = ibctm.NewClientState(suite.chainB.ChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod+trustingPeriod, maxClockDrift, clienttypes.NewHeight(clienttypes.ParseChainID(suite.chainB.ChainID), upgradedClient.(*ibctm.ClientState).LatestHeight.GetRevisionHeight()+10), commitmenttypes.GetSDKSpecs(), upgradePath) + upgradedClient = upgradedClient.(*ibctm.ClientState).ZeroCustomFields() + upgradedClientBz, err = clienttypes.MarshalClientState(suite.chainA.App.AppCodec(), upgradedClient) + suite.Require().NoError(err) + + // upgrade Height is at next block + lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + + // zero custom fields and store in upgrade store + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) //nolint:errcheck // ignore error for test + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) //nolint:errcheck // ignore error for test + + // commit upgrade store changes and update clients + + suite.coordinator.CommitBlock(suite.chainB) + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + }, + expErr: nil, + }, + { + name: "successful upgrade with new unbonding period", + setup: func() { + newUnbondingPeriod := time.Hour * 24 * 7 * 2 + upgradedClient = ibctm.NewClientState(suite.chainB.ChainID, ibctm.DefaultTrustLevel, trustingPeriod, newUnbondingPeriod, maxClockDrift, clienttypes.NewHeight(clienttypes.ParseChainID(suite.chainB.ChainID), upgradedClient.(*ibctm.ClientState).LatestHeight.GetRevisionHeight()+10), commitmenttypes.GetSDKSpecs(), upgradePath) + upgradedClient = upgradedClient.(*ibctm.ClientState).ZeroCustomFields() + upgradedClientBz, err = clienttypes.MarshalClientState(suite.chainA.App.AppCodec(), upgradedClient) + suite.Require().NoError(err) + + // upgrade Height is at next block + lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + + // zero custom fields and store in upgrade store + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) //nolint:errcheck // ignore error for test + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) //nolint:errcheck // ignore error for test + + // commit upgrade store changes and update clients + + suite.coordinator.CommitBlock(suite.chainB) + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + }, + expErr: nil, + }, + { + name: "unsuccessful upgrade: upgrade path not set", + setup: func() { + suite.coordinator.CommitBlock(suite.chainB) + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + // set upgrade path to empty + tmCs.UpgradePath = []string{} + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID, tmCs) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + }, + expErr: clienttypes.ErrInvalidUpgradeClient, + }, + { + name: "unsuccessful upgrade: upgrade consensus state must be tendermint consensus state", + setup: func() { + upgradedConsState = &solomachine.ConsensusState{} + + suite.coordinator.CommitBlock(suite.chainB) + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + }, + expErr: clienttypes.ErrInvalidConsensus, + }, + { + name: "unsuccessful upgrade: upgrade height revision height is more than the current client revision height", + setup: func() { + // upgrade Height is 10 blocks from now + lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+10)) + + // zero custom fields and store in upgrade store + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) //nolint:errcheck // ignore error for test + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) //nolint:errcheck // ignore error for test + + // commit upgrade store changes and update clients + + suite.coordinator.CommitBlock(suite.chainB) + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + }, + expErr: commitmenttypes.ErrInvalidProof, + }, + { + name: "unsuccessful upgrade: committed client does not have zeroed custom fields", + setup: func() { + // non-zeroed upgrade client + upgradedClient = ibctm.NewClientState(newChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod+trustingPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath) + upgradedClientBz, err = clienttypes.MarshalClientState(suite.chainA.App.AppCodec(), upgradedClient) + suite.Require().NoError(err) + + // upgrade Height is at next block + lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + + // zero custom fields and store in upgrade store + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) //nolint:errcheck // ignore error for test + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) //nolint:errcheck // ignore error for test + + // commit upgrade store changes and update clients + + suite.coordinator.CommitBlock(suite.chainB) + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + }, + expErr: commitmenttypes.ErrInvalidProof, + }, + { + name: "unsuccessful upgrade: chain-specified parameters do not match committed client", + setup: func() { + // upgrade Height is at next block + lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + + // zero custom fields and store in upgrade store + err := suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) + suite.Require().NoError(err) + err = suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) + suite.Require().NoError(err) + + // change upgradedClient client-specified parameters + upgradedClient = ibctm.NewClientState("wrongchainID", ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath) + + suite.coordinator.CommitBlock(suite.chainB) + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + }, + expErr: commitmenttypes.ErrInvalidProof, + }, + { + name: "unsuccessful upgrade: client-specified parameters do not match previous client", + setup: func() { + // upgrade Height is at next block + lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + + // zero custom fields and store in upgrade store + err := suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) + suite.Require().NoError(err) + err = suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) + suite.Require().NoError(err) + + // change upgradedClient client-specified parameters + upgradedClient = ibctm.NewClientState(newChainID, ibctm.DefaultTrustLevel, ubdPeriod, ubdPeriod+trustingPeriod, maxClockDrift+5, lastHeight, commitmenttypes.GetSDKSpecs(), upgradePath) + + suite.coordinator.CommitBlock(suite.chainB) + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + }, + expErr: commitmenttypes.ErrInvalidProof, + }, + { + name: "unsuccessful upgrade: upgrade client is not tendermint", + setup: func() { + upgradedClient = &solomachine.ClientState{} + }, + expErr: clienttypes.ErrInvalidClientType, + }, + { + name: "unsuccessful upgrade: relayer-submitted consensus state does not match counterparty-committed consensus state", + setup: func() { + // change submitted upgradedConsensusState + upgradedConsState = &ibctm.ConsensusState{ + NextValidatorsHash: []byte("maliciousValidators"), + } + + // commit upgrade store changes and update clients + suite.coordinator.CommitBlock(suite.chainB) + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + }, + expErr: commitmenttypes.ErrInvalidProof, + }, + { + name: "unsuccessful upgrade: client proof unmarshal failed", + setup: func() { + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + + upgradedClientProof = []byte("proof") + }, + expErr: errors.New("could not unmarshal client merkle proof"), + }, + { + name: "unsuccessful upgrade: consensus state proof unmarshal failed", + setup: func() { + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + + upgradedConsensusStateProof = []byte("proof") + }, + expErr: errors.New("could not unmarshal consensus state merkle proof"), + }, + { + name: "unsuccessful upgrade: client proof verification failed", + setup: func() { + // do not store upgraded client + + // upgrade Height is at next block + lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) //nolint:errcheck // ignore error for test + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + }, + expErr: commitmenttypes.ErrInvalidProof, + }, + { + name: "unsuccessful upgrade: consensus state proof verification failed", + setup: func() { + // do not store upgraded client + + // upgrade Height is at next block + lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) //nolint:errcheck // ignore error for test + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + }, + expErr: commitmenttypes.ErrInvalidProof, + }, + { + name: "unsuccessful upgrade: client state merkle path is empty", + setup: func() { + // upgrade Height is at next block + lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + + // zero custom fields and store in upgrade store + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) //nolint:errcheck // ignore error for test + + // commit upgrade store changes and update clients + suite.coordinator.CommitBlock(suite.chainB) + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + + // SetClientState with empty string upgrade path + tmClient, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + tmClient.UpgradePath = []string{""} + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID, tmClient) + }, + expErr: errors.New("client state proof failed"), + }, + { + name: "unsuccessful upgrade: upgraded height is not greater than current height", + setup: func() { + // upgrade Height is at next block + lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + + // zero custom fields and store in upgrade store + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) //nolint:errcheck // ignore error for test + + // commit upgrade store changes and update clients + suite.coordinator.CommitBlock(suite.chainB) + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + }, + expErr: errors.New("consensus state proof failed"), + }, + { + name: "unsuccessful upgrade: consensus state for upgrade height cannot be found", + setup: func() { + // upgrade Height is at next block + lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+100)) + + // zero custom fields and store in upgrade store + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) //nolint:errcheck // ignore error for + + // commit upgrade store changes and update clients + suite.coordinator.CommitBlock(suite.chainB) + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + }, + expErr: commitmenttypes.ErrInvalidProof, + }, + { + name: "unsuccessful upgrade: client is expired", + setup: func() { + // zero custom fields and store in upgrade store + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) //nolint:errcheck // ignore error for test + + // commit upgrade store changes and update clients + suite.coordinator.CommitBlock(suite.chainB) + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + // expire chainB's client + suite.chainA.ExpireClient(ubdPeriod) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + }, + expErr: commitmenttypes.ErrInvalidProof, + }, + { + name: "unsuccessful upgrade: updated unbonding period is equal to trusting period", + setup: func() { + // upgrade Height is at next block + lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + + // zero custom fields and store in upgrade store + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) //nolint:errcheck // ignore error for test + + // commit upgrade store changes and update clients + suite.coordinator.CommitBlock(suite.chainB) + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + }, + expErr: commitmenttypes.ErrInvalidProof, + }, + { + name: "unsuccessful upgrade: final client is not valid", + setup: func() { + // new client has smaller unbonding period such that old trusting period is no longer valid + upgradedClient = ibctm.NewClientState(newChainID, ibctm.DefaultTrustLevel, trustingPeriod, trustingPeriod, maxClockDrift, newClientHeight, commitmenttypes.GetSDKSpecs(), upgradePath) + upgradedClientBz, err = clienttypes.MarshalClientState(suite.chainA.App.AppCodec(), upgradedClient) + suite.Require().NoError(err) + + // upgrade Height is at next block + lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + + // zero custom fields and store in upgrade store + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) //nolint:errcheck // ignore error for testing + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) //nolint:errcheck // ignore error for testing + + // commit upgrade store changes and update clients + suite.coordinator.CommitBlock(suite.chainB) + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), tmCs.LatestHeight.GetRevisionHeight()) + }, + expErr: commitmenttypes.ErrInvalidProof, + }, + { + name: "unsuccessful upgrade: consensus state not found for latest height", + setup: func() { + // upgrade Height is at next block + lastHeight = clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) + + // zero custom fields and store in upgrade store + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz) //nolint:errcheck // ignore error for test + suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz) //nolint:errcheck // ignore error for test + + // commit upgrade store changes and update clients + + suite.coordinator.CommitBlock(suite.chainB) + err := path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID) + suite.Require().True(found) + tmCs, ok := cs.(*ibctm.ClientState) + suite.Require().True(ok) + + revisionHeight := tmCs.LatestHeight.GetRevisionHeight() + + // set latest height to a height where consensus state does not exist + tmCs.LatestHeight = clienttypes.NewHeight(tmCs.LatestHeight.GetRevisionNumber(), tmCs.LatestHeight.GetRevisionHeight()+5) + suite.chainA.App.GetIBCKeeper().ClientKeeper.SetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID, tmCs) + + upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), revisionHeight) + upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), revisionHeight) + }, + expErr: clienttypes.ErrConsensusStateNotFound, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + // reset suite + suite.SetupTest() + path = ibctesting.NewPath(suite.chainA, suite.chainB) + + path.SetupClients() + + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + revisionNumber := clienttypes.ParseChainID(clientState.ChainId) + + var err error + newChainID, err = clienttypes.SetRevisionNumber(clientState.ChainId, revisionNumber+1) + suite.Require().NoError(err) + + upgradedClient = ibctm.NewClientState(newChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod+trustingPeriod, maxClockDrift, clienttypes.NewHeight(revisionNumber+1, clientState.LatestHeight.GetRevisionHeight()+1), commitmenttypes.GetSDKSpecs(), upgradePath) + + if upgraded, ok := upgradedClient.(*ibctm.ClientState); ok { + upgradedClient = upgraded.ZeroCustomFields() + } + + upgradedClientBz, err = clienttypes.MarshalClientState(suite.chainA.App.AppCodec(), upgradedClient) + suite.Require().NoError(err) + + upgradedConsState = &ibctm.ConsensusState{ + NextValidatorsHash: []byte("nextValsHash"), + } + upgradedConsStateBz, err = clienttypes.MarshalConsensusState(suite.chainA.App.AppCodec(), upgradedConsState) + suite.Require().NoError(err) + + tc.setup() + + cs, ok := suite.chainA.GetClientState(path.EndpointA.ClientID).(*ibctm.ClientState) + suite.Require().True(ok) + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + + // Call ZeroCustomFields on upgraded clients to clear any client-chosen parameters in test-case upgradedClient + if upgraded, ok := upgradedClient.(*ibctm.ClientState); ok { + upgradedClient = upgraded.ZeroCustomFields() + } + + err = cs.VerifyUpgradeAndUpdateState( + suite.chainA.GetContext(), + suite.cdc, + clientStore, + upgradedClient, + upgradedConsState, + upgradedClientProof, + upgradedConsensusStateProof, + ) + + if tc.expErr == nil { + suite.Require().NoError(err, "verify upgrade failed on valid case: %s", tc.name) + + clientState, ok := suite.chainA.GetClientState(path.EndpointA.ClientID).(*ibctm.ClientState) + suite.Require().True(ok) + suite.Require().NotNil(clientState, "verify upgrade failed on valid case: %s", tc.name) + + consensusState, found := suite.chainA.GetConsensusState(path.EndpointA.ClientID, clientState.LatestHeight) + suite.Require().NotNil(consensusState, "verify upgrade failed on valid case: %s", tc.name) + suite.Require().True(found) + } else { + suite.Require().ErrorContains(err, tc.expErr.Error(), "verify upgrade passed on invalid case: %s", tc.name) + } + }) + } +} + +func (suite *TendermintTestSuite) TestVerifyUpgradeWithNewUnbonding() { + suite.SetupTest() + path := ibctesting.NewPath(suite.chainA, suite.chainB) + path.SetupClients() + + clientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + + newUnbondingPeriod := time.Hour * 24 * 7 * 2 // update the unbonding period to two weeks + upgradeClient := ibctm.NewClientState(clientState.ChainId, ibctm.DefaultTrustLevel, trustingPeriod, newUnbondingPeriod, maxClockDrift, clienttypes.NewHeight(1, clientState.LatestHeight.GetRevisionHeight()+1), commitmenttypes.GetSDKSpecs(), upgradePath) + + upgradedClientBz, err := clienttypes.MarshalClientState(suite.chainA.App.AppCodec(), upgradeClient.ZeroCustomFields()) + suite.Require().NoError(err) + + upgradedConsState := &ibctm.ConsensusState{NextValidatorsHash: []byte("nextValsHash")} // mocked consensus state + upgradedConsStateBz, err := clienttypes.MarshalConsensusState(suite.chainA.App.AppCodec(), upgradedConsState) + suite.Require().NoError(err) + + // zero custom fields and store in chainB upgrade store + upgradeHeight := clienttypes.NewHeight(0, uint64(suite.chainB.GetContext().BlockHeight()+1)) // upgrade is at next block height + err = suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(upgradeHeight.GetRevisionHeight()), upgradedClientBz) + suite.Require().NoError(err) + err = suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(upgradeHeight.GetRevisionHeight()), upgradedConsStateBz) + suite.Require().NoError(err) + + // commit upgrade store changes on chainB and update client on chainA + suite.coordinator.CommitBlock(suite.chainB) + + err = path.EndpointA.UpdateClient() + suite.Require().NoError(err) + + upgradedClientProof, _ := suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(upgradeHeight.GetRevisionHeight())), uint64(suite.chainB.LatestCommittedHeader.Header.Height)) + upgradedConsensusStateProof, _ := suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(upgradeHeight.GetRevisionHeight())), uint64(suite.chainB.LatestCommittedHeader.Header.Height)) + + tmClientState, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), path.EndpointA.ClientID) + err = tmClientState.VerifyUpgradeAndUpdateState( + suite.chainA.GetContext(), + suite.cdc, + clientStore, + upgradeClient, + upgradedConsState, + upgradedClientProof, + upgradedConsensusStateProof, + ) + suite.Require().NoError(err) + + upgradedClient, ok := path.EndpointA.GetClientState().(*ibctm.ClientState) + suite.Require().True(ok) + + // assert the unbonding period and the trusting period have been updated correctly + suite.Require().Equal(newUnbondingPeriod, upgradedClient.UnbondingPeriod) + + // expected trusting period = trustingPeriod * newUnbonding / originalUnbonding (224 hours = 9 days and 8 hours) + origUnbondingDec := sdkmath.LegacyNewDec(ubdPeriod.Nanoseconds()) + newUnbondingDec := sdkmath.LegacyNewDec(newUnbondingPeriod.Nanoseconds()) + trustingPeriodDec := sdkmath.LegacyNewDec(trustingPeriod.Nanoseconds()) + + expTrustingPeriod := trustingPeriodDec.Mul(newUnbondingDec).Quo(origUnbondingDec) + suite.Require().Equal(time.Duration(expTrustingPeriod.TruncateInt64()), upgradedClient.TrustingPeriod) +} diff --git a/modules/light-clients/08-wasm/CHANGELOG.md b/modules/light-clients/08-wasm/CHANGELOG.md new file mode 100644 index 0000000..de2673d --- /dev/null +++ b/modules/light-clients/08-wasm/CHANGELOG.md @@ -0,0 +1,154 @@ + + +# Changelog + +## [[Unreleased]] + +### Dependencies + +* [\#7114](https://github.com/cosmos/ibc-go/pull/7114) Bump WasmVM to v2.1.2. +* [\#6828](https://github.com/cosmos/ibc-go/pull/6828) Bump Cosmos SDK to v0.50.9. +* [\#7247](https://github.com/cosmos/ibc-go/pull/7247) Bump CometBFT to v0.38.12. + +### API Breaking + +* [\#6937](https://github.com/cosmos/ibc-go/pull/6937) Remove `WasmConsensusHost` implementation of the `ConsensusHost` interface. + +### State Machine Breaking + +### Improvements + +### Features + +### Bug Fixes + + +## [v0.4.1+ibc-go-v8.4-wasmvm-v2.0](https://github.com/cosmos/ibc-go/releases/tag/modules%2Flight-clients%2F08-wasm%2Fv0.4.1%2Bibc-go-v8.4-wasmvm-v2.0) - 2024-07-31 + +### Dependencies + +* [\#6992](https://github.com/cosmos/ibc-go/pull/6992) Bump ibc-go to v8.4.0 and CosmosSDK to v0.50.7. + +### API Breaking + +* [\#6923](https://github.com/cosmos/ibc-go/pull/6923) The JSON message API for `VerifyMembershipMsg` and `VerifyNonMembershipMsg` payloads for client contract `SudoMsg` has been updated. The field `path` has been changed to `merkle_path`. This change requires updates to 08-wasm client contracts for integration. + + +## [v0.3.0+ibc-go-v8.3-wasmvm-v2.0](https://github.com/cosmos/ibc-go/releases/tag/modules%2Flight-clients%2F08-wasm%2Fv0.3.0%2Bibc-go-v8.3-wasmvm-v2.0) - 2024-07-17 + +### Dependencies + +* [\#6807](https://github.com/cosmos/ibc-go/pull/6807) Update wasmvm to v2.1.0. + +### API Breaking + +* [\#6644](https://github.com/cosmos/ibc-go/pull/6644) Add `v2.MerklePath` for contract api `VerifyMembershipMsg` and `VerifyNonMembershipMsg` structs. Note, this requires a migration for existing client contracts to correctly handle deserialization of `MerklePath.KeyPath` which has changed from `[]string` to `[][]bytes`. In JSON message structures this change is reflected as the `KeyPath` being a marshalled as a list of base64 encoded byte strings. This change supports proving values stored under keys which contain non-utf8 encoded symbols. See migration docs for more details. + +### Improvements + +* [\#5923](https://github.com/cosmos/ibc-go/pull/5923) imp: add 08-wasm build opts for libwasmvm linking disabled + +### Features + +* [\#6055](https://github.com/cosmos/ibc-go/pull/6055) feat: add 08-wasm `ConsensusHost` implementation for custom self client/consensus state validation in 03-connection handshake. + +### Bug Fixes + +* [\#6815](https://github.com/cosmos/ibc-go/pull/6815) Decode to bytes the hex-encoded checksum argument of the `migrate-contract` CLI. + + +## [v0.3.1+ibc-go-v7.4-wasmvm-v1.5](https://github.com/cosmos/ibc-go/releases/tag/modules%2Flight-clients%2F08-wasm%2Fv0.3.1%2Bibc-go-v7.4-wasmvm-v1.5) - 2024-07-31 + +### Dependencies + +* [\#6996](https://github.com/cosmos/ibc-go/pull/6992) Bump ibc-go to v7.4.0, CosmosSDK to v0.47.8 and CometBFT to v0.37.4. + +### API Breaking + +* [\#6923](https://github.com/cosmos/ibc-go/pull/6923) The JSON message API for `VerifyMembershipMsg` and `VerifyNonMembershipMsg` payloads for client contract `SudoMsg` has been updated. The field `path` has been changed to `merkle_path`. This change requires updates to 08-wasm client contracts for integration. + + +## [v0.2.0+ibc-go-v7.3-wasmvm-v1.5](https://github.com/cosmos/ibc-go/releases/tag/modules%2Flight-clients%2F08-wasm%2Fv0.2.0%2Bibc-go-v7.3-wasmvm-v1.5) - 2024-07-17 + +### API Breaking + +* [\#6644](https://github.com/cosmos/ibc-go/pull/6644) Add `v2.MerklePath` for contract api `VerifyMembershipMsg` and `VerifyNonMembershipMsg` structs. Note, this requires a migration for existing client contracts to correctly handle deserialization of `MerklePath.KeyPath` which has changed from `[]string` to `[][]byte`. In JSON message structures this change is reflected as the `KeyPath` being a marshalled as a list of base64 encoded byte strings. This change supports proving values stored under keys which contain non-utf8 encoded symbols. See migration docs for more details. + +### Features + +* [#\6231](https://github.com/cosmos/ibc-go/pull/6231) feat: add CLI to broadcast transaction with `MsgMigrateContract`. + +### Bug Fixes + +* [\#6815](https://github.com/cosmos/ibc-go/pull/6815) Decode to bytes the hex-encoded checksum argument of the `migrate-contract` CLI. + + +## [v0.2.0+ibc-go-v8.3-wasmvm-v2.0](https://github.com/cosmos/ibc-go/releases/tag/modules%2Flight-clients%2F08-wasm%2Fv0.2.0%2Bibc-go-v8.3-wasmvm-v2.0) - 2024-05-23 + +### Dependencies + +* [\#5909](https://github.com/cosmos/ibc-go/pull/5909) Update wasmvm to v2.0.0 and cometBFT to v0.38.6. +* [\#6097](https://github.com/cosmos/ibc-go/pull/6097) Update wasmvm to v2.0.1. +* [\#6350](https://github.com/cosmos/ibc-go/pull/6350) Upgrade Cosmos SDK to v0.50.6. + +### Features + +* [\#5821](https://github.com/cosmos/ibc-go/pull/5821) feat: add `VerifyMembershipProof` RPC query (querier approach for conditional clients). +* [\#6231](https://github.com/cosmos/ibc-go/pull/6231) feat: add CLI to broadcast transaction with `MsgMigrateContract`. + + +## [v0.1.1+ibc-go-v7.3-wasmvm-v1.5](https://github.com/cosmos/ibc-go/releases/tag/modules%2Flight-clients%2F08-wasm%2Fv0.1.1%2Bibc-go-v7.3-wasmvm-v1.5) - 2024-04-12 + +### Dependencies + +* [\#6149](https://github.com/cosmos/ibc-go/pull/6149) Bump wasmvm to v1.5.2. + +### Bug Fixes + +* (cli) [\#5610](https://github.com/cosmos/ibc-go/pull/5610) Register wasm tx cli. + + +## [v0.1.0+ibc-go-v8.0-wasmvm-v1.5](https://github.com/cosmos/ibc-go/releases/tag/modules%2Flight-clients%2F08-wasm%2Fv0.1.0%2Bibc-go-v7.3-wasmvm-v1.5) - 2023-12-18 + +### Features + +* [\#5079](https://github.com/cosmos/ibc-go/pull/5079) feat: 08-wasm light client proxy module for wasm clients. + + +## [v0.1.0+ibc-go-v7.3-wasmvm-v1.5](https://github.com/cosmos/ibc-go/releases/tag/modules%2Flight-clients%2F08-wasm%2Fv0.1.0%2Bibc-go-v8.0-wasmvm-v1.5) - 2023-12-18 + +### Features + +* [\#5079](https://github.com/cosmos/ibc-go/pull/5079) feat: 08-wasm light client proxy module for wasm clients. diff --git a/modules/light-clients/08-wasm/Dockerfile b/modules/light-clients/08-wasm/Dockerfile new file mode 100644 index 0000000..8e63596 --- /dev/null +++ b/modules/light-clients/08-wasm/Dockerfile @@ -0,0 +1,49 @@ +FROM golang:1.23.8-alpine AS builder-base + +ARG LIBWASM_VERSION +ARG TARGETARCH + +RUN test -n "${LIBWASM_VERSION}" +ENV GOPATH="" + +RUN set -eux; apk add --no-cache ca-certificates build-base git libusb-dev linux-headers curl; + +# Copy relevant files before go mod download. Replace directives to local paths break if local +# files are not copied before go mod download. +ADD internal internal +ADD testing testing +ADD modules modules +ADD LICENSE LICENSE + +COPY go.mod . +COPY go.sum . + +RUN go mod download + + +# Since it is not easy to fully cache a RUN script download of libwasmvm, we use two different stages +# and copy the correct file in the final stage. The multistage setup also helps speed up the build process +FROM alpine:3.21 AS amd64-stage +ARG LIBWASM_VERSION +ADD https://github.com/CosmWasm/wasmvm/releases/download/${LIBWASM_VERSION}/libwasmvm_muslc.x86_64.a /lib/libwasmvm_muslc.x86_64.a + + +FROM alpine:3.21 AS arm64-stage +ARG LIBWASM_VERSION +ADD https://github.com/CosmWasm/wasmvm/releases/download/${LIBWASM_VERSION}/libwasmvm_muslc.aarch64.a /lib/libwasmvm_muslc.aarch64.a + +# We have this one with nothing else in it, because COPY --from can't use variables (but FROM can) +FROM ${TARGETARCH}-stage AS libwasm-stage + +# Having this is a separate stage allows the previous stages to run in parallel +FROM builder-base AS builder +WORKDIR /go/modules/light-clients/08-wasm + +COPY --from=libwasm-stage /lib/libwasmvm_muslc.* /lib/ + +RUN go build -mod=readonly -tags "netgo ledger muslc" -ldflags '-X github.com/cosmos/cosmos-sdk/version.Name=sim -X github.com/cosmos/cosmos-sdk/version.AppName=simd -X github.com/cosmos/cosmos-sdk/version.Version= -X github.com/cosmos/cosmos-sdk/version.Commit= -X "github.com/cosmos/cosmos-sdk/version.BuildTags=netgo ledger muslc," -w -s -linkmode=external -extldflags "-Wl,-z,muldefs -static"' -trimpath -o /go/build/ ./... + + +FROM alpine:3.21 +COPY --from=builder /go/build/simd /bin/simd +ENTRYPOINT ["simd"] diff --git a/modules/light-clients/08-wasm/README.md b/modules/light-clients/08-wasm/README.md new file mode 100644 index 0000000..bb78b55 --- /dev/null +++ b/modules/light-clients/08-wasm/README.md @@ -0,0 +1,29 @@ +# `08-wasm` + +## Overview + +Learn about the `08-wasm` light client proxy module. + +### Context + +Traditionally, light clients used by ibc-go have been implemented only in Go, and since ibc-go v7 (with the release of the 02-client refactor), they are [first-class Cosmos SDK modules](/architecture/adr-010-light-clients-as-sdk-modules). This means that updating existing light client implementations or adding support for new light clients is a multi-step, time-consuming process involving on-chain governance: it is necessary to modify the codebase of ibc-go (if the light client is part of its codebase), re-build chains' binaries, pass a governance proposal and have validators upgrade their nodes. + +### Motivation + +To break the limitation of being able to write light client implementations only in Go, the `08-wasm` adds support to run light clients written in a Wasm-compilable language. The light client byte code implements the entry points of a [CosmWasm](https://docs.cosmwasm.com/docs/) smart contract, and runs inside a Wasm VM. The `08-wasm` module exposes a proxy light client interface that routes incoming messages to the appropriate handler function, inside the Wasm VM, for execution. + +Adding a new light client to a chain is just as simple as submitting a governance proposal with the message that stores the byte code of the light client contract. No coordinated upgrade is needed. When the governance proposal passes and the message is executed, the contract is ready to be instantiated upon receiving a relayer-submitted `MsgCreateClient`. The process of creating a Wasm light client is the same as with a regular light client implemented in Go. + +### Use cases + +- Development of light clients for non-Cosmos ecosystem chains: state machines in other ecosystems are, in many cases, implemented in Rust, and thus there are probably libraries used in their light client implementations for which there is no equivalent in Go. This makes the development of a light client in Go very difficult, but relatively simple to do it in Rust. Therefore, writing a CosmWasm smart contract in Rust that implements the light client algorithm becomes a lower effort. + +## Version Matrix + +`08-wasm` has no stable releases yet. To use it, you need to import the git commit that contains the module with the compatible versions of `ibc-go` and `wasmvm`. To do so, run the following command with the desired git commit in your project: + +```sh +go get github.com/cosmos/ibc-go/modules/light-clients/08-wasm@7ee2a2452b79d0bc8316dc622a1243afa058e8cb +``` + +You can find the compatibility matrix between the `08-wasm` module, `ibc-go`, and `wasmvm` in [here](https://github.com/cosmos/ibc-go/blob/main/docs/docs/03-light-clients/04-wasm/03-integration.md#importing-the-08-wasm-module). diff --git a/modules/light-clients/08-wasm/blsverifier/crypto.go b/modules/light-clients/08-wasm/blsverifier/crypto.go new file mode 100644 index 0000000..62828db --- /dev/null +++ b/modules/light-clients/08-wasm/blsverifier/crypto.go @@ -0,0 +1,19 @@ +package blsverifier + +import ( + "fmt" + + "github.com/prysmaticlabs/prysm/v5/crypto/bls" +) + +func AggregatePublicKeys(publicKeys [][]byte) (bls.PublicKey, error) { + return bls.AggregatePublicKeys(publicKeys) +} + +func VerifySignature(signature []byte, message [32]byte, publicKeys [][]byte) (bool, error) { + aggregatedPublicKey, err := AggregatePublicKeys(publicKeys) + if err != nil { + return false, fmt.Errorf("failed to aggregate public keys %v", err) + } + return bls.VerifySignature(signature, message, aggregatedPublicKey) +} diff --git a/modules/light-clients/08-wasm/blsverifier/handler.go b/modules/light-clients/08-wasm/blsverifier/handler.go new file mode 100644 index 0000000..fd16cff --- /dev/null +++ b/modules/light-clients/08-wasm/blsverifier/handler.go @@ -0,0 +1,67 @@ +package blsverifier + +/* + * This custom query handler is used to aggregate public keys and verify a signature using BLS. + * It is used by the 08-wasm union light client, which we we use in the solidity IBC v2 e2e tests. + * The code here is taken from here: https://github.com/unionlabs/union/tree/main/uniond/app/custom_query + */ +import ( + "encoding/json" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + MessageSize = 32 +) + +type CustomQuery struct { + AggregateVerify *QueryAggregateVerify `json:"aggregate_verify,omitempty"` + Aggregate *QueryAggregate `json:"aggregate,omitempty"` +} +type QueryAggregate struct { + PublicKeys [][]byte `json:"public_keys"` +} +type QueryAggregateVerify struct { + PublicKeys [][]byte `json:"public_keys"` + Signature []byte `json:"signature"` + Message []byte `json:"message"` +} + +func CustomQuerier() func(sdk.Context, json.RawMessage) ([]byte, error) { + return func(ctx sdk.Context, request json.RawMessage) ([]byte, error) { + var customQuery CustomQuery + err := json.Unmarshal([]byte(request), &customQuery) + if err != nil { + return nil, fmt.Errorf("failed to parse custom query %v", err) + } + + switch { + case customQuery.Aggregate != nil: + aggregatedPublicKeys, err := AggregatePublicKeys(customQuery.Aggregate.PublicKeys) + if err != nil { + return nil, fmt.Errorf("failed to aggregate public keys %v", err) + } + + return json.Marshal(aggregatedPublicKeys.Marshal()) + case customQuery.AggregateVerify != nil: + if len(customQuery.AggregateVerify.Message) != MessageSize { + return nil, fmt.Errorf("invalid message length (%d), must be a %d bytes hash: %x", len(customQuery.AggregateVerify.Message), MessageSize, customQuery.AggregateVerify.Message) + } + + msg := [MessageSize]byte{} + for i := range MessageSize { + msg[i] = customQuery.AggregateVerify.Message[i] + } + result, err := VerifySignature(customQuery.AggregateVerify.Signature, msg, customQuery.AggregateVerify.PublicKeys) + if err != nil { + return nil, fmt.Errorf("failed to verify signature %v", err) + } + + return json.Marshal(result) + default: + return nil, fmt.Errorf("unknown custom query %v", request) + } + } +} diff --git a/modules/light-clients/08-wasm/client/cli/cli.go b/modules/light-clients/08-wasm/client/cli/cli.go new file mode 100644 index 0000000..6203e32 --- /dev/null +++ b/modules/light-clients/08-wasm/client/cli/cli.go @@ -0,0 +1,43 @@ +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" +) + +// GetQueryCmd returns the query commands for IBC channels +func GetQueryCmd() *cobra.Command { + queryCmd := &cobra.Command{ + Use: "ibc-wasm", + Short: "IBC wasm manager module query subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + queryCmd.AddCommand( + getCmdCode(), + getCmdChecksums(), + ) + + return queryCmd +} + +// NewTxCmd returns a CLI command handler for all x/ibc channel transaction commands. +func NewTxCmd() *cobra.Command { + txCmd := &cobra.Command{ + Use: "ibc-wasm", + Short: "IBC wasm manager module transaction subcommands", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + txCmd.AddCommand( + newSubmitStoreCodeProposalCmd(), + newMigrateContractCmd(), + ) + + return txCmd +} diff --git a/modules/light-clients/08-wasm/client/cli/query.go b/modules/light-clients/08-wasm/client/cli/query.go new file mode 100644 index 0000000..863caaf --- /dev/null +++ b/modules/light-clients/08-wasm/client/cli/query.go @@ -0,0 +1,80 @@ +package cli + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/version" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// getCmdCode defines the command to query wasm code for given checksum. +func getCmdCode() *cobra.Command { + cmd := &cobra.Command{ + Use: "code [checksum]", + Short: "Query wasm code", + Long: "Query wasm code for a light client wasm contract with a given checksum", + Example: fmt.Sprintf("%s query %s wasm code [checksum]", version.AppName, ibcexported.ModuleName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + checksum := args[0] + req := types.QueryCodeRequest{ + Checksum: checksum, + } + + res, err := queryClient.Code(context.Background(), &req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// getCmdChecksums defines the command to query all wasm checksums. +func getCmdChecksums() *cobra.Command { + cmd := &cobra.Command{ + Use: "checksums", + Short: "Query all checksums", + Long: "Query all checksums for all deployed light client wasm contracts", + Example: fmt.Sprintf("%s query %s wasm checksums", version.AppName, ibcexported.ModuleName), + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + req := types.QueryChecksumsRequest{} + + res, err := queryClient.Checksums(context.Background(), &req) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "all wasm code") + + return cmd +} diff --git a/modules/light-clients/08-wasm/client/cli/tx.go b/modules/light-clients/08-wasm/client/cli/tx.go new file mode 100644 index 0000000..ee9445a --- /dev/null +++ b/modules/light-clients/08-wasm/client/cli/tx.go @@ -0,0 +1,128 @@ +package cli + +import ( + "encoding/hex" + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + "github.com/cosmos/cosmos-sdk/version" + govcli "github.com/cosmos/cosmos-sdk/x/gov/client/cli" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +const FlagAuthority = "authority" + +// newSubmitStoreCodeProposalCmd returns the command to send a proposal to store new wasm bytecode. +func newSubmitStoreCodeProposalCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "store-code [path/to/wasm-file]", + Short: "Reads wasm code from the file and creates a proposal to store the wasm code", + Long: "Reads wasm code from the file and creates a proposal to store the wasm code", + Example: fmt.Sprintf("%s tx %s-wasm store-code [path/to/wasm_file]", version.AppName, ibcexported.ModuleName), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + proposal, err := govcli.ReadGovPropFlags(clientCtx, cmd.Flags()) + if err != nil { + return err + } + + authority, _ := cmd.Flags().GetString(FlagAuthority) + if authority != "" { + if _, err = sdk.AccAddressFromBech32(authority); err != nil { + return fmt.Errorf("invalid authority address: %w", err) + } + } else { + authority = sdk.AccAddress(address.Module(govtypes.ModuleName)).String() + } + + code, err := os.ReadFile(args[0]) + if err != nil { + return err + } + + msg := &types.MsgStoreCode{ + Signer: authority, + WasmByteCode: code, + } + + if err := msg.ValidateBasic(); err != nil { + return err + } + + if err := proposal.SetMsgs([]sdk.Msg{msg}); err != nil { + return fmt.Errorf("failed to create a store code proposal message: %w", err) + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), proposal) + }, + } + + cmd.Flags().String(FlagAuthority, "", "The address of the wasm client module authority (defaults to gov)") + + flags.AddTxFlagsToCmd(cmd) + govcli.AddGovPropFlagsToCmd(cmd) + err := cmd.MarkFlagRequired(govcli.FlagTitle) + if err != nil { + panic(err) + } + + return cmd +} + +func newMigrateContractCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "migrate-contract [client-id] [checksum] [migrate-msg]", + Short: "Migrates a contract to a new byte code", + Long: "Migrates the contract for the specified client ID to the byte code corresponding to checksum, passing the JSON-encoded migrate message to the contract", + Example: fmt.Sprintf("%s tx %s-wasm migrate-contract 08-wasm-0 b3a49b2914f5e6a673215e74325c1d153bb6776e079774e52c5b7e674d9ad3ab {}", version.AppName, ibcexported.ModuleName), + Args: cobra.ExactArgs(3), // Ensure exactly three arguments are passed + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + clientID := args[0] + checksumHex := args[1] + migrateMsg := args[2] + + checksum, err := hex.DecodeString(checksumHex) + if err != nil { + return fmt.Errorf("invalid checksum format: %w", err) + } + + // Construct the message + msg := &types.MsgMigrateContract{ + Signer: clientCtx.GetFromAddress().String(), + ClientId: clientID, + Checksum: checksum, + Msg: []byte(migrateMsg), + } + + if err := msg.ValidateBasic(); err != nil { + return err + } + + // Generate or broadcast the transaction + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} diff --git a/modules/light-clients/08-wasm/doc.go b/modules/light-clients/08-wasm/doc.go new file mode 100644 index 0000000..dc0b1eb --- /dev/null +++ b/modules/light-clients/08-wasm/doc.go @@ -0,0 +1,18 @@ +/* +Package wasm implements a concrete LightClientModule, ClientState, ConsensusState, +ClientMessage and types for the proxy light client module communicating +with underlying Wasm light clients. +This implementation is based off the ICS 08 specification +(https://github.com/cosmos/ibc/blob/main/spec/client/ics-008-wasm-client) + +By default the 08-wasm module requires cgo and libwasmvm dependencies available on the system. +However, users of this module may want to depend only on types, without incurring the dependency on cgo or libwasmvm. +In this case, it is possible to build the code with either cgo disabled or a custom build directive: nolink_libwasmvm. +This allows disabling linking of libwasmvm and not forcing users to have specific libraries available on their systems. + +Please refer to the 08-wasm module documentation for more information. + +Note that client identifiers are expected to be in the form: 08-wasm-{N}. +Client identifiers are generated and validated by core IBC, unexpected client identifiers will result in errors. +*/ +package wasm diff --git a/modules/light-clients/08-wasm/go.mod b/modules/light-clients/08-wasm/go.mod new file mode 100644 index 0000000..9221b63 --- /dev/null +++ b/modules/light-clients/08-wasm/go.mod @@ -0,0 +1,242 @@ +module github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10 + +go 1.23.8 + +replace github.com/cosmos/ibc-go/v10 => ../../../ + +replace github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 + +require ( + cosmossdk.io/api v0.9.2 + cosmossdk.io/client/v2 v2.0.0-beta.9 + cosmossdk.io/collections v1.2.1 + cosmossdk.io/core v0.11.3 + cosmossdk.io/errors v1.0.2 + cosmossdk.io/log v1.6.1 + cosmossdk.io/math v1.5.3 + cosmossdk.io/store v1.1.2 + cosmossdk.io/tools/confix v0.1.2 + cosmossdk.io/x/circuit v0.2.0 + cosmossdk.io/x/evidence v0.2.0 + cosmossdk.io/x/feegrant v0.2.0 + cosmossdk.io/x/tx v0.14.0 + cosmossdk.io/x/upgrade v0.2.0 + github.com/CosmWasm/wasmvm/v2 v2.2.4 + github.com/cometbft/cometbft v0.38.17 + github.com/cosmos/cosmos-db v1.1.3 + github.com/cosmos/cosmos-sdk v0.53.4 + github.com/cosmos/gogoproto v1.7.0 + github.com/cosmos/ibc-go/v10 v10.3.0 + github.com/golang/protobuf v1.5.4 + github.com/grpc-ecosystem/grpc-gateway v1.16.0 + github.com/prysmaticlabs/prysm/v5 v5.3.0 + github.com/spf13/cast v1.9.2 + github.com/spf13/cobra v1.10.1 + github.com/spf13/viper v1.20.1 + github.com/stretchr/testify v1.11.1 + google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 + google.golang.org/grpc v1.75.0 +) + +require ( + cel.dev/expr v0.24.0 // indirect + cloud.google.com/go v0.116.0 // indirect + cloud.google.com/go/auth v0.14.1 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect + cloud.google.com/go/compute/metadata v0.7.0 // indirect + cloud.google.com/go/iam v1.2.2 // indirect + cloud.google.com/go/monitoring v1.21.2 // indirect + cloud.google.com/go/storage v1.49.0 // indirect + cosmossdk.io/depinject v1.2.1 // indirect + cosmossdk.io/schema v1.1.0 // indirect + filippo.io/edwards25519 v1.1.0 // indirect + github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect + github.com/99designs/keyring v1.2.2 // indirect + github.com/DataDog/datadog-go v4.8.3+incompatible // indirect + github.com/DataDog/zstd v1.5.7 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/aws/aws-sdk-go v1.49.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect + github.com/bgentry/speakeasy v0.2.0 // indirect + github.com/bits-and-blooms/bitset v1.22.0 // indirect + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/chzyer/readline v1.5.1 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect + github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect + github.com/cockroachdb/apd/v2 v2.0.2 // indirect + github.com/cockroachdb/errors v1.12.0 // indirect + github.com/cockroachdb/fifo v0.0.0-20240616162244-4768e80dfb9a // indirect + github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 // indirect + github.com/cockroachdb/pebble v1.1.5 // indirect + github.com/cockroachdb/redact v1.1.6 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect + github.com/cometbft/cometbft-db v0.14.1 // indirect + github.com/cosmos/btcutil v1.0.5 // indirect + github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect + github.com/cosmos/go-bip39 v1.0.0 // indirect + github.com/cosmos/gogogateway v1.2.0 // indirect + github.com/cosmos/iavl v1.2.2 // indirect + github.com/cosmos/ics23/go v0.11.0 // indirect + github.com/cosmos/ledger-cosmos-go v0.14.0 // indirect + github.com/creachadair/atomicfile v0.3.1 // indirect + github.com/creachadair/tomledit v0.0.24 // indirect + github.com/danieljoos/wincred v1.2.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect + github.com/desertbit/timer v1.0.1 // indirect + github.com/dgraph-io/badger/v4 v4.2.0 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/dvsekhvalnov/jose2go v1.7.0 // indirect + github.com/emicklei/dot v1.6.2 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect + github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect + github.com/ethereum/go-ethereum v1.15.11 // indirect + github.com/fatih/color v1.17.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/getsentry/sentry-go v0.32.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.1 // indirect + github.com/go-kit/kit v0.13.0 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect + github.com/gogo/googleapis v1.4.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v1.2.5 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/snappy v0.0.5-0.20231225225746-43d5d4cd4e0e // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/flatbuffers v24.3.25+incompatible // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/orderedcode v0.0.1 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/gax-go/v2 v2.14.1 // indirect + github.com/gorilla/handlers v1.5.2 // indirect + github.com/gorilla/mux v1.8.1 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect + github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-getter v1.7.8 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-metrics v0.5.4 // indirect + github.com/hashicorp/go-plugin v1.6.3 // indirect + github.com/hashicorp/go-safetemp v1.0.0 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/yamux v0.1.2 // indirect + github.com/hdevalence/ed25519consensus v0.2.0 // indirect + github.com/herumi/bls-eth-go-binary v1.31.0 // indirect + github.com/holiman/uint256 v1.3.2 // indirect + github.com/huandu/skiplist v1.2.1 // indirect + github.com/iancoleman/strcase v0.3.0 // indirect + github.com/improbable-eng/grpc-web v0.15.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/jmhodges/levigo v1.0.0 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/linxGnu/grocksdb v1.9.2 // indirect + github.com/manifoldco/promptui v0.9.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mdp/qrterminal/v3 v3.2.1 // indirect + github.com/minio/highwayhash v1.0.3 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/mtibben/percent v0.2.1 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect + github.com/oklog/run v1.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.63.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/prysmaticlabs/fastssz v0.0.0-20241008181541-518c4ce73516 // indirect + github.com/prysmaticlabs/go-bitfield v0.0.0-20240328144219-a1caa50c3a1e // indirect + github.com/prysmaticlabs/gohashtree v0.0.4-beta.0.20240624100937-73632381301b // indirect + github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/rs/cors v1.11.1 // indirect + github.com/rs/zerolog v1.34.0 // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/sasha-s/go-deadlock v0.3.5 // indirect + github.com/shamaton/msgpack/v2 v2.2.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/pflag v1.0.9 // indirect + github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/supranational/blst v0.3.14 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect + github.com/tendermint/go-amino v0.16.0 // indirect + github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e // indirect + github.com/tidwall/btree v1.7.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ulikunitz/xz v0.5.11 // indirect + github.com/zeebo/errs v1.4.0 // indirect + github.com/zondax/hid v0.9.2 // indirect + github.com/zondax/ledger-go v0.14.3 // indirect + go.etcd.io/bbolt v1.4.0-alpha.1 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.uber.org/mock v0.5.2 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/arch v0.17.0 // indirect + golang.org/x/crypto v0.39.0 // indirect + golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.15.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.26.0 // indirect + golang.org/x/time v0.10.0 // indirect + google.golang.org/api v0.222.0 // indirect + google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + google.golang.org/protobuf v1.36.8 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gotest.tools/v3 v3.5.2 // indirect + nhooyr.io/websocket v1.8.11 // indirect + pgregory.net/rapid v1.2.0 // indirect + rsc.io/qr v0.2.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect +) diff --git a/modules/light-clients/08-wasm/go.sum b/modules/light-clients/08-wasm/go.sum new file mode 100644 index 0000000..b942c2b --- /dev/null +++ b/modules/light-clients/08-wasm/go.sum @@ -0,0 +1,2533 @@ +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= +cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= +cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= +cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= +cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= +cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= +cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= +cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= +cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= +cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= +cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= +cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= +cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= +cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= +cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= +cloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k= +cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= +cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= +cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= +cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= +cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE= +cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= +cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= +cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= +cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= +cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= +cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= +cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= +cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= +cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= +cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= +cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= +cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= +cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= +cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= +cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= +cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= +cloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A= +cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= +cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= +cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= +cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY= +cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= +cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= +cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= +cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= +cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= +cloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI= +cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ= +cloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI= +cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= +cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= +cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= +cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= +cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= +cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= +cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= +cloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg= +cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= +cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= +cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= +cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= +cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= +cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= +cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= +cloud.google.com/go/auth v0.14.1 h1:AwoJbzUdxA/whv1qj3TLKwh3XX5sikny2fc40wUl+h0= +cloud.google.com/go/auth v0.14.1/go.mod h1:4JHUxlGXisL0AW8kXPtUF6ztuOksyfUQNFjfsOCXkPM= +cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= +cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= +cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= +cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= +cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= +cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= +cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= +cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= +cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= +cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= +cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= +cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= +cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= +cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= +cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= +cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= +cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= +cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= +cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= +cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E= +cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= +cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q= +cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= +cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= +cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= +cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= +cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= +cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= +cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= +cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= +cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= +cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= +cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= +cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= +cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= +cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= +cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= +cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= +cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= +cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= +cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= +cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= +cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= +cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= +cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg= +cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= +cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= +cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= +cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= +cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= +cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= +cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= +cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= +cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= +cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= +cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= +cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= +cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= +cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= +cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= +cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= +cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= +cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= +cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= +cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= +cloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM= +cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= +cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= +cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= +cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= +cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= +cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= +cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= +cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= +cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= +cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= +cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= +cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= +cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= +cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= +cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= +cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= +cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= +cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= +cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= +cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= +cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= +cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= +cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= +cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= +cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= +cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= +cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= +cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= +cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= +cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= +cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= +cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= +cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= +cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= +cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= +cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= +cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= +cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= +cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= +cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= +cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= +cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= +cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= +cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= +cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= +cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= +cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= +cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= +cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= +cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= +cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= +cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= +cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= +cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM= +cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= +cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= +cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= +cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= +cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= +cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= +cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= +cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= +cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= +cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= +cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= +cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= +cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= +cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= +cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= +cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= +cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= +cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= +cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= +cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= +cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= +cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= +cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= +cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= +cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= +cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= +cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= +cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= +cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= +cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= +cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= +cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= +cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= +cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw= +cloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA= +cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= +cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= +cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= +cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= +cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= +cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= +cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= +cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= +cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= +cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= +cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= +cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= +cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= +cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= +cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= +cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= +cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= +cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= +cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= +cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= +cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= +cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= +cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= +cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= +cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= +cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= +cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA= +cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= +cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= +cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= +cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= +cloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo= +cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= +cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= +cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= +cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= +cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= +cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= +cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= +cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= +cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= +cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= +cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= +cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg= +cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= +cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= +cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= +cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= +cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= +cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= +cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= +cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= +cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= +cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= +cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= +cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= +cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= +cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk= +cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM= +cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= +cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= +cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= +cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc= +cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= +cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= +cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= +cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= +cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= +cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= +cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= +cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= +cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= +cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= +cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= +cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= +cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= +cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= +cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= +cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= +cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= +cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= +cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= +cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= +cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= +cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= +cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= +cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= +cloud.google.com/go/monitoring v1.21.2 h1:FChwVtClH19E7pJ+e0xUhJPGksctZNVOk2UhMmblmdU= +cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= +cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= +cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= +cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= +cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= +cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= +cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= +cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= +cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= +cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= +cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= +cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= +cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= +cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= +cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= +cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= +cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= +cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= +cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= +cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= +cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= +cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= +cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= +cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= +cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= +cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= +cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= +cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= +cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= +cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= +cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= +cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= +cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= +cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= +cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= +cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= +cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= +cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= +cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= +cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= +cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= +cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= +cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= +cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= +cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= +cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= +cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= +cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= +cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= +cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= +cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= +cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= +cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= +cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= +cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= +cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= +cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= +cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= +cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= +cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= +cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= +cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= +cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= +cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= +cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= +cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= +cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= +cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= +cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= +cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= +cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= +cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= +cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= +cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= +cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= +cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= +cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= +cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= +cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= +cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= +cloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo= +cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= +cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= +cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= +cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= +cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= +cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= +cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= +cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= +cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= +cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= +cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= +cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= +cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= +cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= +cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= +cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= +cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= +cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= +cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= +cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= +cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= +cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= +cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= +cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= +cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= +cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= +cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= +cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= +cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= +cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= +cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= +cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= +cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= +cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= +cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= +cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= +cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= +cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= +cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= +cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc= +cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= +cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= +cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= +cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= +cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= +cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= +cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= +cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= +cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= +cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= +cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= +cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= +cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= +cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec= +cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= +cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= +cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= +cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= +cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= +cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= +cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= +cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= +cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= +cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= +cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= +cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= +cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= +cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= +cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= +cloud.google.com/go/storage v1.49.0 h1:zenOPBOWHCnojRd9aJZAyQXBYqkJkdQS42dxL55CIMw= +cloud.google.com/go/storage v1.49.0/go.mod h1:k1eHhhpLvrPjVGfo0mOUPEJ4Y2+a/Hv5PiwehZI9qGU= +cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= +cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= +cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= +cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= +cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= +cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= +cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= +cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= +cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= +cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= +cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= +cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= +cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= +cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= +cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= +cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= +cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= +cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= +cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= +cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI= +cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io= +cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= +cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= +cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= +cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= +cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= +cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= +cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk= +cloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= +cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= +cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= +cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= +cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= +cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= +cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= +cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= +cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= +cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= +cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= +cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= +cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= +cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= +cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= +cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= +cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= +cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= +cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= +cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= +cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= +cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= +cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= +cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= +cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= +cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= +cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= +cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= +cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= +cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= +cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= +cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= +cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= +cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= +cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= +cosmossdk.io/api v0.9.2 h1:9i9ptOBdmoIEVEVWLtYYHjxZonlF/aOVODLFaxpmNtg= +cosmossdk.io/api v0.9.2/go.mod h1:CWt31nVohvoPMTlPv+mMNCtC0a7BqRdESjCsstHcTkU= +cosmossdk.io/client/v2 v2.0.0-beta.9 h1:xc06zg4G858/pK5plhf8RCfo+KR2mdDKJNrEkfrVAqc= +cosmossdk.io/client/v2 v2.0.0-beta.9/go.mod h1:pHf3CCHX5gmbL9rDCVbXhGI2+/DdAVTEZSLpdd5V9Zs= +cosmossdk.io/collections v1.2.1 h1:mAlNMs5vJwkda4TA+k5q/43p24RVAQ/qyDrjANu3BXE= +cosmossdk.io/collections v1.2.1/go.mod h1:PSsEJ/fqny0VPsHLFT6gXDj/2C1tBOTS9eByK0+PBFU= +cosmossdk.io/core v0.11.3 h1:mei+MVDJOwIjIniaKelE3jPDqShCc/F4LkNNHh+4yfo= +cosmossdk.io/core v0.11.3/go.mod h1:9rL4RE1uDt5AJ4Tg55sYyHWXA16VmpHgbe0PbJc6N2Y= +cosmossdk.io/depinject v1.2.1 h1:eD6FxkIjlVaNZT+dXTQuwQTKZrFZ4UrfCq1RKgzyhMw= +cosmossdk.io/depinject v1.2.1/go.mod h1:lqQEycz0H2JXqvOgVwTsjEdMI0plswI7p6KX+MVqFOM= +cosmossdk.io/errors v1.0.2 h1:wcYiJz08HThbWxd/L4jObeLaLySopyyuUFB5w4AGpCo= +cosmossdk.io/errors v1.0.2/go.mod h1:0rjgiHkftRYPj//3DrD6y8hcm40HcPv/dR4R/4efr0k= +cosmossdk.io/log v1.6.1 h1:YXNwAgbDwMEKwDlCdH8vPcoggma48MgZrTQXCfmMBeI= +cosmossdk.io/log v1.6.1/go.mod h1:gMwsWyyDBjpdG9u2avCFdysXqxq28WJapJvu+vF1y+E= +cosmossdk.io/math v1.5.3 h1:WH6tu6Z3AUCeHbeOSHg2mt9rnoiUWVWaQ2t6Gkll96U= +cosmossdk.io/math v1.5.3/go.mod h1:uqcZv7vexnhMFJF+6zh9EWdm/+Ylyln34IvPnBauPCQ= +cosmossdk.io/schema v1.1.0 h1:mmpuz3dzouCoyjjcMcA/xHBEmMChN+EHh8EHxHRHhzE= +cosmossdk.io/schema v1.1.0/go.mod h1:Gb7pqO+tpR+jLW5qDcNOSv0KtppYs7881kfzakguhhI= +cosmossdk.io/store v1.1.2 h1:3HOZG8+CuThREKv6cn3WSohAc6yccxO3hLzwK6rBC7o= +cosmossdk.io/store v1.1.2/go.mod h1:60rAGzTHevGm592kFhiUVkNC9w7gooSEn5iUBPzHQ6A= +cosmossdk.io/tools/confix v0.1.2 h1:2hoM1oFCNisd0ltSAAZw2i4ponARPmlhuNu3yy0VwI4= +cosmossdk.io/tools/confix v0.1.2/go.mod h1:7XfcbK9sC/KNgVGxgLM0BrFbVcR/+6Dg7MFfpx7duYo= +cosmossdk.io/x/circuit v0.2.0 h1:RJPMBQWCQU77EcM9HDTBnqRhq21fcUxgWZl7BZylJZo= +cosmossdk.io/x/circuit v0.2.0/go.mod h1:CjiGXDeZs64nMv0fG+QmvGVTcn7n3Sv4cDszMRR2JqU= +cosmossdk.io/x/evidence v0.2.0 h1:o72zbmgCM7U0v7z7b0XnMB+NqX0tFamqb1HHkQbhrZ0= +cosmossdk.io/x/evidence v0.2.0/go.mod h1:zx/Xqy+hnGVzkqVuVuvmP9KsO6YCl4SfbAetYi+k+sE= +cosmossdk.io/x/feegrant v0.2.0 h1:oq3WVpoJdxko/XgWmpib63V1mYy9ZQN/1qxDajwGzJ8= +cosmossdk.io/x/feegrant v0.2.0/go.mod h1:9CutZbmhulk/Yo6tQSVD5LG8Lk40ZAQ1OX4d1CODWAE= +cosmossdk.io/x/tx v0.14.0 h1:hB3O25kIcyDW/7kMTLMaO8Ripj3yqs5imceVd6c/heA= +cosmossdk.io/x/tx v0.14.0/go.mod h1:Tn30rSRA1PRfdGB3Yz55W4Sn6EIutr9xtMKSHij+9PM= +cosmossdk.io/x/upgrade v0.2.0 h1:ZHy0xny3wBCSLomyhE06+UmQHWO8cYlVYjfFAJxjz5g= +cosmossdk.io/x/upgrade v0.2.0/go.mod h1:DXDtkvi//TrFyHWSOaeCZGBoiGAE6Rs8/0ABt2pcDD0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= +git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= +github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0= +github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/CosmWasm/wasmvm/v2 v2.2.4 h1:V3UwXJMA8TNOuQETppDQkaXAevF7gOWLYpKvrThPv7o= +github.com/CosmWasm/wasmvm/v2 v2.2.4/go.mod h1:Aj/rB2KMRM8nAdbWxkO23rnQYb5KsoPuH9ZizSi0sVg= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/datadog-go v4.8.3+incompatible h1:fNGaYSuObuQb5nzeTQqowRAd9bpDIRRV4/gUtIBjh8Q= +github.com/DataDog/datadog-go v4.8.3+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= +github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 h1:UQ0AhxogsIRZDkElkblfnwjc3IaltCm2HUMvezQaL7s= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1 h1:oTX4vsorBZo/Zdum6OKPA4o7544hm6smoRv1QjpTwGo= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1/go.mod h1:0wEl7vrAD8mehJyohS9HZy+WyEOaQO2mJx86Cvh93kM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 h1:8nn+rsCvTq9axyEh382S0PFLBeaFwNsT43IrPWzctRU= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE= +github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/adlio/schema v1.3.6 h1:k1/zc2jNfeiZBA5aFTRy37jlBIuCkXCm0XmvpzCKI9I= +github.com/adlio/schema v1.3.6/go.mod h1:qkxwLgPBd1FgLRHYVCmQT/rrBr3JH38J9LjmVzWNudg= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= +github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= +github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.49.0 h1:g9BkW1fo9GqKfwg2+zCD+TW/D36Ux+vtfJ8guF4AYmY= +github.com/aws/aws-sdk-go v1.49.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/bazelbuild/rules_go v0.23.2 h1:Wxu7JjqnF78cKZbsBsARLSXx/jlGaSLCnUV3mTlyHvM= +github.com/bazelbuild/rules_go v0.23.2/go.mod h1:MC23Dc/wkXEyk3Wpq6lCqz0ZAYOZDw2DR5y3N1q2i7M= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bgentry/speakeasy v0.2.0 h1:tgObeVOf8WAvtuAX6DhJ4xks4CFNwPDZiqzGqIHE51E= +github.com/bgentry/speakeasy v0.2.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4= +github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= +github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c= +github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE= +github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= +github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= +github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= +github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.12.0 h1:d7oCs6vuIMUQRVbi6jWWWEJZahLCfJpnJSVobd1/sUo= +github.com/cockroachdb/errors v1.12.0/go.mod h1:SvzfYNNBshAVbZ8wzNc/UPK3w1vf0dKDUP41ucAIf7g= +github.com/cockroachdb/fifo v0.0.0-20240616162244-4768e80dfb9a h1:f52TdbU4D5nozMAhO9TvTJ2ZMCXtN4VIAmfrrZ0JXQ4= +github.com/cockroachdb/fifo v0.0.0-20240616162244-4768e80dfb9a/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M= +github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 h1:ASDL+UJcILMqgNeV5jiqR4j+sTuvQNHdf2chuKj1M5k= +github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506/go.mod h1:Mw7HqKr2kdtu6aYGn3tPmAftiP3QPX63LdK/zcariIo= +github.com/cockroachdb/pebble v1.1.5 h1:5AAWCBWbat0uE0blr8qzufZP5tBjkRyy/jWe1QWLnvw= +github.com/cockroachdb/pebble v1.1.5/go.mod h1:17wO9el1YEigxkP/YtV8NtCivQDgoCyBg5c4VR/eOWo= +github.com/cockroachdb/redact v1.1.6 h1:zXJBwDZ84xJNlHl1rMyCojqyIxv+7YUpQiJLQ7n4314= +github.com/cockroachdb/redact v1.1.6/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/cometbft/cometbft v0.38.17 h1:FkrQNbAjiFqXydeAO81FUzriL4Bz0abYxN/eOHrQGOk= +github.com/cometbft/cometbft v0.38.17/go.mod h1:5l0SkgeLRXi6bBfQuevXjKqML1jjfJJlvI1Ulp02/o4= +github.com/cometbft/cometbft-db v0.14.1 h1:SxoamPghqICBAIcGpleHbmoPqy+crij/++eZz3DlerQ= +github.com/cometbft/cometbft-db v0.14.1/go.mod h1:KHP1YghilyGV/xjD5DP3+2hyigWx0WTp9X+0Gnx0RxQ= +github.com/consensys/bavard v0.1.27 h1:j6hKUrGAy/H+gpNrpLU3I26n1yc+VMGmd6ID5+gAhOs= +github.com/consensys/bavard v0.1.27/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= +github.com/consensys/gnark-crypto v0.16.0 h1:8Dl4eYmUWK9WmlP1Bj6je688gBRJCJbT8Mw4KoTAawo= +github.com/consensys/gnark-crypto v0.16.0/go.mod h1:Ke3j06ndtPTVvo++PhGNgvm+lgpLvzbcE2MqljY7diU= +github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= +github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk= +github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis= +github.com/cosmos/cosmos-db v1.1.3 h1:7QNT77+vkefostcKkhrzDK9uoIEryzFrU9eoMeaQOPY= +github.com/cosmos/cosmos-db v1.1.3/go.mod h1:kN+wGsnwUJZYn8Sy5Q2O0vCYA99MJllkKASbs6Unb9U= +github.com/cosmos/cosmos-proto v1.0.0-beta.5 h1:eNcayDLpip+zVLRLYafhzLvQlSmyab+RC5W7ZfmxJLA= +github.com/cosmos/cosmos-proto v1.0.0-beta.5/go.mod h1:hQGLpiIUloJBMdQMMWb/4wRApmI9hjHH05nefC0Ojec= +github.com/cosmos/cosmos-sdk v0.53.4 h1:kPF6vY68+/xi1/VebSZGpoxQqA52qkhUzqkrgeBn3Mg= +github.com/cosmos/cosmos-sdk v0.53.4/go.mod h1:7U3+WHZtI44dEOnU46+lDzBb2tFh1QlMvi8Z5JugopI= +github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= +github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= +github.com/cosmos/gogogateway v1.2.0 h1:Ae/OivNhp8DqBi/sh2A8a1D0y638GpL3tkmLQAiKxTE= +github.com/cosmos/gogogateway v1.2.0/go.mod h1:iQpLkGWxYcnCdz5iAdLcRBSw3h7NXeOkZ4GUkT+tbFI= +github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU= +github.com/cosmos/gogoproto v1.7.0 h1:79USr0oyXAbxg3rspGh/m4SWNyoz/GLaAh0QlCe2fro= +github.com/cosmos/gogoproto v1.7.0/go.mod h1:yWChEv5IUEYURQasfyBW5ffkMHR/90hiHgbNgrtp4j0= +github.com/cosmos/iavl v1.2.2 h1:qHhKW3I70w+04g5KdsdVSHRbFLgt3yY3qTMd4Xa4rC8= +github.com/cosmos/iavl v1.2.2/go.mod h1:GiM43q0pB+uG53mLxLDzimxM9l/5N9UuSY3/D0huuVw= +github.com/cosmos/ics23/go v0.11.0 h1:jk5skjT0TqX5e5QJbEnwXIS2yI2vnmLOgpQPeM5RtnU= +github.com/cosmos/ics23/go v0.11.0/go.mod h1:A8OjxPE67hHST4Icw94hOxxFEJMBG031xIGF/JHNIY0= +github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE= +github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/crate-crypto/go-eth-kzg v1.3.0 h1:05GrhASN9kDAidaFJOda6A4BEvgvuXbazXg/0E3OOdI= +github.com/crate-crypto/go-eth-kzg v1.3.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= +github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= +github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= +github.com/creachadair/atomicfile v0.3.1 h1:yQORkHjSYySh/tv5th1dkKcn02NEW5JleB84sjt+W4Q= +github.com/creachadair/atomicfile v0.3.1/go.mod h1:mwfrkRxFKwpNAflYZzytbSwxvbK6fdGRRlp0KEQc0qU= +github.com/creachadair/tomledit v0.0.24 h1:5Xjr25R2esu1rKCbQEmjZYlrhFkDspoAbAKb6QKQDhQ= +github.com/creachadair/tomledit v0.0.24/go.mod h1:9qHbShRWQzSCcn617cMzg4eab1vbLCOjOshAWSzWr8U= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U= +github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= +github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs= +github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= +github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= +github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= +github.com/desertbit/timer v1.0.1 h1:yRpYNn5Vaaj6QXecdLMPMJsW81JLiI1eokUft5nBmeo= +github.com/desertbit/timer v1.0.1/go.mod h1:htRrYeY5V/t4iu1xCJ5XsQvp4xve8QulXXctAzxqcwE= +github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs= +github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/dvsekhvalnov/jose2go v1.7.0 h1:bnQc8+GMnidJZA8zc6lLEAb4xNrIqHwO+9TzqvtQZPo= +github.com/dvsekhvalnov/jose2go v1.7.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= +github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= +github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= +github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= +github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= +github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= +github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= +github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= +github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= +github.com/ethereum/c-kzg-4844/v2 v2.1.0 h1:gQropX9YFBhl3g4HYhwE70zq3IHFRgbbNPw0Shwzf5w= +github.com/ethereum/c-kzg-4844/v2 v2.1.0/go.mod h1:TC48kOKjJKPbN7C++qIgt0TJzZ70QznYR7Ob+WXl57E= +github.com/ethereum/go-ethereum v1.15.11 h1:JK73WKeu0WC0O1eyX+mdQAVHUV+UR1a9VB/domDngBU= +github.com/ethereum/go-ethereum v1.15.11/go.mod h1:mf8YiHIb0GR4x4TipcvBUPxJLw1mFdmxzoDi11sDRoI= +github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= +github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/getsentry/sentry-go v0.32.0 h1:YKs+//QmwE3DcYtfKRH8/KyOOF/I6Qnx7qYGNHCGmCY= +github.com/getsentry/sentry-go v0.32.0/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= +github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= +github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= +github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= +github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= +github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= +github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.4.1-0.20201022092350-68b0159b7869/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= +github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= +github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I= +github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.5-0.20231225225746-43d5d4cd4e0e h1:4bw4WeyTYPp0smaXiJZCNnLrvVBqirQVreixayXezGc= +github.com/golang/snappy v0.0.5-0.20231225225746-43d5d4cd4e0e/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= +github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= +github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us= +github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= +github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= +github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-getter v1.7.8 h1:mshVHx1Fto0/MydBekWan5zUipGq7jO0novchgMmSiY= +github.com/hashicorp/go-getter v1.7.8/go.mod h1:2c6CboOEb9jG6YvmC9xdD+tyAFsrUaJPedwXDGr0TM4= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY= +github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= +github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= +github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= +github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= +github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= +github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= +github.com/herumi/bls-eth-go-binary v1.31.0 h1:9eeW3EA4epCb7FIHt2luENpAW69MvKGL5jieHlBiP+w= +github.com/herumi/bls-eth-go-binary v1.31.0/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U= +github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= +github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= +github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= +github.com/huandu/skiplist v1.2.1 h1:dTi93MgjwErA/8idWTzIw4Y1kZsMWx35fmI2c8Rij7w= +github.com/huandu/skiplist v1.2.1/go.mod h1:7v3iFjLcSAzO4fN5B8dvebvo/qsfumiLiDXMrPiHF9w= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= +github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= +github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= +github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= +github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/linxGnu/grocksdb v1.9.2 h1:O3mzvO0wuzQ9mtlHbDrShixyVjVbmuqTjFrzlf43wZ8= +github.com/linxGnu/grocksdb v1.9.2/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA= +github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mdp/qrterminal/v3 v3.2.1 h1:6+yQjiiOsSuXT5n9/m60E54vdgFsw0zhADHhHLrFet4= +github.com/mdp/qrterminal/v3 v3.2.1/go.mod h1:jOTmXvnBsMy5xqLniO0R++Jmjs2sTm9dFSuQ5kpz/SU= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= +github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= +github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/grpc-proxy v0.0.0-20181017164139-0f1106ef9c76/go.mod h1:x5OoJHDHqxHS801UIuhqGl6QdSAEJvtausosHSdazIo= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a h1:dlRvE5fWabOchtH7znfiFCcOvmIYgOeAS5ifBXBlh9Q= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= +github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= +github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= +github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw= +github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= +github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= +github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prysmaticlabs/fastssz v0.0.0-20241008181541-518c4ce73516 h1:xuVAdtz5ShYblG2sPyb4gw01DF8InbOI/kBCQjk7NiM= +github.com/prysmaticlabs/fastssz v0.0.0-20241008181541-518c4ce73516/go.mod h1:h2OlIZD/M6wFvV3YMZbW16lFgh3Rsye00G44J2cwLyU= +github.com/prysmaticlabs/go-bitfield v0.0.0-20240328144219-a1caa50c3a1e h1:ATgOe+abbzfx9kCPeXIW4fiWyDdxlwHw07j8UGhdTd4= +github.com/prysmaticlabs/go-bitfield v0.0.0-20240328144219-a1caa50c3a1e/go.mod h1:wmuf/mdK4VMD+jA9ThwcUKjg3a2XWM9cVfFYjDyY4j4= +github.com/prysmaticlabs/gohashtree v0.0.4-beta.0.20240624100937-73632381301b h1:VK7thFOnhxAZ/5aolr5Os4beiubuD08WiuiHyRqgwks= +github.com/prysmaticlabs/gohashtree v0.0.4-beta.0.20240624100937-73632381301b/go.mod h1:HRuvtXLZ4WkaB1MItToVH2e8ZwKwZPY5/Rcby+CvvLY= +github.com/prysmaticlabs/prysm/v5 v5.3.0 h1:7Lr8ndapBTZg00YE+MgujN6+yvJR6Bdfn28ZDSJ00II= +github.com/prysmaticlabs/prysm/v5 v5.3.0/go.mod h1:r1KhlduqDMIGZ1GhR5pjZ2Ko8Q89noTDYTRoPKwf1+c= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= +github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU= +github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shamaton/msgpack/v2 v2.2.0 h1:IP1m01pHwCrMa6ZccP9B3bqxEMKMSmMVAVKk54g3L/Y= +github.com/shamaton/msgpack/v2 v2.2.0/go.mod h1:6khjYnkx73f7VQU7wjcFS9DFjs+59naVWJv1TB7qdOI= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= +github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= +github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo= +github.com/supranational/blst v0.3.14/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= +github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= +github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e h1:cR8/SYRgyQCt5cNCMniB/ZScMkhI9nk8U5C7SbISXjo= +github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e/go.mod h1:Tu4lItkATkonrYuvtVjG0/rhy15qrNGNTjPdaphtZ/8= +github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI= +github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= +github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= +github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= +github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= +github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= +github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.4.0-alpha.1 h1:3yrqQzbRRPFPdOMWS/QQIVxVnzSkAZQYeWlZFv1kbj4= +go.etcd.io/bbolt v1.4.0-alpha.1/go.mod h1:S/Z/Nm3iuOnyO1W4XuFfPci51Gj6F1Hv0z8hisyYYOw= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= +go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= +golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU= +golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= +golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= +gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= +gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= +google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= +google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= +google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= +google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= +google.golang.org/api v0.222.0 h1:Aiewy7BKLCuq6cUCeOUrsAlzjXPqBkEeQ/iwGHVQa/4= +google.golang.org/api v0.222.0/go.mod h1:efZia3nXpWELrwMlN5vyQrD4GmJN1Vw0x68Et3r+a9c= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= +google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= +google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= +google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= +google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= +google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= +google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= +google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +k8s.io/apimachinery v0.30.4 h1:5QHQI2tInzr8LsT4kU/2+fSeibH1eIHswNx480cqIoY= +k8s.io/apimachinery v0.30.4/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/client-go v0.30.4 h1:eculUe+HPQoPbixfwmaSZGsKcOf7D288tH6hDAdd+wY= +k8s.io/client-go v0.30.4/go.mod h1:IBS0R/Mt0LHkNHF4E6n+SUDPG7+m2po6RZU7YHeOpzc= +k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= +modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= +modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= +modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= +modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= +modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= +modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= +modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= +modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= +modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= +modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= +nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= +nhooyr.io/websocket v1.8.11 h1:f/qXNc2/3DpoSZkHt1DQu6rj4zGC8JmkkLkWss0MgN0= +nhooyr.io/websocket v1.8.11/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY= +rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/modules/light-clients/08-wasm/internal/types/store.go b/modules/light-clients/08-wasm/internal/types/store.go new file mode 100644 index 0000000..9e9ccef --- /dev/null +++ b/modules/light-clients/08-wasm/internal/types/store.go @@ -0,0 +1,227 @@ +package types + +import ( + "bytes" + "errors" + "io" + + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" + + "cosmossdk.io/store/cachekv" + "cosmossdk.io/store/tracekv" + storetypes "cosmossdk.io/store/types" +) + +var ( + _ wasmvmtypes.KVStore = &StoreAdapter{} + _ storetypes.KVStore = &ClientRecoveryStore{} + + SubjectPrefix = []byte("subject/") + SubstitutePrefix = []byte("substitute/") +) + +// ClientRecoveryStore combines two KVStores into one. +// +// Both stores are used for reads, but only the subjectStore is used for writes. For all operations, the key +// is checked to determine which types to use and must be prefixed with either "subject/" or "substitute/" accordingly. +// If the key is not prefixed with either "subject/" or "substitute/", a default action is taken (e.g. no-op for Set/Delete). +type ClientRecoveryStore struct { + subjectStore storetypes.KVStore + substituteStore storetypes.KVStore +} + +// NewClientRecoveryStore returns a new instance of a ClientRecoveryStore +func NewClientRecoveryStore(subjectStore, substituteStore storetypes.KVStore) ClientRecoveryStore { + if subjectStore == nil { + panic(errors.New("subjectStore must not be nil")) + } + if substituteStore == nil { + panic(errors.New("substituteStore must not be nil")) + } + + return ClientRecoveryStore{ + subjectStore: subjectStore, + substituteStore: substituteStore, + } +} + +// Get implements the storetypes.KVStore interface. It allows reads from both the subjectStore and substituteStore. +// +// Get will return an empty byte slice if the key is not prefixed with either "subject/" or "substitute/". +func (s ClientRecoveryStore) Get(key []byte) []byte { + prefix, key := SplitPrefix(key) + + store, found := s.GetStore(prefix) + if !found { + // return a nil byte slice as KVStore.Get() does by default + return []byte(nil) + } + + return store.Get(key) +} + +// Has implements the storetypes.KVStore interface. It allows reads from both the subjectStore and substituteStore. +// +// Note: contracts do not have access to the Has method, it is only implemented here to satisfy the storetypes.KVStore interface. +func (s ClientRecoveryStore) Has(key []byte) bool { + prefix, key := SplitPrefix(key) + + store, found := s.GetStore(prefix) + if !found { + // return false as value when types is not found + return false + } + + return store.Has(key) +} + +// Set implements the storetypes.KVStore interface. It allows writes solely to the subjectStore. +// +// Set will no-op if the key is not prefixed with "subject/". +func (s ClientRecoveryStore) Set(key, value []byte) { + prefix, key := SplitPrefix(key) + if !bytes.Equal(prefix, SubjectPrefix) { + return // no-op + } + s.subjectStore.Set(key, value) +} + +// Delete implements the storetypes.KVStore interface. It allows deletions solely to the subjectStore. +// +// Delete will no-op if the key is not prefixed with "subject/". +func (s ClientRecoveryStore) Delete(key []byte) { + prefix, key := SplitPrefix(key) + if !bytes.Equal(prefix, SubjectPrefix) { + return // no-op + } + + s.subjectStore.Delete(key) +} + +// Iterator implements the storetypes.KVStore interface. It allows iteration over both the subjectStore and substituteStore. +// +// Iterator will return a closed iterator if the start or end keys are not prefixed with either "subject/" or "substitute/". +func (s ClientRecoveryStore) Iterator(start, end []byte) storetypes.Iterator { + prefixStart, start := SplitPrefix(start) + prefixEnd, end := SplitPrefix(end) + + if !bytes.Equal(prefixStart, prefixEnd) { + return s.closedIterator() + } + + store, found := s.GetStore(prefixStart) + if !found { + return s.closedIterator() + } + + return store.Iterator(start, end) +} + +// ReverseIterator implements the storetypes.KVStore interface. It allows iteration over both the subjectStore and substituteStore. +// +// ReverseIterator will return a closed iterator if the start or end keys are not prefixed with either "subject/" or "substitute/". +func (s ClientRecoveryStore) ReverseIterator(start, end []byte) storetypes.Iterator { + prefixStart, start := SplitPrefix(start) + prefixEnd, end := SplitPrefix(end) + + if !bytes.Equal(prefixStart, prefixEnd) { + return s.closedIterator() + } + + store, found := s.GetStore(prefixStart) + if !found { + return s.closedIterator() + } + + return store.ReverseIterator(start, end) +} + +// GetStoreType implements the storetypes.KVStore interface, it is implemented solely to satisfy the interface. +func (s ClientRecoveryStore) GetStoreType() storetypes.StoreType { + return s.substituteStore.GetStoreType() +} + +// CacheWrap implements the storetypes.KVStore interface, it is implemented solely to satisfy the interface. +func (s ClientRecoveryStore) CacheWrap() storetypes.CacheWrap { + return cachekv.NewStore(s) +} + +// CacheWrapWithTrace implements the storetypes.KVStore interface, it is implemented solely to satisfy the interface. +func (s ClientRecoveryStore) CacheWrapWithTrace(w io.Writer, tc storetypes.TraceContext) storetypes.CacheWrap { + return cachekv.NewStore(tracekv.NewStore(s, w, tc)) +} + +// GetStore returns the types to be used for the given key and a boolean flag indicating if that type was found. +// If the key is prefixed with "subject/", the subjectStore is returned. If the key is prefixed with "substitute/", +// the substituteStore is returned. +// +// If the key is not prefixed with either "subject/" or "substitute/", a nil types is returned and the boolean flag is false. +func (s ClientRecoveryStore) GetStore(prefix []byte) (storetypes.KVStore, bool) { + if bytes.Equal(prefix, SubjectPrefix) { + return s.subjectStore, true + } else if bytes.Equal(prefix, SubstitutePrefix) { + return s.substituteStore, true + } + + return nil, false +} + +// closedIterator returns an iterator that is always closed, used when Iterator() or ReverseIterator() is called +// with an invalid prefix or start/end key. +func (s ClientRecoveryStore) closedIterator() storetypes.Iterator { + // Create a dummy iterator that is always closed right away. + it := s.subjectStore.Iterator([]byte{0}, []byte{1}) + it.Close() + + return it +} + +// SplitPrefix splits the key into the prefix and the key itself, if the key is prefixed with either "subject/" or "substitute/". +// If the key is not prefixed with either "subject/" or "substitute/", the prefix is nil. +func SplitPrefix(key []byte) ([]byte, []byte) { + if bytes.HasPrefix(key, SubjectPrefix) { + return SubjectPrefix, bytes.TrimPrefix(key, SubjectPrefix) + } else if bytes.HasPrefix(key, SubstitutePrefix) { + return SubstitutePrefix, bytes.TrimPrefix(key, SubstitutePrefix) + } + + return nil, key +} + +// StoreAdapter bridges the SDK types implementation to wasmvm one. It implements the wasmvmtypes.KVStore interface. +type StoreAdapter struct { + parent storetypes.KVStore +} + +// NewStoreAdapter constructor +func NewStoreAdapter(s storetypes.KVStore) *StoreAdapter { + if s == nil { + panic(errors.New("types must not be nil")) + } + return &StoreAdapter{parent: s} +} + +// Get implements the wasmvmtypes.KVStore interface. +func (s StoreAdapter) Get(key []byte) []byte { + return s.parent.Get(key) +} + +// Set implements the wasmvmtypes.KVStore interface. +func (s StoreAdapter) Set(key, value []byte) { + s.parent.Set(key, value) +} + +// Delete implements the wasmvmtypes.KVStore interface. +func (s StoreAdapter) Delete(key []byte) { + s.parent.Delete(key) +} + +// Iterator implements the wasmvmtypes.KVStore interface. +func (s StoreAdapter) Iterator(start, end []byte) wasmvmtypes.Iterator { + return s.parent.Iterator(start, end) +} + +// ReverseIterator implements the wasmvmtypes.KVStore interface. +func (s StoreAdapter) ReverseIterator(start, end []byte) wasmvmtypes.Iterator { + return s.parent.ReverseIterator(start, end) +} diff --git a/modules/light-clients/08-wasm/internal/types/store_test.go b/modules/light-clients/08-wasm/internal/types/store_test.go new file mode 100644 index 0000000..4aa3537 --- /dev/null +++ b/modules/light-clients/08-wasm/internal/types/store_test.go @@ -0,0 +1,438 @@ +package types_test + +import ( + "encoding/json" + "errors" + "testing" + + dbm "github.com/cosmos/cosmos-db" + testifysuite "github.com/stretchr/testify/suite" + + "cosmossdk.io/log" + "cosmossdk.io/store/prefix" + storetypes "cosmossdk.io/store/types" + + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + + internaltypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/internal/types" + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing/simapp" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +var invalidPrefix = []byte("invalid/") + +type TypesTestSuite struct { + testifysuite.Suite + coordinator *ibctesting.Coordinator + chainA *ibctesting.TestChain +} + +func TestWasmTestSuite(t *testing.T) { + testifysuite.Run(t, new(TypesTestSuite)) +} + +func (suite *TypesTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCustomAppCoordinator(suite.T(), 1, setupTestingApp) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) +} + +// GetSimApp returns the duplicated SimApp from within the 08-wasm directory. +// This must be used instead of chain.GetSimApp() for tests within this directory. +func GetSimApp(chain *ibctesting.TestChain) *simapp.SimApp { + app, ok := chain.App.(*simapp.SimApp) + if !ok { + panic(errors.New("chain is not a simapp.SimApp")) + } + return app +} + +// setupTestingApp provides the duplicated simapp which is specific to the 08-wasm module on chain creation. +func setupTestingApp() (ibctesting.TestingApp, map[string]json.RawMessage) { + db := dbm.NewMemDB() + app := simapp.NewUnitTestSimApp(log.NewNopLogger(), db, nil, true, simtestutil.EmptyAppOptions{}, nil) + return app, app.DefaultGenesis() +} + +// TestClientRecoveryStoreGetStore tests the GetStore method of the ClientRecoveryStore. +func (suite *TypesTestSuite) TestClientRecoveryStoreGetStore() { + subjectStore, substituteStore := suite.GetSubjectAndSubstituteStore() + + testCases := []struct { + name string + prefix []byte + expStore storetypes.KVStore + }{ + { + "success: subject store", + internaltypes.SubjectPrefix, + subjectStore, + }, + { + "success: substitute store", + internaltypes.SubstitutePrefix, + substituteStore, + }, + { + "failure: invalid prefix", + invalidPrefix, + nil, + }, + { + "failure: invalid prefix contains both subject/ and substitute/", + append(internaltypes.SubjectPrefix, internaltypes.SubstitutePrefix...), + nil, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + wrappedStore := internaltypes.NewClientRecoveryStore(subjectStore, substituteStore) + + store, found := wrappedStore.GetStore(tc.prefix) + + storeFound := tc.expStore != nil + if storeFound { + suite.Require().Equal(tc.expStore, store) + suite.Require().True(found) + } else { + suite.Require().Nil(store) + suite.Require().False(found) + } + }) + } +} + +// TestSplitPrefix tests the SplitPrefix function. +func (suite *TypesTestSuite) TestSplitPrefix() { + clientStateKey := host.ClientStateKey() + + testCases := []struct { + name string + prefix []byte + expValues [][]byte + }{ + { + "success: subject prefix", + append(internaltypes.SubjectPrefix, clientStateKey...), + [][]byte{internaltypes.SubjectPrefix, clientStateKey}, + }, + { + "success: substitute prefix", + append(internaltypes.SubstitutePrefix, clientStateKey...), + [][]byte{internaltypes.SubstitutePrefix, clientStateKey}, + }, + { + "success: nil prefix returned", + invalidPrefix, + [][]byte{nil, invalidPrefix}, + }, + { + "success: invalid prefix contains both subject/ and substitute/", + append(internaltypes.SubjectPrefix, internaltypes.SubstitutePrefix...), + [][]byte{internaltypes.SubjectPrefix, internaltypes.SubstitutePrefix}, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + keyPrefix, key := internaltypes.SplitPrefix(tc.prefix) + + suite.Require().Equal(tc.expValues[0], keyPrefix) + suite.Require().Equal(tc.expValues[1], key) + }) + } +} + +// TestClientRecoveryStoreGet tests the Get method of the ClientRecoveryStore. +func (suite *TypesTestSuite) TestClientRecoveryStoreGet() { + subjectStore, substituteStore := suite.GetSubjectAndSubstituteStore() + + testCases := []struct { + name string + prefix []byte + key []byte + expStore storetypes.KVStore + }{ + { + "success: subject store Get", + internaltypes.SubjectPrefix, + host.ClientStateKey(), + subjectStore, + }, + { + "success: substitute store Get", + internaltypes.SubstitutePrefix, + host.ClientStateKey(), + substituteStore, + }, + { + "failure: key not prefixed with subject/ or substitute/", + invalidPrefix, + host.ClientStateKey(), + nil, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + wrappedStore := internaltypes.NewClientRecoveryStore(subjectStore, substituteStore) + + prefixedKey := tc.prefix + prefixedKey = append(prefixedKey, tc.key...) + + storeFound := tc.expStore != nil + if storeFound { + expValue := tc.expStore.Get(tc.key) + + suite.Require().Equal(expValue, wrappedStore.Get(prefixedKey)) + } else { + // expected value when types is not found is an empty byte slice + suite.Require().Equal([]byte(nil), wrappedStore.Get(prefixedKey)) + } + }) + } +} + +// TestClientRecoveryStoreSet tests the Set method of the ClientRecoveryStore. +func (suite *TypesTestSuite) TestClientRecoveryStoreSet() { + testCases := []struct { + name string + prefix []byte + key []byte + expSet bool + }{ + { + "success: subject store Set", + internaltypes.SubjectPrefix, + host.ClientStateKey(), + true, + }, + { + "failure: cannot Set on substitute store", + internaltypes.SubstitutePrefix, + host.ClientStateKey(), + false, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + subjectStore, substituteStore := suite.GetSubjectAndSubstituteStore() + wrappedStore := internaltypes.NewClientRecoveryStore(subjectStore, substituteStore) + + prefixedKey := tc.prefix + prefixedKey = append(prefixedKey, tc.key...) + + wrappedStore.Set(prefixedKey, wasmtesting.MockClientStateBz) + + if tc.expSet { + store, found := wrappedStore.GetStore(tc.prefix) + suite.Require().True(found) + suite.Require().Equal(subjectStore, store) + + value := store.Get(tc.key) + + suite.Require().Equal(wasmtesting.MockClientStateBz, value) + } else { + // Assert that no writes happened to subject or substitute types + suite.Require().NotEqual(wasmtesting.MockClientStateBz, subjectStore.Get(tc.key)) + suite.Require().NotEqual(wasmtesting.MockClientStateBz, substituteStore.Get(tc.key)) + } + }) + } +} + +// TestClientRecoveryStoreDelete tests the Delete method of the ClientRecoveryStore. +func (suite *TypesTestSuite) TestClientRecoveryStoreDelete() { + var ( + mockStoreKey = []byte("mock-key") + mockStoreValue = []byte("mock-value") + ) + + testCases := []struct { + name string + prefix []byte + key []byte + expDelete bool + }{ + { + "success: subject store Delete", + internaltypes.SubjectPrefix, + mockStoreKey, + true, + }, + { + "failure: cannot Delete on substitute store", + internaltypes.SubstitutePrefix, + mockStoreKey, + false, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + subjectStore, substituteStore := suite.GetSubjectAndSubstituteStore() + + // Set values under the mock key: + subjectStore.Set(mockStoreKey, mockStoreValue) + substituteStore.Set(mockStoreKey, mockStoreValue) + + wrappedStore := internaltypes.NewClientRecoveryStore(subjectStore, substituteStore) + + prefixedKey := tc.prefix + prefixedKey = append(prefixedKey, tc.key...) + + wrappedStore.Delete(prefixedKey) + + if tc.expDelete { + store, found := wrappedStore.GetStore(tc.prefix) + suite.Require().True(found) + suite.Require().Equal(subjectStore, store) + + suite.Require().False(store.Has(tc.key)) + } else { + // Assert that no deletions happened to subject or substitute types + suite.Require().True(subjectStore.Has(tc.key)) + suite.Require().True(substituteStore.Has(tc.key)) + } + }) + } +} + +// TestClientRecoveryStoreIterators tests the Iterator/ReverseIterator methods of the ClientRecoveryStore. +func (suite *TypesTestSuite) TestClientRecoveryStoreIterators() { + subjectStore, substituteStore := suite.GetSubjectAndSubstituteStore() + + testCases := []struct { + name string + prefixStart []byte + prefixEnd []byte + start []byte + end []byte + expValid bool + }{ + { + "success: subject store Iterate", + internaltypes.SubjectPrefix, + internaltypes.SubjectPrefix, + []byte("start"), + []byte("end"), + true, + }, + { + "success: substitute store Iterate", + internaltypes.SubstitutePrefix, + internaltypes.SubstitutePrefix, + []byte("start"), + []byte("end"), + true, + }, + { + "failure: key not prefixed", + invalidPrefix, + invalidPrefix, + []byte("start"), + []byte("end"), + false, + }, + { + "failure: start and end keys not prefixed with same prefix", + internaltypes.SubjectPrefix, + internaltypes.SubstitutePrefix, + []byte("start"), + []byte("end"), + false, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + wrappedStore := internaltypes.NewClientRecoveryStore(subjectStore, substituteStore) + + prefixedKeyStart := tc.prefixStart + prefixedKeyStart = append(prefixedKeyStart, tc.start...) + prefixedKeyEnd := tc.prefixEnd + prefixedKeyEnd = append(prefixedKeyEnd, tc.end...) + + if tc.expValid { + suite.Require().NotNil(wrappedStore.Iterator(prefixedKeyStart, prefixedKeyEnd)) + suite.Require().NotNil(wrappedStore.ReverseIterator(prefixedKeyStart, prefixedKeyEnd)) + } else { + // Iterator returned should be Closed, calling `Valid` should return false + suite.Require().False(wrappedStore.Iterator(prefixedKeyStart, prefixedKeyEnd).Valid()) + suite.Require().False(wrappedStore.ReverseIterator(prefixedKeyStart, prefixedKeyEnd).Valid()) + } + }) + } +} + +func (suite *TypesTestSuite) TestNewClientRecoveryStore() { + subjectStore, substituteStore := suite.GetSubjectAndSubstituteStore() + + testCases := []struct { + name string + malleate func() + expPanic bool + }{ + { + "success", + func() {}, + false, + }, + { + "failure: subject store is nil", + func() { + subjectStore = nil + }, + true, + }, + { + "failure: substitute store is nil", + func() { + substituteStore = nil + }, + true, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + tc.malleate() + + if !tc.expPanic { + suite.Require().NotPanics(func() { + internaltypes.NewClientRecoveryStore(subjectStore, substituteStore) + }) + } else { + suite.Require().Panics(func() { + internaltypes.NewClientRecoveryStore(subjectStore, substituteStore) + }) + } + }) + } +} + +// GetSubjectAndSubstituteStore returns two KVStores for testing the migrate client wrapping types. +func (suite *TypesTestSuite) GetSubjectAndSubstituteStore() (storetypes.KVStore, storetypes.KVStore) { + suite.SetupTest() + + ctx := suite.chainA.GetContext() + storeKey := GetSimApp(suite.chainA).GetKey(ibcexported.StoreKey) + + subjectClientStore := prefix.NewStore(ctx.KVStore(storeKey), []byte(clienttypes.FormatClientIdentifier(types.Wasm, 0))) + substituteClientStore := prefix.NewStore(ctx.KVStore(storeKey), []byte(clienttypes.FormatClientIdentifier(types.Wasm, 1))) + + return subjectClientStore, substituteClientStore +} diff --git a/modules/light-clients/08-wasm/keeper/contract_keeper.go b/modules/light-clients/08-wasm/keeper/contract_keeper.go new file mode 100644 index 0000000..d9a2405 --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/contract_keeper.go @@ -0,0 +1,307 @@ +package keeper + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "errors" + + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" + + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + internaltypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/internal/types" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var ( + VMGasRegister = types.NewDefaultWasmGasRegister() + // wasmvmAPI is a wasmvm.GoAPI implementation that is passed to the wasmvm, it + // doesn't implement any functionality, directly returning an error. + wasmvmAPI = wasmvm.GoAPI{ + HumanizeAddress: humanizeAddress, + CanonicalizeAddress: canonicalizeAddress, + ValidateAddress: validateAddress, + } +) + +// instantiateContract calls vm.Instantiate with appropriate arguments. +func (k Keeper) instantiateContract(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { + sdkGasMeter := ctx.GasMeter() + multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, types.VMGasRegister) + gasLimit := VMGasRegister.RuntimeGasForContract(ctx) + + env := getEnv(ctx, clientID) + + msgInfo := wasmvmtypes.MessageInfo{ + Sender: "", + Funds: nil, + } + + ctx.GasMeter().ConsumeGas(types.VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: instantiate") + resp, gasUsed, err := k.GetVM().Instantiate(checksum, env, msgInfo, msg, internaltypes.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) + types.VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) + return resp, err +} + +// callContract calls vm.Sudo with internally constructed gas meter and environment. +func (k Keeper) callContract(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { + sdkGasMeter := ctx.GasMeter() + multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) + gasLimit := VMGasRegister.RuntimeGasForContract(ctx) + + env := getEnv(ctx, clientID) + + ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: sudo") + resp, gasUsed, err := k.GetVM().Sudo(checksum, env, msg, internaltypes.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) + VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) + return resp, err +} + +// queryContract calls vm.Query. +func (k Keeper) queryContract(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.QueryResult, error) { + sdkGasMeter := ctx.GasMeter() + multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) + gasLimit := VMGasRegister.RuntimeGasForContract(ctx) + + env := getEnv(ctx, clientID) + + ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: query") + resp, gasUsed, err := k.GetVM().Query(checksum, env, msg, internaltypes.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) + VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) + + return resp, err +} + +// migrateContract calls vm.Migrate with internally constructed gas meter and environment. +func (k Keeper) migrateContract(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, checksum types.Checksum, msg []byte) (*wasmvmtypes.ContractResult, error) { + sdkGasMeter := ctx.GasMeter() + multipliedGasMeter := types.NewMultipliedGasMeter(sdkGasMeter, VMGasRegister) + gasLimit := VMGasRegister.RuntimeGasForContract(ctx) + + env := getEnv(ctx, clientID) + + ctx.GasMeter().ConsumeGas(VMGasRegister.SetupContractCost(true, len(msg)), "Loading CosmWasm module: migrate") + resp, gasUsed, err := k.GetVM().Migrate(checksum, env, msg, internaltypes.NewStoreAdapter(clientStore), wasmvmAPI, k.newQueryHandler(ctx, clientID), multipliedGasMeter, gasLimit, types.CostJSONDeserialization) + VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) + + return resp, err +} + +// WasmInstantiate accepts a message to instantiate a wasm contract, JSON encodes it and calls instantiateContract. +func (k Keeper) WasmInstantiate(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.InstantiateMessage) error { + encodedData, err := json.Marshal(payload) + if err != nil { + return errorsmod.Wrap(err, "failed to marshal payload for wasm contract instantiation") + } + + checksum := cs.Checksum + res, err := k.instantiateContract(ctx, clientID, clientStore, checksum, encodedData) + if err != nil { + return errorsmod.Wrap(types.ErrVMError, err.Error()) + } + if res.Err != "" { + return errorsmod.Wrap(types.ErrWasmContractCallFailed, res.Err) + } + + if err = checkResponse(res.Ok); err != nil { + return errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) + } + + newClientState, err := validatePostExecutionClientState(clientStore, k.Codec()) + if err != nil { + return err + } + + // Checksum should only be able to be modified during migration. + if !bytes.Equal(checksum, newClientState.Checksum) { + return errorsmod.Wrapf(types.ErrWasmInvalidContractModification, "expected checksum %s, got %s", hex.EncodeToString(checksum), hex.EncodeToString(newClientState.Checksum)) + } + + return nil +} + +// WasmSudo calls the contract with the given payload and returns the result. +// WasmSudo returns an error if: +// - the contract call returns an error +// - the response of the contract call contains non-empty messages +// - the response of the contract call contains non-empty events +// - the response of the contract call contains non-empty attributes +// - the data bytes of the response cannot be unmarshaled into the result type +func (k Keeper) WasmSudo(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.SudoMsg) ([]byte, error) { + encodedData, err := json.Marshal(payload) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to marshal payload for wasm execution") + } + + checksum := cs.Checksum + res, err := k.callContract(ctx, clientID, clientStore, checksum, encodedData) + if err != nil { + return nil, errorsmod.Wrap(types.ErrVMError, err.Error()) + } + if res.Err != "" { + return nil, errorsmod.Wrap(types.ErrWasmContractCallFailed, res.Err) + } + + if err = checkResponse(res.Ok); err != nil { + return nil, errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) + } + + newClientState, err := validatePostExecutionClientState(clientStore, k.Codec()) + if err != nil { + return nil, err + } + + // Checksum should only be able to be modified during migration. + if !bytes.Equal(checksum, newClientState.Checksum) { + return nil, errorsmod.Wrapf(types.ErrWasmInvalidContractModification, "expected checksum %s, got %s", hex.EncodeToString(checksum), hex.EncodeToString(newClientState.Checksum)) + } + + return res.Ok.Data, nil +} + +// WasmMigrate migrate calls the migrate entry point of the contract with the given payload and returns the result. +// WasmMigrate returns an error if: +// - the contract migration returns an error +func (k Keeper) WasmMigrate(ctx sdk.Context, clientStore storetypes.KVStore, cs *types.ClientState, clientID string, payload []byte) error { + res, err := k.migrateContract(ctx, clientID, clientStore, cs.Checksum, payload) + if err != nil { + return errorsmod.Wrap(types.ErrVMError, err.Error()) + } + if res.Err != "" { + return errorsmod.Wrap(types.ErrWasmContractCallFailed, res.Err) + } + + if err = checkResponse(res.Ok); err != nil { + return errorsmod.Wrapf(err, "checksum (%s)", hex.EncodeToString(cs.Checksum)) + } + + _, err = validatePostExecutionClientState(clientStore, k.cdc) + return err +} + +// WasmQuery queries the contract with the given payload and returns the result. +// WasmQuery returns an error if: +// - the contract query returns an error +// - the data bytes of the response cannot be unmarshal into the result type +func (k Keeper) WasmQuery(ctx sdk.Context, clientID string, clientStore storetypes.KVStore, cs *types.ClientState, payload types.QueryMsg) ([]byte, error) { + encodedData, err := json.Marshal(payload) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to marshal payload for wasm query") + } + + res, err := k.queryContract(ctx, clientID, clientStore, cs.Checksum, encodedData) + if err != nil { + return nil, errorsmod.Wrap(types.ErrVMError, err.Error()) + } + if res.Err != "" { + return nil, errorsmod.Wrap(types.ErrWasmContractCallFailed, res.Err) + } + + return res.Ok, nil +} + +// validatePostExecutionClientState validates that the contract has not many any invalid modifications +// to the client state during execution. It ensures that +// - the client state is still present +// - the client state can be unmarshaled successfully. +// - the client state is of type *ClientState +func validatePostExecutionClientState(clientStore storetypes.KVStore, cdc codec.BinaryCodec) (*types.ClientState, error) { + key := host.ClientStateKey() + _, ok := clientStore.(internaltypes.ClientRecoveryStore) + if ok { + key = append(internaltypes.SubjectPrefix, key...) + } + + bz := clientStore.Get(key) + if len(bz) == 0 { + return nil, errorsmod.Wrap(types.ErrWasmInvalidContractModification, clienttypes.ErrClientNotFound.Error()) + } + + clientState, err := unmarshalClientState(cdc, bz) + if err != nil { + return nil, errorsmod.Wrap(types.ErrWasmInvalidContractModification, err.Error()) + } + + cs, ok := clientState.(*types.ClientState) + if !ok { + return nil, errorsmod.Wrapf(types.ErrWasmInvalidContractModification, "expected client state type %T, got %T", (*types.ClientState)(nil), clientState) + } + + return cs, nil +} + +// unmarshalClientState unmarshals the client state from the given bytes. +func unmarshalClientState(cdc codec.BinaryCodec, bz []byte) (exported.ClientState, error) { + var clientState exported.ClientState + if err := cdc.UnmarshalInterface(bz, &clientState); err != nil { + return nil, err + } + + return clientState, nil +} + +// getEnv returns the state of the blockchain environment the contract is running on +func getEnv(ctx sdk.Context, contractAddr string) wasmvmtypes.Env { + chainID := ctx.BlockHeader().ChainID + height := ctx.BlockHeader().Height + + // safety checks before casting below + if height < 0 { + panic(errors.New("block height must never be negative")) + } + nsec := ctx.BlockTime().UnixNano() + if nsec < 0 { + panic(errors.New("block (unix) time must never be negative ")) + } + + env := wasmvmtypes.Env{ + Block: wasmvmtypes.BlockInfo{ + Height: uint64(height), + Time: wasmvmtypes.Uint64(nsec), + ChainID: chainID, + }, + Contract: wasmvmtypes.ContractInfo{ + Address: contractAddr, + }, + } + + return env +} + +func humanizeAddress(canon []byte) (string, uint64, error) { + return "", 0, errors.New("humanizeAddress not implemented") +} + +func canonicalizeAddress(human string) ([]byte, uint64, error) { + return nil, 0, errors.New("canonicalizeAddress not implemented") +} + +func validateAddress(human string) (uint64, error) { + return 0, errors.New("validateAddress not implemented") +} + +// checkResponse returns an error if the response from a sudo, instantiate or migrate call +// to the Wasm VM contains messages, events or attributes. +func checkResponse(response *wasmvmtypes.Response) error { + // Only allow Data to flow back to us. SubMessages, Events and Attributes are not allowed. + if len(response.Messages) > 0 { + return types.ErrWasmSubMessagesNotAllowed + } + if len(response.Events) > 0 { + return types.ErrWasmEventsNotAllowed + } + if len(response.Attributes) > 0 { + return types.ErrWasmAttributesNotAllowed + } + + return nil +} diff --git a/modules/light-clients/08-wasm/keeper/contract_keeper_test.go b/modules/light-clients/08-wasm/keeper/contract_keeper_test.go new file mode 100644 index 0000000..205adac --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/contract_keeper_test.go @@ -0,0 +1,560 @@ +package keeper_test + +import ( + "encoding/json" + + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" + + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" +) + +func (suite *KeeperTestSuite) TestWasmInstantiate() { + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + // Ensure GoAPI is set + suite.Require().NotNil(goapi.CanonicalizeAddress) + suite.Require().NotNil(goapi.HumanizeAddress) + suite.Require().NotNil(goapi.ValidateAddress) + + var payload types.InstantiateMessage + err := json.Unmarshal(initMsg, &payload) + suite.Require().NoError(err) + + wrappedClientState, ok := clienttypes.MustUnmarshalClientState(suite.chainA.App.AppCodec(), payload.ClientState).(*ibctm.ClientState) + suite.Require().True(ok) + + clientState := types.NewClientState(payload.ClientState, payload.Checksum, wrappedClientState.LatestHeight) + clientStateBz := clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), clientState) + store.Set(host.ClientStateKey(), clientStateBz) + + consensusState := types.NewConsensusState(payload.ConsensusState) + consensusStateBz := clienttypes.MustMarshalConsensusState(suite.chainA.App.AppCodec(), consensusState) + store.Set(host.ConsensusStateKey(clientState.LatestHeight), consensusStateBz) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{}}, 0, nil + } + }, + nil, + }, + { + "failure: vm returns error", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return nil, 0, wasmtesting.ErrMockVM + } + }, + types.ErrVMError, + }, + { + "failure: contract returns error", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return &wasmvmtypes.ContractResult{Err: wasmtesting.ErrMockContract.Error()}, 0, nil + } + }, + types.ErrWasmContractCallFailed, + }, + { + "failure: contract returns non-empty messages", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + resp := wasmvmtypes.Response{Messages: []wasmvmtypes.SubMsg{{}}} + + return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmSubMessagesNotAllowed, + }, + { + "failure: contract returns non-empty events", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + resp := wasmvmtypes.Response{Events: []wasmvmtypes.Event{{}}} + + return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmEventsNotAllowed, + }, + { + "failure: contract returns non-empty attributes", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + resp := wasmvmtypes.Response{Attributes: []wasmvmtypes.EventAttribute{{}}} + + return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmAttributesNotAllowed, + }, + { + "failure: change clientstate type", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + store.Set(host.ClientStateKey(), []byte("changed client state")) + + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmInvalidContractModification, + }, + { + "failure: delete clientstate", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + store.Delete(host.ClientStateKey()) + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmInvalidContractModification, + }, + { + "failure: unmarshallable clientstate", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + store.Set(host.ClientStateKey(), []byte("invalid json")) + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmInvalidContractModification, + }, + { + "failure: change checksum", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + var payload types.InstantiateMessage + err := json.Unmarshal(initMsg, &payload) + suite.Require().NoError(err) + + // Change the checksum to something else. + wrappedClientState, ok := clienttypes.MustUnmarshalClientState(suite.chainA.App.AppCodec(), payload.ClientState).(*ibctm.ClientState) + suite.Require().True(ok) + clientState := types.NewClientState(payload.ClientState, []byte("new checksum"), wrappedClientState.LatestHeight) + store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), clientState)) + + resp, err := json.Marshal(types.UpdateStateResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmInvalidContractModification, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + checksum := suite.storeWasmCode(wasmtesting.Code) + + tc.malleate() + + initMsg := types.InstantiateMessage{ + ClientState: clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), wasmtesting.MockTendermitClientState), + ConsensusState: clienttypes.MustMarshalConsensusState(suite.chainA.App.AppCodec(), wasmtesting.MockTendermintClientConsensusState), + Checksum: checksum, + } + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), defaultWasmClientID) + wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper + err := wasmClientKeeper.WasmInstantiate(suite.chainA.GetContext(), defaultWasmClientID, clientStore, &types.ClientState{Checksum: checksum}, initMsg) + + if tc.expError == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +func (suite *KeeperTestSuite) TestWasmMigrate() { + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, goapi wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + // Ensure GoAPI is set + suite.Require().NotNil(goapi.CanonicalizeAddress) + suite.Require().NotNil(goapi.HumanizeAddress) + suite.Require().NotNil(goapi.ValidateAddress) + + resp, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, 0, nil + } + }, + nil, + }, + { + "failure: vm returns error", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return nil, 0, wasmtesting.ErrMockVM + } + }, + types.ErrVMError, + }, + { + "failure: contract returns error", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return &wasmvmtypes.ContractResult{Err: wasmtesting.ErrMockContract.Error()}, 0, nil + } + }, + types.ErrWasmContractCallFailed, + }, + { + "failure: contract returns non-empty messages", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + resp := wasmvmtypes.Response{Messages: []wasmvmtypes.SubMsg{{}}} + + return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmSubMessagesNotAllowed, + }, + { + "failure: contract returns non-empty events", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + resp := wasmvmtypes.Response{Events: []wasmvmtypes.Event{{}}} + + return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmEventsNotAllowed, + }, + { + "failure: contract returns non-empty attributes", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + resp := wasmvmtypes.Response{Attributes: []wasmvmtypes.EventAttribute{{}}} + + return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmAttributesNotAllowed, + }, + { + "failure: change clientstate type", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + store.Set(host.ClientStateKey(), []byte("changed client state")) + + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmInvalidContractModification, + }, + { + "failure: delete clientstate", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + store.Delete(host.ClientStateKey()) + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmInvalidContractModification, + }, + { + "failure: unmarshallable clientstate", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + store.Set(host.ClientStateKey(), []byte("invalid json")) + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmInvalidContractModification, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + _ = suite.storeWasmCode(wasmtesting.Code) + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + tc.malleate() + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), defaultWasmClientID) + wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper + err = wasmClientKeeper.WasmMigrate(suite.chainA.GetContext(), clientStore, &types.ClientState{}, defaultWasmClientID, []byte("{}")) + + if tc.expError == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +func (suite *KeeperTestSuite) TestWasmQuery() { + var payload types.QueryMsg + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, goapi wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + // Ensure GoAPI is set + suite.Require().NotNil(goapi.CanonicalizeAddress) + suite.Require().NotNil(goapi.HumanizeAddress) + suite.Require().NotNil(goapi.ValidateAddress) + + resp, err := json.Marshal(types.StatusResult{Status: exported.Frozen.String()}) + suite.Require().NoError(err) + + return &wasmvmtypes.QueryResult{Ok: resp}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + }, + { + "failure: vm returns error", + func() { + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + return nil, wasmtesting.DefaultGasUsed, wasmtesting.ErrMockVM + }) + }, + types.ErrVMError, + }, + { + "failure: contract returns error", + func() { + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + return &wasmvmtypes.QueryResult{Err: wasmtesting.ErrMockContract.Error()}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmContractCallFailed, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + _ = suite.storeWasmCode(wasmtesting.Code) + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + clientState := endpoint.GetClientState() + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + + wasmClientState, ok := clientState.(*types.ClientState) + suite.Require().True(ok) + + payload = types.QueryMsg{Status: &types.StatusMsg{}} + + tc.malleate() + + wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper + res, err := wasmClientKeeper.WasmQuery(suite.chainA.GetContext(), endpoint.ClientID, clientStore, wasmClientState, payload) + + if tc.expError == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +func (suite *KeeperTestSuite) TestWasmSudo() { + var payload types.SudoMsg + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, goapi wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + // Ensure GoAPI is set + suite.Require().NotNil(goapi.CanonicalizeAddress) + suite.Require().NotNil(goapi.HumanizeAddress) + suite.Require().NotNil(goapi.ValidateAddress) + + resp, err := json.Marshal(types.UpdateStateResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + }, + { + "failure: vm returns error", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return nil, wasmtesting.DefaultGasUsed, wasmtesting.ErrMockVM + }) + }, + types.ErrVMError, + }, + { + "failure: contract returns error", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return &wasmvmtypes.ContractResult{Err: wasmtesting.ErrMockContract.Error()}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmContractCallFailed, + }, + { + "failure: contract returns non-empty messages", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + resp := wasmvmtypes.Response{Messages: []wasmvmtypes.SubMsg{{}}} + + return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmSubMessagesNotAllowed, + }, + { + "failure: contract returns non-empty events", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + resp := wasmvmtypes.Response{Events: []wasmvmtypes.Event{{}}} + + return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmEventsNotAllowed, + }, + { + "failure: contract returns non-empty attributes", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + resp := wasmvmtypes.Response{Attributes: []wasmvmtypes.EventAttribute{{}}} + + return &wasmvmtypes.ContractResult{Ok: &resp}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmAttributesNotAllowed, + }, + { + "failure: unmarshallable clientstate bytes", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + store.Set(host.ClientStateKey(), []byte("invalid json")) + + resp, err := json.Marshal(types.UpdateStateResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmInvalidContractModification, + }, + { + "failure: delete clientstate", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + store.Delete(host.ClientStateKey()) + + resp, err := json.Marshal(types.UpdateStateResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmInvalidContractModification, + }, + { + "failure: change checksum", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + clientState := suite.chainA.GetClientState(defaultWasmClientID) + clientState.(*types.ClientState).Checksum = []byte("new checksum") + store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), clientState)) + + resp, err := json.Marshal(types.UpdateStateResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmInvalidContractModification, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + _ = suite.storeWasmCode(wasmtesting.Code) + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + clientState := endpoint.GetClientState() + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + + wasmClientState, ok := clientState.(*types.ClientState) + suite.Require().True(ok) + + payload = types.SudoMsg{UpdateState: &types.UpdateStateMsg{}} + + tc.malleate() + + wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper + res, err := wasmClientKeeper.WasmSudo(suite.chainA.GetContext(), endpoint.ClientID, clientStore, wasmClientState, payload) + + if tc.expError == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} diff --git a/modules/light-clients/08-wasm/keeper/events.go b/modules/light-clients/08-wasm/keeper/events.go new file mode 100644 index 0000000..846b28f --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/events.go @@ -0,0 +1,39 @@ +package keeper + +import ( + "encoding/hex" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" +) + +// emitStoreWasmCodeEvent emits a store wasm code event +func emitStoreWasmCodeEvent(ctx sdk.Context, checksum types.Checksum) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeStoreWasmCode, + sdk.NewAttribute(types.AttributeKeyWasmChecksum, hex.EncodeToString(checksum)), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} + +// emitMigrateContractEvent emits a migrate contract event +func emitMigrateContractEvent(ctx sdk.Context, clientID string, checksum, newChecksum types.Checksum) { + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.EventTypeMigrateContract, + sdk.NewAttribute(types.AttributeKeyClientID, clientID), + sdk.NewAttribute(types.AttributeKeyWasmChecksum, hex.EncodeToString(checksum)), + sdk.NewAttribute(types.AttributeKeyNewChecksum, hex.EncodeToString(newChecksum)), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }) +} diff --git a/modules/light-clients/08-wasm/keeper/export_test.go b/modules/light-clients/08-wasm/keeper/export_test.go new file mode 100644 index 0000000..b872b02 --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/export_test.go @@ -0,0 +1,18 @@ +package keeper + +import sdk "github.com/cosmos/cosmos-sdk/types" + +// MigrateContractCode is a wrapper around k.migrateContractCode to allow the method to be directly called in tests. +func (k Keeper) MigrateContractCode(ctx sdk.Context, clientID string, newChecksum, migrateMsg []byte) error { + return k.migrateContractCode(ctx, clientID, newChecksum, migrateMsg) +} + +// GetQueryPlugins is a wrapper around k.getQueryPlugins to allow the method to be directly called in tests. +func (k Keeper) GetQueryPlugins() QueryPlugins { + return k.getQueryPlugins() +} + +// SetQueryPlugins is a wrapper around k.setQueryPlugins to allow the method to be directly called in tests. +func (k *Keeper) SetQueryPlugins(plugins QueryPlugins) { + k.setQueryPlugins(plugins) +} diff --git a/modules/light-clients/08-wasm/keeper/genesis.go b/modules/light-clients/08-wasm/keeper/genesis.go new file mode 100644 index 0000000..3898a3d --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/genesis.go @@ -0,0 +1,49 @@ +package keeper + +import ( + wasmvm "github.com/CosmWasm/wasmvm/v2" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" +) + +// InitGenesis initializes the 08-wasm module's state from a provided genesis +// state. +func (k Keeper) InitGenesis(ctx sdk.Context, gs types.GenesisState) error { + storeFn := func(code wasmvm.WasmCode, _ uint64) (wasmvm.Checksum, uint64, error) { + checksum, err := k.GetVM().StoreCodeUnchecked(code) + return checksum, 0, err + } + + for _, contract := range gs.Contracts { + _, err := k.storeWasmCode(ctx, contract.CodeBytes, storeFn) + if err != nil { + return err + } + } + return nil +} + +// ExportGenesis returns the 08-wasm module's exported genesis. This includes the code +// for all contracts previously stored. +func (k Keeper) ExportGenesis(ctx sdk.Context) types.GenesisState { + checksums, err := k.GetAllChecksums(ctx) + if err != nil { + panic(err) + } + + // Grab code from wasmVM and add to genesis state. + var genesisState types.GenesisState + for _, checksum := range checksums { + code, err := k.GetVM().GetCode(checksum) + if err != nil { + panic(err) + } + genesisState.Contracts = append(genesisState.Contracts, types.Contract{ + CodeBytes: code, + }) + } + + return genesisState +} diff --git a/modules/light-clients/08-wasm/keeper/genesis_test.go b/modules/light-clients/08-wasm/keeper/genesis_test.go new file mode 100644 index 0000000..fea3871 --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/genesis_test.go @@ -0,0 +1,89 @@ +package keeper_test + +import ( + "encoding/hex" + + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" +) + +func (suite *KeeperTestSuite) TestInitGenesis() { + var ( + genesisState types.GenesisState + expChecksums []string + ) + + testCases := []struct { + name string + malleate func() + }{ + { + "success", + func() { + checksum := "b3a49b2914f5e6a673215e74325c1d153bb6776e079774e52c5b7e674d9ad3ab" //nolint:gosec // these are not hard-coded credentials + + genesisState = *types.NewGenesisState( + []types.Contract{ + { + CodeBytes: wasmtesting.Code, + }, + }, + ) + + expChecksums = []string{checksum} + }, + }, + { + "success with empty genesis contract", + func() { + genesisState = *types.NewGenesisState([]types.Contract{}) + expChecksums = []string{} + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + ctx := suite.chainA.GetContext() + tc.malleate() + + err := GetSimApp(suite.chainA).WasmClientKeeper.InitGenesis(ctx, genesisState) + suite.Require().NoError(err) + + var storedHashes []string + checksums, err := GetSimApp(suite.chainA).WasmClientKeeper.GetAllChecksums(suite.chainA.GetContext()) + suite.Require().NoError(err) + + for _, hash := range checksums { + storedHashes = append(storedHashes, hex.EncodeToString(hash)) + } + + suite.Require().Equal(len(expChecksums), len(storedHashes)) + suite.Require().ElementsMatch(expChecksums, storedHashes) + }) + } +} + +func (suite *KeeperTestSuite) TestExportGenesis() { + suite.SetupWasmWithMockVM() + + ctx := suite.chainA.GetContext() + + expChecksum := "b3a49b2914f5e6a673215e74325c1d153bb6776e079774e52c5b7e674d9ad3ab" //nolint:gosec // these are not hard-coded credentials + + signer := authtypes.NewModuleAddress(govtypes.ModuleName).String() + + msg := types.NewMsgStoreCode(signer, wasmtesting.Code) + res, err := GetSimApp(suite.chainA).WasmClientKeeper.StoreCode(ctx, msg) + suite.Require().NoError(err) + suite.Require().Equal(expChecksum, hex.EncodeToString(res.Checksum)) + + genesisState := GetSimApp(suite.chainA).WasmClientKeeper.ExportGenesis(ctx) + suite.Require().Len(genesisState.Contracts, 1) + suite.Require().NotEmpty(genesisState.Contracts[0].CodeBytes) +} diff --git a/modules/light-clients/08-wasm/keeper/grpc_query.go b/modules/light-clients/08-wasm/keeper/grpc_query.go new file mode 100644 index 0000000..7428819 --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/grpc_query.go @@ -0,0 +1,64 @@ +package keeper + +import ( + "context" + "encoding/hex" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "cosmossdk.io/collections" + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkquery "github.com/cosmos/cosmos-sdk/types/query" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" +) + +var _ types.QueryServer = (*Keeper)(nil) + +// Code implements the Query/Code gRPC method +func (k Keeper) Code(goCtx context.Context, req *types.QueryCodeRequest) (*types.QueryCodeResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + checksum, err := hex.DecodeString(req.Checksum) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "invalid checksum") + } + + // Only return checksums we previously stored, not arbitrary checksums that might be stored via e.g Wasmd. + if !k.HasChecksum(sdk.UnwrapSDKContext(goCtx), checksum) { + return nil, status.Error(codes.NotFound, errorsmod.Wrap(types.ErrWasmChecksumNotFound, req.Checksum).Error()) + } + + code, err := k.GetVM().GetCode(checksum) + if err != nil { + return nil, status.Error(codes.NotFound, errorsmod.Wrap(types.ErrWasmChecksumNotFound, req.Checksum).Error()) + } + + return &types.QueryCodeResponse{ + Data: code, + }, nil +} + +// Checksums implements the Query/Checksums gRPC method. It returns a list of hex encoded checksums stored. +func (k Keeper) Checksums(goCtx context.Context, req *types.QueryChecksumsRequest) (*types.QueryChecksumsResponse, error) { + checksums, pageRes, err := sdkquery.CollectionPaginate( + goCtx, + k.GetChecksums(), + req.Pagination, + func(key []byte, value collections.NoValue) (string, error) { + return hex.EncodeToString(key), nil + }) + if err != nil { + return nil, err + } + + return &types.QueryChecksumsResponse{ + Checksums: checksums, + Pagination: pageRes, + }, nil +} diff --git a/modules/light-clients/08-wasm/keeper/grpc_query_test.go b/modules/light-clients/08-wasm/keeper/grpc_query_test.go new file mode 100644 index 0000000..5ee6b2d --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/grpc_query_test.go @@ -0,0 +1,130 @@ +package keeper_test + +import ( + "encoding/hex" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + errorsmod "cosmossdk.io/errors" + + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" +) + +func (suite *KeeperTestSuite) TestQueryCode() { + var req *types.QueryCodeRequest + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() { + signer := authtypes.NewModuleAddress(govtypes.ModuleName).String() + msg := types.NewMsgStoreCode(signer, wasmtesting.Code) + + res, err := GetSimApp(suite.chainA).WasmClientKeeper.StoreCode(suite.chainA.GetContext(), msg) + suite.Require().NoError(err) + + req = &types.QueryCodeRequest{Checksum: hex.EncodeToString(res.Checksum)} + }, + nil, + }, + { + "fails with empty request", + func() { + req = &types.QueryCodeRequest{} + }, + status.Error( + codes.NotFound, + errorsmod.Wrap(types.ErrWasmChecksumNotFound, "").Error(), + ), + }, + { + "fails with non-existent checksum", + func() { + req = &types.QueryCodeRequest{Checksum: "test"} + }, + status.Error( + codes.InvalidArgument, + types.ErrInvalidChecksum.Error(), + ), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + tc.malleate() + + res, err := GetSimApp(suite.chainA).WasmClientKeeper.Code(suite.chainA.GetContext(), req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().NotEmpty(res.Data) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestQueryChecksums() { + var expChecksums []string + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success with no checksums", + func() { + expChecksums = []string{} + }, + nil, + }, + { + "success with one checksum", + func() { + signer := authtypes.NewModuleAddress(govtypes.ModuleName).String() + msg := types.NewMsgStoreCode(signer, wasmtesting.Code) + + res, err := GetSimApp(suite.chainA).WasmClientKeeper.StoreCode(suite.chainA.GetContext(), msg) + suite.Require().NoError(err) + + expChecksums = append(expChecksums, hex.EncodeToString(res.Checksum)) + }, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + tc.malleate() + + req := &types.QueryChecksumsRequest{} + res, err := GetSimApp(suite.chainA).WasmClientKeeper.Checksums(suite.chainA.GetContext(), req) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().Equal(len(expChecksums), len(res.Checksums)) + suite.Require().ElementsMatch(expChecksums, res.Checksums) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/modules/light-clients/08-wasm/keeper/keeper.go b/modules/light-clients/08-wasm/keeper/keeper.go new file mode 100644 index 0000000..701bc3d --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/keeper.go @@ -0,0 +1,246 @@ +package keeper + +import ( + "bytes" + "encoding/hex" + + wasmvm "github.com/CosmWasm/wasmvm/v2" + + "cosmossdk.io/collections" + "cosmossdk.io/core/store" + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/log" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// Keeper defines the 08-wasm keeper +type Keeper struct { + // implements gRPC QueryServer interface + types.QueryServer + + cdc codec.BinaryCodec + clientKeeper types.ClientKeeper + + vm types.WasmEngine + + checksums collections.KeySet[[]byte] + storeService store.KVStoreService + + queryPlugins QueryPlugins + + authority string +} + +// Codec returns the 08-wasm module's codec. +func (k Keeper) Codec() codec.BinaryCodec { + return k.cdc +} + +// GetAuthority returns the 08-wasm module's authority. +func (k Keeper) GetAuthority() string { + return k.authority +} + +// Logger returns a module-specific logger. +func (Keeper) Logger(ctx sdk.Context) log.Logger { + return moduleLogger(ctx) +} + +func moduleLogger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", "x/"+exported.ModuleName+"-"+types.ModuleName) +} + +// GetVM returns the keeper's vm engine. +func (k Keeper) GetVM() types.WasmEngine { + return k.vm +} + +// GetChecksums returns the stored checksums. +func (k Keeper) GetChecksums() collections.KeySet[[]byte] { + return k.checksums +} + +// getQueryPlugins returns the set query plugins. +func (k Keeper) getQueryPlugins() QueryPlugins { + return k.queryPlugins +} + +// setQueryPlugins sets the plugins. +func (k *Keeper) setQueryPlugins(plugins QueryPlugins) { + k.queryPlugins = plugins +} + +func (k Keeper) newQueryHandler(ctx sdk.Context, callerID string) *queryHandler { + return newQueryHandler(ctx, k.getQueryPlugins(), callerID) +} + +// storeWasmCode stores the contract to the VM, pins the checksum in the VM's in memory cache and stores the checksum +// in the 08-wasm store. The checksum identifying it is returned if successful. The following checks are made to the +// contract code before storing: +// - Size bounds are checked. Contract length must not be 0 or exceed a specific size (maxWasmSize). +// - The contract must not have already been stored in store. +func (k Keeper) storeWasmCode(ctx sdk.Context, code []byte, storeFn func(code wasmvm.WasmCode, gasLimit uint64) (wasmvm.Checksum, uint64, error)) ([]byte, error) { + var err error + if types.IsGzip(code) { + ctx.GasMeter().ConsumeGas(types.VMGasRegister.UncompressCosts(len(code)), "Uncompress gzip bytecode") + code, err = types.Uncompress(code, types.MaxWasmSize) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to store contract") + } + } + + // run the code through the wasm light client validation process + if err := types.ValidateWasmCode(code); err != nil { + return nil, errorsmod.Wrap(err, "wasm bytecode validation failed") + } + + // Check to see if store already has checksum. + checksum, err := types.CreateChecksum(code) + if err != nil { + return nil, errorsmod.Wrap(err, "wasm bytecode checksum failed") + } + + if k.HasChecksum(ctx, checksum) { + return nil, types.ErrWasmCodeExists + } + + // create the code in the vm + gasLeft := types.VMGasRegister.RuntimeGasForContract(ctx) + vmChecksum, gasUsed, err := storeFn(code, gasLeft) + types.VMGasRegister.ConsumeRuntimeGas(ctx, gasUsed) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to store contract") + } + + // SANITY: We've checked our store, additional safety check to assert that the checksum returned by WasmVM equals checksum generated by us. + if !bytes.Equal(vmChecksum, checksum) { + return nil, errorsmod.Wrapf(types.ErrInvalidChecksum, "expected %s, got %s", hex.EncodeToString(checksum), hex.EncodeToString(vmChecksum)) + } + + // pin the code to the vm in-memory cache + if err := k.GetVM().Pin(vmChecksum); err != nil { + return nil, errorsmod.Wrapf(err, "failed to pin contract with checksum (%s) to vm cache", hex.EncodeToString(vmChecksum)) + } + + // store the checksum + err = k.GetChecksums().Set(ctx, checksum) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to store checksum") + } + + return checksum, nil +} + +// migrateContractCode migrates the contract for a given light client to one denoted by the given new checksum. The checksum we +// are migrating to must first be stored using storeWasmCode and must not match the checksum currently stored for this light client. +func (k Keeper) migrateContractCode(ctx sdk.Context, clientID string, newChecksum, migrateMsg []byte) error { + clientStore := k.clientKeeper.ClientStore(ctx, clientID) + wasmClientState, found := types.GetClientState(clientStore, k.cdc) + if !found { + return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) + } + oldChecksum := wasmClientState.Checksum + + if !k.HasChecksum(ctx, newChecksum) { + return types.ErrWasmChecksumNotFound + } + + if bytes.Equal(wasmClientState.Checksum, newChecksum) { + return errorsmod.Wrapf(types.ErrWasmCodeExists, "new checksum (%s) is the same as current checksum (%s)", hex.EncodeToString(newChecksum), hex.EncodeToString(wasmClientState.Checksum)) + } + + // update the checksum, this needs to be done before the contract migration + // so that wasmMigrate can call the right code. Note that this is not + // persisted to the client store. + wasmClientState.Checksum = newChecksum + + err := k.WasmMigrate(ctx, clientStore, wasmClientState, clientID, migrateMsg) + if err != nil { + return err + } + + // client state may be updated by the contract migration + wasmClientState, err = k.GetWasmClientState(ctx, clientID) + if err != nil { + // note that this also ensures that the updated client state is + // still a wasm client state + return errorsmod.Wrap(err, "failed to retrieve the updated wasm client state") + } + + // update the client state checksum before persisting it + wasmClientState.Checksum = newChecksum + + k.clientKeeper.SetClientState(ctx, clientID, wasmClientState) + + emitMigrateContractEvent(ctx, clientID, oldChecksum, newChecksum) + + return nil +} + +// GetWasmClientState returns the 08-wasm client state for the given client identifier. +func (k Keeper) GetWasmClientState(ctx sdk.Context, clientID string) (*types.ClientState, error) { + clientState, found := k.clientKeeper.GetClientState(ctx, clientID) + if !found { + return nil, errorsmod.Wrapf(clienttypes.ErrClientTypeNotFound, "clientID %s", clientID) + } + + wasmClientState, ok := clientState.(*types.ClientState) + if !ok { + return nil, errorsmod.Wrapf(clienttypes.ErrInvalidClient, "expected type %T, got %T", (*types.ClientState)(nil), wasmClientState) + } + + return wasmClientState, nil +} + +// GetAllChecksums is a helper to get all checksums from the store. +// It returns an empty slice if no checksums are found +func (k Keeper) GetAllChecksums(ctx sdk.Context) ([]types.Checksum, error) { + iterator, err := k.GetChecksums().Iterate(ctx, nil) + if err != nil { + return nil, err + } + + keys, err := iterator.Keys() + if err != nil { + return nil, err + } + + checksums := make([]types.Checksum, 0, len(keys)) + for _, key := range keys { + checksums = append(checksums, key) + } + + return checksums, nil +} + +// HasChecksum returns true if the given checksum exists in the store and +// false otherwise. +func (k Keeper) HasChecksum(ctx sdk.Context, checksum types.Checksum) bool { + found, err := k.GetChecksums().Has(ctx, checksum) + if err != nil { + return false + } + + return found +} + +// InitializePinnedCodes updates wasmvm to pin to cache all contracts marked as pinned +func (k Keeper) InitializePinnedCodes(ctx sdk.Context) error { + checksums, err := k.GetAllChecksums(ctx) + if err != nil { + return err + } + + for _, checksum := range checksums { + if err := k.GetVM().Pin(checksum); err != nil { + return err + } + } + return nil +} diff --git a/modules/light-clients/08-wasm/keeper/keeper_no_vm.go b/modules/light-clients/08-wasm/keeper/keeper_no_vm.go new file mode 100644 index 0000000..ac8945e --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/keeper_no_vm.go @@ -0,0 +1,43 @@ +//go:build !cgo || nolink_libwasmvm + +package keeper + +import ( + storetypes "cosmossdk.io/core/store" + + "github.com/cosmos/cosmos-sdk/codec" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" +) + +// NewKeeperWithVM creates a new Keeper instance with the provided Wasm VM. +// This constructor function is used when binaries are compiled with cgo disabled or the +// custom build directive: nolink_libwasmvm. +// This function is intended to panic and notify users that 08-wasm keeper functionality is not available. +func NewKeeperWithVM( + _ codec.BinaryCodec, + _ storetypes.KVStoreService, + _ types.ClientKeeper, + _ string, + _ types.WasmEngine, + _ types.QueryRouter, + _ ...Option, +) Keeper { + panic("not implemented, please build with cgo enabled or nolink_libwasmvm disabled") +} + +// NewKeeperWithConfig creates a new Keeper instance with the provided Wasm configuration. +// This constructor function is used when binaries are compiled with cgo disabled or the +// custom build directive: nolink_libwasmvm. +// This function is intended to panic and notify users that 08-wasm keeper functionality is not available. +func NewKeeperWithConfig( + _ codec.BinaryCodec, + _ storetypes.KVStoreService, + _ types.ClientKeeper, + _ string, + _ types.WasmConfig, + _ types.QueryRouter, + _ ...Option, +) Keeper { + panic("not implemented, please build with cgo enabled or nolink_libwasmvm disabled") +} diff --git a/modules/light-clients/08-wasm/keeper/keeper_test.go b/modules/light-clients/08-wasm/keeper/keeper_test.go new file mode 100644 index 0000000..aad445a --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/keeper_test.go @@ -0,0 +1,549 @@ +package keeper_test + +import ( + "encoding/json" + "errors" + "testing" + + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" + dbm "github.com/cosmos/cosmos-db" + testifysuite "github.com/stretchr/testify/suite" + + "cosmossdk.io/log" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/runtime" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/keeper" + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing/simapp" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +const ( + defaultWasmClientID = "08-wasm-0" +) + +type KeeperTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + // mockVM is a mock wasm VM that implements the WasmEngine interface + mockVM *wasmtesting.MockWasmEngine + chainA *ibctesting.TestChain +} + +// setupTestingApp provides the duplicated simapp which is specific to the 08-wasm module on chain creation. +func setupTestingApp() (ibctesting.TestingApp, map[string]json.RawMessage) { + db := dbm.NewMemDB() + app := simapp.NewUnitTestSimApp(log.NewNopLogger(), db, nil, true, simtestutil.EmptyAppOptions{}, nil) + return app, app.DefaultGenesis() +} + +// GetSimApp returns the duplicated SimApp from within the 08-wasm directory. +// This must be used instead of chain.GetSimApp() for tests within this directory. +func GetSimApp(chain *ibctesting.TestChain) *simapp.SimApp { + app, ok := chain.App.(*simapp.SimApp) + if !ok { + panic(errors.New("chain is not a simapp.SimApp")) + } + return app +} + +func (suite *KeeperTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCustomAppCoordinator(suite.T(), 1, setupTestingApp) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + + queryHelper := baseapp.NewQueryServerTestHelper(suite.chainA.GetContext(), GetSimApp(suite.chainA).InterfaceRegistry()) + types.RegisterQueryServer(queryHelper, GetSimApp(suite.chainA).WasmClientKeeper) +} + +// SetupWasmWithMockVM sets up mock cometbft chain with a mock vm. +func (suite *KeeperTestSuite) SetupWasmWithMockVM() { + suite.coordinator = ibctesting.NewCustomAppCoordinator(suite.T(), 1, suite.setupWasmWithMockVM) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) +} + +func (suite *KeeperTestSuite) setupWasmWithMockVM() (ibctesting.TestingApp, map[string]json.RawMessage) { + suite.mockVM = wasmtesting.NewMockWasmEngine() + + suite.mockVM.InstantiateFn = func(checksum wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + var payload types.InstantiateMessage + err := json.Unmarshal(initMsg, &payload) + suite.Require().NoError(err) + + wrappedClientState, ok := clienttypes.MustUnmarshalClientState(suite.chainA.App.AppCodec(), payload.ClientState).(*ibctm.ClientState) + suite.Require().True(ok) + + clientState := types.NewClientState(payload.ClientState, payload.Checksum, wrappedClientState.LatestHeight) + clientStateBz := clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), clientState) + store.Set(host.ClientStateKey(), clientStateBz) + + consensusState := types.NewConsensusState(payload.ConsensusState) + consensusStateBz := clienttypes.MustMarshalConsensusState(suite.chainA.App.AppCodec(), consensusState) + store.Set(host.ConsensusStateKey(clientState.LatestHeight), consensusStateBz) + + resp, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, 0, nil + } + + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + resp, err := json.Marshal(types.StatusResult{Status: exported.Active.String()}) + suite.Require().NoError(err) + return &wasmvmtypes.QueryResult{Ok: resp}, wasmtesting.DefaultGasUsed, nil + }) + + db := dbm.NewMemDB() + app := simapp.NewUnitTestSimApp(log.NewNopLogger(), db, nil, true, simtestutil.EmptyAppOptions{}, suite.mockVM) + + return app, app.DefaultGenesis() +} + +// storeWasmCode stores the wasm code on chain and returns the checksum. +func (suite *KeeperTestSuite) storeWasmCode(wasmCode []byte) []byte { + ctx := suite.chainA.GetContext().WithBlockGasMeter(storetypes.NewInfiniteGasMeter()) + + msg := types.NewMsgStoreCode(authtypes.NewModuleAddress(govtypes.ModuleName).String(), wasmCode) + response, err := GetSimApp(suite.chainA).WasmClientKeeper.StoreCode(ctx, msg) + suite.Require().NoError(err) + suite.Require().NotNil(response.Checksum) + return response.Checksum +} + +func (suite *KeeperTestSuite) SetupSnapshotterWithMockVM() *simapp.SimApp { + suite.mockVM = wasmtesting.NewMockWasmEngine() + + return simapp.SetupWithSnapshotter(suite.T(), suite.mockVM) +} + +func TestKeeperTestSuite(t *testing.T) { + testifysuite.Run(t, new(KeeperTestSuite)) +} + +func (suite *KeeperTestSuite) TestNewKeeper() { + testCases := []struct { + name string + instantiateFn func() + expError error + }{ + { + "success", + func() { + keeper.NewKeeperWithVM( + GetSimApp(suite.chainA).AppCodec(), + runtime.NewKVStoreService(GetSimApp(suite.chainA).GetKey(types.StoreKey)), + GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, + GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), + GetSimApp(suite.chainA).WasmClientKeeper.GetVM(), + GetSimApp(suite.chainA).GRPCQueryRouter(), + ) + }, + nil, + }, + { + "failure: empty authority", + func() { + keeper.NewKeeperWithVM( + GetSimApp(suite.chainA).AppCodec(), + runtime.NewKVStoreService(GetSimApp(suite.chainA).GetKey(types.StoreKey)), + GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, + "", // authority + GetSimApp(suite.chainA).WasmClientKeeper.GetVM(), + GetSimApp(suite.chainA).GRPCQueryRouter(), + ) + }, + errors.New("authority must be non-empty"), + }, + { + "failure: nil client keeper", + func() { + keeper.NewKeeperWithVM( + GetSimApp(suite.chainA).AppCodec(), + runtime.NewKVStoreService(GetSimApp(suite.chainA).GetKey(types.StoreKey)), + nil, // client keeper, + GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), + GetSimApp(suite.chainA).WasmClientKeeper.GetVM(), + GetSimApp(suite.chainA).GRPCQueryRouter(), + ) + }, + errors.New("client keeper must not be nil"), + }, + { + "failure: nil wasm VM", + func() { + keeper.NewKeeperWithVM( + GetSimApp(suite.chainA).AppCodec(), + runtime.NewKVStoreService(GetSimApp(suite.chainA).GetKey(types.StoreKey)), + GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, + GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), + nil, + GetSimApp(suite.chainA).GRPCQueryRouter(), + ) + }, + errors.New("wasm VM must not be nil"), + }, + { + "failure: nil store service", + func() { + keeper.NewKeeperWithVM( + GetSimApp(suite.chainA).AppCodec(), + nil, + GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, + GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), + GetSimApp(suite.chainA).WasmClientKeeper.GetVM(), + GetSimApp(suite.chainA).GRPCQueryRouter(), + ) + }, + errors.New("store service must not be nil"), + }, + { + "failure: nil query router", + func() { + keeper.NewKeeperWithVM( + GetSimApp(suite.chainA).AppCodec(), + runtime.NewKVStoreService(GetSimApp(suite.chainA).GetKey(types.StoreKey)), + GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, + GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), + GetSimApp(suite.chainA).WasmClientKeeper.GetVM(), + nil, + ) + }, + errors.New("query router must not be nil"), + }, + } + + for _, tc := range testCases { + tc := tc + suite.SetupTest() + + suite.Run(tc.name, func() { + if tc.expError == nil { + suite.Require().NotPanics( + tc.instantiateFn, + ) + } else { + suite.Require().PanicsWithError(tc.expError.Error(), func() { + tc.instantiateFn() + }) + } + }) + } +} + +func (suite *KeeperTestSuite) TestInitializedPinnedCodes() { + var capturedChecksums []wasmvm.Checksum + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + suite.mockVM.PinFn = func(checksum wasmvm.Checksum) error { + capturedChecksums = append(capturedChecksums, checksum) + return nil + } + }, + nil, + }, + { + "failure: pin error", + func() { + suite.mockVM.PinFn = func(checksum wasmvm.Checksum) error { + return wasmtesting.ErrMockVM + } + }, + wasmtesting.ErrMockVM, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + ctx := suite.chainA.GetContext() + wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper + + contracts := [][]byte{wasmtesting.Code, wasmtesting.CreateMockContract([]byte("gzipped-contract"))} + checksumIDs := make([]types.Checksum, len(contracts)) + signer := authtypes.NewModuleAddress(govtypes.ModuleName).String() + + // store contract on chain + for i, contract := range contracts { + msg := types.NewMsgStoreCode(signer, contract) + + res, err := wasmClientKeeper.StoreCode(ctx, msg) + suite.Require().NoError(err) + + checksumIDs[i] = res.Checksum + } + + // malleate after storing contracts + tc.malleate() + + err := wasmClientKeeper.InitializePinnedCodes(ctx) + + if tc.expError == nil { + suite.Require().NoError(err) + suite.ElementsMatch(checksumIDs, capturedChecksums) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +func (suite *KeeperTestSuite) TestMigrateContract() { + var ( + oldHash []byte + newHash []byte + payload []byte + expClientState *types.ClientState + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success: update client state", + func() { + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + expClientState = types.NewClientState([]byte{1}, newHash, clienttypes.NewHeight(2000, 2)) + store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), expClientState)) + + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil + } + }, + nil, + }, + { + "failure: new and old checksum are the same", + func() { + newHash = oldHash + // this should not be called + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + panic("unreachable") + } + }, + types.ErrWasmCodeExists, + }, + { + "failure: checksum not found", + func() { + err := GetSimApp(suite.chainA).WasmClientKeeper.GetChecksums().Remove(suite.chainA.GetContext(), newHash) + suite.Require().NoError(err) + }, + types.ErrWasmChecksumNotFound, + }, + { + "failure: vm returns error", + func() { + err := GetSimApp(suite.chainA).WasmClientKeeper.GetChecksums().Set(suite.chainA.GetContext(), newHash) + suite.Require().NoError(err) + + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return nil, wasmtesting.DefaultGasUsed, wasmtesting.ErrMockVM + } + }, + types.ErrVMError, + }, + { + "failure: contract returns error", + func() { + err := GetSimApp(suite.chainA).WasmClientKeeper.GetChecksums().Set(suite.chainA.GetContext(), newHash) + suite.Require().NoError(err) + + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return &wasmvmtypes.ContractResult{Err: wasmtesting.ErrMockContract.Error()}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmContractCallFailed, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + suite.storeWasmCode(wasmtesting.Code) + + var err error + oldHash, err = types.CreateChecksum(wasmtesting.Code) + suite.Require().NoError(err) + newHash, err = types.CreateChecksum(wasmtesting.CreateMockContract([]byte{1, 2, 3})) + suite.Require().NoError(err) + + wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper + err = wasmClientKeeper.GetChecksums().Set(suite.chainA.GetContext(), newHash) + suite.Require().NoError(err) + + endpointA := wasmtesting.NewWasmEndpoint(suite.chainA) + err = endpointA.CreateClient() + suite.Require().NoError(err) + + clientState, ok := endpointA.GetClientState().(*types.ClientState) + suite.Require().True(ok) + + expClientState = clientState + + tc.malleate() + + err = wasmClientKeeper.MigrateContractCode(suite.chainA.GetContext(), endpointA.ClientID, newHash, payload) + + // updated client state + clientState, ok = endpointA.GetClientState().(*types.ClientState) + suite.Require().True(ok) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().Equal(expClientState, clientState) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *KeeperTestSuite) TestGetChecksums() { + testCases := []struct { + name string + malleate func() + expResult func(checksums []types.Checksum) + }{ + { + "success: no contract stored.", + func() {}, + func(checksums []types.Checksum) { + suite.Require().Len(checksums, 0) + }, + }, + { + "success: default mock vm contract stored.", + func() { + suite.SetupWasmWithMockVM() + suite.storeWasmCode(wasmtesting.Code) + }, + func(checksums []types.Checksum) { + suite.Require().Len(checksums, 1) + expectedChecksum, err := types.CreateChecksum(wasmtesting.Code) + suite.Require().NoError(err) + suite.Require().Equal(expectedChecksum, checksums[0]) + }, + }, + { + "success: non-empty checksums", + func() { + suite.SetupWasmWithMockVM() + suite.storeWasmCode(wasmtesting.Code) + + err := GetSimApp(suite.chainA).WasmClientKeeper.GetChecksums().Set(suite.chainA.GetContext(), types.Checksum("checksum")) + suite.Require().NoError(err) + }, + func(checksums []types.Checksum) { + suite.Require().Len(checksums, 2) + suite.Require().Contains(checksums, types.Checksum("checksum")) + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + tc.malleate() + + checksums, err := GetSimApp(suite.chainA).WasmClientKeeper.GetAllChecksums(suite.chainA.GetContext()) + suite.Require().NoError(err) + tc.expResult(checksums) + }) + } +} + +func (suite *KeeperTestSuite) TestAddChecksum() { + suite.SetupWasmWithMockVM() + suite.storeWasmCode(wasmtesting.Code) + + wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper + + checksums, err := wasmClientKeeper.GetAllChecksums(suite.chainA.GetContext()) + suite.Require().NoError(err) + // default mock vm contract is stored + suite.Require().Len(checksums, 1) + + checksum1 := types.Checksum("checksum1") + checksum2 := types.Checksum("checksum2") + err = wasmClientKeeper.GetChecksums().Set(suite.chainA.GetContext(), checksum1) + suite.Require().NoError(err) + err = wasmClientKeeper.GetChecksums().Set(suite.chainA.GetContext(), checksum2) + suite.Require().NoError(err) + + // Test adding the same checksum twice + err = wasmClientKeeper.GetChecksums().Set(suite.chainA.GetContext(), checksum1) + suite.Require().NoError(err) + + checksums, err = wasmClientKeeper.GetAllChecksums(suite.chainA.GetContext()) + suite.Require().NoError(err) + suite.Require().Len(checksums, 3) + suite.Require().Contains(checksums, checksum1) + suite.Require().Contains(checksums, checksum2) +} + +func (suite *KeeperTestSuite) TestHasChecksum() { + var checksum types.Checksum + + testCases := []struct { + name string + malleate func() + exprResult bool + }{ + { + "success: checksum exists", + func() { + checksum = types.Checksum("checksum") + err := GetSimApp(suite.chainA).WasmClientKeeper.GetChecksums().Set(suite.chainA.GetContext(), checksum) + suite.Require().NoError(err) + }, + true, + }, + { + "success: checksum does not exist", + func() { + checksum = types.Checksum("non-existent-checksum") + }, + false, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + tc.malleate() + + result := GetSimApp(suite.chainA).WasmClientKeeper.HasChecksum(suite.chainA.GetContext(), checksum) + suite.Require().Equal(tc.exprResult, result) + }) + } +} diff --git a/modules/light-clients/08-wasm/keeper/keeper_vm.go b/modules/light-clients/08-wasm/keeper/keeper_vm.go new file mode 100644 index 0000000..ebe498b --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/keeper_vm.go @@ -0,0 +1,97 @@ +//go:build cgo && !nolink_libwasmvm + +package keeper + +import ( + "errors" + "fmt" + "strings" + + wasmvm "github.com/CosmWasm/wasmvm/v2" + + "cosmossdk.io/collections" + "cosmossdk.io/core/store" + + "github.com/cosmos/cosmos-sdk/codec" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" +) + +// NewKeeperWithVM creates a new Keeper instance with the provided Wasm VM. +// This constructor function is meant to be used when the chain uses x/wasm +// and the same Wasm VM instance should be shared with it. +func NewKeeperWithVM( + cdc codec.BinaryCodec, + storeService store.KVStoreService, + clientKeeper types.ClientKeeper, + authority string, + vm types.WasmEngine, + queryRouter types.QueryRouter, + opts ...Option, +) Keeper { + if clientKeeper == nil { + panic(errors.New("client keeper must not be nil")) + } + + if queryRouter == nil { + panic(errors.New("query router must not be nil")) + } + + if vm == nil { + panic(errors.New("wasm VM must not be nil")) + } + + if storeService == nil { + panic(errors.New("store service must not be nil")) + } + + if strings.TrimSpace(authority) == "" { + panic(errors.New("authority must be non-empty")) + } + + sb := collections.NewSchemaBuilder(storeService) + + keeper := &Keeper{ + cdc: cdc, + vm: vm, + checksums: collections.NewKeySet(sb, types.ChecksumsKey, "checksums", collections.BytesKey), + storeService: storeService, + clientKeeper: clientKeeper, + authority: authority, + } + + _, err := sb.Build() + if err != nil { + panic(err) + } + + // set query plugins to ensure there is a non-nil query plugin + // regardless of what options the user provides + keeper.setQueryPlugins(NewDefaultQueryPlugins(queryRouter)) + + for _, opt := range opts { + opt.apply(keeper) + } + + return *keeper +} + +// NewKeeperWithConfig creates a new Keeper instance with the provided Wasm configuration. +// This constructor function is meant to be used when the chain does not use x/wasm +// and a Wasm VM needs to be instantiated using the provided parameters. +func NewKeeperWithConfig( + cdc codec.BinaryCodec, + storeService store.KVStoreService, + clientKeeper types.ClientKeeper, + authority string, + wasmConfig types.WasmConfig, + queryRouter types.QueryRouter, + opts ...Option, +) Keeper { + vm, err := wasmvm.NewVM(wasmConfig.DataDir, wasmConfig.SupportedCapabilities, types.ContractMemoryLimit, wasmConfig.ContractDebugMode, types.MemoryCacheSize) + if err != nil { + panic(fmt.Errorf("failed to instantiate new Wasm VM instance: %v", err)) + } + + return NewKeeperWithVM(cdc, storeService, clientKeeper, authority, vm, queryRouter, opts...) +} diff --git a/modules/light-clients/08-wasm/keeper/migrations.go b/modules/light-clients/08-wasm/keeper/migrations.go new file mode 100644 index 0000000..c9d248b --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/migrations.go @@ -0,0 +1,72 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" +) + +// Migrator is a struct for handling in-place store migrations. +type Migrator struct { + keeper Keeper +} + +// NewMigrator returns a new Migrator. +func NewMigrator(keeper Keeper) Migrator { + return Migrator{ + keeper: keeper, + } +} + +// MigrateChecksums migrates the wasm store from using a single key to +// store a list of checksums to using a collections.KeySet to store the checksums. +// +// It grabs the checksums stored previously under the old key and stores +// them in the global KeySet collection. It then deletes the old key and +// the checksums stored under it. +func (m Migrator) MigrateChecksums(ctx sdk.Context) error { + checksums, err := m.getStoredChecksums(ctx) + if err != nil { + return err + } + + for _, hash := range checksums { + if err := m.keeper.GetChecksums().Set(ctx, hash); err != nil { + return err + } + } + + // delete the previously stored checksums + if err := m.deleteChecksums(ctx); err != nil { + return err + } + + m.keeper.Logger(ctx).Info("successfully migrated Checksums to collections") + return nil +} + +// getStoredChecksums returns the checksums stored under the KeyChecksums key. +func (m Migrator) getStoredChecksums(ctx sdk.Context) ([][]byte, error) { + store := m.keeper.storeService.OpenKVStore(ctx) + + bz, err := store.Get([]byte(types.KeyChecksums)) + if err != nil { + return [][]byte{}, err + } + + var hashes types.Checksums + err = m.keeper.cdc.Unmarshal(bz, &hashes) + if err != nil { + return [][]byte{}, err + } + + return hashes.Checksums, nil +} + +// deleteChecksums deletes the checksums stored under the KeyChecksums key. +func (m Migrator) deleteChecksums(ctx sdk.Context) error { + store := m.keeper.storeService.OpenKVStore(ctx) + err := store.Delete([]byte(types.KeyChecksums)) + + return err +} diff --git a/modules/light-clients/08-wasm/keeper/migrations_test.go b/modules/light-clients/08-wasm/keeper/migrations_test.go new file mode 100644 index 0000000..de63e93 --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/migrations_test.go @@ -0,0 +1,60 @@ +package keeper_test + +import ( + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/keeper" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" +) + +func (suite *KeeperTestSuite) TestMigrateWasmStore() { + testCases := []struct { + name string + checksums [][]byte + }{ + { + "success: empty checksums", + [][]byte{}, + }, + { + "success: multiple checksums", + [][]byte{[]byte("hash1"), []byte("hash2"), []byte("hash3")}, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupTest() + + suite.storeChecksums(tc.checksums) + + // run the migration + wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper + m := keeper.NewMigrator(wasmClientKeeper) + + err := m.MigrateChecksums(suite.chainA.GetContext()) + suite.Require().NoError(err) + + // check that they were stored in KeySet + for _, hash := range tc.checksums { + suite.Require().True(wasmClientKeeper.GetChecksums().Has(suite.chainA.GetContext(), hash)) + } + + // check that the data under the old key was deleted + store := suite.chainA.GetContext().KVStore(GetSimApp(suite.chainA).GetKey(types.StoreKey)) + suite.Require().Nil(store.Get([]byte(types.KeyChecksums))) + }) + } +} + +// storeChecksums stores the given checksums under the KeyChecksums key, it runs +// each time on an empty store so we don't need to read the previous checksums. +func (suite *KeeperTestSuite) storeChecksums(checksums [][]byte) { + ctx := suite.chainA.GetContext() + + store := ctx.KVStore(GetSimApp(suite.chainA).GetKey(types.StoreKey)) + checksum := types.Checksums{Checksums: checksums} + bz, err := GetSimApp(suite.chainA).AppCodec().Marshal(&checksum) + suite.Require().NoError(err) + + store.Set([]byte(types.KeyChecksums), bz) +} diff --git a/modules/light-clients/08-wasm/keeper/msg_server.go b/modules/light-clients/08-wasm/keeper/msg_server.go new file mode 100644 index 0000000..7e054cd --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/msg_server.go @@ -0,0 +1,79 @@ +package keeper + +import ( + "context" + "encoding/hex" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +var _ types.MsgServer = (*Keeper)(nil) + +// StoreCode defines a rpc handler method for MsgStoreCode +func (k Keeper) StoreCode(goCtx context.Context, msg *types.MsgStoreCode) (*types.MsgStoreCodeResponse, error) { + if k.GetAuthority() != msg.Signer { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "expected %s, got %s", k.GetAuthority(), msg.Signer) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + checksum, err := k.storeWasmCode(ctx, msg.WasmByteCode, k.GetVM().StoreCode) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to store wasm bytecode") + } + + emitStoreWasmCodeEvent(ctx, checksum) + + return &types.MsgStoreCodeResponse{ + Checksum: checksum, + }, nil +} + +// RemoveChecksum defines a rpc handler method for MsgRemoveChecksum +func (k Keeper) RemoveChecksum(goCtx context.Context, msg *types.MsgRemoveChecksum) (*types.MsgRemoveChecksumResponse, + error, +) { + if k.GetAuthority() != msg.Signer { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "expected %s, got %s", k.GetAuthority(), msg.Signer) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + if !k.HasChecksum(ctx, msg.Checksum) { + return nil, types.ErrWasmChecksumNotFound + } + + err := k.GetChecksums().Remove(goCtx, msg.Checksum) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to remove checksum") + } + + // unpin the code from the vm in-memory cache + if err := k.GetVM().Unpin(msg.Checksum); err != nil { + return nil, errorsmod.Wrapf(err, "failed to unpin contract with checksum (%s) from vm cache", hex.EncodeToString(msg.Checksum)) + } + + return &types.MsgRemoveChecksumResponse{}, nil +} + +// MigrateContract defines a rpc handler method for MsgMigrateContract +func (k Keeper) MigrateContract(goCtx context.Context, msg *types.MsgMigrateContract) (*types.MsgMigrateContractResponse, error) { + if k.GetAuthority() != msg.Signer { + return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "expected %s, got %s", k.GetAuthority(), msg.Signer) + } + + ctx := sdk.UnwrapSDKContext(goCtx) + + err := k.migrateContractCode(ctx, msg.ClientId, msg.Checksum, msg.Msg) + if err != nil { + return nil, errorsmod.Wrap(err, "failed to migrate contract") + } + + // event emission is handled in migrateContractCode + + return &types.MsgMigrateContractResponse{}, nil +} diff --git a/modules/light-clients/08-wasm/keeper/msg_server_test.go b/modules/light-clients/08-wasm/keeper/msg_server_test.go new file mode 100644 index 0000000..07e181c --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/msg_server_test.go @@ -0,0 +1,429 @@ +package keeper_test + +import ( + "encoding/hex" + "encoding/json" + "errors" + + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func (suite *KeeperTestSuite) TestMsgStoreCode() { + var ( + msg *types.MsgStoreCode + signer string + data []byte + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + msg = types.NewMsgStoreCode(signer, data) + }, + nil, + }, + { + "fails with duplicate wasm code", + func() { + msg = types.NewMsgStoreCode(signer, data) + + _, err := GetSimApp(suite.chainA).WasmClientKeeper.StoreCode(suite.chainA.GetContext(), msg) + suite.Require().NoError(err) + }, + types.ErrWasmCodeExists, + }, + { + "fails with zero-length wasm code", + func() { + msg = types.NewMsgStoreCode(signer, []byte{}) + }, + types.ErrWasmEmptyCode, + }, + { + "fails with checksum", + func() { + msg = types.NewMsgStoreCode(signer, []byte{0, 1, 3, 4}) + }, + errors.New("Wasm bytes do not start with Wasm magic number"), + }, + { + "fails with wasm code too large", + func() { + msg = types.NewMsgStoreCode(signer, wasmtesting.CreateMockContract([]byte(ibctesting.GenerateString(uint(types.MaxWasmSize))))) + }, + types.ErrWasmCodeTooLarge, + }, + { + "fails with unauthorized signer", + func() { + signer = suite.chainA.SenderAccount.GetAddress().String() + msg = types.NewMsgStoreCode(signer, data) + }, + ibcerrors.ErrUnauthorized, + }, + { + "failure: checksum could not be pinned", + func() { + msg = types.NewMsgStoreCode(signer, data) + + suite.mockVM.PinFn = func(_ wasmvm.Checksum) error { + return wasmtesting.ErrMockVM + } + }, + wasmtesting.ErrMockVM, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + signer = authtypes.NewModuleAddress(govtypes.ModuleName).String() + data = wasmtesting.Code + + tc.malleate() + + ctx := suite.chainA.GetContext() + res, err := GetSimApp(suite.chainA).WasmClientKeeper.StoreCode(ctx, msg) + events := ctx.EventManager().Events() + + if tc.expError == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + suite.Require().NotEmpty(res.Checksum) + + // Verify events + expectedEvents := sdk.Events{ + sdk.NewEvent( + "store_wasm_code", + sdk.NewAttribute(types.AttributeKeyWasmChecksum, hex.EncodeToString(res.Checksum)), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + } + + for _, evt := range expectedEvents { + suite.Require().Contains(events, evt) + } + } else { + suite.Require().Contains(err.Error(), tc.expError.Error()) + suite.Require().Nil(res) + suite.Require().Empty(events) + } + }) + } +} + +func (suite *KeeperTestSuite) TestMsgMigrateContract() { + oldChecksum, err := types.CreateChecksum(wasmtesting.Code) + suite.Require().NoError(err) + + newByteCode := wasmtesting.CreateMockContract([]byte("MockByteCode-TestMsgMigrateContract")) + + govAcc := authtypes.NewModuleAddress(govtypes.ModuleName).String() + + var ( + newChecksum []byte + msg *types.MsgMigrateContract + expClientState *types.ClientState + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success: no update to client state", + func() { + msg = types.NewMsgMigrateContract(govAcc, defaultWasmClientID, newChecksum, []byte("{}")) + + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil + } + }, + nil, + }, + { + "success: update client state", + func() { + msg = types.NewMsgMigrateContract(govAcc, defaultWasmClientID, newChecksum, []byte("{}")) + + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + // the checksum written in the client state will later be overwritten by the message server. + expClientStateBz := wasmtesting.CreateMockClientStateBz(suite.chainA.App.AppCodec(), []byte("invalid checksum")) + var ok bool + expClientState, ok = clienttypes.MustUnmarshalClientState(suite.chainA.App.AppCodec(), expClientStateBz).(*types.ClientState) + suite.Require().True(ok) + store.Set(host.ClientStateKey(), expClientStateBz) + + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil + } + }, + nil, + }, + { + "failure: same checksum", + func() { + msg = types.NewMsgMigrateContract(govAcc, defaultWasmClientID, oldChecksum, []byte("{}")) + + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + panic("unreachable") + } + }, + types.ErrWasmCodeExists, + }, + { + "failure: unauthorized signer", + func() { + msg = types.NewMsgMigrateContract(suite.chainA.SenderAccount.GetAddress().String(), defaultWasmClientID, newChecksum, []byte("{}")) + }, + ibcerrors.ErrUnauthorized, + }, + { + "failure: invalid wasm checksum", + func() { + msg = types.NewMsgMigrateContract(govAcc, defaultWasmClientID, []byte(ibctesting.InvalidID), []byte("{}")) + }, + types.ErrWasmChecksumNotFound, + }, + { + "failure: invalid client id", + func() { + msg = types.NewMsgMigrateContract(govAcc, ibctesting.InvalidID, newChecksum, []byte("{}")) + }, + clienttypes.ErrClientNotFound, + }, + { + "failure: vm returns error", + func() { + msg = types.NewMsgMigrateContract(govAcc, defaultWasmClientID, newChecksum, []byte("{}")) + + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return nil, wasmtesting.DefaultGasUsed, wasmtesting.ErrMockVM + } + }, + types.ErrVMError, + }, + { + "failure: contract returns error", + func() { + msg = types.NewMsgMigrateContract(govAcc, defaultWasmClientID, newChecksum, []byte("{}")) + + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return &wasmvmtypes.ContractResult{Err: wasmtesting.ErrMockContract.Error()}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmContractCallFailed, + }, + { + "failure: incorrect state update", + func() { + msg = types.NewMsgMigrateContract(govAcc, defaultWasmClientID, newChecksum, []byte("{}")) + + suite.mockVM.MigrateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + // the checksum written in here will be overwritten + store.Set(host.ClientStateKey(), []byte("changed client state")) + + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil + } + }, + types.ErrWasmInvalidContractModification, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + var ok bool + suite.SetupWasmWithMockVM() + + _ = suite.storeWasmCode(wasmtesting.Code) + newChecksum = suite.storeWasmCode(newByteCode) + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + // this is the old client state + expClientState, ok = endpoint.GetClientState().(*types.ClientState) + suite.Require().True(ok) + + tc.malleate() + + ctx := suite.chainA.GetContext() + res, err := GetSimApp(suite.chainA).WasmClientKeeper.MigrateContract(ctx, msg) + events := ctx.EventManager().Events().ToABCIEvents() + + if tc.expError == nil { + expClientState.Checksum = newChecksum + + suite.Require().NoError(err) + suite.Require().NotNil(res) + + // updated client state + clientState, ok := endpoint.GetClientState().(*types.ClientState) + suite.Require().True(ok) + + suite.Require().Equal(expClientState, clientState) + + // Verify events + expectedEvents := sdk.Events{ + sdk.NewEvent( + "migrate_contract", + sdk.NewAttribute(types.AttributeKeyClientID, defaultWasmClientID), + sdk.NewAttribute(types.AttributeKeyWasmChecksum, hex.EncodeToString(oldChecksum)), + sdk.NewAttribute(types.AttributeKeyNewChecksum, hex.EncodeToString(newChecksum)), + ), + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + ), + }.ToABCIEvents() + + for _, evt := range expectedEvents { + suite.Require().Contains(events, evt) + } + } else { + suite.Require().ErrorIs(err, tc.expError) + suite.Require().Nil(res) + } + }) + } +} + +func (suite *KeeperTestSuite) TestMsgRemoveChecksum() { + checksum, err := types.CreateChecksum(wasmtesting.Code) + suite.Require().NoError(err) + + govAcc := authtypes.NewModuleAddress(govtypes.ModuleName).String() + + var ( + msg *types.MsgRemoveChecksum + expChecksums []types.Checksum + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + msg = types.NewMsgRemoveChecksum(govAcc, checksum) + + expChecksums = []types.Checksum{} + }, + nil, + }, + { + "success: many checksums", + func() { + msg = types.NewMsgRemoveChecksum(govAcc, checksum) + + expChecksums = []types.Checksum{} + + for i := range 20 { + mockCode := wasmtesting.CreateMockContract([]byte{byte(i)}) + checksum, err := types.CreateChecksum(mockCode) + suite.Require().NoError(err) + + keeper := GetSimApp(suite.chainA).WasmClientKeeper + err = keeper.GetChecksums().Set(suite.chainA.GetContext(), checksum) + suite.Require().NoError(err) + + expChecksums = append(expChecksums, checksum) + } + }, + nil, + }, + { + "failure: checksum is missing", + func() { + msg = types.NewMsgRemoveChecksum(govAcc, []byte{1}) + }, + types.ErrWasmChecksumNotFound, + }, + { + "failure: unauthorized signer", + func() { + msg = types.NewMsgRemoveChecksum(suite.chainA.SenderAccount.GetAddress().String(), checksum) + }, + ibcerrors.ErrUnauthorized, + }, + { + "failure: code has could not be unpinned", + func() { + msg = types.NewMsgRemoveChecksum(govAcc, checksum) + + suite.mockVM.UnpinFn = func(_ wasmvm.Checksum) error { + return wasmtesting.ErrMockVM + } + }, + wasmtesting.ErrMockVM, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + _ = suite.storeWasmCode(wasmtesting.Code) + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + tc.malleate() + + ctx := suite.chainA.GetContext() + res, err := GetSimApp(suite.chainA).WasmClientKeeper.RemoveChecksum(ctx, msg) + events := ctx.EventManager().Events().ToABCIEvents() + + if tc.expError == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + + checksums, err := GetSimApp(suite.chainA).WasmClientKeeper.GetAllChecksums(suite.chainA.GetContext()) + suite.Require().NoError(err) + + // Check equality of checksums up to order + suite.Require().ElementsMatch(expChecksums, checksums) + + // Verify events + suite.Require().Len(events, 0) + } else { + suite.Require().ErrorIs(err, tc.expError) + suite.Require().Nil(res) + } + }) + } +} diff --git a/modules/light-clients/08-wasm/keeper/options.go b/modules/light-clients/08-wasm/keeper/options.go new file mode 100644 index 0000000..b946135 --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/options.go @@ -0,0 +1,23 @@ +package keeper + +// Option is an extension point to instantiate keeper with non default values +type Option interface { + apply(*Keeper) +} + +type optsFn func(*Keeper) + +func (f optsFn) apply(keeper *Keeper) { + f(keeper) +} + +// WithQueryPlugins is an optional constructor parameter to pass custom query plugins for wasmVM requests. +// Missing fields will be filled with default queriers. +func WithQueryPlugins(plugins *QueryPlugins) Option { + return optsFn(func(k *Keeper) { + currentPlugins := k.getQueryPlugins() + newPlugins := currentPlugins.Merge(plugins) + + k.setQueryPlugins(newPlugins) + }) +} diff --git a/modules/light-clients/08-wasm/keeper/options_test.go b/modules/light-clients/08-wasm/keeper/options_test.go new file mode 100644 index 0000000..9491a06 --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/options_test.go @@ -0,0 +1,153 @@ +package keeper_test + +import ( + "encoding/json" + "errors" + + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" + + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/keeper" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" +) + +func mockErrorCustomQuerier() func(sdk.Context, json.RawMessage) ([]byte, error) { + return func(_ sdk.Context, _ json.RawMessage) ([]byte, error) { + return nil, errors.New("custom querier error for TestNewKeeperWithOptions") + } +} + +func mockErrorStargateQuerier() func(sdk.Context, *wasmvmtypes.StargateQuery) ([]byte, error) { + return func(_ sdk.Context, _ *wasmvmtypes.StargateQuery) ([]byte, error) { + return nil, errors.New("stargate querier error for TestNewKeeperWithOptions") + } +} + +func (suite *KeeperTestSuite) TestNewKeeperWithOptions() { + var k keeper.Keeper + testCases := []struct { + name string + malleate func() + verifyFn func(keeper.Keeper) + }{ + { + "success: no options", + func() { + k = keeper.NewKeeperWithVM( + GetSimApp(suite.chainA).AppCodec(), + runtime.NewKVStoreService(GetSimApp(suite.chainA).GetKey(types.StoreKey)), + GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, + GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), + GetSimApp(suite.chainA).WasmClientKeeper.GetVM(), + GetSimApp(suite.chainA).GRPCQueryRouter(), + ) + }, + func(k keeper.Keeper) { + plugins := k.GetQueryPlugins() + + _, err := plugins.Custom(sdk.Context{}, nil) + suite.Require().ErrorIs(err, wasmvmtypes.UnsupportedRequest{Kind: "Custom queries are not allowed"}) + + _, err = plugins.Stargate(sdk.Context{}, &wasmvmtypes.StargateQuery{}) + suite.Require().ErrorIs(err, wasmvmtypes.UnsupportedRequest{Kind: "'' path is not allowed from the contract"}) + }, + }, + { + "success: custom querier", + func() { + querierOption := keeper.WithQueryPlugins(&keeper.QueryPlugins{ + Custom: mockErrorCustomQuerier(), + }) + k = keeper.NewKeeperWithVM( + GetSimApp(suite.chainA).AppCodec(), + runtime.NewKVStoreService(GetSimApp(suite.chainA).GetKey(types.StoreKey)), + GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, + GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), + GetSimApp(suite.chainA).WasmClientKeeper.GetVM(), + GetSimApp(suite.chainA).GRPCQueryRouter(), + querierOption, + ) + }, + func(k keeper.Keeper) { + plugins := k.GetQueryPlugins() + + _, err := plugins.Custom(sdk.Context{}, nil) + suite.Require().ErrorContains(err, "custom querier error for TestNewKeeperWithOptions") + + _, err = plugins.Stargate(sdk.Context{}, &wasmvmtypes.StargateQuery{}) + suite.Require().ErrorIs(err, wasmvmtypes.UnsupportedRequest{Kind: "'' path is not allowed from the contract"}) + }, + }, + { + "success: stargate querier", + func() { + querierOption := keeper.WithQueryPlugins(&keeper.QueryPlugins{ + Stargate: mockErrorStargateQuerier(), + }) + k = keeper.NewKeeperWithVM( + GetSimApp(suite.chainA).AppCodec(), + runtime.NewKVStoreService(GetSimApp(suite.chainA).GetKey(types.StoreKey)), + GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, + GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), + GetSimApp(suite.chainA).WasmClientKeeper.GetVM(), + GetSimApp(suite.chainA).GRPCQueryRouter(), + querierOption, + ) + }, + func(k keeper.Keeper) { + plugins := k.GetQueryPlugins() + + _, err := plugins.Custom(sdk.Context{}, nil) + suite.Require().ErrorIs(err, wasmvmtypes.UnsupportedRequest{Kind: "Custom queries are not allowed"}) + + _, err = plugins.Stargate(sdk.Context{}, &wasmvmtypes.StargateQuery{}) + suite.Require().ErrorContains(err, "stargate querier error for TestNewKeeperWithOptions") + }, + }, + { + "success: both queriers", + func() { + querierOption := keeper.WithQueryPlugins(&keeper.QueryPlugins{ + Custom: mockErrorCustomQuerier(), + Stargate: mockErrorStargateQuerier(), + }) + k = keeper.NewKeeperWithVM( + GetSimApp(suite.chainA).AppCodec(), + runtime.NewKVStoreService(GetSimApp(suite.chainA).GetKey(types.StoreKey)), + GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, + GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), + GetSimApp(suite.chainA).WasmClientKeeper.GetVM(), + GetSimApp(suite.chainA).GRPCQueryRouter(), + querierOption, + ) + }, + func(k keeper.Keeper) { + plugins := k.GetQueryPlugins() + + _, err := plugins.Custom(sdk.Context{}, nil) + suite.Require().ErrorContains(err, "custom querier error for TestNewKeeperWithOptions") + + _, err = plugins.Stargate(sdk.Context{}, &wasmvmtypes.StargateQuery{}) + suite.Require().ErrorContains(err, "stargate querier error for TestNewKeeperWithOptions") + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.SetupTest() + + suite.Run(tc.name, func() { + // make sure the default query plugins are set + k.SetQueryPlugins(keeper.NewDefaultQueryPlugins(GetSimApp(suite.chainA).GRPCQueryRouter())) + + tc.malleate() + tc.verifyFn(k) + + // reset query plugins after each test + k.SetQueryPlugins(keeper.NewDefaultQueryPlugins(GetSimApp(suite.chainA).GRPCQueryRouter())) + }) + } +} diff --git a/modules/light-clients/08-wasm/keeper/querier.go b/modules/light-clients/08-wasm/keeper/querier.go new file mode 100644 index 0000000..f7ba306 --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/querier.go @@ -0,0 +1,182 @@ +package keeper + +import ( + "encoding/json" + "fmt" + "slices" + + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" + + errorsmod "cosmossdk.io/errors" + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + abci "github.com/cometbft/cometbft/abci/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" +) + +/* +`queryHandler` is a contextual querier which references the global `ibcwasm.QueryPluginsI` +to handle queries. The global `ibcwasm.QueryPluginsI` points to a `types.QueryPlugins` which +contains two sub-queriers: `types.CustomQuerier` and `types.StargateQuerier`. These sub-queriers +can be replaced by the user through the options api in the keeper. + +In addition, the `types.StargateQuerier` references a global `types.QueryRouter` which points +to `baseapp.GRPCQueryRouter`. + +This design is based on wasmd's (v0.50.0) querier plugin design. +*/ + +var _ wasmvmtypes.Querier = (*queryHandler)(nil) + +// defaultAcceptList defines a set of default allowed queries made available to the Querier. +var defaultAcceptList = []string{ + "/ibc.core.client.v1.Query/VerifyMembership", +} + +// queryHandler is a wrapper around the sdk.Context and the CallerID that calls +// into the query plugins. +type queryHandler struct { + Ctx sdk.Context + Plugins QueryPlugins + CallerID string +} + +// newQueryHandler returns a default querier that can be used in the contract. +func newQueryHandler(ctx sdk.Context, plugins QueryPlugins, callerID string) *queryHandler { + return &queryHandler{ + Ctx: ctx, + Plugins: plugins, + CallerID: callerID, + } +} + +// GasConsumed implements the wasmvmtypes.Querier interface. +func (q *queryHandler) GasConsumed() uint64 { + return VMGasRegister.ToWasmVMGas(q.Ctx.GasMeter().GasConsumed()) +} + +// Query implements the wasmvmtypes.Querier interface. +func (q *queryHandler) Query(request wasmvmtypes.QueryRequest, gasLimit uint64) ([]byte, error) { + sdkGas := VMGasRegister.FromWasmVMGas(gasLimit) + + // discard all changes/events in subCtx by not committing the cached context + subCtx, _ := q.Ctx.WithGasMeter(storetypes.NewGasMeter(sdkGas)).CacheContext() + + // make sure we charge the higher level context even on panic + defer func() { + q.Ctx.GasMeter().ConsumeGas(subCtx.GasMeter().GasConsumed(), "contract sub-query") + }() + + res, err := q.Plugins.HandleQuery(subCtx, q.CallerID, request) + if err == nil { + return res, nil + } + + moduleLogger(q.Ctx).Debug("Redacting query error", "cause", err) + return nil, redactError(err) +} + +type ( + CustomQuerier func(ctx sdk.Context, request json.RawMessage) ([]byte, error) + StargateQuerier func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) + + // QueryPlugins is a list of queriers that can be used to extend the default querier. + QueryPlugins struct { + Custom CustomQuerier + Stargate StargateQuerier + } +) + +// Merge merges the query plugin with a provided one. +func (e QueryPlugins) Merge(x *QueryPlugins) QueryPlugins { + // only update if this is non-nil and then only set values + if x == nil { + return e + } + + if x.Custom != nil { + e.Custom = x.Custom + } + + if x.Stargate != nil { + e.Stargate = x.Stargate + } + + return e +} + +// HandleQuery implements the ibcwasm.QueryPluginsI interface. +func (e QueryPlugins) HandleQuery(ctx sdk.Context, caller string, request wasmvmtypes.QueryRequest) ([]byte, error) { + if request.Stargate != nil { + return e.Stargate(ctx, request.Stargate) + } + + if request.Custom != nil { + return e.Custom(ctx, request.Custom) + } + + return nil, wasmvmtypes.UnsupportedRequest{Kind: "Unsupported query request"} +} + +// NewDefaultQueryPlugins returns the default set of query plugins +func NewDefaultQueryPlugins(queryRouter types.QueryRouter) QueryPlugins { + return QueryPlugins{ + Custom: RejectCustomQuerier(), + Stargate: AcceptListStargateQuerier([]string{}, queryRouter), + } +} + +// AcceptListStargateQuerier allows all queries that are in the provided accept list. +// This function returns protobuf encoded responses in bytes. +func AcceptListStargateQuerier(acceptedQueries []string, queryRouter types.QueryRouter) func(sdk.Context, *wasmvmtypes.StargateQuery) ([]byte, error) { + return func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) { + // append user defined accepted queries to default list defined above. + acceptedQueries = append(defaultAcceptList, acceptedQueries...) + + isAccepted := slices.Contains(acceptedQueries, request.Path) + if !isAccepted { + return nil, wasmvmtypes.UnsupportedRequest{Kind: fmt.Sprintf("'%s' path is not allowed from the contract", request.Path)} + } + + route := queryRouter.Route(request.Path) + if route == nil { + return nil, wasmvmtypes.UnsupportedRequest{Kind: fmt.Sprintf("No route to query '%s'", request.Path)} + } + + res, err := route(ctx, &abci.RequestQuery{ + Data: request.Data, + Path: request.Path, + }) + if err != nil { + return nil, err + } + if res == nil || res.Value == nil { + return nil, wasmvmtypes.InvalidResponse{Err: "Query response is empty"} + } + + return res.Value, nil + } +} + +// RejectCustomQuerier rejects all custom queries +func RejectCustomQuerier() func(sdk.Context, json.RawMessage) ([]byte, error) { + return func(ctx sdk.Context, request json.RawMessage) ([]byte, error) { + return nil, wasmvmtypes.UnsupportedRequest{Kind: "Custom queries are not allowed"} + } +} + +// Wasmd Issue [#759](https://github.com/CosmWasm/wasmd/issues/759) +// Don't return error string for worries of non-determinism +func redactError(err error) error { + // Do not redact system errors + // SystemErrors must be created in 08-wasm and we can ensure determinism + if wasmvmtypes.ToSystemError(err) != nil { + return err + } + + codespace, code, _ := errorsmod.ABCIInfo(err, false) + return fmt.Errorf("codespace: %s, code: %d", codespace, code) +} diff --git a/modules/light-clients/08-wasm/keeper/querier_test.go b/modules/light-clients/08-wasm/keeper/querier_test.go new file mode 100644 index 0000000..1faad2e --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/querier_test.go @@ -0,0 +1,352 @@ +package keeper_test + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "math" + + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/keeper" + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +type CustomQuery struct { + Echo *QueryEcho `json:"echo,omitempty"` +} + +type QueryEcho struct { + Data string `json:"data"` +} + +func mockCustomQuerier() func(sdk.Context, json.RawMessage) ([]byte, error) { + return func(ctx sdk.Context, request json.RawMessage) ([]byte, error) { + var customQuery CustomQuery + err := json.Unmarshal([]byte(request), &customQuery) + if err != nil { + return nil, wasmtesting.ErrMockContract + } + + if customQuery.Echo != nil { + data, err := json.Marshal(customQuery.Echo.Data) + return data, err + } + + return nil, wasmtesting.ErrMockContract + } +} + +func (suite *KeeperTestSuite) TestCustomQuery() { + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success: custom query", + func() { + querierPlugin := keeper.QueryPlugins{ + Custom: mockCustomQuerier(), + } + + GetSimApp(suite.chainA).WasmClientKeeper.SetQueryPlugins(querierPlugin) + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, querier wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + echo := CustomQuery{ + Echo: &QueryEcho{ + Data: "hello world", + }, + } + echoJSON, err := json.Marshal(echo) + suite.Require().NoError(err) + + resp, err := querier.Query(wasmvmtypes.QueryRequest{ + Custom: json.RawMessage(echoJSON), + }, math.MaxUint64) + suite.Require().NoError(err) + + var respData string + err = json.Unmarshal(resp, &respData) + suite.Require().NoError(err) + suite.Require().Equal("hello world", respData) + + resp, err = json.Marshal(types.StatusResult{Status: exported.Active.String()}) + suite.Require().NoError(err) + + return &wasmvmtypes.QueryResult{Ok: resp}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + }, + { + "failure: default query", + func() { + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, querier wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + resp, err := querier.Query(wasmvmtypes.QueryRequest{Custom: json.RawMessage("{}")}, math.MaxUint64) + suite.Require().ErrorIs(err, wasmvmtypes.UnsupportedRequest{Kind: "Custom queries are not allowed"}) + suite.Require().Nil(resp) + + return nil, wasmtesting.DefaultGasUsed, err + }) + }, + types.ErrVMError, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + _ = suite.storeWasmCode(wasmtesting.Code) + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + tc.malleate() + + wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + clientState, ok := endpoint.GetClientState().(*types.ClientState) + suite.Require().True(ok) + + res, err := wasmClientKeeper.WasmQuery(suite.chainA.GetContext(), endpoint.ClientID, clientStore, clientState, types.QueryMsg{Status: &types.StatusMsg{}}) + + if tc.expError == nil { + suite.Require().Nil(err) + suite.Require().NotNil(res) + } else { + suite.Require().Nil(res) + suite.Require().ErrorIs(err, tc.expError) + } + + // reset query plugins after each test + wasmClientKeeper.SetQueryPlugins(keeper.NewDefaultQueryPlugins(GetSimApp(suite.chainA).GRPCQueryRouter())) + }) + } +} + +func (suite *KeeperTestSuite) TestStargateQuery() { + typeURL := "/ibc.lightclients.wasm.v1.Query/Checksums" + + var ( + endpoint *wasmtesting.WasmEndpoint + checksum []byte + expDiscardedState = false + proofKey = []byte("mock-key") + testKey = []byte("test-key") + value = []byte("mock-value") + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success: custom query", + func() { + querierPlugin := keeper.QueryPlugins{ + Stargate: keeper.AcceptListStargateQuerier([]string{typeURL}, GetSimApp(suite.chainA).GRPCQueryRouter()), + } + + GetSimApp(suite.chainA).WasmClientKeeper.SetQueryPlugins(querierPlugin) + + suite.mockVM.RegisterQueryCallback(types.TimestampAtHeightMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, querier wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + queryRequest := types.QueryChecksumsRequest{} + bz, err := queryRequest.Marshal() + suite.Require().NoError(err) + + resp, err := querier.Query(wasmvmtypes.QueryRequest{ + Stargate: &wasmvmtypes.StargateQuery{ + Path: typeURL, + Data: bz, + }, + }, math.MaxUint64) + suite.Require().NoError(err) + + var respData types.QueryChecksumsResponse + err = respData.Unmarshal(resp) + suite.Require().NoError(err) + + expChecksum := hex.EncodeToString(checksum) + + suite.Require().Len(respData.Checksums, 1) + suite.Require().Equal(expChecksum, respData.Checksums[0]) + + store.Set(testKey, value) + + result, err := json.Marshal(types.TimestampAtHeightResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.QueryResult{Ok: result}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + }, + { + // The following test sets a mock proof key and value in the ibc store and registers a query callback on the Status msg. + // The Status handler will then perform the QueryVerifyMembershipRequest using the wasmvm.Querier. + // As the VerifyMembership query rpc will call directly back into the same client, we also register a callback for VerifyMembership. + // Here we decode the proof and verify the mock proof key and value are set in the ibc store. + // This exercises the full flow through the grpc handler and into the light client for verification, handling encoding and routing. + // Furthermore we write a test key and assert that the state changes made by this handler were discarded by the cachedCtx at the grpc handler. + "success: verify membership query", + func() { + querierPlugin := keeper.QueryPlugins{ + Stargate: keeper.AcceptListStargateQuerier([]string{""}, GetSimApp(suite.chainA).GRPCQueryRouter()), + } + + GetSimApp(suite.chainA).WasmClientKeeper.SetQueryPlugins(querierPlugin) + + store := suite.chainA.GetContext().KVStore(GetSimApp(suite.chainA).GetKey(exported.StoreKey)) + store.Set(proofKey, value) + + suite.coordinator.CommitBlock(suite.chainA) + proof, proofHeight := endpoint.QueryProofAtHeight(proofKey, uint64(suite.chainA.GetContext().BlockHeight())) + + merklePath := commitmenttypes.NewMerklePath(proofKey) + merklePath, err := commitmenttypes.ApplyPrefix(suite.chainA.GetPrefix(), merklePath) + suite.Require().NoError(err) + + suite.mockVM.RegisterQueryCallback(types.TimestampAtHeightMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, querier wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + queryRequest := clienttypes.QueryVerifyMembershipRequest{ + ClientId: endpoint.ClientID, + Proof: proof, + ProofHeight: proofHeight, + MerklePath: merklePath, + Value: value, + } + + bz, err := queryRequest.Marshal() + suite.Require().NoError(err) + + resp, err := querier.Query(wasmvmtypes.QueryRequest{ + Stargate: &wasmvmtypes.StargateQuery{ + Path: "/ibc.core.client.v1.Query/VerifyMembership", + Data: bz, + }, + }, math.MaxUint64) + suite.Require().NoError(err) + + var respData clienttypes.QueryVerifyMembershipResponse + err = respData.Unmarshal(resp) + suite.Require().NoError(err) + + suite.Require().True(respData.Success) + + result, err := json.Marshal(types.TimestampAtHeightResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.QueryResult{Ok: result}, wasmtesting.DefaultGasUsed, nil + }) + + suite.mockVM.RegisterSudoCallback(types.VerifyMembershipMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, + _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction, + ) (*wasmvmtypes.ContractResult, uint64, error) { + var payload types.SudoMsg + err := json.Unmarshal(sudoMsg, &payload) + suite.Require().NoError(err) + + var merkleProof commitmenttypes.MerkleProof + err = suite.chainA.Codec.Unmarshal(payload.VerifyMembership.Proof, &merkleProof) + suite.Require().NoError(err) + + root := commitmenttypes.NewMerkleRoot(suite.chainA.App.LastCommitID().Hash) + err = merkleProof.VerifyMembership(commitmenttypes.GetSDKSpecs(), root, merklePath, payload.VerifyMembership.Value) + suite.Require().NoError(err) + + bz, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + expDiscardedState = true + store.Set(testKey, value) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: bz}}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + }, + { + "failure: default querier", + func() { + suite.mockVM.RegisterQueryCallback(types.TimestampAtHeightMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, querier wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + queryRequest := types.QueryChecksumsRequest{} + bz, err := queryRequest.Marshal() + suite.Require().NoError(err) + + resp, err := querier.Query(wasmvmtypes.QueryRequest{ + Stargate: &wasmvmtypes.StargateQuery{ + Path: typeURL, + Data: bz, + }, + }, math.MaxUint64) + suite.Require().ErrorIs(err, wasmvmtypes.UnsupportedRequest{Kind: fmt.Sprintf("'%s' path is not allowed from the contract", typeURL)}) + suite.Require().Nil(resp) + + store.Set(testKey, value) + + return nil, wasmtesting.DefaultGasUsed, err + }) + }, + wasmvmtypes.UnsupportedRequest{Kind: fmt.Sprintf("'%s' path is not allowed from the contract", typeURL)}, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + expDiscardedState = false + suite.SetupWasmWithMockVM() + checksum = suite.storeWasmCode(wasmtesting.Code) + + endpoint = wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + tc.malleate() + + wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper + + payload := types.QueryMsg{ + TimestampAtHeight: &types.TimestampAtHeightMsg{ + Height: clienttypes.NewHeight(1, 100), + }, + } + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + clientState, ok := endpoint.GetClientState().(*types.ClientState) + suite.Require().True(ok) + + // NOTE: we register query callbacks against: types.TimestampAtHeightMsg{} + // in practise, this can against any client state msg, however registering against types.StatusMsg{} introduces recursive loops + // due to test case: "success: verify membership query" + res, err := wasmClientKeeper.WasmQuery(suite.chainA.GetContext(), endpoint.ClientID, clientStore, clientState, payload) + + if tc.expError == nil { + suite.Require().NoError(err) + suite.Require().NotNil(res) + } else { + suite.Require().Nil(res) + // use error contains as wasmvm errors do not implement errors.Is method + suite.Require().ErrorContains(err, tc.expError.Error()) + } + + clientStore = suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + if expDiscardedState { + suite.Require().False(clientStore.Has(testKey)) + } else { + suite.Require().True(clientStore.Has(testKey)) + } + + // reset query plugins after each test + wasmClientKeeper.SetQueryPlugins(keeper.NewDefaultQueryPlugins(GetSimApp(suite.chainA).GRPCQueryRouter())) + }) + } +} diff --git a/modules/light-clients/08-wasm/keeper/snapshotter.go b/modules/light-clients/08-wasm/keeper/snapshotter.go new file mode 100644 index 0000000..397145d --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/snapshotter.go @@ -0,0 +1,146 @@ +package keeper + +import ( + "encoding/hex" + "io" + + errorsmod "cosmossdk.io/errors" + snapshot "cosmossdk.io/store/snapshots/types" + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" +) + +var _ snapshot.ExtensionSnapshotter = &WasmSnapshotter{} + +// SnapshotFormat defines the default snapshot extension encoding format. +// SnapshotFormat 1 is gzipped wasm byte code for each item payload. No protobuf envelope, no metadata. +const SnapshotFormat = 1 + +// WasmSnapshotter implements the snapshot.ExtensionSnapshotter interface and is used to +// import and export state maintained within the wasmvm cache. +// NOTE: The following ExtensionSnapshotter has been adapted from CosmWasm's x/wasm: +// https://github.com/CosmWasm/wasmd/blob/v0.43.0/x/wasm/keeper/snapshotter.go +type WasmSnapshotter struct { + cms storetypes.MultiStore + keeper *Keeper +} + +// NewWasmSnapshotter creates and returns a new snapshot.ExtensionSnapshotter implementation for the 08-wasm module. +func NewWasmSnapshotter(cms storetypes.MultiStore, keeper *Keeper) snapshot.ExtensionSnapshotter { + return &WasmSnapshotter{ + cms: cms, + keeper: keeper, + } +} + +// SnapshotName implements the snapshot.ExtensionSnapshotter interface. +// A unique name should be provided such that the implementation can be identified by the manager. +func (*WasmSnapshotter) SnapshotName() string { + return types.ModuleName +} + +// SnapshotFormat implements the snapshot.ExtensionSnapshotter interface. +// This is the default format used for encoding payloads when taking a snapshot. +func (*WasmSnapshotter) SnapshotFormat() uint32 { + return SnapshotFormat +} + +// SupportedFormats implements the snapshot.ExtensionSnapshotter interface. +// This defines a list of supported formats the snapshotter extension can restore from. +func (*WasmSnapshotter) SupportedFormats() []uint32 { + return []uint32{SnapshotFormat} +} + +// SnapshotExtension implements the snapshot.ExntensionSnapshotter interface. +// SnapshotExtension is used to write data payloads into the underlying protobuf stream from the 08-wasm module. +func (ws *WasmSnapshotter) SnapshotExtension(height uint64, payloadWriter snapshot.ExtensionPayloadWriter) error { + cacheMS, err := ws.cms.CacheMultiStoreWithVersion(int64(height)) + if err != nil { + return err + } + + ctx := sdk.NewContext(cacheMS, cmtproto.Header{}, false, nil) + + checksums, err := ws.keeper.GetAllChecksums(ctx) + if err != nil { + return err + } + + for _, checksum := range checksums { + wasmCode, err := ws.keeper.GetVM().GetCode(checksum) + if err != nil { + return err + } + + compressedWasm, err := types.GzipIt(wasmCode) + if err != nil { + return err + } + + if err = payloadWriter(compressedWasm); err != nil { + return err + } + } + + return nil +} + +// RestoreExtension implements the snapshot.ExtensionSnapshotter interface. +// RestoreExtension is used to read data from an existing extension state snapshot into the 08-wasm module. +// The payload reader returns io.EOF when it has reached the end of the extension state snapshot. +func (ws *WasmSnapshotter) RestoreExtension(height uint64, format uint32, payloadReader snapshot.ExtensionPayloadReader) error { + if format == ws.SnapshotFormat() { + return ws.processAllItems(height, payloadReader, restoreV1) + } + + return errorsmod.Wrapf(snapshot.ErrUnknownFormat, "expected %d, got %d", ws.SnapshotFormat(), format) +} + +func restoreV1(ctx sdk.Context, k *Keeper, compressedCode []byte) error { + if !types.IsGzip(compressedCode) { + return errorsmod.Wrap(types.ErrInvalidData, "expected wasm code is not gzip format") + } + + wasmCode, err := types.Uncompress(compressedCode, types.MaxWasmSize) + if err != nil { + return errorsmod.Wrap(err, "failed to uncompress wasm code") + } + + checksum, err := k.GetVM().StoreCodeUnchecked(wasmCode) + if err != nil { + return errorsmod.Wrap(err, "failed to store wasm code") + } + + if err := k.GetVM().Pin(checksum); err != nil { + return errorsmod.Wrapf(err, "failed to pin checksum: %s to in-memory cache", hex.EncodeToString(checksum)) + } + + return nil +} + +func (ws *WasmSnapshotter) processAllItems( + height uint64, + payloadReader snapshot.ExtensionPayloadReader, + cb func(sdk.Context, *Keeper, []byte) error, +) error { + ctx := sdk.NewContext(ws.cms, cmtproto.Header{Height: int64(height)}, false, nil) + for { + payload, err := payloadReader() + if err == io.EOF { + break + } else if err != nil { + return err + } + + if err := cb(ctx, ws.keeper, payload); err != nil { + return errorsmod.Wrap(err, "failure processing snapshot item") + } + } + + return nil +} diff --git a/modules/light-clients/08-wasm/keeper/snapshotter_test.go b/modules/light-clients/08-wasm/keeper/snapshotter_test.go new file mode 100644 index 0000000..f1b1fe0 --- /dev/null +++ b/modules/light-clients/08-wasm/keeper/snapshotter_test.go @@ -0,0 +1,121 @@ +package keeper_test + +import ( + "encoding/hex" + "time" + + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing/simapp" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" +) + +func (suite *KeeperTestSuite) TestSnapshotter() { + gzippedContract, err := types.GzipIt(wasmtesting.CreateMockContract([]byte("gzipped-contract"))) + suite.Require().NoError(err) + + testCases := []struct { + name string + contracts [][]byte + }{ + { + name: "single contract", + contracts: [][]byte{wasmtesting.Code}, + }, + { + name: "multiple contracts", + contracts: [][]byte{wasmtesting.Code, gzippedContract}, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + t := suite.T() + wasmClientApp := suite.SetupSnapshotterWithMockVM() + + ctx := wasmClientApp.NewUncachedContext(false, cmtproto.Header{ + ChainID: "foo", + Height: wasmClientApp.LastBlockHeight() + 1, + Time: time.Now(), + }) + + var srcChecksumCodes []byte + var checksums [][]byte + // store contract on chain + for _, contract := range tc.contracts { + signer := authtypes.NewModuleAddress(govtypes.ModuleName).String() + msg := types.NewMsgStoreCode(signer, contract) + + res, err := wasmClientApp.WasmClientKeeper.StoreCode(ctx, msg) + suite.Require().NoError(err) + + checksums = append(checksums, res.Checksum) + srcChecksumCodes = append(srcChecksumCodes, res.Checksum...) + + suite.Require().NoError(err) + } + + // create snapshot + res, err := wasmClientApp.Commit() + suite.Require().NoError(err) + suite.Require().NotNil(res) + + snapshotHeight := uint64(wasmClientApp.LastBlockHeight()) + snapshot, err := wasmClientApp.SnapshotManager().Create(snapshotHeight) + suite.Require().NoError(err) + suite.Require().NotNil(snapshot) + + // setup dest app with snapshot imported + destWasmClientApp := simapp.SetupWithEmptyStore(t, suite.mockVM) + destCtx := destWasmClientApp.NewUncachedContext(false, cmtproto.Header{ + ChainID: "bar", + Height: destWasmClientApp.LastBlockHeight() + 1, + Time: time.Now(), + }) + + resp, err := destWasmClientApp.WasmClientKeeper.Checksums(destCtx, &types.QueryChecksumsRequest{}) + suite.Require().NoError(err) + suite.Require().Empty(resp.Checksums) + + suite.Require().NoError(destWasmClientApp.SnapshotManager().Restore(*snapshot)) + + for i := uint32(0); i < snapshot.Chunks; i++ { + chunkBz, err := wasmClientApp.SnapshotManager().LoadChunk(snapshot.Height, snapshot.Format, i) + suite.Require().NoError(err) + + end, err := destWasmClientApp.SnapshotManager().RestoreChunk(chunkBz) + suite.Require().NoError(err) + + if end { + break + } + } + + var allDestAppChecksumsInWasmVMStore []byte + // check wasm contracts are imported + ctx = destWasmClientApp.NewUncachedContext(false, cmtproto.Header{ + ChainID: "foo", + Height: destWasmClientApp.LastBlockHeight() + 1, + Time: time.Now(), + }) + + for _, checksum := range checksums { + resp, err := destWasmClientApp.WasmClientKeeper.Code(ctx, &types.QueryCodeRequest{Checksum: hex.EncodeToString(checksum)}) + suite.Require().NoError(err) + + checksum, err := types.CreateChecksum(resp.Data) + suite.Require().NoError(err) + + allDestAppChecksumsInWasmVMStore = append(allDestAppChecksumsInWasmVMStore, checksum...) + } + + suite.Require().Equal(srcChecksumCodes, allDestAppChecksumsInWasmVMStore) + }) + } +} diff --git a/modules/light-clients/08-wasm/light_client_module.go b/modules/light-clients/08-wasm/light_client_module.go new file mode 100644 index 0000000..5cf6180 --- /dev/null +++ b/modules/light-clients/08-wasm/light_client_module.go @@ -0,0 +1,483 @@ +package wasm + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + internaltypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/internal/types" + wasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/keeper" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypesv2 "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types/v2" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var _ exported.LightClientModule = (*LightClientModule)(nil) + +// LightClientModule implements the core IBC api.LightClientModule interface. +type LightClientModule struct { + keeper wasmkeeper.Keeper + storeProvider clienttypes.StoreProvider +} + +// NewLightClientModule creates and returns a new 08-wasm LightClientModule. +func NewLightClientModule(keeper wasmkeeper.Keeper, storeProvider clienttypes.StoreProvider) LightClientModule { + return LightClientModule{ + keeper: keeper, + storeProvider: storeProvider, + } +} + +// Initialize unmarshals the provided client and consensus states and performs basic validation. It sets the client +// state and consensus state in the client store. +// It also initializes the wasm contract for the client. +func (l LightClientModule) Initialize(ctx sdk.Context, clientID string, clientStateBz, consensusStateBz []byte) error { + var clientState types.ClientState + if err := l.keeper.Codec().Unmarshal(clientStateBz, &clientState); err != nil { + return err + } + + if err := clientState.Validate(); err != nil { + return err + } + + var consensusState types.ConsensusState + if err := l.keeper.Codec().Unmarshal(consensusStateBz, &consensusState); err != nil { + return err + } + + if err := consensusState.ValidateBasic(); err != nil { + return err + } + + clientStore := l.storeProvider.ClientStore(ctx, clientID) + + // Do not allow initialization of a client with a checksum that hasn't been previously stored via storeWasmCode. + if !l.keeper.HasChecksum(ctx, clientState.Checksum) { + return errorsmod.Wrapf(types.ErrInvalidChecksum, "checksum (%s) has not been previously stored", hex.EncodeToString(clientState.Checksum)) + } + + payload := types.InstantiateMessage{ + ClientState: clientState.Data, + ConsensusState: consensusState.Data, + Checksum: clientState.Checksum, + } + + return l.keeper.WasmInstantiate(ctx, clientID, clientStore, &clientState, payload) +} + +// VerifyClientMessage obtains the client state associated with the client identifier, it then must verify the ClientMessage. +// A ClientMessage could be a Header, Misbehaviour, or batch update. +// It must handle each type of ClientMessage appropriately. Calls to CheckForMisbehaviour, UpdateState, and UpdateStateOnMisbehaviour +// will assume that the content of the ClientMessage has been verified and can be trusted. An error should be returned +// if the ClientMessage fails to verify. +func (l LightClientModule) VerifyClientMessage(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) error { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + cdc := l.keeper.Codec() + + clientState, found := types.GetClientState(clientStore, cdc) + if !found { + return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) + } + + clientMessage, ok := clientMsg.(*types.ClientMessage) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected type: %T, got: %T", &types.ClientMessage{}, clientMsg) + } + + payload := types.QueryMsg{ + VerifyClientMessage: &types.VerifyClientMessageMsg{ClientMessage: clientMessage.Data}, + } + _, err := l.keeper.WasmQuery(ctx, clientID, clientStore, clientState, payload) + return err +} + +// CheckForMisbehaviour obtains the client state associated with the client identifier, it detects misbehaviour in a submitted Header +// message and verifies the correctness of a submitted Misbehaviour ClientMessage. +func (l LightClientModule) CheckForMisbehaviour(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) bool { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + cdc := l.keeper.Codec() + + clientState, found := types.GetClientState(clientStore, cdc) + if !found { + panic(errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID)) + } + + clientMessage, ok := clientMsg.(*types.ClientMessage) + if !ok { + return false + } + + payload := types.QueryMsg{ + CheckForMisbehaviour: &types.CheckForMisbehaviourMsg{ClientMessage: clientMessage.Data}, + } + + res, err := l.keeper.WasmQuery(ctx, clientID, clientStore, clientState, payload) + if err != nil { + return false + } + + var result types.CheckForMisbehaviourResult + if err := json.Unmarshal(res, &result); err != nil { + return false + } + + return result.FoundMisbehaviour +} + +// UpdateStateOnMisbehaviour obtains the client state associated with the client identifier performs appropriate state changes on +// a client state given that misbehaviour has been detected and verified. +// Client state is updated in the store by the contract. +func (l LightClientModule) UpdateStateOnMisbehaviour(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + cdc := l.keeper.Codec() + + clientState, found := types.GetClientState(clientStore, cdc) + if !found { + panic(errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID)) + } + + clientMessage, ok := clientMsg.(*types.ClientMessage) + if !ok { + panic(fmt.Errorf("expected type %T, got %T", &types.ClientMessage{}, clientMsg)) + } + + payload := types.SudoMsg{ + UpdateStateOnMisbehaviour: &types.UpdateStateOnMisbehaviourMsg{ClientMessage: clientMessage.Data}, + } + + _, err := l.keeper.WasmSudo(ctx, clientID, clientStore, clientState, payload) + if err != nil { + panic(err) + } +} + +// UpdateState obtains the client state associated with the client identifier and calls into the appropriate +// contract endpoint. Client state and new consensus states are updated in the store by the contract. +func (l LightClientModule) UpdateState(ctx sdk.Context, clientID string, clientMsg exported.ClientMessage) []exported.Height { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + cdc := l.keeper.Codec() + + clientState, found := types.GetClientState(clientStore, cdc) + if !found { + panic(errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID)) + } + + clientMessage, ok := clientMsg.(*types.ClientMessage) + if !ok { + panic(fmt.Errorf("expected type %T, got %T", &types.ClientMessage{}, clientMsg)) + } + + payload := types.SudoMsg{ + UpdateState: &types.UpdateStateMsg{ClientMessage: clientMessage.Data}, + } + + res, err := l.keeper.WasmSudo(ctx, clientID, clientStore, clientState, payload) + if err != nil { + panic(err) + } + + var result types.UpdateStateResult + if err := json.Unmarshal(res, &result); err != nil { + panic(errorsmod.Wrap(types.ErrWasmInvalidResponseData, err.Error())) + } + + heights := make([]exported.Height, 0, len(result.Heights)) + for _, height := range result.Heights { + heights = append(heights, height) + } + + return heights +} + +// VerifyMembership obtains the client state associated with the client identifier and calls into the appropriate contract endpoint. +// VerifyMembership is a generic proof verification method which verifies a proof of the existence of a value at a given CommitmentPath at the specified height. +// The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). +// If a zero proof height is passed in, it will fail to retrieve the associated consensus state. +func (l LightClientModule) VerifyMembership( + ctx sdk.Context, + clientID string, + height exported.Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path exported.Path, + value []byte, +) error { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + cdc := l.keeper.Codec() + + clientState, found := types.GetClientState(clientStore, cdc) + if !found { + return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) + } + + proofHeight, ok := height.(clienttypes.Height) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", clienttypes.Height{}, height) + } + + if clientState.LatestHeight.LT(height) { + return errorsmod.Wrapf( + ibcerrors.ErrInvalidHeight, + "client state height < proof height (%d < %d), please ensure the client has been updated", clientState.LatestHeight, height, + ) + } + + merklePath, ok := path.(commitmenttypesv2.MerklePath) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", commitmenttypesv2.MerklePath{}, path) + } + + payload := types.SudoMsg{ + VerifyMembership: &types.VerifyMembershipMsg{ + Height: proofHeight, + DelayTimePeriod: delayTimePeriod, + DelayBlockPeriod: delayBlockPeriod, + Proof: proof, + Path: merklePath, + Value: value, + }, + } + + _, err := l.keeper.WasmSudo(ctx, clientID, clientStore, clientState, payload) + return err +} + +// VerifyNonMembership obtains the client state associated with the client identifier and calls into the appropriate contract endpoint. +// VerifyNonMembership is a generic proof verification method which verifies the absence of a given CommitmentPath at a specified height. +// The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). +// If a zero proof height is passed in, it will fail to retrieve the associated consensus state. +func (l LightClientModule) VerifyNonMembership( + ctx sdk.Context, + clientID string, + height exported.Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path exported.Path, +) error { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + cdc := l.keeper.Codec() + + clientState, found := types.GetClientState(clientStore, cdc) + if !found { + return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) + } + + proofHeight, ok := height.(clienttypes.Height) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", clienttypes.Height{}, height) + } + + if clientState.LatestHeight.LT(height) { + return errorsmod.Wrapf( + ibcerrors.ErrInvalidHeight, + "client state height < proof height (%d < %d), please ensure the client has been updated", clientState.LatestHeight, height, + ) + } + + merklePath, ok := path.(commitmenttypesv2.MerklePath) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", commitmenttypesv2.MerklePath{}, path) + } + + payload := types.SudoMsg{ + VerifyNonMembership: &types.VerifyNonMembershipMsg{ + Height: proofHeight, + DelayTimePeriod: delayTimePeriod, + DelayBlockPeriod: delayBlockPeriod, + Proof: proof, + Path: merklePath, + }, + } + + _, err := l.keeper.WasmSudo(ctx, clientID, clientStore, clientState, payload) + return err +} + +// Status obtains the client state associated with the client identifier and calls into the appropriate contract endpoint. +// It returns the status of the wasm client. +// The client may be: +// - Active: frozen height is zero and client is not expired +// - Frozen: frozen height is not zero +// - Expired: the latest consensus state timestamp + trusting period <= current time +// - Unauthorized: the client type is not registered as an allowed client type +// +// A frozen client will become expired, so the Frozen status +// has higher precedence. +func (l LightClientModule) Status(ctx sdk.Context, clientID string) exported.Status { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + cdc := l.keeper.Codec() + + clientState, found := types.GetClientState(clientStore, cdc) + if !found { + return exported.Unknown + } + + // Return unauthorized if the checksum hasn't been previously stored via storeWasmCode. + if !l.keeper.HasChecksum(ctx, clientState.Checksum) { + return exported.Unauthorized + } + + payload := types.QueryMsg{Status: &types.StatusMsg{}} + res, err := l.keeper.WasmQuery(ctx, clientID, clientStore, clientState, payload) + if err != nil { + return exported.Unknown + } + + var result types.StatusResult + if err := json.Unmarshal(res, &result); err != nil { + return exported.Unknown + } + + return exported.Status(result.Status) +} + +// LatestHeight returns the latest height for the client state for the given client identifier. +// If no client is present for the provided client identifier a zero value height is returned. +func (l LightClientModule) LatestHeight(ctx sdk.Context, clientID string) exported.Height { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + cdc := l.keeper.Codec() + + clientState, found := types.GetClientState(clientStore, cdc) + if !found { + return clienttypes.ZeroHeight() + } + + return clientState.LatestHeight +} + +// TimestampAtHeight obtains the client state associated with the client identifier and calls into the appropriate contract endpoint. +// It returns the timestamp in nanoseconds of the consensus state at the given height. +func (l LightClientModule) TimestampAtHeight(ctx sdk.Context, clientID string, height exported.Height) (uint64, error) { + clientStore := l.storeProvider.ClientStore(ctx, clientID) + cdc := l.keeper.Codec() + + clientState, found := types.GetClientState(clientStore, cdc) + if !found { + return 0, errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) + } + + timestampHeight, ok := height.(clienttypes.Height) + if !ok { + return 0, errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", clienttypes.Height{}, height) + } + + payload := types.QueryMsg{ + TimestampAtHeight: &types.TimestampAtHeightMsg{ + Height: timestampHeight, + }, + } + + res, err := l.keeper.WasmQuery(ctx, clientID, clientStore, clientState, payload) + if err != nil { + return 0, errorsmod.Wrapf(err, "height (%s)", height) + } + + var result types.TimestampAtHeightResult + if err := json.Unmarshal(res, &result); err != nil { + return 0, errorsmod.Wrapf(types.ErrWasmInvalidResponseData, "failed to unmarshal result of wasm query: %v", err) + } + + return result.Timestamp, nil +} + +// RecoverClient asserts that the substitute client is a wasm client. It obtains the client state associated with the +// subject client and calls into the appropriate contract endpoint. +// It will verify that a substitute client state is valid and update the subject client state. +// Note that this method is used only for recovery and will not allow changes to the checksum. +func (l LightClientModule) RecoverClient(ctx sdk.Context, clientID, substituteClientID string) error { + substituteClientType, _, err := clienttypes.ParseClientIdentifier(substituteClientID) + if err != nil { + return err + } + + if substituteClientType != types.Wasm { + return errorsmod.Wrapf(clienttypes.ErrInvalidClientType, "expected: %s, got: %s", types.Wasm, substituteClientType) + } + + cdc := l.keeper.Codec() + + subjectClientStore := l.storeProvider.ClientStore(ctx, clientID) + subjectClientState, found := types.GetClientState(subjectClientStore, cdc) + if !found { + return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) + } + + substituteClientStore := l.storeProvider.ClientStore(ctx, substituteClientID) + substituteClientState, found := types.GetClientState(substituteClientStore, cdc) + if !found { + return errorsmod.Wrap(clienttypes.ErrClientNotFound, substituteClientID) + } + + // check that checksums of subject client state and substitute client state match + // changing the checksum is only allowed through the migrate contract RPC endpoint + if !bytes.Equal(subjectClientState.Checksum, substituteClientState.Checksum) { + return errorsmod.Wrapf(clienttypes.ErrInvalidClient, "expected checksums to be equal: expected %s, got %s", hex.EncodeToString(subjectClientState.Checksum), hex.EncodeToString(substituteClientState.Checksum)) + } + + store := internaltypes.NewClientRecoveryStore(subjectClientStore, substituteClientStore) + + payload := types.SudoMsg{ + MigrateClientStore: &types.MigrateClientStoreMsg{}, + } + + _, err = l.keeper.WasmSudo(ctx, clientID, store, subjectClientState, payload) + return err +} + +// VerifyUpgradeAndUpdateState obtains the client state associated with the client identifier and calls into the appropriate contract endpoint. +// The new client and consensus states will be unmarshaled and an error is returned if the new client state is not at a height greater +// than the existing client. On a successful verification, it expects the contract to update the new client state, consensus state, and any other client metadata. +func (l LightClientModule) VerifyUpgradeAndUpdateState( + ctx sdk.Context, + clientID string, + newClient []byte, + newConsState []byte, + upgradeClientProof, + upgradeConsensusStateProof []byte, +) error { + cdc := l.keeper.Codec() + + var newClientState types.ClientState + if err := cdc.Unmarshal(newClient, &newClientState); err != nil { + return errorsmod.Wrap(clienttypes.ErrInvalidClient, err.Error()) + } + + var newConsensusState types.ConsensusState + if err := cdc.Unmarshal(newConsState, &newConsensusState); err != nil { + return errorsmod.Wrap(clienttypes.ErrInvalidConsensus, err.Error()) + } + + clientStore := l.storeProvider.ClientStore(ctx, clientID) + clientState, found := types.GetClientState(clientStore, cdc) + if !found { + return errorsmod.Wrap(clienttypes.ErrClientNotFound, clientID) + } + + // last height of current counterparty chain must be client's latest height + lastHeight := clientState.LatestHeight + if !newClientState.LatestHeight.GT(lastHeight) { + return errorsmod.Wrapf(ibcerrors.ErrInvalidHeight, "upgraded client height %s must be at greater than current client height %s", newClientState.LatestHeight, lastHeight) + } + + payload := types.SudoMsg{ + VerifyUpgradeAndUpdateState: &types.VerifyUpgradeAndUpdateStateMsg{ + UpgradeClientState: newClientState.Data, + UpgradeConsensusState: newConsensusState.Data, + ProofUpgradeClient: upgradeClientProof, + ProofUpgradeConsensusState: upgradeConsensusStateProof, + }, + } + + _, err := l.keeper.WasmSudo(ctx, clientID, clientStore, clientState, payload) + return err +} diff --git a/modules/light-clients/08-wasm/light_client_module_test.go b/modules/light-clients/08-wasm/light_client_module_test.go new file mode 100644 index 0000000..9a3d23b --- /dev/null +++ b/modules/light-clients/08-wasm/light_client_module_test.go @@ -0,0 +1,1582 @@ +package wasm_test + +import ( + "encoding/json" + "errors" + "fmt" + "time" + + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" + + errorsmod "cosmossdk.io/errors" + + internaltypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/internal/types" + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + ibcmock "github.com/cosmos/ibc-go/v10/testing/mock" +) + +const ( + tmClientID = "07-tendermint-0" + wasmClientID = "08-wasm-0" + // Used for checks where look ups for valid client id should fail. + unusedWasmClientID = "08-wasm-100" +) + +func (suite *WasmTestSuite) TestStatus() { + var clientID string + + testCases := []struct { + name string + malleate func() + expStatus exported.Status + }{ + { + "client is active", + func() {}, + exported.Active, + }, + { + "client is frozen", + func() { + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + resp, err := json.Marshal(types.StatusResult{Status: exported.Frozen.String()}) + suite.Require().NoError(err) + return &wasmvmtypes.QueryResult{Ok: resp}, wasmtesting.DefaultGasUsed, nil + }) + }, + exported.Frozen, + }, + { + "client status is expired", + func() { + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + resp, err := json.Marshal(types.StatusResult{Status: exported.Expired.String()}) + suite.Require().NoError(err) + return &wasmvmtypes.QueryResult{Ok: resp}, wasmtesting.DefaultGasUsed, nil + }) + }, + exported.Expired, + }, + { + "client status is unknown: vm returns an error", + func() { + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + return nil, 0, wasmtesting.ErrMockContract + }) + }, + exported.Unknown, + }, + { + "client status is unauthorized: checksum is not stored", + func() { + wasmClientKeeper := GetSimApp(suite.chainA).WasmClientKeeper + err := wasmClientKeeper.GetChecksums().Remove(suite.chainA.GetContext(), suite.checksum) + suite.Require().NoError(err) + }, + exported.Unauthorized, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedWasmClientID + }, + exported.Unknown, + }, + { + "failure: response fails to unmarshal", + func() { + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + return &wasmvmtypes.QueryResult{Ok: []byte("invalid json")}, wasmtesting.DefaultGasUsed, nil + }) + }, + exported.Unknown, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + clientID = endpoint.ClientID + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + tc.malleate() + + status := lightClientModule.Status(suite.chainA.GetContext(), clientID) + suite.Require().Equal(tc.expStatus, status) + }) + } +} + +func (suite *WasmTestSuite) TestTimestampAtHeight() { + var ( + clientID string + height exported.Height + ) + expectedTimestamp := uint64(time.Now().UnixNano()) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() { + suite.mockVM.RegisterQueryCallback(types.TimestampAtHeightMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, queryMsg []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + var payload types.QueryMsg + err := json.Unmarshal(queryMsg, &payload) + suite.Require().NoError(err) + + suite.Require().NotNil(payload.TimestampAtHeight) + suite.Require().Nil(payload.CheckForMisbehaviour) + suite.Require().Nil(payload.Status) + suite.Require().Nil(payload.VerifyClientMessage) + + resp, err := json.Marshal(types.TimestampAtHeightResult{Timestamp: expectedTimestamp}) + suite.Require().NoError(err) + + return &wasmvmtypes.QueryResult{Ok: resp}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + }, + { + "failure: vm returns error", + func() { + suite.mockVM.RegisterQueryCallback(types.TimestampAtHeightMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + return nil, 0, wasmtesting.ErrMockVM + }) + }, + types.ErrVMError, + }, + { + "failure: contract returns error", + func() { + suite.mockVM.RegisterQueryCallback(types.TimestampAtHeightMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + return &wasmvmtypes.QueryResult{Err: wasmtesting.ErrMockContract.Error()}, 0, nil + }) + }, + types.ErrWasmContractCallFailed, + }, + { + "failure: error: invalid height", + func() { + height = ibcmock.Height{} + }, + ibcerrors.ErrInvalidType, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedWasmClientID + }, + clienttypes.ErrClientNotFound, + }, + { + "failure: response fails to unmarshal", + func() { + suite.mockVM.RegisterQueryCallback(types.TimestampAtHeightMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + return &wasmvmtypes.QueryResult{Ok: []byte("invalid json")}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmInvalidResponseData, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + clientID = endpoint.ClientID + + clientState, ok := endpoint.GetClientState().(*types.ClientState) + suite.Require().True(ok) + + height = clientState.LatestHeight + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + tc.malleate() + + timestamp, err := lightClientModule.TimestampAtHeight(suite.chainA.GetContext(), clientID, height) + + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().Equal(expectedTimestamp, timestamp) + } else { + suite.Require().ErrorIs(err, tc.expErr) + suite.Require().Equal(uint64(0), timestamp) + } + }) + } +} + +func (suite *WasmTestSuite) TestInitialize() { + var ( + consensusState exported.ConsensusState + clientState exported.ClientState + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success: new mock client", + func() {}, + nil, + }, + { + "success: validate contract address", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, env wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + var payload types.InstantiateMessage + err := json.Unmarshal(initMsg, &payload) + suite.Require().NoError(err) + + suite.Require().Equal(env.Contract.Address, wasmClientID) + + wrappedClientState, ok := clienttypes.MustUnmarshalClientState(suite.chainA.App.AppCodec(), payload.ClientState).(*ibctm.ClientState) + suite.Require().True(ok) + + clientState := types.NewClientState(payload.ClientState, payload.Checksum, wrappedClientState.LatestHeight) + clientStateBz := clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), clientState) + store.Set(host.ClientStateKey(), clientStateBz) + + consensusState := types.NewConsensusState(payload.ConsensusState) + consensusStateBz := clienttypes.MustMarshalConsensusState(suite.chainA.App.AppCodec(), consensusState) + store.Set(host.ConsensusStateKey(clientState.LatestHeight), consensusStateBz) + + resp, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, 0, nil + } + }, + nil, + }, + { + "failure: cannot unmarshal client state", + func() { + clientState = &solomachine.ClientState{Sequence: 20} + }, + errors.New("proto: wrong wireType = 0 for field Data"), + }, + { + "failure: client state is invalid", + func() { + clientState = &types.ClientState{} + }, + types.ErrInvalidData, + }, + { + "failure: invalid consensus state", + func() { + // set upgraded consensus state to solomachine consensus state + consensusState = &solomachine.ConsensusState{} + }, + types.ErrInvalidData, + }, + { + "failure: checksum has not been stored.", + func() { + clientState = types.NewClientState([]byte{1}, []byte("unknown"), clienttypes.NewHeight(0, 1)) + }, + types.ErrInvalidChecksum, + }, + { + "failure: vm returns error", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return nil, 0, wasmtesting.ErrMockVM + } + }, + types.ErrVMError, + }, + { + "failure: contract returns error", + func() { + suite.mockVM.InstantiateFn = func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ wasmvmtypes.MessageInfo, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return &wasmvmtypes.ContractResult{Err: wasmtesting.ErrMockContract.Error()}, 0, nil + } + }, + types.ErrWasmContractCallFailed, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + wrappedClientStateBz := clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), wasmtesting.MockTendermitClientState) + wrappedClientConsensusStateBz := clienttypes.MustMarshalConsensusState(suite.chainA.App.AppCodec(), wasmtesting.MockTendermintClientConsensusState) + clientState = types.NewClientState(wrappedClientStateBz, suite.checksum, wasmtesting.MockTendermitClientState.LatestHeight) + consensusState = types.NewConsensusState(wrappedClientConsensusStateBz) + + clientID := suite.chainA.App.GetIBCKeeper().ClientKeeper.GenerateClientIdentifier(suite.chainA.GetContext(), clientState.ClientType()) + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + tc.malleate() + + // Marshal client state and consensus state: + clientStateBz := suite.chainA.Codec.MustMarshal(clientState) + consensusStateBz := suite.chainA.Codec.MustMarshal(consensusState) + + err = lightClientModule.Initialize(suite.chainA.GetContext(), clientID, clientStateBz, consensusStateBz) + + if tc.expError == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorContains(err, tc.expError.Error()) + } + }) + } +} + +func (suite *WasmTestSuite) TestVerifyMembership() { + var ( + clientState *types.ClientState + expClientStateBz []byte + path exported.Path + proof []byte + proofHeight exported.Height + value []byte + clientID string + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + expClientStateBz = clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), clientState) + suite.mockVM.RegisterSudoCallback(types.VerifyMembershipMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, _ wasmvm.KVStore, + _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction, + ) (*wasmvmtypes.ContractResult, uint64, error) { + var payload types.SudoMsg + err := json.Unmarshal(sudoMsg, &payload) + suite.Require().NoError(err) + + bz, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: bz}}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + }, + { + "success: with update client state", + func() { + suite.mockVM.RegisterSudoCallback(types.VerifyMembershipMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, + _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction, + ) (*wasmvmtypes.ContractResult, uint64, error) { + var payload types.SudoMsg + err := json.Unmarshal(sudoMsg, &payload) + suite.Require().NoError(err) + + suite.Require().NotNil(payload.VerifyMembership) + suite.Require().Nil(payload.UpdateState) + suite.Require().Nil(payload.UpdateStateOnMisbehaviour) + suite.Require().Nil(payload.VerifyNonMembership) + suite.Require().Nil(payload.VerifyUpgradeAndUpdateState) + suite.Require().Equal(proofHeight, payload.VerifyMembership.Height) + suite.Require().Equal(proof, payload.VerifyMembership.Proof) + suite.Require().Equal(path, payload.VerifyMembership.Path) + suite.Require().Equal(value, payload.VerifyMembership.Value) + + bz, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + expClientStateBz = wasmtesting.CreateMockClientStateBz(suite.chainA.Codec, suite.checksum) + store.Set(host.ClientStateKey(), expClientStateBz) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: bz}}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedWasmClientID + }, + clienttypes.ErrClientNotFound, + }, + { + "failure: contract returns invalid proof error", + func() { + proof = wasmtesting.MockInvalidProofBz + + suite.mockVM.RegisterSudoCallback(types.VerifyMembershipMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, + _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction, + ) (*wasmvmtypes.ContractResult, uint64, error) { + return &wasmvmtypes.ContractResult{Err: commitmenttypes.ErrInvalidProof.Error()}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmContractCallFailed, + }, + { + "failure: proof height greater than client state latest height", + func() { + proofHeight = clienttypes.NewHeight(1, 100) + }, + ibcerrors.ErrInvalidHeight, + }, + { + "failure: invalid path argument", + func() { + path = ibcmock.KeyPath{} + }, + ibcerrors.ErrInvalidType, + }, + { + "failure: proof height is invalid type", + func() { + proofHeight = ibcmock.Height{} + }, + ibcerrors.ErrInvalidType, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + var ok bool + suite.SetupWasmWithMockVM() + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + clientID = endpoint.ClientID + + path = commitmenttypes.NewMerklePath([]byte("/ibc/key/path")) + proof = wasmtesting.MockValidProofBz + proofHeight = clienttypes.NewHeight(0, 1) + value = []byte("value") + + clientState, ok = endpoint.GetClientState().(*types.ClientState) + suite.Require().True(ok) + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + tc.malleate() + + err = lightClientModule.VerifyMembership(suite.chainA.GetContext(), clientID, proofHeight, 0, 0, proof, path, value) + + if tc.expError == nil { + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), clientID) + + suite.Require().NoError(err) + suite.Require().Equal(expClientStateBz, clientStore.Get(host.ClientStateKey())) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +func (suite *WasmTestSuite) TestVerifyNonMembership() { + var ( + clientState *types.ClientState + expClientStateBz []byte + path exported.Path + proof []byte + proofHeight exported.Height + clientID string + ) + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + expClientStateBz = clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), clientState) + suite.mockVM.RegisterSudoCallback(types.VerifyNonMembershipMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, _ wasmvm.KVStore, + _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction, + ) (*wasmvmtypes.ContractResult, uint64, error) { + var payload types.SudoMsg + err := json.Unmarshal(sudoMsg, &payload) + suite.Require().NoError(err) + + suite.Require().NotNil(payload.VerifyNonMembership) + suite.Require().Nil(payload.UpdateState) + suite.Require().Nil(payload.UpdateStateOnMisbehaviour) + suite.Require().Nil(payload.VerifyMembership) + suite.Require().Nil(payload.VerifyUpgradeAndUpdateState) + suite.Require().Equal(proofHeight, payload.VerifyNonMembership.Height) + suite.Require().Equal(proof, payload.VerifyNonMembership.Proof) + suite.Require().Equal(path, payload.VerifyNonMembership.Path) + + bz, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: bz}}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + }, + { + "success: with update client state", + func() { + suite.mockVM.RegisterSudoCallback(types.VerifyNonMembershipMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, + _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction, + ) (*wasmvmtypes.ContractResult, uint64, error) { + var payload types.SudoMsg + err := json.Unmarshal(sudoMsg, &payload) + suite.Require().NoError(err) + + suite.Require().NotNil(payload.VerifyNonMembership) + suite.Require().Nil(payload.UpdateState) + suite.Require().Nil(payload.UpdateStateOnMisbehaviour) + suite.Require().Nil(payload.VerifyMembership) + suite.Require().Nil(payload.VerifyUpgradeAndUpdateState) + suite.Require().Equal(proofHeight, payload.VerifyNonMembership.Height) + suite.Require().Equal(proof, payload.VerifyNonMembership.Proof) + suite.Require().Equal(path, payload.VerifyNonMembership.Path) + + bz, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + expClientStateBz = wasmtesting.CreateMockClientStateBz(suite.chainA.Codec, suite.checksum) + store.Set(host.ClientStateKey(), expClientStateBz) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: bz}}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedWasmClientID + }, + clienttypes.ErrClientNotFound, + }, + { + "failure: wasm vm returns error", + func() { + proof = wasmtesting.MockInvalidProofBz + + suite.mockVM.RegisterSudoCallback(types.VerifyNonMembershipMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, + _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction, + ) (*wasmvmtypes.ContractResult, uint64, error) { + return nil, wasmtesting.DefaultGasUsed, wasmtesting.ErrMockVM + }) + }, + types.ErrVMError, + }, + { + "failure: contract returns invalid proof error", + func() { + proof = wasmtesting.MockInvalidProofBz + + suite.mockVM.RegisterSudoCallback(types.VerifyNonMembershipMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, + _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction, + ) (*wasmvmtypes.ContractResult, uint64, error) { + return &wasmvmtypes.ContractResult{Err: commitmenttypes.ErrInvalidProof.Error()}, wasmtesting.DefaultGasUsed, nil + }) + }, + types.ErrWasmContractCallFailed, + }, + { + "failure: proof height greater than client state latest height", + func() { + proofHeight = clienttypes.NewHeight(1, 100) + }, + ibcerrors.ErrInvalidHeight, + }, + { + "failure: invalid path argument", + func() { + path = ibcmock.KeyPath{} + }, + ibcerrors.ErrInvalidType, + }, + { + "failure: proof height is invalid type", + func() { + proofHeight = ibcmock.Height{} + }, + ibcerrors.ErrInvalidType, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + var ok bool + suite.SetupWasmWithMockVM() + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + clientID = endpoint.ClientID + + path = commitmenttypes.NewMerklePath([]byte("/ibc/key/path")) + proof = wasmtesting.MockInvalidProofBz + proofHeight = clienttypes.NewHeight(0, 1) + + clientState, ok = endpoint.GetClientState().(*types.ClientState) + suite.Require().True(ok) + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + tc.malleate() + + err = lightClientModule.VerifyNonMembership(suite.chainA.GetContext(), clientID, proofHeight, 0, 0, proof, path) + + if tc.expError == nil { + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), clientID) + + suite.Require().NoError(err) + suite.Require().Equal(expClientStateBz, clientStore.Get(host.ClientStateKey())) + } else { + suite.Require().ErrorIs(err, tc.expError) + } + }) + } +} + +func (suite *WasmTestSuite) TestVerifyClientMessage() { + var ( + clientMsg exported.ClientMessage + clientID string + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success: valid misbehaviour", + func() { + suite.mockVM.RegisterQueryCallback(types.VerifyClientMessageMsg{}, func(_ wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + var msg *types.QueryMsg + + err := json.Unmarshal(queryMsg, &msg) + suite.Require().NoError(err) + + suite.Require().NotNil(msg.VerifyClientMessage) + suite.Require().NotNil(msg.VerifyClientMessage.ClientMessage) + suite.Require().Nil(msg.Status) + suite.Require().Nil(msg.CheckForMisbehaviour) + suite.Require().Nil(msg.TimestampAtHeight) + + suite.Require().Equal(env.Contract.Address, wasmClientID) + + resp, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.QueryResult{Ok: resp}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedWasmClientID + }, + clienttypes.ErrClientNotFound, + }, + { + "failure: invalid client message", + func() { + clientMsg = &ibctm.Header{} + + suite.mockVM.RegisterQueryCallback(types.VerifyClientMessageMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, queryMsg []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + resp, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.QueryResult{Ok: resp}, wasmtesting.DefaultGasUsed, nil + }) + }, + ibcerrors.ErrInvalidType, + }, + { + "failure: error return from vm", + func() { + suite.mockVM.RegisterQueryCallback(types.VerifyClientMessageMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, queryMsg []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + return nil, 0, wasmtesting.ErrMockVM + }) + }, + types.ErrVMError, + }, + { + "failure: error return from contract", + func() { + suite.mockVM.RegisterQueryCallback(types.VerifyClientMessageMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, queryMsg []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + return &wasmvmtypes.QueryResult{Err: wasmtesting.ErrMockContract.Error()}, 0, nil + }) + }, + types.ErrWasmContractCallFailed, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + // reset suite to create fresh application state + suite.SetupWasmWithMockVM() + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + clientID = endpoint.ClientID + + clientMsg = &types.ClientMessage{ + Data: clienttypes.MustMarshalClientMessage(suite.chainA.App.AppCodec(), wasmtesting.MockTendermintClientHeader), + } + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + tc.malleate() + + err = lightClientModule.VerifyClientMessage(suite.chainA.GetContext(), clientID, clientMsg) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *WasmTestSuite) TestVerifyUpgradeAndUpdateState() { + var ( + upgradedClient exported.ClientState + upgradedConsState exported.ConsensusState + upgradedClientProof []byte + upgradedConsensusStateProof []byte + clientID string + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success: successful upgrade", + func() { + suite.mockVM.RegisterSudoCallback(types.VerifyUpgradeAndUpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + var payload types.SudoMsg + + err := json.Unmarshal(sudoMsg, &payload) + suite.Require().NoError(err) + + expectedUpgradedClient, ok := upgradedClient.(*types.ClientState) + suite.Require().True(ok) + expectedUpgradedConsensus, ok := upgradedConsState.(*types.ConsensusState) + suite.Require().True(ok) + + // verify payload values + suite.Require().Equal(expectedUpgradedClient.Data, payload.VerifyUpgradeAndUpdateState.UpgradeClientState) + suite.Require().Equal(expectedUpgradedConsensus.Data, payload.VerifyUpgradeAndUpdateState.UpgradeConsensusState) + suite.Require().Equal(upgradedClientProof, payload.VerifyUpgradeAndUpdateState.ProofUpgradeClient) + suite.Require().Equal(upgradedConsensusStateProof, payload.VerifyUpgradeAndUpdateState.ProofUpgradeConsensusState) + + // verify other Sudo fields are nil + suite.Require().Nil(payload.UpdateState) + suite.Require().Nil(payload.UpdateStateOnMisbehaviour) + suite.Require().Nil(payload.VerifyMembership) + suite.Require().Nil(payload.VerifyNonMembership) + + data, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + // set new client state and consensus state + wrappedUpgradedClient, ok := clienttypes.MustUnmarshalClientState(suite.chainA.App.AppCodec(), expectedUpgradedClient.Data).(*ibctm.ClientState) + suite.Require().True(ok) + store.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), upgradedClient)) + store.Set(host.ConsensusStateKey(wrappedUpgradedClient.LatestHeight), clienttypes.MustMarshalConsensusState(suite.chainA.App.AppCodec(), upgradedConsState)) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: data}}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + }, + { + "failure: invalid client state", + func() { + upgradedClient = &solomachine.ClientState{Sequence: 20} + }, + clienttypes.ErrInvalidClient, + }, + { + "failure: invalid height", + func() { + upgradedClient = &types.ClientState{LatestHeight: clienttypes.ZeroHeight()} + }, + ibcerrors.ErrInvalidHeight, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedWasmClientID + }, + clienttypes.ErrClientNotFound, + }, + /* NOTE(jim): This can't fail on unmarshalling, it appears. Any consensus type + we attempt to unmarshal just creates a Wasm ConsensusState that has a + Data field empty. + { + "failure: upgraded consensus state is not wasm consensus state", + func() { + // set upgraded consensus state to solomachine consensus state + upgradedConsState = &solomachine.ConsensusState{} + }, + clienttypes.ErrInvalidConsensus, + }, + */ + { + "failure: vm returns error", + func() { + suite.mockVM.RegisterSudoCallback(types.VerifyUpgradeAndUpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return nil, 0, wasmtesting.ErrMockVM + }) + }, + types.ErrVMError, + }, + { + "failure: contract returns error", + func() { + suite.mockVM.RegisterSudoCallback(types.VerifyUpgradeAndUpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return &wasmvmtypes.ContractResult{Err: wasmtesting.ErrMockContract.Error()}, 0, nil + }) + }, + types.ErrWasmContractCallFailed, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + // reset suite + suite.SetupWasmWithMockVM() + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + clientID = endpoint.ClientID + + clientState, ok := endpoint.GetClientState().(*types.ClientState) + suite.Require().True(ok) + + newLatestHeight := clienttypes.NewHeight(2, 10) + wrappedUpgradedClient := wasmtesting.CreateMockTendermintClientState(newLatestHeight) + wrappedUpgradedClientBz := clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), wrappedUpgradedClient) + upgradedClient = types.NewClientState(wrappedUpgradedClientBz, clientState.Checksum, newLatestHeight) + + wrappedUpgradedConsensus := ibctm.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte("new-hash")), []byte("new-nextValsHash")) + wrappedUpgradedConsensusBz := clienttypes.MustMarshalConsensusState(suite.chainA.App.AppCodec(), wrappedUpgradedConsensus) + upgradedConsState = types.NewConsensusState(wrappedUpgradedConsensusBz) + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + tc.malleate() + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), wasmClientID) + + upgradedClientProof = wasmtesting.MockUpgradedClientStateProofBz + upgradedConsensusStateProof = wasmtesting.MockUpgradedConsensusStateProofBz + + newClient := suite.chainA.Codec.MustMarshal(upgradedClient) + newConsensusState := suite.chainA.Codec.MustMarshal(upgradedConsState) + + err = lightClientModule.VerifyUpgradeAndUpdateState( + suite.chainA.GetContext(), + clientID, + newClient, + newConsensusState, + upgradedClientProof, + upgradedConsensusStateProof, + ) + + if tc.expErr == nil { + suite.Require().NoError(err) + + // verify new client state and consensus state + clientStateBz := clientStore.Get(host.ClientStateKey()) + suite.Require().NotEmpty(clientStateBz) + suite.Require().Equal(upgradedClient, clienttypes.MustUnmarshalClientState(suite.chainA.Codec, clientStateBz)) + + consStateBz := clientStore.Get(host.ConsensusStateKey(upgradedClient.(*types.ClientState).LatestHeight)) + suite.Require().NotEmpty(consStateBz) + suite.Require().Equal(upgradedConsState, clienttypes.MustUnmarshalConsensusState(suite.chainA.Codec, consStateBz)) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *WasmTestSuite) TestCheckForMisbehaviour() { + var ( + clientMessage exported.ClientMessage + clientID string + ) + + testCases := []struct { + name string + malleate func() + foundMisbehaviour bool + expPanic error + }{ + { + "success: no misbehaviour", + func() { + suite.mockVM.RegisterQueryCallback(types.CheckForMisbehaviourMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + resp, err := json.Marshal(types.CheckForMisbehaviourResult{FoundMisbehaviour: false}) + suite.Require().NoError(err) + return &wasmvmtypes.QueryResult{Ok: resp}, wasmtesting.DefaultGasUsed, nil + }) + }, + false, + nil, + }, + { + "success: misbehaviour found", func() { + suite.mockVM.RegisterQueryCallback(types.CheckForMisbehaviourMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + resp, err := json.Marshal(types.CheckForMisbehaviourResult{FoundMisbehaviour: true}) + suite.Require().NoError(err) + return &wasmvmtypes.QueryResult{Ok: resp}, wasmtesting.DefaultGasUsed, nil + }) + }, + true, + nil, + }, + { + "success: contract error, resp cannot be marshalled", func() { + suite.mockVM.RegisterQueryCallback(types.CheckForMisbehaviourMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + resp := "cannot be unmarshalled" + return &wasmvmtypes.QueryResult{Ok: []byte(resp)}, wasmtesting.DefaultGasUsed, nil + }) + }, + false, + nil, + }, + { + "success: contract returns error", func() { + suite.mockVM.RegisterQueryCallback(types.CheckForMisbehaviourMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + return &wasmvmtypes.QueryResult{Err: wasmtesting.ErrMockContract.Error()}, wasmtesting.DefaultGasUsed, nil + }) + }, + false, + nil, + }, + { + "success: vm returns error, ", func() { + suite.mockVM.RegisterQueryCallback(types.CheckForMisbehaviourMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + return nil, 0, errors.New("invalid block ID") + }) + }, + false, + nil, + }, + { + "success: invalid client message", func() { + clientMessage = &ibctm.Header{} + // we will not register the callback here because this test case does not reach the VM + }, + false, + nil, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedWasmClientID + }, + false, // not applicable + fmt.Errorf("%s: %s", unusedWasmClientID, clienttypes.ErrClientNotFound), + }, + { + "failure: response fails to unmarshal", + func() { + suite.mockVM.RegisterQueryCallback(types.CheckForMisbehaviourMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + return &wasmvmtypes.QueryResult{Ok: []byte("invalid json")}, wasmtesting.DefaultGasUsed, nil + }) + }, + false, + nil, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + // reset suite to create fresh application state + suite.SetupWasmWithMockVM() + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + clientID = endpoint.ClientID + + clientMessage = &types.ClientMessage{ + Data: clienttypes.MustMarshalClientMessage(suite.chainA.App.AppCodec(), wasmtesting.MockTendermintClientMisbehaviour), + } + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + tc.malleate() + + var foundMisbehaviour bool + foundMisbehaviourFunc := func() { + foundMisbehaviour = lightClientModule.CheckForMisbehaviour(suite.chainA.GetContext(), clientID, clientMessage) + } + + if tc.expPanic == nil { + foundMisbehaviourFunc() + suite.Require().Equal(tc.foundMisbehaviour, foundMisbehaviour) + } else { + suite.Require().PanicsWithError(tc.expPanic.Error(), foundMisbehaviourFunc) + } + }) + } +} + +func (suite *WasmTestSuite) TestUpdateState() { + mockHeight := clienttypes.NewHeight(1, 50) + + var ( + clientMsg exported.ClientMessage + expectedClientStateBz []byte + clientID string + ) + + testCases := []struct { + name string + malleate func() + expPanic error + expHeights []exported.Height + }{ + { + "success: no update", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, env wasmvmtypes.Env, sudoMsg []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + var msg types.SudoMsg + err := json.Unmarshal(sudoMsg, &msg) + suite.Require().NoError(err) + + suite.Require().NotNil(msg.UpdateState) + suite.Require().NotNil(msg.UpdateState.ClientMessage) + suite.Require().Equal(msg.UpdateState.ClientMessage, clienttypes.MustMarshalClientMessage(suite.chainA.App.AppCodec(), wasmtesting.MockTendermintClientHeader)) + suite.Require().Nil(msg.VerifyMembership) + suite.Require().Nil(msg.VerifyNonMembership) + suite.Require().Nil(msg.UpdateStateOnMisbehaviour) + suite.Require().Nil(msg.VerifyUpgradeAndUpdateState) + + suite.Require().Equal(env.Contract.Address, wasmClientID) + + updateStateResp := types.UpdateStateResult{ + Heights: []clienttypes.Height{}, + } + + resp, err := json.Marshal(updateStateResp) + if err != nil { + return nil, 0, err + } + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + []exported.Height{}, + }, + { + "success: update client", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + var msg types.SudoMsg + err := json.Unmarshal(sudoMsg, &msg) + suite.Require().NoError(err) + + bz := store.Get(host.ClientStateKey()) + suite.Require().NotEmpty(bz) + clientState, ok := clienttypes.MustUnmarshalClientState(suite.chainA.Codec, bz).(*types.ClientState) + suite.Require().True(ok) + clientState.LatestHeight = mockHeight + expectedClientStateBz = clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), clientState) + store.Set(host.ClientStateKey(), expectedClientStateBz) + + updateStateResp := types.UpdateStateResult{ + Heights: []clienttypes.Height{mockHeight}, + } + + resp, err := json.Marshal(updateStateResp) + if err != nil { + return nil, 0, err + } + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + []exported.Height{mockHeight}, + }, + { + "failure: cannot find client state", + func() { + clientID = unusedWasmClientID + }, + fmt.Errorf("08-wasm-100: %s", clienttypes.ErrClientNotFound), + nil, + }, + { + "failure: invalid ClientMessage type", + func() { + // SudoCallback left nil because clientMsg is checked by 08-wasm before callbackFn is called. + clientMsg = &ibctm.Misbehaviour{} + }, + fmt.Errorf("expected type %T, got %T", (*types.ClientMessage)(nil), (*ibctm.Misbehaviour)(nil)), + nil, + }, + { + "failure: VM returns error", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return nil, 0, wasmtesting.ErrMockVM + }) + }, + errorsmod.Wrap(types.ErrVMError, wasmtesting.ErrMockVM.Error()), + nil, + }, + { + "failure: response fails to unmarshal", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: []byte("invalid json")}}, wasmtesting.DefaultGasUsed, nil + }) + }, + fmt.Errorf("invalid character 'i' looking for beginning of value: %s", types.ErrWasmInvalidResponseData), + nil, + }, + { + "failure: callbackFn returns error", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return &wasmvmtypes.ContractResult{Err: wasmtesting.ErrMockContract.Error()}, 0, nil + }) + }, + errorsmod.Wrap(types.ErrWasmContractCallFailed, wasmtesting.ErrMockContract.Error()), + nil, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() // reset + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + clientID = endpoint.ClientID + + expectedClientStateBz = nil + + clientMsg = &types.ClientMessage{ + Data: clienttypes.MustMarshalClientMessage(suite.chainA.App.AppCodec(), wasmtesting.MockTendermintClientHeader), + } + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + tc.malleate() + + var heights []exported.Height + updateState := func() { + heights = lightClientModule.UpdateState(suite.chainA.GetContext(), clientID, clientMsg) + } + + if tc.expPanic == nil { + updateState() + suite.Require().Equal(tc.expHeights, heights) + + if expectedClientStateBz != nil { + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + + clientStateBz := clientStore.Get(host.ClientStateKey()) + suite.Require().Equal(expectedClientStateBz, clientStateBz) + } + } else { + suite.Require().PanicsWithError(tc.expPanic.Error(), updateState) + } + }) + } +} + +func (suite *WasmTestSuite) TestUpdateStateOnMisbehaviour() { + mockHeight := clienttypes.NewHeight(1, 50) + + var ( + clientMsg exported.ClientMessage + expectedClientStateBz []byte + clientID string + ) + + testCases := []struct { + name string + malleate func() + panicErr error + updatedClientState []byte + }{ + { + "success: no update", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateOnMisbehaviourMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + var msg types.SudoMsg + + err := json.Unmarshal(sudoMsg, &msg) + suite.Require().NoError(err) + + suite.Require().NotNil(msg.UpdateStateOnMisbehaviour) + suite.Require().NotNil(msg.UpdateStateOnMisbehaviour.ClientMessage) + suite.Require().Nil(msg.UpdateState) + suite.Require().Nil(msg.UpdateState) + suite.Require().Nil(msg.VerifyMembership) + suite.Require().Nil(msg.VerifyNonMembership) + suite.Require().Nil(msg.VerifyUpgradeAndUpdateState) + + resp, err := json.Marshal(types.EmptyResult{}) + if err != nil { + return nil, 0, err + } + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + nil, + }, + { + "success: client state updated on valid misbehaviour", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateOnMisbehaviourMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + var msg types.SudoMsg + err := json.Unmarshal(sudoMsg, &msg) + suite.Require().NoError(err) + + // set new client state in store + bz := store.Get(host.ClientStateKey()) + suite.Require().NotEmpty(bz) + clientState, ok := clienttypes.MustUnmarshalClientState(suite.chainA.App.AppCodec(), bz).(*types.ClientState) + suite.Require().True(ok) + clientState.LatestHeight = mockHeight + expectedClientStateBz = clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), clientState) + store.Set(host.ClientStateKey(), expectedClientStateBz) + + resp, err := json.Marshal(types.EmptyResult{}) + if err != nil { + return nil, 0, err + } + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, wasmtesting.DefaultGasUsed, nil + }) + }, + nil, + clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), wasmtesting.CreateMockTendermintClientState(mockHeight)), + }, + { + "failure: cannot find client state", + func() { + clientID = unusedWasmClientID + }, + fmt.Errorf("%s: %s", unusedWasmClientID, clienttypes.ErrClientNotFound), + nil, + }, + { + "failure: invalid client message", + func() { + clientMsg = &ibctm.Header{} + // we will not register the callback here because this test case does not reach the VM + }, + fmt.Errorf("expected type %T, got %T", (*types.ClientMessage)(nil), (*ibctm.Header)(nil)), + nil, + }, + { + "failure: err return from vm", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateOnMisbehaviourMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return nil, 0, wasmtesting.ErrMockVM + }) + }, + errorsmod.Wrap(types.ErrVMError, wasmtesting.ErrMockVM.Error()), + nil, + }, + { + "failure: err return from contract", + func() { + suite.mockVM.RegisterSudoCallback(types.UpdateStateOnMisbehaviourMsg{}, func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return &wasmvmtypes.ContractResult{Err: wasmtesting.ErrMockContract.Error()}, 0, nil + }) + }, + errorsmod.Wrap(types.ErrWasmContractCallFailed, wasmtesting.ErrMockContract.Error()), + nil, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + // reset suite to create fresh application state + suite.SetupWasmWithMockVM() + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + clientID = endpoint.ClientID + + expectedClientStateBz = nil + + clientMsg = &types.ClientMessage{ + Data: clienttypes.MustMarshalClientMessage(suite.chainA.App.AppCodec(), wasmtesting.MockTendermintClientMisbehaviour), + } + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + tc.malleate() + + updateFunc := func() { + lightClientModule.UpdateStateOnMisbehaviour(suite.chainA.GetContext(), clientID, clientMsg) + } + + if tc.panicErr == nil { + updateFunc() + if expectedClientStateBz != nil { + store := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + suite.Require().Equal(expectedClientStateBz, store.Get(host.ClientStateKey())) + } + } else { + suite.Require().PanicsWithError(tc.panicErr.Error(), updateFunc) + } + }) + } +} + +func (suite *WasmTestSuite) TestRecoverClient() { + var ( + expectedClientStateBz []byte + subjectClientID, substituteClientID string + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success", + func() { + suite.mockVM.RegisterSudoCallback( + types.MigrateClientStoreMsg{}, + func(_ wasmvm.Checksum, _ wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + var payload types.SudoMsg + err := json.Unmarshal(sudoMsg, &payload) + suite.Require().NoError(err) + + suite.Require().NotNil(payload.MigrateClientStore) + suite.Require().Nil(payload.UpdateState) + suite.Require().Nil(payload.UpdateStateOnMisbehaviour) + suite.Require().Nil(payload.VerifyMembership) + suite.Require().Nil(payload.VerifyNonMembership) + suite.Require().Nil(payload.VerifyUpgradeAndUpdateState) + + bz, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + prefixedKey := internaltypes.SubjectPrefix + prefixedKey = append(prefixedKey, host.ClientStateKey()...) + expectedClientStateBz = wasmtesting.CreateMockClientStateBz(suite.chainA.Codec, suite.checksum) + store.Set(prefixedKey, expectedClientStateBz) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: bz}}, wasmtesting.DefaultGasUsed, nil + }, + ) + }, + nil, + }, + { + "failure: cannot parse malformed substitute client ID", + func() { + substituteClientID = ibctesting.InvalidID + }, + host.ErrInvalidID, + }, + { + "failure: substitute client ID does not contain 08-wasm prefix", + func() { + substituteClientID = tmClientID + }, + clienttypes.ErrInvalidClientType, + }, + { + "failure: cannot find subject client state", + func() { + subjectClientID = unusedWasmClientID + }, + clienttypes.ErrClientNotFound, + }, + { + "failure: cannot find substitute client state", + func() { + substituteClientID = unusedWasmClientID + }, + clienttypes.ErrClientNotFound, + }, + { + "failure: checksums do not match", + func() { + substituteClientState, found := GetSimApp(suite.chainA).IBCKeeper.ClientKeeper.GetClientState(suite.chainA.GetContext(), substituteClientID) + suite.Require().True(found) + + wasmSubstituteClientState, ok := substituteClientState.(*types.ClientState) + suite.Require().True(ok) + + wasmSubstituteClientState.Checksum = []byte("invalid") + GetSimApp(suite.chainA).IBCKeeper.ClientKeeper.SetClientState(suite.chainA.GetContext(), substituteClientID, wasmSubstituteClientState) + }, + clienttypes.ErrInvalidClient, + }, + { + "failure: vm returns error", + func() { + suite.mockVM.RegisterSudoCallback( + types.MigrateClientStoreMsg{}, + func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return nil, wasmtesting.DefaultGasUsed, wasmtesting.ErrMockVM + }, + ) + }, + types.ErrVMError, + }, + { + "failure: contract returns error", + func() { + suite.mockVM.RegisterSudoCallback( + types.MigrateClientStoreMsg{}, + func(_ wasmvm.Checksum, _ wasmvmtypes.Env, _ []byte, _ wasmvm.KVStore, _ wasmvm.GoAPI, _ wasmvm.Querier, _ wasmvm.GasMeter, _ uint64, _ wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + return &wasmvmtypes.ContractResult{Err: wasmtesting.ErrMockContract.Error()}, wasmtesting.DefaultGasUsed, nil + }, + ) + }, + types.ErrWasmContractCallFailed, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + subjectEndpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := subjectEndpoint.CreateClient() + suite.Require().NoError(err) + subjectClientID = subjectEndpoint.ClientID + + substituteEndpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err = substituteEndpoint.CreateClient() + suite.Require().NoError(err) + substituteClientID = substituteEndpoint.ClientID + + expectedClientStateBz = nil + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), subjectClientID) + suite.Require().NoError(err) + + tc.malleate() + + err = lightClientModule.RecoverClient(suite.chainA.GetContext(), subjectClientID, substituteClientID) + + if tc.expErr == nil { + suite.Require().NoError(err) + + subjectClientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), subjectClientID) + suite.Require().Equal(expectedClientStateBz, subjectClientStore.Get(host.ClientStateKey())) + } else { + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} + +func (suite *WasmTestSuite) TestLatestHeight() { + var clientID string + + testCases := []struct { + name string + malleate func() + expHeight clienttypes.Height + }{ + { + "success", + func() { + }, + clienttypes.NewHeight(1, 5), + }, + { + "failure: cannot find substitute client state", + func() { + clientID = unusedWasmClientID + }, + clienttypes.ZeroHeight(), + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + subjectEndpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := subjectEndpoint.CreateClient() + suite.Require().NoError(err) + clientID = subjectEndpoint.ClientID + + lightClientModule, err := suite.chainA.App.GetIBCKeeper().ClientKeeper.Route(suite.chainA.GetContext(), clientID) + suite.Require().NoError(err) + + tc.malleate() + + height := lightClientModule.LatestHeight(suite.chainA.GetContext(), clientID) + + suite.Require().Equal(tc.expHeight, height) + }) + } +} diff --git a/modules/light-clients/08-wasm/module.go b/modules/light-clients/08-wasm/module.go new file mode 100644 index 0000000..4b4d44a --- /dev/null +++ b/modules/light-clients/08-wasm/module.go @@ -0,0 +1,144 @@ +package wasm + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + + "cosmossdk.io/core/appmodule" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/gov/simulation" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/client/cli" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/keeper" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" +) + +var ( + _ module.AppModule = (*AppModule)(nil) + _ module.AppModuleBasic = (*AppModule)(nil) + _ module.HasProposalMsgs = (*AppModule)(nil) + _ module.HasGenesis = (*AppModule)(nil) + _ module.HasName = (*AppModule)(nil) + _ module.HasConsensusVersion = (*AppModule)(nil) + _ module.HasServices = (*AppModule)(nil) + _ appmodule.AppModule = (*AppModule)(nil) +) + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (AppModule) IsOnePerModuleType() {} + +// IsAppModule implements the appmodule.AppModule interface. +func (AppModule) IsAppModule() {} + +// AppModuleBasic defines the basic application module used by the Wasm light client. +// Only the RegisterInterfaces function needs to be implemented. All other function perform +// a no-op. +type AppModuleBasic struct{} + +// Name returns the tendermint module name. +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterLegacyAminoCodec performs a no-op. The Wasm client does not support amino. +func (AppModuleBasic) RegisterLegacyAminoCodec(*codec.LegacyAmino) {} + +// RegisterInterfaces registers module concrete types into protobuf Any. This allows core IBC +// to unmarshal Wasm light client types. +func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) { + types.RegisterInterfaces(registry) +} + +// DefaultGenesis returns an empty state, i.e. no contracts +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(&types.GenesisState{ + Contracts: []types.Contract{}, + }) +} + +// ValidateGenesis performs a no-op. +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingConfig, bz json.RawMessage) error { + var gs types.GenesisState + if err := cdc.UnmarshalJSON(bz, &gs); err != nil { + return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) + } + + return gs.Validate() +} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for Wasm client module. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { + err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) + if err != nil { + panic(err) + } +} + +// GetTxCmd implements AppModuleBasic interface +func (AppModuleBasic) GetTxCmd() *cobra.Command { + return cli.NewTxCmd() +} + +// GetQueryCmd implements AppModuleBasic interface +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd() +} + +// AppModule represents the AppModule for this module +type AppModule struct { + AppModuleBasic + keeper keeper.Keeper +} + +// NewAppModule creates a new 08-wasm module +func NewAppModule(k keeper.Keeper) AppModule { + return AppModule{ + keeper: k, + } +} + +// RegisterServices registers module services. +func (am AppModule) RegisterServices(cfg module.Configurator) { + types.RegisterMsgServer(cfg.MsgServer(), am.keeper) + types.RegisterQueryServer(cfg.QueryServer(), am.keeper) + + wasmMigrator := keeper.NewMigrator(am.keeper) + if err := cfg.RegisterMigration(types.ModuleName, 1, wasmMigrator.MigrateChecksums); err != nil { + panic(fmt.Errorf("failed to migrate 08-wasm module from version 1 to 2 (checksums migration to collections): %v", err)) + } +} + +// ConsensusVersion implements AppModule/ConsensusVersion. +func (AppModule) ConsensusVersion() uint64 { return 2 } + +// ProposalMsgs returns msgs used for governance proposals for simulations. +func (AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg { + return simulation.ProposalMsgs() +} + +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, bz json.RawMessage) { + var gs types.GenesisState + err := cdc.UnmarshalJSON(bz, &gs) + if err != nil { + panic(fmt.Errorf("failed to unmarshal %s genesis state: %s", am.Name(), err)) + } + err = am.keeper.InitGenesis(ctx, gs) + if err != nil { + panic(err) + } +} + +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + gs := am.keeper.ExportGenesis(ctx) + return cdc.MustMarshalJSON(&gs) +} diff --git a/modules/light-clients/08-wasm/simulation/proposals.go b/modules/light-clients/08-wasm/simulation/proposals.go new file mode 100644 index 0000000..7b1ca72 --- /dev/null +++ b/modules/light-clients/08-wasm/simulation/proposals.go @@ -0,0 +1,40 @@ +package simulation + +import ( + "math/rand" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" +) + +// Simulation operation weights constants +const ( + DefaultWeightMsgStoreCode int = 100 + + OpWeightMsgStoreCode = "op_weight_msg_store_code" // #nosec +) + +// ProposalMsgs defines the module weighted proposals' contents +func ProposalMsgs() []simtypes.WeightedProposalMsg { + return []simtypes.WeightedProposalMsg{ + simulation.NewWeightedProposalMsg( + OpWeightMsgStoreCode, + DefaultWeightMsgStoreCode, + SimulateMsgStoreCode, + ), + } +} + +// SimulateMsgStoreCode returns a random MsgStoreCode for the 08-wasm module +func SimulateMsgStoreCode(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) sdk.Msg { + var signer sdk.AccAddress = address.Module("gov") + + return &types.MsgStoreCode{ + Signer: signer.String(), + WasmByteCode: []byte{0x01}, + } +} diff --git a/modules/light-clients/08-wasm/simulation/proposals_test.go b/modules/light-clients/08-wasm/simulation/proposals_test.go new file mode 100644 index 0000000..9d1c87d --- /dev/null +++ b/modules/light-clients/08-wasm/simulation/proposals_test.go @@ -0,0 +1,41 @@ +package simulation_test + +import ( + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/simulation" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" +) + +func TestProposalMsgs(t *testing.T) { + // initialize parameters + s := rand.NewSource(1) + r := rand.New(s) + + ctx := sdk.NewContext(nil, cmtproto.Header{}, true, nil) + accounts := simtypes.RandomAccounts(r, 3) + + // execute ProposalMsgs function + weightedProposalMsgs := simulation.ProposalMsgs() + require.Equal(t, 1, len(weightedProposalMsgs)) + w0 := weightedProposalMsgs[0] + + require.Equal(t, simulation.OpWeightMsgStoreCode, w0.AppParamsKey()) + require.Equal(t, simulation.DefaultWeightMsgStoreCode, w0.DefaultWeight()) + + msg := w0.MsgSimulatorFn()(r, ctx, accounts) + msgStoreCode, ok := msg.(*types.MsgStoreCode) + require.True(t, ok) + + require.Equal(t, sdk.AccAddress(address.Module("gov")).String(), msgStoreCode.Signer) + require.Equal(t, msgStoreCode.WasmByteCode, []byte{0x01}) +} diff --git a/modules/light-clients/08-wasm/testing/mock_engine.go b/modules/light-clients/08-wasm/testing/mock_engine.go new file mode 100644 index 0000000..f454d25 --- /dev/null +++ b/modules/light-clients/08-wasm/testing/mock_engine.go @@ -0,0 +1,284 @@ +package testing + +import ( + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "reflect" + + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" +) + +const DefaultGasUsed = uint64(1) + +var ( + _ types.WasmEngine = (*MockWasmEngine)(nil) + + // queryTypes contains all the possible query message types. + queryTypes = [...]any{types.StatusMsg{}, types.TimestampAtHeightMsg{}, types.VerifyClientMessageMsg{}, types.CheckForMisbehaviourMsg{}} + + // sudoTypes contains all the possible sudo message types. + sudoTypes = [...]any{types.UpdateStateMsg{}, types.UpdateStateOnMisbehaviourMsg{}, types.VerifyUpgradeAndUpdateStateMsg{}, types.VerifyMembershipMsg{}, types.VerifyNonMembershipMsg{}, types.MigrateClientStoreMsg{}} +) + +type ( + // queryFn is a callback function that is invoked when a specific query message type is received. + queryFn func(checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) + + // sudoFn is a callback function that is invoked when a specific sudo message type is received. + sudoFn func(checksum wasmvm.Checksum, env wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) +) + +// MockWasmEngine implements types.WasmEngine for testing purposes. One or multiple messages can be stubbed. +// Without a stub function a panic is thrown. +// ref: https://github.com/CosmWasm/wasmd/blob/v0.42.0/x/wasm/keeper/wasmtesting/mock_engine.go#L19 +type MockWasmEngine struct { + StoreCodeFn func(code wasmvm.WasmCode, gasLimit uint64) (wasmvmtypes.Checksum, uint64, error) + StoreCodeUncheckedFn func(code wasmvm.WasmCode) (wasmvm.Checksum, error) + InstantiateFn func(checksum wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) + MigrateFn func(checksum wasmvm.Checksum, env wasmvmtypes.Env, migrateMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) + GetCodeFn func(checksum wasmvm.Checksum) (wasmvm.WasmCode, error) + PinFn func(checksum wasmvm.Checksum) error + UnpinFn func(checksum wasmvm.Checksum) error + + // queryCallbacks contains a mapping of queryMsg field type name to callback function. + queryCallbacks map[string]queryFn + sudoCallbacks map[string]sudoFn + + // contracts contains a mapping of checksum to code. + storedContracts map[uint32][]byte +} + +// NewMockWasmEngine creates and returns a new instance of the mock wasmvm for testing purposes. +// Each callback method of the mock wasmvm can be overridden to assign specific functionality. +// Default functionality is assigned for StoreCode, StoreCodeUnchecked and GetCode. Both Pin and Unpin are implemented as no-op methods. +// All other callbacks stored in the query and sudo callback maps panic. Use RegisterQueryCallback and RegisterSudoCallback methods +// to assign expected behaviour for test cases. +func NewMockWasmEngine() *MockWasmEngine { + m := &MockWasmEngine{ + queryCallbacks: map[string]queryFn{}, + sudoCallbacks: map[string]sudoFn{}, + storedContracts: map[uint32][]byte{}, + } + + for _, msgType := range queryTypes { + typeName := reflect.TypeOf(msgType).Name() + m.queryCallbacks[typeName] = func(checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + panic(fmt.Errorf("no callback specified for type %s", typeName)) + } + } + + for _, msgType := range sudoTypes { + typeName := reflect.TypeOf(msgType).Name() + m.sudoCallbacks[typeName] = func(checksum wasmvm.Checksum, env wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + panic(fmt.Errorf("no callback specified for type %s", typeName)) + } + } + + // Set up default behavior for Store/Pin/Get + m.StoreCodeFn = func(code wasmvm.WasmCode, gasLimit uint64) (wasmvmtypes.Checksum, uint64, error) { + checkSum, _ := types.CreateChecksum(code) + + m.storedContracts[binary.LittleEndian.Uint32(checkSum)] = code + return checkSum, 0, nil + } + + m.StoreCodeUncheckedFn = func(code wasmvm.WasmCode) (wasmvm.Checksum, error) { + checkSum, _ := types.CreateChecksum(code) + + m.storedContracts[binary.LittleEndian.Uint32(checkSum)] = code + return checkSum, nil + } + + m.PinFn = func(checksum wasmvm.Checksum) error { + return nil + } + + m.UnpinFn = func(checksum wasmvm.Checksum) error { + return nil + } + + m.GetCodeFn = func(checksum wasmvm.Checksum) (wasmvm.WasmCode, error) { + code, ok := m.storedContracts[binary.LittleEndian.Uint32(checksum)] + if !ok { + return nil, errors.New("code not found") + } + return code, nil + } + + return m +} + +// RegisterQueryCallback registers a callback for a specific message type. +func (m *MockWasmEngine) RegisterQueryCallback(queryMessage any, fn queryFn) { + typeName := reflect.TypeOf(queryMessage).Name() + if _, found := m.queryCallbacks[typeName]; !found { + panic(fmt.Errorf("unexpected argument of type %s passed", typeName)) + } + m.queryCallbacks[typeName] = fn +} + +// RegisterSudoCallback registers a callback for a specific sudo message type. +func (m *MockWasmEngine) RegisterSudoCallback(sudoMessage any, fn sudoFn) { + typeName := reflect.TypeOf(sudoMessage).Name() + if _, found := m.sudoCallbacks[typeName]; !found { + panic(fmt.Errorf("unexpected argument of type %s passed", typeName)) + } + m.sudoCallbacks[typeName] = fn +} + +// StoreCode implements the WasmEngine interface. +func (m *MockWasmEngine) StoreCode(code wasmvm.WasmCode, gasLimit uint64) (wasmvmtypes.Checksum, uint64, error) { + if m.StoreCodeFn == nil { + panic(errors.New("mock engine is not properly initialized: StoreCodeFn is nil")) + } + return m.StoreCodeFn(code, gasLimit) +} + +// StoreCodeUnchecked implements the WasmEngine interface. +func (m *MockWasmEngine) StoreCodeUnchecked(code wasmvm.WasmCode) (wasmvm.Checksum, error) { + if m.StoreCodeUncheckedFn == nil { + panic(errors.New("mock engine is not properly initialized: StoreCodeUncheckedFn is nil")) + } + return m.StoreCodeUncheckedFn(code) +} + +// Instantiate implements the WasmEngine interface. +func (m *MockWasmEngine) Instantiate(checksum wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + if m.InstantiateFn == nil { + panic(errors.New("mock engine is not properly initialized: InstantiateFn is nil")) + } + return m.InstantiateFn(checksum, env, info, initMsg, store, goapi, querier, gasMeter, gasLimit, deserCost) +} + +// Query implements the WasmEngine interface. +func (m *MockWasmEngine) Query(checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + msgTypeName := getQueryMsgPayloadTypeName(queryMsg) + + callbackFn, ok := m.queryCallbacks[msgTypeName] + if !ok { + panic(fmt.Errorf("mock engine is not properly initialized: no callback specified for %s", msgTypeName)) + } + + return callbackFn(checksum, env, queryMsg, store, goapi, querier, gasMeter, gasLimit, deserCost) +} + +// Migrate implements the WasmEngine interface. +func (m *MockWasmEngine) Migrate(checksum wasmvm.Checksum, env wasmvmtypes.Env, migrateMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + if m.MigrateFn == nil { + panic(errors.New("mock engine is not properly initialized: MigrateFn is nil")) + } + return m.MigrateFn(checksum, env, migrateMsg, store, goapi, querier, gasMeter, gasLimit, deserCost) +} + +// Sudo implements the WasmEngine interface. +func (m *MockWasmEngine) Sudo(checksum wasmvm.Checksum, env wasmvmtypes.Env, sudoMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + msgTypeName := getSudoMsgPayloadTypeName(sudoMsg) + + sudoFn, ok := m.sudoCallbacks[msgTypeName] + if !ok { + panic(fmt.Errorf("mock engine is not properly initialized: no callback specified for %s", msgTypeName)) + } + + return sudoFn(checksum, env, sudoMsg, store, goapi, querier, gasMeter, gasLimit, deserCost) +} + +// GetCode implements the WasmEngine interface. +func (m *MockWasmEngine) GetCode(checksum wasmvm.Checksum) (wasmvm.WasmCode, error) { + if m.GetCodeFn == nil { + panic(errors.New("mock engine is not properly initialized: GetCodeFn is nil")) + } + return m.GetCodeFn(checksum) +} + +// Pin implements the WasmEngine interface. +func (m *MockWasmEngine) Pin(checksum wasmvm.Checksum) error { + if m.PinFn == nil { + panic(errors.New("mock engine is not properly initialized: PinFn is nil")) + } + return m.PinFn(checksum) +} + +// Unpin implements the WasmEngine interface. +func (m *MockWasmEngine) Unpin(checksum wasmvm.Checksum) error { + if m.UnpinFn == nil { + panic(errors.New("mock engine is not properly initialized: UnpinFn is nil")) + } + return m.UnpinFn(checksum) +} + +// getQueryMsgPayloadTypeName extracts the name of the struct that is populated. +// this value is used as a key to map to a callback function to handle that message type. +func getQueryMsgPayloadTypeName(queryMsgBz []byte) string { + payload := types.QueryMsg{} + if err := json.Unmarshal(queryMsgBz, &payload); err != nil { + panic(err) + } + + var payloadField any + if payload.Status != nil { + payloadField = *payload.Status + } + + if payload.CheckForMisbehaviour != nil { + payloadField = *payload.CheckForMisbehaviour + } + + if payload.TimestampAtHeight != nil { + payloadField = *payload.TimestampAtHeight + } + + if payload.VerifyClientMessage != nil { + payloadField = *payload.VerifyClientMessage + } + + if payloadField == nil { + panic(fmt.Errorf("failed to extract valid query message from bytes: %s", string(queryMsgBz))) + } + + return reflect.TypeOf(payloadField).Name() +} + +// getSudoMsgPayloadTypeName extracts the name of the struct that is populated. +// this value is used as a key to map to a callback function to handle that message type. +func getSudoMsgPayloadTypeName(sudoMsgBz []byte) string { + payload := types.SudoMsg{} + if err := json.Unmarshal(sudoMsgBz, &payload); err != nil { + panic(err) + } + + var payloadField any + if payload.UpdateState != nil { + payloadField = *payload.UpdateState + } + + if payload.UpdateStateOnMisbehaviour != nil { + payloadField = *payload.UpdateStateOnMisbehaviour + } + + if payload.VerifyUpgradeAndUpdateState != nil { + payloadField = *payload.VerifyUpgradeAndUpdateState + } + + if payload.VerifyMembership != nil { + payloadField = *payload.VerifyMembership + } + + if payload.VerifyNonMembership != nil { + payloadField = *payload.VerifyNonMembership + } + + if payload.MigrateClientStore != nil { + payloadField = *payload.MigrateClientStore + } + + if payloadField == nil { + panic(fmt.Errorf("failed to extract valid sudo message from bytes: %s", string(sudoMsgBz))) + } + + return reflect.TypeOf(payloadField).Name() +} diff --git a/modules/light-clients/08-wasm/testing/simapp/README.md b/modules/light-clients/08-wasm/testing/simapp/README.md new file mode 100644 index 0000000..b4c5b4a --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/README.md @@ -0,0 +1,5 @@ +# 08-Wasm Testing SimApp + +This testing directory is a duplicate of the ibc-go testing directory. +It is only here as a way of creating a separate SimApp binary to avoid introducing a dependency on the 08-wasm +module from within ibc-go. diff --git a/modules/light-clients/08-wasm/testing/simapp/ante_handler.go b/modules/light-clients/08-wasm/testing/simapp/ante_handler.go new file mode 100644 index 0000000..b84394a --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/ante_handler.go @@ -0,0 +1,50 @@ +package simapp + +import ( + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + + ibcante "github.com/cosmos/ibc-go/v10/modules/core/ante" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/keeper" +) + +// HandlerOptions extend the SDK's AnteHandler options by requiring the IBC keeper. +type HandlerOptions struct { + ante.HandlerOptions + + IBCKeeper *keeper.Keeper +} + +// NewAnteHandler creates a new ante handler +func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { + if options.AccountKeeper == nil { + return nil, errorsmod.Wrap(ibcerrors.ErrLogic, "account keeper is required for AnteHandler") + } + if options.BankKeeper == nil { + return nil, errorsmod.Wrap(ibcerrors.ErrLogic, "bank keeper is required for AnteHandler") + } + if options.SignModeHandler == nil { + return nil, errorsmod.Wrap(ibcerrors.ErrLogic, "sign mode handler is required for AnteHandler") + } + + anteDecorators := []sdk.AnteDecorator{ + ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first + ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker), + ante.NewValidateBasicDecorator(), + ante.NewTxTimeoutHeightDecorator(), + ante.NewValidateMemoDecorator(options.AccountKeeper), + ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), + ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker), + ante.NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators + ante.NewValidateSigCountDecorator(options.AccountKeeper), + ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer), + ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), + ante.NewIncrementSequenceDecorator(options.AccountKeeper), + ibcante.NewRedundantRelayDecorator(options.IBCKeeper), + } + + return sdk.ChainAnteDecorators(anteDecorators...), nil +} diff --git a/modules/light-clients/08-wasm/testing/simapp/app.go b/modules/light-clients/08-wasm/testing/simapp/app.go new file mode 100644 index 0000000..f42adbb --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/app.go @@ -0,0 +1,1064 @@ +package simapp + +import ( + "encoding/json" + "fmt" + "io" + "maps" + "math/rand" + "os" + "path" + "path/filepath" + "strconv" + + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/gogoproto/proto" + "github.com/spf13/cast" + + autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" + reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1" + "cosmossdk.io/client/v2/autocli" + "cosmossdk.io/core/appmodule" + "cosmossdk.io/log" + storetypes "cosmossdk.io/store/types" + "cosmossdk.io/x/circuit" + circuitkeeper "cosmossdk.io/x/circuit/keeper" + circuittypes "cosmossdk.io/x/circuit/types" + "cosmossdk.io/x/evidence" + evidencekeeper "cosmossdk.io/x/evidence/keeper" + evidencetypes "cosmossdk.io/x/evidence/types" + "cosmossdk.io/x/feegrant" + feegrantkeeper "cosmossdk.io/x/feegrant/keeper" + feegrantmodule "cosmossdk.io/x/feegrant/module" + "cosmossdk.io/x/tx/signing" + "cosmossdk.io/x/upgrade" + upgradekeeper "cosmossdk.io/x/upgrade/keeper" + upgradetypes "cosmossdk.io/x/upgrade/types" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/grpc/cmtservice" + nodeservice "github.com/cosmos/cosmos-sdk/client/grpc/node" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/address" + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/runtime" + runtimeservices "github.com/cosmos/cosmos-sdk/runtime/services" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/server/api" + "github.com/cosmos/cosmos-sdk/server/config" + servertypes "github.com/cosmos/cosmos-sdk/server/types" + "github.com/cosmos/cosmos-sdk/std" + "github.com/cosmos/cosmos-sdk/testutil/testdata/testpb" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/types/msgservice" + "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + "github.com/cosmos/cosmos-sdk/x/auth/posthandler" + authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/auth/vesting" + vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + "github.com/cosmos/cosmos-sdk/x/authz" + authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" + authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module" + "github.com/cosmos/cosmos-sdk/x/bank" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/cosmos-sdk/x/consensus" + consensusparamkeeper "github.com/cosmos/cosmos-sdk/x/consensus/keeper" + consensusparamtypes "github.com/cosmos/cosmos-sdk/x/consensus/types" + "github.com/cosmos/cosmos-sdk/x/crisis" + crisiskeeper "github.com/cosmos/cosmos-sdk/x/crisis/keeper" + crisistypes "github.com/cosmos/cosmos-sdk/x/crisis/types" + distr "github.com/cosmos/cosmos-sdk/x/distribution" + distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/cosmos/cosmos-sdk/x/genutil" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + "github.com/cosmos/cosmos-sdk/x/gov" + govclient "github.com/cosmos/cosmos-sdk/x/gov/client" + govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/cosmos/cosmos-sdk/x/group" + groupkeeper "github.com/cosmos/cosmos-sdk/x/group/keeper" + groupmodule "github.com/cosmos/cosmos-sdk/x/group/module" + "github.com/cosmos/cosmos-sdk/x/mint" + mintkeeper "github.com/cosmos/cosmos-sdk/x/mint/keeper" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + "github.com/cosmos/cosmos-sdk/x/params" + paramsclient "github.com/cosmos/cosmos-sdk/x/params/client" + paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/cosmos/cosmos-sdk/x/slashing" + slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + abci "github.com/cometbft/cometbft/abci/types" + cmtos "github.com/cometbft/cometbft/libs/os" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + + ibcwasm "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/blsverifier" + ibcwasmkeeper "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/keeper" + ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + ica "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts" + icacontroller "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller" + icacontrollerkeeper "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/keeper" + icacontrollertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + icahost "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host" + icahostkeeper "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/keeper" + icahosttypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer" + ibctransferkeeper "github.com/cosmos/ibc-go/v10/modules/apps/transfer/keeper" + ibctransfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + transferv2 "github.com/cosmos/ibc-go/v10/modules/apps/transfer/v2" + ibc "github.com/cosmos/ibc-go/v10/modules/core" + ibcclienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + ibcconnectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + ibcapi "github.com/cosmos/ibc-go/v10/modules/core/api" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibckeeper "github.com/cosmos/ibc-go/v10/modules/core/keeper" + solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibcmock "github.com/cosmos/ibc-go/v10/testing/mock" +) + +const appName = "SimApp" + +var ( + // DefaultNodeHome default home directories for the application daemon + DefaultNodeHome string + + // module account permissions + maccPerms = map[string][]string{ + authtypes.FeeCollectorName: nil, + distrtypes.ModuleName: nil, + minttypes.ModuleName: {authtypes.Minter}, + stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking}, + stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking}, + govtypes.ModuleName: {authtypes.Burner}, + ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + icatypes.ModuleName: nil, + ibcmock.ModuleName: nil, + } +) + +var ( + _ runtime.AppI = (*SimApp)(nil) + _ servertypes.Application = (*SimApp)(nil) +) + +// SimApp extends an ABCI application, but with most of its parameters exported. +// They are exported for convenience in creating helper functions. +type SimApp struct { + *baseapp.BaseApp + legacyAmino *codec.LegacyAmino + appCodec codec.Codec + txConfig client.TxConfig + interfaceRegistry types.InterfaceRegistry + + // keys to access the substores + keys map[string]*storetypes.KVStoreKey + tkeys map[string]*storetypes.TransientStoreKey + memKeys map[string]*storetypes.MemoryStoreKey + + // keepers + AccountKeeper authkeeper.AccountKeeper + BankKeeper bankkeeper.Keeper + StakingKeeper *stakingkeeper.Keeper + SlashingKeeper slashingkeeper.Keeper + MintKeeper mintkeeper.Keeper + DistrKeeper distrkeeper.Keeper + GovKeeper govkeeper.Keeper + CrisisKeeper *crisiskeeper.Keeper + UpgradeKeeper *upgradekeeper.Keeper + ParamsKeeper paramskeeper.Keeper + AuthzKeeper authzkeeper.Keeper + IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly + ICAControllerKeeper icacontrollerkeeper.Keeper + ICAHostKeeper icahostkeeper.Keeper + EvidenceKeeper evidencekeeper.Keeper + TransferKeeper ibctransferkeeper.Keeper + WasmClientKeeper ibcwasmkeeper.Keeper + FeeGrantKeeper feegrantkeeper.Keeper + GroupKeeper groupkeeper.Keeper + ConsensusParamsKeeper consensusparamkeeper.Keeper + CircuitKeeper circuitkeeper.Keeper + + // make IBC modules public for test purposes + // these modules are never directly routed to by the IBC Router + ICAAuthModule ibcmock.IBCModule + + // the module manager + ModuleManager *module.Manager + BasicModuleManager module.BasicManager + + // simulation manager + simulationManager *module.SimulationManager + + // module configurator + configurator module.Configurator +} + +func init() { + userHomeDir, err := os.UserHomeDir() + if err != nil { + panic(err) + } + + DefaultNodeHome = filepath.Join(userHomeDir, ".simapp") +} + +func NewUnitTestSimApp( + logger log.Logger, + db dbm.DB, + traceStore io.Writer, + loadLatest bool, + appOpts servertypes.AppOptions, + mockVM ibcwasmtypes.WasmEngine, + baseAppOptions ...func(*baseapp.BaseApp), +) *SimApp { + wasmDir := path.Join("ibc_08-wasm_client_data", strconv.Itoa(rand.Intn(10000))) + return newSimApp(logger, db, traceStore, loadLatest, appOpts, mockVM, wasmDir, baseAppOptions...) +} + +func NewBinarySimApp( + logger log.Logger, + db dbm.DB, + traceStore io.Writer, + loadLatest bool, + appOpts servertypes.AppOptions, + mockVM ibcwasmtypes.WasmEngine, + baseAppOptions ...func(*baseapp.BaseApp), +) *SimApp { + wasmDir := "ibc_08-wasm_client_data" + return newSimApp(logger, db, traceStore, loadLatest, appOpts, mockVM, wasmDir, baseAppOptions...) +} + +// NewSimApp returns a reference to an initialized SimApp. +func newSimApp( + logger log.Logger, + db dbm.DB, + traceStore io.Writer, + loadLatest bool, + appOpts servertypes.AppOptions, + mockVM ibcwasmtypes.WasmEngine, + wasmDir string, + baseAppOptions ...func(*baseapp.BaseApp), +) *SimApp { + interfaceRegistry, _ := types.NewInterfaceRegistryWithOptions(types.InterfaceRegistryOptions{ + ProtoFiles: proto.HybridResolver, + SigningOptions: signing.Options{ + AddressCodec: address.Bech32Codec{ + Bech32Prefix: sdk.GetConfig().GetBech32AccountAddrPrefix(), + }, + ValidatorAddressCodec: address.Bech32Codec{ + Bech32Prefix: sdk.GetConfig().GetBech32ValidatorAddrPrefix(), + }, + }, + }) + appCodec := codec.NewProtoCodec(interfaceRegistry) + legacyAmino := codec.NewLegacyAmino() + txConfig := authtx.NewTxConfig(appCodec, authtx.DefaultSignModes) + + std.RegisterLegacyAminoCodec(legacyAmino) + std.RegisterInterfaces(interfaceRegistry) + + // Below we could construct and set an application specific mempool and + // ABCI 1.0 PrepareProposal and ProcessProposal handlers. These defaults are + // already set in the SDK's BaseApp, this shows an example of how to override + // them. + // + // Example: + // + // bApp := baseapp.NewBaseApp(...) + // nonceMempool := mempool.NewSenderNonceMempool() + // abciPropHandler := NewDefaultProposalHandler(nonceMempool, bApp) + // + // bApp.SetMempool(nonceMempool) + // bApp.SetPrepareProposal(abciPropHandler.PrepareProposalHandler()) + // bApp.SetProcessProposal(abciPropHandler.ProcessProposalHandler()) + // + // Alternatively, you can construct BaseApp options, append those to + // baseAppOptions and pass them to NewBaseApp. + // + // Example: + // + // prepareOpt = func(app *baseapp.BaseApp) { + // abciPropHandler := baseapp.NewDefaultProposalHandler(nonceMempool, app) + // app.SetPrepareProposal(abciPropHandler.PrepareProposalHandler()) + // } + // baseAppOptions = append(baseAppOptions, prepareOpt) + + bApp := baseapp.NewBaseApp(appName, logger, db, txConfig.TxDecoder(), baseAppOptions...) + bApp.SetCommitMultiStoreTracer(traceStore) + bApp.SetVersion(version.Version) + bApp.SetInterfaceRegistry(interfaceRegistry) + bApp.SetTxEncoder(txConfig.TxEncoder()) + + keys := storetypes.NewKVStoreKeys( + authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, crisistypes.StoreKey, + minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey, + govtypes.StoreKey, group.StoreKey, paramstypes.StoreKey, ibcexported.StoreKey, upgradetypes.StoreKey, feegrant.StoreKey, + evidencetypes.StoreKey, ibctransfertypes.StoreKey, icacontrollertypes.StoreKey, icahosttypes.StoreKey, + authzkeeper.StoreKey, consensusparamtypes.StoreKey, circuittypes.StoreKey, ibcwasmtypes.StoreKey, + ) + + // register streaming services + if err := bApp.RegisterStreamingServices(appOpts, keys); err != nil { + panic(err) + } + + tkeys := storetypes.NewTransientStoreKeys(paramstypes.TStoreKey) + memKeys := storetypes.NewMemoryStoreKeys(ibcmock.MemStoreKey) + + app := &SimApp{ + BaseApp: bApp, + legacyAmino: legacyAmino, + appCodec: appCodec, + txConfig: txConfig, + interfaceRegistry: interfaceRegistry, + keys: keys, + tkeys: tkeys, + memKeys: memKeys, + } + + app.ParamsKeeper = initParamsKeeper(appCodec, legacyAmino, keys[paramstypes.StoreKey], tkeys[paramstypes.TStoreKey]) + + // set the BaseApp's parameter store + app.ConsensusParamsKeeper = consensusparamkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[consensusparamtypes.StoreKey]), authtypes.NewModuleAddress(govtypes.ModuleName).String(), runtime.EventService{}) + bApp.SetParamStore(app.ConsensusParamsKeeper.ParamsStore) + + // SDK module keepers + + // add keepers + app.AccountKeeper = authkeeper.NewAccountKeeper(appCodec, runtime.NewKVStoreService(keys[authtypes.StoreKey]), authtypes.ProtoBaseAccount, maccPerms, authcodec.NewBech32Codec(sdk.Bech32MainPrefix), sdk.Bech32MainPrefix, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + + app.BankKeeper = bankkeeper.NewBaseKeeper( + appCodec, + runtime.NewKVStoreService(keys[banktypes.StoreKey]), + app.AccountKeeper, + BlockedAddresses(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + logger, + ) + app.StakingKeeper = stakingkeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[stakingtypes.StoreKey]), app.AccountKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), authcodec.NewBech32Codec(sdk.Bech32PrefixValAddr), authcodec.NewBech32Codec(sdk.Bech32PrefixConsAddr), + ) + app.MintKeeper = mintkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[minttypes.StoreKey]), app.StakingKeeper, app.AccountKeeper, app.BankKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + + app.DistrKeeper = distrkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[distrtypes.StoreKey]), app.AccountKeeper, app.BankKeeper, app.StakingKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + + app.SlashingKeeper = slashingkeeper.NewKeeper( + appCodec, legacyAmino, runtime.NewKVStoreService(keys[slashingtypes.StoreKey]), app.StakingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + invCheckPeriod := cast.ToUint(appOpts.Get(server.FlagInvCheckPeriod)) + app.CrisisKeeper = crisiskeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[crisistypes.StoreKey]), invCheckPeriod, + app.BankKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String(), app.AccountKeeper.AddressCodec()) + + app.FeeGrantKeeper = feegrantkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[feegrant.StoreKey]), app.AccountKeeper) + + // register the staking hooks + // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks + app.StakingKeeper.SetHooks( + stakingtypes.NewMultiStakingHooks(app.DistrKeeper.Hooks(), app.SlashingKeeper.Hooks()), + ) + + app.CircuitKeeper = circuitkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[circuittypes.StoreKey]), authtypes.NewModuleAddress(govtypes.ModuleName).String(), app.AccountKeeper.AddressCodec()) + app.SetCircuitBreaker(&app.CircuitKeeper) + + app.AuthzKeeper = authzkeeper.NewKeeper(runtime.NewKVStoreService(keys[authzkeeper.StoreKey]), appCodec, app.MsgServiceRouter(), app.AccountKeeper) + + groupConfig := group.DefaultConfig() + /* + Example of setting group params: + groupConfig.MaxMetadataLen = 1000 + */ + app.GroupKeeper = groupkeeper.NewKeeper(keys[group.StoreKey], appCodec, app.MsgServiceRouter(), app.AccountKeeper, groupConfig) + + // get skipUpgradeHeights from the app options + skipUpgradeHeights := map[int64]bool{} + for _, h := range cast.ToIntSlice(appOpts.Get(server.FlagUnsafeSkipUpgrades)) { + skipUpgradeHeights[int64(h)] = true + } + homePath := cast.ToString(appOpts.Get(flags.FlagHome)) + // set the governance module account as the authority for conducting upgrades + app.UpgradeKeeper = upgradekeeper.NewKeeper(skipUpgradeHeights, runtime.NewKVStoreService(keys[upgradetypes.StoreKey]), appCodec, homePath, app.BaseApp, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + + app.IBCKeeper = ibckeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[ibcexported.StoreKey]), app.GetSubspace(ibcexported.ModuleName), app.UpgradeKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + govConfig := govtypes.DefaultConfig() + /* + Example of setting gov params: + govConfig.MaxMetadataLen = 10000 + */ + govKeeper := govkeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[govtypes.StoreKey]), app.AccountKeeper, app.BankKeeper, + app.StakingKeeper, app.DistrKeeper, app.MsgServiceRouter(), govConfig, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + app.GovKeeper = *govKeeper.SetHooks( + govtypes.NewMultiGovHooks( + // register the governance hooks + ), + ) + + // 08-wasm's Keeper can be instantiated in two different ways: + // 1. If the chain uses x/wasm: + // Both x/wasm's Keeper and 08-wasm Keeper should share the same Wasm VM instance. + // - Instantiate the Wasm VM in app.go with the parameters of your choice. + // - Create an Option with this Wasm VM instance (see https://github.com/CosmWasm/wasmd/blob/v0.41.0/x/wasm/keeper/options.go#L26-L32). + // - Pass the option to the x/wasm NewKeeper constructor function (https://github.com/CosmWasm/wasmd/blob/v0.41.0/x/wasm/keeper/keeper_cgo.go#L36). + // - Pass a pointer to the Wasm VM instance to 08-wasm NewKeeperWithVM constructor function. + // + // 2. If the chain does not use x/wasm: + // Even though it is still possible to use method 1 above + // (e.g. instantiating a Wasm VM in app.go an pass it in 08-wasm NewKeeper), + // since there is no need to share the Wasm VM instance with another module + // you can use NewKeeperWithConfig constructor function and provide + // the Wasm VM configuration parameters of your choice. + // Check out the WasmConfig type definition for more information on + // each parameter. Some parameters allow node-level configurations. + // Function DefaultWasmConfig can also be used to use default values. + // + // In the code below we use the second method because we are not using x/wasm in this app.go. + + // NOTE: a random string is appended to the data directory to ensure that every test + // runs using a different data directory. This is required because wasm VM forbids 2 or more + // different VM instances running in the same data directory. In production environments, the + // appended random string is not needed. + wasmConfig := ibcwasmtypes.WasmConfig{ + DataDir: filepath.Join(homePath, wasmDir), + SupportedCapabilities: []string{"iterator"}, + ContractDebugMode: false, + } + if mockVM != nil { + // NOTE: mockVM is used for testing purposes only! + app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithVM( + appCodec, runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), mockVM, app.GRPCQueryRouter(), + ) + } else { + querierOption := ibcwasmkeeper.WithQueryPlugins(&ibcwasmkeeper.QueryPlugins{ + Custom: blsverifier.CustomQuerier(), + Stargate: ibcwasmkeeper.AcceptListStargateQuerier( + []string{"/cosmos.base.tendermint.v1beta1.Service/ABCIQuery"}, + app.GRPCQueryRouter(), + ), + }) + app.WasmClientKeeper = ibcwasmkeeper.NewKeeperWithConfig( + appCodec, runtime.NewKVStoreService(keys[ibcwasmtypes.StoreKey]), app.IBCKeeper.ClientKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), wasmConfig, app.GRPCQueryRouter(), + querierOption, + ) + } + + // ICA Controller keeper + app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[icacontrollertypes.StoreKey]), app.GetSubspace(icacontrollertypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeper, + app.MsgServiceRouter(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // ICA Host keeper + app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[icahosttypes.StoreKey]), app.GetSubspace(icahosttypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeper, + app.AccountKeeper, app.MsgServiceRouter(), + app.GRPCQueryRouter(), authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // Create IBC Router + ibcRouter := porttypes.NewRouter() + ibcRouterV2 := ibcapi.NewRouter() + + // Middleware Stacks + + // Create Transfer Keeper + app.TransferKeeper = ibctransferkeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]), app.GetSubspace(ibctransfertypes.ModuleName), + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeper, + app.MsgServiceRouter(), + app.AccountKeeper, app.BankKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // Mock Module Stack + + // Mock Module setup for testing IBC and also acts as the interchain accounts authentication module + // NOTE: the IBC mock keeper and application module is used only for testing core IBC. Do + // not replicate if you do not need to test core IBC or light clients. + mockModule := ibcmock.NewAppModule() + + // The mock module is used for testing IBC + mockIBCModule := ibcmock.NewIBCModule(&mockModule, ibcmock.NewIBCApp(ibcmock.ModuleName)) + ibcRouter.AddRoute(ibcmock.ModuleName, mockIBCModule) + + // Create Transfer Stack + // SendPacket, since it is originating from the application to core IBC: + // transferKeeper.SendPacket -> channel.SendPacket + + // RecvPacket, message that originates from core IBC and goes down to app, the flow is the other way + // channel.RecvPacket -> transfer.OnRecvPacket + + // transfer stack contains (from top to bottom): + // - Transfer + + // create IBC module from bottom to top of stack + var transferStack porttypes.IBCModule = transfer.NewIBCModule(app.TransferKeeper) + + // Add transfer stack to IBC Router + ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) + + // Create Interchain Accounts Stack + // SendPacket, since it is originating from the application to core IBC: + // icaControllerKeeper.SendTx -> channel.SendPacket + + // initialize ICA module with mock module as the authentication module on the controller side + var icaControllerStack porttypes.IBCModule + var ok bool + icaControllerStack = ibcmock.NewIBCModule(&mockModule, ibcmock.NewIBCApp("")) + app.ICAAuthModule, ok = icaControllerStack.(ibcmock.IBCModule) + if !ok { + panic(fmt.Errorf("cannot convert %T into %T", icaControllerStack, app.ICAAuthModule)) + } + icaControllerStack = icacontroller.NewIBCMiddlewareWithAuth(icaControllerStack, app.ICAControllerKeeper) + + // RecvPacket, message that originates from core IBC and goes down to app, the flow is: + // channel.RecvPacket -> icaHost.OnRecvPacket + + var icaHostStack porttypes.IBCModule = icahost.NewIBCModule(app.ICAHostKeeper) + + // Add host, controller & ica auth modules to IBC router + ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icahosttypes.SubModuleName, icaHostStack). + AddRoute(ibcmock.ModuleName+icacontrollertypes.SubModuleName, icaControllerStack) // ica with mock auth module stack route to ica (top level of middleware stack) + + // register the transfer v2 module. + ibcRouterV2.AddRoute(ibctransfertypes.PortID, transferv2.NewIBCModule(app.TransferKeeper)) + + // Seal the IBC Routers. + app.IBCKeeper.SetRouter(ibcRouter) + app.IBCKeeper.SetRouterV2(ibcRouterV2) + + clientKeeper := app.IBCKeeper.ClientKeeper + storeProvider := app.IBCKeeper.ClientKeeper.GetStoreProvider() + + tmLightClientModule := ibctm.NewLightClientModule(appCodec, storeProvider) + clientKeeper.AddRoute(ibctm.ModuleName, &tmLightClientModule) + + smLightClientModule := solomachine.NewLightClientModule(appCodec, storeProvider) + clientKeeper.AddRoute(solomachine.ModuleName, &smLightClientModule) + + wasmLightClientModule := ibcwasm.NewLightClientModule(app.WasmClientKeeper, storeProvider) + clientKeeper.AddRoute(ibcwasmtypes.ModuleName, &wasmLightClientModule) + + // create evidence keeper with router + evidenceKeeper := evidencekeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[evidencetypes.StoreKey]), app.StakingKeeper, app.SlashingKeeper, app.AccountKeeper.AddressCodec(), runtime.ProvideCometInfoService(), + ) + // If evidence needs to be handled for the app, set routes in router here and seal + app.EvidenceKeeper = *evidenceKeeper + + // **** Module Options **** + + // NOTE: we may consider parsing `appOpts` inside module constructors. For the moment + // we prefer to be more strict in what arguments the modules expect. + skipGenesisInvariants := cast.ToBool(appOpts.Get(crisis.FlagSkipGenesisInvariants)) + + // NOTE: Any module instantiated in the module manager that is later modified + // must be passed by reference here. + app.ModuleManager = module.NewManager( + genutil.NewAppModule( + app.AccountKeeper, app.StakingKeeper, app, + txConfig, + ), + auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, app.GetSubspace(authtypes.ModuleName)), + vesting.NewAppModule(app.AccountKeeper, app.BankKeeper), + bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper, app.GetSubspace(banktypes.ModuleName)), + crisis.NewAppModule(app.CrisisKeeper, skipGenesisInvariants, app.GetSubspace(crisistypes.ModuleName)), + feegrantmodule.NewAppModule(appCodec, app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, app.interfaceRegistry), + gov.NewAppModule(appCodec, &app.GovKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(govtypes.ModuleName)), + mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper, nil, app.GetSubspace(minttypes.ModuleName)), + slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(slashingtypes.ModuleName), app.interfaceRegistry), + distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(distrtypes.ModuleName)), + staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName)), + upgrade.NewAppModule(app.UpgradeKeeper, app.AccountKeeper.AddressCodec()), + evidence.NewAppModule(app.EvidenceKeeper), + params.NewAppModule(app.ParamsKeeper), + authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), + groupmodule.NewAppModule(appCodec, app.GroupKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), + consensus.NewAppModule(appCodec, app.ConsensusParamsKeeper), + circuit.NewAppModule(appCodec, app.CircuitKeeper), + + // IBC modules + ibc.NewAppModule(app.IBCKeeper), + transfer.NewAppModule(app.TransferKeeper), + ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper), + mockModule, + + // IBC light clients + ibcwasm.NewAppModule(app.WasmClientKeeper), // TODO(damian): see if we want to pass the lightclient module here, keeper is used in AppModule.RegisterServices etc + ibctm.NewAppModule(tmLightClientModule), + solomachine.NewAppModule(smLightClientModule), + ) + + // BasicModuleManager defines the module BasicManager is in charge of setting up basic, + // non-dependant module elements, such as codec registration and genesis verification. + // By default it is composed of all the module from the module manager. + // Additionally, app module basics can be overwritten by passing them as argument. + app.BasicModuleManager = module.NewBasicManagerFromManager( + app.ModuleManager, + map[string]module.AppModuleBasic{ + genutiltypes.ModuleName: genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator), + govtypes.ModuleName: gov.NewAppModuleBasic( + []govclient.ProposalHandler{ + paramsclient.ProposalHandler, + }, + ), + }) + app.BasicModuleManager.RegisterLegacyAminoCodec(legacyAmino) + app.BasicModuleManager.RegisterInterfaces(interfaceRegistry) + + // NOTE: upgrade module is required to be prioritized + app.ModuleManager.SetOrderPreBlockers( + upgradetypes.ModuleName, + authtypes.ModuleName, + ) + + // During begin block slashing happens after distr.BeginBlocker so that + // there is nothing left over in the validator fee pool, so as to keep the + // CanWithdrawInvariant invariant. + // NOTE: staking module is required if HistoricalEntries param > 0 + app.ModuleManager.SetOrderBeginBlockers( + minttypes.ModuleName, + distrtypes.ModuleName, + slashingtypes.ModuleName, + evidencetypes.ModuleName, + stakingtypes.ModuleName, + ibcexported.ModuleName, + ibctransfertypes.ModuleName, + genutiltypes.ModuleName, + authz.ModuleName, + icatypes.ModuleName, + ibcwasmtypes.ModuleName, + ibcmock.ModuleName, + ) + app.ModuleManager.SetOrderEndBlockers( + crisistypes.ModuleName, + govtypes.ModuleName, + stakingtypes.ModuleName, + ibcexported.ModuleName, + ibctransfertypes.ModuleName, + genutiltypes.ModuleName, + feegrant.ModuleName, + icatypes.ModuleName, + ibcwasmtypes.ModuleName, + ibcmock.ModuleName, + group.ModuleName, + ) + + // NOTE: The genutils module must occur after staking so that pools are + // properly initialized with tokens from genesis accounts. + // NOTE: The genutils module must also occur after auth so that it can access the params from auth. + genesisModuleOrder := []string{ + authtypes.ModuleName, + banktypes.ModuleName, distrtypes.ModuleName, stakingtypes.ModuleName, + slashingtypes.ModuleName, govtypes.ModuleName, minttypes.ModuleName, crisistypes.ModuleName, + ibcexported.ModuleName, genutiltypes.ModuleName, evidencetypes.ModuleName, authz.ModuleName, ibctransfertypes.ModuleName, + icatypes.ModuleName, ibcmock.ModuleName, feegrant.ModuleName, paramstypes.ModuleName, upgradetypes.ModuleName, + vestingtypes.ModuleName, group.ModuleName, consensusparamtypes.ModuleName, circuittypes.ModuleName, ibcwasmtypes.ModuleName, + } + app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...) + app.ModuleManager.SetOrderExportGenesis(genesisModuleOrder...) + + // Uncomment if you want to set a custom migration order here. + // app.ModuleManager.SetOrderMigrations(custom order) + + app.ModuleManager.RegisterInvariants(app.CrisisKeeper) + app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter()) + err := app.ModuleManager.RegisterServices(app.configurator) + if err != nil { + panic(err) + } + + autocliv1.RegisterQueryServer(app.GRPCQueryRouter(), runtimeservices.NewAutoCLIQueryService(app.ModuleManager.Modules)) + + reflectionSvc, err := runtimeservices.NewReflectionService() + if err != nil { + panic(err) + } + reflectionv1.RegisterReflectionServiceServer(app.GRPCQueryRouter(), reflectionSvc) + + // registerUpgradeHandlers is used for registering any on-chain upgrades. + // Make sure it's called after `app.ModuleManager` and `app.configurator` are set. + app.registerUpgradeHandlers() + + // add test gRPC service for testing gRPC queries in isolation + testpb.RegisterQueryServer(app.GRPCQueryRouter(), testpb.QueryImpl{}) + + // create the simulation manager and define the order of the modules for deterministic simulations + // + // NOTE: this is not required apps that don't use the simulator for fuzz testing + // transactions + overrideModules := map[string]module.AppModuleSimulation{ + authtypes.ModuleName: auth.NewAppModule(app.appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, app.GetSubspace(authtypes.ModuleName)), + } + app.simulationManager = module.NewSimulationManagerFromAppModules(app.ModuleManager.Modules, overrideModules) + + app.simulationManager.RegisterStoreDecoders() + + // initialize stores + app.MountKVStores(keys) + app.MountTransientStores(tkeys) + app.MountMemoryStores(memKeys) + + // initialize BaseApp + app.SetInitChainer(app.InitChainer) + app.SetPreBlocker(app.PreBlocker) + app.SetBeginBlocker(app.BeginBlocker) + app.SetEndBlocker(app.EndBlocker) + app.setAnteHandler(txConfig) + + // must be before Loading version + if manager := app.SnapshotManager(); manager != nil { + err := manager.RegisterExtensions( + ibcwasmkeeper.NewWasmSnapshotter(app.CommitMultiStore(), &app.WasmClientKeeper), + ) + if err != nil { + panic(fmt.Errorf("failed to register snapshot extension: %s", err)) + } + } + + // In v0.46, the SDK introduces _postHandlers_. PostHandlers are like + // antehandlers, but are run _after_ the `runMsgs` execution. They are also + // defined as a chain, and have the same signature as antehandlers. + // + // In baseapp, postHandlers are run in the same store branch as `runMsgs`, + // meaning that both `runMsgs` and `postHandler` state will be committed if + // both are successful, and both will be reverted if any of the two fails. + // + // The SDK exposes a default postHandlers chain, which is comprised of only + // one decorator: the Transaction Tips decorator. However, some chains do + // not need it by default, so feel free to comment the next line if you do + // not need tips. + // To read more about tips: + // https://docs.cosmos.network/main/core/tips.html + // + // Please note that changing any of the anteHandler or postHandler chain is + // likely to be a state-machine breaking change, which needs a coordinated + // upgrade. + app.setPostHandler() + + // At startup, after all modules have been registered, check that all proto + // annotations are correct. + protoFiles, err := proto.MergedRegistry() + if err != nil { + panic(err) + } + err = msgservice.ValidateProtoAnnotations(protoFiles) + if err != nil { + // Once we switch to using protoreflect-based antehandlers, we might + // want to panic here instead of logging a warning. + _, err := fmt.Fprintln(os.Stderr, err.Error()) + if err != nil { + fmt.Println("could not write to stderr") + } + } + + if loadLatest { + if err := app.LoadLatestVersion(); err != nil { + panic(fmt.Errorf("error loading last version: %w", err)) + } + + ctx := app.NewUncachedContext(true, cmtproto.Header{}) + + // Initialize pinned codes in wasmvm as they are not persisted there + if err := app.WasmClientKeeper.InitializePinnedCodes(ctx); err != nil { + cmtos.Exit(fmt.Sprintf("failed initialize pinned codes %s", err)) + } + } + + return app +} + +func (app *SimApp) setAnteHandler(txConfig client.TxConfig) { + anteHandler, err := NewAnteHandler( + HandlerOptions{ + HandlerOptions: ante.HandlerOptions{ + AccountKeeper: app.AccountKeeper, + BankKeeper: app.BankKeeper, + FeegrantKeeper: app.FeeGrantKeeper, + SignModeHandler: txConfig.SignModeHandler(), + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + }, + IBCKeeper: app.IBCKeeper, + }, + ) + if err != nil { + panic(err) + } + + // Set the AnteHandler for the app + app.SetAnteHandler(anteHandler) +} + +func (app *SimApp) setPostHandler() { + postHandler, err := posthandler.NewPostHandler( + posthandler.HandlerOptions{}, + ) + if err != nil { + panic(err) + } + + app.SetPostHandler(postHandler) +} + +// Name returns the name of the App +func (app *SimApp) Name() string { return app.BaseApp.Name() } + +// PreBlocker application updates every pre block +func (app *SimApp) PreBlocker(ctx sdk.Context, _ *abci.RequestFinalizeBlock) (*sdk.ResponsePreBlock, error) { + return app.ModuleManager.PreBlock(ctx) +} + +// BeginBlocker application updates every begin block +func (app *SimApp) BeginBlocker(ctx sdk.Context) (sdk.BeginBlock, error) { + return app.ModuleManager.BeginBlock(ctx) +} + +// EndBlocker application updates every end block +func (app *SimApp) EndBlocker(ctx sdk.Context) (sdk.EndBlock, error) { + return app.ModuleManager.EndBlock(ctx) +} + +// Configurator returns the configurator for the app +func (app *SimApp) Configurator() module.Configurator { + return app.configurator +} + +// InitChainer application update at chain initialization +func (app *SimApp) InitChainer(ctx sdk.Context, req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { + var genesisState GenesisState + if err := json.Unmarshal(req.AppStateBytes, &genesisState); err != nil { + panic(err) + } + if err := app.UpgradeKeeper.SetModuleVersionMap(ctx, app.ModuleManager.GetVersionMap()); err != nil { + panic(err) + } + return app.ModuleManager.InitGenesis(ctx, app.appCodec, genesisState) +} + +// LoadHeight loads a particular height +func (app *SimApp) LoadHeight(height int64) error { + return app.LoadVersion(height) +} + +// LegacyAmino returns SimApp's amino codec. +// +// NOTE: This is solely to be used for testing purposes as it may be desirable +// for modules to register their own custom testing types. +func (app *SimApp) LegacyAmino() *codec.LegacyAmino { + return app.legacyAmino +} + +// AppCodec returns SimApp's app codec. +// +// NOTE: This is solely to be used for testing purposes as it may be desirable +// for modules to register their own custom testing types. +func (app *SimApp) AppCodec() codec.Codec { + return app.appCodec +} + +// InterfaceRegistry returns SimApp's InterfaceRegistry +func (app *SimApp) InterfaceRegistry() types.InterfaceRegistry { + return app.interfaceRegistry +} + +// TxConfig returns SimApp's TxConfig +func (app *SimApp) TxConfig() client.TxConfig { + return app.txConfig +} + +// AutoCliOpts returns the autocli options for the app. +func (app *SimApp) AutoCliOpts() autocli.AppOptions { + modules := make(map[string]appmodule.AppModule, 0) + for _, m := range app.ModuleManager.Modules { + if moduleWithName, ok := m.(module.HasName); ok { + moduleName := moduleWithName.Name() + if appModule, ok := moduleWithName.(appmodule.AppModule); ok { + modules[moduleName] = appModule + } + } + } + + return autocli.AppOptions{ + Modules: modules, + ModuleOptions: runtimeservices.ExtractAutoCLIOptions(app.ModuleManager.Modules), + } +} + +// DefaultGenesis returns a default genesis from the registered AppModuleBasic's. +func (app *SimApp) DefaultGenesis() map[string]json.RawMessage { + return app.BasicModuleManager.DefaultGenesis(app.appCodec) +} + +// GetKey returns the KVStoreKey for the provided store key. +// +// NOTE: This is solely to be used for testing purposes. +func (app *SimApp) GetKey(storeKey string) *storetypes.KVStoreKey { + return app.keys[storeKey] +} + +// GetStoreKeys returns all the stored store keys. +func (app *SimApp) GetStoreKeys() []storetypes.StoreKey { + keys := make([]storetypes.StoreKey, 0, len(app.keys)) + for _, key := range app.keys { + keys = append(keys, key) + } + + return keys +} + +// GetSubspace returns a param subspace for a given module name. +// +// NOTE: This is solely to be used for testing purposes. +func (app *SimApp) GetSubspace(moduleName string) paramstypes.Subspace { + subspace, _ := app.ParamsKeeper.GetSubspace(moduleName) + return subspace +} + +// SimulationManager implements the SimulationApp interface +func (app *SimApp) SimulationManager() *module.SimulationManager { + return app.simulationManager +} + +// RegisterAPIRoutes registers all application module routes with the provided +// API server. +func (app *SimApp) RegisterAPIRoutes(apiSvr *api.Server, apiConfig config.APIConfig) { + clientCtx := apiSvr.ClientCtx + // Register new tx routes from grpc-gateway. + authtx.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // Register new CometBFT queries routes from grpc-gateway. + cmtservice.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // Register node gRPC service for grpc-gateway. + nodeservice.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // Register grpc-gateway routes for all modules. + app.BasicModuleManager.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // register swagger API from root so that other applications can override easily + if err := server.RegisterSwaggerAPI(apiSvr.ClientCtx, apiSvr.Router, apiConfig.Swagger); err != nil { + panic(err) + } +} + +// RegisterTxService implements the Application.RegisterTxService method. +func (app *SimApp) RegisterTxService(clientCtx client.Context) { + authtx.RegisterTxService(app.GRPCQueryRouter(), clientCtx, app.Simulate, app.interfaceRegistry) +} + +// RegisterTendermintService implements the Application.RegisterTendermintService method. +func (app *SimApp) RegisterTendermintService(clientCtx client.Context) { + cmtApp := server.NewCometABCIWrapper(app) + cmtservice.RegisterTendermintService( + clientCtx, + app.GRPCQueryRouter(), + app.interfaceRegistry, + cmtApp.Query, + ) +} + +func (app *SimApp) RegisterNodeService(clientCtx client.Context, cfg config.Config) { + nodeservice.RegisterNodeService(clientCtx, app.GRPCQueryRouter(), cfg) +} + +// GetMaccPerms returns a copy of the module account permissions +// +// NOTE: This is solely to be used for testing purposes. +func GetMaccPerms() map[string][]string { + dupMaccPerms := make(map[string][]string) + maps.Copy(dupMaccPerms, maccPerms) + + return dupMaccPerms +} + +// BlockedAddresses returns all the app's blocked account addresses. +func BlockedAddresses() map[string]bool { + modAccAddrs := make(map[string]bool) + for acc := range GetMaccPerms() { + modAccAddrs[authtypes.NewModuleAddress(acc).String()] = true + } + + // allow the following addresses to receive funds + delete(modAccAddrs, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + delete(modAccAddrs, authtypes.NewModuleAddress(ibcmock.ModuleName).String()) + + return modAccAddrs +} + +// initParamsKeeper init params keeper and its subspaces +func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey storetypes.StoreKey) paramskeeper.Keeper { + paramsKeeper := paramskeeper.NewKeeper(appCodec, legacyAmino, key, tkey) + + // register the key tables for legacy param subspaces + keyTable := ibcclienttypes.ParamKeyTable() + keyTable.RegisterParamSet(&ibcconnectiontypes.Params{}) + paramsKeeper.Subspace(ibcexported.ModuleName).WithKeyTable(keyTable) + paramsKeeper.Subspace(ibctransfertypes.ModuleName).WithKeyTable(ibctransfertypes.ParamKeyTable()) + paramsKeeper.Subspace(icacontrollertypes.SubModuleName).WithKeyTable(icacontrollertypes.ParamKeyTable()) + paramsKeeper.Subspace(icahosttypes.SubModuleName).WithKeyTable(icahosttypes.ParamKeyTable()) + + return paramsKeeper +} + +// IBC TestingApp functions + +// GetBaseApp implements the TestingApp interface. +func (app *SimApp) GetBaseApp() *baseapp.BaseApp { + return app.BaseApp +} + +// GetIBCKeeper implements the TestingApp interface. +func (app *SimApp) GetIBCKeeper() *ibckeeper.Keeper { + return app.IBCKeeper +} + +// GetWasmKeeper implements the TestingApp interface. +func (app *SimApp) GetWasmKeeper() ibcwasmkeeper.Keeper { + return app.WasmClientKeeper +} + +// GetTxConfig implements the TestingApp interface. +func (app *SimApp) GetTxConfig() client.TxConfig { + return app.txConfig +} + +// GetMemKey returns the MemStoreKey for the provided mem key. +// +// NOTE: This is solely used for testing purposes. +func (app *SimApp) GetMemKey(storeKey string) *storetypes.MemoryStoreKey { + return app.memKeys[storeKey] +} diff --git a/modules/light-clients/08-wasm/testing/simapp/encoding.go b/modules/light-clients/08-wasm/testing/simapp/encoding.go new file mode 100644 index 0000000..e0f7b4f --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/encoding.go @@ -0,0 +1,18 @@ +package simapp + +import ( + "github.com/cosmos/cosmos-sdk/std" + + simappparams "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing/simapp/params" +) + +// MakeTestEncodingConfig creates an EncodingConfig for testing. This function +// should be used only in tests or when creating a new app instance (NewApp*()). +// App user shouldn't create new codecs - use the app.AppCodec instead. +// [DEPRECATED] +func MakeTestEncodingConfig() simappparams.EncodingConfig { + encodingConfig := simappparams.MakeTestEncodingConfig() + std.RegisterLegacyAminoCodec(encodingConfig.Amino) + std.RegisterInterfaces(encodingConfig.InterfaceRegistry) + return encodingConfig +} diff --git a/modules/light-clients/08-wasm/testing/simapp/export.go b/modules/light-clients/08-wasm/testing/simapp/export.go new file mode 100644 index 0000000..688ec63 --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/export.go @@ -0,0 +1,246 @@ +package simapp + +import ( + "encoding/json" + "errors" + "log" + + storetypes "cosmossdk.io/store/types" + + servertypes "github.com/cosmos/cosmos-sdk/server/types" + sdk "github.com/cosmos/cosmos-sdk/types" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +// ExportAppStateAndValidators exports the state of the application for a genesis +// file. +func (app *SimApp) ExportAppStateAndValidators( + forZeroHeight bool, jailAllowedAddrs []string, modulesToExport []string, +) (servertypes.ExportedApp, error) { + // as if they could withdraw from the start of the next block + ctx := app.NewContext(true) + + // We export at last height + 1, because that's the height at which + // Tendermint will start InitChain. + height := app.LastBlockHeight() + 1 + if forZeroHeight { + height = 0 + app.prepForZeroHeightGenesis(ctx, jailAllowedAddrs) + } + + genState, err := app.ModuleManager.ExportGenesis(ctx, app.appCodec) + if err != nil { + return servertypes.ExportedApp{}, err + } + appState, err := json.MarshalIndent(genState, "", " ") + if err != nil { + return servertypes.ExportedApp{}, err + } + + validators, err := staking.WriteValidators(ctx, app.StakingKeeper) + return servertypes.ExportedApp{ + AppState: appState, + Validators: validators, + Height: height, + ConsensusParams: app.GetConsensusParams(ctx), + }, err +} + +// prepare for fresh start at zero height +// NOTE zero height genesis is a temporary feature which will be deprecated +// in favour of export at a block height +func (app *SimApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []string) { + applyAllowedAddrs := len(jailAllowedAddrs) > 0 + + // check if there is an allowed address list + + allowedAddrsMap := make(map[string]bool) + + for _, addr := range jailAllowedAddrs { + _, err := sdk.ValAddressFromBech32(addr) + if err != nil { + log.Fatal(err) + } + allowedAddrsMap[addr] = true + } + + /* Just to be safe, assert the invariants on current state. */ + app.CrisisKeeper.AssertInvariants(ctx) + + /* Handle fee distribution state. */ + + // withdraw all validator commission + err := app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { + valBz, err := app.StakingKeeper.ValidatorAddressCodec().StringToBytes(val.GetOperator()) + if err != nil { + panic(err) + } + _, _ = app.DistrKeeper.WithdrawValidatorCommission(ctx, valBz) + return false + }) + if err != nil { + panic(err) + } + + // withdraw all delegator rewards + dels, err := app.StakingKeeper.GetAllDelegations(ctx) + if err != nil { + panic(err) + } + for _, delegation := range dels { + valAddr, err := sdk.ValAddressFromBech32(delegation.ValidatorAddress) + if err != nil { + panic(err) + } + + delAddr, err := sdk.AccAddressFromBech32(delegation.DelegatorAddress) + if err != nil { + panic(err) + } + _, _ = app.DistrKeeper.WithdrawDelegationRewards(ctx, delAddr, valAddr) + } + + // clear validator slash events + app.DistrKeeper.DeleteAllValidatorSlashEvents(ctx) + + // clear validator historical rewards + app.DistrKeeper.DeleteAllValidatorHistoricalRewards(ctx) + + // set context height to zero + height := ctx.BlockHeight() + ctx = ctx.WithBlockHeight(0) + + // reinitialize all validators + err = app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { + valBz, err := sdk.ValAddressFromBech32(val.GetOperator()) + if err != nil { + panic(err) + } + // donate any unwithdrawn outstanding reward fraction tokens to the community pool + scraps, err := app.DistrKeeper.GetValidatorOutstandingRewardsCoins(ctx, valBz) + if err != nil { + panic(err) + } + feePool, err := app.DistrKeeper.FeePool.Get(ctx) + if err != nil { + panic(err) + } + feePool.CommunityPool = feePool.CommunityPool.Add(scraps...) + if err := app.DistrKeeper.FeePool.Set(ctx, feePool); err != nil { + panic(err) + } + + if err := app.DistrKeeper.Hooks().AfterValidatorCreated(ctx, valBz); err != nil { + panic(err) + } + return false + }) + if err != nil { + panic(err) + } + + // reinitialize all delegations + for _, del := range dels { + valAddr, err := sdk.ValAddressFromBech32(del.ValidatorAddress) + if err != nil { + panic(err) + } + delAddr, err := sdk.AccAddressFromBech32(del.DelegatorAddress) + if err != nil { + panic(err) + } + err = app.DistrKeeper.Hooks().BeforeDelegationCreated(ctx, delAddr, valAddr) + if err != nil { + panic(err) + } + err = app.DistrKeeper.Hooks().AfterDelegationModified(ctx, delAddr, valAddr) + if err != nil { + panic(err) + } + } + + // reset context height + ctx = ctx.WithBlockHeight(height) + + /* Handle staking state. */ + + // iterate through redelegations, reset creation height + err = app.StakingKeeper.IterateRedelegations(ctx, func(_ int64, red stakingtypes.Redelegation) (stop bool) { + for i := range red.Entries { + red.Entries[i].CreationHeight = 0 + } + err := app.StakingKeeper.SetRedelegation(ctx, red) + if err != nil { + panic(err) + } + return false + }) + if err != nil { + panic(err) + } + + // iterate through unbonding delegations, reset creation height + err = app.StakingKeeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd stakingtypes.UnbondingDelegation) (stop bool) { + for i := range ubd.Entries { + ubd.Entries[i].CreationHeight = 0 + } + + if err := app.StakingKeeper.SetUnbondingDelegation(ctx, ubd); err != nil { + panic(err) + } + return false + }) + if err != nil { + panic(err) + } + + // Iterate through validators by power descending, reset bond heights, and + // update bond intra-tx counters. + store := ctx.KVStore(app.keys[stakingtypes.StoreKey]) + iter := storetypes.KVStoreReversePrefixIterator(store, stakingtypes.ValidatorsKey) + counter := int16(0) + + for ; iter.Valid(); iter.Next() { + addr := sdk.ValAddress(stakingtypes.AddressFromValidatorsKey(iter.Key())) + validator, err := app.StakingKeeper.GetValidator(ctx, addr) + if err != nil { + panic(errors.New("expected validator, not found")) + } + + validator.UnbondingHeight = 0 + if applyAllowedAddrs && !allowedAddrsMap[addr.String()] { + validator.Jailed = true + } + + if err := app.StakingKeeper.SetValidator(ctx, validator); err != nil { + panic(err) + } + counter++ + } + + iter.Close() + + _, err = app.StakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx) + if err != nil { + log.Fatal(err) + } + + /* Handle slashing state. */ + + // reset start height on signing infos + err = app.SlashingKeeper.IterateValidatorSigningInfos( + ctx, + func(addr sdk.ConsAddress, info slashingtypes.ValidatorSigningInfo) (stop bool) { + info.StartHeight = 0 + if err := app.SlashingKeeper.SetValidatorSigningInfo(ctx, addr, info); err != nil { + panic(err) + } + return false + }, + ) + if err != nil { + panic(err) + } +} diff --git a/modules/light-clients/08-wasm/testing/simapp/genesis.go b/modules/light-clients/08-wasm/testing/simapp/genesis.go new file mode 100644 index 0000000..e7b97fc --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/genesis.go @@ -0,0 +1,14 @@ +package simapp + +import ( + "encoding/json" +) + +// The genesis state of the blockchain is represented here as a map of raw json +// messages key'd by an identifier string. +// The identifier is used to determine which module genesis information belongs +// to so it may be appropriately routed during init chain. +// Within this application default genesis information is retrieved from +// the ModuleBasicManager which populates json from each BasicModule +// object provided to it during init. +type GenesisState map[string]json.RawMessage diff --git a/modules/light-clients/08-wasm/testing/simapp/genesis_account.go b/modules/light-clients/08-wasm/testing/simapp/genesis_account.go new file mode 100644 index 0000000..0af1360 --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/genesis_account.go @@ -0,0 +1,47 @@ +package simapp + +import ( + "errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +var _ authtypes.GenesisAccount = (*SimGenesisAccount)(nil) + +// SimGenesisAccount defines a type that implements the GenesisAccount interface +// to be used for simulation accounts in the genesis state. +type SimGenesisAccount struct { + *authtypes.BaseAccount + + // vesting account fields + OriginalVesting sdk.Coins `json:"original_vesting" yaml:"original_vesting"` // total vesting coins upon initialization + DelegatedFree sdk.Coins `json:"delegated_free" yaml:"delegated_free"` // delegated vested coins at time of delegation + DelegatedVesting sdk.Coins `json:"delegated_vesting" yaml:"delegated_vesting"` // delegated vesting coins at time of delegation + StartTime int64 `json:"start_time" yaml:"start_time"` // vesting start time (UNIX Epoch time) + EndTime int64 `json:"end_time" yaml:"end_time"` // vesting end time (UNIX Epoch time) + + // module account fields + ModuleName string `json:"module_name" yaml:"module_name"` // name of the module account + ModulePermissions []string `json:"module_permissions" yaml:"module_permissions"` // permissions of module account +} + +// Validate checks for errors on the vesting and module account parameters +func (sga SimGenesisAccount) Validate() error { + if !sga.OriginalVesting.IsZero() { + if sga.StartTime >= sga.EndTime { + return errors.New("vesting start-time cannot be before end-time") + } + } + + if sga.ModuleName != "" { + ma := authtypes.ModuleAccount{ + BaseAccount: sga.BaseAccount, Name: sga.ModuleName, Permissions: sga.ModulePermissions, + } + if err := ma.Validate(); err != nil { + return err + } + } + + return sga.BaseAccount.Validate() +} diff --git a/modules/light-clients/08-wasm/testing/simapp/params/amino.go b/modules/light-clients/08-wasm/testing/simapp/params/amino.go new file mode 100644 index 0000000..93d644a --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/params/amino.go @@ -0,0 +1,27 @@ +//go:build test_amino +// +build test_amino + +package params + +import ( + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" +) + +// MakeTestEncodingConfig creates an EncodingConfig for an amino based test configuration. +// This function should be used only internally (in the SDK). +// App user shouldn't create new codecs - use the app.AppCodec instead. +// [DEPRECATED] +func MakeTestEncodingConfig() EncodingConfig { + cdc := codec.NewLegacyAmino() + interfaceRegistry := types.NewInterfaceRegistry() + marshaler := codec.NewAminoCodec(cdc) + + return EncodingConfig{ + InterfaceRegistry: interfaceRegistry, + Marshaler: marshaler, + TxConfig: legacytx.StdTxConfig{Cdc: cdc}, + Amino: cdc, + } +} diff --git a/modules/light-clients/08-wasm/testing/simapp/params/doc.go b/modules/light-clients/08-wasm/testing/simapp/params/doc.go new file mode 100644 index 0000000..e7278cb --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/params/doc.go @@ -0,0 +1,19 @@ +/* +Package params defines the simulation parameters in the simapp. + +It contains the default weights used for each transaction used on the module's +simulation. These weights define the chance for a transaction to be simulated at +any given operation. + +You can replace the default values for the weights by providing a params.json +file with the weights defined for each of the transaction operations: + + { + "op_weight_msg_send": 60, + "op_weight_msg_delegate": 100, + } + +In the example above, the `MsgSend` has 60% chance to be simulated, while the +`MsgDelegate` will always be simulated. +*/ +package params diff --git a/modules/light-clients/08-wasm/testing/simapp/params/encoding.go b/modules/light-clients/08-wasm/testing/simapp/params/encoding.go new file mode 100644 index 0000000..2612bb8 --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/params/encoding.go @@ -0,0 +1,16 @@ +package params + +import ( + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" +) + +// EncodingConfig specifies the concrete encoding types to use for a given app. +// This is provided for compatibility between protobuf and amino implementations. +type EncodingConfig struct { + InterfaceRegistry types.InterfaceRegistry + Codec codec.Codec + TxConfig client.TxConfig + Amino *codec.LegacyAmino +} diff --git a/modules/light-clients/08-wasm/testing/simapp/params/proto.go b/modules/light-clients/08-wasm/testing/simapp/params/proto.go new file mode 100644 index 0000000..32a95f1 --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/params/proto.go @@ -0,0 +1,26 @@ +//go:build !test_amino + +package params + +import ( + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/x/auth/tx" +) + +// MakeTestEncodingConfig creates an EncodingConfig for a non-amino based test configuration. +// This function should be used only internally (in the SDK). +// App user shouldn't create new codecs - use the app.AppCodec instead. +// [DEPRECATED] +func MakeTestEncodingConfig() EncodingConfig { + cdc := codec.NewLegacyAmino() + interfaceRegistry := types.NewInterfaceRegistry() + protoCdc := codec.NewProtoCodec(interfaceRegistry) + + return EncodingConfig{ + InterfaceRegistry: interfaceRegistry, + Codec: protoCdc, + TxConfig: tx.NewTxConfig(protoCdc, tx.DefaultSignModes), + Amino: cdc, + } +} diff --git a/modules/light-clients/08-wasm/testing/simapp/simd/cmd/root.go b/modules/light-clients/08-wasm/testing/simapp/simd/cmd/root.go new file mode 100644 index 0000000..d6ecf8e --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/simd/cmd/root.go @@ -0,0 +1,422 @@ +package cmd + +import ( + "errors" + "fmt" + "io" + "os" + "runtime/debug" + "strings" + + wasmvm "github.com/CosmWasm/wasmvm/v2" + dbm "github.com/cosmos/cosmos-db" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "cosmossdk.io/client/v2/autocli" + "cosmossdk.io/log" + confixcmd "cosmossdk.io/tools/confix/cmd" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/config" + sdkdebug "github.com/cosmos/cosmos-sdk/client/debug" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/client/pruning" + "github.com/cosmos/cosmos-sdk/client/rpc" + "github.com/cosmos/cosmos-sdk/client/snapshot" + "github.com/cosmos/cosmos-sdk/codec" + addresscodec "github.com/cosmos/cosmos-sdk/codec/address" + "github.com/cosmos/cosmos-sdk/server" + serverconfig "github.com/cosmos/cosmos-sdk/server/config" + servertypes "github.com/cosmos/cosmos-sdk/server/types" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + "github.com/cosmos/cosmos-sdk/x/auth/tx" + txmodule "github.com/cosmos/cosmos-sdk/x/auth/tx/config" + "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/crisis" + genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" + + cmtcfg "github.com/cometbft/cometbft/config" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing/simapp" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing/simapp/params" +) + +// NewRootCmd creates a new root command for simd. It is called once in the +// main function. +func NewRootCmd() *cobra.Command { + // we "pre"-instantiate the application for getting the injected/configured encoding configuration + // note, this is not necessary when using app wiring, as depinject can be directly used (see root_v2.go) + tempApp := simapp.NewBinarySimApp(log.NewNopLogger(), dbm.NewMemDB(), nil, true, simtestutil.NewAppOptionsWithFlagHome(tempDir()), nil) + encodingConfig := params.EncodingConfig{ + InterfaceRegistry: tempApp.InterfaceRegistry(), + Codec: tempApp.AppCodec(), + TxConfig: tempApp.TxConfig(), + Amino: tempApp.LegacyAmino(), + } + + initClientCtx := client.Context{}. + WithCodec(encodingConfig.Codec). + WithInterfaceRegistry(encodingConfig.InterfaceRegistry). + WithLegacyAmino(encodingConfig.Amino). + WithInput(os.Stdin). + WithAccountRetriever(types.AccountRetriever{}). + WithHomeDir(simapp.DefaultNodeHome). + WithViper("") // In simapp, we don't use any prefix for env variables. + + rootCmd := &cobra.Command{ + Use: "simd", + Short: "simulation app", + SilenceErrors: true, + PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { + // set the default command outputs + cmd.SetOut(cmd.OutOrStdout()) + cmd.SetErr(cmd.ErrOrStderr()) + + initClientCtx = initClientCtx.WithCmdContext(cmd.Context()) + initClientCtx, err := client.ReadPersistentCommandFlags(initClientCtx, cmd.Flags()) + if err != nil { + return err + } + + initClientCtx, err = config.ReadFromClientConfig(initClientCtx) + if err != nil { + return err + } + + // This needs to go after ReadFromClientConfig, as that function + // sets the RPC client needed for SIGN_MODE_TEXTUAL. + enabledSignModes := append(tx.DefaultSignModes, signing.SignMode_SIGN_MODE_TEXTUAL) //nolint:gocritic // we know we aren't appending to the same slice + txConfigOpts := tx.ConfigOptions{ + EnabledSignModes: enabledSignModes, + TextualCoinMetadataQueryFn: txmodule.NewGRPCCoinMetadataQueryFn(initClientCtx), + } + txConfigWithTextual, err := tx.NewTxConfigWithOptions( + codec.NewProtoCodec(encodingConfig.InterfaceRegistry), + txConfigOpts, + ) + if err != nil { + return err + } + initClientCtx = initClientCtx.WithTxConfig(txConfigWithTextual) + + if err := client.SetCmdClientContextHandler(initClientCtx, cmd); err != nil { + return err + } + + customAppTemplate, customAppConfig := initAppConfig() + customCMTConfig := initCometBFTConfig() + + return server.InterceptConfigsPreRunHandler(cmd, customAppTemplate, customAppConfig, customCMTConfig) + }, + } + + initRootCmd(rootCmd, encodingConfig, tempApp.BasicModuleManager) + + autoCliOpts, err := enrichAutoCliOpts(tempApp.AutoCliOpts(), initClientCtx) + if err != nil { + panic(err) + } + + if err := autoCliOpts.EnhanceRootCommand(rootCmd); err != nil { + panic(err) + } + + return rootCmd +} + +func enrichAutoCliOpts(autoCliOpts autocli.AppOptions, clientCtx client.Context) (autocli.AppOptions, error) { + autoCliOpts.AddressCodec = addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32AccountAddrPrefix()) + autoCliOpts.ValidatorAddressCodec = addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32ValidatorAddrPrefix()) + autoCliOpts.ConsensusAddressCodec = addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32ConsensusAddrPrefix()) + + var err error + clientCtx, err = config.ReadFromClientConfig(clientCtx) + if err != nil { + return autocli.AppOptions{}, err + } + + autoCliOpts.ClientCtx = clientCtx + + return autoCliOpts, nil +} + +// initCometBFTConfig helps to override default CometBFT Config values. +// return cmtcfg.DefaultConfig if no custom configuration is required for the application. +func initCometBFTConfig() *cmtcfg.Config { + cfg := cmtcfg.DefaultConfig() + + // these values put a higher strain on node memory + // cfg.P2P.MaxNumInboundPeers = 100 + // cfg.P2P.MaxNumOutboundPeers = 40 + + return cfg +} + +// initAppConfig helps to override default appConfig template and configs. +// return "", nil if no custom configuration is required for the application. +func initAppConfig() (string, any) { + // The following code snippet is just for reference. + + // WASMConfig defines configuration for the wasm module. + type WASMConfig struct { + // This is the maximum sdk gas (wasm and storage) that we allow for any x/wasm "smart" queries + QueryGasLimit uint64 `mapstructure:"query_gas_limit"` + + // Address defines the gRPC-web server to listen on + LruSize uint64 `mapstructure:"lru_size"` + } + + type CustomAppConfig struct { + serverconfig.Config + + WASM WASMConfig `mapstructure:"wasm"` + } + + // Optionally allow the chain developer to overwrite the SDK's default + // server config. + srvCfg := serverconfig.DefaultConfig() + // The SDK's default minimum gas price is set to "" (empty value) inside + // app.toml. If left empty by validators, the node will halt on startup. + // However, the chain developer can set a default app.toml value for their + // validators here. + // + // In summary: + // - if you leave srvCfg.MinGasPrices = "", all validators MUST tweak their + // own app.toml config, + // - if you set srvCfg.MinGasPrices non-empty, validators CAN tweak their + // own app.toml to override, or use this default value. + // + // In simapp, we set the min gas prices to 0. + srvCfg.MinGasPrices = "0stake" + // srvCfg.BaseConfig.IAVLDisableFastNode = true // disable fastnode by default + + customAppConfig := CustomAppConfig{ + Config: *srvCfg, + WASM: WASMConfig{ + LruSize: 1, + QueryGasLimit: 300000, + }, + } + + customAppTemplate := serverconfig.DefaultConfigTemplate + ` +[wasm] +# This is the maximum sdk gas (wasm and storage) that we allow for any x/wasm "smart" queries +query_gas_limit = 300000 +# This is the number of wasm vm instances we keep cached in memory for speed-up +# Warning: this is currently unstable and may lead to crashes, best to keep for 0 unless testing locally +lru_size = 0` + + return customAppTemplate, customAppConfig +} + +func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig, basicManager module.BasicManager) { + cfg := sdk.GetConfig() + cfg.Seal() + + rootCmd.AddCommand( + genutilcli.InitCmd(basicManager, simapp.DefaultNodeHome), + sdkdebug.Cmd(), + confixcmd.ConfigCommand(), + pruning.Cmd(newApp, simapp.DefaultNodeHome), + snapshot.Cmd(newApp), + server.QueryBlockResultsCmd(), + ) + + server.AddCommands(rootCmd, simapp.DefaultNodeHome, newApp, appExport, addModuleInitFlags) + + // add keybase, auxiliary RPC, query, genesis, and tx child commands + rootCmd.AddCommand( + server.StatusCommand(), + genesisCommand(encodingConfig, basicManager), + txCommand(), + queryCommand(), + keys.Commands(), + ) +} + +func addModuleInitFlags(startCmd *cobra.Command) { + crisis.AddModuleInitFlags(startCmd) + preCheck := func(cmd *cobra.Command, _ []string) error { + return CheckLibwasmVersion(getExpectedLibwasmVersion()) + } + startCmd.PreRunE = chainPreRuns(preCheck, startCmd.PreRunE) +} + +func queryCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "query", + Aliases: []string{"q"}, + Short: "Querying subcommands", + DisableFlagParsing: false, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand( + rpc.ValidatorCommand(), + server.QueryBlockCmd(), + authcmd.QueryTxsByEventsCmd(), + server.QueryBlocksCmd(), + authcmd.QueryTxCmd(), + authcmd.GetSimulateCmd(), + ) + + return cmd +} + +func txCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "tx", + Short: "Transactions subcommands", + DisableFlagParsing: false, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand( + authcmd.GetSignCommand(), + authcmd.GetSignBatchCommand(), + authcmd.GetMultiSignCommand(), + authcmd.GetMultiSignBatchCmd(), + authcmd.GetValidateSignaturesCommand(), + authcmd.GetBroadcastCommand(), + authcmd.GetEncodeCommand(), + authcmd.GetDecodeCommand(), + authcmd.GetSimulateCmd(), + ) + + return cmd +} + +// genesisCommand builds genesis-related `simd genesis` command. Users may provide application specific commands as a parameter +func genesisCommand(encodingConfig params.EncodingConfig, basicManager module.BasicManager, cmds ...*cobra.Command) *cobra.Command { + cmd := genutilcli.Commands(encodingConfig.TxConfig, basicManager, simapp.DefaultNodeHome) + + for _, subCmd := range cmds { + cmd.AddCommand(subCmd) + } + return cmd +} + +// newApp creates the application +func newApp( + logger log.Logger, + db dbm.DB, + traceStore io.Writer, + appOpts servertypes.AppOptions, +) servertypes.Application { + baseappOptions := server.DefaultBaseappOptions(appOpts) + + return simapp.NewBinarySimApp( + logger, db, traceStore, true, + appOpts, nil, + baseappOptions..., + ) +} + +// appExport creates a new simapp (optionally at a given height) and exports state. +func appExport( + logger log.Logger, + db dbm.DB, + traceStore io.Writer, + height int64, + forZeroHeight bool, + jailAllowedAddrs []string, + appOpts servertypes.AppOptions, + modulesToExport []string, +) (servertypes.ExportedApp, error) { + var simApp *simapp.SimApp + + // this check is necessary as we use the flag in x/upgrade. + // we can exit more gracefully by checking the flag here. + homePath, ok := appOpts.Get(flags.FlagHome).(string) + if !ok || homePath == "" { + return servertypes.ExportedApp{}, errors.New("application home not set") + } + + viperAppOpts, ok := appOpts.(*viper.Viper) + if !ok { + return servertypes.ExportedApp{}, errors.New("appOpts is not viper.Viper") + } + + // overwrite the FlagInvCheckPeriod + viperAppOpts.Set(server.FlagInvCheckPeriod, 1) + appOpts = viperAppOpts + + if height != -1 { + simApp = simapp.NewBinarySimApp(logger, db, traceStore, false, appOpts, nil) + + if err := simApp.LoadHeight(height); err != nil { + return servertypes.ExportedApp{}, err + } + } else { + simApp = simapp.NewBinarySimApp(logger, db, traceStore, true, appOpts, nil) + } + + return simApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport) +} + +var tempDir = func() string { + dir, err := os.MkdirTemp("", "simapp") + if err != nil { + dir = simapp.DefaultNodeHome + } + defer os.RemoveAll(dir) + + return dir +} + +// CheckLibwasmVersion ensures that the libwasmvm version loaded at runtime matches the version +// of the github.com/CosmWasm/wasmvm dependency in go.mod. +// Ref: https://github.com/cosmos/ibc-go/issues/4821#issuecomment-1747240445 +func CheckLibwasmVersion(wasmExpectedVersion string) error { + if wasmExpectedVersion == "" { + return errors.New("wasmvm module not exist") + } + wasmVersion, err := wasmvm.LibwasmvmVersion() + if err != nil { + return fmt.Errorf("unable to retrieve libwasmversion %w", err) + } + if !strings.Contains(wasmExpectedVersion, wasmVersion) { + return fmt.Errorf("libwasmversion mismatch. got: %s; expected: %s", wasmVersion, wasmExpectedVersion) + } + return nil +} + +type preRunFn func(cmd *cobra.Command, args []string) error + +func chainPreRuns(pfns ...preRunFn) preRunFn { + return func(cmd *cobra.Command, args []string) error { + for _, pfn := range pfns { + if pfn != nil { + if err := pfn(cmd, args); err != nil { + return err + } + } + } + return nil + } +} + +func getExpectedLibwasmVersion() string { + buildInfo, ok := debug.ReadBuildInfo() + if !ok { + panic("can't read build info") + } + for _, d := range buildInfo.Deps { + if d.Path != "github.com/CosmWasm/wasmvm/v2" { + continue + } + if d.Replace != nil { + return d.Replace.Version + } + return d.Version + } + return "" +} diff --git a/modules/light-clients/08-wasm/testing/simapp/simd/main.go b/modules/light-clients/08-wasm/testing/simapp/simd/main.go new file mode 100644 index 0000000..cf68c8f --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/simd/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "os" + + "cosmossdk.io/log" + + svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing/simapp" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing/simapp/simd/cmd" +) + +func main() { + rootCmd := cmd.NewRootCmd() + if err := svrcmd.Execute(rootCmd, "", simapp.DefaultNodeHome); err != nil { + log.NewLogger(rootCmd.OutOrStderr()).Error("failure when running app", "err", err) + os.Exit(1) + } +} diff --git a/modules/light-clients/08-wasm/testing/simapp/test_helpers.go b/modules/light-clients/08-wasm/testing/simapp/test_helpers.go new file mode 100644 index 0000000..8cd5518 --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/test_helpers.go @@ -0,0 +1,112 @@ +package simapp + +import ( + "encoding/json" + "path/filepath" + "testing" + + dbm "github.com/cosmos/cosmos-db" + "github.com/stretchr/testify/require" + + "cosmossdk.io/log" + sdkmath "cosmossdk.io/math" + "cosmossdk.io/store/snapshots" + snapshottypes "cosmossdk.io/store/snapshots/types" + + bam "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/server" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + abci "github.com/cometbft/cometbft/abci/types" + cmttypes "github.com/cometbft/cometbft/types" + + ibcwasmtypes "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" +) + +func setup(tb testing.TB, chainID string, withGenesis bool, invCheckPeriod uint, mockVM ibcwasmtypes.WasmEngine) (*SimApp, GenesisState) { + tb.Helper() + + db := dbm.NewMemDB() + nodeHome := tb.TempDir() + snapshotDir := filepath.Join(nodeHome, "data", "snapshots") + + snapshotDB, err := dbm.NewDB("metadata", dbm.GoLevelDBBackend, snapshotDir) + require.NoError(tb, err) + tb.Cleanup(func() { snapshotDB.Close() }) + snapshotStore, err := snapshots.NewStore(snapshotDB, snapshotDir) + require.NoError(tb, err) + + appOptions := make(simtestutil.AppOptionsMap, 0) + appOptions[flags.FlagHome] = nodeHome // ensure unique folder + appOptions[server.FlagInvCheckPeriod] = invCheckPeriod + app := NewUnitTestSimApp(log.NewNopLogger(), db, nil, true, appOptions, mockVM, bam.SetChainID(chainID), bam.SetSnapshot(snapshotStore, snapshottypes.SnapshotOptions{KeepRecent: 2})) + + if withGenesis { + return app, app.DefaultGenesis() + } + + return app, GenesisState{} +} + +// SetupWithEmptyStore set up a simapp instance with empty DB +func SetupWithEmptyStore(tb testing.TB, mockVM ibcwasmtypes.WasmEngine) *SimApp { + tb.Helper() + + app, _ := setup(tb, "", false, 0, mockVM) + return app +} + +// SetupWithGenesisValSet initializes a new SimApp with a validator set and genesis accounts +// that also act as delegators. For simplicity, each validator is bonded with a delegation +// of one consensus engine unit in the default token of the simapp from first genesis +// account. A Nop logger is set in SimApp. +func SetupWithGenesisValSetSnapshotter(t *testing.T, mockVM ibcwasmtypes.WasmEngine, valSet *cmttypes.ValidatorSet, genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance) *SimApp { + t.Helper() + + app, genesisState := setup(t, "", true, 5, mockVM) + genesisState, err := simtestutil.GenesisStateWithValSet(app.AppCodec(), genesisState, valSet, genAccs, balances...) + require.NoError(t, err) + + stateBytes, err := json.MarshalIndent(genesisState, "", " ") + require.NoError(t, err) + + // init chain will set the validator set and initialize the genesis accounts + _, err = app.InitChain(&abci.RequestInitChain{ + Validators: []abci.ValidatorUpdate{}, + ConsensusParams: simtestutil.DefaultConsensusParams, + AppStateBytes: stateBytes, + }) + require.NoError(t, err) + + return app +} + +// SetupWithSnapshotter initializes a new SimApp with a configured snapshot db. A Nop logger is set in SimApp. +func SetupWithSnapshotter(t *testing.T, mockVM ibcwasmtypes.WasmEngine) *SimApp { + t.Helper() + + privVal := cmttypes.NewMockPV() + pubKey, err := privVal.GetPubKey() + require.NoError(t, err) + + // create validator set with single validator + validator := cmttypes.NewValidator(pubKey, 1) + valSet := cmttypes.NewValidatorSet([]*cmttypes.Validator{validator}) + + // generate genesis account + senderPrivKey := secp256k1.GenPrivKey() + acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0) + balance := banktypes.Balance{ + Address: acc.GetAddress().String(), + Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(100000000000000))), + } + + app := SetupWithGenesisValSetSnapshotter(t, mockVM, valSet, []authtypes.GenesisAccount{acc}, balance) + + return app +} diff --git a/modules/light-clients/08-wasm/testing/simapp/upgrades.go b/modules/light-clients/08-wasm/testing/simapp/upgrades.go new file mode 100644 index 0000000..79766c0 --- /dev/null +++ b/modules/light-clients/08-wasm/testing/simapp/upgrades.go @@ -0,0 +1,45 @@ +package simapp + +import ( + "context" + + storetypes "cosmossdk.io/store/types" + circuittypes "cosmossdk.io/x/circuit/types" + upgradetypes "cosmossdk.io/x/upgrade/types" + + "github.com/cosmos/cosmos-sdk/types/module" +) + +const ( + IBCWasmUpgrade = "ibcwasm-v8" +) + +// registerUpgradeHandlers registers all supported upgrade handlers +func (app *SimApp) registerUpgradeHandlers() { + app.UpgradeKeeper.SetUpgradeHandler( + IBCWasmUpgrade, + createWasmStoreUpgradeHandler(app.ModuleManager, app.configurator), + ) + + upgradeInfo, err := app.UpgradeKeeper.ReadUpgradeInfoFromDisk() + if err != nil { + panic(err) + } + + if upgradeInfo.Name == IBCWasmUpgrade && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { + storeUpgrades := storetypes.StoreUpgrades{ + Added: []string{ + circuittypes.ModuleName, + }, + } + // configure store loader that checks if version == upgradeHeight and applies store upgrades + app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) + } +} + +// createWasmStoreUpgradeHandler creates an upgrade handler for the 08-wasm ibc-go/v8 SimApp upgrade. +func createWasmStoreUpgradeHandler(mm *module.Manager, configurator module.Configurator) upgradetypes.UpgradeHandler { + return func(ctx context.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + return mm.RunMigrations(ctx, configurator, vm) + } +} diff --git a/modules/light-clients/08-wasm/testing/values.go b/modules/light-clients/08-wasm/testing/values.go new file mode 100644 index 0000000..ca71751 --- /dev/null +++ b/modules/light-clients/08-wasm/testing/values.go @@ -0,0 +1,59 @@ +package testing + +import ( + "errors" + "time" + + "github.com/cosmos/cosmos-sdk/codec" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +var ( + // Represents the code of the wasm contract used in the tests with a mock vm. + WasmMagicNumber = []byte("\x00\x61\x73\x6D") + Code = append(WasmMagicNumber, []byte("0123456780123456780123456780")...) + MockClientStateBz = []byte("client-state-data") + MockConsensusStateBz = []byte("consensus-state-data") + MockTendermitClientState = CreateMockTendermintClientState(clienttypes.NewHeight(1, 10)) + MockTendermintClientHeader = &ibctm.Header{} + MockTendermintClientMisbehaviour = ibctm.NewMisbehaviour("client-id", MockTendermintClientHeader, MockTendermintClientHeader) + MockTendermintClientConsensusState = ibctm.NewConsensusState(time.Now(), commitmenttypes.NewMerkleRoot([]byte("hash")), []byte("nextValsHash")) + MockValidProofBz = []byte("valid proof") + MockInvalidProofBz = []byte("invalid proof") + MockUpgradedClientStateProofBz = []byte("upgraded client state proof") + MockUpgradedConsensusStateProofBz = []byte("upgraded consensus state proof") + + ErrMockContract = errors.New("mock contract error") + ErrMockVM = errors.New("mock vm error") +) + +// CreateMockTendermintClientState returns a valid Tendermint client state for use in tests. +func CreateMockTendermintClientState(height clienttypes.Height) *ibctm.ClientState { + return ibctm.NewClientState( + "chain-id", + ibctm.DefaultTrustLevel, + ibctesting.TrustingPeriod, + ibctesting.UnbondingPeriod, + ibctesting.MaxClockDrift, + height, + commitmenttypes.GetSDKSpecs(), + ibctesting.UpgradePath, + ) +} + +// CreateMockClientStateBz returns valid client state bytes for use in tests. +func CreateMockClientStateBz(cdc codec.BinaryCodec, checksum types.Checksum) []byte { + wrappedClientStateBz := clienttypes.MustMarshalClientState(cdc, MockTendermitClientState) + mockClientSate := types.NewClientState(wrappedClientStateBz, checksum, MockTendermitClientState.LatestHeight) + return clienttypes.MustMarshalClientState(cdc, mockClientSate) +} + +// CreateMockContract returns a well formed (magic number prefixed) wasm contract the given code. +func CreateMockContract(code []byte) []byte { + return append(WasmMagicNumber, code...) +} diff --git a/modules/light-clients/08-wasm/testing/wasm_endpoint.go b/modules/light-clients/08-wasm/testing/wasm_endpoint.go new file mode 100644 index 0000000..17f6a8e --- /dev/null +++ b/modules/light-clients/08-wasm/testing/wasm_endpoint.go @@ -0,0 +1,52 @@ +package testing + +import ( + "github.com/stretchr/testify/require" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +// WasmEndpoint is a wrapper around the ibctesting pkg Endpoint struct. +// It will override any functions which require special handling for the wasm client. +type WasmEndpoint struct { + *ibctesting.Endpoint +} + +// NewWasmEndpoint returns a wasm endpoint with the default ibctesting pkg +// Endpoint embedded. +func NewWasmEndpoint(chain *ibctesting.TestChain) *WasmEndpoint { + return &WasmEndpoint{ + Endpoint: ibctesting.NewDefaultEndpoint(chain), + } +} + +// CreateClient creates an wasm client on a mock cometbft chain. +// The client and consensus states are represented by byte slices +// and the starting height is 1. +func (endpoint *WasmEndpoint) CreateClient() error { + checksum, err := types.CreateChecksum(Code) + require.NoError(endpoint.Chain.TB, err) + + wrappedClientStateBz := clienttypes.MustMarshalClientState(endpoint.Chain.App.AppCodec(), CreateMockTendermintClientState(clienttypes.NewHeight(1, 5))) + wrappedClientConsensusStateBz := clienttypes.MustMarshalConsensusState(endpoint.Chain.App.AppCodec(), MockTendermintClientConsensusState) + + clientState := types.NewClientState(wrappedClientStateBz, checksum, clienttypes.NewHeight(0, 1)) + consensusState := types.NewConsensusState(wrappedClientConsensusStateBz) + + msg, err := clienttypes.NewMsgCreateClient( + clientState, consensusState, endpoint.Chain.SenderAccount.GetAddress().String(), + ) + require.NoError(endpoint.Chain.TB, err) + + res, err := endpoint.Chain.SendMsgs(msg) + if err != nil { + return err + } + + endpoint.ClientID, err = ibctesting.ParseClientIDFromEvents(res.Events) + require.NoError(endpoint.Chain.TB, err) + + return nil +} diff --git a/modules/light-clients/08-wasm/types/client_message.go b/modules/light-clients/08-wasm/types/client_message.go new file mode 100644 index 0000000..7d42719 --- /dev/null +++ b/modules/light-clients/08-wasm/types/client_message.go @@ -0,0 +1,23 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var _ exported.ClientMessage = &ClientMessage{} + +// ClientType is a Wasm light client. +func (ClientMessage) ClientType() string { + return Wasm +} + +// ValidateBasic defines a basic validation for the wasm client message. +func (c ClientMessage) ValidateBasic() error { + if len(c.Data) == 0 { + return errorsmod.Wrap(ErrInvalidData, "data cannot be empty") + } + + return nil +} diff --git a/modules/light-clients/08-wasm/types/client_message_test.go b/modules/light-clients/08-wasm/types/client_message_test.go new file mode 100644 index 0000000..4605cc9 --- /dev/null +++ b/modules/light-clients/08-wasm/types/client_message_test.go @@ -0,0 +1,53 @@ +package types_test + +import ( + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" +) + +func (suite *TypesTestSuite) TestClientMessageValidateBasic() { + testCases := []struct { + name string + clientMessage *types.ClientMessage + expErr error + }{ + { + "valid client message", + &types.ClientMessage{ + Data: []byte("data"), + }, + nil, + }, + { + "data is nil", + &types.ClientMessage{ + Data: nil, + }, + errorsmod.Wrap(types.ErrInvalidData, "data cannot be empty"), + }, + { + "data is empty", + &types.ClientMessage{ + Data: []byte{}, + }, + errorsmod.Wrap(types.ErrInvalidData, "data cannot be empty"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + clientMessage := tc.clientMessage + + suite.Require().Equal(types.Wasm, clientMessage.ClientType()) + err := clientMessage.ValidateBasic() + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} diff --git a/modules/light-clients/08-wasm/types/client_state.go b/modules/light-clients/08-wasm/types/client_state.go new file mode 100644 index 0000000..507d233 --- /dev/null +++ b/modules/light-clients/08-wasm/types/client_state.go @@ -0,0 +1,37 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var _ exported.ClientState = (*ClientState)(nil) + +// NewClientState creates a new ClientState instance. +func NewClientState(data []byte, checksum []byte, height clienttypes.Height) *ClientState { + return &ClientState{ + Data: data, + Checksum: checksum, + LatestHeight: height, + } +} + +// ClientType is Wasm light client. +func (ClientState) ClientType() string { + return Wasm +} + +// Validate performs a basic validation of the client state fields. +func (cs ClientState) Validate() error { + if len(cs.Data) == 0 { + return errorsmod.Wrap(ErrInvalidData, "data cannot be empty") + } + + if err := ValidateWasmChecksum(cs.Checksum); err != nil { + return err + } + + return nil +} diff --git a/modules/light-clients/08-wasm/types/client_state_test.go b/modules/light-clients/08-wasm/types/client_state_test.go new file mode 100644 index 0000000..1804ecc --- /dev/null +++ b/modules/light-clients/08-wasm/types/client_state_test.go @@ -0,0 +1,69 @@ +package types_test + +import ( + errorsmod "cosmossdk.io/errors" + + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" +) + +func (suite *TypesTestSuite) TestValidate() { + testCases := []struct { + name string + clientState *types.ClientState + expErr error + }{ + { + name: "valid client", + clientState: types.NewClientState([]byte{0}, wasmtesting.Code, clienttypes.ZeroHeight()), + expErr: nil, + }, + { + name: "nil data", + clientState: types.NewClientState(nil, wasmtesting.Code, clienttypes.ZeroHeight()), + expErr: errorsmod.Wrap(types.ErrInvalidData, "data cannot be empty"), + }, + { + name: "empty data", + clientState: types.NewClientState([]byte{}, wasmtesting.Code, clienttypes.ZeroHeight()), + expErr: errorsmod.Wrap(types.ErrInvalidData, "data cannot be empty"), + }, + { + name: "nil checksum", + clientState: types.NewClientState([]byte{0}, nil, clienttypes.ZeroHeight()), + expErr: errorsmod.Wrap(types.ErrInvalidChecksum, "checksum cannot be empty"), + }, + { + name: "empty checksum", + clientState: types.NewClientState([]byte{0}, []byte{}, clienttypes.ZeroHeight()), + expErr: errorsmod.Wrap(types.ErrInvalidChecksum, "checksum cannot be empty"), + }, + { + name: "longer than 32 bytes checksum", + clientState: types.NewClientState( + []byte{0}, + []byte{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, + }, + clienttypes.ZeroHeight(), + ), + expErr: errorsmod.Wrap(types.ErrInvalidChecksum, "checksum cannot be empty"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + err := tc.clientState.Validate() + if tc.expErr == nil { + suite.Require().NoError(err, tc.name) + } else { + suite.Require().Error(err, tc.name) + suite.Require().ErrorIs(err, tc.expErr) + } + }) + } +} diff --git a/modules/light-clients/08-wasm/types/codec.go b/modules/light-clients/08-wasm/types/codec.go new file mode 100644 index 0000000..62ad2d6 --- /dev/null +++ b/modules/light-clients/08-wasm/types/codec.go @@ -0,0 +1,34 @@ +package types + +import ( + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/msgservice" + + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// RegisterInterfaces registers the Wasm concrete client-related +// implementations and interfaces. +func RegisterInterfaces(registry codectypes.InterfaceRegistry) { + registry.RegisterImplementations( + (*exported.ClientState)(nil), + &ClientState{}, + ) + registry.RegisterImplementations( + (*exported.ConsensusState)(nil), + &ConsensusState{}, + ) + registry.RegisterImplementations( + (*exported.ClientMessage)(nil), + &ClientMessage{}, + ) + registry.RegisterImplementations( + (*sdk.Msg)(nil), + &MsgStoreCode{}, + &MsgMigrateContract{}, + &MsgRemoveChecksum{}, + ) + + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} diff --git a/modules/light-clients/08-wasm/types/codec_test.go b/modules/light-clients/08-wasm/types/codec_test.go new file mode 100644 index 0000000..dc7119f --- /dev/null +++ b/modules/light-clients/08-wasm/types/codec_test.go @@ -0,0 +1,75 @@ +package types_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + + wasm "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" +) + +func TestCodecTypeRegistration(t *testing.T) { + testCases := []struct { + name string + typeURL string + expError error + }{ + { + "success: ClientState", + sdk.MsgTypeURL(&types.ClientState{}), + nil, + }, + { + "success: ConsensusState", + sdk.MsgTypeURL(&types.ConsensusState{}), + nil, + }, + { + "success: ClientMessage", + sdk.MsgTypeURL(&types.ClientMessage{}), + nil, + }, + { + "success: MsgStoreCode", + sdk.MsgTypeURL(&types.MsgStoreCode{}), + nil, + }, + { + "success: MsgMigrateContract", + sdk.MsgTypeURL(&types.MsgMigrateContract{}), + nil, + }, + { + "success: MsgRemoveChecksum", + sdk.MsgTypeURL(&types.MsgRemoveChecksum{}), + nil, + }, + { + "type not registered on codec", + "ibc.invalid.MsgTypeURL", + errors.New("unable to resolve type URL ibc.invalid.MsgTypeURL"), + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + encodingCfg := moduletestutil.MakeTestEncodingConfig(wasm.AppModuleBasic{}) + msg, err := encodingCfg.Codec.InterfaceRegistry().Resolve(tc.typeURL) + + if tc.expError == nil { + require.NotNil(t, msg) + require.NoError(t, err) + } else { + require.Nil(t, msg) + require.ErrorContains(t, err, tc.expError.Error()) + } + }) + } +} diff --git a/modules/light-clients/08-wasm/types/config.go b/modules/light-clients/08-wasm/types/config.go new file mode 100644 index 0000000..f980665 --- /dev/null +++ b/modules/light-clients/08-wasm/types/config.go @@ -0,0 +1,45 @@ +package types + +import ( + "path/filepath" + "strings" +) + +const ( + // ContractMemoryLimit is the memory limit of each contract execution (in MiB) + // constant value so all nodes run with the same limit. + ContractMemoryLimit = 32 + // MemoryCacheSize is the size of the wasm vm cache (in MiB), it is set to 0 to reduce unnecessary memory usage. + // See: https://github.com/CosmWasm/cosmwasm/pull/1925 + MemoryCacheSize = 0 + + defaultDataDir string = "ibc_08-wasm_client_data" + defaultSupportedCapabilities string = "iterator" + defaultContractDebugMode = false +) + +// WasmConfig defines configuration parameters for the 08-wasm wasm virtual machine instance. +// It includes the `dataDir` intended to be used for wasm blobs and internal caches, as well as a comma separated list +// of features or capabilities the user wishes to enable. A boolean flag is provided to enable debug mode. +type WasmConfig struct { + // DataDir is the directory for Wasm blobs and various caches + DataDir string + // SupportedCapabilities is a slice of capabilities supported by the chain + // See https://github.com/CosmWasm/wasmd/blob/9e44af168570391b0b69822952f206d35320d473/app/wasm.go#L3-L16 + // for more information. + SupportedCapabilities []string + // ContractDebugMode is a flag to log what contracts print. It must be false on all + // production nodes, and only enabled in test environments or debug non-validating nodes. + ContractDebugMode bool +} + +// DefaultWasmConfig returns the default settings for WasmConfig. +// The homePath is the path to the directory where the data directory for +// Wasm blobs and caches will be stored. +func DefaultWasmConfig(homePath string) WasmConfig { + return WasmConfig{ + DataDir: filepath.Join(homePath, defaultDataDir), + SupportedCapabilities: strings.Split(defaultSupportedCapabilities, ","), + ContractDebugMode: defaultContractDebugMode, + } +} diff --git a/modules/light-clients/08-wasm/types/consensus_state.go b/modules/light-clients/08-wasm/types/consensus_state.go new file mode 100644 index 0000000..c72a906 --- /dev/null +++ b/modules/light-clients/08-wasm/types/consensus_state.go @@ -0,0 +1,35 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var _ exported.ConsensusState = (*ConsensusState)(nil) + +// NewConsensusState creates a new ConsensusState instance. +func NewConsensusState(data []byte) *ConsensusState { + return &ConsensusState{ + Data: data, + } +} + +// ClientType returns Wasm type. +func (ConsensusState) ClientType() string { + return Wasm +} + +// GetTimestamp returns block time in nanoseconds of the header that created consensus state. +func (ConsensusState) GetTimestamp() uint64 { + return 0 +} + +// ValidateBasic defines a basic validation for the wasm client consensus state. +func (cs ConsensusState) ValidateBasic() error { + if len(cs.Data) == 0 { + return errorsmod.Wrap(ErrInvalidData, "data cannot be empty") + } + + return nil +} diff --git a/modules/light-clients/08-wasm/types/consensus_state_test.go b/modules/light-clients/08-wasm/types/consensus_state_test.go new file mode 100644 index 0000000..d18efa4 --- /dev/null +++ b/modules/light-clients/08-wasm/types/consensus_state_test.go @@ -0,0 +1,43 @@ +package types_test + +import ( + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" +) + +func (suite *TypesTestSuite) TestConsensusStateValidateBasic() { + testCases := []struct { + name string + consensusState *types.ConsensusState + expectPass bool + }{ + { + "success", + types.NewConsensusState([]byte("data")), + true, + }, + { + "data is nil", + types.NewConsensusState(nil), + false, + }, + { + "data is empty", + types.NewConsensusState([]byte{}), + false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + // check just to increase coverage + suite.Require().Equal(types.Wasm, tc.consensusState.ClientType()) + + err := tc.consensusState.ValidateBasic() + if tc.expectPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } +} diff --git a/modules/light-clients/08-wasm/types/contract_api.go b/modules/light-clients/08-wasm/types/contract_api.go new file mode 100644 index 0000000..3ac2504 --- /dev/null +++ b/modules/light-clients/08-wasm/types/contract_api.go @@ -0,0 +1,125 @@ +package types + +import ( + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypesv2 "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types/v2" +) + +// InstantiateMessage is the message that is sent to the contract's instantiate entry point. +type InstantiateMessage struct { + ClientState []byte `json:"client_state"` + ConsensusState []byte `json:"consensus_state"` + Checksum []byte `json:"checksum"` +} + +// QueryMsg is used to encode messages that are sent to the contract's query entry point. +// The json omitempty tag is mandatory since it omits any empty (default initialized) fields from the encoded JSON, +// this is required in order to be compatible with Rust's enum matching as used in the contract. +// Only one field should be set at a time. +type QueryMsg struct { + Status *StatusMsg `json:"status,omitempty"` + TimestampAtHeight *TimestampAtHeightMsg `json:"timestamp_at_height,omitempty"` + VerifyClientMessage *VerifyClientMessageMsg `json:"verify_client_message,omitempty"` + CheckForMisbehaviour *CheckForMisbehaviourMsg `json:"check_for_misbehaviour,omitempty"` +} + +// StatusMsg is a queryMsg sent to the contract to query the status of the wasm client. +type StatusMsg struct{} + +// TimestampAtHeightMsg is a queryMsg sent to the contract to query the timestamp at a given height. +type TimestampAtHeightMsg struct { + Height clienttypes.Height `json:"height"` +} + +// VerifyClientMessageMsg is a queryMsg sent to the contract to verify a client message. +type VerifyClientMessageMsg struct { + ClientMessage []byte `json:"client_message"` +} + +// CheckForMisbehaviourMsg is a queryMsg sent to the contract to check for misbehaviour. +type CheckForMisbehaviourMsg struct { + ClientMessage []byte `json:"client_message"` +} + +// SudoMsg is used to encode messages that are sent to the contract's sudo entry point. +// The json omitempty tag is mandatory since it omits any empty (default initialized) fields from the encoded JSON, +// this is required in order to be compatible with Rust's enum matching as used in the contract. +// Only one field should be set at a time. +type SudoMsg struct { + UpdateState *UpdateStateMsg `json:"update_state,omitempty"` + UpdateStateOnMisbehaviour *UpdateStateOnMisbehaviourMsg `json:"update_state_on_misbehaviour,omitempty"` + VerifyUpgradeAndUpdateState *VerifyUpgradeAndUpdateStateMsg `json:"verify_upgrade_and_update_state,omitempty"` + VerifyMembership *VerifyMembershipMsg `json:"verify_membership,omitempty"` + VerifyNonMembership *VerifyNonMembershipMsg `json:"verify_non_membership,omitempty"` + MigrateClientStore *MigrateClientStoreMsg `json:"migrate_client_store,omitempty"` +} + +// UpdateStateMsg is a sudoMsg sent to the contract to update the client state. +type UpdateStateMsg struct { + ClientMessage []byte `json:"client_message"` +} + +// UpdateStateOnMisbehaviourMsg is a sudoMsg sent to the contract to update its state on misbehaviour. +type UpdateStateOnMisbehaviourMsg struct { + ClientMessage []byte `json:"client_message"` +} + +// VerifyMembershipMsg is a sudoMsg sent to the contract to verify a membership proof. +type VerifyMembershipMsg struct { + Height clienttypes.Height `json:"height"` + DelayTimePeriod uint64 `json:"delay_time_period"` + DelayBlockPeriod uint64 `json:"delay_block_period"` + Proof []byte `json:"proof"` + Path commitmenttypesv2.MerklePath `json:"merkle_path"` + Value []byte `json:"value"` +} + +// VerifyNonMembershipMsg is a sudoMsg sent to the contract to verify a non-membership proof. +type VerifyNonMembershipMsg struct { + Height clienttypes.Height `json:"height"` + DelayTimePeriod uint64 `json:"delay_time_period"` + DelayBlockPeriod uint64 `json:"delay_block_period"` + Proof []byte `json:"proof"` + Path commitmenttypesv2.MerklePath `json:"merkle_path"` +} + +// VerifyUpgradeAndUpdateStateMsg is a sudoMsg sent to the contract to verify an upgrade and update its state. +type VerifyUpgradeAndUpdateStateMsg struct { + UpgradeClientState []byte `json:"upgrade_client_state"` + UpgradeConsensusState []byte `json:"upgrade_consensus_state"` + ProofUpgradeClient []byte `json:"proof_upgrade_client"` + ProofUpgradeConsensusState []byte `json:"proof_upgrade_consensus_state"` +} + +// MigrateClientStoreMsg is a sudoMsg sent to the contract to verify a given substitute client and update to its state. +type MigrateClientStoreMsg struct{} + +// ContractResult is a type constraint that defines the expected results that can be returned by a contract call/query. +type ContractResult interface { + EmptyResult | StatusResult | TimestampAtHeightResult | CheckForMisbehaviourResult | UpdateStateResult +} + +// EmptyResult is the default return type of any contract call that does not require a custom return type. +type EmptyResult struct{} + +// StatusResult is the expected return type of the statusMsg query. It returns the status of the wasm client. +type StatusResult struct { + Status string `json:"status"` +} + +// TimestampAtHeightResult is the expected return type of the timestampAtHeightMsg query. It returns the timestamp for a light client +// at a given height. +type TimestampAtHeightResult struct { + Timestamp uint64 `json:"timestamp"` +} + +// CheckForMisbehaviourResult is the expected return type of the checkForMisbehaviourMsg query. It returns a boolean indicating +// if misbehaviour was detected. +type CheckForMisbehaviourResult struct { + FoundMisbehaviour bool `json:"found_misbehaviour"` +} + +// UpdateStateResult is the expected return type of the updateStateMsg sudo call. It returns the updated consensus heights. +type UpdateStateResult struct { + Heights []clienttypes.Height `json:"heights"` +} diff --git a/modules/light-clients/08-wasm/types/errors.go b/modules/light-clients/08-wasm/types/errors.go new file mode 100644 index 0000000..31aaa9f --- /dev/null +++ b/modules/light-clients/08-wasm/types/errors.go @@ -0,0 +1,23 @@ +package types + +import errorsmod "cosmossdk.io/errors" + +var ( + ErrInvalid = errorsmod.Register(ModuleName, 2, "invalid") + ErrInvalidData = errorsmod.Register(ModuleName, 3, "invalid data") + ErrInvalidChecksum = errorsmod.Register(ModuleName, 4, "invalid checksum") + ErrInvalidClientMessage = errorsmod.Register(ModuleName, 5, "invalid client message") + ErrRetrieveClientID = errorsmod.Register(ModuleName, 6, "failed to retrieve client id") + // Wasm specific + ErrWasmEmptyCode = errorsmod.Register(ModuleName, 7, "empty wasm code") + ErrWasmCodeTooLarge = errorsmod.Register(ModuleName, 8, "wasm code too large") + ErrWasmCodeExists = errorsmod.Register(ModuleName, 9, "wasm code already exists") + ErrWasmChecksumNotFound = errorsmod.Register(ModuleName, 10, "wasm checksum not found") + ErrWasmSubMessagesNotAllowed = errorsmod.Register(ModuleName, 11, "execution of sub messages is not allowed") + ErrWasmEventsNotAllowed = errorsmod.Register(ModuleName, 12, "returning events from a contract is not allowed") + ErrWasmAttributesNotAllowed = errorsmod.Register(ModuleName, 13, "returning attributes from a contract is not allowed") + ErrWasmContractCallFailed = errorsmod.Register(ModuleName, 14, "wasm contract call failed") + ErrWasmInvalidResponseData = errorsmod.Register(ModuleName, 15, "wasm contract returned invalid response data") + ErrWasmInvalidContractModification = errorsmod.Register(ModuleName, 16, "wasm contract made invalid state modifications") + ErrVMError = errorsmod.Register(ModuleName, 17, "wasm VM error") +) diff --git a/modules/light-clients/08-wasm/types/events.go b/modules/light-clients/08-wasm/types/events.go new file mode 100644 index 0000000..75eb343 --- /dev/null +++ b/modules/light-clients/08-wasm/types/events.go @@ -0,0 +1,18 @@ +package types + +// IBC 08-wasm events +const ( + // EventTypeStoreWasmCode defines the event type for bytecode storage + EventTypeStoreWasmCode = "store_wasm_code" + // EventTypeMigrateContract defines the event type for a contract migration + EventTypeMigrateContract = "migrate_contract" + + // AttributeKeyWasmChecksum denotes the checksum of the wasm code that was stored or migrated + AttributeKeyWasmChecksum = "wasm_checksum" + // AttributeKeyClientID denotes the client identifier of the wasm client + AttributeKeyClientID = "client_id" + // AttributeKeyNewChecksum denotes the checksum of the new wasm code. + AttributeKeyNewChecksum = "new_checksum" + + AttributeValueCategory = ModuleName +) diff --git a/modules/light-clients/08-wasm/types/expected_interfaces.go b/modules/light-clients/08-wasm/types/expected_interfaces.go new file mode 100644 index 0000000..14f8a63 --- /dev/null +++ b/modules/light-clients/08-wasm/types/expected_interfaces.go @@ -0,0 +1,124 @@ +package types + +import ( + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" + + "github.com/cosmos/cosmos-sdk/baseapp" +) + +type WasmEngine interface { + // StoreCode will compile the Wasm code, and store the resulting compiled module + // as well as the original code. Both can be referenced later via Checksum. + // This must be done one time for given code, after which it can be + // instantiated many times, and each instance called many times. + // + // Returns both the checksum, as well as the gas cost of compilation (in CosmWasm Gas) or an error. + StoreCode(code wasmvm.WasmCode, gasLimit uint64) (wasmvmtypes.Checksum, uint64, error) + + // StoreCodeUnchecked will compile the wasm code, and store the resulting pre-compile + // as well as the original code. Both can be referenced later via checksum + // This must be done one time for given code, after which it can be + // instantiated many times, and each instance called many times. + // It does the same as StoreCode but without the static checks. + // This allows restoring previous contract code in genesis and state-sync that may have been initially stored under different configuration constraints. + StoreCodeUnchecked(code wasmvm.WasmCode) (wasmvm.Checksum, error) + + // Instantiate will create a new contract based on the given checksum. + // We can set the initMsg (contract "genesis") here, and it then receives + // an account and address and can be invoked (Execute) many times. + // + // Storage should be set with a PrefixedKVStore that this code can safely access. + // + // Under the hood, we may recompile the wasm, use a cached native compile, or even use a cached instance + // for performance. + Instantiate( + checksum wasmvm.Checksum, + env wasmvmtypes.Env, + info wasmvmtypes.MessageInfo, + initMsg []byte, + store wasmvm.KVStore, + goapi wasmvm.GoAPI, + querier wasmvm.Querier, + gasMeter wasmvm.GasMeter, + gasLimit uint64, + deserCost wasmvmtypes.UFraction, + ) (*wasmvmtypes.ContractResult, uint64, error) + + // Query allows a client to execute a contract-specific query. If the result is not empty, it should be + // valid json-encoded data to return to the client. + // The meaning of path and data can be determined by the code. Path is the suffix of the abci.QueryRequest.Path + Query( + checksum wasmvm.Checksum, + env wasmvmtypes.Env, + queryMsg []byte, + store wasmvm.KVStore, + goapi wasmvm.GoAPI, + querier wasmvm.Querier, + gasMeter wasmvm.GasMeter, + gasLimit uint64, + deserCost wasmvmtypes.UFraction, + ) (*wasmvmtypes.QueryResult, uint64, error) + + // Migrate will migrate an existing contract to a new code binary. + // This takes storage of the data from the original contract and the checksum of the new contract that should + // replace it. This allows it to run a migration step if needed, or return an error if unable to migrate + // the given data. + // + // MigrateMsg has some data on how to perform the migration. + Migrate( + checksum wasmvm.Checksum, + env wasmvmtypes.Env, + migrateMsg []byte, + store wasmvm.KVStore, + goapi wasmvm.GoAPI, + querier wasmvm.Querier, + gasMeter wasmvm.GasMeter, + gasLimit uint64, + deserCost wasmvmtypes.UFraction, + ) (*wasmvmtypes.ContractResult, uint64, error) + + // Sudo allows native Go modules to make privileged (sudo) calls on the contract. + // The contract can expose entry points that cannot be triggered by any transaction, but only via + // native Go modules, and delegate the access control to the system. + // + // These work much like Migrate (same scenario) but allows custom apps to extend the privileged entry points + // without forking cosmwasm-vm. + Sudo( + checksum wasmvm.Checksum, + env wasmvmtypes.Env, + sudoMsg []byte, + store wasmvm.KVStore, + goapi wasmvm.GoAPI, + querier wasmvm.Querier, + gasMeter wasmvm.GasMeter, + gasLimit uint64, + deserCost wasmvmtypes.UFraction, + ) (*wasmvmtypes.ContractResult, uint64, error) + + // GetCode will load the original wasm code for the given checksum. + // This will only succeed if that checksum was previously returned from + // a call to Create. + // + // This can be used so that the (short) checksum is stored in the iavl tree + // and the larger binary blobs (wasm and pre-compiles) are all managed by the + // rust library + GetCode(checksum wasmvm.Checksum) (wasmvm.WasmCode, error) + + // Pin pins a code to an in-memory cache, such that is + // always loaded quickly when executed. + // Pin is idempotent. + Pin(checksum wasmvm.Checksum) error + + // Unpin removes the guarantee of a contract to be pinned (see Pin). + // After calling this, the code may or may not remain in memory depending on + // the implementor's choice. + // Unpin is idempotent. + Unpin(checksum wasmvm.Checksum) error +} + +type QueryRouter interface { + // Route returns the GRPCQueryHandler for a given query route path or nil + // if not found + Route(path string) baseapp.GRPCQueryHandler +} diff --git a/modules/light-clients/08-wasm/types/expected_keepers.go b/modules/light-clients/08-wasm/types/expected_keepers.go new file mode 100644 index 0000000..6147fa9 --- /dev/null +++ b/modules/light-clients/08-wasm/types/expected_keepers.go @@ -0,0 +1,16 @@ +package types + +import ( + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// ClientKeeper defines the expected client keeper +type ClientKeeper interface { + ClientStore(ctx sdk.Context, clientID string) storetypes.KVStore + GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool) + SetClientState(ctx sdk.Context, clientID string, clientState exported.ClientState) +} diff --git a/modules/light-clients/08-wasm/types/gas_register.go b/modules/light-clients/08-wasm/types/gas_register.go new file mode 100644 index 0000000..7d3f8a9 --- /dev/null +++ b/modules/light-clients/08-wasm/types/gas_register.go @@ -0,0 +1,268 @@ +package types + +import ( + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + storetypes "cosmossdk.io/store/types" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +const ( + // DefaultGasMultiplier is how many CosmWasm gas points = 1 Cosmos SDK gas point. + // + // CosmWasm gas strategy is documented in https://github.com/CosmWasm/cosmwasm/blob/v1.0.0-beta/docs/GAS.md. + // Cosmos SDK reference costs can be found here: https://github.com/cosmos/cosmos-sdk/blob/v0.42.10/store/types/gas.go#L198-L209. + // + // The original multiplier of 100 up to CosmWasm 0.16 was based on + // "A write at ~3000 gas and ~200us = 10 gas per us (microsecond) cpu/io + // Rough timing have 88k gas at 90us, which is equal to 1k sdk gas... (one read)" + // as well as manual Wasmer benchmarks from 2019. This was then multiplied by 150_000 + // in the 0.16 -> 1.0 upgrade (https://github.com/CosmWasm/cosmwasm/pull/1120). + // In the 2.0 upgrade, this was reduced by a factor of 1000 (https://github.com/CosmWasm/cosmwasm/pull/1884). + // + // The multiplier deserves more reproducible benchmarking and a strategy that allows easy adjustments. + // This is tracked in https://github.com/CosmWasm/wasmd/issues/566 and https://github.com/CosmWasm/wasmd/issues/631. + // Gas adjustments are consensus breaking but may happen in any release marked as consensus breaking. + // Do not make assumptions on how much gas an operation will consume in places that are hard to adjust, + // such as hardcoding them in contracts. + // + // Please note that all gas prices returned to wasmvm should have this multiplied. + // Benchmarks and numbers were discussed in: https://github.com/CosmWasm/wasmd/pull/634#issuecomment-938055852 + DefaultGasMultiplier uint64 = 140_000 + // DefaultInstanceCost is how much SDK gas we charge each time we load a WASM instance. + // Creating a new instance is costly, and this helps put a recursion limit to contracts calling contracts. + // Benchmarks and numbers were discussed in: https://github.com/CosmWasm/wasmd/pull/634#issuecomment-938056803 + DefaultInstanceCost uint64 = 60_000 + // DefaultInstanceCostDiscount is charged instead of DefaultInstanceCost for cases where + // we assume the contract is loaded from an in-memory cache. + // For a long time it was implicitly just 0 in those cases. + // Now we use something small that roughly reflects the 45µs startup time (30x cheaper than DefaultInstanceCost). + DefaultInstanceCostDiscount uint64 = 2_000 + // DefaultCompileCost is how much SDK gas is charged *per byte* for compiling WASM code. + // Benchmarks and numbers were discussed in: https://github.com/CosmWasm/wasmd/pull/634#issuecomment-938056803 + DefaultCompileCost uint64 = 3 + // DefaultEventAttributeDataCost is how much SDK gas is charged *per byte* for attribute data in events. + // This is used with len(key) + len(value) + DefaultEventAttributeDataCost uint64 = 1 + // DefaultContractMessageDataCost is how much SDK gas is charged *per byte* of the message that goes to the contract + // This is used with len(msg). Note that the message is deserialized in the receiving contract and this is charged + // with wasm gas already. The derserialization of results is also charged in wasmvm. I am unsure if we need to add + // additional costs here. + // Note: also used for error fields on reply, and data on reply. Maybe these should be pulled out to a different (non-zero) field + DefaultContractMessageDataCost uint64 = 0 + // DefaultPerAttributeCost is how much SDK gas we charge per attribute count. + DefaultPerAttributeCost uint64 = 10 + // DefaultPerCustomEventCost is how much SDK gas we charge per event count. + DefaultPerCustomEventCost uint64 = 20 + // DefaultEventAttributeDataFreeTier number of bytes of total attribute data we do not charge. + DefaultEventAttributeDataFreeTier = 100 +) + +// default: 0.15 gas. +// see https://github.com/CosmWasm/wasmd/pull/898#discussion_r937727200 +var ( + defaultPerByteUncompressCost = wasmvmtypes.UFraction{ + Numerator: 15, + Denominator: 100, + } + + VMGasRegister = NewDefaultWasmGasRegister() +) + +// DefaultPerByteUncompressCost is how much SDK gas we charge per source byte to unpack +func DefaultPerByteUncompressCost() wasmvmtypes.UFraction { + return defaultPerByteUncompressCost +} + +// GasRegister abstract source for gas costs +type GasRegister interface { + // UncompressCosts costs to unpack a new wasm contract + UncompressCosts(byteLength int) storetypes.Gas + // SetupContractCost are charged when interacting with a Wasm contract, i.e. every time + // the contract is prepared for execution through any entry point (execute/instantiate/sudo/query/ibc_*/...). + SetupContractCost(discount bool, msgLen int) storetypes.Gas + // ReplyCosts costs to handle a message reply + ReplyCosts(discount bool, reply wasmvmtypes.Reply) storetypes.Gas + // EventCosts costs to persist an event + EventCosts(attrs []wasmvmtypes.EventAttribute, events wasmvmtypes.Array[wasmvmtypes.Event]) storetypes.Gas + // ToWasmVMGas converts from Cosmos SDK gas units to [CosmWasm gas] (aka. wasmvm gas) + // + // [CosmWasm gas]: https://github.com/CosmWasm/cosmwasm/blob/v1.3.1/docs/GAS.md + ToWasmVMGas(source storetypes.Gas) uint64 + // FromWasmVMGas converts from [CosmWasm gas] (aka. wasmvm gas) to Cosmos SDK gas units + // + // [CosmWasm gas]: https://github.com/CosmWasm/cosmwasm/blob/v1.3.1/docs/GAS.md + FromWasmVMGas(source uint64) storetypes.Gas +} + +// WasmGasRegisterConfig config type +type WasmGasRegisterConfig struct { + // InstanceCost are charged when interacting with a Wasm contract. + // "Instance" refers to the in-memory Instance of the Wasm runtime, not the contract address on chain. + // InstanceCost are part of a contract's setup cost. + InstanceCost storetypes.Gas + // InstanceCostDiscount is a discounted version of InstanceCost. It is charged whenever + // we can reasonably assume that a contract is in one of the in-memory caches. E.g. + // when the contract is pinned or we send a reply to a contract that was executed before. + // See also https://github.com/CosmWasm/wasmd/issues/1798 for more thinking around + // discount cases. + InstanceCostDiscount storetypes.Gas + // CompileCost costs to persist and "compile" a new wasm contract + CompileCost storetypes.Gas + // UncompressCost costs per byte to unpack a contract + UncompressCost wasmvmtypes.UFraction + // GasMultiplier is how many cosmwasm gas points = 1 sdk gas point + // SDK reference costs can be found here: https://github.com/cosmos/cosmos-sdk/blob/02c6c9fafd58da88550ab4d7d494724a477c8a68/store/types/gas.go#L153-L164 + GasMultiplier storetypes.Gas + // EventPerAttributeCost is how much SDK gas is charged *per byte* for attribute data in events. + // This is used with len(key) + len(value) + EventPerAttributeCost storetypes.Gas + // EventAttributeDataCost is how much SDK gas is charged *per byte* for attribute data in events. + // This is used with len(key) + len(value) + EventAttributeDataCost storetypes.Gas + // EventAttributeDataFreeTier number of bytes of total attribute data that is free of charge + EventAttributeDataFreeTier uint64 + // ContractMessageDataCost SDK gas charged *per byte* of the message that goes to the contract + // This is used with len(msg) + ContractMessageDataCost storetypes.Gas + // CustomEventCost cost per custom event + CustomEventCost uint64 +} + +// DefaultGasRegisterConfig default values +func DefaultGasRegisterConfig() WasmGasRegisterConfig { + return WasmGasRegisterConfig{ + InstanceCost: DefaultInstanceCost, + InstanceCostDiscount: DefaultInstanceCostDiscount, + CompileCost: DefaultCompileCost, + GasMultiplier: DefaultGasMultiplier, + EventPerAttributeCost: DefaultPerAttributeCost, + CustomEventCost: DefaultPerCustomEventCost, + EventAttributeDataCost: DefaultEventAttributeDataCost, + EventAttributeDataFreeTier: DefaultEventAttributeDataFreeTier, + ContractMessageDataCost: DefaultContractMessageDataCost, + UncompressCost: DefaultPerByteUncompressCost(), + } +} + +// WasmGasRegister implements GasRegister interface +type WasmGasRegister struct { + c WasmGasRegisterConfig +} + +// NewDefaultWasmGasRegister creates instance with default values +func NewDefaultWasmGasRegister() WasmGasRegister { + return NewWasmGasRegister(DefaultGasRegisterConfig()) +} + +// NewWasmGasRegister constructor +func NewWasmGasRegister(c WasmGasRegisterConfig) WasmGasRegister { + if c.GasMultiplier == 0 { + panic(errorsmod.Wrap(sdkerrors.ErrLogic, "GasMultiplier can not be 0")) + } + return WasmGasRegister{ + c: c, + } +} + +// UncompressCosts costs to unpack a new wasm contract +func (g WasmGasRegister) UncompressCosts(byteLength int) storetypes.Gas { + if byteLength < 0 { + panic(errorsmod.Wrap(ErrInvalid, "negative length")) + } + return g.c.UncompressCost.Mul(uint64(byteLength)).Floor() +} + +// SetupContractCost costs when interacting with a wasm contract. +// Set discount to true in cases where you can reasonably assume the contract +// is loaded from an in-memory cache (e.g. pinned contracts or replies). +func (g WasmGasRegister) SetupContractCost(discount bool, msgLen int) storetypes.Gas { + if msgLen < 0 { + panic(errorsmod.Wrap(ErrInvalid, "negative length")) + } + dataCost := storetypes.Gas(msgLen) * g.c.ContractMessageDataCost + if discount { + return g.c.InstanceCostDiscount + dataCost + } + return g.c.InstanceCost + dataCost +} + +// ReplyCosts costs to handle a message reply. +// Set discount to true in cases where you can reasonably assume the contract +// is loaded from an in-memory cache (e.g. pinned contracts or replies). +func (g WasmGasRegister) ReplyCosts(discount bool, reply wasmvmtypes.Reply) storetypes.Gas { + var eventGas storetypes.Gas + msgLen := len(reply.Result.Err) + if reply.Result.Ok != nil { + msgLen += len(reply.Result.Ok.Data) + var attrs []wasmvmtypes.EventAttribute + for _, e := range reply.Result.Ok.Events { + eventGas += storetypes.Gas(len(e.Type)) * g.c.EventAttributeDataCost + attrs = append(attrs, e.Attributes...) + } + // apply free tier on the whole set not per event + eventGas += g.EventCosts(attrs, nil) + } + return eventGas + g.SetupContractCost(discount, msgLen) +} + +// EventCosts costs to persist an event +func (g WasmGasRegister) EventCosts(attrs []wasmvmtypes.EventAttribute, events wasmvmtypes.Array[wasmvmtypes.Event]) storetypes.Gas { + gas, remainingFreeTier := g.eventAttributeCosts(attrs, g.c.EventAttributeDataFreeTier) + for _, e := range events { + gas += g.c.CustomEventCost + gas += storetypes.Gas(len(e.Type)) * g.c.EventAttributeDataCost // no free tier with event type + var attrCost storetypes.Gas + attrCost, remainingFreeTier = g.eventAttributeCosts(e.Attributes, remainingFreeTier) + gas += attrCost + } + return gas +} + +func (g WasmGasRegister) eventAttributeCosts(attrs []wasmvmtypes.EventAttribute, freeTier uint64) (storetypes.Gas, uint64) { + if len(attrs) == 0 { + return 0, freeTier + } + var storedBytes uint64 + for _, l := range attrs { + storedBytes += uint64(len(l.Key)) + uint64(len(l.Value)) + } + storedBytes, freeTier = calcWithFreeTier(storedBytes, freeTier) + // total Length * costs + attribute count * costs + r := sdkmath.NewIntFromUint64(g.c.EventAttributeDataCost).Mul(sdkmath.NewIntFromUint64(storedBytes)). + Add(sdkmath.NewIntFromUint64(g.c.EventPerAttributeCost).Mul(sdkmath.NewIntFromUint64(uint64(len(attrs))))) + if !r.IsUint64() { + panic(storetypes.ErrorOutOfGas{Descriptor: "overflow"}) + } + return r.Uint64(), freeTier +} + +// apply free tier +func calcWithFreeTier(storedBytes, freeTier uint64) (uint64, uint64) { + if storedBytes <= freeTier { + return 0, freeTier - storedBytes + } + storedBytes -= freeTier + return storedBytes, 0 +} + +// ToWasmVMGas converts from Cosmos SDK gas units to [CosmWasm gas] (aka. wasmvm gas) +// +// [CosmWasm gas]: https://github.com/CosmWasm/cosmwasm/blob/v1.3.1/docs/GAS.md +func (g WasmGasRegister) ToWasmVMGas(source storetypes.Gas) uint64 { + x := source * g.c.GasMultiplier + if x < source { + panic(storetypes.ErrorOutOfGas{Descriptor: "overflow"}) + } + return x +} + +// FromWasmVMGas converts from [CosmWasm gas] (aka. wasmvm gas) to Cosmos SDK gas units +// +// [CosmWasm gas]: https://github.com/CosmWasm/cosmwasm/blob/v1.3.1/docs/GAS.md +func (g WasmGasRegister) FromWasmVMGas(source uint64) storetypes.Gas { + return source / g.c.GasMultiplier +} diff --git a/modules/light-clients/08-wasm/types/gas_register_custom.go b/modules/light-clients/08-wasm/types/gas_register_custom.go new file mode 100644 index 0000000..0347abf --- /dev/null +++ b/modules/light-clients/08-wasm/types/gas_register_custom.go @@ -0,0 +1,62 @@ +package types + +import ( + "math" + + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" + + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// While gas_register.go is a direct copy of https://github.com/CosmWasm/wasmd/blob/main/x/wasm/types/gas_register.go +// This file contains additional constructs that can be maintained separately. +// Most of these functions are slight modifications of keeper function from wasmd, which act on `WasmGasRegister` instead of `Keeper`. +const ( + // DefaultDeserializationCostPerByte The formula should be `len(data) * deserializationCostPerByte` + DefaultDeserializationCostPerByte = 1 +) + +var CostJSONDeserialization = wasmvmtypes.UFraction{ + Numerator: DefaultDeserializationCostPerByte * DefaultGasMultiplier, + Denominator: 1, +} + +func (g WasmGasRegister) RuntimeGasForContract(ctx sdk.Context) uint64 { + meter := ctx.GasMeter() + if meter.IsOutOfGas() { + return 0 + } + // infinite gas meter with limit=0 or MaxUint64 + if meter.Limit() == 0 || meter.Limit() == math.MaxUint64 { + return math.MaxUint64 + } + return g.ToWasmVMGas(meter.Limit() - meter.GasConsumedToLimit()) +} + +func (g WasmGasRegister) ConsumeRuntimeGas(ctx sdk.Context, gas uint64) { + consumed := g.FromWasmVMGas(gas) + ctx.GasMeter().ConsumeGas(consumed, "wasm contract") + // throw OutOfGas error if we ran out (got exactly to zero due to better limit enforcing) + if ctx.GasMeter().IsOutOfGas() { + panic(storetypes.ErrorOutOfGas{Descriptor: "Wasmer function execution"}) + } +} + +// MultipliedGasMeter wraps the GasMeter from context and multiplies all reads by out defined multiplier +type MultipliedGasMeter struct { + originalMeter storetypes.GasMeter + GasRegister GasRegister +} + +func NewMultipliedGasMeter(originalMeter storetypes.GasMeter, gr GasRegister) MultipliedGasMeter { + return MultipliedGasMeter{originalMeter: originalMeter, GasRegister: gr} +} + +var _ wasmvm.GasMeter = MultipliedGasMeter{} + +func (m MultipliedGasMeter) GasConsumed() storetypes.Gas { + return m.GasRegister.ToWasmVMGas(m.originalMeter.GasConsumed()) +} diff --git a/modules/light-clients/08-wasm/types/genesis.go b/modules/light-clients/08-wasm/types/genesis.go new file mode 100644 index 0000000..d3e34d6 --- /dev/null +++ b/modules/light-clients/08-wasm/types/genesis.go @@ -0,0 +1,22 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" +) + +// NewGenesisState creates an 08-wasm GenesisState instance. +func NewGenesisState(contracts []Contract) *GenesisState { + return &GenesisState{Contracts: contracts} +} + +// Validate performs basic genesis state validation returning an error upon any +// failure. +func (gs GenesisState) Validate() error { + for _, contract := range gs.Contracts { + if err := ValidateWasmCode(contract.CodeBytes); err != nil { + return errorsmod.Wrap(err, "wasm bytecode validation failed") + } + } + + return nil +} diff --git a/modules/light-clients/08-wasm/types/genesis.pb.go b/modules/light-clients/08-wasm/types/genesis.pb.go new file mode 100644 index 0000000..cf09c29 --- /dev/null +++ b/modules/light-clients/08-wasm/types/genesis.pb.go @@ -0,0 +1,504 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/lightclients/wasm/v1/genesis.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// GenesisState defines 08-wasm's keeper genesis state +type GenesisState struct { + // uploaded light client wasm contracts + Contracts []Contract `protobuf:"bytes,1,rep,name=contracts,proto3" json:"contracts"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_05e250654f164e20, []int{0} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetContracts() []Contract { + if m != nil { + return m.Contracts + } + return nil +} + +// Contract stores contract code +type Contract struct { + // contract byte code + CodeBytes []byte `protobuf:"bytes,1,opt,name=code_bytes,json=codeBytes,proto3" json:"code_bytes,omitempty"` +} + +func (m *Contract) Reset() { *m = Contract{} } +func (m *Contract) String() string { return proto.CompactTextString(m) } +func (*Contract) ProtoMessage() {} +func (*Contract) Descriptor() ([]byte, []int) { + return fileDescriptor_05e250654f164e20, []int{1} +} +func (m *Contract) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Contract) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Contract.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Contract) XXX_Merge(src proto.Message) { + xxx_messageInfo_Contract.Merge(m, src) +} +func (m *Contract) XXX_Size() int { + return m.Size() +} +func (m *Contract) XXX_DiscardUnknown() { + xxx_messageInfo_Contract.DiscardUnknown(m) +} + +var xxx_messageInfo_Contract proto.InternalMessageInfo + +func init() { + proto.RegisterType((*GenesisState)(nil), "ibc.lightclients.wasm.v1.GenesisState") + proto.RegisterType((*Contract)(nil), "ibc.lightclients.wasm.v1.Contract") +} + +func init() { + proto.RegisterFile("ibc/lightclients/wasm/v1/genesis.proto", fileDescriptor_05e250654f164e20) +} + +var fileDescriptor_05e250654f164e20 = []byte{ + // 263 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xcb, 0x4c, 0x4a, 0xd6, + 0xcf, 0xc9, 0x4c, 0xcf, 0x28, 0x49, 0xce, 0xc9, 0x4c, 0xcd, 0x2b, 0x29, 0xd6, 0x2f, 0x4f, 0x2c, + 0xce, 0xd5, 0x2f, 0x33, 0xd4, 0x4f, 0x4f, 0xcd, 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x2b, 0x28, 0xca, + 0x2f, 0xc9, 0x17, 0x92, 0xc8, 0x4c, 0x4a, 0xd6, 0x43, 0x56, 0xa7, 0x07, 0x52, 0xa7, 0x57, 0x66, + 0x28, 0x25, 0x92, 0x9e, 0x9f, 0x9e, 0x0f, 0x56, 0xa4, 0x0f, 0x62, 0x41, 0xd4, 0x2b, 0x85, 0x71, + 0xf1, 0xb8, 0x43, 0x0c, 0x08, 0x2e, 0x49, 0x2c, 0x49, 0x15, 0x72, 0xe3, 0xe2, 0x4c, 0xce, 0xcf, + 0x2b, 0x29, 0x4a, 0x4c, 0x2e, 0x29, 0x96, 0x60, 0x54, 0x60, 0xd6, 0xe0, 0x36, 0x52, 0xd2, 0xc3, + 0x65, 0xa6, 0x9e, 0x33, 0x54, 0xa9, 0x13, 0xcb, 0x89, 0x7b, 0xf2, 0x0c, 0x41, 0x08, 0xad, 0x4a, + 0xfa, 0x5c, 0x1c, 0x30, 0x49, 0x21, 0x59, 0x2e, 0xae, 0xe4, 0xfc, 0x94, 0xd4, 0xf8, 0xa4, 0xca, + 0x92, 0x54, 0x90, 0xa1, 0x8c, 0x1a, 0x3c, 0x20, 0xa5, 0x29, 0xa9, 0x4e, 0x20, 0x01, 0x2b, 0x96, + 0x8e, 0x05, 0xf2, 0x0c, 0x4e, 0x51, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, + 0x91, 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, + 0xe5, 0x90, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0x9f, 0x9c, 0x5f, 0x9c, + 0x9b, 0x5f, 0xac, 0x9f, 0x99, 0x94, 0xac, 0x9b, 0x9e, 0xaf, 0x9f, 0x9b, 0x9f, 0x52, 0x9a, 0x93, + 0x5a, 0x0c, 0x09, 0x17, 0x5d, 0x58, 0xc0, 0x18, 0x58, 0xe8, 0x42, 0xc3, 0xc6, 0x40, 0xbf, 0xa4, + 0xb2, 0x20, 0xb5, 0x38, 0x89, 0x0d, 0xec, 0x57, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe9, + 0xf2, 0x1e, 0x11, 0x45, 0x01, 0x00, 0x00, +} + +func (m *GenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Contracts) > 0 { + for iNdEx := len(m.Contracts) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Contracts[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *Contract) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Contract) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Contract) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.CodeBytes) > 0 { + i -= len(m.CodeBytes) + copy(dAtA[i:], m.CodeBytes) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.CodeBytes))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Contracts) > 0 { + for _, e := range m.Contracts { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + return n +} + +func (m *Contract) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.CodeBytes) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Contracts", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Contracts = append(m.Contracts, Contract{}) + if err := m.Contracts[len(m.Contracts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Contract) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Contract: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Contract: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CodeBytes", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CodeBytes = append(m.CodeBytes[:0], dAtA[iNdEx:postIndex]...) + if m.CodeBytes == nil { + m.CodeBytes = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/light-clients/08-wasm/types/genesis_test.go b/modules/light-clients/08-wasm/types/genesis_test.go new file mode 100644 index 0000000..364388a --- /dev/null +++ b/modules/light-clients/08-wasm/types/genesis_test.go @@ -0,0 +1,41 @@ +package types_test + +import ( + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" +) + +func (suite *TypesTestSuite) TestValidateGenesis() { + testCases := []struct { + name string + genState *types.GenesisState + expErr error + }{ + { + "valid genesis", + &types.GenesisState{ + Contracts: []types.Contract{{CodeBytes: []byte{1}}}, + }, + nil, + }, + { + "invalid genesis", + &types.GenesisState{ + Contracts: []types.Contract{{CodeBytes: []byte{}}}, + }, + errorsmod.Wrap(types.ErrWasmEmptyCode, "wasm bytecode validation failed"), + }, + } + + for _, tc := range testCases { + tc := tc + err := tc.genState.Validate() + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().ErrorIs(err, tc.expErr) + } + } +} diff --git a/modules/light-clients/08-wasm/types/keys.go b/modules/light-clients/08-wasm/types/keys.go new file mode 100644 index 0000000..3dc10b5 --- /dev/null +++ b/modules/light-clients/08-wasm/types/keys.go @@ -0,0 +1,21 @@ +package types + +import "cosmossdk.io/collections" + +const ( + // ModuleName for the wasm client + ModuleName = "08-wasm" + + // StoreKey is the store key string for 08-wasm + StoreKey = ModuleName + + // Wasm is the client type for IBC light clients created using 08-wasm + Wasm = ModuleName + + // KeyChecksums is the key under which all checksums are stored + // Deprecated: in favor of collections.KeySet + KeyChecksums = "checksums" +) + +// ChecksumsKey is the key under which all checksums are stored +var ChecksumsKey = collections.NewPrefix(0) diff --git a/modules/light-clients/08-wasm/types/msgs.go b/modules/light-clients/08-wasm/types/msgs.go new file mode 100644 index 0000000..c9e879d --- /dev/null +++ b/modules/light-clients/08-wasm/types/msgs.go @@ -0,0 +1,94 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" +) + +var ( + _ sdk.Msg = (*MsgStoreCode)(nil) + _ sdk.Msg = (*MsgMigrateContract)(nil) + _ sdk.Msg = (*MsgRemoveChecksum)(nil) + _ sdk.HasValidateBasic = (*MsgStoreCode)(nil) + _ sdk.HasValidateBasic = (*MsgMigrateContract)(nil) + _ sdk.HasValidateBasic = (*MsgRemoveChecksum)(nil) +) + +// NewMsgStoreCode creates a new MsgStoreCode instance +func NewMsgStoreCode(signer string, code []byte) *MsgStoreCode { + return &MsgStoreCode{ + Signer: signer, + WasmByteCode: code, + } +} + +// ValidateBasic implements sdk.HasValidateBasic +func (m MsgStoreCode) ValidateBasic() error { + if err := ValidateWasmCode(m.WasmByteCode); err != nil { + return err + } + + _, err := sdk.AccAddressFromBech32(m.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + + return nil +} + +// NewMsgRemoveChecksum creates a new MsgRemoveChecksum instance +func NewMsgRemoveChecksum(signer string, checksum []byte) *MsgRemoveChecksum { + return &MsgRemoveChecksum{ + Signer: signer, + Checksum: checksum, + } +} + +// ValidateBasic implements sdk.HasValidateBasic +func (m MsgRemoveChecksum) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(m.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + + if err := ValidateWasmChecksum(m.Checksum); err != nil { + return err + } + + return nil +} + +// MsgMigrateContract creates a new MsgMigrateContract instance +func NewMsgMigrateContract(signer, clientID string, checksum, migrateMsg []byte) *MsgMigrateContract { + return &MsgMigrateContract{ + Signer: signer, + ClientId: clientID, + Checksum: checksum, + Msg: migrateMsg, + } +} + +// ValidateBasic implements sdk.HasValidateBasic +func (m MsgMigrateContract) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(m.Signer) + if err != nil { + return errorsmod.Wrapf(ibcerrors.ErrInvalidAddress, "string could not be parsed as address: %v", err) + } + + if err := ValidateWasmChecksum(m.Checksum); err != nil { + return err + } + + if err := ValidateClientID(m.ClientId); err != nil { + return err + } + + if len(m.Msg) == 0 { + return errorsmod.Wrap(ibcerrors.ErrInvalidRequest, "migrate message cannot be empty") + } + + return nil +} diff --git a/modules/light-clients/08-wasm/types/msgs_test.go b/modules/light-clients/08-wasm/types/msgs_test.go new file mode 100644 index 0000000..269164b --- /dev/null +++ b/modules/light-clients/08-wasm/types/msgs_test.go @@ -0,0 +1,270 @@ +package types_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func TestMsgStoreCodeValidateBasic(t *testing.T) { + signer := sdk.AccAddress(ibctesting.TestAccAddress).String() + testCases := []struct { + name string + msg *types.MsgStoreCode + expErr error + }{ + { + "success: valid signer address, valid length code", + types.NewMsgStoreCode(signer, wasmtesting.Code), + nil, + }, + { + "failure: code is empty", + types.NewMsgStoreCode(signer, []byte("")), + types.ErrWasmEmptyCode, + }, + { + "failure: code is too large", + types.NewMsgStoreCode(signer, make([]byte, types.MaxWasmSize+1)), + types.ErrWasmCodeTooLarge, + }, + { + "failure: signer is invalid", + types.NewMsgStoreCode("invalid", wasmtesting.Code), + ibcerrors.ErrInvalidAddress, + }, + } + + for _, tc := range testCases { + tc := tc + + err := tc.msg.ValidateBasic() + if tc.expErr == nil { + require.NoError(t, err) + } else { + require.ErrorIs(t, err, tc.expErr) + } + } +} + +func (suite *TypesTestSuite) TestMsgStoreCodeGetSigners() { + testCases := []struct { + name string + address sdk.AccAddress + expErr error + }{ + {"success: valid address", sdk.AccAddress(ibctesting.TestAccAddress), nil}, + {"failure: nil address", nil, errors.New("empty address string is not allowed")}, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupTest() + + address := tc.address + msg := types.NewMsgStoreCode(address.String(), wasmtesting.Code) + + signers, _, err := GetSimApp(suite.chainA).AppCodec().GetMsgV1Signers(msg) + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().Equal(address.Bytes(), signers[0]) + } else { + suite.Require().Error(err) + suite.Require().Equal(err.Error(), tc.expErr.Error()) + } + }) + } +} + +func TestMsgMigrateContractValidateBasic(t *testing.T) { + signer := sdk.AccAddress(ibctesting.TestAccAddress).String() + validChecksum, err := types.CreateChecksum(wasmtesting.Code) + require.NoError(t, err, t.Name()) + validMigrateMsg := []byte("{}") + + testCases := []struct { + name string + msg *types.MsgMigrateContract + expErr error + }{ + { + "success: valid signer address, valid checksum, valid migrate msg", + types.NewMsgMigrateContract(signer, defaultWasmClientID, validChecksum, validMigrateMsg), + nil, + }, + { + "failure: invalid signer address", + types.NewMsgMigrateContract(ibctesting.InvalidID, defaultWasmClientID, validChecksum, validMigrateMsg), + ibcerrors.ErrInvalidAddress, + }, + { + "failure: clientID is not a valid client identifier", + types.NewMsgMigrateContract(signer, ibctesting.InvalidID, validChecksum, validMigrateMsg), + host.ErrInvalidID, + }, + { + "failure: clientID is not a wasm client identifier", + types.NewMsgMigrateContract(signer, ibctesting.FirstClientID, validChecksum, validMigrateMsg), + host.ErrInvalidID, + }, + { + "failure: checksum is nil", + types.NewMsgMigrateContract(signer, defaultWasmClientID, nil, validMigrateMsg), + errorsmod.Wrap(types.ErrInvalidChecksum, "checksum cannot be empty"), + }, + { + "failure: checksum is empty", + types.NewMsgMigrateContract(signer, defaultWasmClientID, []byte{}, validMigrateMsg), + errorsmod.Wrap(types.ErrInvalidChecksum, "checksum cannot be empty"), + }, + { + "failure: checksum is not 32 bytes", + types.NewMsgMigrateContract(signer, defaultWasmClientID, []byte{1}, validMigrateMsg), + errorsmod.Wrapf(types.ErrInvalidChecksum, "expected length of 32 bytes, got %d", 1), + }, + { + "failure: migrateMsg is nil", + types.NewMsgMigrateContract(signer, defaultWasmClientID, validChecksum, nil), + errorsmod.Wrap(ibcerrors.ErrInvalidRequest, "migrate message cannot be empty"), + }, + { + "failure: migrateMsg is empty", + types.NewMsgMigrateContract(signer, defaultWasmClientID, validChecksum, []byte("")), + errorsmod.Wrap(ibcerrors.ErrInvalidRequest, "migrate message cannot be empty"), + }, + } + + for _, tc := range testCases { + tc := tc + + err := tc.msg.ValidateBasic() + if tc.expErr == nil { + require.NoError(t, err) + } else { + require.ErrorIs(t, err, tc.expErr, tc.name) + } + } +} + +func (suite *TypesTestSuite) TestMsgMigrateContractGetSigners() { + checksum, err := types.CreateChecksum(wasmtesting.Code) + suite.Require().NoError(err) + + testCases := []struct { + name string + address sdk.AccAddress + expErr error + }{ + {"success: valid address", sdk.AccAddress(ibctesting.TestAccAddress), nil}, + {"failure: nil address", nil, errors.New("empty address string is not allowed")}, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupTest() + + address := tc.address + msg := types.NewMsgMigrateContract(address.String(), defaultWasmClientID, checksum, []byte("{}")) + + signers, _, err := GetSimApp(suite.chainA).AppCodec().GetMsgV1Signers(msg) + if tc.expErr == nil { + suite.Require().NoError(err) + suite.Require().Equal(address.Bytes(), signers[0]) + } else { + suite.Require().Error(err) + suite.Require().Equal(err.Error(), tc.expErr.Error()) + } + }) + } +} + +func TestMsgRemoveChecksumValidateBasic(t *testing.T) { + signer := sdk.AccAddress(ibctesting.TestAccAddress).String() + checksum, err := types.CreateChecksum(wasmtesting.Code) + require.NoError(t, err, t.Name()) + + testCases := []struct { + name string + msg *types.MsgRemoveChecksum + expErr error + }{ + { + "success: valid signer address, valid length checksum", + types.NewMsgRemoveChecksum(signer, checksum), + nil, + }, + { + "failure: checksum is empty", + types.NewMsgRemoveChecksum(signer, []byte("")), + types.ErrInvalidChecksum, + }, + { + "failure: checksum is nil", + types.NewMsgRemoveChecksum(signer, nil), + types.ErrInvalidChecksum, + }, + { + "failure: signer is invalid", + types.NewMsgRemoveChecksum(ibctesting.InvalidID, checksum), + ibcerrors.ErrInvalidAddress, + }, + } + + for _, tc := range testCases { + tc := tc + + err := tc.msg.ValidateBasic() + + if tc.expErr == nil { + require.NoError(t, err, tc.name) + } else { + require.ErrorIs(t, err, tc.expErr, tc.name) + } + } +} + +func (suite *TypesTestSuite) TestMsgRemoveChecksumGetSigners() { + checksum, err := types.CreateChecksum(wasmtesting.Code) + suite.Require().NoError(err) + + testCases := []struct { + name string + address sdk.AccAddress + expError error + }{ + {"success: valid address", sdk.AccAddress(ibctesting.TestAccAddress), nil}, + {"failure: nil address", nil, errors.New("empty address string is not allowed")}, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + suite.SetupTest() + + address := tc.address + msg := types.NewMsgRemoveChecksum(address.String(), checksum) + + signers, _, err := GetSimApp(suite.chainA).AppCodec().GetMsgV1Signers(msg) + if tc.expError == nil { + suite.Require().NoError(err) + suite.Require().Equal(address.Bytes(), signers[0]) + } else { + suite.Require().Error(err) + suite.Require().Equal(err.Error(), tc.expError.Error()) + } + }) + } +} diff --git a/modules/light-clients/08-wasm/types/query.pb.go b/modules/light-clients/08-wasm/types/query.pb.go new file mode 100644 index 0000000..b91403c --- /dev/null +++ b/modules/light-clients/08-wasm/types/query.pb.go @@ -0,0 +1,1054 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/lightclients/wasm/v1/query.proto + +package types + +import ( + context "context" + fmt "fmt" + query "github.com/cosmos/cosmos-sdk/types/query" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// QueryChecksumsRequest is the request type for the Query/Checksums RPC method. +type QueryChecksumsRequest struct { + // pagination defines an optional pagination for the request. + Pagination *query.PageRequest `protobuf:"bytes,1,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryChecksumsRequest) Reset() { *m = QueryChecksumsRequest{} } +func (m *QueryChecksumsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryChecksumsRequest) ProtoMessage() {} +func (*QueryChecksumsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_9e3718a8cb915777, []int{0} +} +func (m *QueryChecksumsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryChecksumsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryChecksumsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryChecksumsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryChecksumsRequest.Merge(m, src) +} +func (m *QueryChecksumsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryChecksumsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryChecksumsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryChecksumsRequest proto.InternalMessageInfo + +func (m *QueryChecksumsRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryChecksumsResponse is the response type for the Query/Checksums RPC method. +type QueryChecksumsResponse struct { + // checksums is a list of the hex encoded checksums of all wasm codes stored. + Checksums []string `protobuf:"bytes,1,rep,name=checksums,proto3" json:"checksums,omitempty"` + // pagination defines the pagination in the response. + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryChecksumsResponse) Reset() { *m = QueryChecksumsResponse{} } +func (m *QueryChecksumsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryChecksumsResponse) ProtoMessage() {} +func (*QueryChecksumsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_9e3718a8cb915777, []int{1} +} +func (m *QueryChecksumsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryChecksumsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryChecksumsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryChecksumsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryChecksumsResponse.Merge(m, src) +} +func (m *QueryChecksumsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryChecksumsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryChecksumsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryChecksumsResponse proto.InternalMessageInfo + +func (m *QueryChecksumsResponse) GetChecksums() []string { + if m != nil { + return m.Checksums + } + return nil +} + +func (m *QueryChecksumsResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryCodeRequest is the request type for the Query/Code RPC method. +type QueryCodeRequest struct { + // checksum is a hex encoded string of the code stored. + Checksum string `protobuf:"bytes,1,opt,name=checksum,proto3" json:"checksum,omitempty"` +} + +func (m *QueryCodeRequest) Reset() { *m = QueryCodeRequest{} } +func (m *QueryCodeRequest) String() string { return proto.CompactTextString(m) } +func (*QueryCodeRequest) ProtoMessage() {} +func (*QueryCodeRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_9e3718a8cb915777, []int{2} +} +func (m *QueryCodeRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryCodeRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryCodeRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryCodeRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryCodeRequest.Merge(m, src) +} +func (m *QueryCodeRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryCodeRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryCodeRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryCodeRequest proto.InternalMessageInfo + +func (m *QueryCodeRequest) GetChecksum() string { + if m != nil { + return m.Checksum + } + return "" +} + +// QueryCodeResponse is the response type for the Query/Code RPC method. +type QueryCodeResponse struct { + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` +} + +func (m *QueryCodeResponse) Reset() { *m = QueryCodeResponse{} } +func (m *QueryCodeResponse) String() string { return proto.CompactTextString(m) } +func (*QueryCodeResponse) ProtoMessage() {} +func (*QueryCodeResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_9e3718a8cb915777, []int{3} +} +func (m *QueryCodeResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryCodeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryCodeResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryCodeResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryCodeResponse.Merge(m, src) +} +func (m *QueryCodeResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryCodeResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryCodeResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryCodeResponse proto.InternalMessageInfo + +func (m *QueryCodeResponse) GetData() []byte { + if m != nil { + return m.Data + } + return nil +} + +func init() { + proto.RegisterType((*QueryChecksumsRequest)(nil), "ibc.lightclients.wasm.v1.QueryChecksumsRequest") + proto.RegisterType((*QueryChecksumsResponse)(nil), "ibc.lightclients.wasm.v1.QueryChecksumsResponse") + proto.RegisterType((*QueryCodeRequest)(nil), "ibc.lightclients.wasm.v1.QueryCodeRequest") + proto.RegisterType((*QueryCodeResponse)(nil), "ibc.lightclients.wasm.v1.QueryCodeResponse") +} + +func init() { + proto.RegisterFile("ibc/lightclients/wasm/v1/query.proto", fileDescriptor_9e3718a8cb915777) +} + +var fileDescriptor_9e3718a8cb915777 = []byte{ + // 434 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xcf, 0x6b, 0xd4, 0x40, + 0x14, 0xc7, 0x77, 0xd6, 0x2a, 0x66, 0xf4, 0xa0, 0x03, 0xca, 0x12, 0x4a, 0x28, 0xf1, 0x47, 0x4b, + 0xcb, 0xce, 0xdb, 0xb4, 0x08, 0x82, 0x17, 0x51, 0xd0, 0xab, 0xe6, 0xd8, 0x8b, 0x4c, 0x26, 0x43, + 0x76, 0x30, 0xc9, 0xa4, 0x9d, 0xc9, 0x4a, 0x11, 0x11, 0xfc, 0x0b, 0x04, 0x8f, 0xfa, 0xe7, 0x78, + 0xf0, 0x58, 0xf0, 0xe2, 0x51, 0x76, 0xfd, 0x43, 0x24, 0x33, 0x49, 0xb3, 0x15, 0x97, 0xdd, 0xdb, + 0xe4, 0xf1, 0x79, 0xdf, 0xef, 0xf7, 0xbd, 0x3c, 0x7c, 0x5f, 0x26, 0x1c, 0x72, 0x99, 0x4d, 0x0d, + 0xcf, 0xa5, 0x28, 0x8d, 0x86, 0x77, 0x4c, 0x17, 0x30, 0x8b, 0xe0, 0xa4, 0x16, 0xa7, 0x67, 0xb4, + 0x3a, 0x55, 0x46, 0x91, 0x91, 0x4c, 0x38, 0x5d, 0xa6, 0x68, 0x43, 0xd1, 0x59, 0xe4, 0x6f, 0x67, + 0x4a, 0x65, 0xb9, 0x00, 0x56, 0x49, 0x60, 0x65, 0xa9, 0x0c, 0x33, 0x52, 0x95, 0xda, 0xf5, 0xf9, + 0xfb, 0x5c, 0xe9, 0x42, 0x69, 0x48, 0x98, 0x16, 0x4e, 0x10, 0x66, 0x51, 0x22, 0x0c, 0x8b, 0xa0, + 0x62, 0x99, 0x2c, 0x2d, 0xec, 0xd8, 0xf0, 0x0d, 0xbe, 0xf3, 0xba, 0x21, 0x9e, 0x4f, 0x05, 0x7f, + 0xab, 0xeb, 0x42, 0xc7, 0xe2, 0xa4, 0x16, 0xda, 0x90, 0x17, 0x18, 0xf7, 0xf0, 0x08, 0xed, 0xa0, + 0xbd, 0x1b, 0x87, 0x0f, 0xa9, 0x53, 0xa6, 0x8d, 0x32, 0x75, 0x51, 0x5b, 0x65, 0xfa, 0x8a, 0x65, + 0xa2, 0xed, 0x8d, 0x97, 0x3a, 0xc3, 0x8f, 0xf8, 0xee, 0xbf, 0x06, 0xba, 0x52, 0xa5, 0x16, 0x64, + 0x1b, 0x7b, 0xbc, 0x2b, 0x8e, 0xd0, 0xce, 0x95, 0x3d, 0x2f, 0xee, 0x0b, 0xe4, 0xe5, 0x25, 0xff, + 0xa1, 0xf5, 0xdf, 0x5d, 0xeb, 0xef, 0xa4, 0x2f, 0x05, 0xa0, 0xf8, 0x96, 0x0b, 0xa0, 0xd2, 0x2e, + 0x20, 0xf1, 0xf1, 0xf5, 0xce, 0xc9, 0x8e, 0xe6, 0xc5, 0x17, 0xdf, 0xe1, 0x2e, 0xbe, 0xbd, 0xc4, + 0xb7, 0x59, 0x09, 0xde, 0x4a, 0x99, 0x61, 0x16, 0xbe, 0x19, 0xdb, 0xf7, 0xe1, 0xf7, 0x21, 0xbe, + 0x6a, 0x49, 0xf2, 0x15, 0x61, 0xef, 0x62, 0x3e, 0x02, 0x74, 0xd5, 0x7f, 0xa3, 0xff, 0x5d, 0xb5, + 0x3f, 0xd9, 0xbc, 0xc1, 0xc5, 0x09, 0x0f, 0x3e, 0xfd, 0xfc, 0xf3, 0x65, 0xf8, 0x80, 0xdc, 0x83, + 0x95, 0x87, 0xd4, 0x6f, 0xf2, 0x1b, 0xc2, 0x5b, 0xcd, 0x30, 0x64, 0x7f, 0x9d, 0x4f, 0xbf, 0x21, + 0xff, 0x60, 0x23, 0xb6, 0x8d, 0xf3, 0xc4, 0xc6, 0x79, 0x44, 0x8e, 0x36, 0x88, 0x03, 0xef, 0xbb, + 0xe7, 0x07, 0xe0, 0x2a, 0x15, 0xcf, 0x8e, 0x7f, 0xcc, 0x03, 0x74, 0x3e, 0x0f, 0xd0, 0xef, 0x79, + 0x80, 0x3e, 0x2f, 0x82, 0xc1, 0xf9, 0x22, 0x18, 0xfc, 0x5a, 0x04, 0x83, 0xe3, 0xa7, 0x99, 0x34, + 0xd3, 0x3a, 0xa1, 0x5c, 0x15, 0xd0, 0x9e, 0xb4, 0x4c, 0xf8, 0x38, 0x53, 0x50, 0xa8, 0xb4, 0xce, + 0x85, 0x76, 0x56, 0xe3, 0xce, 0x6b, 0xf2, 0x78, 0xdc, 0xda, 0x4d, 0xc0, 0x9c, 0x55, 0x42, 0x27, + 0xd7, 0xec, 0x91, 0x1f, 0xfd, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xff, 0x3e, 0xd1, 0xdc, 0x70, 0x03, + 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // Get all Wasm checksums + Checksums(ctx context.Context, in *QueryChecksumsRequest, opts ...grpc.CallOption) (*QueryChecksumsResponse, error) + // Get Wasm code for given checksum + Code(ctx context.Context, in *QueryCodeRequest, opts ...grpc.CallOption) (*QueryCodeResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) Checksums(ctx context.Context, in *QueryChecksumsRequest, opts ...grpc.CallOption) (*QueryChecksumsResponse, error) { + out := new(QueryChecksumsResponse) + err := c.cc.Invoke(ctx, "/ibc.lightclients.wasm.v1.Query/Checksums", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) Code(ctx context.Context, in *QueryCodeRequest, opts ...grpc.CallOption) (*QueryCodeResponse, error) { + out := new(QueryCodeResponse) + err := c.cc.Invoke(ctx, "/ibc.lightclients.wasm.v1.Query/Code", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // Get all Wasm checksums + Checksums(context.Context, *QueryChecksumsRequest) (*QueryChecksumsResponse, error) + // Get Wasm code for given checksum + Code(context.Context, *QueryCodeRequest) (*QueryCodeResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) Checksums(ctx context.Context, req *QueryChecksumsRequest) (*QueryChecksumsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Checksums not implemented") +} +func (*UnimplementedQueryServer) Code(ctx context.Context, req *QueryCodeRequest) (*QueryCodeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Code not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_Checksums_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryChecksumsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Checksums(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.lightclients.wasm.v1.Query/Checksums", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Checksums(ctx, req.(*QueryChecksumsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_Code_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryCodeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Code(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.lightclients.wasm.v1.Query/Code", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Code(ctx, req.(*QueryCodeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ibc.lightclients.wasm.v1.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Checksums", + Handler: _Query_Checksums_Handler, + }, + { + MethodName: "Code", + Handler: _Query_Code_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "ibc/lightclients/wasm/v1/query.proto", +} + +func (m *QueryChecksumsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryChecksumsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryChecksumsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryChecksumsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryChecksumsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryChecksumsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Checksums) > 0 { + for iNdEx := len(m.Checksums) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Checksums[iNdEx]) + copy(dAtA[i:], m.Checksums[iNdEx]) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Checksums[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *QueryCodeRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryCodeRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryCodeRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Checksum) > 0 { + i -= len(m.Checksum) + copy(dAtA[i:], m.Checksum) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Checksum))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryCodeResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryCodeResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryCodeResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryChecksumsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryChecksumsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Checksums) > 0 { + for _, s := range m.Checksums { + l = len(s) + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryCodeRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Checksum) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryCodeResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Data) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryChecksumsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryChecksumsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryChecksumsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryChecksumsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryChecksumsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryChecksumsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Checksums", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Checksums = append(m.Checksums, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryCodeRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryCodeRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryCodeRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Checksum", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Checksum = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryCodeResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryCodeResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryCodeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/light-clients/08-wasm/types/query.pb.gw.go b/modules/light-clients/08-wasm/types/query.pb.gw.go new file mode 100644 index 0000000..eda04d4 --- /dev/null +++ b/modules/light-clients/08-wasm/types/query.pb.gw.go @@ -0,0 +1,272 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: ibc/lightclients/wasm/v1/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +var ( + filter_Query_Checksums_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Query_Checksums_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryChecksumsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Checksums_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Checksums(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Checksums_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryChecksumsRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Checksums_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Checksums(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_Code_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryCodeRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["checksum"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "checksum") + } + + protoReq.Checksum, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "checksum", err) + } + + msg, err := client.Code(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Code_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryCodeRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["checksum"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "checksum") + } + + protoReq.Checksum, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "checksum", err) + } + + msg, err := server.Code(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_Checksums_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Checksums_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Checksums_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Code_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Code_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Code_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_Checksums_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Checksums_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Checksums_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Code_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Code_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Code_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_Checksums_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"ibc", "lightclients", "wasm", "v1", "checksums"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_Code_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6}, []string{"ibc", "lightclients", "wasm", "v1", "checksums", "checksum", "code"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Query_Checksums_0 = runtime.ForwardResponseMessage + + forward_Query_Code_0 = runtime.ForwardResponseMessage +) diff --git a/modules/light-clients/08-wasm/types/store.go b/modules/light-clients/08-wasm/types/store.go new file mode 100644 index 0000000..5f51831 --- /dev/null +++ b/modules/light-clients/08-wasm/types/store.go @@ -0,0 +1,44 @@ +package types + +import ( + "fmt" + + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" + + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +// GetClientState retrieves the client state from the store using the provided KVStore and codec. +// It returns the unmarshaled ClientState and a boolean indicating if the state was found. +func GetClientState(store storetypes.KVStore, cdc codec.BinaryCodec) (*ClientState, bool) { + bz := store.Get(host.ClientStateKey()) + if len(bz) == 0 { + return nil, false + } + + clientStateI := clienttypes.MustUnmarshalClientState(cdc, bz) + var clientState *ClientState + clientState, ok := clientStateI.(*ClientState) + if !ok { + panic(fmt.Errorf("cannot convert %T into %T", clientStateI, clientState)) + } + return clientState, ok +} + +// Checksum is a type alias used for wasm byte code checksums. +type Checksum = wasmvmtypes.Checksum + +// CreateChecksum creates a sha256 checksum from the given wasm code, it forwards the +// call to the wasmvm package. The code is checked for the following conditions: +// - code length is zero. +// - code length is less than 4 bytes (magic number length). +// - code does not start with the wasm magic number. +func CreateChecksum(code []byte) (Checksum, error) { + return wasmvm.CreateChecksum(code) +} diff --git a/modules/light-clients/08-wasm/types/tx.pb.go b/modules/light-clients/08-wasm/types/tx.pb.go new file mode 100644 index 0000000..69395dd --- /dev/null +++ b/modules/light-clients/08-wasm/types/tx.pb.go @@ -0,0 +1,1524 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/lightclients/wasm/v1/tx.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/cosmos/cosmos-sdk/types/msgservice" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// MsgStoreCode defines the request type for the StoreCode rpc. +type MsgStoreCode struct { + // signer address + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // wasm byte code of light client contract. It can be raw or gzip compressed + WasmByteCode []byte `protobuf:"bytes,2,opt,name=wasm_byte_code,json=wasmByteCode,proto3" json:"wasm_byte_code,omitempty"` +} + +func (m *MsgStoreCode) Reset() { *m = MsgStoreCode{} } +func (m *MsgStoreCode) String() string { return proto.CompactTextString(m) } +func (*MsgStoreCode) ProtoMessage() {} +func (*MsgStoreCode) Descriptor() ([]byte, []int) { + return fileDescriptor_1d9737363bf1e38d, []int{0} +} +func (m *MsgStoreCode) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgStoreCode) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgStoreCode.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgStoreCode) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgStoreCode.Merge(m, src) +} +func (m *MsgStoreCode) XXX_Size() int { + return m.Size() +} +func (m *MsgStoreCode) XXX_DiscardUnknown() { + xxx_messageInfo_MsgStoreCode.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgStoreCode proto.InternalMessageInfo + +func (m *MsgStoreCode) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgStoreCode) GetWasmByteCode() []byte { + if m != nil { + return m.WasmByteCode + } + return nil +} + +// MsgStoreCodeResponse defines the response type for the StoreCode rpc +type MsgStoreCodeResponse struct { + // checksum is the sha256 hash of the stored code + Checksum []byte `protobuf:"bytes,1,opt,name=checksum,proto3" json:"checksum,omitempty"` +} + +func (m *MsgStoreCodeResponse) Reset() { *m = MsgStoreCodeResponse{} } +func (m *MsgStoreCodeResponse) String() string { return proto.CompactTextString(m) } +func (*MsgStoreCodeResponse) ProtoMessage() {} +func (*MsgStoreCodeResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_1d9737363bf1e38d, []int{1} +} +func (m *MsgStoreCodeResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgStoreCodeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgStoreCodeResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgStoreCodeResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgStoreCodeResponse.Merge(m, src) +} +func (m *MsgStoreCodeResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgStoreCodeResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgStoreCodeResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgStoreCodeResponse proto.InternalMessageInfo + +func (m *MsgStoreCodeResponse) GetChecksum() []byte { + if m != nil { + return m.Checksum + } + return nil +} + +// MsgRemoveChecksum defines the request type for the MsgRemoveChecksum rpc. +type MsgRemoveChecksum struct { + // signer address + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // checksum is the sha256 hash to be removed from the store + Checksum []byte `protobuf:"bytes,2,opt,name=checksum,proto3" json:"checksum,omitempty"` +} + +func (m *MsgRemoveChecksum) Reset() { *m = MsgRemoveChecksum{} } +func (m *MsgRemoveChecksum) String() string { return proto.CompactTextString(m) } +func (*MsgRemoveChecksum) ProtoMessage() {} +func (*MsgRemoveChecksum) Descriptor() ([]byte, []int) { + return fileDescriptor_1d9737363bf1e38d, []int{2} +} +func (m *MsgRemoveChecksum) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgRemoveChecksum) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgRemoveChecksum.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgRemoveChecksum) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgRemoveChecksum.Merge(m, src) +} +func (m *MsgRemoveChecksum) XXX_Size() int { + return m.Size() +} +func (m *MsgRemoveChecksum) XXX_DiscardUnknown() { + xxx_messageInfo_MsgRemoveChecksum.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgRemoveChecksum proto.InternalMessageInfo + +func (m *MsgRemoveChecksum) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgRemoveChecksum) GetChecksum() []byte { + if m != nil { + return m.Checksum + } + return nil +} + +// MsgStoreChecksumResponse defines the response type for the StoreCode rpc +type MsgRemoveChecksumResponse struct { +} + +func (m *MsgRemoveChecksumResponse) Reset() { *m = MsgRemoveChecksumResponse{} } +func (m *MsgRemoveChecksumResponse) String() string { return proto.CompactTextString(m) } +func (*MsgRemoveChecksumResponse) ProtoMessage() {} +func (*MsgRemoveChecksumResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_1d9737363bf1e38d, []int{3} +} +func (m *MsgRemoveChecksumResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgRemoveChecksumResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgRemoveChecksumResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgRemoveChecksumResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgRemoveChecksumResponse.Merge(m, src) +} +func (m *MsgRemoveChecksumResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgRemoveChecksumResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgRemoveChecksumResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgRemoveChecksumResponse proto.InternalMessageInfo + +// MsgMigrateContract defines the request type for the MigrateContract rpc. +type MsgMigrateContract struct { + // signer address + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // the client id of the contract + ClientId string `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + // checksum is the sha256 hash of the new wasm byte code for the contract + Checksum []byte `protobuf:"bytes,3,opt,name=checksum,proto3" json:"checksum,omitempty"` + // the json encoded message to be passed to the contract on migration + Msg []byte `protobuf:"bytes,4,opt,name=msg,proto3" json:"msg,omitempty"` +} + +func (m *MsgMigrateContract) Reset() { *m = MsgMigrateContract{} } +func (m *MsgMigrateContract) String() string { return proto.CompactTextString(m) } +func (*MsgMigrateContract) ProtoMessage() {} +func (*MsgMigrateContract) Descriptor() ([]byte, []int) { + return fileDescriptor_1d9737363bf1e38d, []int{4} +} +func (m *MsgMigrateContract) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgMigrateContract) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgMigrateContract.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgMigrateContract) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgMigrateContract.Merge(m, src) +} +func (m *MsgMigrateContract) XXX_Size() int { + return m.Size() +} +func (m *MsgMigrateContract) XXX_DiscardUnknown() { + xxx_messageInfo_MsgMigrateContract.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgMigrateContract proto.InternalMessageInfo + +func (m *MsgMigrateContract) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgMigrateContract) GetClientId() string { + if m != nil { + return m.ClientId + } + return "" +} + +func (m *MsgMigrateContract) GetChecksum() []byte { + if m != nil { + return m.Checksum + } + return nil +} + +func (m *MsgMigrateContract) GetMsg() []byte { + if m != nil { + return m.Msg + } + return nil +} + +// MsgMigrateContractResponse defines the response type for the MigrateContract rpc +type MsgMigrateContractResponse struct { +} + +func (m *MsgMigrateContractResponse) Reset() { *m = MsgMigrateContractResponse{} } +func (m *MsgMigrateContractResponse) String() string { return proto.CompactTextString(m) } +func (*MsgMigrateContractResponse) ProtoMessage() {} +func (*MsgMigrateContractResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_1d9737363bf1e38d, []int{5} +} +func (m *MsgMigrateContractResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgMigrateContractResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgMigrateContractResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgMigrateContractResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgMigrateContractResponse.Merge(m, src) +} +func (m *MsgMigrateContractResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgMigrateContractResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgMigrateContractResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgMigrateContractResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*MsgStoreCode)(nil), "ibc.lightclients.wasm.v1.MsgStoreCode") + proto.RegisterType((*MsgStoreCodeResponse)(nil), "ibc.lightclients.wasm.v1.MsgStoreCodeResponse") + proto.RegisterType((*MsgRemoveChecksum)(nil), "ibc.lightclients.wasm.v1.MsgRemoveChecksum") + proto.RegisterType((*MsgRemoveChecksumResponse)(nil), "ibc.lightclients.wasm.v1.MsgRemoveChecksumResponse") + proto.RegisterType((*MsgMigrateContract)(nil), "ibc.lightclients.wasm.v1.MsgMigrateContract") + proto.RegisterType((*MsgMigrateContractResponse)(nil), "ibc.lightclients.wasm.v1.MsgMigrateContractResponse") +} + +func init() { proto.RegisterFile("ibc/lightclients/wasm/v1/tx.proto", fileDescriptor_1d9737363bf1e38d) } + +var fileDescriptor_1d9737363bf1e38d = []byte{ + // 446 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x53, 0x3d, 0x6f, 0x13, 0x41, + 0x14, 0xf4, 0xda, 0x10, 0xc5, 0x0f, 0x2b, 0xc0, 0x09, 0x81, 0xb9, 0xa0, 0x53, 0xb0, 0x10, 0x8a, + 0x02, 0xde, 0xcd, 0x07, 0x05, 0xa2, 0x42, 0x49, 0x45, 0x71, 0xcd, 0x41, 0x43, 0x1a, 0xcb, 0xb7, + 0xb7, 0x5a, 0xaf, 0xf0, 0x7a, 0xad, 0x7b, 0x6b, 0x83, 0x3b, 0x84, 0xf8, 0x01, 0xfc, 0x94, 0xfc, + 0x0c, 0xca, 0x94, 0x14, 0x14, 0xc8, 0x2e, 0xf2, 0x37, 0xd0, 0x7d, 0x99, 0x3b, 0x47, 0x8e, 0x70, + 0x77, 0xfb, 0x34, 0x6f, 0x66, 0xee, 0x8d, 0x06, 0x9e, 0xaa, 0x90, 0xb3, 0xa1, 0x92, 0x03, 0xcb, + 0x87, 0x4a, 0x8c, 0x2c, 0xb2, 0xcf, 0x7d, 0xd4, 0x6c, 0x7a, 0xc4, 0xec, 0x17, 0x3a, 0x8e, 0x8d, + 0x35, 0x4e, 0x5b, 0x85, 0x9c, 0x96, 0x21, 0x34, 0x81, 0xd0, 0xe9, 0x91, 0xfb, 0x88, 0x1b, 0xd4, + 0x06, 0x99, 0x46, 0x99, 0x6c, 0x68, 0x94, 0xd9, 0x4a, 0xe7, 0x23, 0xb4, 0x7c, 0x94, 0xef, 0xad, + 0x89, 0xc5, 0x99, 0x89, 0x84, 0xf3, 0x10, 0xb6, 0x50, 0xc9, 0x91, 0x88, 0xdb, 0x64, 0x8f, 0xec, + 0x37, 0x83, 0xfc, 0xe5, 0x3c, 0x83, 0x9d, 0x84, 0xab, 0x17, 0xce, 0xac, 0xe8, 0x71, 0x13, 0x89, + 0x76, 0x7d, 0x8f, 0xec, 0xb7, 0x82, 0x56, 0x32, 0x3d, 0x9d, 0xd9, 0x74, 0xfb, 0xcd, 0x9d, 0x6f, + 0x57, 0x17, 0x07, 0xf9, 0x4a, 0xe7, 0x18, 0x1e, 0x94, 0xa9, 0x03, 0x81, 0x63, 0x33, 0x42, 0xe1, + 0xb8, 0xb0, 0xcd, 0x07, 0x82, 0x7f, 0xc2, 0x89, 0x4e, 0x45, 0x5a, 0xc1, 0xf2, 0xdd, 0xf9, 0x00, + 0xf7, 0x7d, 0x94, 0x81, 0xd0, 0x66, 0x2a, 0xce, 0xf2, 0xe1, 0x5a, 0x4f, 0x65, 0xa2, 0x7a, 0x95, + 0xa8, 0xea, 0x64, 0x17, 0x1e, 0x5f, 0x63, 0x2d, 0xec, 0x74, 0xbe, 0x13, 0x70, 0x7c, 0x94, 0xbe, + 0x92, 0x71, 0x3f, 0xf9, 0x8d, 0x91, 0x8d, 0xfb, 0xdc, 0xae, 0x15, 0xdd, 0x85, 0x66, 0x76, 0xdc, + 0x9e, 0x8a, 0x52, 0xd5, 0x66, 0xb0, 0x9d, 0x0d, 0xde, 0x45, 0x15, 0x47, 0x8d, 0xaa, 0x23, 0xe7, + 0x1e, 0x34, 0x34, 0xca, 0xf6, 0xad, 0x74, 0x9c, 0x7c, 0x56, 0x3d, 0x3e, 0x01, 0xf7, 0xba, 0x8b, + 0xc2, 0xe4, 0xf1, 0xef, 0x3a, 0x34, 0x7c, 0x94, 0x0e, 0x87, 0xe6, 0xbf, 0xac, 0x9e, 0xd3, 0x75, + 0x79, 0xd3, 0xf2, 0xe1, 0x5d, 0xfa, 0x7f, 0xb8, 0x65, 0x40, 0x31, 0xec, 0xac, 0x24, 0xf0, 0xe2, + 0x46, 0x86, 0x2a, 0xd8, 0x3d, 0xd9, 0x00, 0xbc, 0xd4, 0x9c, 0xc0, 0xdd, 0xd5, 0x04, 0x5e, 0xde, + 0xc8, 0xb3, 0x82, 0x76, 0x5f, 0x6d, 0x82, 0x2e, 0x64, 0xdd, 0xdb, 0x5f, 0xaf, 0x2e, 0x0e, 0xc8, + 0xe9, 0xf9, 0xcf, 0xb9, 0x47, 0x2e, 0xe7, 0x1e, 0xf9, 0x33, 0xf7, 0xc8, 0x8f, 0x85, 0x57, 0xbb, + 0x5c, 0x78, 0xb5, 0x5f, 0x0b, 0xaf, 0x76, 0xfe, 0x56, 0x2a, 0x3b, 0x98, 0x84, 0x94, 0x1b, 0xcd, + 0xf2, 0x0e, 0xa9, 0x90, 0x77, 0xa5, 0x61, 0xda, 0x44, 0x93, 0xa1, 0xc0, 0xac, 0x92, 0xdd, 0xa2, + 0x93, 0x87, 0xaf, 0xbb, 0x79, 0x2d, 0x0f, 0x99, 0x9d, 0x8d, 0x05, 0x86, 0x5b, 0x69, 0xd1, 0x4e, + 0xfe, 0x06, 0x00, 0x00, 0xff, 0xff, 0xec, 0x80, 0x8a, 0x8c, 0xc0, 0x03, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MsgClient is the client API for Msg service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MsgClient interface { + // StoreCode defines a rpc handler method for MsgStoreCode. + StoreCode(ctx context.Context, in *MsgStoreCode, opts ...grpc.CallOption) (*MsgStoreCodeResponse, error) + // RemoveChecksum defines a rpc handler method for MsgRemoveChecksum. + RemoveChecksum(ctx context.Context, in *MsgRemoveChecksum, opts ...grpc.CallOption) (*MsgRemoveChecksumResponse, error) + // MigrateContract defines a rpc handler method for MsgMigrateContract. + MigrateContract(ctx context.Context, in *MsgMigrateContract, opts ...grpc.CallOption) (*MsgMigrateContractResponse, error) +} + +type msgClient struct { + cc grpc1.ClientConn +} + +func NewMsgClient(cc grpc1.ClientConn) MsgClient { + return &msgClient{cc} +} + +func (c *msgClient) StoreCode(ctx context.Context, in *MsgStoreCode, opts ...grpc.CallOption) (*MsgStoreCodeResponse, error) { + out := new(MsgStoreCodeResponse) + err := c.cc.Invoke(ctx, "/ibc.lightclients.wasm.v1.Msg/StoreCode", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) RemoveChecksum(ctx context.Context, in *MsgRemoveChecksum, opts ...grpc.CallOption) (*MsgRemoveChecksumResponse, error) { + out := new(MsgRemoveChecksumResponse) + err := c.cc.Invoke(ctx, "/ibc.lightclients.wasm.v1.Msg/RemoveChecksum", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) MigrateContract(ctx context.Context, in *MsgMigrateContract, opts ...grpc.CallOption) (*MsgMigrateContractResponse, error) { + out := new(MsgMigrateContractResponse) + err := c.cc.Invoke(ctx, "/ibc.lightclients.wasm.v1.Msg/MigrateContract", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MsgServer is the server API for Msg service. +type MsgServer interface { + // StoreCode defines a rpc handler method for MsgStoreCode. + StoreCode(context.Context, *MsgStoreCode) (*MsgStoreCodeResponse, error) + // RemoveChecksum defines a rpc handler method for MsgRemoveChecksum. + RemoveChecksum(context.Context, *MsgRemoveChecksum) (*MsgRemoveChecksumResponse, error) + // MigrateContract defines a rpc handler method for MsgMigrateContract. + MigrateContract(context.Context, *MsgMigrateContract) (*MsgMigrateContractResponse, error) +} + +// UnimplementedMsgServer can be embedded to have forward compatible implementations. +type UnimplementedMsgServer struct { +} + +func (*UnimplementedMsgServer) StoreCode(ctx context.Context, req *MsgStoreCode) (*MsgStoreCodeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method StoreCode not implemented") +} +func (*UnimplementedMsgServer) RemoveChecksum(ctx context.Context, req *MsgRemoveChecksum) (*MsgRemoveChecksumResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RemoveChecksum not implemented") +} +func (*UnimplementedMsgServer) MigrateContract(ctx context.Context, req *MsgMigrateContract) (*MsgMigrateContractResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method MigrateContract not implemented") +} + +func RegisterMsgServer(s grpc1.Server, srv MsgServer) { + s.RegisterService(&_Msg_serviceDesc, srv) +} + +func _Msg_StoreCode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgStoreCode) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).StoreCode(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.lightclients.wasm.v1.Msg/StoreCode", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).StoreCode(ctx, req.(*MsgStoreCode)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_RemoveChecksum_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgRemoveChecksum) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).RemoveChecksum(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.lightclients.wasm.v1.Msg/RemoveChecksum", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).RemoveChecksum(ctx, req.(*MsgRemoveChecksum)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_MigrateContract_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgMigrateContract) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).MigrateContract(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ibc.lightclients.wasm.v1.Msg/MigrateContract", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).MigrateContract(ctx, req.(*MsgMigrateContract)) + } + return interceptor(ctx, in, info, handler) +} + +var _Msg_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ibc.lightclients.wasm.v1.Msg", + HandlerType: (*MsgServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "StoreCode", + Handler: _Msg_StoreCode_Handler, + }, + { + MethodName: "RemoveChecksum", + Handler: _Msg_RemoveChecksum_Handler, + }, + { + MethodName: "MigrateContract", + Handler: _Msg_MigrateContract_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "ibc/lightclients/wasm/v1/tx.proto", +} + +func (m *MsgStoreCode) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgStoreCode) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgStoreCode) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.WasmByteCode) > 0 { + i -= len(m.WasmByteCode) + copy(dAtA[i:], m.WasmByteCode) + i = encodeVarintTx(dAtA, i, uint64(len(m.WasmByteCode))) + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgStoreCodeResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgStoreCodeResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgStoreCodeResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Checksum) > 0 { + i -= len(m.Checksum) + copy(dAtA[i:], m.Checksum) + i = encodeVarintTx(dAtA, i, uint64(len(m.Checksum))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgRemoveChecksum) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgRemoveChecksum) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgRemoveChecksum) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Checksum) > 0 { + i -= len(m.Checksum) + copy(dAtA[i:], m.Checksum) + i = encodeVarintTx(dAtA, i, uint64(len(m.Checksum))) + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgRemoveChecksumResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgRemoveChecksumResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgRemoveChecksumResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgMigrateContract) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgMigrateContract) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgMigrateContract) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Msg) > 0 { + i -= len(m.Msg) + copy(dAtA[i:], m.Msg) + i = encodeVarintTx(dAtA, i, uint64(len(m.Msg))) + i-- + dAtA[i] = 0x22 + } + if len(m.Checksum) > 0 { + i -= len(m.Checksum) + copy(dAtA[i:], m.Checksum) + i = encodeVarintTx(dAtA, i, uint64(len(m.Checksum))) + i-- + dAtA[i] = 0x1a + } + if len(m.ClientId) > 0 { + i -= len(m.ClientId) + copy(dAtA[i:], m.ClientId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ClientId))) + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgMigrateContractResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgMigrateContractResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgMigrateContractResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgStoreCode) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.WasmByteCode) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgStoreCodeResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Checksum) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgRemoveChecksum) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Checksum) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgRemoveChecksumResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgMigrateContract) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ClientId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Checksum) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Msg) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgMigrateContractResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgStoreCode) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgStoreCode: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgStoreCode: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field WasmByteCode", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.WasmByteCode = append(m.WasmByteCode[:0], dAtA[iNdEx:postIndex]...) + if m.WasmByteCode == nil { + m.WasmByteCode = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgStoreCodeResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgStoreCodeResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgStoreCodeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Checksum", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Checksum = append(m.Checksum[:0], dAtA[iNdEx:postIndex]...) + if m.Checksum == nil { + m.Checksum = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgRemoveChecksum) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgRemoveChecksum: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgRemoveChecksum: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Checksum", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Checksum = append(m.Checksum[:0], dAtA[iNdEx:postIndex]...) + if m.Checksum == nil { + m.Checksum = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgRemoveChecksumResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgRemoveChecksumResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgRemoveChecksumResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgMigrateContract) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgMigrateContract: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgMigrateContract: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ClientId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Checksum", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Checksum = append(m.Checksum[:0], dAtA[iNdEx:postIndex]...) + if m.Checksum == nil { + m.Checksum = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Msg", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Msg = append(m.Msg[:0], dAtA[iNdEx:postIndex]...) + if m.Msg == nil { + m.Msg = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgMigrateContractResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgMigrateContractResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgMigrateContractResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTx(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/light-clients/08-wasm/types/types_test.go b/modules/light-clients/08-wasm/types/types_test.go new file mode 100644 index 0000000..9cca3ca --- /dev/null +++ b/modules/light-clients/08-wasm/types/types_test.go @@ -0,0 +1,54 @@ +package types_test + +import ( + "encoding/json" + "errors" + "testing" + + dbm "github.com/cosmos/cosmos-db" + testifysuite "github.com/stretchr/testify/suite" + + "cosmossdk.io/log" + + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing/simapp" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +const ( + tmClientID = "07-tendermint-0" + defaultWasmClientID = "08-wasm-0" +) + +type TypesTestSuite struct { + testifysuite.Suite + coordinator *ibctesting.Coordinator + chainA *ibctesting.TestChain +} + +func TestWasmTestSuite(t *testing.T) { + testifysuite.Run(t, new(TypesTestSuite)) +} + +func (suite *TypesTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCustomAppCoordinator(suite.T(), 1, setupTestingApp) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) +} + +// GetSimApp returns the duplicated SimApp from within the 08-wasm directory. +// This must be used instead of chain.GetSimApp() for tests within this directory. +func GetSimApp(chain *ibctesting.TestChain) *simapp.SimApp { + app, ok := chain.App.(*simapp.SimApp) + if !ok { + panic(errors.New("chain is not a simapp.SimApp")) + } + return app +} + +// setupTestingApp provides the duplicated simapp which is specific to the 08-wasm module on chain creation. +func setupTestingApp() (ibctesting.TestingApp, map[string]json.RawMessage) { + db := dbm.NewMemDB() + app := simapp.NewUnitTestSimApp(log.NewNopLogger(), db, nil, true, simtestutil.EmptyAppOptions{}, nil) + return app, app.DefaultGenesis() +} diff --git a/modules/light-clients/08-wasm/types/utils.go b/modules/light-clients/08-wasm/types/utils.go new file mode 100644 index 0000000..98bfb8b --- /dev/null +++ b/modules/light-clients/08-wasm/types/utils.go @@ -0,0 +1,71 @@ +package types + +import ( + "bytes" + "compress/gzip" + "io" +) + +// Copied gzip feature from wasmd +// https://github.com/CosmWasm/wasmd/blob/v0.31.0/x/wasm/ioutils/utils.go + +// Note: []byte can never be const as they are inherently mutable + +// magic bytes to identify gzip. +// See https://www.ietf.org/rfc/rfc1952.txt +// and https://github.com/golang/go/blob/master/src/net/http/sniff.go#L186 +var gzipIdent = []byte("\x1F\x8B\x08") + +// IsGzip returns checks if the file contents are gzip compressed +func IsGzip(input []byte) bool { + return len(input) >= 3 && bytes.Equal(gzipIdent, input[0:3]) +} + +// Uncompress expects a valid gzip source to unpack or fails. See IsGzip +func Uncompress(gzipSrc []byte, limit uint64) ([]byte, error) { + if uint64(len(gzipSrc)) > limit { + return nil, ErrWasmCodeTooLarge + } + zr, err := gzip.NewReader(bytes.NewReader(gzipSrc)) + if err != nil { + return nil, err + } + zr.Multistream(false) + defer zr.Close() + return io.ReadAll(limitReader(zr, int64(limit))) +} + +// limitReader returns a Reader that reads from r +// but stops with types.ErrLimit after n bytes. +// The underlying implementation is a *io.LimitedReader. +func limitReader(r io.Reader, n int64) io.Reader { + return &limitedReader{r: &io.LimitedReader{R: r, N: n}} +} + +type limitedReader struct { + r *io.LimitedReader +} + +func (l *limitedReader) Read(p []byte) (n int, err error) { + if l.r.N <= 0 { + return 0, ErrWasmCodeTooLarge + } + return l.r.Read(p) +} + +// GzipIt compresses the input ([]byte) +func GzipIt(input []byte) ([]byte, error) { + // Create gzip writer. + var b bytes.Buffer + w := gzip.NewWriter(&b) + _, err := w.Write(input) + if err != nil { + return nil, err + } + err = w.Close() // You must close this first to flush the bytes to the buffer. + if err != nil { + return nil, err + } + + return b.Bytes(), nil +} diff --git a/modules/light-clients/08-wasm/types/validation.go b/modules/light-clients/08-wasm/types/validation.go new file mode 100644 index 0000000..df9532c --- /dev/null +++ b/modules/light-clients/08-wasm/types/validation.go @@ -0,0 +1,53 @@ +package types + +import ( + "strings" + + errorsmod "cosmossdk.io/errors" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" +) + +// MaxWasmSize denotes the maximum size (in bytes) a contract is allowed to be. +const MaxWasmSize uint64 = 3 * 1024 * 1024 + +// ValidateWasmCode valides that the size of the wasm code is in the allowed range +// and that the contents are of a wasm binary. +func ValidateWasmCode(code []byte) error { + if len(code) == 0 { + return ErrWasmEmptyCode + } + if uint64(len(code)) > MaxWasmSize { + return ErrWasmCodeTooLarge + } + + return nil +} + +// ValidateWasmChecksum validates that the checksum is of the correct length +func ValidateWasmChecksum(checksum Checksum) error { + lenChecksum := len(checksum) + if lenChecksum == 0 { + return errorsmod.Wrap(ErrInvalidChecksum, "checksum cannot be empty") + } + if lenChecksum != 32 { // sha256 output is 256 bits long + return errorsmod.Wrapf(ErrInvalidChecksum, "expected length of 32 bytes, got %d", lenChecksum) + } + + return nil +} + +// ValidateClientID validates the client identifier by ensuring that it conforms +// to the 02-client identifier format and that it is a 08-wasm clientID. +func ValidateClientID(clientID string) error { + if !clienttypes.IsValidClientID(clientID) { + return errorsmod.Wrapf(host.ErrInvalidID, "invalid client identifier %s", clientID) + } + + if !strings.HasPrefix(clientID, Wasm) { + return errorsmod.Wrapf(host.ErrInvalidID, "client identifier %s does not contain %s prefix", clientID, Wasm) + } + + return nil +} diff --git a/modules/light-clients/08-wasm/types/validation_test.go b/modules/light-clients/08-wasm/types/validation_test.go new file mode 100644 index 0000000..cb39a0e --- /dev/null +++ b/modules/light-clients/08-wasm/types/validation_test.go @@ -0,0 +1,160 @@ +package types_test + +import ( + "crypto/rand" + "testing" + + "github.com/stretchr/testify/require" + + errorsmod "cosmossdk.io/errors" + + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func TestValidateWasmCode(t *testing.T) { + var code []byte + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + code = wasmtesting.Code + }, + nil, + }, + { + "failure: empty byte slice", + func() { + code = []byte{} + }, + types.ErrWasmEmptyCode, + }, + { + "failure: byte slice too large", + func() { + expLength := types.MaxWasmSize + 1 + code = make([]byte, expLength) + length, err := rand.Read(code) + require.NoError(t, err, t.Name()) + require.Equal(t, expLength, uint64(length), t.Name()) + }, + types.ErrWasmCodeTooLarge, + }, + } + + for _, tc := range testCases { + tc.malleate() + + err := types.ValidateWasmCode(code) + + if tc.expError == nil { + require.NoError(t, err, tc.name) + } else { + require.ErrorIs(t, err, tc.expError, tc.name) + } + } +} + +func TestValidateWasmChecksum(t *testing.T) { + var checksum types.Checksum + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success", + func() { + hash, err := types.CreateChecksum(wasmtesting.Code) + require.NoError(t, err, t.Name()) + checksum = hash + }, + nil, + }, + { + "failure: nil byte slice", + func() { + checksum = nil + }, + errorsmod.Wrap(types.ErrInvalidChecksum, "checksum cannot be empty"), + }, + { + "failure: empty byte slice", + func() { + checksum = []byte{} + }, + errorsmod.Wrap(types.ErrInvalidChecksum, "checksum cannot be empty"), + }, + { + "failure: byte slice size is not 32", + func() { + checksum = []byte{1} + }, + errorsmod.Wrapf(types.ErrInvalidChecksum, "expected length of 32 bytes, got %d", 1), + }, + } + + for _, tc := range testCases { + tc.malleate() + + err := types.ValidateWasmChecksum(checksum) + + if tc.expError == nil { + require.NoError(t, err, tc.name) + } else { + require.ErrorContains(t, err, tc.expError.Error(), tc.name) + } + } +} + +func TestValidateClientID(t *testing.T) { + var clientID string + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success: valid wasm client identifier", + func() { + clientID = defaultWasmClientID + }, + nil, + }, + { + "failure: empty clientID", + func() { + clientID = "" + }, + errorsmod.Wrapf(host.ErrInvalidID, "invalid client identifier %s", clientID), + }, + { + "failure: clientID is not a wasm client identifier", + func() { + clientID = ibctesting.FirstClientID + }, + errorsmod.Wrapf(host.ErrInvalidID, "client identifier %s does not contain %s prefix", ibctesting.FirstClientID, types.Wasm), + }, + } + + for _, tc := range testCases { + tc.malleate() + + err := types.ValidateClientID(clientID) + + if tc.expError == nil { + require.NoError(t, err, tc.name) + } else { + require.ErrorContains(t, err, tc.expError.Error(), tc.name) + } + } +} diff --git a/modules/light-clients/08-wasm/types/wasm.pb.go b/modules/light-clients/08-wasm/types/wasm.pb.go new file mode 100644 index 0000000..207e6f6 --- /dev/null +++ b/modules/light-clients/08-wasm/types/wasm.pb.go @@ -0,0 +1,934 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ibc/lightclients/wasm/v1/wasm.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + types "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Wasm light client's Client state +type ClientState struct { + // bytes encoding the client state of the underlying light client + // implemented as a Wasm contract. + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + Checksum []byte `protobuf:"bytes,2,opt,name=checksum,proto3" json:"checksum,omitempty"` + LatestHeight types.Height `protobuf:"bytes,3,opt,name=latest_height,json=latestHeight,proto3" json:"latest_height"` +} + +func (m *ClientState) Reset() { *m = ClientState{} } +func (m *ClientState) String() string { return proto.CompactTextString(m) } +func (*ClientState) ProtoMessage() {} +func (*ClientState) Descriptor() ([]byte, []int) { + return fileDescriptor_678928ebbdee1807, []int{0} +} +func (m *ClientState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ClientState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ClientState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ClientState) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClientState.Merge(m, src) +} +func (m *ClientState) XXX_Size() int { + return m.Size() +} +func (m *ClientState) XXX_DiscardUnknown() { + xxx_messageInfo_ClientState.DiscardUnknown(m) +} + +var xxx_messageInfo_ClientState proto.InternalMessageInfo + +// Wasm light client's ConsensusState +type ConsensusState struct { + // bytes encoding the consensus state of the underlying light client + // implemented as a Wasm contract. + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` +} + +func (m *ConsensusState) Reset() { *m = ConsensusState{} } +func (m *ConsensusState) String() string { return proto.CompactTextString(m) } +func (*ConsensusState) ProtoMessage() {} +func (*ConsensusState) Descriptor() ([]byte, []int) { + return fileDescriptor_678928ebbdee1807, []int{1} +} +func (m *ConsensusState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ConsensusState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ConsensusState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ConsensusState) XXX_Merge(src proto.Message) { + xxx_messageInfo_ConsensusState.Merge(m, src) +} +func (m *ConsensusState) XXX_Size() int { + return m.Size() +} +func (m *ConsensusState) XXX_DiscardUnknown() { + xxx_messageInfo_ConsensusState.DiscardUnknown(m) +} + +var xxx_messageInfo_ConsensusState proto.InternalMessageInfo + +// Wasm light client message (either header(s) or misbehaviour) +type ClientMessage struct { + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` +} + +func (m *ClientMessage) Reset() { *m = ClientMessage{} } +func (m *ClientMessage) String() string { return proto.CompactTextString(m) } +func (*ClientMessage) ProtoMessage() {} +func (*ClientMessage) Descriptor() ([]byte, []int) { + return fileDescriptor_678928ebbdee1807, []int{2} +} +func (m *ClientMessage) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ClientMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ClientMessage.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ClientMessage) XXX_Merge(src proto.Message) { + xxx_messageInfo_ClientMessage.Merge(m, src) +} +func (m *ClientMessage) XXX_Size() int { + return m.Size() +} +func (m *ClientMessage) XXX_DiscardUnknown() { + xxx_messageInfo_ClientMessage.DiscardUnknown(m) +} + +var xxx_messageInfo_ClientMessage proto.InternalMessageInfo + +// Checksums defines a list of all checksums that are stored +// +// Deprecated: This message is deprecated in favor of storing the checksums +// using a Collections.KeySet. +// +// Deprecated: Do not use. +type Checksums struct { + Checksums [][]byte `protobuf:"bytes,1,rep,name=checksums,proto3" json:"checksums,omitempty"` +} + +func (m *Checksums) Reset() { *m = Checksums{} } +func (m *Checksums) String() string { return proto.CompactTextString(m) } +func (*Checksums) ProtoMessage() {} +func (*Checksums) Descriptor() ([]byte, []int) { + return fileDescriptor_678928ebbdee1807, []int{3} +} +func (m *Checksums) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Checksums) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Checksums.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Checksums) XXX_Merge(src proto.Message) { + xxx_messageInfo_Checksums.Merge(m, src) +} +func (m *Checksums) XXX_Size() int { + return m.Size() +} +func (m *Checksums) XXX_DiscardUnknown() { + xxx_messageInfo_Checksums.DiscardUnknown(m) +} + +var xxx_messageInfo_Checksums proto.InternalMessageInfo + +func (m *Checksums) GetChecksums() [][]byte { + if m != nil { + return m.Checksums + } + return nil +} + +func init() { + proto.RegisterType((*ClientState)(nil), "ibc.lightclients.wasm.v1.ClientState") + proto.RegisterType((*ConsensusState)(nil), "ibc.lightclients.wasm.v1.ConsensusState") + proto.RegisterType((*ClientMessage)(nil), "ibc.lightclients.wasm.v1.ClientMessage") + proto.RegisterType((*Checksums)(nil), "ibc.lightclients.wasm.v1.Checksums") +} + +func init() { + proto.RegisterFile("ibc/lightclients/wasm/v1/wasm.proto", fileDescriptor_678928ebbdee1807) +} + +var fileDescriptor_678928ebbdee1807 = []byte{ + // 341 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x91, 0xb1, 0x4e, 0xf3, 0x30, + 0x10, 0xc7, 0xe3, 0xb6, 0xfa, 0xf4, 0xd5, 0x6d, 0x19, 0x22, 0x86, 0x28, 0x42, 0x69, 0x55, 0x96, + 0x82, 0x14, 0xbb, 0x85, 0x05, 0x75, 0x42, 0xad, 0x90, 0x58, 0x58, 0xca, 0xd6, 0x05, 0x25, 0xae, + 0x95, 0x58, 0x24, 0x75, 0xd5, 0x73, 0x8a, 0x78, 0x03, 0xc4, 0xc4, 0x23, 0xf0, 0x38, 0x1d, 0x3b, + 0x32, 0x21, 0xd4, 0xbe, 0x08, 0xb2, 0x9d, 0x02, 0x0b, 0x4c, 0xb9, 0xfc, 0xfd, 0xf3, 0xdd, 0x4f, + 0x3e, 0x7c, 0x2c, 0x62, 0x46, 0x33, 0x91, 0xa4, 0x8a, 0x65, 0x82, 0xcf, 0x15, 0xd0, 0x87, 0x08, + 0x72, 0xba, 0x1a, 0x98, 0x2f, 0x59, 0x2c, 0xa5, 0x92, 0xae, 0x27, 0x62, 0x46, 0x7e, 0x42, 0xc4, + 0x1c, 0xae, 0x06, 0xfe, 0x61, 0x22, 0x13, 0x69, 0x20, 0xaa, 0x2b, 0xcb, 0xfb, 0x6d, 0xdd, 0x94, + 0xc9, 0x25, 0xa7, 0x96, 0xd7, 0xed, 0x6c, 0x65, 0x81, 0xee, 0x33, 0xc2, 0x8d, 0xb1, 0x09, 0x6e, + 0x55, 0xa4, 0xb8, 0xeb, 0xe2, 0xda, 0x2c, 0x52, 0x91, 0x87, 0x3a, 0xa8, 0xd7, 0x9c, 0x98, 0xda, + 0xf5, 0xf1, 0x7f, 0x96, 0x72, 0x76, 0x0f, 0x45, 0xee, 0x55, 0x4c, 0xfe, 0xf5, 0xef, 0x5e, 0xe1, + 0x56, 0x16, 0x29, 0x0e, 0xea, 0x2e, 0xe5, 0x5a, 0xcb, 0xab, 0x76, 0x50, 0xaf, 0x71, 0xe6, 0x13, + 0x2d, 0xaa, 0x07, 0x93, 0x72, 0xdc, 0x6a, 0x40, 0xae, 0x0d, 0x31, 0xaa, 0xad, 0xdf, 0xdb, 0xce, + 0xa4, 0x69, 0xaf, 0xd9, 0x6c, 0x58, 0x7b, 0x7a, 0x6d, 0x3b, 0xdd, 0x53, 0x7c, 0x30, 0x96, 0x73, + 0xe0, 0x73, 0x28, 0xe0, 0x57, 0x9d, 0x92, 0x3d, 0xc1, 0x2d, 0xeb, 0x7d, 0xc3, 0x01, 0xa2, 0xe4, + 0x2f, 0x34, 0xc4, 0xf5, 0x71, 0xe9, 0x0b, 0xee, 0x11, 0xae, 0xef, 0xe5, 0xc1, 0x43, 0x9d, 0x6a, + 0xaf, 0x39, 0xf9, 0x0e, 0x86, 0x15, 0x0f, 0x8d, 0xa6, 0xeb, 0x6d, 0x80, 0x36, 0xdb, 0x00, 0x7d, + 0x6c, 0x03, 0xf4, 0xb2, 0x0b, 0x9c, 0xcd, 0x2e, 0x70, 0xde, 0x76, 0x81, 0x33, 0xbd, 0x4c, 0x84, + 0x4a, 0x8b, 0x98, 0x30, 0x99, 0x53, 0x26, 0x21, 0x97, 0x40, 0x45, 0xcc, 0xc2, 0x44, 0xd2, 0x5c, + 0xce, 0x8a, 0x8c, 0x83, 0xdd, 0x5f, 0xb8, 0x5f, 0x60, 0xff, 0x22, 0x2c, 0x77, 0xd8, 0xa7, 0xea, + 0x71, 0xc1, 0x21, 0xfe, 0x67, 0x5e, 0xfd, 0xfc, 0x33, 0x00, 0x00, 0xff, 0xff, 0xdb, 0x8b, 0xe8, + 0xa1, 0xed, 0x01, 0x00, 0x00, +} + +func (m *ClientState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ClientState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ClientState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.LatestHeight.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintWasm(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.Checksum) > 0 { + i -= len(m.Checksum) + copy(dAtA[i:], m.Checksum) + i = encodeVarintWasm(dAtA, i, uint64(len(m.Checksum))) + i-- + dAtA[i] = 0x12 + } + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintWasm(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ConsensusState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ConsensusState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ConsensusState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintWasm(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *ClientMessage) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ClientMessage) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ClientMessage) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintWasm(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Checksums) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Checksums) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Checksums) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Checksums) > 0 { + for iNdEx := len(m.Checksums) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Checksums[iNdEx]) + copy(dAtA[i:], m.Checksums[iNdEx]) + i = encodeVarintWasm(dAtA, i, uint64(len(m.Checksums[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintWasm(dAtA []byte, offset int, v uint64) int { + offset -= sovWasm(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *ClientState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Data) + if l > 0 { + n += 1 + l + sovWasm(uint64(l)) + } + l = len(m.Checksum) + if l > 0 { + n += 1 + l + sovWasm(uint64(l)) + } + l = m.LatestHeight.Size() + n += 1 + l + sovWasm(uint64(l)) + return n +} + +func (m *ConsensusState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Data) + if l > 0 { + n += 1 + l + sovWasm(uint64(l)) + } + return n +} + +func (m *ClientMessage) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Data) + if l > 0 { + n += 1 + l + sovWasm(uint64(l)) + } + return n +} + +func (m *Checksums) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Checksums) > 0 { + for _, b := range m.Checksums { + l = len(b) + n += 1 + l + sovWasm(uint64(l)) + } + } + return n +} + +func sovWasm(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozWasm(x uint64) (n int) { + return sovWasm(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *ClientState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWasm + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ClientState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ClientState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWasm + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthWasm + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthWasm + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Checksum", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWasm + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthWasm + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthWasm + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Checksum = append(m.Checksum[:0], dAtA[iNdEx:postIndex]...) + if m.Checksum == nil { + m.Checksum = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LatestHeight", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWasm + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthWasm + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthWasm + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.LatestHeight.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipWasm(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthWasm + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ConsensusState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWasm + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ConsensusState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ConsensusState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWasm + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthWasm + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthWasm + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipWasm(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthWasm + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ClientMessage) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWasm + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ClientMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ClientMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWasm + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthWasm + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthWasm + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipWasm(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthWasm + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Checksums) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWasm + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Checksums: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Checksums: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Checksums", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowWasm + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthWasm + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthWasm + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Checksums = append(m.Checksums, make([]byte, postIndex-iNdEx)) + copy(m.Checksums[len(m.Checksums)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipWasm(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthWasm + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipWasm(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowWasm + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowWasm + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowWasm + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthWasm + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupWasm + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthWasm + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthWasm = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowWasm = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupWasm = fmt.Errorf("proto: unexpected end of group") +) diff --git a/modules/light-clients/08-wasm/types/wasm_vm.go b/modules/light-clients/08-wasm/types/wasm_vm.go new file mode 100644 index 0000000..de9c63f --- /dev/null +++ b/modules/light-clients/08-wasm/types/wasm_vm.go @@ -0,0 +1,7 @@ +//go:build cgo && !nolink_libwasmvm + +package types + +import wasmvm "github.com/CosmWasm/wasmvm/v2" + +var _ WasmEngine = (*wasmvm.VM)(nil) diff --git a/modules/light-clients/08-wasm/wasm_test.go b/modules/light-clients/08-wasm/wasm_test.go new file mode 100644 index 0000000..8f14da2 --- /dev/null +++ b/modules/light-clients/08-wasm/wasm_test.go @@ -0,0 +1,118 @@ +package wasm_test + +import ( + "encoding/json" + "errors" + "testing" + + wasmvm "github.com/CosmWasm/wasmvm/v2" + wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" + dbm "github.com/cosmos/cosmos-db" + testifysuite "github.com/stretchr/testify/suite" + + "cosmossdk.io/log" + storetypes "cosmossdk.io/store/types" + + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/testing/simapp" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +type WasmTestSuite struct { + testifysuite.Suite + coordinator *ibctesting.Coordinator + chainA *ibctesting.TestChain + mockVM *wasmtesting.MockWasmEngine + + checksum types.Checksum +} + +func TestWasmTestSuite(t *testing.T) { + testifysuite.Run(t, new(WasmTestSuite)) +} + +func (suite *WasmTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCustomAppCoordinator(suite.T(), 1, setupTestingApp) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) +} + +// GetSimApp returns the duplicated SimApp from within the 08-wasm directory. +// This must be used instead of chain.GetSimApp() for tests within this directory. +func GetSimApp(chain *ibctesting.TestChain) *simapp.SimApp { + app, ok := chain.App.(*simapp.SimApp) + if !ok { + panic(errors.New("chain is not a simapp.SimApp")) + } + return app +} + +// setupTestingApp provides the duplicated simapp which is specific to the 08-wasm module on chain creation. +func setupTestingApp() (ibctesting.TestingApp, map[string]json.RawMessage) { + db := dbm.NewMemDB() + app := simapp.NewUnitTestSimApp(log.NewNopLogger(), db, nil, true, simtestutil.EmptyAppOptions{}, nil) + return app, app.DefaultGenesis() +} + +// SetupWasmWithMockVM sets up mock cometbft chain with a mock vm. +func (suite *WasmTestSuite) SetupWasmWithMockVM() { + suite.coordinator = ibctesting.NewCustomAppCoordinator(suite.T(), 1, suite.setupWasmWithMockVM) + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) + suite.checksum = storeWasmCode(suite, wasmtesting.Code) +} + +func (suite *WasmTestSuite) setupWasmWithMockVM() (ibctesting.TestingApp, map[string]json.RawMessage) { + suite.mockVM = wasmtesting.NewMockWasmEngine() + + suite.mockVM.InstantiateFn = func(checksum wasmvm.Checksum, env wasmvmtypes.Env, info wasmvmtypes.MessageInfo, initMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.ContractResult, uint64, error) { + var payload types.InstantiateMessage + err := json.Unmarshal(initMsg, &payload) + suite.Require().NoError(err) + + wrappedClientState, ok := clienttypes.MustUnmarshalClientState(suite.chainA.App.AppCodec(), payload.ClientState).(*ibctm.ClientState) + suite.Require().True(ok) + + clientState := types.NewClientState(payload.ClientState, payload.Checksum, wrappedClientState.LatestHeight) + clientStateBz := clienttypes.MustMarshalClientState(suite.chainA.App.AppCodec(), clientState) + store.Set(host.ClientStateKey(), clientStateBz) + + consensusState := types.NewConsensusState(payload.ConsensusState) + consensusStateBz := clienttypes.MustMarshalConsensusState(suite.chainA.App.AppCodec(), consensusState) + store.Set(host.ConsensusStateKey(clientState.LatestHeight), consensusStateBz) + + resp, err := json.Marshal(types.EmptyResult{}) + suite.Require().NoError(err) + + return &wasmvmtypes.ContractResult{Ok: &wasmvmtypes.Response{Data: resp}}, 0, nil + } + + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.QueryResult, uint64, error) { + resp, err := json.Marshal(types.StatusResult{Status: exported.Active.String()}) + suite.Require().NoError(err) + return &wasmvmtypes.QueryResult{Ok: resp}, wasmtesting.DefaultGasUsed, nil + }) + + db := dbm.NewMemDB() + app := simapp.NewUnitTestSimApp(log.NewNopLogger(), db, nil, true, simtestutil.EmptyAppOptions{}, suite.mockVM) + + return app, app.DefaultGenesis() +} + +// storeWasmCode stores the wasm code on chain and returns the checksum. +func storeWasmCode(suite *WasmTestSuite, wasmCode []byte) types.Checksum { + ctx := suite.chainA.GetContext().WithBlockGasMeter(storetypes.NewInfiniteGasMeter()) + + msg := types.NewMsgStoreCode(authtypes.NewModuleAddress(govtypes.ModuleName).String(), wasmCode) + response, err := GetSimApp(suite.chainA).WasmClientKeeper.StoreCode(ctx, msg) + suite.Require().NoError(err) + suite.Require().NotNil(response.Checksum) + return response.Checksum +} diff --git a/modules/light-clients/09-localhost/doc.go b/modules/light-clients/09-localhost/doc.go new file mode 100644 index 0000000..ba78a3a --- /dev/null +++ b/modules/light-clients/09-localhost/doc.go @@ -0,0 +1,10 @@ +/* +Package solomachine implements a concrete LightClientModule, ClientState, ConsensusState, +Header and Misbehaviour types for the Localhost light client. +This implementation is based off the ICS 09 specification +(https://github.com/cosmos/ibc/blob/main/spec/client/ics-009-loopback-cilent) + +Note the client identifier is expected to be: 09-localhost. +This is validated by core IBC in the 02-client submodule. +*/ +package localhost diff --git a/modules/light-clients/09-localhost/light_client_module.go b/modules/light-clients/09-localhost/light_client_module.go new file mode 100644 index 0000000..574f62f --- /dev/null +++ b/modules/light-clients/09-localhost/light_client_module.go @@ -0,0 +1,181 @@ +package localhost + +import ( + "bytes" + + corestore "cosmossdk.io/core/store" + errorsmod "cosmossdk.io/errors" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + commitmenttypesv2 "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types/v2" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + ibcerrors "github.com/cosmos/ibc-go/v10/modules/core/errors" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +const ( + // ModuleName defines the 09-localhost light client module name + ModuleName = "09-localhost" +) + +// SentinelProof defines the 09-localhost sentinel proof. +// Submission of nil or empty proofs is disallowed in core IBC messaging. +// This serves as a placeholder value for relayers to leverage as the proof field in various message types. +// Localhost client state verification will fail if the sentintel proof value is not provided. +var SentinelProof = []byte{0x01} + +var _ exported.LightClientModule = (*LightClientModule)(nil) + +// LightClientModule implements the core IBC api.LightClientModule interface. +type LightClientModule struct { + cdc codec.BinaryCodec + storeService corestore.KVStoreService +} + +// NewLightClientModule creates and returns a new 09-localhost LightClientModule. +func NewLightClientModule(cdc codec.BinaryCodec, storeService corestore.KVStoreService) *LightClientModule { + return &LightClientModule{ + cdc: cdc, + storeService: storeService, + } +} + +// Initialize returns an error because it is stateless. +func (LightClientModule) Initialize(_ sdk.Context, _ string, _, _ []byte) error { + return errorsmod.Wrap(clienttypes.ErrClientExists, "localhost is stateless and cannot be initialized") +} + +// VerifyClientMessage is unsupported by the 09-localhost client type and returns an error. +func (LightClientModule) VerifyClientMessage(_ sdk.Context, _ string, _ exported.ClientMessage) error { + return errorsmod.Wrap(clienttypes.ErrUpdateClientFailed, "client message verification is unsupported by the localhost client") +} + +// CheckForMisbehaviour is unsupported by the 09-localhost client type and performs a no-op, returning false. +func (LightClientModule) CheckForMisbehaviour(_ sdk.Context, _ string, _ exported.ClientMessage) bool { + return false +} + +// UpdateStateOnMisbehaviour is unsupported by the 09-localhost client type and performs a no-op. +func (LightClientModule) UpdateStateOnMisbehaviour(_ sdk.Context, _ string, _ exported.ClientMessage) { + // no-op +} + +// UpdateState performs a no-op and returns the context height in the updated heights return value. +func (LightClientModule) UpdateState(ctx sdk.Context, _ string, _ exported.ClientMessage) []exported.Height { + return []exported.Height{clienttypes.GetSelfHeight(ctx)} +} + +// VerifyMembership is a generic proof verification method which verifies the existence of a given key and value within the IBC store. +// The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). +// The caller must provide the full IBC store. +func (l LightClientModule) VerifyMembership( + ctx sdk.Context, + clientID string, + height exported.Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path exported.Path, + value []byte, +) error { + ibcStore := l.storeService.OpenKVStore(ctx) + + // ensure the proof provided is the expected sentinel localhost client proof + if !bytes.Equal(proof, SentinelProof) { + return errorsmod.Wrapf(commitmenttypes.ErrInvalidProof, "expected %s, got %s", string(SentinelProof), string(proof)) + } + + merklePath, ok := path.(commitmenttypesv2.MerklePath) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", commitmenttypesv2.MerklePath{}, path) + } + + if len(merklePath.GetKeyPath()) != 2 { + return errorsmod.Wrapf(host.ErrInvalidPath, "path must be of length 2: %s", merklePath.GetKeyPath()) + } + + // The commitment prefix (eg: "ibc") is omitted when operating on the core IBC store + bz, err := ibcStore.Get(merklePath.KeyPath[1]) + if err != nil { + panic(err) + } + if bz == nil { + return errorsmod.Wrapf(clienttypes.ErrFailedMembershipVerification, "value not found for path %s", path) + } + + if !bytes.Equal(bz, value) { + return errorsmod.Wrapf(clienttypes.ErrFailedMembershipVerification, "value provided does not equal value stored at path: %s", path) + } + + return nil +} + +// VerifyNonMembership is a generic proof verification method which verifies the absence of a given CommitmentPath within the IBC store. +// The caller is expected to construct the full CommitmentPath from a CommitmentPrefix and a standardized path (as defined in ICS 24). +// The caller must provide the full IBC store. +func (l LightClientModule) VerifyNonMembership( + ctx sdk.Context, + clientID string, + height exported.Height, + delayTimePeriod uint64, + delayBlockPeriod uint64, + proof []byte, + path exported.Path, +) error { + ibcStore := l.storeService.OpenKVStore(ctx) + + // ensure the proof provided is the expected sentinel localhost client proof + if !bytes.Equal(proof, SentinelProof) { + return errorsmod.Wrapf(commitmenttypes.ErrInvalidProof, "expected %s, got %s", string(SentinelProof), string(proof)) + } + + merklePath, ok := path.(commitmenttypesv2.MerklePath) + if !ok { + return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected %T, got %T", commitmenttypesv2.MerklePath{}, path) + } + + if len(merklePath.GetKeyPath()) != 2 { + return errorsmod.Wrapf(host.ErrInvalidPath, "path must be of length 2: %s", merklePath.GetKeyPath()) + } + + // The commitment prefix (eg: "ibc") is omitted when operating on the core IBC store + has, err := ibcStore.Has(merklePath.KeyPath[1]) + if err != nil { + return errorsmod.Wrapf(err, "error checking for value for path %s", path) + } + if has { + return errorsmod.Wrapf(clienttypes.ErrFailedNonMembershipVerification, "value found for path %s", path) + } + + return nil +} + +// Status always returns Active. The 09-localhost status cannot be changed. +func (LightClientModule) Status(_ sdk.Context, _ string) exported.Status { + return exported.Active +} + +// LatestHeight returns the context height. +func (LightClientModule) LatestHeight(ctx sdk.Context, _ string) exported.Height { + return clienttypes.GetSelfHeight(ctx) +} + +// TimestampAtHeight returns the current block time retrieved from the application context. The localhost client does not store consensus states and thus +// cannot provide a timestamp for the provided height. +func (LightClientModule) TimestampAtHeight(ctx sdk.Context, _ string, _ exported.Height) (uint64, error) { + return uint64(ctx.BlockTime().UnixNano()), nil +} + +// RecoverClient returns an error. The localhost cannot be modified by proposals. +func (LightClientModule) RecoverClient(_ sdk.Context, _, _ string) error { + return errorsmod.Wrap(clienttypes.ErrUpdateClientFailed, "cannot update localhost client with a proposal") +} + +// VerifyUpgradeAndUpdateState returns an error since localhost cannot be upgraded. +func (LightClientModule) VerifyUpgradeAndUpdateState(_ sdk.Context, _ string, _, _, _, _ []byte) error { + return errorsmod.Wrap(clienttypes.ErrInvalidUpgradeClient, "cannot upgrade localhost client") +} diff --git a/modules/light-clients/09-localhost/light_client_module_test.go b/modules/light-clients/09-localhost/light_client_module_test.go new file mode 100644 index 0000000..3446241 --- /dev/null +++ b/modules/light-clients/09-localhost/light_client_module_test.go @@ -0,0 +1,369 @@ +package localhost_test + +import ( + "errors" + "testing" + + testifysuite "github.com/stretchr/testify/suite" + + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + localhost "github.com/cosmos/ibc-go/v10/modules/light-clients/09-localhost" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + "github.com/cosmos/ibc-go/v10/testing/mock" +) + +type LocalhostTestSuite struct { + testifysuite.Suite + + coordinator ibctesting.Coordinator + chain *ibctesting.TestChain +} + +func (suite *LocalhostTestSuite) SetupTest() { + suite.coordinator = *ibctesting.NewCoordinator(suite.T(), 1) + suite.chain = suite.coordinator.GetChain(ibctesting.GetChainID(1)) +} + +func TestLocalhostTestSuite(t *testing.T) { + testifysuite.Run(t, new(LocalhostTestSuite)) +} + +func (suite *LocalhostTestSuite) TestInitialize() { + lightClientModule, err := suite.chain.App.GetIBCKeeper().ClientKeeper.Route(suite.chain.GetContext(), exported.LocalhostClientID) + suite.Require().NoError(err) + + err = lightClientModule.Initialize(suite.chain.GetContext(), exported.LocalhostClientID, nil, nil) + suite.Require().Error(err) +} + +func (suite *LocalhostTestSuite) TestVerifyClientMessage() { + lightClientModule, err := suite.chain.App.GetIBCKeeper().ClientKeeper.Route(suite.chain.GetContext(), exported.LocalhostClientID) + suite.Require().NoError(err) + + err = lightClientModule.VerifyClientMessage(suite.chain.GetContext(), exported.LocalhostClientID, nil) + suite.Require().Error(err) +} + +func (suite *LocalhostTestSuite) TestVerifyCheckForMisbehaviour() { + lightClientModule, err := suite.chain.App.GetIBCKeeper().ClientKeeper.Route(suite.chain.GetContext(), exported.LocalhostClientID) + suite.Require().NoError(err) + + suite.Require().False(lightClientModule.CheckForMisbehaviour(suite.chain.GetContext(), exported.LocalhostClientID, nil)) +} + +func (suite *LocalhostTestSuite) TestUpdateState() { + lightClientModule, err := suite.chain.App.GetIBCKeeper().ClientKeeper.Route(suite.chain.GetContext(), exported.LocalhostClientID) + suite.Require().NoError(err) + + heights := lightClientModule.UpdateState(suite.chain.GetContext(), exported.LocalhostClientID, nil) + + expHeight := clienttypes.NewHeight(1, uint64(suite.chain.GetContext().BlockHeight())) + suite.Require().True(heights[0].EQ(expHeight)) +} + +func (suite *LocalhostTestSuite) TestVerifyMembership() { + var ( + path exported.Path + value []byte + ) + + testCases := []struct { + name string + malleate func() + expErr error + }{ + { + "success: connection state verification", + func() { + connectionEnd := connectiontypes.NewConnectionEnd( + connectiontypes.OPEN, + exported.LocalhostClientID, + connectiontypes.NewCounterparty(exported.LocalhostClientID, exported.LocalhostConnectionID, suite.chain.GetPrefix()), + connectiontypes.GetCompatibleVersions(), 0, + ) + + suite.chain.GetSimApp().GetIBCKeeper().ConnectionKeeper.SetConnection(suite.chain.GetContext(), exported.LocalhostConnectionID, connectionEnd) + + merklePath := commitmenttypes.NewMerklePath(host.ConnectionKey(exported.LocalhostConnectionID)) + merklePath, err := commitmenttypes.ApplyPrefix(suite.chain.GetPrefix(), merklePath) + suite.Require().NoError(err) + + path = merklePath + value = suite.chain.Codec.MustMarshal(&connectionEnd) + }, + nil, + }, + { + "success: channel state verification", + func() { + channel := channeltypes.NewChannel( + channeltypes.OPEN, + channeltypes.UNORDERED, + channeltypes.NewCounterparty(mock.PortID, ibctesting.FirstChannelID), + []string{exported.LocalhostConnectionID}, + mock.Version, + ) + + suite.chain.GetSimApp().GetIBCKeeper().ChannelKeeper.SetChannel(suite.chain.GetContext(), mock.PortID, ibctesting.FirstChannelID, channel) + + merklePath := commitmenttypes.NewMerklePath(host.ChannelKey(mock.PortID, ibctesting.FirstChannelID)) + merklePath, err := commitmenttypes.ApplyPrefix(suite.chain.GetPrefix(), merklePath) + suite.Require().NoError(err) + + path = merklePath + value = suite.chain.Codec.MustMarshal(&channel) + }, + nil, + }, + { + "success: next sequence recv verification", + func() { + nextSeqRecv := uint64(100) + suite.chain.GetSimApp().GetIBCKeeper().ChannelKeeper.SetNextSequenceRecv(suite.chain.GetContext(), mock.PortID, ibctesting.FirstChannelID, nextSeqRecv) + + merklePath := commitmenttypes.NewMerklePath(host.NextSequenceRecvKey(mock.PortID, ibctesting.FirstChannelID)) + merklePath, err := commitmenttypes.ApplyPrefix(suite.chain.GetPrefix(), merklePath) + suite.Require().NoError(err) + + path = merklePath + value = sdk.Uint64ToBigEndian(nextSeqRecv) + }, + nil, + }, + { + "success: packet commitment verification", + func() { + packet := channeltypes.NewPacket( + ibctesting.MockPacketData, + 1, + ibctesting.MockPort, + ibctesting.FirstChannelID, + ibctesting.MockPort, + ibctesting.FirstChannelID, + clienttypes.NewHeight(0, 10), + 0, + ) + + commitmentBz := channeltypes.CommitPacket(packet) + suite.chain.GetSimApp().GetIBCKeeper().ChannelKeeper.SetPacketCommitment(suite.chain.GetContext(), mock.PortID, ibctesting.FirstChannelID, 1, commitmentBz) + + merklePath := commitmenttypes.NewMerklePath(host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence())) + merklePath, err := commitmenttypes.ApplyPrefix(suite.chain.GetPrefix(), merklePath) + suite.Require().NoError(err) + + path = merklePath + value = commitmentBz + }, + nil, + }, + { + "success: packet acknowledgement verification", + func() { + suite.chain.GetSimApp().GetIBCKeeper().ChannelKeeper.SetPacketAcknowledgement(suite.chain.GetContext(), mock.PortID, ibctesting.FirstChannelID, 1, ibctesting.MockAcknowledgement) + + merklePath := commitmenttypes.NewMerklePath(host.PacketAcknowledgementKey(mock.PortID, ibctesting.FirstChannelID, 1)) + merklePath, err := commitmenttypes.ApplyPrefix(suite.chain.GetPrefix(), merklePath) + suite.Require().NoError(err) + + path = merklePath + value = ibctesting.MockAcknowledgement + }, + nil, + }, + { + "failure: invalid type for key path", + func() { + path = mock.KeyPath{} + }, + errors.New("expected v2.MerklePath, got mock.KeyPath: invalid type"), + }, + { + "failure: key path has too many elements", + func() { + path = commitmenttypes.NewMerklePath([]byte("ibc"), []byte("test"), []byte("key")) + }, + errors.New("invalid path"), + }, + { + "failure: no value found at provided key path", + func() { + merklePath := commitmenttypes.NewMerklePath(host.PacketAcknowledgementKey(mock.PortID, ibctesting.FirstChannelID, 100)) + merklePath, err := commitmenttypes.ApplyPrefix(suite.chain.GetPrefix(), merklePath) + suite.Require().NoError(err) + + path = merklePath + value = ibctesting.MockAcknowledgement + }, + errors.New("value not found for path"), + }, + { + "failure: invalid value, bytes are not equal", + func() { + channel := channeltypes.NewChannel( + channeltypes.OPEN, + channeltypes.UNORDERED, + channeltypes.NewCounterparty(mock.PortID, ibctesting.FirstChannelID), + []string{exported.LocalhostConnectionID}, + mock.Version, + ) + + suite.chain.GetSimApp().GetIBCKeeper().ChannelKeeper.SetChannel(suite.chain.GetContext(), mock.PortID, ibctesting.FirstChannelID, channel) + + merklePath := commitmenttypes.NewMerklePath(host.ChannelKey(mock.PortID, ibctesting.FirstChannelID)) + merklePath, err := commitmenttypes.ApplyPrefix(suite.chain.GetPrefix(), merklePath) + suite.Require().NoError(err) + + path = merklePath + + // modify the channel before marshalling to value bz + channel.State = channeltypes.CLOSED + value = suite.chain.Codec.MustMarshal(&channel) + }, + errors.New("value provided does not equal value stored at path"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + tc.malleate() + + lightClientModule, err := suite.chain.App.GetIBCKeeper().ClientKeeper.Route(suite.chain.GetContext(), exported.LocalhostClientID) + suite.Require().NoError(err) + + err = lightClientModule.VerifyMembership( + suite.chain.GetContext(), + exported.LocalhostClientID, + clienttypes.ZeroHeight(), + 0, 0, // use zero values for delay periods + localhost.SentinelProof, + path, + value, + ) + + if tc.expErr == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.ErrorContains(err, tc.expErr.Error()) + } + }) + } +} + +func (suite *LocalhostTestSuite) TestVerifyNonMembership() { + var path exported.Path + + testCases := []struct { + name string + malleate func() + expError error + }{ + { + "success: packet receipt absence verification", + func() { + merklePath := commitmenttypes.NewMerklePath(host.PacketReceiptKey(mock.PortID, ibctesting.FirstChannelID, 1)) + merklePath, err := commitmenttypes.ApplyPrefix(suite.chain.GetPrefix(), merklePath) + suite.Require().NoError(err) + + path = merklePath + }, + nil, + }, + { + "packet receipt absence verification fails", + func() { + suite.chain.GetSimApp().GetIBCKeeper().ChannelKeeper.SetPacketReceipt(suite.chain.GetContext(), mock.PortID, ibctesting.FirstChannelID, 1) + + merklePath := commitmenttypes.NewMerklePath(host.PacketReceiptKey(mock.PortID, ibctesting.FirstChannelID, 1)) + merklePath, err := commitmenttypes.ApplyPrefix(suite.chain.GetPrefix(), merklePath) + suite.Require().NoError(err) + + path = merklePath + }, + errors.New("non-membership verification failed"), + }, + { + "invalid type for key path", + func() { + path = mock.KeyPath{} + }, + errors.New("expected v2.MerklePath, got mock.KeyPath: invalid type"), + }, + { + "key path has too many elements", + func() { + path = commitmenttypes.NewMerklePath([]byte("ibc"), []byte("test"), []byte("key")) + }, + errors.New("invalid path"), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + tc.malleate() + + lightClientModule, err := suite.chain.App.GetIBCKeeper().ClientKeeper.Route(suite.chain.GetContext(), exported.LocalhostClientID) + suite.Require().NoError(err) + + err = lightClientModule.VerifyNonMembership( + suite.chain.GetContext(), + exported.LocalhostClientID, + clienttypes.ZeroHeight(), + 0, 0, // use zero values for delay periods + localhost.SentinelProof, + path, + ) + + if tc.expError == nil { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.ErrorContains(err, tc.expError.Error()) + } + }) + } +} + +func (suite *LocalhostTestSuite) TestStatus() { + lightClientModule, err := suite.chain.App.GetIBCKeeper().ClientKeeper.Route(suite.chain.GetContext(), exported.LocalhostClientID) + suite.Require().NoError(err) + + suite.Require().Equal(exported.Active, lightClientModule.Status(suite.chain.GetContext(), exported.LocalhostClientID)) +} + +func (suite *LocalhostTestSuite) TestGetTimestampAtHeight() { + lightClientModule, err := suite.chain.App.GetIBCKeeper().ClientKeeper.Route(suite.chain.GetContext(), exported.LocalhostClientID) + suite.Require().NoError(err) + + ctx := suite.chain.GetContext() + timestamp, err := lightClientModule.TimestampAtHeight(ctx, exported.LocalhostClientID, nil) + suite.Require().NoError(err) + suite.Require().Equal(uint64(ctx.BlockTime().UnixNano()), timestamp) +} + +func (suite *LocalhostTestSuite) TestRecoverClient() { + lightClientModule, err := suite.chain.App.GetIBCKeeper().ClientKeeper.Route(suite.chain.GetContext(), exported.LocalhostClientID) + suite.Require().NoError(err) + + err = lightClientModule.RecoverClient(suite.chain.GetContext(), exported.LocalhostClientID, exported.LocalhostClientID) + suite.Require().Error(err) +} + +func (suite *LocalhostTestSuite) TestVerifyUpgradeAndUpdateState() { + lightClientModule, err := suite.chain.App.GetIBCKeeper().ClientKeeper.Route(suite.chain.GetContext(), exported.LocalhostClientID) + suite.Require().NoError(err) + + err = lightClientModule.VerifyUpgradeAndUpdateState(suite.chain.GetContext(), exported.LocalhostClientID, nil, nil, nil, nil) + suite.Require().Error(err) +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..61ba195 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "ibc-go", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/proto/buf.gen.gogo.yaml b/proto/buf.gen.gogo.yaml new file mode 100644 index 0000000..6e5cefa --- /dev/null +++ b/proto/buf.gen.gogo.yaml @@ -0,0 +1,8 @@ +version: v1 +plugins: + - name: gocosmos + out: .. + opt: plugins=grpc,Mgoogle/protobuf/any.proto=github.com/cosmos/cosmos-sdk/codec/types + - name: grpc-gateway + out: .. + opt: logtostderr=true,allow_colon_final_segments=true diff --git a/proto/buf.gen.swagger.yaml b/proto/buf.gen.swagger.yaml new file mode 100644 index 0000000..70a0f2b --- /dev/null +++ b/proto/buf.gen.swagger.yaml @@ -0,0 +1,5 @@ +version: v1 +plugins: + - name: swagger + out: ../tmp-swagger-gen + opt: logtostderr=true,fqn_for_swagger_name=true,simple_operation_ids=true diff --git a/proto/buf.lock b/proto/buf.lock new file mode 100644 index 0000000..74baa8f --- /dev/null +++ b/proto/buf.lock @@ -0,0 +1,28 @@ +# Generated by buf. DO NOT EDIT. +version: v1 +deps: + - remote: buf.build + owner: cosmos + repository: cosmos-proto + commit: 1935555c206d4afb9e94615dfd0fad31 + digest: shake256:c74d91a3ac7ae07d579e90eee33abf9b29664047ac8816500cf22c081fec0d72d62c89ce0bebafc1f6fec7aa5315be72606717740ca95007248425102c365377 + - remote: buf.build + owner: cosmos + repository: cosmos-sdk + commit: aa25660f4ff746388669ce36b3778442 + digest: shake256:a20eb29eb7284d9d0b76e94324a6e24e3665d13682bed0d5beac647d7109b7b2f22080301276779a91f394c97dab334da36dfc01d4252d9f869b090bfc8248aa + - remote: buf.build + owner: cosmos + repository: gogo-proto + commit: 34d970b699f84aa382f3c29773a60836 + digest: shake256:3d3bee5229ba579e7d19ffe6e140986a228b48a8c7fe74348f308537ab95e9135210e81812489d42cd8941d33ff71f11583174ccc5972e86e6112924b6ce9f04 + - remote: buf.build + owner: cosmos + repository: ics23 + commit: 55085f7c710a45f58fa09947208eb70b + digest: shake256:9bf0bc495b5a11c88d163d39ef521bc4b00bc1374a05758c91d82821bdc61f09e8c2c51dda8452529bf80137f34d852561eacbe9550a59015d51cecb0dacb628 + - remote: buf.build + owner: googleapis + repository: googleapis + commit: 8d7204855ec14631a499bd7393ce1970 + digest: shake256:40bf4112960cad01281930beed85829910768e32e80e986791596853eccd42c0cbd9d96690b918f658020d2d427e16f8b6514e2ac7f4a10306fd32e77be44329 diff --git a/proto/buf.yaml b/proto/buf.yaml new file mode 100644 index 0000000..dd29c6e --- /dev/null +++ b/proto/buf.yaml @@ -0,0 +1,23 @@ +version: v1 +name: buf.build/cosmos/ibc +deps: + - buf.build/cosmos/cosmos-sdk:aa25660f4ff746388669ce36b3778442 + - buf.build/cosmos/cosmos-proto:1935555c206d4afb9e94615dfd0fad31 + - buf.build/cosmos/gogo-proto:a14993478f40695898ed8a86931094b6656e8a5d + - buf.build/googleapis/googleapis:8d7204855ec14631a499bd7393ce1970 + - buf.build/cosmos/ics23:b1abd8678aab07165efd453c96796a179eb3131f +breaking: + use: + - FILE +lint: + use: + - DEFAULT + - COMMENTS + - FILE_LOWER_SNAKE_CASE + except: + - UNARY_RPC + - COMMENT_FIELD + - SERVICE_SUFFIX + - PACKAGE_VERSION_SUFFIX + - RPC_REQUEST_STANDARD_NAME + - RPC_RESPONSE_STANDARD_NAME diff --git a/proto/ibc/applications/interchain_accounts/controller/v1/controller.proto b/proto/ibc/applications/interchain_accounts/controller/v1/controller.proto new file mode 100644 index 0000000..b64e517 --- /dev/null +++ b/proto/ibc/applications/interchain_accounts/controller/v1/controller.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package ibc.applications.interchain_accounts.controller.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types"; + +// Params defines the set of on-chain interchain accounts parameters. +// The following parameters may be used to disable the controller submodule. +message Params { + // controller_enabled enables or disables the controller submodule. + bool controller_enabled = 1; +} diff --git a/proto/ibc/applications/interchain_accounts/controller/v1/query.proto b/proto/ibc/applications/interchain_accounts/controller/v1/query.proto new file mode 100644 index 0000000..45dc1d4 --- /dev/null +++ b/proto/ibc/applications/interchain_accounts/controller/v1/query.proto @@ -0,0 +1,42 @@ +syntax = "proto3"; + +package ibc.applications.interchain_accounts.controller.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types"; + +import "ibc/applications/interchain_accounts/controller/v1/controller.proto"; +import "google/api/annotations.proto"; + +// Query provides defines the gRPC querier service. +service Query { + // InterchainAccount returns the interchain account address for a given owner address on a given connection + rpc InterchainAccount(QueryInterchainAccountRequest) returns (QueryInterchainAccountResponse) { + option (google.api.http).get = + "/ibc/apps/interchain_accounts/controller/v1/owners/{owner}/connections/{connection_id}"; + } + + // Params queries all parameters of the ICA controller submodule. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/ibc/apps/interchain_accounts/controller/v1/params"; + } +} + +// QueryInterchainAccountRequest is the request type for the Query/InterchainAccount RPC method. +message QueryInterchainAccountRequest { + string owner = 1; + string connection_id = 2; +} + +// QueryInterchainAccountResponse the response type for the Query/InterchainAccount RPC method. +message QueryInterchainAccountResponse { + string address = 1; +} + +// QueryParamsRequest is the request type for the Query/Params RPC method. +message QueryParamsRequest {} + +// QueryParamsResponse is the response type for the Query/Params RPC method. +message QueryParamsResponse { + // params defines the parameters of the module. + Params params = 1; +} diff --git a/proto/ibc/applications/interchain_accounts/controller/v1/tx.proto b/proto/ibc/applications/interchain_accounts/controller/v1/tx.proto new file mode 100644 index 0000000..c0c6c1e --- /dev/null +++ b/proto/ibc/applications/interchain_accounts/controller/v1/tx.proto @@ -0,0 +1,82 @@ +syntax = "proto3"; + +package ibc.applications.interchain_accounts.controller.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types"; + +import "gogoproto/gogo.proto"; +import "ibc/applications/interchain_accounts/v1/packet.proto"; +import "ibc/applications/interchain_accounts/controller/v1/controller.proto"; +import "cosmos/msg/v1/msg.proto"; +import "ibc/core/channel/v1/channel.proto"; + +// Msg defines the 27-interchain-accounts/controller Msg service. +service Msg { + option (cosmos.msg.v1.service) = true; + + // RegisterInterchainAccount defines a rpc handler for MsgRegisterInterchainAccount. + rpc RegisterInterchainAccount(MsgRegisterInterchainAccount) returns (MsgRegisterInterchainAccountResponse); + // SendTx defines a rpc handler for MsgSendTx. + rpc SendTx(MsgSendTx) returns (MsgSendTxResponse); + // UpdateParams defines a rpc handler for MsgUpdateParams. + rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); +} + +// MsgRegisterInterchainAccount defines the payload for Msg/RegisterAccount +message MsgRegisterInterchainAccount { + option (cosmos.msg.v1.signer) = "owner"; + + option (gogoproto.goproto_getters) = false; + + string owner = 1; + string connection_id = 2; + string version = 3; + ibc.core.channel.v1.Order ordering = 4; +} + +// MsgRegisterInterchainAccountResponse defines the response for Msg/RegisterAccount +message MsgRegisterInterchainAccountResponse { + option (gogoproto.goproto_getters) = false; + + string channel_id = 1; + string port_id = 2; +} + +// MsgSendTx defines the payload for Msg/SendTx +message MsgSendTx { + option (cosmos.msg.v1.signer) = "owner"; + + option (gogoproto.goproto_getters) = false; + + string owner = 1; + string connection_id = 2; + ibc.applications.interchain_accounts.v1.InterchainAccountPacketData packet_data = 3 [(gogoproto.nullable) = false]; + // Relative timeout timestamp provided will be added to the current block time during transaction execution. + // The timeout timestamp must be non-zero. + uint64 relative_timeout = 4; +} + +// MsgSendTxResponse defines the response for MsgSendTx +message MsgSendTxResponse { + option (gogoproto.goproto_getters) = false; + + uint64 sequence = 1; +} + +// MsgUpdateParams defines the payload for Msg/UpdateParams +message MsgUpdateParams { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + // signer address + string signer = 1; + + // params defines the 27-interchain-accounts/controller parameters to update. + // + // NOTE: All parameters must be supplied. + Params params = 2 [(gogoproto.nullable) = false]; +} + +// MsgUpdateParamsResponse defines the response for Msg/UpdateParams +message MsgUpdateParamsResponse {} diff --git a/proto/ibc/applications/interchain_accounts/genesis/v1/genesis.proto b/proto/ibc/applications/interchain_accounts/genesis/v1/genesis.proto new file mode 100644 index 0000000..2f1f4fd --- /dev/null +++ b/proto/ibc/applications/interchain_accounts/genesis/v1/genesis.proto @@ -0,0 +1,47 @@ +syntax = "proto3"; + +package ibc.applications.interchain_accounts.genesis.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/genesis/types"; + +import "gogoproto/gogo.proto"; +import "ibc/applications/interchain_accounts/controller/v1/controller.proto"; +import "ibc/applications/interchain_accounts/host/v1/host.proto"; + +// GenesisState defines the interchain accounts genesis state +message GenesisState { + ControllerGenesisState controller_genesis_state = 1 [(gogoproto.nullable) = false]; + HostGenesisState host_genesis_state = 2 [(gogoproto.nullable) = false]; +} + +// ControllerGenesisState defines the interchain accounts controller genesis state +message ControllerGenesisState { + repeated ActiveChannel active_channels = 1 [(gogoproto.nullable) = false]; + repeated RegisteredInterchainAccount interchain_accounts = 2 [(gogoproto.nullable) = false]; + repeated string ports = 3; + ibc.applications.interchain_accounts.controller.v1.Params params = 4 [(gogoproto.nullable) = false]; +} + +// HostGenesisState defines the interchain accounts host genesis state +message HostGenesisState { + repeated ActiveChannel active_channels = 1 [(gogoproto.nullable) = false]; + repeated RegisteredInterchainAccount interchain_accounts = 2 [(gogoproto.nullable) = false]; + string port = 3; + ibc.applications.interchain_accounts.host.v1.Params params = 4 [(gogoproto.nullable) = false]; +} + +// ActiveChannel contains a connection ID, port ID and associated active channel ID, as well as a boolean flag to +// indicate if the channel is middleware enabled +message ActiveChannel { + string connection_id = 1; + string port_id = 2; + string channel_id = 3; + bool is_middleware_enabled = 4; +} + +// RegisteredInterchainAccount contains a connection ID, port ID and associated interchain account address +message RegisteredInterchainAccount { + string connection_id = 1; + string port_id = 2; + string account_address = 3; +} diff --git a/proto/ibc/applications/interchain_accounts/host/v1/host.proto b/proto/ibc/applications/interchain_accounts/host/v1/host.proto new file mode 100644 index 0000000..8dfa8aa --- /dev/null +++ b/proto/ibc/applications/interchain_accounts/host/v1/host.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +package ibc.applications.interchain_accounts.host.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types"; + +// Params defines the set of on-chain interchain accounts parameters. +// The following parameters may be used to disable the host submodule. +message Params { + // host_enabled enables or disables the host submodule. + bool host_enabled = 1; + // allow_messages defines a list of sdk message typeURLs allowed to be executed on a host chain. + repeated string allow_messages = 2; +} + +// QueryRequest defines the parameters for a particular query request +// by an interchain account. +message QueryRequest { + // path defines the path of the query request as defined by ADR-021. + // https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-021-protobuf-query-encoding.md#custom-query-registration-and-routing + string path = 1; + // data defines the payload of the query request as defined by ADR-021. + // https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-021-protobuf-query-encoding.md#custom-query-registration-and-routing + bytes data = 2; +} diff --git a/proto/ibc/applications/interchain_accounts/host/v1/query.proto b/proto/ibc/applications/interchain_accounts/host/v1/query.proto new file mode 100644 index 0000000..d76d407 --- /dev/null +++ b/proto/ibc/applications/interchain_accounts/host/v1/query.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +package ibc.applications.interchain_accounts.host.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types"; + +import "google/api/annotations.proto"; +import "ibc/applications/interchain_accounts/host/v1/host.proto"; + +// Query provides defines the gRPC querier service. +service Query { + // Params queries all parameters of the ICA host submodule. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/ibc/apps/interchain_accounts/host/v1/params"; + } +} + +// QueryParamsRequest is the request type for the Query/Params RPC method. +message QueryParamsRequest {} + +// QueryParamsResponse is the response type for the Query/Params RPC method. +message QueryParamsResponse { + // params defines the parameters of the module. + Params params = 1; +} diff --git a/proto/ibc/applications/interchain_accounts/host/v1/tx.proto b/proto/ibc/applications/interchain_accounts/host/v1/tx.proto new file mode 100644 index 0000000..479fa31 --- /dev/null +++ b/proto/ibc/applications/interchain_accounts/host/v1/tx.proto @@ -0,0 +1,60 @@ +syntax = "proto3"; + +package ibc.applications.interchain_accounts.host.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types"; + +import "gogoproto/gogo.proto"; +import "cosmos/msg/v1/msg.proto"; +import "ibc/applications/interchain_accounts/host/v1/host.proto"; + +// Msg defines the 27-interchain-accounts/host Msg service. +service Msg { + option (cosmos.msg.v1.service) = true; + + // UpdateParams defines a rpc handler for MsgUpdateParams. + rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); + + // ModuleQuerySafe defines a rpc handler for MsgModuleQuerySafe. + rpc ModuleQuerySafe(MsgModuleQuerySafe) returns (MsgModuleQuerySafeResponse); +} + +// MsgUpdateParams defines the payload for Msg/UpdateParams +message MsgUpdateParams { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + // signer address + string signer = 1; + + // params defines the 27-interchain-accounts/host parameters to update. + // + // NOTE: All parameters must be supplied. + Params params = 2 [(gogoproto.nullable) = false]; +} + +// MsgUpdateParamsResponse defines the response for Msg/UpdateParams +message MsgUpdateParamsResponse {} + +// MsgModuleQuerySafe defines the payload for Msg/ModuleQuerySafe +message MsgModuleQuerySafe { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + // signer address + string signer = 1; + + // requests defines the module safe queries to execute. + repeated QueryRequest requests = 2 [(gogoproto.nullable) = false]; +} + +// MsgModuleQuerySafeResponse defines the response for Msg/ModuleQuerySafe +message MsgModuleQuerySafeResponse { + // height at which the responses were queried + uint64 height = 1; + + // protobuf encoded responses for each query + repeated bytes responses = 2; +} diff --git a/proto/ibc/applications/interchain_accounts/v1/account.proto b/proto/ibc/applications/interchain_accounts/v1/account.proto new file mode 100644 index 0000000..d395e50 --- /dev/null +++ b/proto/ibc/applications/interchain_accounts/v1/account.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +package ibc.applications.interchain_accounts.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types"; + +import "cosmos_proto/cosmos.proto"; +import "gogoproto/gogo.proto"; +import "cosmos/auth/v1beta1/auth.proto"; + +// An InterchainAccount is defined as a BaseAccount & the address of the account owner on the controller chain +message InterchainAccount { + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + option (cosmos_proto.implements_interface) = "ibc.applications.interchain_accounts.v1.InterchainAccountI"; + + cosmos.auth.v1beta1.BaseAccount base_account = 1 [(gogoproto.embed) = true]; + string account_owner = 2; +} diff --git a/proto/ibc/applications/interchain_accounts/v1/metadata.proto b/proto/ibc/applications/interchain_accounts/v1/metadata.proto new file mode 100644 index 0000000..18fd18e --- /dev/null +++ b/proto/ibc/applications/interchain_accounts/v1/metadata.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package ibc.applications.interchain_accounts.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types"; + +// Metadata defines a set of protocol specific data encoded into the ICS27 channel version bytestring +// See ICS004: https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#Versioning +message Metadata { + // version defines the ICS27 protocol version + string version = 1; + // controller_connection_id is the connection identifier associated with the controller chain + string controller_connection_id = 2; + // host_connection_id is the connection identifier associated with the host chain + string host_connection_id = 3; + // address defines the interchain account address to be fulfilled upon the OnChanOpenTry handshake step + // NOTE: the address field is empty on the OnChanOpenInit handshake step + string address = 4; + // encoding defines the supported codec format + string encoding = 5; + // tx_type defines the type of transactions the interchain account can execute + string tx_type = 6; +} diff --git a/proto/ibc/applications/interchain_accounts/v1/packet.proto b/proto/ibc/applications/interchain_accounts/v1/packet.proto new file mode 100644 index 0000000..adc933d --- /dev/null +++ b/proto/ibc/applications/interchain_accounts/v1/packet.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +package ibc.applications.interchain_accounts.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types"; + +import "google/protobuf/any.proto"; +import "gogoproto/gogo.proto"; + +// Type defines a classification of message issued from a controller chain to its associated interchain accounts +// host +enum Type { + option (gogoproto.goproto_enum_prefix) = false; + + // Default zero value enumeration + TYPE_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "UNSPECIFIED"]; + // Execute a transaction on an interchain accounts host chain + TYPE_EXECUTE_TX = 1 [(gogoproto.enumvalue_customname) = "EXECUTE_TX"]; +} + +// InterchainAccountPacketData is comprised of a raw transaction, type of transaction and optional memo field. +message InterchainAccountPacketData { + Type type = 1; + bytes data = 2; + string memo = 3; +} + +// CosmosTx contains a list of sdk.Msg's. It should be used when sending transactions to an SDK host chain. +message CosmosTx { + repeated google.protobuf.Any messages = 1; +} diff --git a/proto/ibc/applications/transfer/v1/authz.proto b/proto/ibc/applications/transfer/v1/authz.proto new file mode 100644 index 0000000..9f47b72 --- /dev/null +++ b/proto/ibc/applications/transfer/v1/authz.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; + +package ibc.applications.transfer.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types"; + +import "cosmos_proto/cosmos.proto"; +import "gogoproto/gogo.proto"; +import "cosmos/base/v1beta1/coin.proto"; + +// Allocation defines the spend limit for a particular port and channel +message Allocation { + // the port on which the packet will be sent + string source_port = 1; + // the channel by which the packet will be sent + string source_channel = 2; + // spend limitation on the channel + repeated cosmos.base.v1beta1.Coin spend_limit = 3 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; + // allow list of receivers, an empty allow list permits any receiver address + repeated string allow_list = 4; + // allow list of memo strings, an empty list prohibits all memo strings; + // a list only with "*" permits any memo string + repeated string allowed_packet_data = 5; +} + +// TransferAuthorization allows the grantee to spend up to spend_limit coins from +// the granter's account for ibc transfer on a specific channel +message TransferAuthorization { + option (cosmos_proto.implements_interface) = "cosmos.authz.v1beta1.Authorization"; + + // port and channel amounts + repeated Allocation allocations = 1 [(gogoproto.nullable) = false]; +} diff --git a/proto/ibc/applications/transfer/v1/denomtrace.proto b/proto/ibc/applications/transfer/v1/denomtrace.proto new file mode 100644 index 0000000..d72396e --- /dev/null +++ b/proto/ibc/applications/transfer/v1/denomtrace.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package ibc.applications.transfer.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types"; + +// DenomTrace contains the base denomination for ICS20 fungible tokens and the +// source tracing information path. +message DenomTrace { + option deprecated = true; + // path defines the chain of port/channel identifiers used for tracing the + // source of the fungible token. + string path = 1; + // base denomination of the relayed fungible token. + string base_denom = 2; +} diff --git a/proto/ibc/applications/transfer/v1/genesis.proto b/proto/ibc/applications/transfer/v1/genesis.proto new file mode 100644 index 0000000..12aa528 --- /dev/null +++ b/proto/ibc/applications/transfer/v1/genesis.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package ibc.applications.transfer.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types"; + +import "ibc/applications/transfer/v1/transfer.proto"; +import "ibc/applications/transfer/v1/token.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "gogoproto/gogo.proto"; + +// GenesisState defines the ibc-transfer genesis state +message GenesisState { + string port_id = 1; + repeated Denom denoms = 2 [(gogoproto.castrepeated) = "Denoms", (gogoproto.nullable) = false]; + ibc.applications.transfer.v1.Params params = 3 [(gogoproto.nullable) = false]; + // total_escrowed contains the total amount of tokens escrowed + // by the transfer module + repeated cosmos.base.v1beta1.Coin total_escrowed = 4 + [(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", (gogoproto.nullable) = false]; +} diff --git a/proto/ibc/applications/transfer/v1/packet.proto b/proto/ibc/applications/transfer/v1/packet.proto new file mode 100644 index 0000000..08cee25 --- /dev/null +++ b/proto/ibc/applications/transfer/v1/packet.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package ibc.applications.transfer.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types"; + +// FungibleTokenPacketData defines a struct for the packet payload +// See FungibleTokenPacketData spec: +// https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#data-structures +message FungibleTokenPacketData { + // the token denomination to be transferred + string denom = 1; + // the token amount to be transferred + string amount = 2; + // the sender address + string sender = 3; + // the recipient address on the destination chain + string receiver = 4; + // optional memo + string memo = 5; +} diff --git a/proto/ibc/applications/transfer/v1/query.proto b/proto/ibc/applications/transfer/v1/query.proto new file mode 100644 index 0000000..08de401 --- /dev/null +++ b/proto/ibc/applications/transfer/v1/query.proto @@ -0,0 +1,122 @@ +syntax = "proto3"; + +package ibc.applications.transfer.v1; + +import "gogoproto/gogo.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "ibc/applications/transfer/v1/transfer.proto"; +import "ibc/applications/transfer/v1/token.proto"; +import "cosmos/base/query/v1beta1/pagination.proto"; +import "google/api/annotations.proto"; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types"; + +// Query provides defines the gRPC querier service. +service Query { + // Params queries all parameters of the ibc-transfer module. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/ibc/apps/transfer/v1/params"; + } + + // Denoms queries all denominations + rpc Denoms(QueryDenomsRequest) returns (QueryDenomsResponse) { + option (google.api.http).get = "/ibc/apps/transfer/v1/denoms"; + } + + // Denom queries a denomination + rpc Denom(QueryDenomRequest) returns (QueryDenomResponse) { + option (google.api.http).get = "/ibc/apps/transfer/v1/denoms/{hash=**}"; + } + + // DenomHash queries a denomination hash information. + rpc DenomHash(QueryDenomHashRequest) returns (QueryDenomHashResponse) { + option (google.api.http).get = "/ibc/apps/transfer/v1/denom_hashes/{trace=**}"; + } + + // EscrowAddress returns the escrow address for a particular port and channel id. + rpc EscrowAddress(QueryEscrowAddressRequest) returns (QueryEscrowAddressResponse) { + option (google.api.http).get = "/ibc/apps/transfer/v1/channels/{channel_id}/ports/{port_id}/escrow_address"; + } + + // TotalEscrowForDenom returns the total amount of tokens in escrow based on the denom. + rpc TotalEscrowForDenom(QueryTotalEscrowForDenomRequest) returns (QueryTotalEscrowForDenomResponse) { + option (google.api.http).get = "/ibc/apps/transfer/v1/total_escrow/{denom=**}"; + } +} + +// QueryParamsRequest is the request type for the Query/Params RPC method. +message QueryParamsRequest {} + +// QueryParamsResponse is the response type for the Query/Params RPC method. +message QueryParamsResponse { + // params defines the parameters of the module. + Params params = 1; +} + +// QueryDenomRequest is the request type for the Query/Denom RPC +// method +message QueryDenomRequest { + // hash (in hex format) or denom (full denom with ibc prefix) of the on chain denomination. + string hash = 1; +} + +// QueryDenomResponse is the response type for the Query/Denom RPC +// method. +message QueryDenomResponse { + // denom returns the requested denomination. + Denom denom = 1; +} + +// QueryDenomsRequest is the request type for the Query/Denoms RPC +// method +message QueryDenomsRequest { + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +// QueryDenomsResponse is the response type for the Query/Denoms RPC +// method. +message QueryDenomsResponse { + // denoms returns all denominations. + repeated Denom denoms = 1 [(gogoproto.castrepeated) = "Denoms", (gogoproto.nullable) = false]; + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryDenomHashRequest is the request type for the Query/DenomHash RPC +// method +message QueryDenomHashRequest { + // The denomination trace ([port_id]/[channel_id])+/[denom] + string trace = 1; +} + +// QueryDenomHashResponse is the response type for the Query/DenomHash RPC +// method. +message QueryDenomHashResponse { + // hash (in hex format) of the denomination trace information. + string hash = 1; +} + +// QueryEscrowAddressRequest is the request type for the EscrowAddress RPC method. +message QueryEscrowAddressRequest { + // unique port identifier + string port_id = 1; + // unique channel identifier + string channel_id = 2; +} + +// QueryEscrowAddressResponse is the response type of the EscrowAddress RPC method. +message QueryEscrowAddressResponse { + // the escrow account address + string escrow_address = 1; +} + +// QueryTotalEscrowForDenomRequest is the request type for TotalEscrowForDenom RPC method. +message QueryTotalEscrowForDenomRequest { + string denom = 1; +} + +// QueryTotalEscrowForDenomResponse is the response type for TotalEscrowForDenom RPC method. +message QueryTotalEscrowForDenomResponse { + cosmos.base.v1beta1.Coin amount = 1 [(gogoproto.nullable) = false]; +} diff --git a/proto/ibc/applications/transfer/v1/token.proto b/proto/ibc/applications/transfer/v1/token.proto new file mode 100644 index 0000000..68d621c --- /dev/null +++ b/proto/ibc/applications/transfer/v1/token.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; + +package ibc.applications.transfer.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types"; + +import "gogoproto/gogo.proto"; + +// Token defines a struct which represents a token to be transferred. +message Token { + // the token denomination + Denom denom = 1 [(gogoproto.nullable) = false]; + // the token amount to be transferred + string amount = 2; +} + +// Denom holds the base denom of a Token and a trace of the chains it was sent through. +message Denom { + // the base token denomination + string base = 1; + // the trace of the token + repeated Hop trace = 3 [(gogoproto.nullable) = false]; +} + +// Hop defines a port ID, channel ID pair specifying a unique "hop" in a trace +message Hop { + option (gogoproto.goproto_stringer) = false; + string port_id = 1; + string channel_id = 2; +} diff --git a/proto/ibc/applications/transfer/v1/transfer.proto b/proto/ibc/applications/transfer/v1/transfer.proto new file mode 100644 index 0000000..20c0821 --- /dev/null +++ b/proto/ibc/applications/transfer/v1/transfer.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package ibc.applications.transfer.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types"; + +// Params defines the set of IBC transfer parameters. +// NOTE: To prevent a single token from being transferred, set the +// TransfersEnabled parameter to true and then set the bank module's SendEnabled +// parameter for the denomination to false. +message Params { + // send_enabled enables or disables all cross-chain token transfers from this + // chain. + bool send_enabled = 1; + // receive_enabled enables or disables all cross-chain token transfers to this + // chain. + bool receive_enabled = 2; +} diff --git a/proto/ibc/applications/transfer/v1/tx.proto b/proto/ibc/applications/transfer/v1/tx.proto new file mode 100644 index 0000000..07d82e4 --- /dev/null +++ b/proto/ibc/applications/transfer/v1/tx.proto @@ -0,0 +1,83 @@ +syntax = "proto3"; + +package ibc.applications.transfer.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types"; + +import "amino/amino.proto"; +import "gogoproto/gogo.proto"; +import "cosmos/msg/v1/msg.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "ibc/core/client/v1/client.proto"; +import "ibc/applications/transfer/v1/transfer.proto"; + +// Msg defines the ibc/transfer Msg service. +service Msg { + option (cosmos.msg.v1.service) = true; + + // Transfer defines a rpc handler method for MsgTransfer. + rpc Transfer(MsgTransfer) returns (MsgTransferResponse); + + // UpdateParams defines a rpc handler for MsgUpdateParams. + rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); +} + +// MsgTransfer defines a msg to transfer fungible tokens (i.e Coins) between +// ICS20 enabled chains. See ICS Spec here: +// https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#data-structures +message MsgTransfer { + option (amino.name) = "cosmos-sdk/MsgTransfer"; + option (cosmos.msg.v1.signer) = "sender"; + + option (gogoproto.goproto_getters) = false; + + // the port on which the packet will be sent + string source_port = 1; + // the channel by which the packet will be sent + string source_channel = 2; + // token to be transferred + cosmos.base.v1beta1.Coin token = 3 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true]; + // the sender address + string sender = 4; + // the recipient address on the destination chain + string receiver = 5; + // Timeout height relative to the current block height. + // If you are sending with IBC v1 protocol, either timeout_height or timeout_timestamp must be set. + // If you are sending with IBC v2 protocol, timeout_timestamp must be set, and timeout_height must be omitted. + ibc.core.client.v1.Height timeout_height = 6 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true]; + // Timeout timestamp in absolute nanoseconds since unix epoch. + // If you are sending with IBC v1 protocol, either timeout_height or timeout_timestamp must be set. + // If you are sending with IBC v2 protocol, timeout_timestamp must be set. + uint64 timeout_timestamp = 7; + // optional memo + string memo = 8; + // optional encoding + string encoding = 9; +} + +// MsgTransferResponse defines the Msg/Transfer response type. +message MsgTransferResponse { + option (gogoproto.goproto_getters) = false; + + // sequence number of the transfer packet sent + uint64 sequence = 1; +} + +// MsgUpdateParams is the Msg/UpdateParams request type. +message MsgUpdateParams { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + // signer address + string signer = 1; + + // params defines the transfer parameters to update. + // + // NOTE: All parameters must be supplied. + Params params = 2 [(gogoproto.nullable) = false]; +} + +// MsgUpdateParamsResponse defines the response structure for executing a +// MsgUpdateParams message. +message MsgUpdateParamsResponse {} diff --git a/proto/ibc/core/channel/v1/channel.proto b/proto/ibc/core/channel/v1/channel.proto new file mode 100644 index 0000000..bc8726d --- /dev/null +++ b/proto/ibc/core/channel/v1/channel.proto @@ -0,0 +1,171 @@ +syntax = "proto3"; + +package ibc.core.channel.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types"; + +import "gogoproto/gogo.proto"; +import "ibc/core/client/v1/client.proto"; + +// Channel defines pipeline for exactly-once packet delivery between specific +// modules on separate blockchains, which has at least one end capable of +// sending packets and one end capable of receiving packets. +message Channel { + option (gogoproto.goproto_getters) = false; + + // current state of the channel end + State state = 1; + // whether the channel is ordered or unordered + Order ordering = 2; + // counterparty channel end + Counterparty counterparty = 3 [(gogoproto.nullable) = false]; + // list of connection identifiers, in order, along which packets sent on + // this channel will travel + repeated string connection_hops = 4; + // opaque channel version, which is agreed upon during the handshake + string version = 5; +} + +// IdentifiedChannel defines a channel with additional port and channel +// identifier fields. +message IdentifiedChannel { + option (gogoproto.goproto_getters) = false; + + // current state of the channel end + State state = 1; + // whether the channel is ordered or unordered + Order ordering = 2; + // counterparty channel end + Counterparty counterparty = 3 [(gogoproto.nullable) = false]; + // list of connection identifiers, in order, along which packets sent on + // this channel will travel + repeated string connection_hops = 4; + // opaque channel version, which is agreed upon during the handshake + string version = 5; + // port identifier + string port_id = 6; + // channel identifier + string channel_id = 7; +} + +// State defines if a channel is in one of the following states: +// CLOSED, INIT, TRYOPEN, OPEN, or UNINITIALIZED. +enum State { + option (gogoproto.goproto_enum_prefix) = false; + + // Default State + STATE_UNINITIALIZED_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "UNINITIALIZED"]; + // A channel has just started the opening handshake. + STATE_INIT = 1 [(gogoproto.enumvalue_customname) = "INIT"]; + // A channel has acknowledged the handshake step on the counterparty chain. + STATE_TRYOPEN = 2 [(gogoproto.enumvalue_customname) = "TRYOPEN"]; + // A channel has completed the handshake. Open channels are + // ready to send and receive packets. + STATE_OPEN = 3 [(gogoproto.enumvalue_customname) = "OPEN"]; + // A channel has been closed and can no longer be used to send or receive + // packets. + STATE_CLOSED = 4 [(gogoproto.enumvalue_customname) = "CLOSED"]; +} + +// Order defines if a channel is ORDERED or UNORDERED +enum Order { + option (gogoproto.goproto_enum_prefix) = false; + + // zero-value for channel ordering + ORDER_NONE_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "NONE"]; + // packets can be delivered in any order, which may differ from the order in + // which they were sent. + ORDER_UNORDERED = 1 [(gogoproto.enumvalue_customname) = "UNORDERED"]; + // packets are delivered exactly in the order which they were sent + ORDER_ORDERED = 2 [(gogoproto.enumvalue_customname) = "ORDERED"]; +} + +// Counterparty defines a channel end counterparty +message Counterparty { + option (gogoproto.goproto_getters) = false; + + // port on the counterparty chain which owns the other end of the channel. + string port_id = 1; + // channel end on the counterparty chain + string channel_id = 2; +} + +// Packet defines a type that carries data across different chains through IBC +message Packet { + option (gogoproto.goproto_getters) = false; + + // number corresponds to the order of sends and receives, where a Packet + // with an earlier sequence number must be sent and received before a Packet + // with a later sequence number. + uint64 sequence = 1; + // identifies the port on the sending chain. + string source_port = 2; + // identifies the channel end on the sending chain. + string source_channel = 3; + // identifies the port on the receiving chain. + string destination_port = 4; + // identifies the channel end on the receiving chain. + string destination_channel = 5; + // actual opaque bytes transferred directly to the application module + bytes data = 6; + // block height after which the packet times out + ibc.core.client.v1.Height timeout_height = 7 [(gogoproto.nullable) = false]; + // block timestamp (in nanoseconds) after which the packet times out + uint64 timeout_timestamp = 8; +} + +// PacketState defines the generic type necessary to retrieve and store +// packet commitments, acknowledgements, and receipts. +// Caller is responsible for knowing the context necessary to interpret this +// state as a commitment, acknowledgement, or a receipt. +message PacketState { + option (gogoproto.goproto_getters) = false; + + // channel port identifier. + string port_id = 1; + // channel unique identifier. + string channel_id = 2; + // packet sequence. + uint64 sequence = 3; + // embedded data that represents packet state. + bytes data = 4; +} + +// PacketId is an identifier for a unique Packet +// Source chains refer to packets by source port/channel +// Destination chains refer to packets by destination port/channel +message PacketId { + option (gogoproto.goproto_getters) = false; + + // channel port identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; + // packet sequence + uint64 sequence = 3; +} + +// Acknowledgement is the recommended acknowledgement format to be used by +// app-specific protocols. +// NOTE: The field numbers 21 and 22 were explicitly chosen to avoid accidental +// conflicts with other protobuf message formats used for acknowledgements. +// The first byte of any message with this format will be the non-ASCII values +// `0xaa` (result) or `0xb2` (error). Implemented as defined by ICS: +// https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope +message Acknowledgement { + // response contains either a result or an error and must be non-empty + oneof response { + bytes result = 21; + string error = 22; + } +} + +// Timeout defines an execution deadline structure for 04-channel handlers. +// This includes packet lifecycle handlers. +// A valid Timeout contains either one or both of a timestamp and block height (sequence). +message Timeout { + // block height after which the packet times out + ibc.core.client.v1.Height height = 1 [(gogoproto.nullable) = false]; + // block timestamp (in nanoseconds) after which the packet times out + uint64 timestamp = 2; +} diff --git a/proto/ibc/core/channel/v1/genesis.proto b/proto/ibc/core/channel/v1/genesis.proto new file mode 100644 index 0000000..c6c95d4 --- /dev/null +++ b/proto/ibc/core/channel/v1/genesis.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + +package ibc.core.channel.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types"; + +import "gogoproto/gogo.proto"; +import "ibc/core/channel/v1/channel.proto"; + +// GenesisState defines the ibc channel submodule's genesis state. +message GenesisState { + repeated IdentifiedChannel channels = 1 [(gogoproto.casttype) = "IdentifiedChannel", (gogoproto.nullable) = false]; + repeated PacketState acknowledgements = 2 [(gogoproto.nullable) = false]; + repeated PacketState commitments = 3 [(gogoproto.nullable) = false]; + repeated PacketState receipts = 4 [(gogoproto.nullable) = false]; + repeated PacketSequence send_sequences = 5 [(gogoproto.nullable) = false]; + repeated PacketSequence recv_sequences = 6 [(gogoproto.nullable) = false]; + repeated PacketSequence ack_sequences = 7 [(gogoproto.nullable) = false]; + // the sequence for the next generated channel identifier + uint64 next_channel_sequence = 8; +} + +// PacketSequence defines the genesis type necessary to retrieve and store +// next send and receive sequences. +message PacketSequence { + string port_id = 1; + string channel_id = 2; + uint64 sequence = 3; +} diff --git a/proto/ibc/core/channel/v1/query.proto b/proto/ibc/core/channel/v1/query.proto new file mode 100644 index 0000000..91dbfbf --- /dev/null +++ b/proto/ibc/core/channel/v1/query.proto @@ -0,0 +1,402 @@ +syntax = "proto3"; + +package ibc.core.channel.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types"; + +import "ibc/core/client/v1/client.proto"; +import "cosmos/base/query/v1beta1/pagination.proto"; +import "ibc/core/channel/v1/channel.proto"; +import "google/api/annotations.proto"; +import "google/protobuf/any.proto"; +import "gogoproto/gogo.proto"; + +// Query provides defines the gRPC querier service +service Query { + // Channel queries an IBC Channel. + rpc Channel(QueryChannelRequest) returns (QueryChannelResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels/{channel_id}/ports/{port_id}"; + } + + // Channels queries all the IBC channels of a chain. + rpc Channels(QueryChannelsRequest) returns (QueryChannelsResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels"; + } + + // ConnectionChannels queries all the channels associated with a connection + // end. + rpc ConnectionChannels(QueryConnectionChannelsRequest) returns (QueryConnectionChannelsResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/connections/{connection}/channels"; + } + + // ChannelClientState queries for the client state for the channel associated + // with the provided channel identifiers. + rpc ChannelClientState(QueryChannelClientStateRequest) returns (QueryChannelClientStateResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels/{channel_id}/" + "ports/{port_id}/client_state"; + } + + // ChannelConsensusState queries for the consensus state for the channel + // associated with the provided channel identifiers. + rpc ChannelConsensusState(QueryChannelConsensusStateRequest) returns (QueryChannelConsensusStateResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels/{channel_id}/" + "ports/{port_id}/consensus_state/revision/" + "{revision_number}/height/{revision_height}"; + } + + // PacketCommitment queries a stored packet commitment hash. + rpc PacketCommitment(QueryPacketCommitmentRequest) returns (QueryPacketCommitmentResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels/{channel_id}/ports/{port_id}/" + "packet_commitments/{sequence}"; + } + + // PacketCommitments returns all the packet commitments hashes associated + // with a channel. + rpc PacketCommitments(QueryPacketCommitmentsRequest) returns (QueryPacketCommitmentsResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels/{channel_id}/" + "ports/{port_id}/packet_commitments"; + } + + // PacketReceipt queries if a given packet sequence has been received on the + // queried chain + rpc PacketReceipt(QueryPacketReceiptRequest) returns (QueryPacketReceiptResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels/{channel_id}/" + "ports/{port_id}/packet_receipts/{sequence}"; + } + + // PacketAcknowledgement queries a stored packet acknowledgement hash. + rpc PacketAcknowledgement(QueryPacketAcknowledgementRequest) returns (QueryPacketAcknowledgementResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels/{channel_id}/" + "ports/{port_id}/packet_acks/{sequence}"; + } + + // PacketAcknowledgements returns all the packet acknowledgements associated + // with a channel. + rpc PacketAcknowledgements(QueryPacketAcknowledgementsRequest) returns (QueryPacketAcknowledgementsResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels/{channel_id}/" + "ports/{port_id}/packet_acknowledgements"; + } + + // UnreceivedPackets returns all the unreceived IBC packets associated with a + // channel and sequences. + rpc UnreceivedPackets(QueryUnreceivedPacketsRequest) returns (QueryUnreceivedPacketsResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels/{channel_id}/ports/{port_id}/" + "packet_commitments/" + "{packet_commitment_sequences}/unreceived_packets"; + } + + // UnreceivedAcks returns all the unreceived IBC acknowledgements associated + // with a channel and sequences. + rpc UnreceivedAcks(QueryUnreceivedAcksRequest) returns (QueryUnreceivedAcksResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels/{channel_id}/" + "ports/{port_id}/packet_commitments/" + "{packet_ack_sequences}/unreceived_acks"; + } + + // NextSequenceReceive returns the next receive sequence for a given channel. + rpc NextSequenceReceive(QueryNextSequenceReceiveRequest) returns (QueryNextSequenceReceiveResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels/{channel_id}/" + "ports/{port_id}/next_sequence"; + } + + // NextSequenceSend returns the next send sequence for a given channel. + rpc NextSequenceSend(QueryNextSequenceSendRequest) returns (QueryNextSequenceSendResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels/{channel_id}/" + "ports/{port_id}/next_sequence_send"; + } +} + +// QueryChannelRequest is the request type for the Query/Channel RPC method +message QueryChannelRequest { + // port unique identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; +} + +// QueryChannelResponse is the response type for the Query/Channel RPC method. +// Besides the Channel end, it includes a proof and the height from which the +// proof was retrieved. +message QueryChannelResponse { + // channel associated with the request identifiers + ibc.core.channel.v1.Channel channel = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} + +// QueryChannelsRequest is the request type for the Query/Channels RPC method +message QueryChannelsRequest { + // pagination request + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +// QueryChannelsResponse is the response type for the Query/Channels RPC method. +message QueryChannelsResponse { + // list of stored channels of the chain. + repeated ibc.core.channel.v1.IdentifiedChannel channels = 1; + // pagination response + cosmos.base.query.v1beta1.PageResponse pagination = 2; + // query block height + ibc.core.client.v1.Height height = 3 [(gogoproto.nullable) = false]; +} + +// QueryConnectionChannelsRequest is the request type for the +// Query/QueryConnectionChannels RPC method +message QueryConnectionChannelsRequest { + // connection unique identifier + string connection = 1; + // pagination request + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryConnectionChannelsResponse is the Response type for the +// Query/QueryConnectionChannels RPC method +message QueryConnectionChannelsResponse { + // list of channels associated with a connection. + repeated ibc.core.channel.v1.IdentifiedChannel channels = 1; + // pagination response + cosmos.base.query.v1beta1.PageResponse pagination = 2; + // query block height + ibc.core.client.v1.Height height = 3 [(gogoproto.nullable) = false]; +} + +// QueryChannelClientStateRequest is the request type for the Query/ClientState +// RPC method +message QueryChannelClientStateRequest { + // port unique identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; +} + +// QueryChannelClientStateResponse is the Response type for the +// Query/QueryChannelClientState RPC method +message QueryChannelClientStateResponse { + // client state associated with the channel + ibc.core.client.v1.IdentifiedClientState identified_client_state = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} + +// QueryChannelConsensusStateRequest is the request type for the +// Query/ConsensusState RPC method +message QueryChannelConsensusStateRequest { + // port unique identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; + // revision number of the consensus state + uint64 revision_number = 3; + // revision height of the consensus state + uint64 revision_height = 4; +} + +// QueryChannelClientStateResponse is the Response type for the +// Query/QueryChannelClientState RPC method +message QueryChannelConsensusStateResponse { + // consensus state associated with the channel + google.protobuf.Any consensus_state = 1; + // client ID associated with the consensus state + string client_id = 2; + // merkle proof of existence + bytes proof = 3; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 4 [(gogoproto.nullable) = false]; +} + +// QueryPacketCommitmentRequest is the request type for the +// Query/PacketCommitment RPC method +message QueryPacketCommitmentRequest { + // port unique identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; + // packet sequence + uint64 sequence = 3; +} + +// QueryPacketCommitmentResponse defines the client query response for a packet +// which also includes a proof and the height from which the proof was +// retrieved +message QueryPacketCommitmentResponse { + // packet associated with the request fields + bytes commitment = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} + +// QueryPacketCommitmentsRequest is the request type for the +// Query/QueryPacketCommitments RPC method +message QueryPacketCommitmentsRequest { + // port unique identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; + // pagination request + cosmos.base.query.v1beta1.PageRequest pagination = 3; +} + +// QueryPacketCommitmentsResponse is the request type for the +// Query/QueryPacketCommitments RPC method +message QueryPacketCommitmentsResponse { + repeated ibc.core.channel.v1.PacketState commitments = 1; + // pagination response + cosmos.base.query.v1beta1.PageResponse pagination = 2; + // query block height + ibc.core.client.v1.Height height = 3 [(gogoproto.nullable) = false]; +} + +// QueryPacketReceiptRequest is the request type for the +// Query/PacketReceipt RPC method +message QueryPacketReceiptRequest { + // port unique identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; + // packet sequence + uint64 sequence = 3; +} + +// QueryPacketReceiptResponse defines the client query response for a packet +// receipt which also includes a proof, and the height from which the proof was +// retrieved +message QueryPacketReceiptResponse { + // success flag for if receipt exists + bool received = 2; + // merkle proof of existence + bytes proof = 3; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 4 [(gogoproto.nullable) = false]; +} + +// QueryPacketAcknowledgementRequest is the request type for the +// Query/PacketAcknowledgement RPC method +message QueryPacketAcknowledgementRequest { + // port unique identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; + // packet sequence + uint64 sequence = 3; +} + +// QueryPacketAcknowledgementResponse defines the client query response for a +// packet which also includes a proof and the height from which the +// proof was retrieved +message QueryPacketAcknowledgementResponse { + // packet associated with the request fields + bytes acknowledgement = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} + +// QueryPacketAcknowledgementsRequest is the request type for the +// Query/QueryPacketCommitments RPC method +message QueryPacketAcknowledgementsRequest { + // port unique identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; + // pagination request + cosmos.base.query.v1beta1.PageRequest pagination = 3; + // list of packet sequences + repeated uint64 packet_commitment_sequences = 4; +} + +// QueryPacketAcknowledgemetsResponse is the request type for the +// Query/QueryPacketAcknowledgements RPC method +message QueryPacketAcknowledgementsResponse { + repeated ibc.core.channel.v1.PacketState acknowledgements = 1; + // pagination response + cosmos.base.query.v1beta1.PageResponse pagination = 2; + // query block height + ibc.core.client.v1.Height height = 3 [(gogoproto.nullable) = false]; +} + +// QueryUnreceivedPacketsRequest is the request type for the +// Query/UnreceivedPackets RPC method +message QueryUnreceivedPacketsRequest { + // port unique identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; + // list of packet sequences + repeated uint64 packet_commitment_sequences = 3; +} + +// QueryUnreceivedPacketsResponse is the response type for the +// Query/UnreceivedPacketCommitments RPC method +message QueryUnreceivedPacketsResponse { + // list of unreceived packet sequences + repeated uint64 sequences = 1; + // query block height + ibc.core.client.v1.Height height = 2 [(gogoproto.nullable) = false]; +} + +// QueryUnreceivedAcks is the request type for the +// Query/UnreceivedAcks RPC method +message QueryUnreceivedAcksRequest { + // port unique identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; + // list of acknowledgement sequences + repeated uint64 packet_ack_sequences = 3; +} + +// QueryUnreceivedAcksResponse is the response type for the +// Query/UnreceivedAcks RPC method +message QueryUnreceivedAcksResponse { + // list of unreceived acknowledgement sequences + repeated uint64 sequences = 1; + // query block height + ibc.core.client.v1.Height height = 2 [(gogoproto.nullable) = false]; +} + +// QueryNextSequenceReceiveRequest is the request type for the +// Query/QueryNextSequenceReceiveRequest RPC method +message QueryNextSequenceReceiveRequest { + // port unique identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; +} + +// QuerySequenceResponse is the response type for the +// Query/QueryNextSequenceReceiveResponse RPC method +message QueryNextSequenceReceiveResponse { + // next sequence receive number + uint64 next_sequence_receive = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} + +// QueryNextSequenceSendRequest is the request type for the +// Query/QueryNextSequenceSend RPC method +message QueryNextSequenceSendRequest { + // port unique identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; +} + +// QueryNextSequenceSendResponse is the request type for the +// Query/QueryNextSequenceSend RPC method +message QueryNextSequenceSendResponse { + // next sequence send number + uint64 next_sequence_send = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} diff --git a/proto/ibc/core/channel/v1/tx.proto b/proto/ibc/core/channel/v1/tx.proto new file mode 100644 index 0000000..68ca232 --- /dev/null +++ b/proto/ibc/core/channel/v1/tx.proto @@ -0,0 +1,257 @@ +syntax = "proto3"; + +package ibc.core.channel.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types"; + +import "gogoproto/gogo.proto"; +import "cosmos/msg/v1/msg.proto"; +import "ibc/core/client/v1/client.proto"; +import "ibc/core/channel/v1/channel.proto"; + +// Msg defines the ibc/channel Msg service. +service Msg { + option (cosmos.msg.v1.service) = true; + + // ChannelOpenInit defines a rpc handler method for MsgChannelOpenInit. + rpc ChannelOpenInit(MsgChannelOpenInit) returns (MsgChannelOpenInitResponse); + + // ChannelOpenTry defines a rpc handler method for MsgChannelOpenTry. + rpc ChannelOpenTry(MsgChannelOpenTry) returns (MsgChannelOpenTryResponse); + + // ChannelOpenAck defines a rpc handler method for MsgChannelOpenAck. + rpc ChannelOpenAck(MsgChannelOpenAck) returns (MsgChannelOpenAckResponse); + + // ChannelOpenConfirm defines a rpc handler method for MsgChannelOpenConfirm. + rpc ChannelOpenConfirm(MsgChannelOpenConfirm) returns (MsgChannelOpenConfirmResponse); + + // ChannelCloseInit defines a rpc handler method for MsgChannelCloseInit. + rpc ChannelCloseInit(MsgChannelCloseInit) returns (MsgChannelCloseInitResponse); + + // ChannelCloseConfirm defines a rpc handler method for + // MsgChannelCloseConfirm. + rpc ChannelCloseConfirm(MsgChannelCloseConfirm) returns (MsgChannelCloseConfirmResponse); + + // RecvPacket defines a rpc handler method for MsgRecvPacket. + rpc RecvPacket(MsgRecvPacket) returns (MsgRecvPacketResponse); + + // Timeout defines a rpc handler method for MsgTimeout. + rpc Timeout(MsgTimeout) returns (MsgTimeoutResponse); + + // TimeoutOnClose defines a rpc handler method for MsgTimeoutOnClose. + rpc TimeoutOnClose(MsgTimeoutOnClose) returns (MsgTimeoutOnCloseResponse); + + // Acknowledgement defines a rpc handler method for MsgAcknowledgement. + rpc Acknowledgement(MsgAcknowledgement) returns (MsgAcknowledgementResponse); +} + +// ResponseResultType defines the possible outcomes of the execution of a message +enum ResponseResultType { + option (gogoproto.goproto_enum_prefix) = false; + + // Default zero value enumeration + RESPONSE_RESULT_TYPE_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "UNSPECIFIED"]; + // The message did not call the IBC application callbacks (because, for example, the packet had already been relayed) + RESPONSE_RESULT_TYPE_NOOP = 1 [(gogoproto.enumvalue_customname) = "NOOP"]; + // The message was executed successfully + RESPONSE_RESULT_TYPE_SUCCESS = 2 [(gogoproto.enumvalue_customname) = "SUCCESS"]; + // The message was executed unsuccessfully + RESPONSE_RESULT_TYPE_FAILURE = 3 [(gogoproto.enumvalue_customname) = "FAILURE"]; +} + +// MsgChannelOpenInit defines an sdk.Msg to initialize a channel handshake. It +// is called by a relayer on Chain A. +message MsgChannelOpenInit { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + string port_id = 1; + Channel channel = 2 [(gogoproto.nullable) = false]; + string signer = 3; +} + +// MsgChannelOpenInitResponse defines the Msg/ChannelOpenInit response type. +message MsgChannelOpenInitResponse { + option (gogoproto.goproto_getters) = false; + + string channel_id = 1; + string version = 2; +} + +// MsgChannelOpenInit defines a msg sent by a Relayer to try to open a channel +// on Chain B. The version field within the Channel field has been deprecated. Its +// value will be ignored by core IBC. +message MsgChannelOpenTry { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + string port_id = 1; + // Deprecated: this field is unused. Crossing hello's are no longer supported in core IBC. + string previous_channel_id = 2 [deprecated = true]; + // NOTE: the version field within the channel has been deprecated. Its value will be ignored by core IBC. + Channel channel = 3 [(gogoproto.nullable) = false]; + string counterparty_version = 4; + bytes proof_init = 5; + ibc.core.client.v1.Height proof_height = 6 [(gogoproto.nullable) = false]; + string signer = 7; +} + +// MsgChannelOpenTryResponse defines the Msg/ChannelOpenTry response type. +message MsgChannelOpenTryResponse { + option (gogoproto.goproto_getters) = false; + + string version = 1; + string channel_id = 2; +} + +// MsgChannelOpenAck defines a msg sent by a Relayer to Chain A to acknowledge +// the change of channel state to TRYOPEN on Chain B. +message MsgChannelOpenAck { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + string port_id = 1; + string channel_id = 2; + string counterparty_channel_id = 3; + string counterparty_version = 4; + bytes proof_try = 5; + ibc.core.client.v1.Height proof_height = 6 [(gogoproto.nullable) = false]; + string signer = 7; +} + +// MsgChannelOpenAckResponse defines the Msg/ChannelOpenAck response type. +message MsgChannelOpenAckResponse {} + +// MsgChannelOpenConfirm defines a msg sent by a Relayer to Chain B to +// acknowledge the change of channel state to OPEN on Chain A. +message MsgChannelOpenConfirm { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + string port_id = 1; + string channel_id = 2; + bytes proof_ack = 3; + ibc.core.client.v1.Height proof_height = 4 [(gogoproto.nullable) = false]; + string signer = 5; +} + +// MsgChannelOpenConfirmResponse defines the Msg/ChannelOpenConfirm response +// type. +message MsgChannelOpenConfirmResponse {} + +// MsgChannelCloseInit defines a msg sent by a Relayer to Chain A +// to close a channel with Chain B. +message MsgChannelCloseInit { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + string port_id = 1; + string channel_id = 2; + string signer = 3; +} + +// MsgChannelCloseInitResponse defines the Msg/ChannelCloseInit response type. +message MsgChannelCloseInitResponse {} + +// MsgChannelCloseConfirm defines a msg sent by a Relayer to Chain B +// to acknowledge the change of channel state to CLOSED on Chain A. +message MsgChannelCloseConfirm { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + string port_id = 1; + string channel_id = 2; + bytes proof_init = 3; + ibc.core.client.v1.Height proof_height = 4 [(gogoproto.nullable) = false]; + string signer = 5; +} + +// MsgChannelCloseConfirmResponse defines the Msg/ChannelCloseConfirm response +// type. +message MsgChannelCloseConfirmResponse {} + +// MsgRecvPacket receives incoming IBC packet +message MsgRecvPacket { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + Packet packet = 1 [(gogoproto.nullable) = false]; + bytes proof_commitment = 2; + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; + string signer = 4; +} + +// MsgRecvPacketResponse defines the Msg/RecvPacket response type. +message MsgRecvPacketResponse { + option (gogoproto.goproto_getters) = false; + + ResponseResultType result = 1; +} + +// MsgTimeout receives timed-out packet +message MsgTimeout { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + Packet packet = 1 [(gogoproto.nullable) = false]; + bytes proof_unreceived = 2; + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; + uint64 next_sequence_recv = 4; + string signer = 5; +} + +// MsgTimeoutResponse defines the Msg/Timeout response type. +message MsgTimeoutResponse { + option (gogoproto.goproto_getters) = false; + + ResponseResultType result = 1; +} + +// MsgTimeoutOnClose timed-out packet upon counterparty channel closure. +message MsgTimeoutOnClose { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + Packet packet = 1 [(gogoproto.nullable) = false]; + bytes proof_unreceived = 2; + bytes proof_close = 3; + ibc.core.client.v1.Height proof_height = 4 [(gogoproto.nullable) = false]; + uint64 next_sequence_recv = 5; + string signer = 6; +} + +// MsgTimeoutOnCloseResponse defines the Msg/TimeoutOnClose response type. +message MsgTimeoutOnCloseResponse { + option (gogoproto.goproto_getters) = false; + + ResponseResultType result = 1; +} + +// MsgAcknowledgement receives incoming IBC acknowledgement +message MsgAcknowledgement { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + Packet packet = 1 [(gogoproto.nullable) = false]; + bytes acknowledgement = 2; + bytes proof_acked = 3; + ibc.core.client.v1.Height proof_height = 4 [(gogoproto.nullable) = false]; + string signer = 5; +} + +// MsgAcknowledgementResponse defines the Msg/Acknowledgement response type. +message MsgAcknowledgementResponse { + option (gogoproto.goproto_getters) = false; + + ResponseResultType result = 1; +} diff --git a/proto/ibc/core/channel/v2/genesis.proto b/proto/ibc/core/channel/v2/genesis.proto new file mode 100644 index 0000000..b8485a3 --- /dev/null +++ b/proto/ibc/core/channel/v2/genesis.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package ibc.core.channel.v2; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types"; + +import "gogoproto/gogo.proto"; + +// GenesisState defines the ibc channel/v2 submodule's genesis state. +message GenesisState { + repeated PacketState acknowledgements = 2 [(gogoproto.nullable) = false]; + repeated PacketState commitments = 3 [(gogoproto.nullable) = false]; + repeated PacketState receipts = 4 [(gogoproto.nullable) = false]; + repeated PacketState async_packets = 5 [(gogoproto.nullable) = false]; + repeated PacketSequence send_sequences = 6 [(gogoproto.nullable) = false]; +} + +// PacketState defines the generic type necessary to retrieve and store +// packet commitments, acknowledgements, and receipts. +// Caller is responsible for knowing the context necessary to interpret this +// state as a commitment, acknowledgement, or a receipt. +message PacketState { + option (gogoproto.goproto_getters) = false; + + // client unique identifier. + string client_id = 1; + // packet sequence. + uint64 sequence = 2; + // embedded data that represents packet state. + bytes data = 3; +} + +// PacketSequence defines the genesis type necessary to retrieve and store next send sequences. +message PacketSequence { + // client unique identifier. + string client_id = 1; + // packet sequence + uint64 sequence = 2; +} diff --git a/proto/ibc/core/channel/v2/packet.proto b/proto/ibc/core/channel/v2/packet.proto new file mode 100644 index 0000000..17f521f --- /dev/null +++ b/proto/ibc/core/channel/v2/packet.proto @@ -0,0 +1,68 @@ + +syntax = "proto3"; + +package ibc.core.channel.v2; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types"; + +import "gogoproto/gogo.proto"; + +// Packet defines a type that carries data across different chains through IBC +message Packet { + // number corresponds to the order of sends and receives, where a Packet + // with an earlier sequence number must be sent and received before a Packet + // with a later sequence number. + uint64 sequence = 1; + // identifies the sending client on the sending chain. + string source_client = 2; + // identifies the receiving client on the receiving chain. + string destination_client = 3; + // timeout timestamp in seconds after which the packet times out. + uint64 timeout_timestamp = 4; + // a list of payloads, each one for a specific application. + repeated Payload payloads = 5 [(gogoproto.nullable) = false]; +} + +// Payload contains the source and destination ports and payload for the application (version, encoding, raw bytes) +message Payload { + // specifies the source port of the packet. + string source_port = 1; + // specifies the destination port of the packet. + string destination_port = 2; + // version of the specified application. + string version = 3; + // the encoding used for the provided value. + string encoding = 4; + // the raw bytes for the payload. + bytes value = 5; +} + +// Acknowledgement contains a list of all ack results associated with a single packet. +// In the case of a successful receive, the acknowledgement will contain an app acknowledgement +// for each application that received a payload in the same order that the payloads were sent +// in the packet. +// If the receive is not successful, the acknowledgement will contain a single app acknowledgment +// which will be a constant error acknowledgment as defined by the IBC v2 protocol. +message Acknowledgement { + repeated bytes app_acknowledgements = 1; +} + +// PacketStatus specifies the status of a RecvPacketResult. +enum PacketStatus { + // PACKET_STATUS_UNSPECIFIED indicates an unknown packet status. + PACKET_STATUS_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "NONE"]; + // PACKET_STATUS_SUCCESS indicates a successful packet receipt. + PACKET_STATUS_SUCCESS = 1 [(gogoproto.enumvalue_customname) = "Success"]; + // PACKET_STATUS_FAILURE indicates a failed packet receipt. + PACKET_STATUS_FAILURE = 2 [(gogoproto.enumvalue_customname) = "Failure"]; + // PACKET_STATUS_ASYNC indicates an async packet receipt. + PACKET_STATUS_ASYNC = 3 [(gogoproto.enumvalue_customname) = "Async"]; +} + +// RecvPacketResult speecifies the status of a packet as well as the acknowledgement bytes. +message RecvPacketResult { + // status of the packet + PacketStatus status = 1; + // acknowledgement of the packet + bytes acknowledgement = 2; +} diff --git a/proto/ibc/core/channel/v2/query.proto b/proto/ibc/core/channel/v2/query.proto new file mode 100644 index 0000000..f9b0681 --- /dev/null +++ b/proto/ibc/core/channel/v2/query.proto @@ -0,0 +1,199 @@ +syntax = "proto3"; + +package ibc.core.channel.v2; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types"; + +import "cosmos/base/query/v1beta1/pagination.proto"; +import "ibc/core/channel/v2/genesis.proto"; +import "ibc/core/client/v1/client.proto"; +import "google/api/annotations.proto"; +import "gogoproto/gogo.proto"; + +// Query provides defines the gRPC querier service +service Query { + // NextSequenceSend returns the next send sequence for a given channel. + rpc NextSequenceSend(QueryNextSequenceSendRequest) returns (QueryNextSequenceSendResponse) { + option (google.api.http).get = "/ibc/core/channel/v2/clients/{client_id}/next_sequence_send"; + } + + // PacketCommitment queries a stored packet commitment hash. + rpc PacketCommitment(QueryPacketCommitmentRequest) returns (QueryPacketCommitmentResponse) { + option (google.api.http).get = "/ibc/core/channel/v2/clients/{client_id}/packet_commitments/{sequence}"; + } + + // PacketCommitments queries a stored packet commitment hash. + rpc PacketCommitments(QueryPacketCommitmentsRequest) returns (QueryPacketCommitmentsResponse) { + option (google.api.http).get = "/ibc/core/channel/v2/clients/{client_id}/packet_commitments"; + } + + // PacketAcknowledgement queries a stored acknowledgement commitment hash. + rpc PacketAcknowledgement(QueryPacketAcknowledgementRequest) returns (QueryPacketAcknowledgementResponse) { + option (google.api.http).get = "/ibc/core/channel/v2/clients/{client_id}/packet_acks/{sequence}"; + } + + // PacketAcknowledgements returns all packet acknowledgements associated with a channel. + rpc PacketAcknowledgements(QueryPacketAcknowledgementsRequest) returns (QueryPacketAcknowledgementsResponse) { + option (google.api.http).get = "/ibc/core/channel/v2/clients/{client_id}/packet_acknowledgements"; + } + + // PacketReceipt queries a stored packet receipt. + rpc PacketReceipt(QueryPacketReceiptRequest) returns (QueryPacketReceiptResponse) { + option (google.api.http).get = "/ibc/core/channel/v2/clients/{client_id}/packet_receipts/{sequence}"; + } + + // UnreceivedPackets returns all the unreceived IBC packets associated with a channel and sequences. + rpc UnreceivedPackets(QueryUnreceivedPacketsRequest) returns (QueryUnreceivedPacketsResponse) { + option (google.api.http).get = "/ibc/core/channel/v2/clients/{client_id}/packet_commitments/" + "{sequences}/unreceived_packets"; + } + + // UnreceivedAcks returns all the unreceived IBC acknowledgements associated with a channel and sequences. + rpc UnreceivedAcks(QueryUnreceivedAcksRequest) returns (QueryUnreceivedAcksResponse) { + option (google.api.http).get = + "/ibc/core/channel/v2/clients/{client_id}/packet_commitments/{packet_ack_sequences}/unreceived_acks"; + } +} + +// QueryNextSequenceSendRequest is the request type for the Query/QueryNextSequenceSend RPC method +message QueryNextSequenceSendRequest { + // client unique identifier + string client_id = 1; +} + +// QueryNextSequenceSendResponse is the response type for the Query/QueryNextSequenceSend RPC method +message QueryNextSequenceSendResponse { + // next sequence send number + uint64 next_sequence_send = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} + +// QueryPacketCommitmentRequest is the request type for the Query/PacketCommitment RPC method. +message QueryPacketCommitmentRequest { + // client unique identifier + string client_id = 1; + // packet sequence + uint64 sequence = 2; +} + +// QueryPacketCommitmentResponse is the response type for the Query/PacketCommitment RPC method. +message QueryPacketCommitmentResponse { + // packet associated with the request fields + bytes commitment = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} + +// QueryPacketCommitmentsRequest is the request type for the Query/PacketCommitments RPC method. +message QueryPacketCommitmentsRequest { + // client unique identifier + string client_id = 1; + // pagination request + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryPacketCommitmentResponse is the response type for the Query/PacketCommitment RPC method. +message QueryPacketCommitmentsResponse { + // collection of packet commitments for the requested channel identifier. + repeated ibc.core.channel.v2.PacketState commitments = 1; + // pagination response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; + // query block height. + ibc.core.client.v1.Height height = 3 [(gogoproto.nullable) = false]; +} + +// QueryPacketAcknowledgementRequest is the request type for the Query/PacketAcknowledgement RPC method. +message QueryPacketAcknowledgementRequest { + // client unique identifier + string client_id = 1; + // packet sequence + uint64 sequence = 2; +} + +// QueryPacketAcknowledgementResponse is the response type for the Query/PacketAcknowledgement RPC method. +message QueryPacketAcknowledgementResponse { + // acknowledgement associated with the request fields + bytes acknowledgement = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} + +// QueryPacketAcknowledgementsRequest is the request type for the +// Query/QueryPacketCommitments RPC method +message QueryPacketAcknowledgementsRequest { + // client unique identifier + string client_id = 1; + // pagination request + cosmos.base.query.v1beta1.PageRequest pagination = 2; + // list of packet sequences + repeated uint64 packet_commitment_sequences = 3; +} + +// QueryPacketAcknowledgemetsResponse is the request type for the +// Query/QueryPacketAcknowledgements RPC method +message QueryPacketAcknowledgementsResponse { + repeated ibc.core.channel.v2.PacketState acknowledgements = 1; + // pagination response + cosmos.base.query.v1beta1.PageResponse pagination = 2; + // query block height + ibc.core.client.v1.Height height = 3 [(gogoproto.nullable) = false]; +} + +// QueryPacketReceiptRequest is the request type for the Query/PacketReceipt RPC method. +message QueryPacketReceiptRequest { + // client unique identifier + string client_id = 1; + // packet sequence + uint64 sequence = 2; +} + +// QueryPacketReceiptResponse is the response type for the Query/PacketReceipt RPC method. +message QueryPacketReceiptResponse { + // success flag for if receipt exists + bool received = 2; + // merkle proof of existence or absence + bytes proof = 3; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 4 [(gogoproto.nullable) = false]; +} + +// QueryUnreceivedPacketsRequest is the request type for the Query/UnreceivedPackets RPC method +message QueryUnreceivedPacketsRequest { + // client unique identifier + string client_id = 1; + // list of packet sequences + repeated uint64 sequences = 2; +} + +// QueryUnreceivedPacketsResponse is the response type for the Query/UnreceivedPacketCommitments RPC method +message QueryUnreceivedPacketsResponse { + // list of unreceived packet sequences + repeated uint64 sequences = 1; + // query block height + ibc.core.client.v1.Height height = 2 [(gogoproto.nullable) = false]; +} + +// QueryUnreceivedAcks is the request type for the +// Query/UnreceivedAcks RPC method +message QueryUnreceivedAcksRequest { + // client unique identifier + string client_id = 1; + // list of acknowledgement sequences + repeated uint64 packet_ack_sequences = 2; +} + +// QueryUnreceivedAcksResponse is the response type for the +// Query/UnreceivedAcks RPC method +message QueryUnreceivedAcksResponse { + // list of unreceived acknowledgement sequences + repeated uint64 sequences = 1; + // query block height + ibc.core.client.v1.Height height = 2 [(gogoproto.nullable) = false]; +} diff --git a/proto/ibc/core/channel/v2/tx.proto b/proto/ibc/core/channel/v2/tx.proto new file mode 100644 index 0000000..a150808 --- /dev/null +++ b/proto/ibc/core/channel/v2/tx.proto @@ -0,0 +1,117 @@ +syntax = "proto3"; + +package ibc.core.channel.v2; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types"; + +import "gogoproto/gogo.proto"; +import "cosmos/msg/v1/msg.proto"; +import "ibc/core/channel/v2/packet.proto"; +import "ibc/core/client/v1/client.proto"; + +// Msg defines the ibc/channel/v2 Msg service. +service Msg { + option (cosmos.msg.v1.service) = true; + + // SendPacket defines a rpc handler method for MsgSendPacket. + rpc SendPacket(MsgSendPacket) returns (MsgSendPacketResponse); + + // RecvPacket defines a rpc handler method for MsgRecvPacket. + rpc RecvPacket(MsgRecvPacket) returns (MsgRecvPacketResponse); + + // Timeout defines a rpc handler method for MsgTimeout. + rpc Timeout(MsgTimeout) returns (MsgTimeoutResponse); + + // Acknowledgement defines a rpc handler method for MsgAcknowledgement. + rpc Acknowledgement(MsgAcknowledgement) returns (MsgAcknowledgementResponse); +} + +// MsgSendPacket sends an outgoing IBC packet. +message MsgSendPacket { + option (cosmos.msg.v1.signer) = "signer"; + option (gogoproto.goproto_getters) = false; + + string source_client = 1; + uint64 timeout_timestamp = 2; + repeated Payload payloads = 3 [(gogoproto.nullable) = false]; + string signer = 4; +} + +// MsgSendPacketResponse defines the Msg/SendPacket response type. +message MsgSendPacketResponse { + option (gogoproto.goproto_getters) = false; + + uint64 sequence = 1; +} + +// MsgRecvPacket receives an incoming IBC packet. +message MsgRecvPacket { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + Packet packet = 1 [(gogoproto.nullable) = false]; + bytes proof_commitment = 2; + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; + string signer = 4; +} + +// ResponseResultType defines the possible outcomes of the execution of a message +enum ResponseResultType { + option (gogoproto.goproto_enum_prefix) = false; + + // Default zero value enumeration + RESPONSE_RESULT_TYPE_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "UNSPECIFIED"]; + // The message did not call the IBC application callbacks (because, for example, the packet had already been relayed) + RESPONSE_RESULT_TYPE_NOOP = 1 [(gogoproto.enumvalue_customname) = "NOOP"]; + // The message was executed successfully + RESPONSE_RESULT_TYPE_SUCCESS = 2 [(gogoproto.enumvalue_customname) = "SUCCESS"]; + // The message was executed unsuccessfully + RESPONSE_RESULT_TYPE_FAILURE = 3 [(gogoproto.enumvalue_customname) = "FAILURE"]; +} + +// MsgRecvPacketResponse defines the Msg/RecvPacket response type. +message MsgRecvPacketResponse { + option (gogoproto.goproto_getters) = false; + + ResponseResultType result = 1; +} + +// MsgTimeout receives timed-out packet +message MsgTimeout { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + Packet packet = 1 [(gogoproto.nullable) = false]; + bytes proof_unreceived = 2; + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; + string signer = 5; +} + +// MsgTimeoutResponse defines the Msg/Timeout response type. +message MsgTimeoutResponse { + option (gogoproto.goproto_getters) = false; + + ResponseResultType result = 1; +} + +// MsgAcknowledgement receives incoming IBC acknowledgement. +message MsgAcknowledgement { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + Packet packet = 1 [(gogoproto.nullable) = false]; + Acknowledgement acknowledgement = 2 [(gogoproto.nullable) = false]; + bytes proof_acked = 3; + ibc.core.client.v1.Height proof_height = 4 [(gogoproto.nullable) = false]; + string signer = 5; +} + +// MsgAcknowledgementResponse defines the Msg/Acknowledgement response type. +message MsgAcknowledgementResponse { + option (gogoproto.goproto_getters) = false; + + ResponseResultType result = 1; +} diff --git a/proto/ibc/core/client/v1/client.proto b/proto/ibc/core/client/v1/client.proto new file mode 100644 index 0000000..118d580 --- /dev/null +++ b/proto/ibc/core/client/v1/client.proto @@ -0,0 +1,66 @@ +syntax = "proto3"; + +package ibc.core.client.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/02-client/types"; + +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; + +// IdentifiedClientState defines a client state with an additional client +// identifier field. +message IdentifiedClientState { + // client identifier + string client_id = 1; + // client state + google.protobuf.Any client_state = 2; +} + +// ConsensusStateWithHeight defines a consensus state with an additional height +// field. +message ConsensusStateWithHeight { + // consensus state height + Height height = 1 [(gogoproto.nullable) = false]; + // consensus state + google.protobuf.Any consensus_state = 2; +} + +// ClientConsensusStates defines all the stored consensus states for a given +// client. +message ClientConsensusStates { + // client identifier + string client_id = 1; + // consensus states and their heights associated with the client + repeated ConsensusStateWithHeight consensus_states = 2 [(gogoproto.nullable) = false]; +} + +// Height is a monotonically increasing data type +// that can be compared against another Height for the purposes of updating and +// freezing clients +// +// Normally the RevisionHeight is incremented at each height while keeping +// RevisionNumber the same. However some consensus algorithms may choose to +// reset the height in certain conditions e.g. hard forks, state-machine +// breaking changes In these cases, the RevisionNumber is incremented so that +// height continues to be monitonically increasing even as the RevisionHeight +// gets reset +// +// Please note that json tags for generated Go code are overridden to explicitly exclude the omitempty jsontag. +// This enforces the Go json marshaller to always emit zero values for both revision_number and revision_height. +message Height { + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + + // the revision that the client is currently on + uint64 revision_number = 1 [(gogoproto.jsontag) = "revision_number"]; + // the height within the given revision + uint64 revision_height = 2 [(gogoproto.jsontag) = "revision_height"]; +} + +// Params defines the set of IBC light client parameters. +message Params { + // allowed_clients defines the list of allowed client state types which can be created + // and interacted with. If a client type is removed from the allowed clients list, usage + // of this client will be disabled until it is added again to the list. + repeated string allowed_clients = 1; +} diff --git a/proto/ibc/core/client/v1/genesis.proto b/proto/ibc/core/client/v1/genesis.proto new file mode 100644 index 0000000..3d6852c --- /dev/null +++ b/proto/ibc/core/client/v1/genesis.proto @@ -0,0 +1,44 @@ +syntax = "proto3"; + +package ibc.core.client.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/02-client/types"; + +import "ibc/core/client/v1/client.proto"; +import "gogoproto/gogo.proto"; + +// GenesisState defines the ibc client submodule's genesis state. +message GenesisState { + // client states with their corresponding identifiers + repeated IdentifiedClientState clients = 1 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "IdentifiedClientStates"]; + // consensus states from each client + repeated ClientConsensusStates clients_consensus = 2 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "ClientsConsensusStates"]; + // metadata from each client + repeated IdentifiedGenesisMetadata clients_metadata = 3 [(gogoproto.nullable) = false]; + Params params = 4 [(gogoproto.nullable) = false]; + // Deprecated: create_localhost has been deprecated. + // The localhost client is automatically created at genesis. + bool create_localhost = 5 [deprecated = true]; + // the sequence for the next generated client identifier + uint64 next_client_sequence = 6; +} + +// GenesisMetadata defines the genesis type for metadata that will be used +// to export all client store keys that are not client or consensus states. +message GenesisMetadata { + option (gogoproto.goproto_getters) = false; + + // store key of metadata without clientID-prefix + bytes key = 1; + // metadata value + bytes value = 2; +} + +// IdentifiedGenesisMetadata has the client metadata with the corresponding +// client id. +message IdentifiedGenesisMetadata { + string client_id = 1; + repeated GenesisMetadata client_metadata = 2 [(gogoproto.nullable) = false]; +} diff --git a/proto/ibc/core/client/v1/query.proto b/proto/ibc/core/client/v1/query.proto new file mode 100644 index 0000000..c989c28 --- /dev/null +++ b/proto/ibc/core/client/v1/query.proto @@ -0,0 +1,263 @@ +syntax = "proto3"; + +package ibc.core.client.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/02-client/types"; + +import "cosmos/base/query/v1beta1/pagination.proto"; +import "cosmos/query/v1/query.proto"; +import "ibc/core/client/v1/client.proto"; +import "ibc/core/commitment/v2/commitment.proto"; +import "google/protobuf/any.proto"; +import "google/api/annotations.proto"; +import "gogoproto/gogo.proto"; + +// Query provides defines the gRPC querier service +service Query { + // ClientState queries an IBC light client. + rpc ClientState(QueryClientStateRequest) returns (QueryClientStateResponse) { + option (google.api.http).get = "/ibc/core/client/v1/client_states/{client_id}"; + } + + // ClientStates queries all the IBC light clients of a chain. + rpc ClientStates(QueryClientStatesRequest) returns (QueryClientStatesResponse) { + option (google.api.http).get = "/ibc/core/client/v1/client_states"; + } + + // ConsensusState queries a consensus state associated with a client state at + // a given height. + rpc ConsensusState(QueryConsensusStateRequest) returns (QueryConsensusStateResponse) { + option (google.api.http).get = "/ibc/core/client/v1/consensus_states/" + "{client_id}/revision/{revision_number}/" + "height/{revision_height}"; + } + + // ConsensusStates queries all the consensus state associated with a given + // client. + rpc ConsensusStates(QueryConsensusStatesRequest) returns (QueryConsensusStatesResponse) { + option (google.api.http).get = "/ibc/core/client/v1/consensus_states/{client_id}"; + } + + // ConsensusStateHeights queries the height of every consensus states associated with a given client. + rpc ConsensusStateHeights(QueryConsensusStateHeightsRequest) returns (QueryConsensusStateHeightsResponse) { + option (google.api.http).get = "/ibc/core/client/v1/consensus_states/{client_id}/heights"; + } + + // Status queries the status of an IBC client. + rpc ClientStatus(QueryClientStatusRequest) returns (QueryClientStatusResponse) { + option (google.api.http).get = "/ibc/core/client/v1/client_status/{client_id}"; + } + + // ClientParams queries all parameters of the ibc client submodule. + rpc ClientParams(QueryClientParamsRequest) returns (QueryClientParamsResponse) { + option (google.api.http).get = "/ibc/core/client/v1/params"; + } + + // ClientCreator queries the creator of a given client. + rpc ClientCreator(QueryClientCreatorRequest) returns (QueryClientCreatorResponse) { + option (google.api.http).get = "/ibc/core/client/v1/client_creator/{client_id}"; + } + + // UpgradedClientState queries an Upgraded IBC light client. + rpc UpgradedClientState(QueryUpgradedClientStateRequest) returns (QueryUpgradedClientStateResponse) { + option (google.api.http).get = "/ibc/core/client/v1/upgraded_client_states"; + } + + // UpgradedConsensusState queries an Upgraded IBC consensus state. + rpc UpgradedConsensusState(QueryUpgradedConsensusStateRequest) returns (QueryUpgradedConsensusStateResponse) { + option (google.api.http).get = "/ibc/core/client/v1/upgraded_consensus_states"; + } + + // VerifyMembership queries an IBC light client for proof verification of a value at a given key path. + rpc VerifyMembership(QueryVerifyMembershipRequest) returns (QueryVerifyMembershipResponse) { + option (cosmos.query.v1.module_query_safe) = true; + option (google.api.http) = { + post: "/ibc/core/client/v1/verify_membership" + body: "*" + }; + } +} + +// QueryClientStateRequest is the request type for the Query/ClientState RPC +// method +message QueryClientStateRequest { + // client state unique identifier + string client_id = 1; +} + +// QueryClientStateResponse is the response type for the Query/ClientState RPC +// method. Besides the client state, it includes a proof and the height from +// which the proof was retrieved. +message QueryClientStateResponse { + // client state associated with the request identifier + google.protobuf.Any client_state = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} + +// QueryClientStatesRequest is the request type for the Query/ClientStates RPC +// method +message QueryClientStatesRequest { + // pagination request + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +// QueryClientStatesResponse is the response type for the Query/ClientStates RPC +// method. +message QueryClientStatesResponse { + // list of stored ClientStates of the chain. + repeated IdentifiedClientState client_states = 1 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "IdentifiedClientStates"]; + // pagination response + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryConsensusStateRequest is the request type for the Query/ConsensusState +// RPC method. Besides the consensus state, it includes a proof and the height +// from which the proof was retrieved. +message QueryConsensusStateRequest { + // client identifier + string client_id = 1; + // consensus state revision number + uint64 revision_number = 2; + // consensus state revision height + uint64 revision_height = 3; + // latest_height overrides the height field and queries the latest stored + // ConsensusState + bool latest_height = 4; +} + +// QueryConsensusStateResponse is the response type for the Query/ConsensusState +// RPC method +message QueryConsensusStateResponse { + // consensus state associated with the client identifier at the given height + google.protobuf.Any consensus_state = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} + +// QueryConsensusStatesRequest is the request type for the Query/ConsensusStates +// RPC method. +message QueryConsensusStatesRequest { + // client identifier + string client_id = 1; + // pagination request + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryConsensusStatesResponse is the response type for the +// Query/ConsensusStates RPC method +message QueryConsensusStatesResponse { + // consensus states associated with the identifier + repeated ConsensusStateWithHeight consensus_states = 1 [(gogoproto.nullable) = false]; + // pagination response + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryConsensusStateHeightsRequest is the request type for Query/ConsensusStateHeights +// RPC method. +message QueryConsensusStateHeightsRequest { + // client identifier + string client_id = 1; + // pagination request + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryConsensusStateHeightsResponse is the response type for the +// Query/ConsensusStateHeights RPC method +message QueryConsensusStateHeightsResponse { + // consensus state heights + repeated Height consensus_state_heights = 1 [(gogoproto.nullable) = false]; + // pagination response + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryClientStatusRequest is the request type for the Query/ClientStatus RPC +// method +message QueryClientStatusRequest { + // client unique identifier + string client_id = 1; +} + +// QueryClientStatusResponse is the response type for the Query/ClientStatus RPC +// method. It returns the current status of the IBC client. +message QueryClientStatusResponse { + string status = 1; +} + +// QueryClientParamsRequest is the request type for the Query/ClientParams RPC +// method. +message QueryClientParamsRequest {} + +// QueryClientParamsResponse is the response type for the Query/ClientParams RPC +// method. +message QueryClientParamsResponse { + // params defines the parameters of the module. + Params params = 1; +} + +// QueryClientCreatorRequest is the request type for the Query/ClientCreator RPC +// method. +message QueryClientCreatorRequest { + // client unique identifier + string client_id = 1; +} + +// QueryClientCreatorResponse is the response type for the Query/ClientCreator RPC +// method. +message QueryClientCreatorResponse { + // creator of the client + string creator = 1; +} + +// QueryUpgradedClientStateRequest is the request type for the +// Query/UpgradedClientState RPC method +message QueryUpgradedClientStateRequest {} + +// QueryUpgradedClientStateResponse is the response type for the +// Query/UpgradedClientState RPC method. +message QueryUpgradedClientStateResponse { + // client state associated with the request identifier + google.protobuf.Any upgraded_client_state = 1; +} + +// QueryUpgradedConsensusStateRequest is the request type for the +// Query/UpgradedConsensusState RPC method +message QueryUpgradedConsensusStateRequest {} + +// QueryUpgradedConsensusStateResponse is the response type for the +// Query/UpgradedConsensusState RPC method. +message QueryUpgradedConsensusStateResponse { + // Consensus state associated with the request identifier + google.protobuf.Any upgraded_consensus_state = 1; +} + +// QueryVerifyMembershipRequest is the request type for the Query/VerifyMembership RPC method +message QueryVerifyMembershipRequest { + // client unique identifier. + string client_id = 1; + // the proof to be verified by the client. + bytes proof = 2; + // the height of the commitment root at which the proof is verified. + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; + // reserved: deprecated field. + reserved 4; + // the value which is proven. + bytes value = 5; + // optional time delay + uint64 time_delay = 6; + // optional block delay + uint64 block_delay = 7; + // the commitment key path. + ibc.core.commitment.v2.MerklePath merkle_path = 8 [(gogoproto.nullable) = false]; +} + +// QueryVerifyMembershipResponse is the response type for the Query/VerifyMembership RPC method +message QueryVerifyMembershipResponse { + // boolean indicating success or failure of proof verification. + bool success = 1; +} diff --git a/proto/ibc/core/client/v1/tx.proto b/proto/ibc/core/client/v1/tx.proto new file mode 100644 index 0000000..6a9aad0 --- /dev/null +++ b/proto/ibc/core/client/v1/tx.proto @@ -0,0 +1,199 @@ +syntax = "proto3"; + +package ibc.core.client.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/02-client/types"; + +import "amino/amino.proto"; +import "cosmos/msg/v1/msg.proto"; +import "cosmos/upgrade/v1beta1/upgrade.proto"; +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; +import "ibc/core/client/v1/client.proto"; + +// Msg defines the ibc/client Msg service. +service Msg { + option (cosmos.msg.v1.service) = true; + + // CreateClient defines a rpc handler method for MsgCreateClient. + rpc CreateClient(MsgCreateClient) returns (MsgCreateClientResponse); + + // UpdateClient defines a rpc handler method for MsgUpdateClient. + rpc UpdateClient(MsgUpdateClient) returns (MsgUpdateClientResponse); + + // UpgradeClient defines a rpc handler method for MsgUpgradeClient. + rpc UpgradeClient(MsgUpgradeClient) returns (MsgUpgradeClientResponse); + + // SubmitMisbehaviour defines a rpc handler method for MsgSubmitMisbehaviour. + rpc SubmitMisbehaviour(MsgSubmitMisbehaviour) returns (MsgSubmitMisbehaviourResponse); + + // RecoverClient defines a rpc handler method for MsgRecoverClient. + rpc RecoverClient(MsgRecoverClient) returns (MsgRecoverClientResponse); + + // IBCSoftwareUpgrade defines a rpc handler method for MsgIBCSoftwareUpgrade. + rpc IBCSoftwareUpgrade(MsgIBCSoftwareUpgrade) returns (MsgIBCSoftwareUpgradeResponse); + + // UpdateClientParams defines a rpc handler method for MsgUpdateParams. + rpc UpdateClientParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); + + // DeleteClientCreator defines a rpc handler method for MsgDeleteClientCreator. + rpc DeleteClientCreator(MsgDeleteClientCreator) returns (MsgDeleteClientCreatorResponse); +} + +// MsgCreateClient defines a message to create an IBC client +message MsgCreateClient { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + // light client state + google.protobuf.Any client_state = 1; + // consensus state associated with the client that corresponds to a given + // height. + google.protobuf.Any consensus_state = 2; + // signer address + string signer = 3; +} + +// MsgCreateClientResponse defines the Msg/CreateClient response type. +message MsgCreateClientResponse { + option (gogoproto.goproto_getters) = false; + + string client_id = 1; +} + +// MsgUpdateClient defines an sdk.Msg to update a IBC client state using +// the given client message. +message MsgUpdateClient { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + // client unique identifier + string client_id = 1; + // client message to update the light client + google.protobuf.Any client_message = 2; + // signer address + string signer = 3; +} + +// MsgUpdateClientResponse defines the Msg/UpdateClient response type. +message MsgUpdateClientResponse {} + +// MsgUpgradeClient defines an sdk.Msg to upgrade an IBC client to a new client +// state +message MsgUpgradeClient { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + // client unique identifier + string client_id = 1; + // upgraded client state + google.protobuf.Any client_state = 2; + // upgraded consensus state, only contains enough information to serve as a + // basis of trust in update logic + google.protobuf.Any consensus_state = 3; + // proof that old chain committed to new client + bytes proof_upgrade_client = 4; + // proof that old chain committed to new consensus state + bytes proof_upgrade_consensus_state = 5; + // signer address + string signer = 6; +} + +// MsgUpgradeClientResponse defines the Msg/UpgradeClient response type. +message MsgUpgradeClientResponse {} + +// MsgSubmitMisbehaviour defines an sdk.Msg type that submits Evidence for +// light client misbehaviour. +// This message has been deprecated. Use MsgUpdateClient instead. +message MsgSubmitMisbehaviour { + option deprecated = true; + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + // client unique identifier + string client_id = 1; + // misbehaviour used for freezing the light client + google.protobuf.Any misbehaviour = 2; + // signer address + string signer = 3; +} + +// MsgSubmitMisbehaviourResponse defines the Msg/SubmitMisbehaviour response +// type. +message MsgSubmitMisbehaviourResponse {} + +// MsgRecoverClient defines the message used to recover a frozen or expired client. +message MsgRecoverClient { + option (amino.name) = "cosmos-sdk/MsgRecoverClient"; + option (gogoproto.goproto_getters) = false; + option (cosmos.msg.v1.signer) = "signer"; + + // the client identifier for the client to be updated if the proposal passes + string subject_client_id = 1; + // the substitute client identifier for the client which will replace the subject + // client + string substitute_client_id = 2; + + // signer address + string signer = 3; +} + +// MsgRecoverClientResponse defines the Msg/RecoverClient response type. +message MsgRecoverClientResponse {} + +// MsgIBCSoftwareUpgrade defines the message used to schedule an upgrade of an IBC client using a v1 governance proposal +message MsgIBCSoftwareUpgrade { + option (cosmos.msg.v1.signer) = "signer"; + cosmos.upgrade.v1beta1.Plan plan = 1 [(gogoproto.nullable) = false]; + // An UpgradedClientState must be provided to perform an IBC breaking upgrade. + // This will make the chain commit to the correct upgraded (self) client state + // before the upgrade occurs, so that connecting chains can verify that the + // new upgraded client is valid by verifying a proof on the previous version + // of the chain. This will allow IBC connections to persist smoothly across + // planned chain upgrades. Correspondingly, the UpgradedClientState field has been + // deprecated in the Cosmos SDK to allow for this logic to exist solely in + // the 02-client module. + google.protobuf.Any upgraded_client_state = 2; + // signer address + string signer = 3; +} + +// MsgIBCSoftwareUpgradeResponse defines the Msg/IBCSoftwareUpgrade response type. +message MsgIBCSoftwareUpgradeResponse {} + +// MsgUpdateParams defines the sdk.Msg type to update the client parameters. +message MsgUpdateParams { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + // signer address + string signer = 1; + + // params defines the client parameters to update. + // + // NOTE: All parameters must be supplied. + Params params = 2 [(gogoproto.nullable) = false]; +} + +// MsgUpdateParamsResponse defines the MsgUpdateParams response type. +message MsgUpdateParamsResponse {} + +// MsgDeleteClientCreator defines a message to delete the client creator of a client +message MsgDeleteClientCreator { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + // client identifier + string client_id = 1; + // signer address + string signer = 2; +} + +// MsgDeleteClientCreatorResponse defines the Msg/DeleteClientCreator response type. +message MsgDeleteClientCreatorResponse {} diff --git a/proto/ibc/core/client/v2/config.proto b/proto/ibc/core/client/v2/config.proto new file mode 100644 index 0000000..688c5fc --- /dev/null +++ b/proto/ibc/core/client/v2/config.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package ibc.core.client.v2; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types"; + +// Config is a **per-client** configuration struct that sets which relayers are allowed to relay v2 IBC messages +// for a given client. +// If it is set, then only relayers in the allow list can send v2 messages +// If it is not set, then the client allows permissionless relaying of v2 messages +message Config { + // allowed_relayers defines the set of allowed relayers for IBC V2 protocol for the given client + repeated string allowed_relayers = 1; +} \ No newline at end of file diff --git a/proto/ibc/core/client/v2/counterparty.proto b/proto/ibc/core/client/v2/counterparty.proto new file mode 100644 index 0000000..c5182f1 --- /dev/null +++ b/proto/ibc/core/client/v2/counterparty.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package ibc.core.client.v2; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types"; + +// CounterpartyInfo defines the key that the counterparty will use to message our client +message CounterpartyInfo { + // merkle prefix key is the prefix that ics provable keys are stored under + repeated bytes merkle_prefix = 1; + // client identifier is the identifier used to send packet messages to our client + string client_id = 2; +} diff --git a/proto/ibc/core/client/v2/genesis.proto b/proto/ibc/core/client/v2/genesis.proto new file mode 100644 index 0000000..1a72b98 --- /dev/null +++ b/proto/ibc/core/client/v2/genesis.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package ibc.core.client.v2; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types"; + +import "ibc/core/client/v2/counterparty.proto"; +import "gogoproto/gogo.proto"; + +// GenesisCounterpartyInfo defines the state associating a client with a counterparty. +message GenesisCounterpartyInfo { + // ClientId is the ID of the given client. + string client_id = 1; + + // CounterpartyInfo is the counterparty info of the given client. + CounterpartyInfo counterparty_info = 2 [(gogoproto.nullable) = false]; +} + +// GenesisState defines the ibc client v2 submodule's genesis state. +message GenesisState { + // counterparty info for each client + repeated GenesisCounterpartyInfo counterparty_infos = 1 [(gogoproto.nullable) = false]; +} diff --git a/proto/ibc/core/client/v2/query.proto b/proto/ibc/core/client/v2/query.proto new file mode 100644 index 0000000..ce95e48 --- /dev/null +++ b/proto/ibc/core/client/v2/query.proto @@ -0,0 +1,46 @@ +syntax = "proto3"; + +package ibc.core.client.v2; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types"; + +import "ibc/core/client/v2/counterparty.proto"; +import "ibc/core/client/v2/config.proto"; +import "google/api/annotations.proto"; + +// Query provides defines the gRPC querier service +service Query { + // CounterpartyInfo queries an IBC light counter party info. + rpc CounterpartyInfo(QueryCounterpartyInfoRequest) returns (QueryCounterpartyInfoResponse) { + option (google.api.http).get = "/ibc/core/client/v2/counterparty_info/{client_id}"; + } + + // Config queries the IBC client v2 configuration for a given client. + rpc Config(QueryConfigRequest) returns (QueryConfigResponse) { + option (google.api.http).get = "/ibc/core/client/v2/config/{client_id}"; + } +} + +// QueryCounterpartyInfoRequest is the request type for the Query/CounterpartyInfo RPC +// method +message QueryCounterpartyInfoRequest { + // client state unique identifier + string client_id = 1; +} + +// QueryCounterpartyInfoResponse is the response type for the +// Query/CounterpartyInfo RPC method. +message QueryCounterpartyInfoResponse { + CounterpartyInfo counterparty_info = 1; +} + +// QueryConfigRequest is the request type for the Query/Config RPC method +message QueryConfigRequest { + // client state unique identifier + string client_id = 1; +} + +// QueryConfigResponse is the response type for the Query/Config RPC method +message QueryConfigResponse { + Config config = 1; +} diff --git a/proto/ibc/core/client/v2/tx.proto b/proto/ibc/core/client/v2/tx.proto new file mode 100644 index 0000000..5ff58f5 --- /dev/null +++ b/proto/ibc/core/client/v2/tx.proto @@ -0,0 +1,58 @@ +syntax = "proto3"; + +package ibc.core.client.v2; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types"; + +import "cosmos/msg/v1/msg.proto"; +import "gogoproto/gogo.proto"; +import "ibc/core/client/v2/config.proto"; + +// Msg defines the ibc/client/v2 Msg service. +service Msg { + option (cosmos.msg.v1.service) = true; + + // RegisterCounterparty defines a rpc handler method for MsgRegisterCounterparty. + rpc RegisterCounterparty(MsgRegisterCounterparty) returns (MsgRegisterCounterpartyResponse); + + // UpdateClientConfig defines a rpc handler method for MsgUpdateClientConfig. + rpc UpdateClientConfig(MsgUpdateClientConfig) returns (MsgUpdateClientConfigResponse); +} + +// MsgRegisterCounterparty defines a message to register a counterparty on a client +message MsgRegisterCounterparty { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + // client identifier + string client_id = 1; + // counterparty merkle prefix + repeated bytes counterparty_merkle_prefix = 2; + // counterparty client identifier + string counterparty_client_id = 3; + // signer address + string signer = 4; +} + +// MsgRegisterCounterpartyResponse defines the Msg/RegisterCounterparty response type. +message MsgRegisterCounterpartyResponse {} + +// MsgUpdateClientConfig defines the sdk.Msg type to update the configuration for a given client +message MsgUpdateClientConfig { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + // client identifier + string client_id = 1; + // allowed relayers + // + // NOTE: All fields in the config must be supplied. + Config config = 2 [(gogoproto.nullable) = false]; + // signer address + string signer = 3; +} + +// MsgUpdateClientConfigResponse defines the MsgUpdateClientConfig response type. +message MsgUpdateClientConfigResponse {} diff --git a/proto/ibc/core/commitment/v1/commitment.proto b/proto/ibc/core/commitment/v1/commitment.proto new file mode 100644 index 0000000..b8d73db --- /dev/null +++ b/proto/ibc/core/commitment/v1/commitment.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; + +package ibc.core.commitment.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types"; + +import "gogoproto/gogo.proto"; +import "cosmos/ics23/v1/proofs.proto"; + +// MerkleRoot defines a merkle root hash. +// In the Cosmos SDK, the AppHash of a block header becomes the root. +message MerkleRoot { + option (gogoproto.goproto_getters) = false; + + bytes hash = 1; +} + +// MerklePrefix is merkle path prefixed to the key. +// The constructed key from the Path and the key will be append(Path.KeyPath, +// append(Path.KeyPrefix, key...)) +message MerklePrefix { + bytes key_prefix = 1; +} + +// MerkleProof is a wrapper type over a chain of CommitmentProofs. +// It demonstrates membership or non-membership for an element or set of +// elements, verifiable in conjunction with a known commitment root. Proofs +// should be succinct. +// MerkleProofs are ordered from leaf-to-root +message MerkleProof { + repeated cosmos.ics23.v1.CommitmentProof proofs = 1; +} diff --git a/proto/ibc/core/commitment/v2/commitment.proto b/proto/ibc/core/commitment/v2/commitment.proto new file mode 100644 index 0000000..216ec40 --- /dev/null +++ b/proto/ibc/core/commitment/v2/commitment.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; + +package ibc.core.commitment.v2; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types/v2"; + +// MerklePath is the path used to verify commitment proofs, which can be an +// arbitrary structured object (defined by a commitment type). +// ICS-23 verification supports membership proofs for nested merkle trees. +// The ICS-24 standard provable keys MUST be stored in the lowest level tree with an optional prefix. +// The IC24 provable tree may then be stored in a higher level tree(s) that hash up to the root hash +// stored in the consensus state of the client. +// Each element of the path represents the key of a merkle tree from the root to the leaf. +// The elements of the path before the final element must be the path to the tree that contains +// the ICS24 provable store. Thus, it should remain constant for all ICS24 proofs. +// The final element of the path is the key of the leaf in the ICS24 provable store, +// Thus IBC core will append the ICS24 path to the final element of the MerklePath +// stored in the counterparty to create the full path to the leaf for proof verification. +// Examples: +// Cosmos SDK: +// The Cosmos SDK commits to a multi-tree where each store is an IAVL tree and all store hashes +// are hashed in a simple merkle tree to get the final root hash. Thus, the MerklePath in the counterparty +// MerklePrefix has the following structure: ["ibc", ""] +// The core IBC handler will append the ICS24 path to the final element of the MerklePath +// like so: ["ibc", "{packetCommitmentPath}"] which will then be used for final verification. +// Ethereum: +// The Ethereum client commits to a single Patricia merkle trie. The ICS24 provable store is managed +// by the smart contract state. Each smart contract has a specific prefix reserved within the global trie. +// Thus the MerklePath in the counterparty is the prefix to the smart contract state in the global trie. +// Since there is only one tree in the commitment structure of ethereum the MerklePath in the counterparty +// MerklePrefix has the following structure: ["IBCCoreContractAddressStoragePrefix"] +// The core IBC handler will append the ICS24 path to the final element of the MerklePath +// like so: ["IBCCoreContractAddressStoragePrefix{packetCommitmentPath}"] which will then be used for final +// verification. Thus the MerklePath in the counterparty MerklePrefix is the nested key path from the root hash of the +// consensus state down to the ICS24 provable store. The IBC handler retrieves the counterparty key path to the ICS24 +// provable store from the MerklePath and appends the ICS24 path to get the final key path to the value being verified +// by the client against the root hash in the client's consensus state. +message MerklePath { + repeated bytes key_path = 1; +} diff --git a/proto/ibc/core/connection/v1/connection.proto b/proto/ibc/core/connection/v1/connection.proto new file mode 100644 index 0000000..296484f --- /dev/null +++ b/proto/ibc/core/connection/v1/connection.proto @@ -0,0 +1,114 @@ +syntax = "proto3"; + +package ibc.core.connection.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types"; + +import "gogoproto/gogo.proto"; +import "ibc/core/commitment/v1/commitment.proto"; + +// ICS03 - Connection Data Structures as defined in +// https://github.com/cosmos/ibc/blob/master/spec/core/ics-003-connection-semantics#data-structures + +// ConnectionEnd defines a stateful object on a chain connected to another +// separate one. +// NOTE: there must only be 2 defined ConnectionEnds to establish +// a connection between two chains. +message ConnectionEnd { + option (gogoproto.goproto_getters) = false; + // client associated with this connection. + string client_id = 1; + // IBC version which can be utilised to determine encodings or protocols for + // channels or packets utilising this connection. + repeated Version versions = 2; + // current state of the connection end. + State state = 3; + // counterparty chain associated with this connection. + Counterparty counterparty = 4 [(gogoproto.nullable) = false]; + // delay period that must pass before a consensus state can be used for + // packet-verification NOTE: delay period logic is only implemented by some + // clients. + uint64 delay_period = 5; +} + +// IdentifiedConnection defines a connection with additional connection +// identifier field. +message IdentifiedConnection { + option (gogoproto.goproto_getters) = false; + // connection identifier. + string id = 1; + // client associated with this connection. + string client_id = 2; + // IBC version which can be utilised to determine encodings or protocols for + // channels or packets utilising this connection + repeated Version versions = 3; + // current state of the connection end. + State state = 4; + // counterparty chain associated with this connection. + Counterparty counterparty = 5 [(gogoproto.nullable) = false]; + // delay period associated with this connection. + uint64 delay_period = 6; +} + +// State defines if a connection is in one of the following states: +// INIT, TRYOPEN, OPEN or UNINITIALIZED. +enum State { + option (gogoproto.goproto_enum_prefix) = false; + + // Default State + STATE_UNINITIALIZED_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "UNINITIALIZED"]; + // A connection end has just started the opening handshake. + STATE_INIT = 1 [(gogoproto.enumvalue_customname) = "INIT"]; + // A connection end has acknowledged the handshake step on the counterparty + // chain. + STATE_TRYOPEN = 2 [(gogoproto.enumvalue_customname) = "TRYOPEN"]; + // A connection end has completed the handshake. + STATE_OPEN = 3 [(gogoproto.enumvalue_customname) = "OPEN"]; +} + +// Counterparty defines the counterparty chain associated with a connection end. +message Counterparty { + option (gogoproto.goproto_getters) = false; + + // identifies the client on the counterparty chain associated with a given + // connection. + string client_id = 1; + // identifies the connection end on the counterparty chain associated with a + // given connection. + string connection_id = 2; + // commitment merkle prefix of the counterparty chain. + ibc.core.commitment.v1.MerklePrefix prefix = 3 [(gogoproto.nullable) = false]; +} + +// ClientPaths define all the connection paths for a client state. +message ClientPaths { + // list of connection paths + repeated string paths = 1; +} + +// ConnectionPaths define all the connection paths for a given client state. +message ConnectionPaths { + // client state unique identifier + string client_id = 1; + // list of connection paths + repeated string paths = 2; +} + +// Version defines the versioning scheme used to negotiate the IBC version in +// the connection handshake. +message Version { + option (gogoproto.goproto_getters) = false; + + // unique version identifier + string identifier = 1; + // list of features compatible with the specified identifier + repeated string features = 2; +} + +// Params defines the set of Connection parameters. +message Params { + // maximum expected time per block (in nanoseconds), used to enforce block delay. This parameter should reflect the + // largest amount of time that the chain might reasonably take to produce the next block under normal operating + // conditions. A safe choice is 3-5x the expected time per block. + uint64 max_expected_time_per_block = 1; +} diff --git a/proto/ibc/core/connection/v1/genesis.proto b/proto/ibc/core/connection/v1/genesis.proto new file mode 100644 index 0000000..f4afdf9 --- /dev/null +++ b/proto/ibc/core/connection/v1/genesis.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package ibc.core.connection.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types"; + +import "gogoproto/gogo.proto"; +import "ibc/core/connection/v1/connection.proto"; + +// GenesisState defines the ibc connection submodule's genesis state. +message GenesisState { + repeated IdentifiedConnection connections = 1 [(gogoproto.nullable) = false]; + repeated ConnectionPaths client_connection_paths = 2 [(gogoproto.nullable) = false]; + // the sequence for the next generated connection identifier + uint64 next_connection_sequence = 3; + Params params = 4 [(gogoproto.nullable) = false]; +} diff --git a/proto/ibc/core/connection/v1/query.proto b/proto/ibc/core/connection/v1/query.proto new file mode 100644 index 0000000..ea0ef4b --- /dev/null +++ b/proto/ibc/core/connection/v1/query.proto @@ -0,0 +1,152 @@ +syntax = "proto3"; + +package ibc.core.connection.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types"; + +import "gogoproto/gogo.proto"; +import "cosmos/base/query/v1beta1/pagination.proto"; +import "ibc/core/client/v1/client.proto"; +import "ibc/core/connection/v1/connection.proto"; +import "google/api/annotations.proto"; +import "google/protobuf/any.proto"; + +// Query provides defines the gRPC querier service +service Query { + // Connection queries an IBC connection end. + rpc Connection(QueryConnectionRequest) returns (QueryConnectionResponse) { + option (google.api.http).get = "/ibc/core/connection/v1/connections/{connection_id}"; + } + + // Connections queries all the IBC connections of a chain. + rpc Connections(QueryConnectionsRequest) returns (QueryConnectionsResponse) { + option (google.api.http).get = "/ibc/core/connection/v1/connections"; + } + + // ClientConnections queries the connection paths associated with a client + // state. + rpc ClientConnections(QueryClientConnectionsRequest) returns (QueryClientConnectionsResponse) { + option (google.api.http).get = "/ibc/core/connection/v1/client_connections/{client_id}"; + } + + // ConnectionClientState queries the client state associated with the + // connection. + rpc ConnectionClientState(QueryConnectionClientStateRequest) returns (QueryConnectionClientStateResponse) { + option (google.api.http).get = "/ibc/core/connection/v1/connections/{connection_id}/client_state"; + } + + // ConnectionConsensusState queries the consensus state associated with the + // connection. + rpc ConnectionConsensusState(QueryConnectionConsensusStateRequest) returns (QueryConnectionConsensusStateResponse) { + option (google.api.http).get = "/ibc/core/connection/v1/connections/{connection_id}/consensus_state/" + "revision/{revision_number}/height/{revision_height}"; + } + + // ConnectionParams queries all parameters of the ibc connection submodule. + rpc ConnectionParams(QueryConnectionParamsRequest) returns (QueryConnectionParamsResponse) { + option (google.api.http).get = "/ibc/core/connection/v1/params"; + } +} + +// QueryConnectionRequest is the request type for the Query/Connection RPC +// method +message QueryConnectionRequest { + // connection unique identifier + string connection_id = 1; +} + +// QueryConnectionResponse is the response type for the Query/Connection RPC +// method. Besides the connection end, it includes a proof and the height from +// which the proof was retrieved. +message QueryConnectionResponse { + // connection associated with the request identifier + ibc.core.connection.v1.ConnectionEnd connection = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} + +// QueryConnectionsRequest is the request type for the Query/Connections RPC +// method +message QueryConnectionsRequest { + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +// QueryConnectionsResponse is the response type for the Query/Connections RPC +// method. +message QueryConnectionsResponse { + // list of stored connections of the chain. + repeated ibc.core.connection.v1.IdentifiedConnection connections = 1; + // pagination response + cosmos.base.query.v1beta1.PageResponse pagination = 2; + // query block height + ibc.core.client.v1.Height height = 3 [(gogoproto.nullable) = false]; +} + +// QueryClientConnectionsRequest is the request type for the +// Query/ClientConnections RPC method +message QueryClientConnectionsRequest { + // client identifier associated with a connection + string client_id = 1; +} + +// QueryClientConnectionsResponse is the response type for the +// Query/ClientConnections RPC method +message QueryClientConnectionsResponse { + // slice of all the connection paths associated with a client. + repeated string connection_paths = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was generated + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} + +// QueryConnectionClientStateRequest is the request type for the +// Query/ConnectionClientState RPC method +message QueryConnectionClientStateRequest { + // connection identifier + string connection_id = 1; +} + +// QueryConnectionClientStateResponse is the response type for the +// Query/ConnectionClientState RPC method +message QueryConnectionClientStateResponse { + // client state associated with the channel + ibc.core.client.v1.IdentifiedClientState identified_client_state = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} + +// QueryConnectionConsensusStateRequest is the request type for the +// Query/ConnectionConsensusState RPC method +message QueryConnectionConsensusStateRequest { + // connection identifier + string connection_id = 1; + uint64 revision_number = 2; + uint64 revision_height = 3; +} + +// QueryConnectionConsensusStateResponse is the response type for the +// Query/ConnectionConsensusState RPC method +message QueryConnectionConsensusStateResponse { + // consensus state associated with the channel + google.protobuf.Any consensus_state = 1; + // client ID associated with the consensus state + string client_id = 2; + // merkle proof of existence + bytes proof = 3; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 4 [(gogoproto.nullable) = false]; +} + +// QueryConnectionParamsRequest is the request type for the Query/ConnectionParams RPC method. +message QueryConnectionParamsRequest {} + +// QueryConnectionParamsResponse is the response type for the Query/ConnectionParams RPC method. +message QueryConnectionParamsResponse { + // params defines the parameters of the module. + Params params = 1; +} diff --git a/proto/ibc/core/connection/v1/tx.proto b/proto/ibc/core/connection/v1/tx.proto new file mode 100644 index 0000000..1cb3f9b --- /dev/null +++ b/proto/ibc/core/connection/v1/tx.proto @@ -0,0 +1,150 @@ +syntax = "proto3"; + +package ibc.core.connection.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types"; + +import "gogoproto/gogo.proto"; +import "cosmos/msg/v1/msg.proto"; +import "google/protobuf/any.proto"; +import "ibc/core/client/v1/client.proto"; +import "ibc/core/connection/v1/connection.proto"; + +// Msg defines the ibc/connection Msg service. +service Msg { + option (cosmos.msg.v1.service) = true; + + // ConnectionOpenInit defines a rpc handler method for MsgConnectionOpenInit. + rpc ConnectionOpenInit(MsgConnectionOpenInit) returns (MsgConnectionOpenInitResponse); + + // ConnectionOpenTry defines a rpc handler method for MsgConnectionOpenTry. + rpc ConnectionOpenTry(MsgConnectionOpenTry) returns (MsgConnectionOpenTryResponse); + + // ConnectionOpenAck defines a rpc handler method for MsgConnectionOpenAck. + rpc ConnectionOpenAck(MsgConnectionOpenAck) returns (MsgConnectionOpenAckResponse); + + // ConnectionOpenConfirm defines a rpc handler method for + // MsgConnectionOpenConfirm. + rpc ConnectionOpenConfirm(MsgConnectionOpenConfirm) returns (MsgConnectionOpenConfirmResponse); + + // UpdateConnectionParams defines a rpc handler method for + // MsgUpdateParams. + rpc UpdateConnectionParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); +} + +// MsgConnectionOpenInit defines the msg sent by an account on Chain A to +// initialize a connection with Chain B. +message MsgConnectionOpenInit { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + string client_id = 1; + Counterparty counterparty = 2 [(gogoproto.nullable) = false]; + Version version = 3; + uint64 delay_period = 4; + string signer = 5; +} + +// MsgConnectionOpenInitResponse defines the Msg/ConnectionOpenInit response +// type. +message MsgConnectionOpenInitResponse {} + +// MsgConnectionOpenTry defines a msg sent by a Relayer to try to open a +// connection on Chain B. +message MsgConnectionOpenTry { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + string client_id = 1; + // Deprecated: this field is unused. Crossing hellos are no longer supported in core IBC. + string previous_connection_id = 2 [deprecated = true]; + // Deprecated: this field is unused. + google.protobuf.Any client_state = 3 [deprecated = true]; + Counterparty counterparty = 4 [(gogoproto.nullable) = false]; + uint64 delay_period = 5; + repeated Version counterparty_versions = 6; + ibc.core.client.v1.Height proof_height = 7 [(gogoproto.nullable) = false]; + // proof of the initialization the connection on Chain A: `UNINITIALIZED -> + // INIT` + bytes proof_init = 8; + // Deprecated: this field is unused. + bytes proof_client = 9 [deprecated = true]; + // Deprecated: this field is unused. + bytes proof_consensus = 10 [deprecated = true]; + // Deprecated: this field is unused. + ibc.core.client.v1.Height consensus_height = 11 [deprecated = true, (gogoproto.nullable) = false]; + string signer = 12; + // Deprecated: this field is unused. + bytes host_consensus_state_proof = 13 [deprecated = true]; +} + +// MsgConnectionOpenTryResponse defines the Msg/ConnectionOpenTry response type. +message MsgConnectionOpenTryResponse {} + +// MsgConnectionOpenAck defines a msg sent by a Relayer to Chain A to +// acknowledge the change of connection state to TRYOPEN on Chain B. +message MsgConnectionOpenAck { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + string connection_id = 1; + string counterparty_connection_id = 2; + Version version = 3; + // Deprecated: this field is unused. + google.protobuf.Any client_state = 4 [deprecated = true]; + ibc.core.client.v1.Height proof_height = 5 [(gogoproto.nullable) = false]; + // proof of the initialization the connection on Chain B: `UNINITIALIZED -> + // TRYOPEN` + bytes proof_try = 6; + // Deprecated: this field is unused. + bytes proof_client = 7 [deprecated = true]; + // Deprecated: this field is unused. + bytes proof_consensus = 8 [deprecated = true]; + // Deprecated: this field is unused. + ibc.core.client.v1.Height consensus_height = 9 [deprecated = true, (gogoproto.nullable) = false]; + string signer = 10; + // Deprecated: this field is unused. + bytes host_consensus_state_proof = 11 [deprecated = true]; +} + +// MsgConnectionOpenAckResponse defines the Msg/ConnectionOpenAck response type. +message MsgConnectionOpenAckResponse {} + +// MsgConnectionOpenConfirm defines a msg sent by a Relayer to Chain B to +// acknowledge the change of connection state to OPEN on Chain A. +message MsgConnectionOpenConfirm { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + string connection_id = 1; + // proof for the change of the connection state on Chain A: `INIT -> OPEN` + bytes proof_ack = 2; + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; + string signer = 4; +} + +// MsgConnectionOpenConfirmResponse defines the Msg/ConnectionOpenConfirm +// response type. +message MsgConnectionOpenConfirmResponse {} + +// MsgUpdateParams defines the sdk.Msg type to update the connection parameters. +message MsgUpdateParams { + option (cosmos.msg.v1.signer) = "signer"; + + option (gogoproto.goproto_getters) = false; + + // signer address + string signer = 1; + + // params defines the connection parameters to update. + // + // NOTE: All parameters must be supplied. + Params params = 2 [(gogoproto.nullable) = false]; +} + +// MsgUpdateParamsResponse defines the MsgUpdateParams response type. +message MsgUpdateParamsResponse {} diff --git a/proto/ibc/core/types/v1/genesis.proto b/proto/ibc/core/types/v1/genesis.proto new file mode 100644 index 0000000..55ab8f7 --- /dev/null +++ b/proto/ibc/core/types/v1/genesis.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +package ibc.core.types.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/types"; + +import "gogoproto/gogo.proto"; +import "ibc/core/client/v1/genesis.proto"; +import "ibc/core/client/v2/genesis.proto"; +import "ibc/core/connection/v1/genesis.proto"; +import "ibc/core/channel/v1/genesis.proto"; +import "ibc/core/channel/v2/genesis.proto"; + +// GenesisState defines the ibc module's genesis state. +message GenesisState { + // ICS002 - Clients genesis state + ibc.core.client.v1.GenesisState client_genesis = 1 [(gogoproto.nullable) = false]; + // ICS003 - Connections genesis state + ibc.core.connection.v1.GenesisState connection_genesis = 2 [(gogoproto.nullable) = false]; + // ICS004 - Channel genesis state + ibc.core.channel.v1.GenesisState channel_genesis = 3 [(gogoproto.nullable) = false]; + // ICS002 - Clients/v2 genesis state + ibc.core.client.v2.GenesisState client_v2_genesis = 4 [(gogoproto.nullable) = false]; + // ICS004 - Channel/v2 genesis state + ibc.core.channel.v2.GenesisState channel_v2_genesis = 5 [(gogoproto.nullable) = false]; +} diff --git a/proto/ibc/lightclients/solomachine/v2/solomachine.proto b/proto/ibc/lightclients/solomachine/v2/solomachine.proto new file mode 100644 index 0000000..c480c15 --- /dev/null +++ b/proto/ibc/lightclients/solomachine/v2/solomachine.proto @@ -0,0 +1,189 @@ +syntax = "proto3"; + +package ibc.lightclients.solomachine.v2; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/core/02-client/migrations/v7"; + +import "ibc/core/connection/v1/connection.proto"; +import "ibc/core/channel/v1/channel.proto"; +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; + +// ClientState defines a solo machine client that tracks the current consensus +// state and if the client is frozen. +message ClientState { + option (gogoproto.goproto_getters) = false; + // latest sequence of the client state + uint64 sequence = 1; + // frozen sequence of the solo machine + bool is_frozen = 2; + ConsensusState consensus_state = 3; + // when set to true, will allow governance to update a solo machine client. + // The client will be unfrozen if it is frozen. + bool allow_update_after_proposal = 4; +} + +// ConsensusState defines a solo machine consensus state. The sequence of a +// consensus state is contained in the "height" key used in storing the +// consensus state. +message ConsensusState { + option (gogoproto.goproto_getters) = false; + // public key of the solo machine + google.protobuf.Any public_key = 1; + // diversifier allows the same public key to be reused across different solo + // machine clients (potentially on different chains) without being considered + // misbehaviour. + string diversifier = 2; + uint64 timestamp = 3; +} + +// Header defines a solo machine consensus header +message Header { + option (gogoproto.goproto_getters) = false; + // sequence to update solo machine public key at + uint64 sequence = 1; + uint64 timestamp = 2; + bytes signature = 3; + google.protobuf.Any new_public_key = 4; + string new_diversifier = 5; +} + +// Misbehaviour defines misbehaviour for a solo machine which consists +// of a sequence and two signatures over different messages at that sequence. +message Misbehaviour { + option (gogoproto.goproto_getters) = false; + string client_id = 1; + uint64 sequence = 2; + SignatureAndData signature_one = 3; + SignatureAndData signature_two = 4; +} + +// SignatureAndData contains a signature and the data signed over to create that +// signature. +message SignatureAndData { + option (gogoproto.goproto_getters) = false; + bytes signature = 1; + DataType data_type = 2; + bytes data = 3; + uint64 timestamp = 4; +} + +// TimestampedSignatureData contains the signature data and the timestamp of the +// signature. +message TimestampedSignatureData { + option (gogoproto.goproto_getters) = false; + bytes signature_data = 1; + uint64 timestamp = 2; +} + +// SignBytes defines the signed bytes used for signature verification. +message SignBytes { + option (gogoproto.goproto_getters) = false; + + uint64 sequence = 1; + uint64 timestamp = 2; + string diversifier = 3; + // type of the data used + DataType data_type = 4; + // marshaled data + bytes data = 5; +} + +// DataType defines the type of solo machine proof being created. This is done +// to preserve uniqueness of different data sign byte encodings. +enum DataType { + option (gogoproto.goproto_enum_prefix) = false; + + // Default State + DATA_TYPE_UNINITIALIZED_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "UNSPECIFIED"]; + // Data type for client state verification + DATA_TYPE_CLIENT_STATE = 1 [(gogoproto.enumvalue_customname) = "CLIENT"]; + // Data type for consensus state verification + DATA_TYPE_CONSENSUS_STATE = 2 [(gogoproto.enumvalue_customname) = "CONSENSUS"]; + // Data type for connection state verification + DATA_TYPE_CONNECTION_STATE = 3 [(gogoproto.enumvalue_customname) = "CONNECTION"]; + // Data type for channel state verification + DATA_TYPE_CHANNEL_STATE = 4 [(gogoproto.enumvalue_customname) = "CHANNEL"]; + // Data type for packet commitment verification + DATA_TYPE_PACKET_COMMITMENT = 5 [(gogoproto.enumvalue_customname) = "PACKETCOMMITMENT"]; + // Data type for packet acknowledgement verification + DATA_TYPE_PACKET_ACKNOWLEDGEMENT = 6 [(gogoproto.enumvalue_customname) = "PACKETACKNOWLEDGEMENT"]; + // Data type for packet receipt absence verification + DATA_TYPE_PACKET_RECEIPT_ABSENCE = 7 [(gogoproto.enumvalue_customname) = "PACKETRECEIPTABSENCE"]; + // Data type for next sequence recv verification + DATA_TYPE_NEXT_SEQUENCE_RECV = 8 [(gogoproto.enumvalue_customname) = "NEXTSEQUENCERECV"]; + // Data type for header verification + DATA_TYPE_HEADER = 9 [(gogoproto.enumvalue_customname) = "HEADER"]; +} + +// HeaderData returns the SignBytes data for update verification. +message HeaderData { + option (gogoproto.goproto_getters) = false; + + // header public key + google.protobuf.Any new_pub_key = 1; + // header diversifier + string new_diversifier = 2; +} + +// ClientStateData returns the SignBytes data for client state verification. +message ClientStateData { + option (gogoproto.goproto_getters) = false; + + bytes path = 1; + google.protobuf.Any client_state = 2; +} + +// ConsensusStateData returns the SignBytes data for consensus state +// verification. +message ConsensusStateData { + option (gogoproto.goproto_getters) = false; + + bytes path = 1; + google.protobuf.Any consensus_state = 2; +} + +// ConnectionStateData returns the SignBytes data for connection state +// verification. +message ConnectionStateData { + option (gogoproto.goproto_getters) = false; + + bytes path = 1; + ibc.core.connection.v1.ConnectionEnd connection = 2; +} + +// ChannelStateData returns the SignBytes data for channel state +// verification. +message ChannelStateData { + option (gogoproto.goproto_getters) = false; + + bytes path = 1; + ibc.core.channel.v1.Channel channel = 2; +} + +// PacketCommitmentData returns the SignBytes data for packet commitment +// verification. +message PacketCommitmentData { + bytes path = 1; + bytes commitment = 2; +} + +// PacketAcknowledgementData returns the SignBytes data for acknowledgement +// verification. +message PacketAcknowledgementData { + bytes path = 1; + bytes acknowledgement = 2; +} + +// PacketReceiptAbsenceData returns the SignBytes data for +// packet receipt absence verification. +message PacketReceiptAbsenceData { + bytes path = 1; +} + +// NextSequenceRecvData returns the SignBytes data for verification of the next +// sequence to be received. +message NextSequenceRecvData { + bytes path = 1; + uint64 next_seq_recv = 2; +} diff --git a/proto/ibc/lightclients/solomachine/v3/solomachine.proto b/proto/ibc/lightclients/solomachine/v3/solomachine.proto new file mode 100644 index 0000000..3d883fe --- /dev/null +++ b/proto/ibc/lightclients/solomachine/v3/solomachine.proto @@ -0,0 +1,99 @@ +syntax = "proto3"; + +package ibc.lightclients.solomachine.v3; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine;solomachine"; + +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; + +// ClientState defines a solo machine client that tracks the current consensus +// state and if the client is frozen. +message ClientState { + option (gogoproto.goproto_getters) = false; + // latest sequence of the client state + uint64 sequence = 1; + // frozen sequence of the solo machine + bool is_frozen = 2; + ConsensusState consensus_state = 3; +} + +// ConsensusState defines a solo machine consensus state. The sequence of a +// consensus state is contained in the "height" key used in storing the +// consensus state. +message ConsensusState { + option (gogoproto.goproto_getters) = false; + // public key of the solo machine + google.protobuf.Any public_key = 1; + // diversifier allows the same public key to be reused across different solo + // machine clients (potentially on different chains) without being considered + // misbehaviour. + string diversifier = 2; + uint64 timestamp = 3; +} + +// Header defines a solo machine consensus header +message Header { + option (gogoproto.goproto_getters) = false; + + uint64 timestamp = 1; + bytes signature = 2; + google.protobuf.Any new_public_key = 3; + string new_diversifier = 4; +} + +// Misbehaviour defines misbehaviour for a solo machine which consists +// of a sequence and two signatures over different messages at that sequence. +message Misbehaviour { + option (gogoproto.goproto_getters) = false; + + uint64 sequence = 1; + SignatureAndData signature_one = 2; + SignatureAndData signature_two = 3; +} + +// SignatureAndData contains a signature and the data signed over to create that +// signature. +message SignatureAndData { + option (gogoproto.goproto_getters) = false; + + bytes signature = 1; + bytes path = 2; + bytes data = 3; + uint64 timestamp = 4; +} + +// TimestampedSignatureData contains the signature data and the timestamp of the +// signature. +message TimestampedSignatureData { + option (gogoproto.goproto_getters) = false; + + bytes signature_data = 1; + uint64 timestamp = 2; +} + +// SignBytes defines the signed bytes used for signature verification. +message SignBytes { + option (gogoproto.goproto_getters) = false; + + // the sequence number + uint64 sequence = 1; + // the proof timestamp + uint64 timestamp = 2; + // the public key diversifier + string diversifier = 3; + // the standardised path bytes + bytes path = 4; + // the marshaled data bytes + bytes data = 5; +} + +// HeaderData returns the SignBytes data for update verification. +message HeaderData { + option (gogoproto.goproto_getters) = false; + + // header public key + google.protobuf.Any new_pub_key = 1; + // header diversifier + string new_diversifier = 2; +} diff --git a/proto/ibc/lightclients/tendermint/v1/tendermint.proto b/proto/ibc/lightclients/tendermint/v1/tendermint.proto new file mode 100644 index 0000000..48b934a --- /dev/null +++ b/proto/ibc/lightclients/tendermint/v1/tendermint.proto @@ -0,0 +1,101 @@ +syntax = "proto3"; + +package ibc.lightclients.tendermint.v1; + +option go_package = "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint;tendermint"; + +import "tendermint/types/validator.proto"; +import "tendermint/types/types.proto"; +import "cosmos/ics23/v1/proofs.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "ibc/core/client/v1/client.proto"; +import "ibc/core/commitment/v1/commitment.proto"; +import "gogoproto/gogo.proto"; + +// ClientState from Tendermint tracks the current validator set, latest height, +// and a possible frozen height. +message ClientState { + option (gogoproto.goproto_getters) = false; + + string chain_id = 1; + Fraction trust_level = 2 [(gogoproto.nullable) = false]; + // duration of the period since the LatestTimestamp during which the + // submitted headers are valid for upgrade + google.protobuf.Duration trusting_period = 3 [(gogoproto.nullable) = false, (gogoproto.stdduration) = true]; + // duration of the staking unbonding period + google.protobuf.Duration unbonding_period = 4 [(gogoproto.nullable) = false, (gogoproto.stdduration) = true]; + // defines how much new (untrusted) header's Time can drift into the future. + google.protobuf.Duration max_clock_drift = 5 [(gogoproto.nullable) = false, (gogoproto.stdduration) = true]; + // Block height when the client was frozen due to a misbehaviour + ibc.core.client.v1.Height frozen_height = 6 [(gogoproto.nullable) = false]; + // Latest height the client was updated to + ibc.core.client.v1.Height latest_height = 7 [(gogoproto.nullable) = false]; + + // Proof specifications used in verifying counterparty state + repeated cosmos.ics23.v1.ProofSpec proof_specs = 8; + + // Path at which next upgraded client will be committed. + // Each element corresponds to the key for a single CommitmentProof in the + // chained proof. NOTE: ClientState must stored under + // `{upgradePath}/{upgradeHeight}/clientState` ConsensusState must be stored + // under `{upgradepath}/{upgradeHeight}/consensusState` For SDK chains using + // the default upgrade module, upgrade_path should be []string{"upgrade", + // "upgradedIBCState"}` + repeated string upgrade_path = 9; + + // allow_update_after_expiry is deprecated + bool allow_update_after_expiry = 10 [deprecated = true]; + // allow_update_after_misbehaviour is deprecated + bool allow_update_after_misbehaviour = 11 [deprecated = true]; +} + +// ConsensusState defines the consensus state from Tendermint. +message ConsensusState { + option (gogoproto.goproto_getters) = false; + + // timestamp that corresponds to the block height in which the ConsensusState + // was stored. + google.protobuf.Timestamp timestamp = 1 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; + // commitment root (i.e app hash) + ibc.core.commitment.v1.MerkleRoot root = 2 [(gogoproto.nullable) = false]; + bytes next_validators_hash = 3 [(gogoproto.casttype) = "github.com/cometbft/cometbft/libs/bytes.HexBytes"]; +} + +// Misbehaviour is a wrapper over two conflicting Headers +// that implements Misbehaviour interface expected by ICS-02 +message Misbehaviour { + option (gogoproto.goproto_getters) = false; + + // ClientID is deprecated + string client_id = 1 [deprecated = true]; + Header header_1 = 2 [(gogoproto.customname) = "Header1"]; + Header header_2 = 3 [(gogoproto.customname) = "Header2"]; +} + +// Header defines the Tendermint client consensus Header. +// It encapsulates all the information necessary to update from a trusted +// Tendermint ConsensusState. The inclusion of TrustedHeight and +// TrustedValidators allows this update to process correctly, so long as the +// ConsensusState for the TrustedHeight exists, this removes race conditions +// among relayers The SignedHeader and ValidatorSet are the new untrusted update +// fields for the client. The TrustedHeight is the height of a stored +// ConsensusState on the client that will be used to verify the new untrusted +// header. The Trusted ConsensusState must be within the unbonding period of +// current time in order to correctly verify, and the TrustedValidators must +// hash to TrustedConsensusState.NextValidatorsHash since that is the last +// trusted validator set at the TrustedHeight. +message Header { + .tendermint.types.SignedHeader signed_header = 1 [(gogoproto.embed) = true]; + + .tendermint.types.ValidatorSet validator_set = 2; + ibc.core.client.v1.Height trusted_height = 3 [(gogoproto.nullable) = false]; + .tendermint.types.ValidatorSet trusted_validators = 4; +} + +// Fraction defines the protobuf message type for tmmath.Fraction that only +// supports positive values. +message Fraction { + uint64 numerator = 1; + uint64 denominator = 2; +} diff --git a/proto/ibc/lightclients/wasm/v1/genesis.proto b/proto/ibc/lightclients/wasm/v1/genesis.proto new file mode 100644 index 0000000..c1b155a --- /dev/null +++ b/proto/ibc/lightclients/wasm/v1/genesis.proto @@ -0,0 +1,20 @@ + +syntax = "proto3"; +package ibc.lightclients.wasm.v1; + +import "gogoproto/gogo.proto"; + +option go_package = "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types"; + +// GenesisState defines 08-wasm's keeper genesis state +message GenesisState { + // uploaded light client wasm contracts + repeated Contract contracts = 1 [(gogoproto.nullable) = false]; +} + +// Contract stores contract code +message Contract { + option (gogoproto.goproto_getters) = false; + // contract byte code + bytes code_bytes = 1; +} diff --git a/proto/ibc/lightclients/wasm/v1/query.proto b/proto/ibc/lightclients/wasm/v1/query.proto new file mode 100644 index 0000000..455b954 --- /dev/null +++ b/proto/ibc/lightclients/wasm/v1/query.proto @@ -0,0 +1,46 @@ +syntax = "proto3"; +package ibc.lightclients.wasm.v1; + +import "google/api/annotations.proto"; +import "cosmos/base/query/v1beta1/pagination.proto"; + +option go_package = "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types"; + +// Query service for wasm module +service Query { + // Get all Wasm checksums + rpc Checksums(QueryChecksumsRequest) returns (QueryChecksumsResponse) { + option (google.api.http).get = "/ibc/lightclients/wasm/v1/checksums"; + } + + // Get Wasm code for given checksum + rpc Code(QueryCodeRequest) returns (QueryCodeResponse) { + option (google.api.http).get = "/ibc/lightclients/wasm/v1/checksums/{checksum}/code"; + } +} + +// QueryChecksumsRequest is the request type for the Query/Checksums RPC method. +message QueryChecksumsRequest { + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +// QueryChecksumsResponse is the response type for the Query/Checksums RPC method. +message QueryChecksumsResponse { + // checksums is a list of the hex encoded checksums of all wasm codes stored. + repeated string checksums = 1; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryCodeRequest is the request type for the Query/Code RPC method. +message QueryCodeRequest { + // checksum is a hex encoded string of the code stored. + string checksum = 1; +} + +// QueryCodeResponse is the response type for the Query/Code RPC method. +message QueryCodeResponse { + bytes data = 1; +} diff --git a/proto/ibc/lightclients/wasm/v1/tx.proto b/proto/ibc/lightclients/wasm/v1/tx.proto new file mode 100644 index 0000000..5a9a53c --- /dev/null +++ b/proto/ibc/lightclients/wasm/v1/tx.proto @@ -0,0 +1,66 @@ +syntax = "proto3"; +package ibc.lightclients.wasm.v1; + +option go_package = "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types"; + +import "cosmos/msg/v1/msg.proto"; + +// Msg defines the ibc/08-wasm Msg service. +service Msg { + option (cosmos.msg.v1.service) = true; + + // StoreCode defines a rpc handler method for MsgStoreCode. + rpc StoreCode(MsgStoreCode) returns (MsgStoreCodeResponse); + + // RemoveChecksum defines a rpc handler method for MsgRemoveChecksum. + rpc RemoveChecksum(MsgRemoveChecksum) returns (MsgRemoveChecksumResponse); + + // MigrateContract defines a rpc handler method for MsgMigrateContract. + rpc MigrateContract(MsgMigrateContract) returns (MsgMigrateContractResponse); +} + +// MsgStoreCode defines the request type for the StoreCode rpc. +message MsgStoreCode { + option (cosmos.msg.v1.signer) = "signer"; + + // signer address + string signer = 1; + // wasm byte code of light client contract. It can be raw or gzip compressed + bytes wasm_byte_code = 2; +} + +// MsgStoreCodeResponse defines the response type for the StoreCode rpc +message MsgStoreCodeResponse { + // checksum is the sha256 hash of the stored code + bytes checksum = 1; +} + +// MsgRemoveChecksum defines the request type for the MsgRemoveChecksum rpc. +message MsgRemoveChecksum { + option (cosmos.msg.v1.signer) = "signer"; + + // signer address + string signer = 1; + // checksum is the sha256 hash to be removed from the store + bytes checksum = 2; +} + +// MsgStoreChecksumResponse defines the response type for the StoreCode rpc +message MsgRemoveChecksumResponse {} + +// MsgMigrateContract defines the request type for the MigrateContract rpc. +message MsgMigrateContract { + option (cosmos.msg.v1.signer) = "signer"; + + // signer address + string signer = 1; + // the client id of the contract + string client_id = 2; + // checksum is the sha256 hash of the new wasm byte code for the contract + bytes checksum = 3; + // the json encoded message to be passed to the contract on migration + bytes msg = 4; +} + +// MsgMigrateContractResponse defines the response type for the MigrateContract rpc +message MsgMigrateContractResponse {} diff --git a/proto/ibc/lightclients/wasm/v1/wasm.proto b/proto/ibc/lightclients/wasm/v1/wasm.proto new file mode 100644 index 0000000..f144b99 --- /dev/null +++ b/proto/ibc/lightclients/wasm/v1/wasm.proto @@ -0,0 +1,43 @@ + +syntax = "proto3"; +package ibc.lightclients.wasm.v1; + +import "gogoproto/gogo.proto"; +import "ibc/core/client/v1/client.proto"; + +option go_package = "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v10/types"; + +// Wasm light client's Client state +message ClientState { + option (gogoproto.goproto_getters) = false; + // bytes encoding the client state of the underlying light client + // implemented as a Wasm contract. + bytes data = 1; + bytes checksum = 2; + ibc.core.client.v1.Height latest_height = 3 [(gogoproto.nullable) = false]; +} + +// Wasm light client's ConsensusState +message ConsensusState { + option (gogoproto.goproto_getters) = false; + // bytes encoding the consensus state of the underlying light client + // implemented as a Wasm contract. + bytes data = 1; +} + +// Wasm light client message (either header(s) or misbehaviour) +message ClientMessage { + option (gogoproto.goproto_getters) = false; + + bytes data = 1; +} + +// Checksums defines a list of all checksums that are stored +// +// Deprecated: This message is deprecated in favor of storing the checksums +// using a Collections.KeySet. +message Checksums { + option deprecated = true; + + repeated bytes checksums = 1; +} diff --git a/releases-decision-tree.png b/releases-decision-tree.png new file mode 100644 index 0000000..309b011 Binary files /dev/null and b/releases-decision-tree.png differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..05da126 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +certifi==2023.7.22 +charset-normalizer==3.3.0 +idna==3.7 +semver==3.0.2 +urllib3==2.0.7 +requests==2.31.0 diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..7e92dfb --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,3 @@ +Generally we should avoid shell scripting and write tests purely in Golang. +However, some libraries are not Goroutine-safe (e.g. app simulations cannot be run safely in parallel), +and OS-native threading may be more efficient for many parallel simulations, so we use shell scripts here. diff --git a/scripts/build-wasm-simapp-docker.sh b/scripts/build-wasm-simapp-docker.sh new file mode 100755 index 0000000..6824e4b --- /dev/null +++ b/scripts/build-wasm-simapp-docker.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -eou pipefail + +# build_wasm_image extracts the correct libwasm version and checksum +# based on the go.mod and builds a docker image with the provided tag. +function build_wasm_image() { + local version="$(scripts/get-libwasm-version.py --get-version)" + local checksum="$(scripts/get-libwasm-version.py --get-checksum)" + docker build . -t "${1}" -f modules/light-clients/08-wasm/Dockerfile --build-arg LIBWASM_VERSION=${version} --build-arg LIBWASM_CHECKSUM=${checksum} +} + +# default to latest if no tag is specified. +TAG="${1:-ibc-go-wasm-simd:latest}" + +build_wasm_image "${TAG}" diff --git a/scripts/compatibility.md b/scripts/compatibility.md new file mode 100644 index 0000000..a94b593 --- /dev/null +++ b/scripts/compatibility.md @@ -0,0 +1,40 @@ +# Compatibility Generation + +## Introduction + +The generate-compatibility-json.py script is used to generate matrices that can be fed into github workflows +as the matrix for the compatibility job. + +This is done by generating a matrix of all possible combinations based on a provided release branch +e.g. release-v10.0.x + +## Matrix Generation + +The generation script is provided a file containing tests, e.g. e2e/tests/transfer/base_test.go and a version under +test. The script will then look at any annotations present in the test in order to determine which tests should +and shouldn't be run. + +## Annotations + +Annotations can be arbitrarily added to the test files in order to control which tests are run. + +The general syntax is: + +`//compatibility:{some_annotation}:{value}` + +In order to apply an annotation to a specific test, the following syntax is used: + +`//compatibility:{TEST_NAME}:{annotation}:{value}` + +The annotations can be present anywhere in the file, typically it is easiest to place the annotations near the test +or test suite they are controlling. + +The following annotations are supported: + +| Annotation | Example Value | Purpose | Example in test file | +|-------------------------|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------| +| from_version | v7.4.0 | Tests should only run if a semver comparison is greater than or equal to this version. Generally this will just be the minimum supported version of ibc-go | // compatibility:from_version:v7.4.0 | +| TEST_NAME:from_versions | v8.4.0,v8.5.0,v10.0.0 | For some tests, they should only be run against a specific release line. This annotation is test case specific, and ensures the test case is run based on the major and minor versions specified. If a version is provided to the tool, and a matching major minor version is not listed, the test will be skipped. | // compatibility:TestScheduleIBCUpgrade_Succeeds:from_versions: v8.4.0,v8.5.0,v10.0.0 | +| TEST_NAME:skip | true | A flag to ensure that this test is not included in the compatibility tests at all. | // compatibility:TestMsgSendTx_SuccessfulSubmitGovProposal:skip:true | + +> Note: if additional control is required, the script can be modified to support additional annotations. diff --git a/scripts/generate-compatibility-json.py b/scripts/generate-compatibility-json.py new file mode 100755 index 0000000..7f61400 --- /dev/null +++ b/scripts/generate-compatibility-json.py @@ -0,0 +1,347 @@ +#!/usr/bin/python3 + +import argparse +import json +import re +from typing import List, Dict + +import requests +import semver + +COMPATIBILITY_FLAG = "compatibility" +FROM_VERSION = "from_version" +# FROM_VERSIONS should be specified on individual tests if the features under test are only supported +# from specific versions of release lines. +FROM_VERSIONS = "from_versions" +# SKIP is a flag that can be used to skip a test from running in compatibility tests. +SKIP = "skip" +# fields will contain arbitrary key value pairs in comments that use the compatibility flag. +FIELDS = "fields" +TEST_SUITE = "test_suite" +TESTS = "tests" +RELEASES_URL = "https://api.github.com/repos/cosmos/ibc-go/releases" +# CHAIN_A should be specified if just chain-a -> chain-b tests should be run. +CHAIN_A = "chain-a" +# CHAIN_B should be specified if just chain-b -> chain-a tests should be run. +CHAIN_B = "chain-b" +# ALL is the default value chosen, and used to indicate that a test matrix which contains. +# both chain-a -> chain-b and chain-b -> chain-a tests should be run. +ALL = "all" +HERMES = "hermes" +DEFAULT_IMAGE = "ghcr.io/cosmos/ibc-go-simd" +RLY = "rly" +# MAX_VERSION is a version string that will be greater than any other semver version. +MAX_VERSION = "9999.999.999" +RELEASE_PREFIX = "release-" + + +def parse_version(version: str) -> semver.Version: + """ + parse_version takes in a version string which can be in multiple formats, + and converts it into a valid semver.Version which can be compared with each other. + The version string is a docker tag. It can be in the format of + - main + - v1.2.3 + - 1.2.3 + - release-v1.2.3 (a tagged release) + - release-v1.2.x (a release branch) + """ + if version.startswith(RELEASE_PREFIX): + # strip off the release prefix and parse the actual version + version = version[len(RELEASE_PREFIX):] + if version.startswith("v"): + # semver versions do not include a "v" prefix. + version = version[1:] + # ensure "main" is always greater than other versions for semver comparison. + if version == "main": + # main will always be the newest release. + version = MAX_VERSION + if version.endswith("x"): + # we always assume the release branch is newer than the previous release. + # for example, release-v9.0.x is newer than release-v9.0.1 + version = version.replace("x", "999", 1) + return semver.Version.parse(version) + + +def parse_args() -> argparse.Namespace: + """Parse command line arguments.""" + parser = argparse.ArgumentParser(description="Generate Compatibility JSON.") + parser.add_argument( + "--file", + help="The test file to look at. Specify the path to a file under e2e/tests", + ) + parser.add_argument( + "--release-version", + default="main", + help="The version to run tests for.", + ) + parser.add_argument( + "--image", + default=DEFAULT_IMAGE, + help=f"Specify the image to be used in the test. Default: {DEFAULT_IMAGE}", + ) + parser.add_argument( + "--relayer", + choices=[HERMES, RLY], + default=HERMES, + help=f"Specify relayer, either {HERMES} or {RLY}", + ) + parser.add_argument( + "--chain", + choices=[CHAIN_A, CHAIN_B, ALL], + default=ALL, + help=f"Specify the chain to run tests for must be one of ({CHAIN_A}, {CHAIN_B}, {ALL})", + ) + return parser.parse_args() + + +def main(): + args = parse_args() + + file_metadata = _build_file_metadata(args.file) + tags = _get_ibc_go_releases(args.release_version) + + # extract the "from_version" annotation specified in the test file. + # this will be the default minimum version that tests will use. + min_version = parse_version(file_metadata[FIELDS][FROM_VERSION]) + + all_versions = [parse_version(v) for v in tags] + + # get all tags between the min and max versions. + tags_to_test = _get_tags_to_test(min_version, all_versions) + + # we also want to test the release version against itself, as well as already released versions. + tags_to_test.append(args.release_version) + + # for each compatibility test run, we are using a single test suite. + test_suite = file_metadata[TEST_SUITE] + + # all possible test files that exist within the suite. + test_functions = file_metadata[TESTS] + + include_entries = [] + + seen = set() + for test in test_functions: + for version in tags_to_test: + if not _test_should_be_run(test, version, file_metadata[FIELDS]): + continue + + _add_test_entries(include_entries, seen, version, test_suite, test, args) + + # compatibility_json is the json object that will be used as the input to a github workflow + # which will expand out into a matrix of tests to run. + compatibility_json = { + "include": include_entries, + } + _validate(compatibility_json) + + # output the json on a single line. This ensures the output is directly passable to a github workflow. + print(json.dumps(compatibility_json), end="") + + +def _add_test_entries(include_entries, seen, version, test_suite, test, args): + """_add_test_entries adds two different test entries to the test_entries list. One for chain-a -> chain-b and one + from chain-b -> chain-a. entries are only added if there are no duplicate entries that have already been added.""" + + # add entry from chain-a -> chain-b + _add_test_entry(include_entries, seen, args.chain, CHAIN_A, args.release_version, version, test_suite, test, + args.relayer, args.image) + # add entry from chain-b -> chain-a + _add_test_entry(include_entries, seen, args.chain, CHAIN_B, version, args.release_version, test_suite, test, + args.relayer, args.image) + + +def _add_test_entry(include_entries, seen, chain_arg, chain, version_a="", version_b="", entrypoint="", test="", + relayer="", + chain_image=""): + """_add_test_entry adds a test entry to the include_entries list if it has not already been added.""" + entry = (version_a, version_b, test, entrypoint, relayer, chain_image) + # ensure we don't add duplicate entries. + if entry not in seen and chain_arg in (chain, ALL): + include_entries.append( + { + "chain-a": version_a, + "chain-b": version_b, + "entrypoint": entrypoint, + "test": test, + "relayer-type": relayer, + "chain-image": chain_image + } + ) + seen.add(entry) + + +def _get_tags_to_test(min_version: semver.Version, all_versions: List[semver.Version]): + """return all tags that are between the min and max versions""" + max_version = max(all_versions) + return ["v" + str(v) for v in all_versions if min_version <= v <= max_version] + + +def _validate(compatibility_json: Dict): + """validates that the generated compatibility json fields will be valid for a github workflow.""" + if "include" not in compatibility_json: + raise ValueError("no include entries found") + + required_keys = frozenset({"chain-a", "chain-b", "entrypoint", "test", "relayer-type", "chain-image"}) + for k in required_keys: + for item in compatibility_json["include"]: + if k not in item: + raise ValueError(f"key {k} not found in {item.keys()}") + if not item[k]: + raise ValueError(f"key {k} must have non empty value") + + if len(compatibility_json["include"]) > 256: + # if this error occurs, split out the workflow into two jobs, one for chain-a and one for chain-b + # using the --chain flag for this script. + raise ValueError(f"maximum number of jobs exceeded (256): {len(compatibility_json['include'])}. " + f"Consider using the --chain argument to split the jobs.") + + +def _test_should_be_run(test_name: str, version: str, file_fields: Dict) -> bool: + """determines if the test should be run. Each test can have its own versions defined, if it has been defined + we can check to see if this test should run, based on the other test parameters. + + If no custom version is specified, the test suite level version is used to determine if the test should run. + """ + + # the test has been explicitly marked to be skipped for compatibility tests. + if file_fields.get(f"{test_name}:{SKIP}") == "true": + return False + + test_semver_version = parse_version(version) + + specified_from_version = file_fields.get(f"{test_name}:{FROM_VERSION}") + if specified_from_version is not None: + # the test has specified a minimum version for which to run. + return test_semver_version >= parse_version(specified_from_version) + + # check to see if there is a list of versions that this test should run for. + specified_versions_str = file_fields.get(f"{test_name}:{FROM_VERSIONS}") + + # no custom minimum version defined for this test + # run it as normal using the from_version specified on the test suite. + if specified_versions_str is None: + # if there is nothing specified for this particular test, we just compare it to the version + # specified at the test suite level. + test_suite_level_version = file_fields[FROM_VERSION] + return test_semver_version >= parse_version(test_suite_level_version) + + specified_versions = specified_versions_str.split(",") + + for v in specified_versions: + semver_v = parse_version(v) + # if the major and minor versions match, there was a specified release line for this version. + # do a comparison on that version to determine if the test should run. + if semver_v.major == test_semver_version.major and semver_v.minor == test_semver_version.minor: + return semver_v >= test_semver_version + + # there was no version defined for this version's release line, but there were versions specified for other release + # lines, we assume we should not be running the test. + return False + + +def _get_ibc_go_releases(from_version: str) -> List[str]: + releases = [] + + from_version_semver = parse_version(from_version) + + # ref: documentation https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#list-releases + resp = requests.get(RELEASES_URL, params={"per_page": 1000}) + resp.raise_for_status() + + response_body = resp.json() + + all_tags = [release["tag_name"] for release in response_body] + for tag in all_tags: + # skip alphas, betas and rcs + if any(t in tag for t in ("beta", "rc", "alpha", "icq")): + continue + try: + semver_tag = parse_version(tag) + except ValueError: # skip any non semver tags. + continue + + # get all versions + if semver_tag <= from_version_semver: + releases.append(tag) + + return releases + + +def _build_file_metadata(file_name: str) -> Dict: + """_build_file_metadata constructs a dictionary of metadata from the test file.""" + file_lines = _load_file_lines(file_name) + return { + TEST_SUITE: _extract_test_suite_function(file_lines), + TESTS: _extract_all_test_functions(file_lines), + FIELDS: _extract_script_fields(file_lines) + } + + +def _extract_test_suite_function(file_lines: List[str]) -> str: + """extracts the name of the test suite function in the file. It is assumed + there is exactly one test suite defined""" + for line in file_lines: + line = line.strip() + if "(t *testing.T)" in line: + return re.search(r"func\s+(.*)\(", line).group(1) + raise ValueError("unable to find test suite in file lines") + + +def _extract_all_test_functions(file_lines: List[str]) -> List[str]: + """creates a list of all test functions that should be run in the compatibility tests + based on the version provided""" + all_tests = [] + for i, line in enumerate(file_lines): + line = line.strip() + + if line.startswith("//"): + continue + + if not _is_test_function(line): + continue + + test_function = _test_function_match(line).group(1) + all_tests.append(test_function) + + return all_tests + + +def _is_test_function(line: str) -> bool: + """determines if the line contains a test function definition.""" + return _test_function_match(line) is not None + + +def _test_function_match(line: str) -> re.Match: + return re.match(r".*\).*(Test.*)\(\)", line) + + +def _extract_script_fields(file_lines: List[str]) -> Dict: + """extract any field in the format of + // compatibility:field_name:value + e.g. + // compatibility:from_version: v7.0.0 + // compatibility:foo: bar + becomes + { + "from_version": "v7.0.0", + "foo": "bar" + } + """ + script_fields = {} + for line in file_lines: + line = line.strip() + match = re.match(rf"//\s*{COMPATIBILITY_FLAG}\s*:\s*(.*):\s*(.*)", line) + if match: + script_fields[match.group(1)] = match.group(2) + return script_fields + + +def _load_file_lines(file_name: str) -> List[str]: + with open(file_name, "r") as f: + return f.readlines() + + +if __name__ == "__main__": + main() diff --git a/scripts/get-libwasm-version.py b/scripts/get-libwasm-version.py new file mode 100755 index 0000000..7981d88 --- /dev/null +++ b/scripts/get-libwasm-version.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +""" +The purpose of this script is to output the version of the libwasm library that is +specified in the go.mod file in the wasm module. + +This should be passed as a build argument to the Dockerfile to determine which static library should +be added. + +usage: get-libwasm-version.py [-h] [--get-version | --no-get-version | --get-checksum | --no-get-checksum] [--wasm-library WASM_LIBRARY] [--wasm-go-mod-path WASM_GO_MOD_PATH] + +Wasm dockerfile utility + +options: + -h, --help show this help message and exit + --get-version, --no-get-version + Get the current version of CosmWasm specified in wasm module. + --get-checksum, --no-get-checksum + Returns the checksum of the libwasm library for the provided version. + --wasm-library WASM_LIBRARY + The name of the library to return the checksum for. + --wasm-go-mod-path WASM_GO_MOD_PATH + The relative path to the go.mod file for the wasm module. +""" + +import argparse +import requests + +WASM_IMPORT = "github.com/CosmWasm/wasmvm/v2" + + +def _get_wasm_version(wasm_go_mod_path: str) -> str: + """get the version of the cosm wasm module from the go.mod file""" + with open(wasm_go_mod_path, "r") as f: + for line in f: + if WASM_IMPORT in line: + return _extract_wasm_version(line) + raise ValueError(f"Could not find {WASM_IMPORT} in {wasm_go_mod_path}") + + +def _get_wasm_lib_checksum(wasm_version: str, wasm_lib: str) -> str: + """get the checksum of the wasm library for the given version""" + checksums_url = f"https://github.com/CosmWasm/wasmvm/releases/download/{wasm_version}/checksums.txt" + resp = requests.get(checksums_url) + resp.raise_for_status() + + for line in resp.text.splitlines(): + if wasm_lib in line: + return line.split(" ")[0].strip() + + raise ValueError(f"Could not find {wasm_lib} in {checksums_url}") + + +def _extract_wasm_version(line: str) -> str: + """extract the version from a line in the go.mod file""" + return line.split(" ")[1].strip() + + +def _parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Wasm dockerfile utility") + + group = parser.add_mutually_exclusive_group() + group.add_argument( + "--get-version", + action=argparse.BooleanOptionalAction, + help="Get the current version of CosmWasm specified in wasm module.", + ) + group.add_argument( + "--get-checksum", + action=argparse.BooleanOptionalAction, + help="Returns the checksum of the libwasm library for the provided version." + ) + parser.add_argument( + "--wasm-library", + default="libwasmvm_muslc.x86_64.a", + help="The name of the library to return the checksum for." + ) + parser.add_argument( + "--wasm-go-mod-path", + default="modules/light-clients/08-wasm/go.mod", + help="The relative path to the go.mod file for the wasm module." + ) + return parser.parse_args() + + +def main(args: argparse.Namespace): + if args.get_version: + version = _get_wasm_version(args.wasm_go_mod_path) + print(version) + return + if args.get_checksum: + checksum = _get_wasm_lib_checksum(_get_wasm_version(args.wasm_go_mod_path), args.wasm_library) + print(checksum) + return + + +if __name__ == "__main__": + main(_parse_args()) diff --git a/scripts/go-lint-all.sh b/scripts/go-lint-all.sh new file mode 100755 index 0000000..fdfca7b --- /dev/null +++ b/scripts/go-lint-all.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +set -e -o pipefail + +REPO_ROOT="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )/.." &> /dev/null && pwd )" +export REPO_ROOT + +lint_module() { + local root="$1" + shift + cd "$(dirname "$root")" && + echo "linting $(grep "^module" go.mod) [$(date -u +"%Y-%m-%dT%H:%M:%S")]" && + golangci-lint run ./... -c "${REPO_ROOT}/.golangci.yml" "$@" +} +export -f lint_module + +find "${REPO_ROOT}" -type f -name go.mod -print0 | + xargs -0 -I{} bash -c 'lint_module "$@"' _ {} "$@" diff --git a/scripts/go-mod-tidy-all.sh b/scripts/go-mod-tidy-all.sh new file mode 100755 index 0000000..03153b3 --- /dev/null +++ b/scripts/go-mod-tidy-all.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -euo pipefail + +for modfile in $(find . -name go.mod); do + echo "Updating $modfile" + DIR=$(dirname $modfile) + (cd $DIR; go mod tidy) +done diff --git a/scripts/go-test-all.py b/scripts/go-test-all.py new file mode 100755 index 0000000..6f22c51 --- /dev/null +++ b/scripts/go-test-all.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +""" +The purpose of this script is to run unit tests for all go modules in the current +directory. It works by recursively searching for all go.mod files in the directory and +subdirectories and then running `go test` on each of them. + +It is not intended to be run directly, but rather to be called by the Makefile. +""" +import os +import subprocess + +def require_env_var(name): + """ Require an environment variable to be set. """ + value = os.environ.get(name, None) + if value is None: + print(f"Error: {name} environment variable is not set") + exit(1) + return value + +def find_go_modules(directory): + """ Find all go.mod files in the current directory and subdirectories. """ + go_mod_files = [] + for root, _, files in os.walk(directory): + if 'go.mod' in files: + go_mod_files.append(root) + return go_mod_files + +def run_tests_for_module(module, *runargs): + """ Run the unit tests for the given module. """ + os.chdir(module) + + print(f"Running unit tests for {module}") + + # add runargs to test_command + test_command = f'go test -mod=readonly {" ".join(runargs)} ./...' + result = subprocess.run(test_command, shell=True) + return result.returncode + + +def run_tests(directory, *runargs): + """ Run the unit tests for all modules in dir. """ + print("Starting unit tests") + + # Find all go.mod files and get their directory names + go_modules = find_go_modules(directory) + + exit_code = 0 + for gomod in sorted(go_modules): + res = run_tests_for_module(gomod, *runargs) + if res != 0: + exit_code = res + exit(exit_code) + +if __name__ == '__main__': + # Get the environment variables given to us by the Makefile + ARGS = require_env_var('ARGS') + EXTRA_ARGS = require_env_var('EXTRA_ARGS') + TEST_PACKAGES = require_env_var('TEST_PACKAGES') + + run_tests(os.getcwd(), ARGS, EXTRA_ARGS, TEST_PACKAGES) diff --git a/scripts/init-simapp.sh b/scripts/init-simapp.sh new file mode 100755 index 0000000..dcaa51d --- /dev/null +++ b/scripts/init-simapp.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +SIMD_BIN=${SIMD_BIN:=$(which simd 2>/dev/null)} + +if [ -z "$SIMD_BIN" ]; then echo "SIMD_BIN is not set. Make sure to run make install before"; exit 1; fi +echo "using $SIMD_BIN" +if [ -d "$($SIMD_BIN config home)" ]; then rm -r $($SIMD_BIN config home); fi +$SIMD_BIN config set client chain-id simapp-1 +$SIMD_BIN config set client keyring-backend test +$SIMD_BIN config set app api.enable true +$SIMD_BIN keys add alice +$SIMD_BIN keys add bob +$SIMD_BIN init test --chain-id simapp-1 +$SIMD_BIN genesis add-genesis-account alice 5000000000stake --keyring-backend test +$SIMD_BIN genesis add-genesis-account bob 5000000000stake --keyring-backend test +$SIMD_BIN genesis gentx alice 1000000stake --chain-id simapp-1 +$SIMD_BIN genesis collect-gentxs \ No newline at end of file diff --git a/scripts/linkify_changelog.py b/scripts/linkify_changelog.py new file mode 100644 index 0000000..27de75e --- /dev/null +++ b/scripts/linkify_changelog.py @@ -0,0 +1,15 @@ +import fileinput +import re + +# This script goes through the provided file, and replaces any " \#", +# with the valid mark down formatted link to it. e.g. +# " [\#number](https://github.com/cosmos/cosmos-sdk/issues/) +# Note that if the number is for a PR, github will auto-redirect you when you click the link. +# It is safe to run the script multiple times in succession. +# +# Example: +# +# $ python ./scripts/linkify_changelog.py CHANGELOG.md +for line in fileinput.input(inplace=1): + line = re.sub(r"\s\\#([0-9]+)", r" [\\#\1](https://github.com/cosmos/ibc-go/issues/\1)", line.rstrip()) + print(line) diff --git a/scripts/protoc-swagger-gen.sh b/scripts/protoc-swagger-gen.sh new file mode 100755 index 0000000..ec491c2 --- /dev/null +++ b/scripts/protoc-swagger-gen.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -eo pipefail + +mkdir -p ./tmp-swagger-gen +cd proto +proto_dirs=$(find ./ -path -prune -o -name '*.proto' -print0 | xargs -0 -n1 dirname | sort | uniq) +for dir in $proto_dirs; do + # generate swagger files (filter query files) + query_file=$(find "${dir}" -maxdepth 1 \( -name 'query.proto' -o -name 'service.proto' \)) + if [[ ! -z "$query_file" ]]; then + buf generate --template buf.gen.swagger.yaml $query_file + fi +done + +cd .. + +# combine swagger files +# uses nodejs package `swagger-combine`. +# all the individual swagger files need to be configured in `config.json` for merging +swagger-combine ./docs/client/config.json -o ./docs/client/swagger-ui/swagger.yaml -f yaml --continueOnConflictingPaths true --includeDefinitions true + +# clean swagger files +rm -rf ./tmp-swagger-gen \ No newline at end of file diff --git a/scripts/protocgen.sh b/scripts/protocgen.sh new file mode 100755 index 0000000..1f0a562 --- /dev/null +++ b/scripts/protocgen.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +set -eo pipefail + +echo "Generating gogo proto code" +cd proto + +buf generate --template buf.gen.gogo.yaml $file + +cd .. + +# move proto files to the right places +cp -r github.com/cosmos/ibc-go/v*/modules/* modules/ +cp -r github.com/cosmos/ibc-go/modules/light-clients/08-wasm/v*/* modules/light-clients/08-wasm/ +# If other modules are added later with protos, they need to be added above here 👆 +rm -rf github.com + +# copy legacy denom trace to internal/types +mv modules/apps/transfer/types/denomtrace.pb.go modules/apps/transfer/internal/types/ + +go mod tidy diff --git a/simapp/README.md b/simapp/README.md new file mode 100644 index 0000000..7c49c95 --- /dev/null +++ b/simapp/README.md @@ -0,0 +1,110 @@ +# `SimApp` + +`SimApp` is a CLI application built using the Cosmos SDK for testing and educational purposes. + +## Running Testnets with `simd` + +Want to spin up a quick testnet with your friends? Follow these steps. Unless stated otherwise, all participants in the testnet must follow through with each step. + +### 1. Download and Setup + +Download IBC-go and unzip it. You can do this manually (via the GitHub UI) or with the git clone command: + +```sh +git clone github.com/cosmos/ibc-go.git +``` + +Next, run this command to build the `simd` binary in the `build` directory: + +```sh +make build +``` + +Use the following command and skip all the next steps to configure your SimApp node: + +```sh +make init-simapp +``` + +If you've run `simd` in the past, you may need to reset your database before starting up a new testnet. You can do that with this command: + +```sh +# you need to provide the moniker and chain ID +$ ./simd init [moniker] --chain-id [chain-id] +``` + +The command should initialize a new working directory at the `~simapp` location. +The `moniker` and `chain-id` can be anything, but you must use the same `chain-id` subsequently. + +### 2. Create a New Key + +Execute this command to create a new key: + +```sh + ./simd keys add [key_name] +``` + +⚠️ The command will create a new key with your chosen name. +Save the output somewhere safe; you'll need the address later. + +### 3. Add Genesis Account + +Add a genesis account to your testnet blockchain: + +```sh +./simd genesis add-genesis-account [key_name] [amount] +``` + +Where `key_name` is the same key name as before, and the `amount` is something like `10000000000000000000000000stake`. + +### 4. Add the Genesis Transaction + +This creates the genesis transaction for your testnet chain: + +```sh +./simd genesis gentx [key_name] [amount] --chain-id [chain-id] +``` + +The amount should be at least `1000000000stake`. Providing too much or too little may result in errors when you start your node. + +### 5. Create the Genesis File + +A participant must create the genesis file `genesis.json` with every participant's transaction. +You can do this by gathering all the Genesis transactions under `config/gentx` and then executing this command: + +```sh +./simd genesis collect-gentxs +``` + +The command will create a new `genesis.json` file that includes data from all the validators. We sometimes call this the "super genesis file" to distinguish it from single-validator genesis files. + +Once you've received the super genesis file, overwrite your original `genesis.json` file with the new super `genesis.json`. + +Modify your `config/config.toml` (in the simapp working directory) to include the other participants as persistent peers: + +```toml +# Comma-separated list of nodes to keep persistent connections to +persistent_peers = "[validator_address]@[ip_address]:[port],[validator_address]@[ip_address]:[port]" +``` + +You can find `validator_address` by executing: + +```sh +./simd comet show-node-id +``` + +The output will be the hex-encoded `validator_address`. The default `port` is 26656. + +### 6. Start the Nodes + +Finally, execute this command to start your nodes: + +```sh +./simd start +``` + +Now you have a small testnet that you can use to try out changes to the Cosmos SDK or CometBFT! + +> ⚠️ NOTE: Sometimes, creating the network through the `collect-gentxs` will fail, and validators will start in a funny state (and then panic). +> +> If this happens, you can try to create and start the network first with a single validator and then add additional validators using a `create-validator` transaction. diff --git a/simapp/ante.go b/simapp/ante.go new file mode 100644 index 0000000..7d2a8e5 --- /dev/null +++ b/simapp/ante.go @@ -0,0 +1,56 @@ +package simapp + +import ( + "errors" + + circuitante "cosmossdk.io/x/circuit/ante" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + + ibcante "github.com/cosmos/ibc-go/v10/modules/core/ante" + "github.com/cosmos/ibc-go/v10/modules/core/keeper" +) + +// HandlerOptions are the options required for constructing a default SDK AnteHandler. +type HandlerOptions struct { + ante.HandlerOptions + CircuitKeeper circuitante.CircuitBreaker + IBCKeeper *keeper.Keeper +} + +// NewAnteHandler returns an AnteHandler that checks and increments sequence +// numbers, checks signatures & account numbers, and deducts fees from the first +// signer. +func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { + if options.AccountKeeper == nil { + return nil, errors.New("account keeper is required for ante builder") + } + + if options.BankKeeper == nil { + return nil, errors.New("bank keeper is required for ante builder") + } + + if options.SignModeHandler == nil { + return nil, errors.New("sign mode handler is required for ante builder") + } + + anteDecorators := []sdk.AnteDecorator{ + ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first + circuitante.NewCircuitBreakerDecorator(options.CircuitKeeper), + ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker), + ante.NewValidateBasicDecorator(), + ante.NewTxTimeoutHeightDecorator(), + ante.NewValidateMemoDecorator(options.AccountKeeper), + ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), + ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker), + ante.NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators + ante.NewValidateSigCountDecorator(options.AccountKeeper), + ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer), + ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), + ante.NewIncrementSequenceDecorator(options.AccountKeeper), + ibcante.NewRedundantRelayDecorator(options.IBCKeeper), + } + + return sdk.ChainAnteDecorators(anteDecorators...), nil +} diff --git a/simapp/app.go b/simapp/app.go new file mode 100644 index 0000000..80f5120 --- /dev/null +++ b/simapp/app.go @@ -0,0 +1,883 @@ +package simapp + +import ( + "encoding/json" + "fmt" + "io" + "maps" + "os" + "path/filepath" + + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/gogoproto/proto" + "github.com/spf13/cast" + + autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" + reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1" + "cosmossdk.io/client/v2/autocli" + "cosmossdk.io/core/appmodule" + "cosmossdk.io/log" + storetypes "cosmossdk.io/store/types" + "cosmossdk.io/x/circuit" + circuitkeeper "cosmossdk.io/x/circuit/keeper" + circuittypes "cosmossdk.io/x/circuit/types" + "cosmossdk.io/x/evidence" + evidencekeeper "cosmossdk.io/x/evidence/keeper" + evidencetypes "cosmossdk.io/x/evidence/types" + "cosmossdk.io/x/feegrant" + feegrantkeeper "cosmossdk.io/x/feegrant/keeper" + feegrantmodule "cosmossdk.io/x/feegrant/module" + "cosmossdk.io/x/tx/signing" + "cosmossdk.io/x/upgrade" + upgradekeeper "cosmossdk.io/x/upgrade/keeper" + upgradetypes "cosmossdk.io/x/upgrade/types" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/grpc/cmtservice" + nodeservice "github.com/cosmos/cosmos-sdk/client/grpc/node" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/address" + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/runtime" + runtimeservices "github.com/cosmos/cosmos-sdk/runtime/services" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/server/api" + "github.com/cosmos/cosmos-sdk/server/config" + servertypes "github.com/cosmos/cosmos-sdk/server/types" + "github.com/cosmos/cosmos-sdk/std" + "github.com/cosmos/cosmos-sdk/testutil/testdata/testpb" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/types/msgservice" + "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + "github.com/cosmos/cosmos-sdk/x/auth/posthandler" + authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/auth/vesting" + vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + "github.com/cosmos/cosmos-sdk/x/authz" + authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" + authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module" + "github.com/cosmos/cosmos-sdk/x/bank" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/cosmos-sdk/x/consensus" + consensusparamkeeper "github.com/cosmos/cosmos-sdk/x/consensus/keeper" + consensusparamtypes "github.com/cosmos/cosmos-sdk/x/consensus/types" + "github.com/cosmos/cosmos-sdk/x/crisis" + crisiskeeper "github.com/cosmos/cosmos-sdk/x/crisis/keeper" + crisistypes "github.com/cosmos/cosmos-sdk/x/crisis/types" + distr "github.com/cosmos/cosmos-sdk/x/distribution" + distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/cosmos/cosmos-sdk/x/genutil" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + "github.com/cosmos/cosmos-sdk/x/gov" + govclient "github.com/cosmos/cosmos-sdk/x/gov/client" + govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/cosmos/cosmos-sdk/x/group" + groupkeeper "github.com/cosmos/cosmos-sdk/x/group/keeper" + groupmodule "github.com/cosmos/cosmos-sdk/x/group/module" + "github.com/cosmos/cosmos-sdk/x/mint" + mintkeeper "github.com/cosmos/cosmos-sdk/x/mint/keeper" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + "github.com/cosmos/cosmos-sdk/x/params" + paramsclient "github.com/cosmos/cosmos-sdk/x/params/client" + paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/cosmos/cosmos-sdk/x/slashing" + slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + abci "github.com/cometbft/cometbft/abci/types" + + ica "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts" + icacontroller "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller" + icacontrollerkeeper "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/keeper" + icacontrollertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + icahost "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host" + icahostkeeper "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/keeper" + icahosttypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer" + ibctransferkeeper "github.com/cosmos/ibc-go/v10/modules/apps/transfer/keeper" + ibctransfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + ibc "github.com/cosmos/ibc-go/v10/modules/core" + ibcclienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + ibcconnectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibckeeper "github.com/cosmos/ibc-go/v10/modules/core/keeper" + solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" +) + +const appName = "SimApp" + +var ( + // DefaultNodeHome default home directories for the application daemon + DefaultNodeHome string + + // module account permissions + maccPerms = map[string][]string{ + authtypes.FeeCollectorName: nil, + distrtypes.ModuleName: nil, + minttypes.ModuleName: {authtypes.Minter}, + stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking}, + stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking}, + govtypes.ModuleName: {authtypes.Burner}, + ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + icatypes.ModuleName: nil, + } +) + +var ( + _ runtime.AppI = (*SimApp)(nil) + _ servertypes.Application = (*SimApp)(nil) +) + +// SimApp extends an ABCI application, but with most of its parameters exported. +// They are exported for convenience in creating helper functions. +type SimApp struct { + *baseapp.BaseApp + legacyAmino *codec.LegacyAmino + appCodec codec.Codec + txConfig client.TxConfig + interfaceRegistry types.InterfaceRegistry + + // keys to access the substores + keys map[string]*storetypes.KVStoreKey + tkeys map[string]*storetypes.TransientStoreKey + + // keepers + AccountKeeper authkeeper.AccountKeeper + BankKeeper bankkeeper.Keeper + StakingKeeper *stakingkeeper.Keeper + SlashingKeeper slashingkeeper.Keeper + MintKeeper mintkeeper.Keeper + DistrKeeper distrkeeper.Keeper + GovKeeper govkeeper.Keeper + CrisisKeeper *crisiskeeper.Keeper + UpgradeKeeper *upgradekeeper.Keeper + ParamsKeeper paramskeeper.Keeper + AuthzKeeper authzkeeper.Keeper + IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly + ICAControllerKeeper icacontrollerkeeper.Keeper + ICAHostKeeper icahostkeeper.Keeper + EvidenceKeeper evidencekeeper.Keeper + TransferKeeper ibctransferkeeper.Keeper + FeeGrantKeeper feegrantkeeper.Keeper + GroupKeeper groupkeeper.Keeper + ConsensusParamsKeeper consensusparamkeeper.Keeper + CircuitKeeper circuitkeeper.Keeper + + // the module manager + ModuleManager *module.Manager + BasicModuleManager module.BasicManager + + // simulation manager + simulationManager *module.SimulationManager + + // module configurator + configurator module.Configurator +} + +func init() { + userHomeDir, err := os.UserHomeDir() + if err != nil { + panic(err) + } + + DefaultNodeHome = filepath.Join(userHomeDir, ".simapp") +} + +// NewSimApp returns a reference to an initialized SimApp. +func NewSimApp( + logger log.Logger, + db dbm.DB, + traceStore io.Writer, + loadLatest bool, + appOpts servertypes.AppOptions, + baseAppOptions ...func(*baseapp.BaseApp), +) *SimApp { + interfaceRegistry, _ := types.NewInterfaceRegistryWithOptions(types.InterfaceRegistryOptions{ + ProtoFiles: proto.HybridResolver, + SigningOptions: signing.Options{ + AddressCodec: address.Bech32Codec{ + Bech32Prefix: sdk.GetConfig().GetBech32AccountAddrPrefix(), + }, + ValidatorAddressCodec: address.Bech32Codec{ + Bech32Prefix: sdk.GetConfig().GetBech32ValidatorAddrPrefix(), + }, + }, + }) + appCodec := codec.NewProtoCodec(interfaceRegistry) + legacyAmino := codec.NewLegacyAmino() + txConfig := authtx.NewTxConfig(appCodec, authtx.DefaultSignModes) + + std.RegisterLegacyAminoCodec(legacyAmino) + std.RegisterInterfaces(interfaceRegistry) + + // Below we could construct and set an application specific mempool and + // ABCI 1.0 PrepareProposal and ProcessProposal handlers. These defaults are + // already set in the SDK's BaseApp, this shows an example of how to override + // them. + // + // Example: + // + // bApp := baseapp.NewBaseApp(...) + // nonceMempool := mempool.NewSenderNonceMempool() + // abciPropHandler := NewDefaultProposalHandler(nonceMempool, bApp) + // + // bApp.SetMempool(nonceMempool) + // bApp.SetPrepareProposal(abciPropHandler.PrepareProposalHandler()) + // bApp.SetProcessProposal(abciPropHandler.ProcessProposalHandler()) + // + // Alternatively, you can construct BaseApp options, append those to + // baseAppOptions and pass them to NewBaseApp. + // + // Example: + // + // prepareOpt = func(app *baseapp.BaseApp) { + // abciPropHandler := baseapp.NewDefaultProposalHandler(nonceMempool, app) + // app.SetPrepareProposal(abciPropHandler.PrepareProposalHandler()) + // } + // baseAppOptions = append(baseAppOptions, prepareOpt) + + bApp := baseapp.NewBaseApp(appName, logger, db, txConfig.TxDecoder(), baseAppOptions...) + bApp.SetCommitMultiStoreTracer(traceStore) + bApp.SetVersion(version.Version) + bApp.SetInterfaceRegistry(interfaceRegistry) + bApp.SetTxEncoder(txConfig.TxEncoder()) + + keys := storetypes.NewKVStoreKeys( + authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, crisistypes.StoreKey, + minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey, + govtypes.StoreKey, group.StoreKey, paramstypes.StoreKey, ibcexported.StoreKey, upgradetypes.StoreKey, feegrant.StoreKey, + evidencetypes.StoreKey, ibctransfertypes.StoreKey, icacontrollertypes.StoreKey, icahosttypes.StoreKey, + authzkeeper.StoreKey, consensusparamtypes.StoreKey, circuittypes.StoreKey, + ) + + // register streaming services + if err := bApp.RegisterStreamingServices(appOpts, keys); err != nil { + panic(err) + } + + tkeys := storetypes.NewTransientStoreKeys(paramstypes.TStoreKey) + + app := &SimApp{ + BaseApp: bApp, + legacyAmino: legacyAmino, + appCodec: appCodec, + txConfig: txConfig, + interfaceRegistry: interfaceRegistry, + keys: keys, + tkeys: tkeys, + } + + app.ParamsKeeper = initParamsKeeper(appCodec, legacyAmino, keys[paramstypes.StoreKey], tkeys[paramstypes.TStoreKey]) + + // set the BaseApp's parameter store + app.ConsensusParamsKeeper = consensusparamkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[consensusparamtypes.StoreKey]), authtypes.NewModuleAddress(govtypes.ModuleName).String(), runtime.EventService{}) + bApp.SetParamStore(app.ConsensusParamsKeeper.ParamsStore) + + // SDK module keepers + + // add keepers + app.AccountKeeper = authkeeper.NewAccountKeeper(appCodec, runtime.NewKVStoreService(keys[authtypes.StoreKey]), authtypes.ProtoBaseAccount, maccPerms, authcodec.NewBech32Codec(sdk.Bech32MainPrefix), sdk.Bech32MainPrefix, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + + app.BankKeeper = bankkeeper.NewBaseKeeper( + appCodec, + runtime.NewKVStoreService(keys[banktypes.StoreKey]), + app.AccountKeeper, + BlockedAddresses(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + logger, + ) + app.StakingKeeper = stakingkeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[stakingtypes.StoreKey]), app.AccountKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), authcodec.NewBech32Codec(sdk.Bech32PrefixValAddr), authcodec.NewBech32Codec(sdk.Bech32PrefixConsAddr), + ) + app.MintKeeper = mintkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[minttypes.StoreKey]), app.StakingKeeper, app.AccountKeeper, app.BankKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + + app.DistrKeeper = distrkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[distrtypes.StoreKey]), app.AccountKeeper, app.BankKeeper, app.StakingKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + + app.SlashingKeeper = slashingkeeper.NewKeeper( + appCodec, legacyAmino, runtime.NewKVStoreService(keys[slashingtypes.StoreKey]), app.StakingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + invCheckPeriod := cast.ToUint(appOpts.Get(server.FlagInvCheckPeriod)) + app.CrisisKeeper = crisiskeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[crisistypes.StoreKey]), invCheckPeriod, + app.BankKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String(), app.AccountKeeper.AddressCodec()) + + app.FeeGrantKeeper = feegrantkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[feegrant.StoreKey]), app.AccountKeeper) + + // register the staking hooks + // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks + app.StakingKeeper.SetHooks( + stakingtypes.NewMultiStakingHooks(app.DistrKeeper.Hooks(), app.SlashingKeeper.Hooks()), + ) + + app.CircuitKeeper = circuitkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[circuittypes.StoreKey]), authtypes.NewModuleAddress(govtypes.ModuleName).String(), app.AccountKeeper.AddressCodec()) + app.SetCircuitBreaker(&app.CircuitKeeper) + + app.AuthzKeeper = authzkeeper.NewKeeper(runtime.NewKVStoreService(keys[authzkeeper.StoreKey]), appCodec, app.MsgServiceRouter(), app.AccountKeeper) + + groupConfig := group.DefaultConfig() + /* + Example of setting group params: + groupConfig.MaxMetadataLen = 1000 + */ + app.GroupKeeper = groupkeeper.NewKeeper(keys[group.StoreKey], appCodec, app.MsgServiceRouter(), app.AccountKeeper, groupConfig) + + // get skipUpgradeHeights from the app options + skipUpgradeHeights := map[int64]bool{} + for _, h := range cast.ToIntSlice(appOpts.Get(server.FlagUnsafeSkipUpgrades)) { + skipUpgradeHeights[int64(h)] = true + } + homePath := cast.ToString(appOpts.Get(flags.FlagHome)) + // set the governance module account as the authority for conducting upgrades + app.UpgradeKeeper = upgradekeeper.NewKeeper(skipUpgradeHeights, runtime.NewKVStoreService(keys[upgradetypes.StoreKey]), appCodec, homePath, app.BaseApp, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + + app.IBCKeeper = ibckeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[ibcexported.StoreKey]), app.GetSubspace(ibcexported.ModuleName), app.UpgradeKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + govConfig := govtypes.DefaultConfig() + /* + Example of setting gov params: + govConfig.MaxMetadataLen = 10000 + */ + govKeeper := govkeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[govtypes.StoreKey]), app.AccountKeeper, app.BankKeeper, + app.StakingKeeper, app.DistrKeeper, app.MsgServiceRouter(), govConfig, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + app.GovKeeper = *govKeeper.SetHooks( + govtypes.NewMultiGovHooks( + // register the governance hooks + ), + ) + + // ICA Controller keeper + app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[icacontrollertypes.StoreKey]), app.GetSubspace(icacontrollertypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeper, + app.MsgServiceRouter(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // ICA Host keeper + app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[icahosttypes.StoreKey]), app.GetSubspace(icahosttypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeper, app.AccountKeeper, + app.MsgServiceRouter(), app.GRPCQueryRouter(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // Create IBC Router + ibcRouter := porttypes.NewRouter() + + // Middleware Stacks + + // Create Transfer Keeper + app.TransferKeeper = ibctransferkeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]), app.GetSubspace(ibctransfertypes.ModuleName), + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeper, + app.MsgServiceRouter(), + app.AccountKeeper, app.BankKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // Create Transfer Stack + // SendPacket, since it is originating from the application to core IBC: + // transferKeeper.SendPacket -> channel.SendPacket + + // RecvPacket, message that originates from core IBC and goes down to app, the flow is the other way + // channel.RecvPacket -> transfer.OnRecvPacket + + // transfer stack contains (from top to bottom): + // - Transfer + + // create IBC module from bottom to top of stack + var transferStack porttypes.IBCModule = transfer.NewIBCModule(app.TransferKeeper) + + // Add transfer stack to IBC Router + ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) + + // Create Interchain Accounts Stack + // SendPacket, since it is originating from the application to core IBC: + // icaControllerKeeper.SendTx -> channel.SendPacket + var icaControllerStack porttypes.IBCModule = icacontroller.NewIBCMiddleware(app.ICAControllerKeeper) + + // RecvPacket, message that originates from core IBC and goes down to app, the flow is: + // channel.RecvPacket -> icaHost.OnRecvPacket + + var icaHostStack porttypes.IBCModule = icahost.NewIBCModule(app.ICAHostKeeper) + + // Add host, controller & ica auth modules to IBC router + ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icahosttypes.SubModuleName, icaHostStack) + + // Set the IBC Routers + app.IBCKeeper.SetRouter(ibcRouter) + + clientKeeper := app.IBCKeeper.ClientKeeper + storeProvider := clientKeeper.GetStoreProvider() + + tmLightClientModule := ibctm.NewLightClientModule(appCodec, storeProvider) + clientKeeper.AddRoute(ibctm.ModuleName, &tmLightClientModule) + + smLightClientModule := solomachine.NewLightClientModule(appCodec, storeProvider) + clientKeeper.AddRoute(solomachine.ModuleName, &smLightClientModule) + + // create evidence keeper with router + evidenceKeeper := evidencekeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[evidencetypes.StoreKey]), app.StakingKeeper, app.SlashingKeeper, app.AccountKeeper.AddressCodec(), runtime.ProvideCometInfoService(), + ) + // If evidence needs to be handled for the app, set routes in router here and seal + app.EvidenceKeeper = *evidenceKeeper + + // **** Module Options **** + + // NOTE: we may consider parsing `appOpts` inside module constructors. For the moment + // we prefer to be more strict in what arguments the modules expect. + skipGenesisInvariants := cast.ToBool(appOpts.Get(crisis.FlagSkipGenesisInvariants)) + + // NOTE: Any module instantiated in the module manager that is later modified + // must be passed by reference here. + app.ModuleManager = module.NewManager( + genutil.NewAppModule( + app.AccountKeeper, app.StakingKeeper, app, + txConfig, + ), + auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, app.GetSubspace(authtypes.ModuleName)), + vesting.NewAppModule(app.AccountKeeper, app.BankKeeper), + bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper, app.GetSubspace(banktypes.ModuleName)), + crisis.NewAppModule(app.CrisisKeeper, skipGenesisInvariants, app.GetSubspace(crisistypes.ModuleName)), + feegrantmodule.NewAppModule(appCodec, app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, app.interfaceRegistry), + gov.NewAppModule(appCodec, &app.GovKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(govtypes.ModuleName)), + mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper, nil, app.GetSubspace(minttypes.ModuleName)), + slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(slashingtypes.ModuleName), app.interfaceRegistry), + distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(distrtypes.ModuleName)), + staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName)), + upgrade.NewAppModule(app.UpgradeKeeper, app.AccountKeeper.AddressCodec()), + evidence.NewAppModule(app.EvidenceKeeper), + params.NewAppModule(app.ParamsKeeper), + authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), + groupmodule.NewAppModule(appCodec, app.GroupKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), + consensus.NewAppModule(appCodec, app.ConsensusParamsKeeper), + circuit.NewAppModule(appCodec, app.CircuitKeeper), + + // IBC modules + ibc.NewAppModule(app.IBCKeeper), + transfer.NewAppModule(app.TransferKeeper), + ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper), + + // IBC light clients + ibctm.NewAppModule(tmLightClientModule), + solomachine.NewAppModule(smLightClientModule), + ) + + // BasicModuleManager defines the module BasicManager is in charge of setting up basic, + // non-dependant module elements, such as codec registration and genesis verification. + // By default it is composed of all the module from the module manager. + // Additionally, app module basics can be overwritten by passing them as argument. + app.BasicModuleManager = module.NewBasicManagerFromManager( + app.ModuleManager, + map[string]module.AppModuleBasic{ + genutiltypes.ModuleName: genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator), + govtypes.ModuleName: gov.NewAppModuleBasic( + []govclient.ProposalHandler{ + paramsclient.ProposalHandler, + }, + ), + }) + app.BasicModuleManager.RegisterLegacyAminoCodec(legacyAmino) + app.BasicModuleManager.RegisterInterfaces(interfaceRegistry) + + // NOTE: upgrade module is required to be prioritized + app.ModuleManager.SetOrderPreBlockers( + upgradetypes.ModuleName, + authtypes.ModuleName, + ) + + // During begin block slashing happens after distr.BeginBlocker so that + // there is nothing left over in the validator fee pool, so as to keep the + // CanWithdrawInvariant invariant. + // NOTE: staking module is required if HistoricalEntries param > 0 + app.ModuleManager.SetOrderBeginBlockers( + minttypes.ModuleName, + distrtypes.ModuleName, + slashingtypes.ModuleName, + evidencetypes.ModuleName, + stakingtypes.ModuleName, + ibcexported.ModuleName, + ibctransfertypes.ModuleName, + genutiltypes.ModuleName, + authz.ModuleName, + icatypes.ModuleName, + ) + app.ModuleManager.SetOrderEndBlockers( + crisistypes.ModuleName, + govtypes.ModuleName, + stakingtypes.ModuleName, + ibcexported.ModuleName, + ibctransfertypes.ModuleName, + genutiltypes.ModuleName, + feegrant.ModuleName, + icatypes.ModuleName, + group.ModuleName, + ) + + // NOTE: The genutils module must occur after staking so that pools are + // properly initialized with tokens from genesis accounts. + // NOTE: The genutils module must also occur after auth so that it can access the params from auth. + genesisModuleOrder := []string{ + authtypes.ModuleName, + banktypes.ModuleName, distrtypes.ModuleName, stakingtypes.ModuleName, + slashingtypes.ModuleName, govtypes.ModuleName, minttypes.ModuleName, crisistypes.ModuleName, + ibcexported.ModuleName, genutiltypes.ModuleName, evidencetypes.ModuleName, authz.ModuleName, ibctransfertypes.ModuleName, + icatypes.ModuleName, feegrant.ModuleName, paramstypes.ModuleName, upgradetypes.ModuleName, + vestingtypes.ModuleName, group.ModuleName, consensusparamtypes.ModuleName, circuittypes.ModuleName, + } + app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...) + app.ModuleManager.SetOrderExportGenesis(genesisModuleOrder...) + + // Uncomment if you want to set a custom migration order here. + // app.ModuleManager.SetOrderMigrations(custom order) + + app.ModuleManager.RegisterInvariants(app.CrisisKeeper) + app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter()) + err := app.ModuleManager.RegisterServices(app.configurator) + if err != nil { + panic(err) + } + + // registerUpgradeHandlers is used for registering any on-chain upgrades. + // Make sure it's called after `app.ModuleManager` and `app.configurator` are set. + app.registerUpgradeHandlers() + + autocliv1.RegisterQueryServer(app.GRPCQueryRouter(), runtimeservices.NewAutoCLIQueryService(app.ModuleManager.Modules)) + + reflectionSvc, err := runtimeservices.NewReflectionService() + if err != nil { + panic(err) + } + reflectionv1.RegisterReflectionServiceServer(app.GRPCQueryRouter(), reflectionSvc) + + // add test gRPC service for testing gRPC queries in isolation + testpb.RegisterQueryServer(app.GRPCQueryRouter(), testpb.QueryImpl{}) + + // create the simulation manager and define the order of the modules for deterministic simulations + // + // NOTE: this is not required apps that don't use the simulator for fuzz testing + // transactions + overrideModules := map[string]module.AppModuleSimulation{ + authtypes.ModuleName: auth.NewAppModule(app.appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, app.GetSubspace(authtypes.ModuleName)), + } + app.simulationManager = module.NewSimulationManagerFromAppModules(app.ModuleManager.Modules, overrideModules) + + app.simulationManager.RegisterStoreDecoders() + + // initialize stores + app.MountKVStores(keys) + app.MountTransientStores(tkeys) + + // initialize BaseApp + app.SetInitChainer(app.InitChainer) + app.SetPreBlocker(app.PreBlocker) + app.SetBeginBlocker(app.BeginBlocker) + app.SetEndBlocker(app.EndBlocker) + app.setAnteHandler(txConfig) + + // In v0.46, the SDK introduces _postHandlers_. PostHandlers are like + // antehandlers, but are run _after_ the `runMsgs` execution. They are also + // defined as a chain, and have the same signature as antehandlers. + // + // In baseapp, postHandlers are run in the same store branch as `runMsgs`, + // meaning that both `runMsgs` and `postHandler` state will be committed if + // both are successful, and both will be reverted if any of the two fails. + // + // The SDK exposes a default postHandlers chain, which is comprised of only + // one decorator: the Transaction Tips decorator. However, some chains do + // not need it by default, so feel free to comment the next line if you do + // not need tips. + // To read more about tips: + // https://docs.cosmos.network/main/core/tips.html + // + // Please note that changing any of the anteHandler or postHandler chain is + // likely to be a state-machine breaking change, which needs a coordinated + // upgrade. + app.setPostHandler() + + // At startup, after all modules have been registered, check that all proto + // annotations are correct. + protoFiles, err := proto.MergedRegistry() + if err != nil { + panic(err) + } + err = msgservice.ValidateProtoAnnotations(protoFiles) + if err != nil { + // Once we switch to using protoreflect-based antehandlers, we might + // want to panic here instead of logging a warning. + _, err := fmt.Fprintln(os.Stderr, err.Error()) + if err != nil { + fmt.Println("could not write to stderr") + } + } + + if loadLatest { + if err := app.LoadLatestVersion(); err != nil { + panic(fmt.Errorf("error loading last version: %w", err)) + } + } + + return app +} + +func (app *SimApp) setAnteHandler(txConfig client.TxConfig) { + anteHandler, err := NewAnteHandler( + HandlerOptions{ + ante.HandlerOptions{ + AccountKeeper: app.AccountKeeper, + BankKeeper: app.BankKeeper, + SignModeHandler: txConfig.SignModeHandler(), + FeegrantKeeper: app.FeeGrantKeeper, + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + }, + &app.CircuitKeeper, + app.IBCKeeper, + }, + ) + if err != nil { + panic(err) + } + + // Set the AnteHandler for the app + app.SetAnteHandler(anteHandler) +} + +func (app *SimApp) setPostHandler() { + postHandler, err := posthandler.NewPostHandler( + posthandler.HandlerOptions{}, + ) + if err != nil { + panic(err) + } + + app.SetPostHandler(postHandler) +} + +// Name returns the name of the App +func (app *SimApp) Name() string { return app.BaseApp.Name() } + +// PreBlocker application updates every pre block +func (app *SimApp) PreBlocker(ctx sdk.Context, _ *abci.RequestFinalizeBlock) (*sdk.ResponsePreBlock, error) { + return app.ModuleManager.PreBlock(ctx) +} + +// BeginBlocker application updates every begin block +func (app *SimApp) BeginBlocker(ctx sdk.Context) (sdk.BeginBlock, error) { + return app.ModuleManager.BeginBlock(ctx) +} + +// EndBlocker application updates every end block +func (app *SimApp) EndBlocker(ctx sdk.Context) (sdk.EndBlock, error) { + return app.ModuleManager.EndBlock(ctx) +} + +// Configurator returns the configurator for the app +func (app *SimApp) Configurator() module.Configurator { + return app.configurator +} + +// InitChainer application update at chain initialization +func (app *SimApp) InitChainer(ctx sdk.Context, req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { + var genesisState GenesisState + if err := json.Unmarshal(req.AppStateBytes, &genesisState); err != nil { + panic(err) + } + if err := app.UpgradeKeeper.SetModuleVersionMap(ctx, app.ModuleManager.GetVersionMap()); err != nil { + panic(err) + } + return app.ModuleManager.InitGenesis(ctx, app.appCodec, genesisState) +} + +// LoadHeight loads a particular height +func (app *SimApp) LoadHeight(height int64) error { + return app.LoadVersion(height) +} + +// LegacyAmino returns SimApp's amino codec. +// +// NOTE: This is solely to be used for testing purposes as it may be desirable +// for modules to register their own custom testing types. +func (app *SimApp) LegacyAmino() *codec.LegacyAmino { + return app.legacyAmino +} + +// AppCodec returns SimApp's app codec. +// +// NOTE: This is solely to be used for testing purposes as it may be desirable +// for modules to register their own custom testing types. +func (app *SimApp) AppCodec() codec.Codec { + return app.appCodec +} + +// InterfaceRegistry returns SimApp's InterfaceRegistry +func (app *SimApp) InterfaceRegistry() types.InterfaceRegistry { + return app.interfaceRegistry +} + +// TxConfig returns SimApp's TxConfig +func (app *SimApp) TxConfig() client.TxConfig { + return app.txConfig +} + +// AutoCliOpts returns the autocli options for the app. +func (app *SimApp) AutoCliOpts() autocli.AppOptions { + modules := make(map[string]appmodule.AppModule, 0) + for _, m := range app.ModuleManager.Modules { + if moduleWithName, ok := m.(module.HasName); ok { + moduleName := moduleWithName.Name() + if appModule, ok := moduleWithName.(appmodule.AppModule); ok { + modules[moduleName] = appModule + } + } + } + + return autocli.AppOptions{ + Modules: modules, + ModuleOptions: runtimeservices.ExtractAutoCLIOptions(app.ModuleManager.Modules), + } +} + +// DefaultGenesis returns a default genesis from the registered AppModuleBasic's. +func (app *SimApp) DefaultGenesis() map[string]json.RawMessage { + return app.BasicModuleManager.DefaultGenesis(app.appCodec) +} + +// GetKey returns the KVStoreKey for the provided store key. +// +// NOTE: This is solely to be used for testing purposes. +func (app *SimApp) GetKey(storeKey string) *storetypes.KVStoreKey { + return app.keys[storeKey] +} + +// GetStoreKeys returns all the stored store keys. +func (app *SimApp) GetStoreKeys() []storetypes.StoreKey { + keys := make([]storetypes.StoreKey, 0, len(app.keys)) + for _, key := range app.keys { + keys = append(keys, key) + } + + return keys +} + +// GetSubspace returns a param subspace for a given module name. +// +// NOTE: This is solely to be used for testing purposes. +func (app *SimApp) GetSubspace(moduleName string) paramstypes.Subspace { + subspace, _ := app.ParamsKeeper.GetSubspace(moduleName) + return subspace +} + +// SimulationManager implements the SimulationApp interface +func (app *SimApp) SimulationManager() *module.SimulationManager { + return app.simulationManager +} + +// RegisterAPIRoutes registers all application module routes with the provided +// API server. +func (app *SimApp) RegisterAPIRoutes(apiSvr *api.Server, apiConfig config.APIConfig) { + clientCtx := apiSvr.ClientCtx + // Register new tx routes from grpc-gateway. + authtx.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // Register new CometBFT queries routes from grpc-gateway. + cmtservice.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // Register node gRPC service for grpc-gateway. + nodeservice.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // Register grpc-gateway routes for all modules. + app.BasicModuleManager.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // register swagger API from root so that other applications can override easily + if err := server.RegisterSwaggerAPI(apiSvr.ClientCtx, apiSvr.Router, apiConfig.Swagger); err != nil { + panic(err) + } +} + +// RegisterTxService implements the Application.RegisterTxService method. +func (app *SimApp) RegisterTxService(clientCtx client.Context) { + authtx.RegisterTxService(app.GRPCQueryRouter(), clientCtx, app.Simulate, app.interfaceRegistry) +} + +// RegisterTendermintService implements the Application.RegisterTendermintService method. +func (app *SimApp) RegisterTendermintService(clientCtx client.Context) { + cmtApp := server.NewCometABCIWrapper(app) + cmtservice.RegisterTendermintService( + clientCtx, + app.GRPCQueryRouter(), + app.interfaceRegistry, + cmtApp.Query, + ) +} + +func (app *SimApp) RegisterNodeService(clientCtx client.Context, cfg config.Config) { + nodeservice.RegisterNodeService(clientCtx, app.GRPCQueryRouter(), cfg) +} + +// GetMaccPerms returns a copy of the module account permissions +// +// NOTE: This is solely to be used for testing purposes. +func GetMaccPerms() map[string][]string { + dupMaccPerms := make(map[string][]string) + maps.Copy(dupMaccPerms, maccPerms) + + return dupMaccPerms +} + +// BlockedAddresses returns all the app's blocked account addresses. +func BlockedAddresses() map[string]bool { + modAccAddrs := make(map[string]bool) + for acc := range GetMaccPerms() { + modAccAddrs[authtypes.NewModuleAddress(acc).String()] = true + } + + // allow the following addresses to receive funds + delete(modAccAddrs, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + + return modAccAddrs +} + +// initParamsKeeper init params keeper and its subspaces +func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey storetypes.StoreKey) paramskeeper.Keeper { + paramsKeeper := paramskeeper.NewKeeper(appCodec, legacyAmino, key, tkey) + + // register the key tables for legacy param subspaces + keyTable := ibcclienttypes.ParamKeyTable() + keyTable.RegisterParamSet(&ibcconnectiontypes.Params{}) + paramsKeeper.Subspace(ibcexported.ModuleName).WithKeyTable(keyTable) + paramsKeeper.Subspace(ibctransfertypes.ModuleName).WithKeyTable(ibctransfertypes.ParamKeyTable()) + paramsKeeper.Subspace(icacontrollertypes.SubModuleName).WithKeyTable(icacontrollertypes.ParamKeyTable()) + paramsKeeper.Subspace(icahosttypes.SubModuleName).WithKeyTable(icahosttypes.ParamKeyTable()) + + return paramsKeeper +} diff --git a/simapp/export.go b/simapp/export.go new file mode 100644 index 0000000..5e52098 --- /dev/null +++ b/simapp/export.go @@ -0,0 +1,249 @@ +package simapp + +import ( + "encoding/json" + "errors" + "log" + + storetypes "cosmossdk.io/store/types" + + servertypes "github.com/cosmos/cosmos-sdk/server/types" + sdk "github.com/cosmos/cosmos-sdk/types" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +// ExportAppStateAndValidators exports the state of the application for a genesis +// file. +func (app *SimApp) ExportAppStateAndValidators( + forZeroHeight bool, jailAllowedAddrs []string, modulesToExport []string, +) (servertypes.ExportedApp, error) { + // as if they could withdraw from the start of the next block + ctx := app.NewContext(true) + + // We export at last height + 1, because that's the height at which + // Tendermint will start InitChain. + height := app.LastBlockHeight() + 1 + if forZeroHeight { + height = 0 + app.prepForZeroHeightGenesis(ctx, jailAllowedAddrs) + } + + genState, err := app.ModuleManager.ExportGenesis(ctx, app.appCodec) + if err != nil { + return servertypes.ExportedApp{}, err + } + appState, err := json.MarshalIndent(genState, "", " ") + if err != nil { + return servertypes.ExportedApp{}, err + } + + validators, err := staking.WriteValidators(ctx, app.StakingKeeper) + return servertypes.ExportedApp{ + AppState: appState, + Validators: validators, + Height: height, + ConsensusParams: app.GetConsensusParams(ctx), + }, err +} + +// prepare for fresh start at zero height +// NOTE zero height genesis is a temporary feature which will be deprecated +// in favour of export at a block height +func (app *SimApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []string) { + applyAllowedAddrs := len(jailAllowedAddrs) > 0 + + // check if there is an allowed address list + + allowedAddrsMap := make(map[string]bool) + + for _, addr := range jailAllowedAddrs { + _, err := sdk.ValAddressFromBech32(addr) + if err != nil { + log.Fatal(err) + } + allowedAddrsMap[addr] = true + } + + /* Just to be safe, assert the invariants on current state. */ + app.CrisisKeeper.AssertInvariants(ctx) + + /* Handle fee distribution state. */ + + // withdraw all validator commission + err := app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { + valBz, err := app.StakingKeeper.ValidatorAddressCodec().StringToBytes(val.GetOperator()) + if err != nil { + panic(err) + } + _, _ = app.DistrKeeper.WithdrawValidatorCommission(ctx, valBz) + return false + }) + if err != nil { + panic(err) + } + + // withdraw all delegator rewards + dels, err := app.StakingKeeper.GetAllDelegations(ctx) + if err != nil { + panic(err) + } + + for _, delegation := range dels { + valAddr, err := sdk.ValAddressFromBech32(delegation.ValidatorAddress) + if err != nil { + panic(err) + } + + delAddr, err := sdk.AccAddressFromBech32(delegation.DelegatorAddress) + if err != nil { + panic(err) + } + _, _ = app.DistrKeeper.WithdrawDelegationRewards(ctx, delAddr, valAddr) + } + + // clear validator slash events + app.DistrKeeper.DeleteAllValidatorSlashEvents(ctx) + + // clear validator historical rewards + app.DistrKeeper.DeleteAllValidatorHistoricalRewards(ctx) + + // set context height to zero + height := ctx.BlockHeight() + ctx = ctx.WithBlockHeight(0) + + // reinitialize all validators + err = app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { + valBz, err := sdk.ValAddressFromBech32(val.GetOperator()) + if err != nil { + panic(err) + } + // donate any unwithdrawn outstanding reward fraction tokens to the community pool + scraps, err := app.DistrKeeper.GetValidatorOutstandingRewardsCoins(ctx, valBz) + if err != nil { + panic(err) + } + feePool, err := app.DistrKeeper.FeePool.Get(ctx) + if err != nil { + panic(err) + } + feePool.CommunityPool = feePool.CommunityPool.Add(scraps...) + if err := app.DistrKeeper.FeePool.Set(ctx, feePool); err != nil { + panic(err) + } + + if err := app.DistrKeeper.Hooks().AfterValidatorCreated(ctx, valBz); err != nil { + panic(err) + } + return false + }) + if err != nil { + panic(err) + } + + // reinitialize all delegations + for _, del := range dels { + valAddr, err := sdk.ValAddressFromBech32(del.ValidatorAddress) + if err != nil { + panic(err) + } + delAddr, err := sdk.AccAddressFromBech32(del.DelegatorAddress) + if err != nil { + panic(err) + } + err = app.DistrKeeper.Hooks().BeforeDelegationCreated(ctx, delAddr, valAddr) + if err != nil { + panic(err) + } + err = app.DistrKeeper.Hooks().AfterDelegationModified(ctx, delAddr, valAddr) + if err != nil { + panic(err) + } + } + + // reset context height + ctx = ctx.WithBlockHeight(height) + + /* Handle staking state. */ + + // iterate through redelegations, reset creation height + err = app.StakingKeeper.IterateRedelegations(ctx, func(_ int64, red stakingtypes.Redelegation) (stop bool) { + for i := range red.Entries { + red.Entries[i].CreationHeight = 0 + } + err = app.StakingKeeper.SetRedelegation(ctx, red) + if err != nil { + panic(err) + } + return false + }) + if err != nil { + panic(err) + } + + // iterate through unbonding delegations, reset creation height + err = app.StakingKeeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd stakingtypes.UnbondingDelegation) (stop bool) { + for i := range ubd.Entries { + ubd.Entries[i].CreationHeight = 0 + } + err = app.StakingKeeper.SetUnbondingDelegation(ctx, ubd) + if err != nil { + panic(err) + } + return false + }) + if err != nil { + panic(err) + } + + // Iterate through validators by power descending, reset bond heights, and + // update bond intra-tx counters. + store := ctx.KVStore(app.keys[stakingtypes.StoreKey]) + iter := storetypes.KVStoreReversePrefixIterator(store, stakingtypes.ValidatorsKey) + counter := int16(0) + + for ; iter.Valid(); iter.Next() { + addr := sdk.ValAddress(stakingtypes.AddressFromValidatorsKey(iter.Key())) + validator, err := app.StakingKeeper.GetValidator(ctx, addr) + if err != nil { + panic(errors.New("expected validator, not found")) + } + + validator.UnbondingHeight = 0 + if applyAllowedAddrs && !allowedAddrsMap[addr.String()] { + validator.Jailed = true + } + + err = app.StakingKeeper.SetValidator(ctx, validator) + if err != nil { + panic(errors.New("couldn't set validator")) + } + counter++ + } + + iter.Close() + + _, err = app.StakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx) + if err != nil { + log.Fatal(err) + } + + /* Handle slashing state. */ + + // reset start height on signing infos + err = app.SlashingKeeper.IterateValidatorSigningInfos( + ctx, + func(addr sdk.ConsAddress, info slashingtypes.ValidatorSigningInfo) (stop bool) { + info.StartHeight = 0 + err = app.SlashingKeeper.SetValidatorSigningInfo(ctx, addr, info) + if err != nil { + panic(err) + } + return false + }, + ) + if err != nil { + log.Fatal(err) + } +} diff --git a/simapp/genesis.go b/simapp/genesis.go new file mode 100644 index 0000000..b267f13 --- /dev/null +++ b/simapp/genesis.go @@ -0,0 +1,14 @@ +package simapp + +import ( + "encoding/json" +) + +// GenesisState of the blockchain is represented here as a map of raw json +// messages key'd by an identifier string. +// The identifier is used to determine which module genesis information belongs +// to so it may be appropriately routed during init chain. +// Within this application default genesis information is retrieved from +// the ModuleBasicManager which populates json from each BasicModule +// object provided to it during init. +type GenesisState map[string]json.RawMessage diff --git a/simapp/go.mod b/simapp/go.mod new file mode 100644 index 0000000..fa55793 --- /dev/null +++ b/simapp/go.mod @@ -0,0 +1,230 @@ +module github.com/cosmos/ibc-go/simapp + +go 1.23.8 + +replace ( + github.com/cosmos/ibc-go/v10 => ../ + github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 +) + +require ( + cosmossdk.io/api v0.9.2 + cosmossdk.io/client/v2 v2.0.0-beta.9 + cosmossdk.io/core v0.11.3 + cosmossdk.io/log v1.6.1 + cosmossdk.io/store v1.1.2 + cosmossdk.io/tools/confix v0.1.2 + cosmossdk.io/x/circuit v0.2.0 + cosmossdk.io/x/evidence v0.2.0 + cosmossdk.io/x/feegrant v0.2.0 + cosmossdk.io/x/tx v0.14.0 + cosmossdk.io/x/upgrade v0.2.0 + github.com/cometbft/cometbft v0.38.17 + github.com/cosmos/cosmos-db v1.1.3 + github.com/cosmos/cosmos-sdk v0.53.4 + github.com/cosmos/gogoproto v1.7.0 + github.com/cosmos/ibc-go/v10 v10.3.0 + github.com/spf13/cast v1.9.2 + github.com/spf13/cobra v1.10.1 + github.com/spf13/viper v1.20.1 + github.com/stretchr/testify v1.11.1 +) + +require ( + cel.dev/expr v0.24.0 // indirect + cloud.google.com/go v0.116.0 // indirect + cloud.google.com/go/auth v0.14.1 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect + cloud.google.com/go/compute/metadata v0.7.0 // indirect + cloud.google.com/go/iam v1.2.2 // indirect + cloud.google.com/go/monitoring v1.21.2 // indirect + cloud.google.com/go/storage v1.49.0 // indirect + cosmossdk.io/collections v1.2.1 // indirect + cosmossdk.io/depinject v1.2.1 // indirect + cosmossdk.io/errors v1.0.2 // indirect + cosmossdk.io/math v1.5.3 // indirect + cosmossdk.io/schema v1.1.0 // indirect + filippo.io/edwards25519 v1.1.0 // indirect + github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect + github.com/99designs/keyring v1.2.2 // indirect + github.com/DataDog/datadog-go v4.8.3+incompatible // indirect + github.com/DataDog/zstd v1.5.7 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/aws/aws-sdk-go v1.49.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect + github.com/bgentry/speakeasy v0.2.0 // indirect + github.com/bits-and-blooms/bitset v1.22.0 // indirect + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/chzyer/readline v1.5.1 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect + github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect + github.com/cockroachdb/apd/v2 v2.0.2 // indirect + github.com/cockroachdb/errors v1.12.0 // indirect + github.com/cockroachdb/fifo v0.0.0-20240616162244-4768e80dfb9a // indirect + github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 // indirect + github.com/cockroachdb/pebble v1.1.5 // indirect + github.com/cockroachdb/redact v1.1.6 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect + github.com/cometbft/cometbft-db v0.14.1 // indirect + github.com/cosmos/btcutil v1.0.5 // indirect + github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect + github.com/cosmos/go-bip39 v1.0.0 // indirect + github.com/cosmos/gogogateway v1.2.0 // indirect + github.com/cosmos/iavl v1.2.2 // indirect + github.com/cosmos/ics23/go v0.11.0 // indirect + github.com/cosmos/ledger-cosmos-go v0.14.0 // indirect + github.com/creachadair/atomicfile v0.3.1 // indirect + github.com/creachadair/tomledit v0.0.24 // indirect + github.com/danieljoos/wincred v1.2.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect + github.com/desertbit/timer v1.0.1 // indirect + github.com/dgraph-io/badger/v4 v4.2.0 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/dvsekhvalnov/jose2go v1.7.0 // indirect + github.com/emicklei/dot v1.6.2 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect + github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect + github.com/ethereum/go-ethereum v1.15.11 // indirect + github.com/fatih/color v1.17.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/getsentry/sentry-go v0.32.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.1 // indirect + github.com/go-kit/kit v0.13.0 // indirect + github.com/go-kit/log v0.2.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect + github.com/gogo/googleapis v1.4.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v1.2.5 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.5-0.20231225225746-43d5d4cd4e0e // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/flatbuffers v24.3.25+incompatible // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/orderedcode v0.0.1 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/gax-go/v2 v2.14.1 // indirect + github.com/gorilla/handlers v1.5.2 // indirect + github.com/gorilla/mux v1.8.1 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-getter v1.7.8 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-metrics v0.5.4 // indirect + github.com/hashicorp/go-plugin v1.6.3 // indirect + github.com/hashicorp/go-safetemp v1.0.0 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/yamux v0.1.2 // indirect + github.com/hdevalence/ed25519consensus v0.2.0 // indirect + github.com/holiman/uint256 v1.3.2 // indirect + github.com/huandu/skiplist v1.2.1 // indirect + github.com/iancoleman/strcase v0.3.0 // indirect + github.com/improbable-eng/grpc-web v0.15.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/jmhodges/levigo v1.0.0 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/linxGnu/grocksdb v1.9.2 // indirect + github.com/manifoldco/promptui v0.9.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mdp/qrterminal/v3 v3.2.1 // indirect + github.com/minio/highwayhash v1.0.3 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mtibben/percent v0.2.1 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect + github.com/oklog/run v1.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.63.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/rs/cors v1.11.1 // indirect + github.com/rs/zerolog v1.34.0 // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/sasha-s/go-deadlock v0.3.5 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/pflag v1.0.9 // indirect + github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect + github.com/tendermint/go-amino v0.16.0 // indirect + github.com/tidwall/btree v1.7.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ulikunitz/xz v0.5.11 // indirect + github.com/zeebo/errs v1.4.0 // indirect + github.com/zondax/hid v0.9.2 // indirect + github.com/zondax/ledger-go v0.14.3 // indirect + go.etcd.io/bbolt v1.4.0-alpha.1 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.uber.org/mock v0.5.2 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/arch v0.17.0 // indirect + golang.org/x/crypto v0.39.0 // indirect + golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.15.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.26.0 // indirect + golang.org/x/time v0.10.0 // indirect + google.golang.org/api v0.222.0 // indirect + google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + google.golang.org/grpc v1.75.0 // indirect + google.golang.org/protobuf v1.36.8 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gotest.tools/v3 v3.5.2 // indirect + nhooyr.io/websocket v1.8.11 // indirect + pgregory.net/rapid v1.2.0 // indirect + rsc.io/qr v0.2.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect +) diff --git a/simapp/go.sum b/simapp/go.sum new file mode 100644 index 0000000..da038e8 --- /dev/null +++ b/simapp/go.sum @@ -0,0 +1,2457 @@ +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= +cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= +cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= +cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= +cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= +cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= +cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= +cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= +cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= +cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= +cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= +cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= +cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= +cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= +cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= +cloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k= +cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= +cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= +cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= +cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= +cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE= +cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= +cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= +cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= +cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= +cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= +cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= +cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= +cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= +cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= +cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= +cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= +cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= +cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= +cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= +cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= +cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= +cloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A= +cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= +cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= +cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= +cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY= +cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= +cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= +cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= +cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= +cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= +cloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI= +cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ= +cloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI= +cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= +cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= +cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= +cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= +cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= +cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= +cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= +cloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg= +cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= +cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= +cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= +cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= +cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= +cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= +cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= +cloud.google.com/go/auth v0.14.1 h1:AwoJbzUdxA/whv1qj3TLKwh3XX5sikny2fc40wUl+h0= +cloud.google.com/go/auth v0.14.1/go.mod h1:4JHUxlGXisL0AW8kXPtUF6ztuOksyfUQNFjfsOCXkPM= +cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= +cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= +cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= +cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= +cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= +cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= +cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= +cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= +cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= +cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= +cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= +cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= +cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= +cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= +cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= +cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= +cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= +cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= +cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= +cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E= +cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= +cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q= +cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= +cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= +cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= +cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= +cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= +cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= +cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= +cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= +cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= +cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= +cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= +cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= +cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= +cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= +cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= +cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= +cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= +cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= +cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= +cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= +cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= +cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= +cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg= +cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= +cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= +cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= +cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= +cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= +cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= +cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= +cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= +cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= +cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= +cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= +cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= +cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= +cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= +cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= +cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= +cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= +cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= +cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= +cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= +cloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM= +cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= +cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= +cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= +cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= +cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= +cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= +cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= +cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= +cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= +cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= +cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= +cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= +cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= +cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= +cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= +cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= +cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= +cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= +cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= +cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= +cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= +cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= +cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= +cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= +cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= +cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= +cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= +cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= +cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= +cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= +cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= +cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= +cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= +cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= +cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= +cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= +cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= +cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= +cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= +cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= +cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= +cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= +cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= +cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= +cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= +cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= +cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= +cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= +cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= +cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= +cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= +cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= +cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= +cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM= +cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= +cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= +cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= +cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= +cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= +cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= +cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= +cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= +cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= +cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= +cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= +cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= +cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= +cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= +cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= +cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= +cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= +cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= +cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= +cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= +cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= +cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= +cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= +cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= +cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= +cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= +cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= +cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= +cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= +cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= +cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= +cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= +cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= +cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw= +cloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA= +cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= +cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= +cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= +cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= +cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= +cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= +cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= +cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= +cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= +cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= +cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= +cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= +cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= +cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= +cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= +cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= +cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= +cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= +cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= +cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= +cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= +cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= +cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= +cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= +cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= +cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= +cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA= +cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= +cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= +cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= +cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= +cloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo= +cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= +cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= +cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= +cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= +cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= +cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= +cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= +cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= +cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= +cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= +cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= +cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg= +cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= +cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= +cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= +cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= +cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= +cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= +cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= +cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= +cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= +cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= +cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= +cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= +cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= +cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk= +cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM= +cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= +cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= +cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= +cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc= +cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= +cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= +cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= +cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= +cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= +cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= +cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= +cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= +cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= +cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= +cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= +cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= +cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= +cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= +cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= +cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= +cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= +cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= +cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= +cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= +cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= +cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= +cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= +cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= +cloud.google.com/go/monitoring v1.21.2 h1:FChwVtClH19E7pJ+e0xUhJPGksctZNVOk2UhMmblmdU= +cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= +cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= +cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= +cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= +cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= +cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= +cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= +cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= +cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= +cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= +cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= +cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= +cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= +cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= +cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= +cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= +cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= +cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= +cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= +cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= +cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= +cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= +cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= +cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= +cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= +cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= +cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= +cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= +cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= +cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= +cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= +cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= +cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= +cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= +cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= +cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= +cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= +cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= +cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= +cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= +cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= +cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= +cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= +cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= +cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= +cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= +cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= +cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= +cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= +cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= +cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= +cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= +cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= +cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= +cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= +cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= +cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= +cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= +cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= +cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= +cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= +cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= +cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= +cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= +cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= +cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= +cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= +cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= +cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= +cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= +cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= +cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= +cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= +cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= +cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= +cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= +cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= +cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= +cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= +cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= +cloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo= +cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= +cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= +cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= +cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= +cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= +cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= +cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= +cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= +cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= +cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= +cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= +cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= +cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= +cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= +cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= +cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= +cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= +cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= +cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= +cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= +cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= +cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= +cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= +cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= +cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= +cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= +cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= +cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= +cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= +cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= +cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= +cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= +cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= +cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= +cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= +cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= +cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= +cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= +cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= +cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc= +cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= +cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= +cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= +cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= +cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= +cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= +cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= +cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= +cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= +cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= +cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= +cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= +cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= +cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec= +cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= +cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= +cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= +cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= +cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= +cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= +cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= +cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= +cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= +cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= +cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= +cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= +cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= +cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= +cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= +cloud.google.com/go/storage v1.49.0 h1:zenOPBOWHCnojRd9aJZAyQXBYqkJkdQS42dxL55CIMw= +cloud.google.com/go/storage v1.49.0/go.mod h1:k1eHhhpLvrPjVGfo0mOUPEJ4Y2+a/Hv5PiwehZI9qGU= +cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= +cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= +cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= +cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= +cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= +cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= +cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= +cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= +cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= +cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= +cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= +cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= +cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= +cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= +cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= +cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= +cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= +cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= +cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= +cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI= +cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io= +cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= +cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= +cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= +cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= +cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= +cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= +cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk= +cloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= +cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= +cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= +cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= +cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= +cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= +cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= +cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= +cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= +cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= +cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= +cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= +cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= +cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= +cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= +cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= +cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= +cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= +cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= +cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= +cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= +cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= +cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= +cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= +cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= +cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= +cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= +cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= +cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= +cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= +cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= +cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= +cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= +cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= +cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= +cosmossdk.io/api v0.9.2 h1:9i9ptOBdmoIEVEVWLtYYHjxZonlF/aOVODLFaxpmNtg= +cosmossdk.io/api v0.9.2/go.mod h1:CWt31nVohvoPMTlPv+mMNCtC0a7BqRdESjCsstHcTkU= +cosmossdk.io/client/v2 v2.0.0-beta.9 h1:xc06zg4G858/pK5plhf8RCfo+KR2mdDKJNrEkfrVAqc= +cosmossdk.io/client/v2 v2.0.0-beta.9/go.mod h1:pHf3CCHX5gmbL9rDCVbXhGI2+/DdAVTEZSLpdd5V9Zs= +cosmossdk.io/collections v1.2.1 h1:mAlNMs5vJwkda4TA+k5q/43p24RVAQ/qyDrjANu3BXE= +cosmossdk.io/collections v1.2.1/go.mod h1:PSsEJ/fqny0VPsHLFT6gXDj/2C1tBOTS9eByK0+PBFU= +cosmossdk.io/core v0.11.3 h1:mei+MVDJOwIjIniaKelE3jPDqShCc/F4LkNNHh+4yfo= +cosmossdk.io/core v0.11.3/go.mod h1:9rL4RE1uDt5AJ4Tg55sYyHWXA16VmpHgbe0PbJc6N2Y= +cosmossdk.io/depinject v1.2.1 h1:eD6FxkIjlVaNZT+dXTQuwQTKZrFZ4UrfCq1RKgzyhMw= +cosmossdk.io/depinject v1.2.1/go.mod h1:lqQEycz0H2JXqvOgVwTsjEdMI0plswI7p6KX+MVqFOM= +cosmossdk.io/errors v1.0.2 h1:wcYiJz08HThbWxd/L4jObeLaLySopyyuUFB5w4AGpCo= +cosmossdk.io/errors v1.0.2/go.mod h1:0rjgiHkftRYPj//3DrD6y8hcm40HcPv/dR4R/4efr0k= +cosmossdk.io/log v1.6.1 h1:YXNwAgbDwMEKwDlCdH8vPcoggma48MgZrTQXCfmMBeI= +cosmossdk.io/log v1.6.1/go.mod h1:gMwsWyyDBjpdG9u2avCFdysXqxq28WJapJvu+vF1y+E= +cosmossdk.io/math v1.5.3 h1:WH6tu6Z3AUCeHbeOSHg2mt9rnoiUWVWaQ2t6Gkll96U= +cosmossdk.io/math v1.5.3/go.mod h1:uqcZv7vexnhMFJF+6zh9EWdm/+Ylyln34IvPnBauPCQ= +cosmossdk.io/schema v1.1.0 h1:mmpuz3dzouCoyjjcMcA/xHBEmMChN+EHh8EHxHRHhzE= +cosmossdk.io/schema v1.1.0/go.mod h1:Gb7pqO+tpR+jLW5qDcNOSv0KtppYs7881kfzakguhhI= +cosmossdk.io/store v1.1.2 h1:3HOZG8+CuThREKv6cn3WSohAc6yccxO3hLzwK6rBC7o= +cosmossdk.io/store v1.1.2/go.mod h1:60rAGzTHevGm592kFhiUVkNC9w7gooSEn5iUBPzHQ6A= +cosmossdk.io/tools/confix v0.1.2 h1:2hoM1oFCNisd0ltSAAZw2i4ponARPmlhuNu3yy0VwI4= +cosmossdk.io/tools/confix v0.1.2/go.mod h1:7XfcbK9sC/KNgVGxgLM0BrFbVcR/+6Dg7MFfpx7duYo= +cosmossdk.io/x/circuit v0.2.0 h1:RJPMBQWCQU77EcM9HDTBnqRhq21fcUxgWZl7BZylJZo= +cosmossdk.io/x/circuit v0.2.0/go.mod h1:CjiGXDeZs64nMv0fG+QmvGVTcn7n3Sv4cDszMRR2JqU= +cosmossdk.io/x/evidence v0.2.0 h1:o72zbmgCM7U0v7z7b0XnMB+NqX0tFamqb1HHkQbhrZ0= +cosmossdk.io/x/evidence v0.2.0/go.mod h1:zx/Xqy+hnGVzkqVuVuvmP9KsO6YCl4SfbAetYi+k+sE= +cosmossdk.io/x/feegrant v0.2.0 h1:oq3WVpoJdxko/XgWmpib63V1mYy9ZQN/1qxDajwGzJ8= +cosmossdk.io/x/feegrant v0.2.0/go.mod h1:9CutZbmhulk/Yo6tQSVD5LG8Lk40ZAQ1OX4d1CODWAE= +cosmossdk.io/x/tx v0.14.0 h1:hB3O25kIcyDW/7kMTLMaO8Ripj3yqs5imceVd6c/heA= +cosmossdk.io/x/tx v0.14.0/go.mod h1:Tn30rSRA1PRfdGB3Yz55W4Sn6EIutr9xtMKSHij+9PM= +cosmossdk.io/x/upgrade v0.2.0 h1:ZHy0xny3wBCSLomyhE06+UmQHWO8cYlVYjfFAJxjz5g= +cosmossdk.io/x/upgrade v0.2.0/go.mod h1:DXDtkvi//TrFyHWSOaeCZGBoiGAE6Rs8/0ABt2pcDD0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= +git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= +github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0= +github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/datadog-go v4.8.3+incompatible h1:fNGaYSuObuQb5nzeTQqowRAd9bpDIRRV4/gUtIBjh8Q= +github.com/DataDog/datadog-go v4.8.3+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE= +github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 h1:UQ0AhxogsIRZDkElkblfnwjc3IaltCm2HUMvezQaL7s= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1 h1:oTX4vsorBZo/Zdum6OKPA4o7544hm6smoRv1QjpTwGo= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1/go.mod h1:0wEl7vrAD8mehJyohS9HZy+WyEOaQO2mJx86Cvh93kM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 h1:8nn+rsCvTq9axyEh382S0PFLBeaFwNsT43IrPWzctRU= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE= +github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/adlio/schema v1.3.6 h1:k1/zc2jNfeiZBA5aFTRy37jlBIuCkXCm0XmvpzCKI9I= +github.com/adlio/schema v1.3.6/go.mod h1:qkxwLgPBd1FgLRHYVCmQT/rrBr3JH38J9LjmVzWNudg= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= +github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= +github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go v1.49.0 h1:g9BkW1fo9GqKfwg2+zCD+TW/D36Ux+vtfJ8guF4AYmY= +github.com/aws/aws-sdk-go v1.49.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bgentry/speakeasy v0.2.0 h1:tgObeVOf8WAvtuAX6DhJ4xks4CFNwPDZiqzGqIHE51E= +github.com/bgentry/speakeasy v0.2.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4= +github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= +github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c= +github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE= +github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= +github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= +github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= +github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.12.0 h1:d7oCs6vuIMUQRVbi6jWWWEJZahLCfJpnJSVobd1/sUo= +github.com/cockroachdb/errors v1.12.0/go.mod h1:SvzfYNNBshAVbZ8wzNc/UPK3w1vf0dKDUP41ucAIf7g= +github.com/cockroachdb/fifo v0.0.0-20240616162244-4768e80dfb9a h1:f52TdbU4D5nozMAhO9TvTJ2ZMCXtN4VIAmfrrZ0JXQ4= +github.com/cockroachdb/fifo v0.0.0-20240616162244-4768e80dfb9a/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M= +github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 h1:ASDL+UJcILMqgNeV5jiqR4j+sTuvQNHdf2chuKj1M5k= +github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506/go.mod h1:Mw7HqKr2kdtu6aYGn3tPmAftiP3QPX63LdK/zcariIo= +github.com/cockroachdb/pebble v1.1.5 h1:5AAWCBWbat0uE0blr8qzufZP5tBjkRyy/jWe1QWLnvw= +github.com/cockroachdb/pebble v1.1.5/go.mod h1:17wO9el1YEigxkP/YtV8NtCivQDgoCyBg5c4VR/eOWo= +github.com/cockroachdb/redact v1.1.6 h1:zXJBwDZ84xJNlHl1rMyCojqyIxv+7YUpQiJLQ7n4314= +github.com/cockroachdb/redact v1.1.6/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/cometbft/cometbft v0.38.17 h1:FkrQNbAjiFqXydeAO81FUzriL4Bz0abYxN/eOHrQGOk= +github.com/cometbft/cometbft v0.38.17/go.mod h1:5l0SkgeLRXi6bBfQuevXjKqML1jjfJJlvI1Ulp02/o4= +github.com/cometbft/cometbft-db v0.14.1 h1:SxoamPghqICBAIcGpleHbmoPqy+crij/++eZz3DlerQ= +github.com/cometbft/cometbft-db v0.14.1/go.mod h1:KHP1YghilyGV/xjD5DP3+2hyigWx0WTp9X+0Gnx0RxQ= +github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= +github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk= +github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis= +github.com/cosmos/cosmos-db v1.1.3 h1:7QNT77+vkefostcKkhrzDK9uoIEryzFrU9eoMeaQOPY= +github.com/cosmos/cosmos-db v1.1.3/go.mod h1:kN+wGsnwUJZYn8Sy5Q2O0vCYA99MJllkKASbs6Unb9U= +github.com/cosmos/cosmos-proto v1.0.0-beta.5 h1:eNcayDLpip+zVLRLYafhzLvQlSmyab+RC5W7ZfmxJLA= +github.com/cosmos/cosmos-proto v1.0.0-beta.5/go.mod h1:hQGLpiIUloJBMdQMMWb/4wRApmI9hjHH05nefC0Ojec= +github.com/cosmos/cosmos-sdk v0.53.4 h1:kPF6vY68+/xi1/VebSZGpoxQqA52qkhUzqkrgeBn3Mg= +github.com/cosmos/cosmos-sdk v0.53.4/go.mod h1:7U3+WHZtI44dEOnU46+lDzBb2tFh1QlMvi8Z5JugopI= +github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= +github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= +github.com/cosmos/gogogateway v1.2.0 h1:Ae/OivNhp8DqBi/sh2A8a1D0y638GpL3tkmLQAiKxTE= +github.com/cosmos/gogogateway v1.2.0/go.mod h1:iQpLkGWxYcnCdz5iAdLcRBSw3h7NXeOkZ4GUkT+tbFI= +github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU= +github.com/cosmos/gogoproto v1.7.0 h1:79USr0oyXAbxg3rspGh/m4SWNyoz/GLaAh0QlCe2fro= +github.com/cosmos/gogoproto v1.7.0/go.mod h1:yWChEv5IUEYURQasfyBW5ffkMHR/90hiHgbNgrtp4j0= +github.com/cosmos/iavl v1.2.2 h1:qHhKW3I70w+04g5KdsdVSHRbFLgt3yY3qTMd4Xa4rC8= +github.com/cosmos/iavl v1.2.2/go.mod h1:GiM43q0pB+uG53mLxLDzimxM9l/5N9UuSY3/D0huuVw= +github.com/cosmos/ics23/go v0.11.0 h1:jk5skjT0TqX5e5QJbEnwXIS2yI2vnmLOgpQPeM5RtnU= +github.com/cosmos/ics23/go v0.11.0/go.mod h1:A8OjxPE67hHST4Icw94hOxxFEJMBG031xIGF/JHNIY0= +github.com/cosmos/ledger-cosmos-go v0.14.0 h1:WfCHricT3rPbkPSVKRH+L4fQGKYHuGOK9Edpel8TYpE= +github.com/cosmos/ledger-cosmos-go v0.14.0/go.mod h1:E07xCWSBl3mTGofZ2QnL4cIUzMbbGVyik84QYKbX3RA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creachadair/atomicfile v0.3.1 h1:yQORkHjSYySh/tv5th1dkKcn02NEW5JleB84sjt+W4Q= +github.com/creachadair/atomicfile v0.3.1/go.mod h1:mwfrkRxFKwpNAflYZzytbSwxvbK6fdGRRlp0KEQc0qU= +github.com/creachadair/tomledit v0.0.24 h1:5Xjr25R2esu1rKCbQEmjZYlrhFkDspoAbAKb6QKQDhQ= +github.com/creachadair/tomledit v0.0.24/go.mod h1:9qHbShRWQzSCcn617cMzg4eab1vbLCOjOshAWSzWr8U= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs= +github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= +github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= +github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= +github.com/desertbit/timer v1.0.1 h1:yRpYNn5Vaaj6QXecdLMPMJsW81JLiI1eokUft5nBmeo= +github.com/desertbit/timer v1.0.1/go.mod h1:htRrYeY5V/t4iu1xCJ5XsQvp4xve8QulXXctAzxqcwE= +github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs= +github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/dvsekhvalnov/jose2go v1.7.0 h1:bnQc8+GMnidJZA8zc6lLEAb4xNrIqHwO+9TzqvtQZPo= +github.com/dvsekhvalnov/jose2go v1.7.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= +github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= +github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= +github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= +github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= +github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= +github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= +github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= +github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/ethereum/go-ethereum v1.15.11 h1:JK73WKeu0WC0O1eyX+mdQAVHUV+UR1a9VB/domDngBU= +github.com/ethereum/go-ethereum v1.15.11/go.mod h1:mf8YiHIb0GR4x4TipcvBUPxJLw1mFdmxzoDi11sDRoI= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/getsentry/sentry-go v0.32.0 h1:YKs+//QmwE3DcYtfKRH8/KyOOF/I6Qnx7qYGNHCGmCY= +github.com/getsentry/sentry-go v0.32.0/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= +github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= +github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= +github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= +github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= +github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= +github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.4.1-0.20201022092350-68b0159b7869/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= +github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= +github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I= +github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.5-0.20231225225746-43d5d4cd4e0e h1:4bw4WeyTYPp0smaXiJZCNnLrvVBqirQVreixayXezGc= +github.com/golang/snappy v0.0.5-0.20231225225746-43d5d4cd4e0e/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= +github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= +github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us= +github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= +github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= +github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-getter v1.7.8 h1:mshVHx1Fto0/MydBekWan5zUipGq7jO0novchgMmSiY= +github.com/hashicorp/go-getter v1.7.8/go.mod h1:2c6CboOEb9jG6YvmC9xdD+tyAFsrUaJPedwXDGr0TM4= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY= +github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= +github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= +github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= +github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= +github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU= +github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo= +github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= +github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= +github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= +github.com/huandu/skiplist v1.2.1 h1:dTi93MgjwErA/8idWTzIw4Y1kZsMWx35fmI2c8Rij7w= +github.com/huandu/skiplist v1.2.1/go.mod h1:7v3iFjLcSAzO4fN5B8dvebvo/qsfumiLiDXMrPiHF9w= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= +github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= +github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= +github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= +github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/linxGnu/grocksdb v1.9.2 h1:O3mzvO0wuzQ9mtlHbDrShixyVjVbmuqTjFrzlf43wZ8= +github.com/linxGnu/grocksdb v1.9.2/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA= +github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mdp/qrterminal/v3 v3.2.1 h1:6+yQjiiOsSuXT5n9/m60E54vdgFsw0zhADHhHLrFet4= +github.com/mdp/qrterminal/v3 v3.2.1/go.mod h1:jOTmXvnBsMy5xqLniO0R++Jmjs2sTm9dFSuQ5kpz/SU= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= +github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= +github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/grpc-proxy v0.0.0-20181017164139-0f1106ef9c76/go.mod h1:x5OoJHDHqxHS801UIuhqGl6QdSAEJvtausosHSdazIo= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a h1:dlRvE5fWabOchtH7znfiFCcOvmIYgOeAS5ifBXBlh9Q= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= +github.com/opencontainers/image-spec v1.1.0-rc5/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss= +github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= +github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw= +github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= +github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= +github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= +github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU= +github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= +github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= +github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= +github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= +github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI= +github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= +github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= +github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= +github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= +github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= +github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.4.0-alpha.1 h1:3yrqQzbRRPFPdOMWS/QQIVxVnzSkAZQYeWlZFv1kbj4= +go.etcd.io/bbolt v1.4.0-alpha.1/go.mod h1:S/Z/Nm3iuOnyO1W4XuFfPci51Gj6F1Hv0z8hisyYYOw= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= +go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= +go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= +golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU= +golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= +golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= +gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= +gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= +google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= +google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= +google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= +google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= +google.golang.org/api v0.222.0 h1:Aiewy7BKLCuq6cUCeOUrsAlzjXPqBkEeQ/iwGHVQa/4= +google.golang.org/api v0.222.0/go.mod h1:efZia3nXpWELrwMlN5vyQrD4GmJN1Vw0x68Et3r+a9c= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= +google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= +google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= +google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= +google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= +google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= +google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= +google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= +modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= +modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= +modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= +modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= +modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= +modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= +modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= +modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= +modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= +modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= +nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= +nhooyr.io/websocket v1.8.11 h1:f/qXNc2/3DpoSZkHt1DQu6rj4zGC8JmkkLkWss0MgN0= +nhooyr.io/websocket v1.8.11/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY= +rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/simapp/params/doc.go b/simapp/params/doc.go new file mode 100644 index 0000000..e7278cb --- /dev/null +++ b/simapp/params/doc.go @@ -0,0 +1,19 @@ +/* +Package params defines the simulation parameters in the simapp. + +It contains the default weights used for each transaction used on the module's +simulation. These weights define the chance for a transaction to be simulated at +any given operation. + +You can replace the default values for the weights by providing a params.json +file with the weights defined for each of the transaction operations: + + { + "op_weight_msg_send": 60, + "op_weight_msg_delegate": 100, + } + +In the example above, the `MsgSend` has 60% chance to be simulated, while the +`MsgDelegate` will always be simulated. +*/ +package params diff --git a/simapp/params/encoding.go b/simapp/params/encoding.go new file mode 100644 index 0000000..2612bb8 --- /dev/null +++ b/simapp/params/encoding.go @@ -0,0 +1,16 @@ +package params + +import ( + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" +) + +// EncodingConfig specifies the concrete encoding types to use for a given app. +// This is provided for compatibility between protobuf and amino implementations. +type EncodingConfig struct { + InterfaceRegistry types.InterfaceRegistry + Codec codec.Codec + TxConfig client.TxConfig + Amino *codec.LegacyAmino +} diff --git a/simapp/params/params.go b/simapp/params/params.go new file mode 100644 index 0000000..d4a08a2 --- /dev/null +++ b/simapp/params/params.go @@ -0,0 +1,7 @@ +package params + +// Simulation parameter constants +const ( + StakePerAccount = "stake_per_account" + InitiallyBondedValidators = "initially_bonded_validators" +) diff --git a/simapp/params/weights.go b/simapp/params/weights.go new file mode 100644 index 0000000..f35c2db --- /dev/null +++ b/simapp/params/weights.go @@ -0,0 +1,28 @@ +package params + +// Default simulation operation weights for messages and gov proposals +const ( + DefaultWeightMsgSend int = 100 + DefaultWeightMsgMultiSend int = 10 + DefaultWeightMsgSetWithdrawAddress int = 50 + DefaultWeightMsgWithdrawDelegationReward int = 50 + DefaultWeightMsgWithdrawValidatorCommission int = 50 + DefaultWeightMsgFundCommunityPool int = 50 + DefaultWeightMsgDeposit int = 100 + DefaultWeightMsgVote int = 67 + DefaultWeightMsgVoteWeighted int = 33 + DefaultWeightMsgUnjail int = 100 + DefaultWeightMsgCreateValidator int = 100 + DefaultWeightMsgEditValidator int = 5 + DefaultWeightMsgDelegate int = 100 + DefaultWeightMsgUndelegate int = 100 + DefaultWeightMsgBeginRedelegate int = 100 + + DefaultWeightCommunitySpendProposal int = 5 + DefaultWeightTextProposal int = 5 + DefaultWeightParamChangeProposal int = 5 + + // feegrant + DefaultWeightGrantFeeAllowance int = 100 + DefaultWeightRevokeFeeAllowance int = 100 +) diff --git a/simapp/simd/cmd/cmd_test.go b/simapp/simd/cmd/cmd_test.go new file mode 100644 index 0000000..71e6d66 --- /dev/null +++ b/simapp/simd/cmd/cmd_test.go @@ -0,0 +1,25 @@ +package cmd_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" + "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" + + "github.com/cosmos/ibc-go/simapp" + "github.com/cosmos/ibc-go/simapp/simd/cmd" +) + +func TestInitCmd(t *testing.T) { + rootCmd := cmd.NewRootCmd() + rootCmd.SetArgs([]string{ + "init", // Test the init cmd + "simapp-test", // Moniker + fmt.Sprintf("--%s=%s", cli.FlagOverwrite, "true"), // Overwrite genesis.json, in case it already exists + }) + + require.NoError(t, svrcmd.Execute(rootCmd, "", simapp.DefaultNodeHome)) +} diff --git a/simapp/simd/cmd/root.go b/simapp/simd/cmd/root.go new file mode 100644 index 0000000..a49ab0f --- /dev/null +++ b/simapp/simd/cmd/root.go @@ -0,0 +1,367 @@ +package cmd + +import ( + "errors" + "io" + "os" + + dbm "github.com/cosmos/cosmos-db" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "cosmossdk.io/client/v2/autocli" + "cosmossdk.io/log" + confixcmd "cosmossdk.io/tools/confix/cmd" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/config" + "github.com/cosmos/cosmos-sdk/client/debug" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/client/pruning" + "github.com/cosmos/cosmos-sdk/client/rpc" + "github.com/cosmos/cosmos-sdk/client/snapshot" + "github.com/cosmos/cosmos-sdk/codec" + addresscodec "github.com/cosmos/cosmos-sdk/codec/address" + "github.com/cosmos/cosmos-sdk/server" + serverconfig "github.com/cosmos/cosmos-sdk/server/config" + servertypes "github.com/cosmos/cosmos-sdk/server/types" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + "github.com/cosmos/cosmos-sdk/x/auth/tx" + txmodule "github.com/cosmos/cosmos-sdk/x/auth/tx/config" + "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/crisis" + genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" + + cmtcfg "github.com/cometbft/cometbft/config" + + "github.com/cosmos/ibc-go/simapp" + "github.com/cosmos/ibc-go/simapp/params" +) + +// NewRootCmd creates a new root command for simd. It is called once in the +// main function. +func NewRootCmd() *cobra.Command { + // we "pre"-instantiate the application for getting the injected/configured encoding configuration + // note, this is not necessary when using app wiring, as depinject can be directly used (see root_v2.go) + tempApp := simapp.NewSimApp(log.NewNopLogger(), dbm.NewMemDB(), nil, true, simtestutil.NewAppOptionsWithFlagHome(tempDir())) + encodingConfig := params.EncodingConfig{ + InterfaceRegistry: tempApp.InterfaceRegistry(), + Codec: tempApp.AppCodec(), + TxConfig: tempApp.TxConfig(), + Amino: tempApp.LegacyAmino(), + } + + initClientCtx := client.Context{}. + WithCodec(encodingConfig.Codec). + WithInterfaceRegistry(encodingConfig.InterfaceRegistry). + WithLegacyAmino(encodingConfig.Amino). + WithInput(os.Stdin). + WithAccountRetriever(types.AccountRetriever{}). + WithHomeDir(simapp.DefaultNodeHome). + WithViper("") // In simapp, we don't use any prefix for env variables. + + rootCmd := &cobra.Command{ + Use: "simd", + Short: "simulation app", + SilenceErrors: true, + PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { + // set the default command outputs + cmd.SetOut(cmd.OutOrStdout()) + cmd.SetErr(cmd.ErrOrStderr()) + + initClientCtx = initClientCtx.WithCmdContext(cmd.Context()) + initClientCtx, err := client.ReadPersistentCommandFlags(initClientCtx, cmd.Flags()) + if err != nil { + return err + } + + initClientCtx, err = config.ReadFromClientConfig(initClientCtx) + if err != nil { + return err + } + + // This needs to go after ReadFromClientConfig, as that function + // sets the RPC client needed for SIGN_MODE_TEXTUAL. This sign mode + // is only available if the client is online. + if !initClientCtx.Offline { + txConfigOpts := tx.ConfigOptions{ + EnabledSignModes: append(tx.DefaultSignModes, signing.SignMode_SIGN_MODE_TEXTUAL), + TextualCoinMetadataQueryFn: txmodule.NewGRPCCoinMetadataQueryFn(initClientCtx), + } + txConfigWithTextual, err := tx.NewTxConfigWithOptions( + codec.NewProtoCodec(encodingConfig.InterfaceRegistry), + txConfigOpts, + ) + if err != nil { + return err + } + initClientCtx = initClientCtx.WithTxConfig(txConfigWithTextual) + } + + if err := client.SetCmdClientContextHandler(initClientCtx, cmd); err != nil { + return err + } + + customAppTemplate, customAppConfig := initAppConfig() + customCMTConfig := initCometBFTConfig() + + return server.InterceptConfigsPreRunHandler(cmd, customAppTemplate, customAppConfig, customCMTConfig) + }, + } + + initRootCmd(rootCmd, encodingConfig, tempApp.BasicModuleManager) + + autoCliOpts, err := enrichAutoCliOpts(tempApp.AutoCliOpts(), initClientCtx) + if err != nil { + panic(err) + } + + if err := autoCliOpts.EnhanceRootCommand(rootCmd); err != nil { + panic(err) + } + + return rootCmd +} + +func enrichAutoCliOpts(autoCliOpts autocli.AppOptions, clientCtx client.Context) (autocli.AppOptions, error) { + autoCliOpts.AddressCodec = addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32AccountAddrPrefix()) + autoCliOpts.ValidatorAddressCodec = addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32ValidatorAddrPrefix()) + autoCliOpts.ConsensusAddressCodec = addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32ConsensusAddrPrefix()) + + var err error + clientCtx, err = config.ReadFromClientConfig(clientCtx) + if err != nil { + return autocli.AppOptions{}, err + } + + autoCliOpts.ClientCtx = clientCtx + + return autoCliOpts, nil +} + +// initCometBFTConfig helps to override default CometBFT Config values. +// return cmtcfg.DefaultConfig if no custom configuration is required for the application. +func initCometBFTConfig() *cmtcfg.Config { + cfg := cmtcfg.DefaultConfig() + + // these values put a higher strain on node memory + // cfg.P2P.MaxNumInboundPeers = 100 + // cfg.P2P.MaxNumOutboundPeers = 40 + + return cfg +} + +// initAppConfig helps to override default appConfig template and configs. +// return "", nil if no custom configuration is required for the application. +func initAppConfig() (string, any) { + // The following code snippet is just for reference. + + // WASMConfig defines configuration for the wasm module. + type WASMConfig struct { + // This is the maximum sdk gas (wasm and storage) that we allow for any x/wasm "smart" queries + QueryGasLimit uint64 `mapstructure:"query_gas_limit"` + + // Address defines the gRPC-web server to listen on + LruSize uint64 `mapstructure:"lru_size"` + } + + type CustomAppConfig struct { + serverconfig.Config + + WASM WASMConfig `mapstructure:"wasm"` + } + + // Optionally allow the chain developer to overwrite the SDK's default + // server config. + srvCfg := serverconfig.DefaultConfig() + // The SDK's default minimum gas price is set to "" (empty value) inside + // app.toml. If left empty by validators, the node will halt on startup. + // However, the chain developer can set a default app.toml value for their + // validators here. + // + // In summary: + // - if you leave srvCfg.MinGasPrices = "", all validators MUST tweak their + // own app.toml config, + // - if you set srvCfg.MinGasPrices non-empty, validators CAN tweak their + // own app.toml to override, or use this default value. + // + // In simapp, we set the min gas prices to 0. + srvCfg.MinGasPrices = "0stake" + // srvCfg.BaseConfig.IAVLDisableFastNode = true // disable fastnode by default + + customAppConfig := CustomAppConfig{ + Config: *srvCfg, + WASM: WASMConfig{ + LruSize: 1, + QueryGasLimit: 300000, + }, + } + + customAppTemplate := serverconfig.DefaultConfigTemplate + ` +[wasm] +# This is the maximum sdk gas (wasm and storage) that we allow for any x/wasm "smart" queries +query_gas_limit = 300000 +# This is the number of wasm vm instances we keep cached in memory for speed-up +# Warning: this is currently unstable and may lead to crashes, best to keep for 0 unless testing locally +lru_size = 0` + + return customAppTemplate, customAppConfig +} + +func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig, basicManager module.BasicManager) { + cfg := sdk.GetConfig() + cfg.Seal() + + rootCmd.AddCommand( + genutilcli.InitCmd(basicManager, simapp.DefaultNodeHome), + debug.Cmd(), + confixcmd.ConfigCommand(), + pruning.Cmd(newApp, simapp.DefaultNodeHome), + snapshot.Cmd(newApp), + server.QueryBlockResultsCmd(), + ) + + server.AddCommands(rootCmd, simapp.DefaultNodeHome, newApp, appExport, addModuleInitFlags) + + // add keybase, auxiliary RPC, query, genesis, and tx child commands + rootCmd.AddCommand( + server.StatusCommand(), + genesisCommand(encodingConfig, basicManager), + txCommand(), + queryCommand(), + keys.Commands(), + ) +} + +func addModuleInitFlags(startCmd *cobra.Command) { + crisis.AddModuleInitFlags(startCmd) +} + +func queryCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "query", + Aliases: []string{"q"}, + Short: "Querying subcommands", + DisableFlagParsing: false, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand( + rpc.ValidatorCommand(), + server.QueryBlockCmd(), + authcmd.QueryTxsByEventsCmd(), + server.QueryBlocksCmd(), + authcmd.QueryTxCmd(), + authcmd.GetSimulateCmd(), + ) + + return cmd +} + +func txCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "tx", + Short: "Transactions subcommands", + DisableFlagParsing: false, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand( + authcmd.GetSignCommand(), + authcmd.GetSignBatchCommand(), + authcmd.GetMultiSignCommand(), + authcmd.GetMultiSignBatchCmd(), + authcmd.GetValidateSignaturesCommand(), + authcmd.GetBroadcastCommand(), + authcmd.GetEncodeCommand(), + authcmd.GetDecodeCommand(), + authcmd.GetSimulateCmd(), + ) + + return cmd +} + +// genesisCommand builds genesis-related `simd genesis` command. Users may provide application specific commands as a parameter +func genesisCommand(encodingConfig params.EncodingConfig, basicManager module.BasicManager, cmds ...*cobra.Command) *cobra.Command { + cmd := genutilcli.Commands(encodingConfig.TxConfig, basicManager, simapp.DefaultNodeHome) + + for _, subCmd := range cmds { + cmd.AddCommand(subCmd) + } + return cmd +} + +// newApp creates the application +func newApp( + logger log.Logger, + db dbm.DB, + traceStore io.Writer, + appOpts servertypes.AppOptions, +) servertypes.Application { + baseappOptions := server.DefaultBaseappOptions(appOpts) + + return simapp.NewSimApp( + logger, db, traceStore, true, + appOpts, + baseappOptions..., + ) +} + +// appExport creates a new simapp (optionally at a given height) and exports state. +func appExport( + logger log.Logger, + db dbm.DB, + traceStore io.Writer, + height int64, + forZeroHeight bool, + jailAllowedAddrs []string, + appOpts servertypes.AppOptions, + modulesToExport []string, +) (servertypes.ExportedApp, error) { + var simApp *simapp.SimApp + + // this check is necessary as we use the flag in x/upgrade. + // we can exit more gracefully by checking the flag here. + homePath, ok := appOpts.Get(flags.FlagHome).(string) + if !ok || homePath == "" { + return servertypes.ExportedApp{}, errors.New("application home not set") + } + + viperAppOpts, ok := appOpts.(*viper.Viper) + if !ok { + return servertypes.ExportedApp{}, errors.New("appOpts is not viper.Viper") + } + + // overwrite the FlagInvCheckPeriod + viperAppOpts.Set(server.FlagInvCheckPeriod, 1) + appOpts = viperAppOpts + + if height != -1 { + simApp = simapp.NewSimApp(logger, db, traceStore, false, appOpts) + + if err := simApp.LoadHeight(height); err != nil { + return servertypes.ExportedApp{}, err + } + } else { + simApp = simapp.NewSimApp(logger, db, traceStore, true, appOpts) + } + + return simApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport) +} + +var tempDir = func() string { + dir, err := os.MkdirTemp("", "simapp") + if err != nil { + dir = simapp.DefaultNodeHome + } + defer os.RemoveAll(dir) + + return dir +} diff --git a/simapp/simd/main.go b/simapp/simd/main.go new file mode 100644 index 0000000..a5068b8 --- /dev/null +++ b/simapp/simd/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "os" + + "cosmossdk.io/log" + + svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" + + "github.com/cosmos/ibc-go/simapp" + "github.com/cosmos/ibc-go/simapp/simd/cmd" +) + +func main() { + rootCmd := cmd.NewRootCmd() + if err := svrcmd.Execute(rootCmd, "", simapp.DefaultNodeHome); err != nil { + log.NewLogger(rootCmd.OutOrStderr()).Error("failure when running app", "err", err) + os.Exit(1) + } +} diff --git a/simapp/upgrades.go b/simapp/upgrades.go new file mode 100644 index 0000000..42d60c0 --- /dev/null +++ b/simapp/upgrades.go @@ -0,0 +1,83 @@ +package simapp + +import ( + storetypes "cosmossdk.io/store/types" + circuittypes "cosmossdk.io/x/circuit/types" + upgradetypes "cosmossdk.io/x/upgrade/types" + + consensusparamtypes "github.com/cosmos/cosmos-sdk/x/consensus/types" + crisistypes "github.com/cosmos/cosmos-sdk/x/crisis/types" + + "github.com/cosmos/ibc-go/simapp/upgrades" +) + +// registerUpgradeHandlers registers all supported upgrade handlers +func (app *SimApp) registerUpgradeHandlers() { + app.UpgradeKeeper.SetUpgradeHandler( + upgrades.V7, + upgrades.CreateV7UpgradeHandler( + app.ModuleManager, + app.configurator, + app.appCodec, + *app.IBCKeeper.ClientKeeper, + app.ConsensusParamsKeeper, + app.ParamsKeeper, + ), + ) + + app.UpgradeKeeper.SetUpgradeHandler( + upgrades.V7_1, + upgrades.CreateV7LocalhostUpgradeHandler(app.ModuleManager, app.configurator, *app.IBCKeeper.ClientKeeper), + ) + + app.UpgradeKeeper.SetUpgradeHandler( + upgrades.V8, + upgrades.CreateDefaultUpgradeHandler( + app.ModuleManager, + app.configurator, + ), + ) + + app.UpgradeKeeper.SetUpgradeHandler( + upgrades.V8_1, + upgrades.CreateDefaultUpgradeHandler( + app.ModuleManager, + app.configurator, + ), + ) + + app.UpgradeKeeper.SetUpgradeHandler( + upgrades.V10, + upgrades.CreateDefaultUpgradeHandler( + app.ModuleManager, + app.configurator, + ), + ) + + upgradeInfo, err := app.UpgradeKeeper.ReadUpgradeInfoFromDisk() + if err != nil { + panic(err) + } + + if upgradeInfo.Name == upgrades.V7 && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { + storeUpgrades := storetypes.StoreUpgrades{ + Added: []string{ + consensusparamtypes.StoreKey, + crisistypes.StoreKey, + }, + } + + // configure store loader that checks if version == upgradeHeight and applies store upgrades + app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) + } + + if upgradeInfo.Name == upgrades.V8 && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { + storeUpgrades := storetypes.StoreUpgrades{ + Added: []string{ + circuittypes.ModuleName, + }, + } + // configure store loader that checks if version == upgradeHeight and applies store upgrades + app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) + } +} diff --git a/simapp/upgrades/upgrades.go b/simapp/upgrades/upgrades.go new file mode 100644 index 0000000..b0cd59c --- /dev/null +++ b/simapp/upgrades/upgrades.go @@ -0,0 +1,86 @@ +package upgrades + +import ( + "context" + + upgradetypes "cosmossdk.io/x/upgrade/types" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + consensusparamskeeper "github.com/cosmos/cosmos-sdk/x/consensus/keeper" + paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + + clientkeeper "github.com/cosmos/ibc-go/v10/modules/core/02-client/keeper" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctmmigrations "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint/migrations" +) + +const ( + // V7 defines the upgrade name for the ibc-go/v7 upgrade handler. + V7 = "v7" + // V7_1 defines the upgrade name for the ibc-go/v7.1 upgrade handler. + V7_1 = "v7.1" + // V8 defines the upgrade name for the ibc-go/v8 upgrade handler. + V8 = "v8" + // V8_1 defines the upgrade name for the ibc-go/v8.1 upgrade handler. + V8_1 = "v8.1" + // V10 defines the upgrade name for the ibc-go/v10 upgrade handler. + V10 = "v10" +) + +// CreateDefaultUpgradeHandler creates an upgrade handler which can be used for regular upgrade tests +// that do not require special logic +func CreateDefaultUpgradeHandler( + mm *module.Manager, + configurator module.Configurator, +) upgradetypes.UpgradeHandler { + return func(ctx context.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + return mm.RunMigrations(ctx, configurator, vm) + } +} + +// CreateV7UpgradeHandler creates an upgrade handler for the ibc-go/v7 SimApp upgrade. +func CreateV7UpgradeHandler( + mm *module.Manager, + configurator module.Configurator, + cdc codec.BinaryCodec, + clientKeeper clientkeeper.Keeper, + consensusParamsKeeper consensusparamskeeper.Keeper, + paramsKeeper paramskeeper.Keeper, +) upgradetypes.UpgradeHandler { + return func(goCtx context.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + // OPTIONAL: prune expired tendermint consensus states to save storage space + if _, err := ibctmmigrations.PruneExpiredConsensusStates(ctx, cdc, &clientKeeper); err != nil { + return nil, err + } + + legacyBaseAppSubspace := paramsKeeper.Subspace(baseapp.Paramspace).WithKeyTable(paramstypes.ConsensusParamsKeyTable()) + err := baseapp.MigrateParams(ctx, legacyBaseAppSubspace, consensusParamsKeeper.ParamsStore) + if err != nil { + panic(err) + } + + return mm.RunMigrations(goCtx, configurator, vm) + } +} + +// CreateV7LocalhostUpgradeHandler creates an upgrade handler for the ibc-go/v7.1 SimApp upgrade. +func CreateV7LocalhostUpgradeHandler( + mm *module.Manager, + configurator module.Configurator, + clientKeeper clientkeeper.Keeper, +) upgradetypes.UpgradeHandler { + return func(goCtx context.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + // explicitly update the IBC 02-client params, adding the localhost client type + params := clientKeeper.GetParams(ctx) + params.AllowedClients = append(params.AllowedClients, exported.Localhost) + clientKeeper.SetParams(ctx, params) + + return mm.RunMigrations(goCtx, configurator, vm) + } +} diff --git a/testing/README.md b/testing/README.md new file mode 100644 index 0000000..fbd857a --- /dev/null +++ b/testing/README.md @@ -0,0 +1,330 @@ +# IBC Testing Package + +## Components + +The testing package is comprised of four parts constructed as a stack. + +- coordinator +- chain +- path +- endpoint + +A coordinator sits at the highest level and contains all the chains which have been initialized. +It also stores and updates the current global time. The time is manually incremented by a `TimeIncrement`. +This allows all the chains to remain in synchrony avoiding the issue of a counterparty being perceived to +be in the future. The coordinator also contains functions to do basic setup of clients, connections, and channels +between two chains. + +A chain is an SDK application (as represented by an app.go file). Inside the chain is an `TestingApp` which allows +the chain to simulate block production and transaction processing. The chain contains by default a single tendermint +validator. A chain is used to process SDK messages. + +A path connects two channel endpoints. It contains all the information needed to relay between two endpoints. + +An endpoint represents a channel (and its associated client and connections) on some specific chain. It contains +references to the chain it is on and the counterparty endpoint it is connected to. The endpoint contains functions +to interact with initialization and updates of its associated clients, connections, and channels. It can send, receive, +and acknowledge packets. + +In general: + +- endpoints are used for initialization and execution of IBC logic on one side of an IBC connection +- paths are used to relay packets +- chains are used to commit SDK messages +- coordinator is used to setup a path between two chains + +## Integration + +To integrate the testing package into your tests, you will need to define: + +- a testing application +- a function to initialize the testing application + +### TestingApp + +Your project will likely already have an application defined. This application +will need to be extended to fulfill the `TestingApp` interface. + +```go +type TestingApp interface { + abci.Application + + // ibc-go additions + GetBaseApp() *baseapp.BaseApp + GetStakingKeeper() ibctestingtypes.StakingKeeper + GetIBCKeeper() *keeper.Keeper + GetTxConfig() client.TxConfig + + // Implemented by SimApp + AppCodec() codec.Codec + + // Implemented by BaseApp + LastCommitID() sdk.CommitID + LastBlockHeight() int64 +} +``` + +To begin, you will need to extend your application by adding the following functions: + +```go +// TestingApp functions +// Example using SimApp to implement TestingApp + +// GetBaseApp implements the TestingApp interface. +func (app *SimApp) GetBaseApp() *baseapp.BaseApp { + return app.BaseApp +} + +// GetStakingKeeper implements the TestingApp interface. +func (app *SimApp) GetStakingKeeper() ibctestingtypes.Keeper { + return app.StakingKeeper +} + +// GetIBCKeeper implements the TestingApp interface. +func (app *SimApp) GetIBCKeeper() *ibckeeper.Keeper { + return app.IBCKeeper +} + +// GetTxConfig implements the TestingApp interface. +func (app *SimApp) GetTxConfig() client.TxConfig { + return app.txConfig +} + +``` + +Your application may need to define `AppCodec()` if it does not already exist: + +```go +// AppCodec returns SimApp's app codec. +// +// NOTE: This is solely to be used for testing purposes as it may be desirable +// for modules to register their own custom testing types. +func (app *SimApp) AppCodec() codec.Codec { + return app.appCodec +} +``` + +It is assumed your application contains an embedded BaseApp and thus implements the abci.Application interface, `LastCommitID()` and `LastBlockHeight()` + +### Initialize TestingApp + +The testing package requires that you provide a function to initialize your TestingApp. This is how ibc-go implements the initialize function with its `SimApp`: + +```go +func SetupTestingApp() (TestingApp, map[string]json.RawMessage) { + db := dbm.NewMemDB() + app := simapp.NewSimApp(log.NewNopLogger(), db, nil, true, simtestutil.EmptyAppOptions{}) + return app, app.DefaultGenesis() +} +``` + +This function returns the TestingApp and the default genesis state used to initialize the testing app. + +Change the value of `DefaultTestingAppInit` to use your function: + +```go +func init() { + ibctesting.DefaultTestingAppInit = SetupTestingApp +} +``` + +## Example + +Here is an example of how to setup your testing environment in every package you are testing: + +```go +// KeeperTestSuite is a testing suite to test keeper functions. +type KeeperTestSuite struct { + testifysuite.Suite + + coordinator *ibctesting.Coordinator + + // testing chains used for convenience and readability + chainA *ibctesting.TestChain + chainB *ibctesting.TestChain +} + +// TestKeeperTestSuite runs all the tests within this package. +func TestKeeperTestSuite(t *testing.T) { + testifysuite.Run(t, new(KeeperTestSuite)) +} + +// SetupTest creates a coordinator with 2 test chains. +func (suite *KeeperTestSuite) SetupTest() { + suite.coordinator = ibctesting.NewCoordinator(suite.T(), 2) // initializes 2 test chains + suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(1)) // convenience and readability + suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(2)) // convenience and readability +} + +``` + +To create interaction between chainA and chainB, we need to construct a `Path` these chains will use. +A path contains two endpoints, `EndpointA` and `EndpointB` (corresponding to the order of the chains passed +into the `NewPath` function). A path is a pointer and its values will be filled in as necessary during the +setup portion of testing. + +Endpoint Struct: + +```go +// Endpoint is a which represents a channel endpoint and its associated +// client and connections. It contains client, connection, and channel +// configuration parameters. Endpoint functions will utilize the parameters +// set in the configuration structs when executing IBC messages. +type Endpoint struct { + Chain *TestChain + Counterparty *Endpoint + ClientID string + ConnectionID string + ChannelID string + + ClientConfig ClientConfig + ConnectionConfig *ConnectionConfig + ChannelConfig *ChannelConfig +} +``` + +The fields empty after `NewPath` is called are `ClientID`, `ConnectionID` and +`ChannelID` as the clients, connections, and channels for these endpoints have not yet been created. The +`ClientConfig`, `ConnectionConfig` and `ChannelConfig` contain all the necessary information for clients, +connections, and channels to be initialized. If you would like to use endpoints which are initialized to +use your Port IDs, you might add a helper function similar to the one found in transfer: + +```go +func NewTransferPath(chainA, chainB *ibctesting.TestChain) *ibctesting.Path { + path := ibctesting.NewPath(chainA, chainB) + path.EndpointA.ChannelConfig.PortID = ibctesting.TransferPort + path.EndpointB.ChannelConfig.PortID = ibctesting.TransferPort + + return path +} + +``` + +Path configurations should be set to the desired values before calling any `Setup` coordinator functions. + +To initialize the clients, connections, and channels for a path we can call the Setup functions of the coordinator: + +- Setup() -> setup clients, connections, channels +- SetupClients() -> setup clients only +- SetupConnections() -> setup clients and connections only + +Here is a basic example of the testing package being used to simulate IBC functionality: + +```go + path := ibctesting.NewPath(suite.chainA, suite.chainB) // clientID, connectionID, channelID empty + suite.coordinator.Setup(path) // clientID, connectionID, channelID filled + suite.Require().Equal("07-tendermint-0", path.EndpointA.ClientID) + suite.Require().Equal("connection-0", path.EndpointA.ClientID) + suite.Require().Equal("channel-0", path.EndpointA.ClientID) + + // send on endpointA + sequence, err := path.EndpointA.SendPacket(timeoutHeight1, timeoutTimestamp1, packet1Data) + + // create packet 1 + packet1 := NewPacket() // NewPacket would construct your packet + + // receive on endpointB + path.EndpointB.RecvPacket(packet1) + + // acknowledge the receipt of the packet + path.EndpointA.AcknowledgePacket(packet1, ack) + + // we can also relay + sequence, err := path.EndpointA.SendPacket(timeoutHeight2, timeoutTimestamp2, packet2Data) + + packet2 := NewPacket() + + path.RelayPacket(packet2) + + // if needed we can update our clients + path.EndpointB.UpdateClient() +``` + +### Transfer Testing Example + +If ICS 20 had its own simapp, its testing setup might include a `testing/app.go` file with the following contents: + +```go +package transfertesting + +import ( + "encoding/json" + + "github.com/cometbft/cometbft/libs/log" + dbm "github.com/cometbft/cometbft-db" + + "github.com/cosmos/ibc-go/v10/modules/apps/transfer/simapp" + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func SetupTransferTestingApp() (ibctesting.TestingApp, map[string]json.RawMessage) { + db := dbm.NewMemDB() + encCdc := simapp.MakeTestEncodingConfig() + app := simapp.NewSimApp( + log.NewNopLogger(), + db, + nil, + true, + map[int64]bool{}, + simapp.DefaultNodeHome, + 5, + encCdc, + simapp.EmptyAppOptions{}, + ) + return app, simapp.NewDefaultGenesisState(encCdc.Marshaler) +} + +func init() { + ibctesting.DefaultTestingAppInit = SetupTransferTestingApp +} + +func NewTransferPath(chainA, chainB *ibctesting.TestChain) *ibctesting.Path { + path := ibctesting.NewPath(chainA, chainB) + path.EndpointA.ChannelConfig.PortID = ibctesting.TransferPort + path.EndpointB.ChannelConfig.PortID = ibctesting.TransferPort + + return path +} + +func GetTransferSimApp(chain *ibctesting.TestChain) *simapp.SimApp { + app, ok := chain.App.(*simapp.SimApp) + if !ok { + panic("not transfer app") + } + + return app +} +``` + +### Middleware Testing + +When writing IBC applications acting as middleware, it might be desirable to test integration points. +This can be done by wiring a middleware stack in the app.go file using existing applications as middleware and IBC base applications. +The mock module may also be leveraged to act as a base application in the instance that such an application is not available for testing or causes dependency concerns. + +The mock IBC module contains a `MockIBCApp`. This struct contains a function field for every IBC App Module callback. +Each of these functions can be individually set to mock expected behaviour of a base application. +The portID and scoped keeper for the `MockIBCApp` should be set within `MockIBCApp` before calling `NewIBCModule`. + +For example, if one wanted to test that the base application cannot affect the outcome of the `OnChanOpenTry` callback, the mock module base application callback could be updated as such: + +```go +mockModule.IBCApp.OnChanOpenTry = func(ctx sdk.Context, portID, channelID, version string) error { + return fmt.Errorf("mock base app must not be called for OnChanOpenTry") +} +``` + +Using a mock module as a base application in a middleware stack may require adding the module to your `SimApp`. +This is because IBC will route to the top level IBC module of a middleware stack, so a module which never +sits at the top of middleware stack will need to be accessed via a public field in `SimApp` + +This might look like: + +```go +suite.chainA.GetSimApp().ICAAuthModule.IBCApp.OnChanOpenInit = func( + ctx sdk.Context, order channeltypes.Order, connectionHops []string, + portID, channelID string, counterparty channeltypes.Counterparty, version string, +) error { + return fmt.Errorf("mock ica auth fails") +} +``` diff --git a/testing/chain.go b/testing/chain.go new file mode 100644 index 0000000..560547e --- /dev/null +++ b/testing/chain.go @@ -0,0 +1,630 @@ +package ibctesting + +import ( + "encoding/json" + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cometbft/cometbft/crypto/tmhash" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + cmtprotoversion "github.com/cometbft/cometbft/proto/tendermint/version" + cmttypes "github.com/cometbft/cometbft/types" + cmtversion "github.com/cometbft/cometbft/version" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + "github.com/cosmos/ibc-go/v10/testing/simapp" +) + +var MaxAccounts = 10 + +type SenderAccount struct { + SenderPrivKey cryptotypes.PrivKey + SenderAccount sdk.AccountI +} + +const ( + DefaultGenesisAccBalance = "10000000000000000000" +) + +// TestChain is a testing struct that wraps a simapp with the last TM Header, the current ABCI +// header and the validators of the TestChain. It also contains a field called ChainID. This +// is the clientID that *other* chains use to refer to this TestChain. The SenderAccount +// is used for delivering transactions through the application state. +// NOTE: the actual application uses an empty chain-id for ease of testing. +type TestChain struct { + testing.TB + + Coordinator *Coordinator + App TestingApp + ChainID string + LatestCommittedHeader *ibctm.Header // header for last block height committed + ProposedHeader cmtproto.Header // proposed (uncommitted) header for current block height + TxConfig client.TxConfig + Codec codec.Codec + + Vals *cmttypes.ValidatorSet + NextVals *cmttypes.ValidatorSet + + // Signers is a map from validator address to the PrivValidator + // The map is converted into an array that is the same order as the validators right before signing commit + // This ensures that signers will always be in correct order even as validator powers change. + // If a test adds a new validator after chain creation, then the signer map must be updated to include + // the new PrivValidator entry. + Signers map[string]cmttypes.PrivValidator + + // TrustedValidators is a mapping used to obtain the validator set from which we can prove a header update. + // It maps from a header height to the next validator set associated with that header. + TrustedValidators map[uint64]*cmttypes.ValidatorSet + + // autogenerated sender private key + SenderPrivKey cryptotypes.PrivKey + SenderAccount sdk.AccountI + + SenderAccounts []SenderAccount + + // Short-term solution to override the logic of the standard SendMsgs function. + // See issue https://github.com/cosmos/ibc-go/issues/3123 for more information. + SendMsgsOverride func(msgs ...sdk.Msg) (*abci.ExecTxResult, error) +} + +// NewTestChainWithValSet initializes a new TestChain instance with the given validator set +// and signer array. It also initializes 10 Sender accounts with a balance of 10000000000000000000 coins of +// bond denom to use for tests. +// +// The first block height is committed to state in order to allow for client creations on +// counterparty chains. The TestChain will return with a block height starting at 2. +// +// Time management is handled by the Coordinator in order to ensure synchrony between chains. +// Each update of any chain increments the block header time for all chains by 5 seconds. +// +// NOTE: to use a custom sender privkey and account for testing purposes, replace and modify this +// constructor function. +// +// CONTRACT: Validator array must be provided in the order expected by Tendermint. +// i.e. sorted first by power and then lexicographically by address. +func NewTestChainWithValSet(tb testing.TB, coord *Coordinator, chainID string, valSet *cmttypes.ValidatorSet, signers map[string]cmttypes.PrivValidator) *TestChain { + tb.Helper() + return newTestChainWithValSet(tb, coord, chainID, valSet, signers, DefaultTestingAppInit) +} + +func newTestChainWithValSet(tb testing.TB, coord *Coordinator, chainID string, valSet *cmttypes.ValidatorSet, signers map[string]cmttypes.PrivValidator, appCreator AppCreator) *TestChain { + tb.Helper() + genAccs := []authtypes.GenesisAccount{} + genBals := []banktypes.Balance{} + senderAccs := []SenderAccount{} + + // generate genesis accounts + for i := range MaxAccounts { + senderPrivKey := secp256k1.GenPrivKey() + acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), uint64(i), 0) + amount, ok := sdkmath.NewIntFromString(DefaultGenesisAccBalance) + require.True(tb, ok) + + // add sender account + balance := banktypes.Balance{ + Address: acc.GetAddress().String(), + Coins: sdk.NewCoins( + sdk.NewCoin(sdk.DefaultBondDenom, amount), + sdk.NewCoin(SecondaryDenom, amount), + ), + } + + genAccs = append(genAccs, acc) + genBals = append(genBals, balance) + + senderAcc := SenderAccount{ + SenderAccount: acc, + SenderPrivKey: senderPrivKey, + } + + senderAccs = append(senderAccs, senderAcc) + } + + app := setupWithGenesisValSet(tb, valSet, genAccs, chainID, sdk.DefaultPowerReduction, appCreator, genBals...) + + // create current header and call begin block + header := cmtproto.Header{ + ChainID: chainID, + Height: 1, + Time: coord.CurrentTime.UTC(), + } + + txConfig := app.GetTxConfig() + + // create an account to send transactions from + chain := &TestChain{ + TB: tb, + Coordinator: coord, + ChainID: chainID, + App: app, + ProposedHeader: header, + TxConfig: txConfig, + Codec: app.AppCodec(), + Vals: valSet, + NextVals: valSet, + Signers: signers, + TrustedValidators: make(map[uint64]*cmttypes.ValidatorSet, 0), + SenderPrivKey: senderAccs[0].SenderPrivKey, + SenderAccount: senderAccs[0].SenderAccount, + SenderAccounts: senderAccs, + } + + // commit genesis block + chain.NextBlock() + + return chain +} + +// AppCreator is a function which returns a TestingApp and a GenesisState +type AppCreator func() (TestingApp, map[string]json.RawMessage) + +// NewCustomAppTestChain creates a TestChain instance with the provided AppCreator function. +func NewCustomAppTestChain(t *testing.T, coord *Coordinator, chainID string, appCreator AppCreator) *TestChain { + t.Helper() + // generate validators private/public key + var ( + validatorsPerChain = 4 + validators []*cmttypes.Validator + signersByAddress = make(map[string]cmttypes.PrivValidator, validatorsPerChain) + ) + + for range validatorsPerChain { + _, privVal := cmttypes.RandValidator(false, 100) + pubKey, err := privVal.GetPubKey() + require.NoError(t, err) + validators = append(validators, cmttypes.NewValidator(pubKey, 1)) + signersByAddress[pubKey.Address().String()] = privVal + } + + // construct validator set; + // Note that the validators are sorted by voting power + // or, if equal, by address lexical order + valSet := cmttypes.NewValidatorSet(validators) + + return newTestChainWithValSet(t, coord, chainID, valSet, signersByAddress, appCreator) +} + +// NewTestChain initializes a new test chain with a default of 4 validators +// Use this function if the tests do not need custom control over the validator set +func NewTestChain(t *testing.T, coord *Coordinator, chainID string) *TestChain { + t.Helper() + return NewCustomAppTestChain(t, coord, chainID, DefaultTestingAppInit) +} + +// GetContext returns the current context for the application. +func (chain *TestChain) GetContext() sdk.Context { + return chain.App.GetBaseApp().NewUncachedContext(false, chain.ProposedHeader) +} + +// GetSimApp returns the SimApp to allow usage ofnon-interface fields. +// CONTRACT: This function should not be called by third parties implementing +// their own SimApp. +func (chain *TestChain) GetSimApp() *simapp.SimApp { + app, ok := chain.App.(*simapp.SimApp) + require.True(chain.TB, ok) + + return app +} + +// QueryProof performs an abci query with the given key and returns the proto encoded merkle proof +// for the query and the height at which the proof will succeed on a tendermint verifier. +func (chain *TestChain) QueryProof(key []byte) ([]byte, clienttypes.Height) { + return chain.QueryProofAtHeight(key, chain.App.LastBlockHeight()) +} + +// QueryProofAtHeight performs an abci query with the given key and returns the proto encoded merkle proof +// for the query and the height at which the proof will succeed on a tendermint verifier. Only the IBC +// store is supported +func (chain *TestChain) QueryProofAtHeight(key []byte, height int64) ([]byte, clienttypes.Height) { + return chain.QueryProofForStore(exported.StoreKey, key, height) +} + +// QueryProofForStore performs an abci query with the given key and returns the proto encoded merkle proof +// for the query and the height at which the proof will succeed on a tendermint verifier. +func (chain *TestChain) QueryProofForStore(storeKey string, key []byte, height int64) ([]byte, clienttypes.Height) { + res, err := chain.App.Query( + chain.GetContext().Context(), + &abci.RequestQuery{ + Path: fmt.Sprintf("store/%s/key", storeKey), + Height: height - 1, + Data: key, + Prove: true, + }) + require.NoError(chain.TB, err) + + merkleProof, err := commitmenttypes.ConvertProofs(res.ProofOps) + require.NoError(chain.TB, err) + + proof, err := chain.App.AppCodec().Marshal(&merkleProof) + require.NoError(chain.TB, err) + + revision := clienttypes.ParseChainID(chain.ChainID) + + // proof height + 1 is returned as the proof created corresponds to the height the proof + // was created in the IAVL tree. Tendermint and subsequently the clients that rely on it + // have heights 1 above the IAVL tree. Thus we return proof height + 1 + return proof, clienttypes.NewHeight(revision, uint64(res.Height)+1) +} + +// QueryUpgradeProof performs an abci query with the given key and returns the proto encoded merkle proof +// for the query and the height at which the proof will succeed on a tendermint verifier. +func (chain *TestChain) QueryUpgradeProof(key []byte, height uint64) ([]byte, clienttypes.Height) { + res, err := chain.App.Query( + chain.GetContext().Context(), + &abci.RequestQuery{ + Path: "store/upgrade/key", + Height: int64(height - 1), + Data: key, + Prove: true, + }) + require.NoError(chain.TB, err) + + merkleProof, err := commitmenttypes.ConvertProofs(res.ProofOps) + require.NoError(chain.TB, err) + + proof, err := chain.App.AppCodec().Marshal(&merkleProof) + require.NoError(chain.TB, err) + + revision := clienttypes.ParseChainID(chain.ChainID) + + // proof height + 1 is returned as the proof created corresponds to the height the proof + // was created in the IAVL tree. Tendermint and subsequently the clients that rely on it + // have heights 1 above the IAVL tree. Thus we return proof height + 1 + return proof, clienttypes.NewHeight(revision, uint64(res.Height+1)) +} + +// QueryConsensusStateProof performs an abci query for a consensus state +// stored on the given clientID. The proof and consensusHeight are returned. +func (chain *TestChain) QueryConsensusStateProof(clientID string) ([]byte, clienttypes.Height) { + consensusHeight, ok := chain.GetClientLatestHeight(clientID).(clienttypes.Height) + require.True(chain.TB, ok) + consensusKey := host.FullConsensusStateKey(clientID, consensusHeight) + consensusProof, _ := chain.QueryProof(consensusKey) + + return consensusProof, consensusHeight +} + +// NextBlock sets the last header to the current header and increments the current header to be +// at the next block height. It does not update the time as that is handled by the Coordinator. +// It will call FinalizeBlock and Commit and apply the validator set changes to the next validators +// of the next block being created. This follows the Tendermint protocol of applying valset changes +// returned on block `n` to the validators of block `n+2`. +// It calls BeginBlock with the new block created before returning. +func (chain *TestChain) NextBlock() { + res, err := chain.App.FinalizeBlock(&abci.RequestFinalizeBlock{ + Height: chain.ProposedHeader.Height, + Time: chain.ProposedHeader.GetTime(), + NextValidatorsHash: chain.NextVals.Hash(), + }) + require.NoError(chain.TB, err) + chain.commitBlock(res) +} + +func (chain *TestChain) commitBlock(res *abci.ResponseFinalizeBlock) { + _, err := chain.App.Commit() + require.NoError(chain.TB, err) + + // set the last header to the current header + // use nil trusted fields + chain.LatestCommittedHeader = chain.CurrentTMClientHeader() + // set the trusted validator set to the next validator set + // The latest trusted validator set is the next validator set + // associated with the header being committed in storage. This will + // allow for header updates to be proved against these validators. + chain.TrustedValidators[uint64(chain.ProposedHeader.Height)] = chain.NextVals + + // val set changes returned from previous block get applied to the next validators + // of this block. See tendermint spec for details. + chain.Vals = chain.NextVals + chain.NextVals = ApplyValSetChanges(chain, chain.Vals, res.ValidatorUpdates) + + // increment the proposer priority of validators + chain.Vals.IncrementProposerPriority(1) + + // increment the current header + chain.ProposedHeader = cmtproto.Header{ + ChainID: chain.ChainID, + Height: chain.App.LastBlockHeight() + 1, + AppHash: chain.App.LastCommitID().Hash, + // NOTE: the time is increased by the coordinator to maintain time synchrony amongst + // chains. + Time: chain.ProposedHeader.Time, + ValidatorsHash: chain.Vals.Hash(), + NextValidatorsHash: chain.NextVals.Hash(), + ProposerAddress: chain.Vals.Proposer.Address, + } +} + +// sendMsgs delivers a transaction through the application without returning the result. +func (chain *TestChain) sendMsgs(msgs ...sdk.Msg) error { + _, err := chain.SendMsgs(msgs...) + return err +} + +// SendMsgs delivers a transaction through the application using a predefined sender. +// It updates the senders sequence number and updates the TestChain's headers. +// It returns the result and error if one occurred. +func (chain *TestChain) SendMsgs(msgs ...sdk.Msg) (*abci.ExecTxResult, error) { + senderAccount := SenderAccount{ + SenderPrivKey: chain.SenderPrivKey, + SenderAccount: chain.SenderAccount, + } + + return chain.SendMsgsWithSender(senderAccount, msgs...) +} + +// SendMsgsWithSender delivers a transaction through the application using the provided sender. +func (chain *TestChain) SendMsgsWithSender(sender SenderAccount, msgs ...sdk.Msg) (*abci.ExecTxResult, error) { + if chain.SendMsgsOverride != nil { + return chain.SendMsgsOverride(msgs...) + } + + // ensure the chain has the latest time + chain.Coordinator.UpdateTimeForChain(chain) + + // increment acc sequence regardless of success or failure tx execution + defer func() { + err := sender.SenderAccount.SetSequence(sender.SenderAccount.GetSequence() + 1) + if err != nil { + panic(err) + } + }() + + resp, err := simapp.SignAndDeliver( + chain.TB, + chain.TxConfig, + chain.App.GetBaseApp(), + msgs, + chain.ChainID, + []uint64{sender.SenderAccount.GetAccountNumber()}, + []uint64{sender.SenderAccount.GetSequence()}, + true, + chain.ProposedHeader.GetTime(), + chain.NextVals.Hash(), + sender.SenderPrivKey, + ) + if err != nil { + return nil, err + } + + chain.commitBlock(resp) + + require.Len(chain.TB, resp.TxResults, 1) + txResult := resp.TxResults[0] + + if txResult.Code != 0 { + return txResult, fmt.Errorf("%s/%d: %q", txResult.Codespace, txResult.Code, txResult.Log) + } + + chain.Coordinator.IncrementTime() + + return txResult, nil +} + +// GetClientState retrieves the client state for the provided clientID. The client is +// expected to exist otherwise testing will fail. +func (chain *TestChain) GetClientState(clientID string) exported.ClientState { + clientState, found := chain.App.GetIBCKeeper().ClientKeeper.GetClientState(chain.GetContext(), clientID) + require.True(chain.TB, found) + + return clientState +} + +// GetConsensusState retrieves the consensus state for the provided clientID and height. +// It will return a success boolean depending on if consensus state exists or not. +func (chain *TestChain) GetConsensusState(clientID string, height exported.Height) (exported.ConsensusState, bool) { + return chain.App.GetIBCKeeper().ClientKeeper.GetClientConsensusState(chain.GetContext(), clientID, height) +} + +// GetAcknowledgement retrieves an acknowledgement for the provided packet. If the +// acknowledgement does not exist then testing will fail. +func (chain *TestChain) GetAcknowledgement(packet channeltypes.Packet) []byte { + ack, found := chain.App.GetIBCKeeper().ChannelKeeper.GetPacketAcknowledgement(chain.GetContext(), packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + require.True(chain.TB, found) + + return ack +} + +// GetPrefix returns the prefix for used by a chain in connection creation +func (chain *TestChain) GetPrefix() commitmenttypes.MerklePrefix { + return commitmenttypes.NewMerklePrefix(chain.App.GetIBCKeeper().ConnectionKeeper.GetCommitmentPrefix().Bytes()) +} + +// ExpireClient fast forwards the chain's block time by the provided amount of time which will +// expire any clients with a trusting period less than or equal to this amount of time. +func (chain *TestChain) ExpireClient(amount time.Duration) { + chain.Coordinator.IncrementTimeBy(amount) +} + +// CurrentTMClientHeader creates a TM header using the current header parameters +// on the chain. The trusted fields in the header are set to nil. +func (chain *TestChain) CurrentTMClientHeader() *ibctm.Header { + return chain.CreateTMClientHeader( + chain.ChainID, + chain.ProposedHeader.Height, + clienttypes.Height{}, + chain.ProposedHeader.Time, + chain.Vals, + chain.NextVals, + nil, + chain.Signers, + ) +} + +// CommitHeader takes in a proposed header and returns a signed cometbft header. +// The signers passed in must match the validator set provided. The signers will +// be used to sign over the proposed header. +func CommitHeader(proposedHeader cmttypes.Header, valSet *cmttypes.ValidatorSet, signers map[string]cmttypes.PrivValidator) (*cmtproto.SignedHeader, error) { + hhash := proposedHeader.Hash() + blockID := MakeBlockID(hhash, 3, unusedHash) + voteSet := cmttypes.NewVoteSet(proposedHeader.ChainID, proposedHeader.Height, 1, cmtproto.PrecommitType, valSet) + + // MakeExtCommit expects a signer array in the same order as the validator array. + // Thus we iterate over the ordered validator set and construct a signer array + // from the signer map in the same order. + signerArr := make([]cmttypes.PrivValidator, len(valSet.Validators)) + for i, v := range valSet.Validators { //nolint:staticcheck // need to check for nil validator set + signerArr[i] = signers[v.Address.String()] + } + + extCommit, err := cmttypes.MakeExtCommit(blockID, proposedHeader.Height, 1, voteSet, signerArr, proposedHeader.Time, false) + if err != nil { + return nil, err + } + + signedHeader := &cmtproto.SignedHeader{ + Header: proposedHeader.ToProto(), + Commit: extCommit.ToCommit().ToProto(), + } + + return signedHeader, nil +} + +// CreateTMClientHeader creates a TM header to update the TM client. Args are passed in to allow +// caller flexibility to use params that differ from the chain. +func (chain *TestChain) CreateTMClientHeader(chainID string, blockHeight int64, trustedHeight clienttypes.Height, timestamp time.Time, cmtValSet, nextVals, cmtTrustedVals *cmttypes.ValidatorSet, signers map[string]cmttypes.PrivValidator) *ibctm.Header { + var ( + valSet *cmtproto.ValidatorSet + trustedVals *cmtproto.ValidatorSet + ) + require.NotNil(chain.TB, cmtValSet) + + proposedHeader := cmttypes.Header{ + Version: cmtprotoversion.Consensus{Block: cmtversion.BlockProtocol, App: 2}, + ChainID: chainID, + Height: blockHeight, + Time: timestamp, + LastBlockID: MakeBlockID(make([]byte, tmhash.Size), 10_000, make([]byte, tmhash.Size)), + LastCommitHash: chain.App.LastCommitID().Hash, + DataHash: unusedHash, + ValidatorsHash: cmtValSet.Hash(), + NextValidatorsHash: nextVals.Hash(), + ConsensusHash: unusedHash, + AppHash: chain.ProposedHeader.AppHash, + LastResultsHash: unusedHash, + EvidenceHash: unusedHash, + ProposerAddress: cmtValSet.Proposer.Address, //nolint:staticcheck + } + + signedHeader, err := CommitHeader(proposedHeader, cmtValSet, signers) + require.NoError(chain.TB, err) + + if cmtValSet != nil { //nolint:staticcheck + valSet, err = cmtValSet.ToProto() + require.NoError(chain.TB, err) + valSet.TotalVotingPower = cmtValSet.TotalVotingPower() + } + + if cmtTrustedVals != nil { + trustedVals, err = cmtTrustedVals.ToProto() + require.NoError(chain.TB, err) + trustedVals.TotalVotingPower = cmtTrustedVals.TotalVotingPower() + } + + // The trusted fields may be nil. They may be filled before relaying messages to a client. + // The relayer is responsible for querying client and injecting appropriate trusted fields. + return &ibctm.Header{ + SignedHeader: signedHeader, + ValidatorSet: valSet, + TrustedHeight: trustedHeight, + TrustedValidators: trustedVals, + } +} + +// MakeBlockID copied unimported test functions from cmttypes to use them here +func MakeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) cmttypes.BlockID { + return cmttypes.BlockID{ + Hash: hash, + PartSetHeader: cmttypes.PartSetHeader{ + Total: partSetSize, + Hash: partSetHash, + }, + } +} + +// GetClientLatestHeight returns the latest height for the client state with the given client identifier. +// If an invalid client identifier is provided then a zero value height will be returned and testing will fail. +func (chain *TestChain) GetClientLatestHeight(clientID string) exported.Height { + latestHeight := chain.App.GetIBCKeeper().ClientKeeper.GetClientLatestHeight(chain.GetContext(), clientID) + require.False(chain.TB, latestHeight.IsZero()) + return latestHeight +} + +// GetTimeoutHeight is a convenience function which returns a IBC packet timeout height +// to be used for testing. It returns the current IBC height + 100 blocks +func (chain *TestChain) GetTimeoutHeight() clienttypes.Height { + return clienttypes.NewHeight(clienttypes.ParseChainID(chain.ChainID), uint64(chain.GetContext().BlockHeight())+100) +} + +// GetTimeoutTimestamp is a convenience function which returns a IBC packet timeout timestamp +// to be used for testing. It returns the current block timestamp + default timestamp delta (1 hour). +func (chain *TestChain) GetTimeoutTimestamp() uint64 { + return uint64(chain.GetContext().BlockTime().UnixNano()) + DefaultTimeoutTimestampDelta +} + +// GetTimeoutTimestampSecs is a convenience function which returns a IBC packet timeout timestamp in seconds +// to be used for testing. It returns the current block timestamp + default timestamp delta (1 hour). +func (chain *TestChain) GetTimeoutTimestampSecs() uint64 { + return uint64(chain.GetContext().BlockTime().Unix()) + uint64(time.Hour.Seconds()) +} + +// DeleteKey deletes the specified key from the ibc store. +func (chain *TestChain) DeleteKey(key []byte) { + storeKey := chain.GetSimApp().GetKey(exported.StoreKey) + kvStore := chain.GetContext().KVStore(storeKey) + kvStore.Delete(key) +} + +// IBCClientHeader will construct a 07-tendermint Header to update the light client +// on the counterparty chain. The trustedHeight must be passed in as a non-zero height. +func (chain *TestChain) IBCClientHeader(header *ibctm.Header, trustedHeight clienttypes.Height) (*ibctm.Header, error) { + if trustedHeight.IsZero() { + return nil, errorsmod.Wrap(ibctm.ErrInvalidHeaderHeight, "trustedHeight must be a non-zero height") + } + + cmtTrustedVals, ok := chain.TrustedValidators[trustedHeight.RevisionHeight] + if !ok { + return nil, fmt.Errorf("unable to find trusted validators at height %d", trustedHeight.RevisionHeight) + } + + trustedVals, err := cmtTrustedVals.ToProto() + if err != nil { + return nil, err + } + + header.TrustedHeight = trustedHeight + trustedVals.TotalVotingPower = cmtTrustedVals.TotalVotingPower() + header.TrustedValidators = trustedVals + + return header, nil +} + +// GetSenderAccount returns the sender account associated with the provided private key. +func (chain *TestChain) GetSenderAccount(privKey cryptotypes.PrivKey) SenderAccount { + account := chain.GetSimApp().AccountKeeper.GetAccount(chain.GetContext(), sdk.AccAddress(privKey.PubKey().Address())) + + return SenderAccount{ + SenderPrivKey: privKey, + SenderAccount: account, + } +} diff --git a/testing/chain_test.go b/testing/chain_test.go new file mode 100644 index 0000000..57433f2 --- /dev/null +++ b/testing/chain_test.go @@ -0,0 +1,77 @@ +package ibctesting_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" + + ibctesting "github.com/cosmos/ibc-go/v10/testing" +) + +func TestChangeValSet(t *testing.T) { + coord := ibctesting.NewCoordinator(t, 2) + chainA := coord.GetChain(ibctesting.GetChainID(1)) + chainB := coord.GetChain(ibctesting.GetChainID(2)) + + path := ibctesting.NewPath(chainA, chainB) + path.Setup() + + amount, ok := sdkmath.NewIntFromString("10000000000000000000") + require.True(t, ok) + amount2, ok := sdkmath.NewIntFromString("30000000000000000000") + require.True(t, ok) + + val, err := chainA.GetSimApp().StakingKeeper.GetValidators(chainA.GetContext(), 4) + require.NoError(t, err) + + chainA.GetSimApp().StakingKeeper.Delegate(chainA.GetContext(), chainA.SenderAccounts[1].SenderAccount.GetAddress(), //nolint:errcheck // ignore error for test + amount, types.Unbonded, val[1], true) + chainA.GetSimApp().StakingKeeper.Delegate(chainA.GetContext(), chainA.SenderAccounts[3].SenderAccount.GetAddress(), //nolint:errcheck // ignore error for test + amount2, types.Unbonded, val[3], true) + + coord.CommitBlock(chainA) + + // verify that update clients works even after validator update goes into effect + err = path.EndpointB.UpdateClient() + require.NoError(t, err) + err = path.EndpointB.UpdateClient() + require.NoError(t, err) +} + +func TestJailProposerValidator(t *testing.T) { + coord := ibctesting.NewCoordinator(t, 2) + chainA := coord.GetChain(ibctesting.GetChainID(1)) + chainB := coord.GetChain(ibctesting.GetChainID(2)) + + path := ibctesting.NewPath(chainA, chainB) + coord.Setup(path) + + // save valset length before jailing + valsetLen := len(chainA.Vals.Validators) + + // jail the proposer validator in chain A + propAddr := sdk.ConsAddress(chainA.Vals.Proposer.Address) + + err := chainA.GetSimApp().StakingKeeper.Jail( + chainA.GetContext(), propAddr) + require.NoError(t, err) + + coord.CommitBlock(chainA) + + // verify that update clients works even after validator update goes into effect + err = path.EndpointB.UpdateClient() + require.NoError(t, err) + err = path.EndpointB.UpdateClient() + require.NoError(t, err) + + // check that the jailing has taken effect in chain A + require.Equal(t, valsetLen-1, len(chainA.Vals.Validators)) + + // check that the valset in chain A has a new proposer + require.False(t, propAddr.Equals(sdk.ConsAddress(chainA.Vals.Proposer.Address))) +} diff --git a/testing/config.go b/testing/config.go new file mode 100644 index 0000000..2cd12cb --- /dev/null +++ b/testing/config.go @@ -0,0 +1,61 @@ +package ibctesting + +import ( + "time" + + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + "github.com/cosmos/ibc-go/v10/testing/mock" +) + +type ClientConfig interface { + GetClientType() string +} + +type TendermintConfig struct { + TrustLevel ibctm.Fraction + TrustingPeriod time.Duration + UnbondingPeriod time.Duration + MaxClockDrift time.Duration +} + +func NewTendermintConfig() *TendermintConfig { + return &TendermintConfig{ + TrustLevel: DefaultTrustLevel, + TrustingPeriod: TrustingPeriod, + UnbondingPeriod: UnbondingPeriod, + MaxClockDrift: MaxClockDrift, + } +} + +func (*TendermintConfig) GetClientType() string { + return exported.Tendermint +} + +type ConnectionConfig struct { + DelayPeriod uint64 + Version *connectiontypes.Version +} + +func NewConnectionConfig() *ConnectionConfig { + return &ConnectionConfig{ + DelayPeriod: DefaultDelayPeriod, + Version: ConnectionVersion, + } +} + +type ChannelConfig struct { + PortID string + Version string + Order channeltypes.Order +} + +func NewChannelConfig() *ChannelConfig { + return &ChannelConfig{ + PortID: mock.PortID, + Version: DefaultChannelVersion, + Order: channeltypes.UNORDERED, + } +} diff --git a/testing/coordinator.go b/testing/coordinator.go new file mode 100644 index 0000000..7ee3e9c --- /dev/null +++ b/testing/coordinator.go @@ -0,0 +1,161 @@ +package ibctesting + +import ( + "fmt" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +var ( + ChainIDPrefix = "testchain" + // to disable revision format, set ChainIDSuffix to "" + ChainIDSuffix = "-1" + globalStartTime = time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC) + TimeIncrement = time.Second * 5 +) + +// Coordinator is a testing struct which contains N TestChain's. It handles keeping all chains +// in sync with regards to time. +type Coordinator struct { + *testing.T + + CurrentTime time.Time + Chains map[string]*TestChain +} + +// NewCoordinator initializes Coordinator with N TestChain's +func NewCoordinator(t *testing.T, n int) *Coordinator { + t.Helper() + return NewCustomAppCoordinator(t, n, DefaultTestingAppInit) +} + +// NewCustomAppCoordinator initializes a Coordinator with N TestChain's using the given AppCreator function. +func NewCustomAppCoordinator(t *testing.T, n int, appCreator AppCreator) *Coordinator { + t.Helper() + chains := make(map[string]*TestChain) + coord := &Coordinator{ + T: t, + CurrentTime: globalStartTime, + } + + for i := 1; i <= n; i++ { + chainID := GetChainID(i) + chains[chainID] = NewCustomAppTestChain(t, coord, chainID, appCreator) + } + coord.Chains = chains + + return coord +} + +// IncrementTime iterates through all the TestChain's and increments their current header time +// by 5 seconds. +// +// CONTRACT: this function must be called after every Commit on any TestChain. +func (coord *Coordinator) IncrementTime() { + coord.IncrementTimeBy(TimeIncrement) +} + +// IncrementTimeBy iterates through all the TestChain's and increments their current header time +// by specified time. +func (coord *Coordinator) IncrementTimeBy(increment time.Duration) { + coord.CurrentTime = coord.CurrentTime.Add(increment).UTC() + coord.UpdateTime() +} + +// UpdateTime updates all clocks for the TestChains to the current global time. +func (coord *Coordinator) UpdateTime() { + for _, chain := range coord.Chains { + coord.UpdateTimeForChain(chain) + } +} + +// UpdateTimeForChain updates the clock for a specific chain. +func (coord *Coordinator) UpdateTimeForChain(chain *TestChain) { + chain.ProposedHeader.Time = coord.CurrentTime.UTC() +} + +// Setup constructs a TM client, connection, and channel on both chains provided. It will +// fail if any error occurs. +// Deprecated: please use path.Setup(), this function will be removed in v10 +func (*Coordinator) Setup(path *Path) { + path.Setup() +} + +// SetupClients is a helper function to create clients on both chains. It assumes the +// caller does not anticipate any errors. +// Deprecated: please use path.SetupClients(), this function will be removed in v10 +func (*Coordinator) SetupClients(path *Path) { + path.SetupClients() +} + +// SetupConnections is a helper function to create clients and the appropriate +// connections on both the source and counterparty chain. It assumes the caller does not +// anticipate any errors. +// Deprecated: please use path.SetupConnections(), this function will be removed in v10 +func (*Coordinator) SetupConnections(path *Path) { + path.SetupConnections() +} + +// CreateConnections constructs and executes connection handshake messages in order to create +// OPEN channels on chainA and chainB. The connection information of for chainA and chainB +// are returned within a TestConnection struct. The function expects the connections to be +// successfully opened otherwise testing will fail. +// Deprecated: please use path.CreateConnections(), this function will be removed in v10 +func (*Coordinator) CreateConnections(path *Path) { + path.CreateConnections() +} + +// CreateMockChannels constructs and executes channel handshake messages to create OPEN +// channels that use a mock application module that returns nil on all callbacks. This +// function is expects the channels to be successfully opened otherwise testing will +// fail. +func (*Coordinator) CreateMockChannels(path *Path) { + path.EndpointA.ChannelConfig.PortID = MockPort + path.EndpointB.ChannelConfig.PortID = MockPort + + path.CreateChannels() +} + +// CreateTransferChannels constructs and executes channel handshake messages to create OPEN +// ibc-transfer channels on chainA and chainB. The function expects the channels to be +// successfully opened otherwise testing will fail. +func (*Coordinator) CreateTransferChannels(path *Path) { + path.EndpointA.ChannelConfig.PortID = TransferPort + path.EndpointB.ChannelConfig.PortID = TransferPort + + path.CreateChannels() +} + +// GetChain returns the TestChain using the given chainID and returns an error if it does +// not exist. +func (coord *Coordinator) GetChain(chainID string) *TestChain { + chain, found := coord.Chains[chainID] + require.True(coord.T, found, fmt.Sprintf("%s chain does not exist", chainID)) + return chain +} + +// GetChainID returns the chainID used for the provided index. +func GetChainID(index int) string { + return ChainIDPrefix + strconv.Itoa(index) + ChainIDSuffix +} + +// CommitBlock commits a block on the provided indexes and then increments the global time. +// +// CONTRACT: the passed in list of indexes must not contain duplicates +func (coord *Coordinator) CommitBlock(chains ...*TestChain) { + for _, chain := range chains { + chain.NextBlock() + } + coord.IncrementTime() +} + +// CommitNBlocks commits n blocks to state and updates the block height by 1 for each commit. +func (coord *Coordinator) CommitNBlocks(chain *TestChain, n uint64) { + for i := uint64(0); i < n; i++ { + chain.NextBlock() + coord.IncrementTime() + } +} diff --git a/testing/endpoint.go b/testing/endpoint.go new file mode 100644 index 0000000..e2d8c4c --- /dev/null +++ b/testing/endpoint.go @@ -0,0 +1,709 @@ +package ibctesting + +import ( + "errors" + "fmt" + "strings" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/baseapp" + + abci "github.com/cometbft/cometbft/abci/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + commitmenttypesv2 "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types/v2" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" +) + +// Endpoint is a which represents a channel endpoint and its associated +// client and connections. It contains client, connection, and channel +// configuration parameters. Endpoint functions will utilize the parameters +// set in the configuration structs when executing IBC messages. +type Endpoint struct { + Chain *TestChain + Counterparty *Endpoint + ClientID string + ConnectionID string + ChannelID string + + ClientConfig ClientConfig + ConnectionConfig *ConnectionConfig + ChannelConfig *ChannelConfig + + MerklePathPrefix commitmenttypesv2.MerklePath + // disableUniqueChannelIDs is used to enforce, in a test, + // the old way to generate channel IDs (all channels are called channel-0) + // It is used only by one test suite and should not be used for new tests. + disableUniqueChannelIDs bool +} + +// NewEndpoint constructs a new endpoint without the counterparty. +// CONTRACT: the counterparty endpoint must be set by the caller. +func NewEndpoint( + chain *TestChain, clientConfig ClientConfig, + connectionConfig *ConnectionConfig, channelConfig *ChannelConfig, +) *Endpoint { + return &Endpoint{ + Chain: chain, + ClientConfig: clientConfig, + ConnectionConfig: connectionConfig, + ChannelConfig: channelConfig, + MerklePathPrefix: MerklePath, + } +} + +// NewDefaultEndpoint constructs a new endpoint using default values. +// CONTRACT: the counterparty endpoitn must be set by the caller. +func NewDefaultEndpoint(chain *TestChain) *Endpoint { + return &Endpoint{ + Chain: chain, + ClientConfig: NewTendermintConfig(), + ConnectionConfig: NewConnectionConfig(), + ChannelConfig: NewChannelConfig(), + MerklePathPrefix: MerklePath, + } +} + +// QueryProof queries proof associated with this endpoint using the latest client state +// height on the counterparty chain. +func (endpoint *Endpoint) QueryProof(key []byte) ([]byte, clienttypes.Height) { + // obtain the counterparty client height. + latestCounterpartyHeight := endpoint.Counterparty.GetClientLatestHeight() + // query proof on the counterparty using the latest height of the IBC client + return endpoint.QueryProofAtHeight(key, latestCounterpartyHeight.GetRevisionHeight()) +} + +// QueryProofAtHeight queries proof associated with this endpoint using the proof height +// provided +func (endpoint *Endpoint) QueryProofAtHeight(key []byte, height uint64) ([]byte, clienttypes.Height) { + // query proof on the counterparty using the latest height of the IBC client + return endpoint.Chain.QueryProofAtHeight(key, int64(height)) +} + +// CreateClient creates an IBC client on the endpoint. It will update the +// clientID for the endpoint if the message is successfully executed. +// NOTE: a solo machine client will be created with an empty diversifier. +func (endpoint *Endpoint) CreateClient() (err error) { + // ensure counterparty has committed state + endpoint.Counterparty.Chain.NextBlock() + + var ( + clientState exported.ClientState + consensusState exported.ConsensusState + ) + + switch endpoint.ClientConfig.GetClientType() { + case exported.Tendermint: + tmConfig, ok := endpoint.ClientConfig.(*TendermintConfig) + require.True(endpoint.Chain.TB, ok) + + height, ok := endpoint.Counterparty.Chain.LatestCommittedHeader.GetHeight().(clienttypes.Height) + require.True(endpoint.Chain.TB, ok) + clientState = ibctm.NewClientState( + endpoint.Counterparty.Chain.ChainID, tmConfig.TrustLevel, tmConfig.TrustingPeriod, tmConfig.UnbondingPeriod, tmConfig.MaxClockDrift, + height, commitmenttypes.GetSDKSpecs(), UpgradePath) + consensusState = endpoint.Counterparty.Chain.LatestCommittedHeader.ConsensusState() + case exported.Solomachine: + // TODO + // solo := NewSolomachine(endpoint.Chain.TB, endpoint.Chain.Codec, clientID, "", 1) + // clientState = solo.ClientState() + // consensusState = solo.ConsensusState() + default: + err = fmt.Errorf("client type %s is not supported", endpoint.ClientConfig.GetClientType()) + } + + if err != nil { + return err + } + + msg, err := clienttypes.NewMsgCreateClient( + clientState, consensusState, endpoint.Chain.SenderAccount.GetAddress().String(), + ) + require.NoError(endpoint.Chain.TB, err) + + res, err := endpoint.Chain.SendMsgs(msg) + if err != nil { + return err + } + + endpoint.ClientID, err = ParseClientIDFromEvents(res.Events) + require.NoError(endpoint.Chain.TB, err) + + return nil +} + +// UpdateClient updates the IBC client associated with the endpoint. +func (endpoint *Endpoint) UpdateClient() (err error) { + // ensure counterparty has committed state + endpoint.Chain.Coordinator.CommitBlock(endpoint.Counterparty.Chain) + + var header exported.ClientMessage + + switch endpoint.ClientConfig.GetClientType() { + case exported.Tendermint: + trustedHeight, ok := endpoint.GetClientLatestHeight().(clienttypes.Height) + require.True(endpoint.Chain.TB, ok) + header, err = endpoint.Counterparty.Chain.IBCClientHeader(endpoint.Counterparty.Chain.LatestCommittedHeader, trustedHeight) + default: + err = fmt.Errorf("client type %s is not supported", endpoint.ClientConfig.GetClientType()) + } + + if err != nil { + return err + } + + msg, err := clienttypes.NewMsgUpdateClient( + endpoint.ClientID, header, + endpoint.Chain.SenderAccount.GetAddress().String(), + ) + require.NoError(endpoint.Chain.TB, err) + + return endpoint.Chain.sendMsgs(msg) +} + +// FreezeClient freezes the IBC client associated with the endpoint. +func (endpoint *Endpoint) FreezeClient() { + clientState := endpoint.Chain.GetClientState(endpoint.ClientID) + tmClientState, ok := clientState.(*ibctm.ClientState) + require.True(endpoint.Chain.TB, ok) + + tmClientState.FrozenHeight = clienttypes.NewHeight(0, 1) + endpoint.Chain.App.GetIBCKeeper().ClientKeeper.SetClientState(endpoint.Chain.GetContext(), endpoint.ClientID, tmClientState) +} + +// UpgradeChain will upgrade a chain's chainID to the next revision number. +// It will also update the counterparty client. +// TODO: implement actual upgrade chain functionality via scheduling an upgrade +// and upgrading the client via MsgUpgradeClient +// see reference https://github.com/cosmos/ibc-go/pull/1169 +func (endpoint *Endpoint) UpgradeChain() error { + if strings.TrimSpace(endpoint.Counterparty.ClientID) == "" { + return errors.New("cannot upgrade chain if there is no counterparty client") + } + + clientState := endpoint.Counterparty.GetClientState() + tmClientState, ok := clientState.(*ibctm.ClientState) + require.True(endpoint.Chain.TB, ok) + + // increment revision number in chainID + oldChainID := tmClientState.ChainId + if !clienttypes.IsRevisionFormat(oldChainID) { + return fmt.Errorf("cannot upgrade chain which is not of revision format: %s", oldChainID) + } + + revisionNumber := clienttypes.ParseChainID(oldChainID) + newChainID, err := clienttypes.SetRevisionNumber(oldChainID, revisionNumber+1) + if err != nil { + return err + } + + // update chain + baseapp.SetChainID(newChainID)(endpoint.Chain.App.GetBaseApp()) + endpoint.Chain.ChainID = newChainID + endpoint.Chain.ProposedHeader.ChainID = newChainID + endpoint.Chain.NextBlock() // commit changes + + // update counterparty client manually + tmClientState.ChainId = newChainID + tmClientState.LatestHeight = clienttypes.NewHeight(revisionNumber+1, tmClientState.LatestHeight.GetRevisionHeight()+1) + + endpoint.Counterparty.SetClientState(clientState) + + tmConsensusState := &ibctm.ConsensusState{ + Timestamp: endpoint.Chain.LatestCommittedHeader.GetTime(), + Root: commitmenttypes.NewMerkleRoot(endpoint.Chain.LatestCommittedHeader.Header.GetAppHash()), + NextValidatorsHash: endpoint.Chain.LatestCommittedHeader.Header.NextValidatorsHash, + } + + latestHeight := endpoint.Counterparty.GetClientLatestHeight() + + endpoint.Counterparty.SetConsensusState(tmConsensusState, latestHeight) + + // ensure the next update isn't identical to the one set in state + endpoint.Chain.Coordinator.IncrementTime() + endpoint.Chain.NextBlock() + + return endpoint.Counterparty.UpdateClient() +} + +// ConnOpenInit will construct and execute a MsgConnectionOpenInit on the associated endpoint. +func (endpoint *Endpoint) ConnOpenInit() error { + msg := connectiontypes.NewMsgConnectionOpenInit( + endpoint.ClientID, + endpoint.Counterparty.ClientID, + endpoint.Counterparty.Chain.GetPrefix(), DefaultOpenInitVersion, endpoint.ConnectionConfig.DelayPeriod, + endpoint.Chain.SenderAccount.GetAddress().String(), + ) + res, err := endpoint.Chain.SendMsgs(msg) + if err != nil { + return err + } + + endpoint.ConnectionID, err = ParseConnectionIDFromEvents(res.Events) + require.NoError(endpoint.Chain.TB, err) + + return nil +} + +// ConnOpenTry will construct and execute a MsgConnectionOpenTry on the associated endpoint. +func (endpoint *Endpoint) ConnOpenTry() error { + err := endpoint.UpdateClient() + require.NoError(endpoint.Chain.TB, err) + + initProof, proofHeight := endpoint.QueryConnectionHandshakeProof() + + msg := connectiontypes.NewMsgConnectionOpenTry( + endpoint.ClientID, endpoint.Counterparty.ConnectionID, endpoint.Counterparty.ClientID, + endpoint.Counterparty.Chain.GetPrefix(), []*connectiontypes.Version{ConnectionVersion}, + endpoint.ConnectionConfig.DelayPeriod, initProof, proofHeight, + endpoint.Chain.SenderAccount.GetAddress().String(), + ) + res, err := endpoint.Chain.SendMsgs(msg) + if err != nil { + return err + } + + if endpoint.ConnectionID == "" { + endpoint.ConnectionID, err = ParseConnectionIDFromEvents(res.Events) + require.NoError(endpoint.Chain.TB, err) + } + + return nil +} + +// ConnOpenAck will construct and execute a MsgConnectionOpenAck on the associated endpoint. +func (endpoint *Endpoint) ConnOpenAck() error { + err := endpoint.UpdateClient() + require.NoError(endpoint.Chain.TB, err) + + tryProof, proofHeight := endpoint.QueryConnectionHandshakeProof() + + msg := connectiontypes.NewMsgConnectionOpenAck( + endpoint.ConnectionID, endpoint.Counterparty.ConnectionID, // testing doesn't use flexible selection + tryProof, proofHeight, ConnectionVersion, + endpoint.Chain.SenderAccount.GetAddress().String(), + ) + return endpoint.Chain.sendMsgs(msg) +} + +// ConnOpenConfirm will construct and execute a MsgConnectionOpenConfirm on the associated endpoint. +func (endpoint *Endpoint) ConnOpenConfirm() error { + err := endpoint.UpdateClient() + require.NoError(endpoint.Chain.TB, err) + + connectionKey := host.ConnectionKey(endpoint.Counterparty.ConnectionID) + proof, height := endpoint.Counterparty.Chain.QueryProof(connectionKey) + + msg := connectiontypes.NewMsgConnectionOpenConfirm( + endpoint.ConnectionID, + proof, height, + endpoint.Chain.SenderAccount.GetAddress().String(), + ) + return endpoint.Chain.sendMsgs(msg) +} + +// QueryConnectionHandshakeProof returns all the proofs necessary to execute OpenTry or Open Ack of +// the connection handshakes. It returns the proof of the counterparty connection and the proof height. +func (endpoint *Endpoint) QueryConnectionHandshakeProof() ( + connectionProof []byte, proofHeight clienttypes.Height, +) { + // query proof for the connection on the counterparty + connectionKey := host.ConnectionKey(endpoint.Counterparty.ConnectionID) + connectionProof, proofHeight = endpoint.Counterparty.QueryProof(connectionKey) + + return connectionProof, proofHeight +} + +var sequenceNumber int + +// IncrementNextChannelSequence incrementes the value "nextChannelSequence" in the store, +// which is used to determine the next channel ID. +// This guarantees that we'll have always different IDs while running tests. +func (endpoint *Endpoint) IncrementNextChannelSequence() { + if endpoint.disableUniqueChannelIDs { + return + } + sequenceNumber++ + endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.SetNextChannelSequence(endpoint.Chain.GetContext(), uint64(sequenceNumber)) +} + +// ChanOpenInit will construct and execute a MsgChannelOpenInit on the associated endpoint. +func (endpoint *Endpoint) ChanOpenInit() error { + endpoint.IncrementNextChannelSequence() + msg := channeltypes.NewMsgChannelOpenInit( + endpoint.ChannelConfig.PortID, + endpoint.ChannelConfig.Version, endpoint.ChannelConfig.Order, []string{endpoint.ConnectionID}, + endpoint.Counterparty.ChannelConfig.PortID, + endpoint.Chain.SenderAccount.GetAddress().String(), + ) + res, err := endpoint.Chain.SendMsgs(msg) + if err != nil { + return err + } + + endpoint.ChannelID, err = ParseChannelIDFromEvents(res.Events) + require.NoError(endpoint.Chain.TB, err) + + // update version to selected app version + // NOTE: this update must be performed after SendMsgs() + endpoint.ChannelConfig.Version = endpoint.GetChannel().Version + endpoint.Counterparty.ChannelConfig.Version = endpoint.GetChannel().Version + + return nil +} + +// ChanOpenTry will construct and execute a MsgChannelOpenTry on the associated endpoint. +func (endpoint *Endpoint) ChanOpenTry() error { + endpoint.IncrementNextChannelSequence() + err := endpoint.UpdateClient() + require.NoError(endpoint.Chain.TB, err) + + channelKey := host.ChannelKey(endpoint.Counterparty.ChannelConfig.PortID, endpoint.Counterparty.ChannelID) + proof, height := endpoint.Counterparty.Chain.QueryProof(channelKey) + + msg := channeltypes.NewMsgChannelOpenTry( + endpoint.ChannelConfig.PortID, + endpoint.ChannelConfig.Version, endpoint.ChannelConfig.Order, []string{endpoint.ConnectionID}, + endpoint.Counterparty.ChannelConfig.PortID, endpoint.Counterparty.ChannelID, endpoint.Counterparty.ChannelConfig.Version, + proof, height, + endpoint.Chain.SenderAccount.GetAddress().String(), + ) + res, err := endpoint.Chain.SendMsgs(msg) + if err != nil { + return err + } + + if endpoint.ChannelID == "" { + endpoint.ChannelID, err = ParseChannelIDFromEvents(res.Events) + require.NoError(endpoint.Chain.TB, err) + } + + // update version to selected app version + // NOTE: this update must be performed after the endpoint channelID is set + endpoint.ChannelConfig.Version = endpoint.GetChannel().Version + endpoint.Counterparty.ChannelConfig.Version = endpoint.GetChannel().Version + + return nil +} + +// ChanOpenAck will construct and execute a MsgChannelOpenAck on the associated endpoint. +func (endpoint *Endpoint) ChanOpenAck() error { + err := endpoint.UpdateClient() + require.NoError(endpoint.Chain.TB, err) + + channelKey := host.ChannelKey(endpoint.Counterparty.ChannelConfig.PortID, endpoint.Counterparty.ChannelID) + proof, height := endpoint.Counterparty.Chain.QueryProof(channelKey) + + msg := channeltypes.NewMsgChannelOpenAck( + endpoint.ChannelConfig.PortID, endpoint.ChannelID, + endpoint.Counterparty.ChannelID, endpoint.Counterparty.ChannelConfig.Version, // testing doesn't use flexible selection + proof, height, + endpoint.Chain.SenderAccount.GetAddress().String(), + ) + + if err = endpoint.Chain.sendMsgs(msg); err != nil { + return err + } + + endpoint.ChannelConfig.Version = endpoint.GetChannel().Version + + return nil +} + +// ChanOpenConfirm will construct and execute a MsgChannelOpenConfirm on the associated endpoint. +func (endpoint *Endpoint) ChanOpenConfirm() error { + err := endpoint.UpdateClient() + require.NoError(endpoint.Chain.TB, err) + + channelKey := host.ChannelKey(endpoint.Counterparty.ChannelConfig.PortID, endpoint.Counterparty.ChannelID) + proof, height := endpoint.Counterparty.Chain.QueryProof(channelKey) + + msg := channeltypes.NewMsgChannelOpenConfirm( + endpoint.ChannelConfig.PortID, endpoint.ChannelID, + proof, height, + endpoint.Chain.SenderAccount.GetAddress().String(), + ) + return endpoint.Chain.sendMsgs(msg) +} + +// ChanCloseInit will construct and execute a MsgChannelCloseInit on the associated endpoint. +// +// NOTE: does not work with ibc-transfer module +func (endpoint *Endpoint) ChanCloseInit() error { + msg := channeltypes.NewMsgChannelCloseInit( + endpoint.ChannelConfig.PortID, endpoint.ChannelID, + endpoint.Chain.SenderAccount.GetAddress().String(), + ) + return endpoint.Chain.sendMsgs(msg) +} + +// SendPacket sends a packet through the channel keeper using the associated endpoint +// The counterparty client is updated so proofs can be sent to the counterparty chain. +// The packet sequence generated for the packet to be sent is returned. An error +// is returned if one occurs. +func (endpoint *Endpoint) SendPacket( + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + data []byte, +) (uint64, error) { + // no need to send message, acting as a module + sequence, err := endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.SendPacket(endpoint.Chain.GetContext(), endpoint.ChannelConfig.PortID, endpoint.ChannelID, timeoutHeight, timeoutTimestamp, data) + if err != nil { + return 0, err + } + + // commit changes since no message was sent + endpoint.Chain.Coordinator.CommitBlock(endpoint.Chain) + + err = endpoint.Counterparty.UpdateClient() + if err != nil { + return 0, err + } + + return sequence, nil +} + +// RecvPacket receives a packet on the associated endpoint. +// The counterparty client is updated. +func (endpoint *Endpoint) RecvPacket(packet channeltypes.Packet) error { + _, err := endpoint.RecvPacketWithResult(packet) + if err != nil { + return err + } + + return nil +} + +// RecvPacketWithResult receives a packet on the associated endpoint and the result +// of the transaction is returned. The counterparty client is updated. +func (endpoint *Endpoint) RecvPacketWithResult(packet channeltypes.Packet) (*abci.ExecTxResult, error) { + // get proof of packet commitment on source + packetKey := host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + proof, proofHeight := endpoint.Counterparty.Chain.QueryProof(packetKey) + + recvMsg := channeltypes.NewMsgRecvPacket(packet, proof, proofHeight, endpoint.Chain.SenderAccount.GetAddress().String()) + + // receive on counterparty and update source client + res, err := endpoint.Chain.SendMsgs(recvMsg) + if err != nil { + return nil, err + } + + if err := endpoint.Counterparty.UpdateClient(); err != nil { + return nil, err + } + + return res, nil +} + +// WriteAcknowledgement writes an acknowledgement on the channel associated with the endpoint. +// The counterparty client is updated. +func (endpoint *Endpoint) WriteAcknowledgement(ack exported.Acknowledgement, packet exported.PacketI) error { + // no need to send message, acting as a handler + err := endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.WriteAcknowledgement(endpoint.Chain.GetContext(), packet, ack) + if err != nil { + return err + } + + // commit changes since no message was sent + endpoint.Chain.Coordinator.CommitBlock(endpoint.Chain) + + return endpoint.Counterparty.UpdateClient() +} + +// AcknowledgePacket sends a MsgAcknowledgement to the channel associated with the endpoint. +func (endpoint *Endpoint) AcknowledgePacket(packet channeltypes.Packet, ack []byte) error { + // get proof of acknowledgement on counterparty + packetKey := host.PacketAcknowledgementKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + proof, proofHeight := endpoint.Counterparty.QueryProof(packetKey) + + ackMsg := channeltypes.NewMsgAcknowledgement(packet, ack, proof, proofHeight, endpoint.Chain.SenderAccount.GetAddress().String()) + + return endpoint.Chain.sendMsgs(ackMsg) +} + +// AcknowledgePacketWithResult sends a MsgAcknowledgement to the channel associated with the endpoint and returns the result. +func (endpoint *Endpoint) AcknowledgePacketWithResult(packet channeltypes.Packet, ack []byte) (*abci.ExecTxResult, error) { + // get proof of acknowledgement on counterparty + packetKey := host.PacketAcknowledgementKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + proof, proofHeight := endpoint.Counterparty.QueryProof(packetKey) + + ackMsg := channeltypes.NewMsgAcknowledgement(packet, ack, proof, proofHeight, endpoint.Chain.SenderAccount.GetAddress().String()) + + return endpoint.Chain.SendMsgs(ackMsg) +} + +// TimeoutPacketWithResult sends a MsgTimeout to the channel associated with the endpoint. +func (endpoint *Endpoint) TimeoutPacketWithResult(packet channeltypes.Packet) (*abci.ExecTxResult, error) { + // get proof for timeout based on channel order + var packetKey []byte + + switch endpoint.ChannelConfig.Order { + case channeltypes.ORDERED: + packetKey = host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) + case channeltypes.UNORDERED: + packetKey = host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + default: + return nil, fmt.Errorf("unsupported order type %s", endpoint.ChannelConfig.Order) + } + + counterparty := endpoint.Counterparty + proof, proofHeight := counterparty.QueryProof(packetKey) + nextSeqRecv, found := counterparty.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextSequenceRecv(counterparty.Chain.GetContext(), counterparty.ChannelConfig.PortID, counterparty.ChannelID) + require.True(endpoint.Chain.TB, found) + + timeoutMsg := channeltypes.NewMsgTimeout( + packet, nextSeqRecv, + proof, proofHeight, endpoint.Chain.SenderAccount.GetAddress().String(), + ) + + return endpoint.Chain.SendMsgs(timeoutMsg) +} + +// TimeoutPacket sends a MsgTimeout to the channel associated with the endpoint. +func (endpoint *Endpoint) TimeoutPacket(packet channeltypes.Packet) error { + _, err := endpoint.TimeoutPacketWithResult(packet) + return err +} + +// TimeoutOnClose sends a MsgTimeoutOnClose to the channel associated with the endpoint. +func (endpoint *Endpoint) TimeoutOnClose(packet channeltypes.Packet) error { + // get proof for timeout based on channel order + var packetKey []byte + + switch endpoint.ChannelConfig.Order { + case channeltypes.ORDERED: + packetKey = host.NextSequenceRecvKey(packet.GetDestPort(), packet.GetDestChannel()) + case channeltypes.UNORDERED: + packetKey = host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + default: + return fmt.Errorf("unsupported order type %s", endpoint.ChannelConfig.Order) + } + + proof, proofHeight := endpoint.Counterparty.QueryProof(packetKey) + + channelKey := host.ChannelKey(packet.GetDestPort(), packet.GetDestChannel()) + closedProof, _ := endpoint.Counterparty.QueryProof(channelKey) + + nextSeqRecv, found := endpoint.Counterparty.Chain.App.GetIBCKeeper().ChannelKeeper.GetNextSequenceRecv(endpoint.Counterparty.Chain.GetContext(), endpoint.Counterparty.ChannelConfig.PortID, endpoint.Counterparty.ChannelID) + require.True(endpoint.Chain.TB, found) + + timeoutOnCloseMsg := channeltypes.NewMsgTimeoutOnClose( + packet, nextSeqRecv, + proof, closedProof, proofHeight, endpoint.Chain.SenderAccount.GetAddress().String(), + ) + + return endpoint.Chain.sendMsgs(timeoutOnCloseMsg) +} + +// Deprecated: usage of this function should be replaced by `UpdateChannel` +// SetChannelState sets a channel state +func (endpoint *Endpoint) SetChannelState(state channeltypes.State) error { + channel := endpoint.GetChannel() + + channel.State = state + endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.SetChannel(endpoint.Chain.GetContext(), endpoint.ChannelConfig.PortID, endpoint.ChannelID, channel) + + endpoint.Chain.Coordinator.CommitBlock(endpoint.Chain) + + return endpoint.Counterparty.UpdateClient() +} + +// UpdateChannel updates the channel associated with the given endpoint. It accepts a +// closure which takes a channel allowing the caller to modify its fields. +func (endpoint *Endpoint) UpdateChannel(updater func(channel *channeltypes.Channel)) { + channel := endpoint.GetChannel() + updater(&channel) + endpoint.SetChannel(channel) + + endpoint.Chain.Coordinator.CommitBlock(endpoint.Chain) + + err := endpoint.Counterparty.UpdateClient() + require.NoError(endpoint.Chain.TB, err) +} + +// GetClientLatestHeight returns the latest height for the client state for this endpoint. +// The client state is expected to exist otherwise testing will fail. +func (endpoint *Endpoint) GetClientLatestHeight() exported.Height { + return endpoint.Chain.GetClientLatestHeight(endpoint.ClientID) +} + +// GetClientState retrieves the client state for this endpoint. The +// client state is expected to exist otherwise testing will fail. +func (endpoint *Endpoint) GetClientState() exported.ClientState { + return endpoint.Chain.GetClientState(endpoint.ClientID) +} + +// SetClientState sets the client state for this endpoint. +func (endpoint *Endpoint) SetClientState(clientState exported.ClientState) { + endpoint.Chain.App.GetIBCKeeper().ClientKeeper.SetClientState(endpoint.Chain.GetContext(), endpoint.ClientID, clientState) +} + +// GetConsensusState retrieves the Consensus State for this endpoint at the provided height. +// The consensus state is expected to exist otherwise testing will fail. +func (endpoint *Endpoint) GetConsensusState(height exported.Height) exported.ConsensusState { + consensusState, found := endpoint.Chain.GetConsensusState(endpoint.ClientID, height) + require.True(endpoint.Chain.TB, found) + + return consensusState +} + +// SetConsensusState sets the consensus state for this endpoint. +func (endpoint *Endpoint) SetConsensusState(consensusState exported.ConsensusState, height exported.Height) { + endpoint.Chain.App.GetIBCKeeper().ClientKeeper.SetClientConsensusState(endpoint.Chain.GetContext(), endpoint.ClientID, height, consensusState) +} + +// GetConnection retrieves an IBC Connection for the endpoint. The +// connection is expected to exist otherwise testing will fail. +func (endpoint *Endpoint) GetConnection() connectiontypes.ConnectionEnd { + connection, found := endpoint.Chain.App.GetIBCKeeper().ConnectionKeeper.GetConnection(endpoint.Chain.GetContext(), endpoint.ConnectionID) + require.True(endpoint.Chain.TB, found) + + return connection +} + +// SetConnection sets the connection for this endpoint. +func (endpoint *Endpoint) SetConnection(connection connectiontypes.ConnectionEnd) { + endpoint.Chain.App.GetIBCKeeper().ConnectionKeeper.SetConnection(endpoint.Chain.GetContext(), endpoint.ConnectionID, connection) +} + +// GetChannel retrieves an IBC Channel for the endpoint. The channel +// is expected to exist otherwise testing will fail. +func (endpoint *Endpoint) GetChannel() channeltypes.Channel { + channel, found := endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.GetChannel(endpoint.Chain.GetContext(), endpoint.ChannelConfig.PortID, endpoint.ChannelID) + require.True(endpoint.Chain.TB, found) + + return channel +} + +// SetChannel sets the channel for this endpoint. +func (endpoint *Endpoint) SetChannel(channel channeltypes.Channel) { + endpoint.Chain.App.GetIBCKeeper().ChannelKeeper.SetChannel(endpoint.Chain.GetContext(), endpoint.ChannelConfig.PortID, endpoint.ChannelID, channel) +} + +// QueryClientStateProof performs and abci query for a client stat associated +// with this endpoint and returns the ClientState along with the proof. +func (endpoint *Endpoint) QueryClientStateProof() (exported.ClientState, []byte) { + // retrieve client state to provide proof for + clientState := endpoint.GetClientState() + + clientKey := host.FullClientStateKey(endpoint.ClientID) + clientProof, _ := endpoint.QueryProof(clientKey) + + return clientState, clientProof +} + +// UpdateConnection updates the connection associated with the given endpoint. It accepts a +// closure which takes a connection allowing the caller to modify the connection fields. +func (endpoint *Endpoint) UpdateConnection(updater func(connection *connectiontypes.ConnectionEnd)) { + connection := endpoint.GetConnection() + updater(&connection) + + endpoint.SetConnection(connection) +} diff --git a/testing/endpoint_v2.go b/testing/endpoint_v2.go new file mode 100644 index 0000000..735183c --- /dev/null +++ b/testing/endpoint_v2.go @@ -0,0 +1,105 @@ +package ibctesting + +import ( + "github.com/cosmos/gogoproto/proto" + + sdk "github.com/cosmos/cosmos-sdk/types" + + clientv2types "github.com/cosmos/ibc-go/v10/modules/core/02-client/v2/types" + channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + hostv2 "github.com/cosmos/ibc-go/v10/modules/core/24-host/v2" +) + +// RegisterCounterparty will construct and execute a MsgRegisterCounterparty on the associated endpoint. +func (endpoint *Endpoint) RegisterCounterparty() (err error) { + msg := clientv2types.NewMsgRegisterCounterparty(endpoint.ClientID, endpoint.Counterparty.MerklePathPrefix.KeyPath, endpoint.Counterparty.ClientID, endpoint.Chain.SenderAccount.GetAddress().String()) + + // setup counterparty + _, err = endpoint.Chain.SendMsgs(msg) + + return err +} + +// MsgSendPacket sends a packet on the associated endpoint using a predefined sender. The constructed packet is returned. +func (endpoint *Endpoint) MsgSendPacket(timeoutTimestamp uint64, payload channeltypesv2.Payload) (channeltypesv2.Packet, error) { + senderAccount := SenderAccount{ + SenderPrivKey: endpoint.Chain.SenderPrivKey, + SenderAccount: endpoint.Chain.SenderAccount, + } + + return endpoint.MsgSendPacketWithSender(timeoutTimestamp, payload, senderAccount) +} + +// MsgSendPacketWithSender sends a packet on the associated endpoint using the provided sender. The constructed packet is returned. +func (endpoint *Endpoint) MsgSendPacketWithSender(timeoutTimestamp uint64, payload channeltypesv2.Payload, sender SenderAccount) (channeltypesv2.Packet, error) { + msgSendPacket := channeltypesv2.NewMsgSendPacket(endpoint.ClientID, timeoutTimestamp, sender.SenderAccount.GetAddress().String(), payload) + + res, err := endpoint.Chain.SendMsgsWithSender(sender, msgSendPacket) + if err != nil { + return channeltypesv2.Packet{}, err + } + + if err := endpoint.Counterparty.UpdateClient(); err != nil { + return channeltypesv2.Packet{}, err + } + + // TODO: parse the packet from events instead of from the response. https://github.com/cosmos/ibc-go/issues/7459 + // get sequence from msg response + var msgData sdk.TxMsgData + err = proto.Unmarshal(res.Data, &msgData) + if err != nil { + return channeltypesv2.Packet{}, err + } + msgResponse := msgData.MsgResponses[0] + var sendResponse channeltypesv2.MsgSendPacketResponse + err = proto.Unmarshal(msgResponse.Value, &sendResponse) + if err != nil { + return channeltypesv2.Packet{}, err + } + packet := channeltypesv2.NewPacket(sendResponse.Sequence, endpoint.ClientID, endpoint.Counterparty.ClientID, timeoutTimestamp, payload) + + return packet, nil +} + +// MsgRecvPacket sends a MsgRecvPacket on the associated endpoint with the provided packet. +func (endpoint *Endpoint) MsgRecvPacket(packet channeltypesv2.Packet) error { + // get proof of packet commitment from chainA + packetKey := hostv2.PacketCommitmentKey(packet.SourceClient, packet.Sequence) + proof, proofHeight := endpoint.Counterparty.QueryProof(packetKey) + + msg := channeltypesv2.NewMsgRecvPacket(packet, proof, proofHeight, endpoint.Chain.SenderAccount.GetAddress().String()) + + if err := endpoint.Chain.sendMsgs(msg); err != nil { + return err + } + + return endpoint.Counterparty.UpdateClient() +} + +// MsgAcknowledgePacket sends a MsgAcknowledgement on the associated endpoint with the provided packet and ack. +func (endpoint *Endpoint) MsgAcknowledgePacket(packet channeltypesv2.Packet, ack channeltypesv2.Acknowledgement) error { + packetKey := hostv2.PacketAcknowledgementKey(packet.DestinationClient, packet.Sequence) + proof, proofHeight := endpoint.Counterparty.QueryProof(packetKey) + + msg := channeltypesv2.NewMsgAcknowledgement(packet, ack, proof, proofHeight, endpoint.Chain.SenderAccount.GetAddress().String()) + + if err := endpoint.Chain.sendMsgs(msg); err != nil { + return err + } + + return endpoint.Counterparty.UpdateClient() +} + +// MsgTimeoutPacket sends a MsgTimeout on the associated endpoint with the provided packet. +func (endpoint *Endpoint) MsgTimeoutPacket(packet channeltypesv2.Packet) error { + packetKey := hostv2.PacketReceiptKey(packet.DestinationClient, packet.Sequence) + proof, proofHeight := endpoint.Counterparty.QueryProof(packetKey) + + msg := channeltypesv2.NewMsgTimeout(packet, proof, proofHeight, endpoint.Chain.SenderAccount.GetAddress().String()) + + if err := endpoint.Chain.sendMsgs(msg); err != nil { + return err + } + + return endpoint.Counterparty.UpdateClient() +} diff --git a/testing/events.go b/testing/events.go new file mode 100644 index 0000000..6b32466 --- /dev/null +++ b/testing/events.go @@ -0,0 +1,341 @@ +package ibctesting + +import ( + "encoding/hex" + "errors" + "fmt" + "slices" + "strconv" + + "github.com/cosmos/gogoproto/proto" + testifysuite "github.com/stretchr/testify/suite" + + abci "github.com/cometbft/cometbft/abci/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" +) + +// ParseClientIDFromEvents parses events emitted from a MsgCreateClient and returns the +// client identifier. +func ParseClientIDFromEvents(events []abci.Event) (string, error) { + for _, ev := range events { + if ev.Type == clienttypes.EventTypeCreateClient { + if attribute, found := attributeByKey(ev.Attributes, clienttypes.AttributeKeyClientID); found { + return attribute.Value, nil + } + } + } + return "", errors.New("client identifier event attribute not found") +} + +// ParseConnectionIDFromEvents parses events emitted from a MsgConnectionOpenInit or +// MsgConnectionOpenTry and returns the connection identifier. +func ParseConnectionIDFromEvents(events []abci.Event) (string, error) { + for _, ev := range events { + if ev.Type == connectiontypes.EventTypeConnectionOpenInit || + ev.Type == connectiontypes.EventTypeConnectionOpenTry { + if attribute, found := attributeByKey(ev.Attributes, connectiontypes.AttributeKeyConnectionID); found { + return attribute.Value, nil + } + } + } + return "", errors.New("connection identifier event attribute not found") +} + +// ParseChannelIDFromEvents parses events emitted from a MsgChannelOpenInit or +// MsgChannelOpenTry or a MsgCreateChannel and returns the channel identifier. +func ParseChannelIDFromEvents(events []abci.Event) (string, error) { + for _, ev := range events { + if ev.Type == channeltypes.EventTypeChannelOpenInit || ev.Type == channeltypes.EventTypeChannelOpenTry { + if attribute, found := attributeByKey(ev.Attributes, channeltypes.AttributeKeyChannelID); found { + return attribute.Value, nil + } + } + } + return "", errors.New("channel identifier event attribute not found") +} + +// ParseV1PacketFromEvents parses events emitted from a send packet and returns +// the first EventTypeSendPacket packet found. +// Returns an error if no packet is found. +func ParseV1PacketFromEvents(events []abci.Event) (channeltypes.Packet, error) { + packets, err := ParseIBCV1Packets(channeltypes.EventTypeSendPacket, events) + if err != nil { + return channeltypes.Packet{}, err + } + return packets[0], nil +} + +// ParseRecvV1PacketFromEvents parses events emitted from a MsgRecvPacket and returns +// the first EventTypeRecvPacket packet found. +// Returns an error if no packet is found. +func ParseRecvV1PacketFromEvents(events []abci.Event) (channeltypes.Packet, error) { + packets, err := ParseIBCV1Packets(channeltypes.EventTypeRecvPacket, events) + if err != nil { + return channeltypes.Packet{}, err + } + return packets[0], nil +} + +// ParseIBCV1Packets parses events and returns all the v1 packets found. +// Returns an error if no v1 packet is found. +func ParseIBCV1Packets(eventType string, events []abci.Event) ([]channeltypes.Packet, error) { + ferr := func(err error) ([]channeltypes.Packet, error) { + return nil, fmt.Errorf("ibctesting.ParseIBCV1Packets: %w", err) + } + var packets []channeltypes.Packet + for _, ev := range events { + if ev.Type != eventType { + continue + } + + var packet channeltypes.Packet + for _, attr := range ev.Attributes { + switch attr.Key { + case channeltypes.AttributeKeyDataHex: + data, err := hex.DecodeString(attr.Value) + if err != nil { + return ferr(err) + } + packet.Data = data + case channeltypes.AttributeKeySequence: + seq, err := strconv.ParseUint(attr.Value, 10, 64) + if err != nil { + return ferr(err) + } + + packet.Sequence = seq + + case channeltypes.AttributeKeySrcPort: + packet.SourcePort = attr.Value + + case channeltypes.AttributeKeySrcChannel: + packet.SourceChannel = attr.Value + + case channeltypes.AttributeKeyDstPort: + packet.DestinationPort = attr.Value + + case channeltypes.AttributeKeyDstChannel: + packet.DestinationChannel = attr.Value + + case channeltypes.AttributeKeyTimeoutHeight: + height, err := clienttypes.ParseHeight(attr.Value) + if err != nil { + return ferr(err) + } + + packet.TimeoutHeight = height + + case channeltypes.AttributeKeyTimeoutTimestamp: + timestamp, err := strconv.ParseUint(attr.Value, 10, 64) + if err != nil { + return ferr(err) + } + + packet.TimeoutTimestamp = timestamp + + default: + continue + } + } + + packets = append(packets, packet) + } + if len(packets) == 0 { + return ferr(errors.New("acknowledgement event attribute not found")) + } + return packets, nil +} + +// ParseIBCV2Packets parses events and returns all the v2 packets found. +// Returns an error if no v2 packet is found. +func ParseIBCV2Packets(eventType string, events []abci.Event) ([]channeltypesv2.Packet, error) { + packets := make([]channeltypesv2.Packet, 0) + for _, event := range events { + if event.Type != eventType { + continue + } + + var packet channeltypesv2.Packet + Loop: + for _, attr := range event.Attributes { + switch attr.Key { + // If we find a complete packet, we unmarshall it. We don't need to check for any other + // attributes from this event. + case channeltypesv2.AttributeKeyEncodedPacketHex: + data, err := hex.DecodeString(attr.Value) + if err != nil { + return nil, fmt.Errorf("ibctesting.ParseIBCV2Packets: %w", err) + } + if err := proto.Unmarshal(data, &packet); err != nil { + return nil, fmt.Errorf("ibctesting.ParseIBCV2Packets: %w", err) + } + break Loop + + case channeltypesv2.AttributeKeySequence: + seq, err := strconv.ParseUint(attr.Value, 10, 64) + if err != nil { + return nil, fmt.Errorf("ibctesting.ParseIBCV2Packets: %w", err) + } + packet.Sequence = seq + + case channeltypesv2.AttributeKeyTimeoutTimestamp: + timestamp, err := strconv.ParseUint(attr.Value, 10, 64) + if err != nil { + return nil, fmt.Errorf("ibctesting.ParseIBCV2Packets: %w", err) + } + packet.TimeoutTimestamp = timestamp + + case channeltypesv2.AttributeKeyDstClient: + packet.DestinationClient = attr.Value + + case channeltypesv2.AttributeKeySrcClient: + packet.SourceClient = attr.Value + } + } + packets = append(packets, packet) + } + + if len(packets) == 0 { + return nil, errors.New("no IBC v2 packets found in events") + } + + return packets, nil +} + +// ParseAckFromEvents parses events emitted from a MsgRecvPacket and returns the +// acknowledgement. +func ParseAckFromEvents(events []abci.Event) ([]byte, error) { + for _, ev := range events { + if ev.Type == channeltypes.EventTypeWriteAck { + if attribute, found := attributeByKey(ev.Attributes, channeltypes.AttributeKeyAckHex); found { + value, err := hex.DecodeString(attribute.Value) + if err != nil { + return nil, err + } + return value, nil + } + } + } + return nil, errors.New("acknowledgement event attribute not found") +} + +// ParseProposalIDFromEvents parses events emitted from MsgSubmitProposal and returns proposalID +func ParseProposalIDFromEvents(events []abci.Event) (uint64, error) { + for _, event := range events { + if attribute, found := attributeByKey(event.Attributes, "proposal_id"); found { + return strconv.ParseUint(attribute.Value, 10, 64) + } + } + return 0, errors.New("proposalID event attribute not found") +} + +// ParsePacketSequenceFromEvents parses events emitted from MsgRecvPacket and returns the packet sequence +func ParsePacketSequenceFromEvents(events []abci.Event) (uint64, error) { + for _, event := range events { + if attribute, found := attributeByKey(event.Attributes, "packet_sequence"); found { + return strconv.ParseUint(attribute.Value, 10, 64) + } + } + return 0, errors.New("packet sequence event attribute not found") +} + +// AssertEvents asserts that expected events are present in the actual events. +func AssertEvents( + suite *testifysuite.Suite, + expected []abci.Event, + actual []abci.Event, +) { + foundEvents := make(map[int]bool) + + for i, expectedEvent := range expected { + for _, actualEvent := range actual { + if shouldProcessEvent(expectedEvent, actualEvent) { + attributeMatch := true + for _, expectedAttr := range expectedEvent.Attributes { + // any expected attributes that are not contained in the actual events will cause this event + // not to match + attributeMatch = attributeMatch && containsAttribute(actualEvent.Attributes, expectedAttr.Key, expectedAttr.Value) + } + + if attributeMatch { + foundEvents[i] = true + } + } + } + } + + for i, expectedEvent := range expected { + suite.Require().True(foundEvents[i], "event: %s was not found in events", expectedEvent.Type) + } +} + +// shouldProcessEvent returns true if the given expected event should be processed based on event type. +func shouldProcessEvent(expectedEvent abci.Event, actualEvent abci.Event) bool { + if expectedEvent.Type != actualEvent.Type { + return false + } + // the actual event will have an extra attribute added automatically + // by Cosmos SDK since v0.50, that's why we subtract 1 when comparing + // with the number of attributes in the expected event. + if containsAttributeKey(actualEvent.Attributes, "msg_index") { + return len(expectedEvent.Attributes) == len(actualEvent.Attributes)-1 + } + + return len(expectedEvent.Attributes) == len(actualEvent.Attributes) +} + +// containsAttribute returns true if the given key/value pair is contained in the given attributes. +// NOTE: this ignores the indexed field, which can be set or unset depending on how the events are retrieved. +func containsAttribute(attrs []abci.EventAttribute, key, value string) bool { + return slices.ContainsFunc(attrs, func(attr abci.EventAttribute) bool { + return attr.Key == key && attr.Value == value + }) +} + +// containsAttributeKey returns true if the given key is contained in the given attributes. +func containsAttributeKey(attrs []abci.EventAttribute, key string) bool { + _, found := attributeByKey(attrs, key) + return found +} + +// attributeByKey returns the event attribute's value keyed by the given key and a boolean indicating its presence in the given attributes. +func attributeByKey(attributes []abci.EventAttribute, key string) (abci.EventAttribute, bool) { + idx := slices.IndexFunc(attributes, func(a abci.EventAttribute) bool { return a.Key == key }) + if idx == -1 { + return abci.EventAttribute{}, false + } + return attributes[idx], true +} + +// ParsePacketFromEvents parses events emitted from a send packet and returns +// the first EventTypeSendPacket packet found. +// Returns an error if no packet is found. +// +// Deprecated: This function will be removed in the next major release. Use +// ParseV1PacketFromEvents instead +func ParsePacketFromEvents(events []abci.Event) (channeltypes.Packet, error) { + return ParseV1PacketFromEvents(events) +} + +// ParseRecvPacketFromEvents parses events emitted from a MsgRecvPacket and returns +// the first EventTypeRecvPacket packet found. +// Returns an error if no packet is found. +// +// Deprecated: This function will be removed in the next major release. Use +// ParseRecvV1PacketFromEvents instead +func ParseRecvPacketFromEvents(events []abci.Event) (channeltypes.Packet, error) { + return ParseRecvV1PacketFromEvents(events) +} + +// ParsePacketsFromEvents parses events emitted from a MsgRecvPacket and returns +// all the packets found. +// Returns an error if no packet is found. +// +// Deprecated: This function will be removed in the next major release. Use ParseIBCV1Packets instead. +func ParsePacketsFromEvents(eventType string, events []abci.Event) ([]channeltypes.Packet, error) { + return ParseIBCV1Packets(eventType, events) +} diff --git a/testing/events_test.go b/testing/events_test.go new file mode 100644 index 0000000..8776470 --- /dev/null +++ b/testing/events_test.go @@ -0,0 +1,390 @@ +package ibctesting_test + +import ( + "encoding/hex" + "testing" + + "github.com/cosmos/gogoproto/proto" + "github.com/stretchr/testify/require" + + abci "github.com/cometbft/cometbft/abci/types" + + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + ibctesting "github.com/cosmos/ibc-go/v10/testing" + mockv1 "github.com/cosmos/ibc-go/v10/testing/mock" + mockv2 "github.com/cosmos/ibc-go/v10/testing/mock/v2" +) + +func TestParseV1PacketsFromEvents(t *testing.T) { + testCases := []struct { + name string + events []abci.Event + expectedPackets []channeltypes.Packet + expectedError string + }{ + { + name: "success", + events: []abci.Event{ + { + Type: "xxx", + }, + { + Type: channeltypes.EventTypeSendPacket, + Attributes: []abci.EventAttribute{ + { + Key: channeltypes.AttributeKeyDataHex, + Value: hex.EncodeToString([]byte("data1")), + }, + { + Key: channeltypes.AttributeKeySequence, + Value: "42", + }, + { + Key: channeltypes.AttributeKeySrcPort, + Value: "srcPort", + }, + { + Key: channeltypes.AttributeKeySrcChannel, + Value: "srcChannel", + }, + { + Key: channeltypes.AttributeKeyDstPort, + Value: "dstPort", + }, + { + Key: channeltypes.AttributeKeyDstChannel, + Value: "dstChannel", + }, + { + Key: channeltypes.AttributeKeyTimeoutHeight, + Value: "1-2", + }, + { + Key: channeltypes.AttributeKeyTimeoutTimestamp, + Value: "1000", + }, + }, + }, + { + Type: "yyy", + }, + { + Type: channeltypes.EventTypeSendPacket, + Attributes: []abci.EventAttribute{ + { + Key: channeltypes.AttributeKeyDataHex, + Value: hex.EncodeToString([]byte("data2")), + }, + { + Key: channeltypes.AttributeKeySequence, + Value: "43", + }, + { + Key: channeltypes.AttributeKeySrcPort, + Value: "srcPort", + }, + { + Key: channeltypes.AttributeKeySrcChannel, + Value: "srcChannel", + }, + { + Key: channeltypes.AttributeKeyDstPort, + Value: "dstPort", + }, + { + Key: channeltypes.AttributeKeyDstChannel, + Value: "dstChannel", + }, + { + Key: channeltypes.AttributeKeyTimeoutHeight, + Value: "1-3", + }, + { + Key: channeltypes.AttributeKeyTimeoutTimestamp, + Value: "1001", + }, + }, + }, + }, + expectedPackets: []channeltypes.Packet{ + { + Sequence: 42, + SourcePort: "srcPort", + SourceChannel: "srcChannel", + DestinationPort: "dstPort", + DestinationChannel: "dstChannel", + Data: []byte("data1"), + TimeoutHeight: types.Height{ + RevisionNumber: 1, + RevisionHeight: 2, + }, + TimeoutTimestamp: 1000, + }, + { + Sequence: 43, + SourcePort: "srcPort", + SourceChannel: "srcChannel", + DestinationPort: "dstPort", + DestinationChannel: "dstChannel", + Data: []byte("data2"), + TimeoutHeight: types.Height{ + RevisionNumber: 1, + RevisionHeight: 3, + }, + TimeoutTimestamp: 1001, + }, + }, + }, + + { + name: "fail: no events", + events: []abci.Event{}, + expectedError: "acknowledgement event attribute not found", + }, + { + name: "fail: events without packet", + events: []abci.Event{ + { + Type: "xxx", + }, + { + Type: "yyy", + }, + }, + expectedError: "acknowledgement event attribute not found", + }, + { + name: "fail: event packet with invalid AttributeKeySequence", + events: []abci.Event{ + { + Type: channeltypes.EventTypeSendPacket, + Attributes: []abci.EventAttribute{ + { + Key: channeltypes.AttributeKeySequence, + Value: "x", + }, + }, + }, + }, + expectedError: "strconv.ParseUint: parsing \"x\": invalid syntax", + }, + { + name: "fail: event packet with invalid AttributeKeyTimeoutHeight", + events: []abci.Event{ + { + Type: channeltypes.EventTypeSendPacket, + Attributes: []abci.EventAttribute{ + { + Key: channeltypes.AttributeKeyTimeoutHeight, + Value: "x", + }, + }, + }, + }, + expectedError: "expected height string format: {revision}-{height}. Got: x: invalid height", + }, + { + name: "fail: event packet with invalid AttributeKeyTimeoutTimestamp", + events: []abci.Event{ + { + Type: channeltypes.EventTypeSendPacket, + Attributes: []abci.EventAttribute{ + { + Key: channeltypes.AttributeKeyTimeoutTimestamp, + Value: "x", + }, + }, + }, + }, + expectedError: "strconv.ParseUint: parsing \"x\": invalid syntax", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + allPackets, err := ibctesting.ParseIBCV1Packets(channeltypes.EventTypeSendPacket, tc.events) + + if tc.expectedError == "" { + require.NoError(t, err) + require.Equal(t, tc.expectedPackets, allPackets) + } else { + require.ErrorContains(t, err, tc.expectedError) + } + + firstPacket, err := ibctesting.ParseV1PacketFromEvents(tc.events) + + if tc.expectedError == "" { + require.NoError(t, err) + require.Equal(t, tc.expectedPackets[0], firstPacket) + } else { + require.ErrorContains(t, err, tc.expectedError) + } + }) + } +} + +func TestParseV2PacketsFromEvents(t *testing.T) { + testCases := []struct { + name string + eventType string + events []abci.Event + expectedPackets []channeltypesv2.Packet + expectedError string + }{ + { + name: "success: One v2 packet without payload + One v2 packet with payload", + eventType: channeltypesv2.EventTypeRecvPacket, + events: []abci.Event{ + { + Type: "xxx", + }, + { + Type: channeltypesv2.EventTypeRecvPacket, + Attributes: []abci.EventAttribute{ + { + Key: channeltypesv2.AttributeKeySequence, + Value: "42", + }, + { + Key: channeltypesv2.AttributeKeySrcClient, + Value: "srcClient", + }, + { + Key: channeltypesv2.AttributeKeyDstClient, + Value: "destClient", + }, + { + Key: channeltypesv2.AttributeKeyTimeoutTimestamp, + Value: "1283798137", + }, + }, + }, + { + Type: "yyy", + }, + { + Type: channeltypesv2.EventTypeRecvPacket, + // If AttributeKeyEncodedPacketHex is present, other attributes are ignored. + Attributes: []abci.EventAttribute{ + { + Key: channeltypesv2.AttributeKeySequence, + Value: "43", // Value Ignored + }, + { + Key: channeltypesv2.AttributeKeySrcClient, + Value: "srcClient-2", // Value Ignored + }, + { + Key: channeltypesv2.AttributeKeyDstClient, + Value: "destClient-2", // Value Ignored + }, + { + Key: channeltypesv2.AttributeKeyTimeoutTimestamp, + Value: "12837997475", // Value Ignored + }, + { + Key: channeltypesv2.AttributeKeyEncodedPacketHex, + Value: func() string { + payload := mockv2.NewMockPayload(mockv2.ModuleNameA, mockv2.ModuleNameB) + packet := channeltypesv2.NewPacket(44, "srcClient", "destClient", 19474197444, payload) + encodedPacket, err := proto.Marshal(&packet) + if err != nil { + panic(err) + } + return hex.EncodeToString(encodedPacket) + }(), + }, + }, + }, + }, + expectedPackets: []channeltypesv2.Packet{ + { + Sequence: 42, + SourceClient: "srcClient", + DestinationClient: "destClient", + TimeoutTimestamp: 1283798137, + }, + { + Sequence: 44, + SourceClient: "srcClient", + DestinationClient: "destClient", + TimeoutTimestamp: 19474197444, + Payloads: []channeltypesv2.Payload{ + { + SourcePort: mockv2.ModuleNameA, + DestinationPort: mockv2.ModuleNameB, + Version: mockv1.Version, + Encoding: transfertypes.EncodingProtobuf, + Value: mockv1.MockPacketData, + }, + }, + }, + }, + }, + + { + name: "fail: no events", + eventType: channeltypesv2.EventTypeSendPacket, + events: []abci.Event{}, + expectedError: "no IBC v2 packets found in events", + }, + { + name: "fail: events without packet", + eventType: channeltypesv2.EventTypeSendPacket, + events: []abci.Event{ + { + Type: "xxx", + }, + { + Type: "yyy", + }, + }, + expectedError: "no IBC v2 packets found in events", + }, + { + name: "fail: event packet with invalid AttributeKeySequence", + eventType: channeltypesv2.EventTypeSendPacket, + events: []abci.Event{ + { + Type: channeltypesv2.EventTypeSendPacket, + Attributes: []abci.EventAttribute{ + { + Key: channeltypesv2.AttributeKeySequence, + Value: "x", + }, + }, + }, + }, + expectedError: "strconv.ParseUint: parsing \"x\": invalid syntax", + }, + { + name: "fail: event packet with invalid AttributeKeyTimeoutHeight", + eventType: channeltypesv2.EventTypeSendPacket, + events: []abci.Event{ + { + Type: channeltypesv2.EventTypeSendPacket, + Attributes: []abci.EventAttribute{ + { + Key: channeltypesv2.AttributeKeyTimeoutTimestamp, + Value: "x", + }, + }, + }, + }, + expectedError: "strconv.ParseUint: parsing \"x\": invalid syntax", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + packets, err := ibctesting.ParseIBCV2Packets(tc.eventType, tc.events) + if tc.expectedError != "" { + require.ErrorContains(t, err, tc.expectedError) + return + } + require.NoError(t, err) + require.Equal(t, tc.expectedPackets, packets) + }) + } +} diff --git a/testing/mock/README.md b/testing/mock/README.md new file mode 100644 index 0000000..5777ffa --- /dev/null +++ b/testing/mock/README.md @@ -0,0 +1,6 @@ +This package is only intended to be used for testing core IBC. In order to maintain secure +testing, we need to do message passing and execution which requires connecting an IBC application +module that fulfills all the callbacks. We cannot connect to ibc-transfer which does not support +all channel types so instead we create a mock application module which does nothing. It simply +return nil in all cases so no error ever occurs. It is intended to be as minimal and lightweight +as possible and should never import simapp. diff --git a/testing/mock/ack.go b/testing/mock/ack.go new file mode 100644 index 0000000..e7e0633 --- /dev/null +++ b/testing/mock/ack.go @@ -0,0 +1,23 @@ +package mock + +// EmptyAcknowledgement implements the exported.Acknowledgement interface and always returns an empty byte string as Response +type EmptyAcknowledgement struct { + Response []byte +} + +// NewEmptyAcknowledgement returns a new instance of EmptyAcknowledgement +func NewEmptyAcknowledgement() EmptyAcknowledgement { + return EmptyAcknowledgement{ + Response: []byte{}, + } +} + +// Success implements the Acknowledgement interface +func (EmptyAcknowledgement) Success() bool { + return true +} + +// Acknowledgement implements the Acknowledgement interface +func (EmptyAcknowledgement) Acknowledgement() []byte { + return []byte{} +} diff --git a/testing/mock/address_codec.go b/testing/mock/address_codec.go new file mode 100644 index 0000000..c5bb7c2 --- /dev/null +++ b/testing/mock/address_codec.go @@ -0,0 +1,27 @@ +package mock + +import ( + "errors" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type TestAddressCodec struct{} + +func (TestAddressCodec) StringToBytes(text string) ([]byte, error) { + hexBytes, err := sdk.AccAddressFromHexUnsafe(text) + if err == nil { + return hexBytes, nil + } + + bech32Bytes, err := sdk.AccAddressFromBech32(text) + if err == nil { + return bech32Bytes, nil + } + + return nil, errors.New("invalid address format") +} + +func (TestAddressCodec) BytesToString(bz []byte) (string, error) { + return sdk.AccAddress(bz).String(), nil +} diff --git a/testing/mock/doc.go b/testing/mock/doc.go new file mode 100644 index 0000000..f336bf0 --- /dev/null +++ b/testing/mock/doc.go @@ -0,0 +1,9 @@ +/* +This package is only intended to be used for testing core IBC. In order to maintain secure +testing, we need to do message passing and execution which requires connecting an IBC application +module that fulfills all the callbacks. We cannot connect to ibc-transfer which does not support +all channel types so instead we create a mock application module which does nothing. It simply +return nil in all cases so no error ever occurs. It is intended to be as minimal and lightweight +as possible and should never import simapp. +*/ +package mock diff --git a/testing/mock/events.go b/testing/mock/events.go new file mode 100644 index 0000000..761e465 --- /dev/null +++ b/testing/mock/events.go @@ -0,0 +1,40 @@ +package mock + +import sdk "github.com/cosmos/cosmos-sdk/types" + +const ( + MockEventType = "mock-event-type" + MockEventTypeRecvPacket = "mock-recv-packet" + MockEventTypeAckPacket = "mock-ack-packet" + MockEventTypeTimeoutPacket = "mock-timeout" + + MockAttributeKey1 = "mock-attribute-key-1" + MockAttributeKey2 = "mock-attribute-key-2" + + MockAttributeValue1 = "mock-attribute-value-1" + MockAttributeValue2 = "mock-attribute-value-2" +) + +// NewMockRecvPacketEvent returns a mock receive packet event +func NewMockRecvPacketEvent() sdk.Event { + return newMockEvent(MockEventTypeRecvPacket) +} + +// NewMockAckPacketEvent returns a mock acknowledgement packet event +func NewMockAckPacketEvent() sdk.Event { + return newMockEvent(MockEventTypeAckPacket) +} + +// NewMockTimeoutPacketEvent emits a mock timeout packet event +func NewMockTimeoutPacketEvent() sdk.Event { + return newMockEvent(MockEventTypeTimeoutPacket) +} + +// emitMockEvent returns a mock event with the given event type +func newMockEvent(eventType string) sdk.Event { + return sdk.NewEvent( + eventType, + sdk.NewAttribute(MockAttributeKey1, MockAttributeValue1), + sdk.NewAttribute(MockAttributeKey2, MockAttributeValue2), + ) +} diff --git a/testing/mock/ibc_app.go b/testing/mock/ibc_app.go new file mode 100644 index 0000000..e481042 --- /dev/null +++ b/testing/mock/ibc_app.go @@ -0,0 +1,125 @@ +package mock + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +// IBCApp contains IBC application module callbacks as defined in 05-port. +type IBCApp struct { + PortID string + + OnChanOpenInit func( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID string, + channelID string, + counterparty channeltypes.Counterparty, + version string, + ) (string, error) + + OnChanOpenTry func( + ctx sdk.Context, + order channeltypes.Order, + connectionHops []string, + portID, + channelID string, + counterparty channeltypes.Counterparty, + counterpartyVersion string, + ) (version string, err error) + + OnChanOpenAck func( + ctx sdk.Context, + portID, + channelID string, + counterpartyChannelID string, + counterpartyVersion string, + ) error + + OnChanOpenConfirm func( + ctx sdk.Context, + portID, + channelID string, + ) error + + OnChanCloseInit func( + ctx sdk.Context, + portID, + channelID string, + ) error + + OnChanCloseConfirm func( + ctx sdk.Context, + portID, + channelID string, + ) error + + // OnRecvPacket must return an acknowledgement that implements the Acknowledgement interface. + // In the case of an asynchronous acknowledgement, nil should be returned. + // If the acknowledgement returned is successful, the state changes on callback are written, + // otherwise the application state changes are discarded. In either case the packet is received + // and the acknowledgement is written (in synchronous cases). + OnRecvPacket func( + ctx sdk.Context, + channelVersion string, + packet channeltypes.Packet, + relayer sdk.AccAddress, + ) exported.Acknowledgement + + OnAcknowledgementPacket func( + ctx sdk.Context, + channelVersion string, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, + ) error + + OnTimeoutPacket func( + ctx sdk.Context, + channelVersion string, + packet channeltypes.Packet, + relayer sdk.AccAddress, + ) error + + OnChanUpgradeInit func( + ctx sdk.Context, + portID, channelID string, + order channeltypes.Order, + connectionHops []string, + version string, + ) (string, error) + + OnChanUpgradeTry func( + ctx sdk.Context, + portID, channelID string, + order channeltypes.Order, + connectionHops []string, + counterpartyVersion string, + ) (string, error) + + OnChanUpgradeAck func( + ctx sdk.Context, + portID, + channelID, + counterpartyVersion string, + ) error + + OnChanUpgradeOpen func( + ctx sdk.Context, + portID, + channelID string, + order channeltypes.Order, + connectionHops []string, + version string, + ) +} + +// NewIBCApp returns a IBCApp. An empty PortID indicates the mock app doesn't bind/claim ports. +func NewIBCApp(portID string) *IBCApp { + return &IBCApp{ + PortID: portID, + } +} diff --git a/testing/mock/ibc_module.go b/testing/mock/ibc_module.go new file mode 100644 index 0000000..6b0f681 --- /dev/null +++ b/testing/mock/ibc_module.go @@ -0,0 +1,152 @@ +package mock + +import ( + "bytes" + "reflect" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +var ( + _ porttypes.IBCModule = (*IBCModule)(nil) + _ porttypes.PacketDataUnmarshaler = (*IBCModule)(nil) +) + +// applicationCallbackError is a custom error type that will be unique for testing purposes. +type applicationCallbackError struct{} + +func (applicationCallbackError) Error() string { + return "mock application callback failed" +} + +// IBCModule implements the ICS26 callbacks for testing/mock. +type IBCModule struct { + appModule *AppModule + IBCApp *IBCApp // base application of an IBC middleware stack +} + +// NewIBCModule creates a new IBCModule given the underlying mock IBC application and scopedKeeper. +func NewIBCModule(appModule *AppModule, app *IBCApp) IBCModule { + appModule.ibcApps = append(appModule.ibcApps, app) + return IBCModule{ + appModule: appModule, + IBCApp: app, + } +} + +// OnChanOpenInit implements the IBCModule interface. +func (im IBCModule) OnChanOpenInit( + ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, + channelID string, counterparty channeltypes.Counterparty, version string, +) (string, error) { + if strings.TrimSpace(version) == "" { + version = Version + } + + if im.IBCApp.OnChanOpenInit != nil { + return im.IBCApp.OnChanOpenInit(ctx, order, connectionHops, portID, channelID, counterparty, version) + } + + return version, nil +} + +// OnChanOpenTry implements the IBCModule interface. +func (im IBCModule) OnChanOpenTry( + ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, + channelID string, counterparty channeltypes.Counterparty, counterpartyVersion string, +) (version string, err error) { + if im.IBCApp.OnChanOpenTry != nil { + return im.IBCApp.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, counterparty, counterpartyVersion) + } + + return Version, nil +} + +// OnChanOpenAck implements the IBCModule interface. +func (im IBCModule) OnChanOpenAck(ctx sdk.Context, portID string, channelID string, counterpartyChannelID string, counterpartyVersion string) error { + if im.IBCApp.OnChanOpenAck != nil { + return im.IBCApp.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) + } + + return nil +} + +// OnChanOpenConfirm implements the IBCModule interface. +func (im IBCModule) OnChanOpenConfirm(ctx sdk.Context, portID, channelID string) error { + if im.IBCApp.OnChanOpenConfirm != nil { + return im.IBCApp.OnChanOpenConfirm(ctx, portID, channelID) + } + + return nil +} + +// OnChanCloseInit implements the IBCModule interface. +func (im IBCModule) OnChanCloseInit(ctx sdk.Context, portID, channelID string) error { + if im.IBCApp.OnChanCloseInit != nil { + return im.IBCApp.OnChanCloseInit(ctx, portID, channelID) + } + + return nil +} + +// OnChanCloseConfirm implements the IBCModule interface. +func (im IBCModule) OnChanCloseConfirm(ctx sdk.Context, portID, channelID string) error { + if im.IBCApp.OnChanCloseConfirm != nil { + return im.IBCApp.OnChanCloseConfirm(ctx, portID, channelID) + } + + return nil +} + +// OnRecvPacket implements the IBCModule interface. +func (im IBCModule) OnRecvPacket(ctx sdk.Context, channelVersion string, packet channeltypes.Packet, relayer sdk.AccAddress) exported.Acknowledgement { + if im.IBCApp.OnRecvPacket != nil { + return im.IBCApp.OnRecvPacket(ctx, channelVersion, packet, relayer) + } + + ctx.EventManager().EmitEvent(NewMockRecvPacketEvent()) + + if bytes.Equal(MockPacketData, packet.GetData()) { + return MockAcknowledgement + } else if bytes.Equal(MockAsyncPacketData, packet.GetData()) { + return nil + } + + return MockFailAcknowledgement +} + +// OnAcknowledgementPacket implements the IBCModule interface. +func (im IBCModule) OnAcknowledgementPacket(ctx sdk.Context, channelVersion string, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress) error { + if im.IBCApp.OnAcknowledgementPacket != nil { + return im.IBCApp.OnAcknowledgementPacket(ctx, channelVersion, packet, acknowledgement, relayer) + } + + ctx.EventManager().EmitEvent(NewMockAckPacketEvent()) + + return nil +} + +// OnTimeoutPacket implements the IBCModule interface. +func (im IBCModule) OnTimeoutPacket(ctx sdk.Context, channelVersion string, packet channeltypes.Packet, relayer sdk.AccAddress) error { + if im.IBCApp.OnTimeoutPacket != nil { + return im.IBCApp.OnTimeoutPacket(ctx, channelVersion, packet, relayer) + } + + ctx.EventManager().EmitEvent(NewMockTimeoutPacketEvent()) + + return nil +} + +// UnmarshalPacketData returns the MockPacketData. This function implements the optional +// PacketDataUnmarshaler interface required for ADR 008 support. +func (IBCModule) UnmarshalPacketData(ctx sdk.Context, portID string, channelID string, bz []byte) (any, string, error) { + if reflect.DeepEqual(bz, MockPacketData) { + return MockPacketData, Version, nil + } + return nil, "", MockApplicationCallbackError +} diff --git a/testing/mock/middleware.go b/testing/mock/middleware.go new file mode 100644 index 0000000..9edef6f --- /dev/null +++ b/testing/mock/middleware.go @@ -0,0 +1,157 @@ +package mock + +import ( + "bytes" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +const ( + MockBlockUpgrade = "mockblockupgrade" +) + +var _ porttypes.Middleware = (*BlockUpgradeMiddleware)(nil) + +// BlockUpgradeMiddleware does not implement the UpgradeableModule interface +type BlockUpgradeMiddleware struct { + appModule *AppModule + IBCApp *IBCApp // base application of an IBC middleware stack +} + +// NewIBCModule creates a new IBCModule given the underlying mock IBC application and scopedKeeper. +func NewBlockUpgradeMiddleware(appModule *AppModule, app *IBCApp) BlockUpgradeMiddleware { + appModule.ibcApps = append(appModule.ibcApps, app) + return BlockUpgradeMiddleware{ + appModule: appModule, + IBCApp: app, + } +} + +// OnChanOpenInit implements the IBCModule interface. +func (im BlockUpgradeMiddleware) OnChanOpenInit( + ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, + channelID string, counterparty channeltypes.Counterparty, version string, +) (string, error) { + if strings.TrimSpace(version) == "" { + version = Version + } + + if im.IBCApp.OnChanOpenInit != nil { + return im.IBCApp.OnChanOpenInit(ctx, order, connectionHops, portID, channelID, counterparty, version) + } + + return version, nil +} + +// OnChanOpenTry implements the IBCModule interface. +func (im BlockUpgradeMiddleware) OnChanOpenTry( + ctx sdk.Context, order channeltypes.Order, connectionHops []string, portID string, + channelID string, counterparty channeltypes.Counterparty, counterpartyVersion string, +) (version string, err error) { + if im.IBCApp.OnChanOpenTry != nil { + return im.IBCApp.OnChanOpenTry(ctx, order, connectionHops, portID, channelID, counterparty, counterpartyVersion) + } + + return Version, nil +} + +// OnChanOpenAck implements the IBCModule interface. +func (im BlockUpgradeMiddleware) OnChanOpenAck(ctx sdk.Context, portID string, channelID string, counterpartyChannelID string, counterpartyVersion string) error { + if im.IBCApp.OnChanOpenAck != nil { + return im.IBCApp.OnChanOpenAck(ctx, portID, channelID, counterpartyChannelID, counterpartyVersion) + } + + return nil +} + +// OnChanOpenConfirm implements the IBCModule interface. +func (im BlockUpgradeMiddleware) OnChanOpenConfirm(ctx sdk.Context, portID, channelID string) error { + if im.IBCApp.OnChanOpenConfirm != nil { + return im.IBCApp.OnChanOpenConfirm(ctx, portID, channelID) + } + + return nil +} + +// OnChanCloseInit implements the IBCModule interface. +func (im BlockUpgradeMiddleware) OnChanCloseInit(ctx sdk.Context, portID, channelID string) error { + if im.IBCApp.OnChanCloseInit != nil { + return im.IBCApp.OnChanCloseInit(ctx, portID, channelID) + } + + return nil +} + +// OnChanCloseConfirm implements the IBCModule interface. +func (im BlockUpgradeMiddleware) OnChanCloseConfirm(ctx sdk.Context, portID, channelID string) error { + if im.IBCApp.OnChanCloseConfirm != nil { + return im.IBCApp.OnChanCloseConfirm(ctx, portID, channelID) + } + + return nil +} + +// OnRecvPacket implements the IBCModule interface. +func (im BlockUpgradeMiddleware) OnRecvPacket(ctx sdk.Context, channelVersion string, packet channeltypes.Packet, relayer sdk.AccAddress) exported.Acknowledgement { + if im.IBCApp.OnRecvPacket != nil { + return im.IBCApp.OnRecvPacket(ctx, channelVersion, packet, relayer) + } + + if bytes.Equal(MockPacketData, packet.GetData()) { + return MockAcknowledgement + } else if bytes.Equal(MockAsyncPacketData, packet.GetData()) { + return nil + } + + return MockFailAcknowledgement +} + +// OnAcknowledgementPacket implements the IBCModule interface. +func (im BlockUpgradeMiddleware) OnAcknowledgementPacket(ctx sdk.Context, channelVersion string, packet channeltypes.Packet, acknowledgement []byte, relayer sdk.AccAddress) error { + if im.IBCApp.OnAcknowledgementPacket != nil { + return im.IBCApp.OnAcknowledgementPacket(ctx, channelVersion, packet, acknowledgement, relayer) + } + + return nil +} + +// OnTimeoutPacket implements the IBCModule interface. +func (im BlockUpgradeMiddleware) OnTimeoutPacket(ctx sdk.Context, channelVersion string, packet channeltypes.Packet, relayer sdk.AccAddress) error { + if im.IBCApp.OnTimeoutPacket != nil { + return im.IBCApp.OnTimeoutPacket(ctx, channelVersion, packet, relayer) + } + + return nil +} + +// SendPacket implements the ICS4 Wrapper interface +func (BlockUpgradeMiddleware) SendPacket( + ctx sdk.Context, + sourcePort string, + sourceChannel string, + timeoutHeight clienttypes.Height, + timeoutTimestamp uint64, + data []byte, +) (uint64, error) { + return 0, nil +} + +// WriteAcknowledgement implements the ICS4 Wrapper interface +func (BlockUpgradeMiddleware) WriteAcknowledgement( + ctx sdk.Context, + packet exported.PacketI, + ack exported.Acknowledgement, +) error { + return nil +} + +// GetAppVersion returns the application version of the underlying application +func (BlockUpgradeMiddleware) GetAppVersion(ctx sdk.Context, portID, channelID string) (string, bool) { + return Version, true +} diff --git a/testing/mock/mock.go b/testing/mock/mock.go new file mode 100644 index 0000000..f998297 --- /dev/null +++ b/testing/mock/mock.go @@ -0,0 +1,159 @@ +package mock + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + + "cosmossdk.io/core/appmodule" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + + abci "github.com/cometbft/cometbft/abci/types" + + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + "github.com/cosmos/ibc-go/v10/modules/core/exported" +) + +const ( + ModuleName = "mock" + + MemStoreKey = "memory:mock" + + PortID = ModuleName + + Version = "mock-version" +) + +var ( + MockAcknowledgement = channeltypes.NewResultAcknowledgement([]byte("mock acknowledgement")) + MockFailAcknowledgement = channeltypes.NewErrorAcknowledgement(errors.New("mock failed acknowledgement")) + MockPacketData = []byte("mock packet data") + MockFailPacketData = []byte("mock failed packet data") + MockAsyncPacketData = []byte("mock async packet data") + UpgradeVersion = fmt.Sprintf("%s-v2", Version) + // MockApplicationCallbackError should be returned when an application callback should fail. It is possible to + // test that this error was returned using ErrorIs. + MockApplicationCallbackError error = &applicationCallbackError{} +) + +var ( + TestKey = []byte("test-key") + TestValue = []byte("test-value") +) + +var ( + _ module.AppModuleBasic = (*AppModuleBasic)(nil) + _ appmodule.AppModule = (*AppModule)(nil) + + _ porttypes.IBCModule = (*IBCModule)(nil) +) + +// AppModuleBasic is the mock AppModuleBasic. +type AppModuleBasic struct{} + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (AppModuleBasic) IsOnePerModuleType() {} + +// IsAppModule implements the appmodule.AppModule interface. +func (AppModuleBasic) IsAppModule() {} + +// Name implements AppModuleBasic interface. +func (AppModuleBasic) Name() string { + return ModuleName +} + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (AppModule) IsOnePerModuleType() {} + +// IsAppModule implements the appmodule.AppModule interface. +func (AppModule) IsAppModule() {} + +// RegisterLegacyAminoCodec implements AppModuleBasic interface. +func (AppModuleBasic) RegisterLegacyAminoCodec(*codec.LegacyAmino) {} + +// RegisterInterfaces implements AppModuleBasic interface. +func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) {} + +// DefaultGenesis implements AppModuleBasic interface. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return nil +} + +// ValidateGenesis implements the AppModuleBasic interface. +func (AppModuleBasic) ValidateGenesis(codec.JSONCodec, client.TxEncodingConfig, json.RawMessage) error { + return nil +} + +// RegisterGRPCGatewayRoutes implements AppModuleBasic interface. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(_ client.Context, _ *runtime.ServeMux) {} + +// GetTxCmd implements AppModuleBasic interface. +func (AppModuleBasic) GetTxCmd() *cobra.Command { + return nil +} + +// GetQueryCmd implements AppModuleBasic interface. +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return nil +} + +// AppModule represents the AppModule for the mock module. +type AppModule struct { + AppModuleBasic + ibcApps []*IBCApp +} + +// NewAppModule returns a mock AppModule instance. +func NewAppModule() AppModule { + return AppModule{} +} + +// RegisterInvariants implements the AppModule interface. +func (AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {} + +// RegisterServices implements the AppModule interface. +func (AppModule) RegisterServices(module.Configurator) {} + +// InitGenesis implements the AppModule interface. +func (AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} + +// ExportGenesis implements the AppModule interface. +func (AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + return nil +} + +// ConsensusVersion implements AppModule/ConsensusVersion. +func (AppModule) ConsensusVersion() uint64 { return 1 } + +var _ exported.Path = KeyPath{} + +// KeyPath defines a placeholder struct which implements the exported.Path interface +type KeyPath struct{} + +// String implements the exported.Path interface +func (KeyPath) String() string { + return "" +} + +// Empty implements the exported.Path interface +func (KeyPath) Empty() bool { + return false +} + +var _ exported.Height = Height{} + +// Height defines a placeholder struct which implements the exported.Height interface +type Height struct { + exported.Height +} diff --git a/testing/mock/v2/ibc_app.go b/testing/mock/v2/ibc_app.go new file mode 100644 index 0000000..988524b --- /dev/null +++ b/testing/mock/v2/ibc_app.go @@ -0,0 +1,14 @@ +package mock + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" +) + +type IBCApp struct { + OnSendPacket func(ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, payload channeltypesv2.Payload, signer sdk.AccAddress) error + OnRecvPacket func(ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, payload channeltypesv2.Payload, relayer sdk.AccAddress) channeltypesv2.RecvPacketResult + OnTimeoutPacket func(ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, payload channeltypesv2.Payload, relayer sdk.AccAddress) error + OnAcknowledgementPacket func(ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, payload channeltypesv2.Payload, acknowledgement []byte, relayer sdk.AccAddress) error +} diff --git a/testing/mock/v2/ibc_module.go b/testing/mock/v2/ibc_module.go new file mode 100644 index 0000000..9a64a8e --- /dev/null +++ b/testing/mock/v2/ibc_module.go @@ -0,0 +1,75 @@ +package mock + +import ( + "bytes" + + sdk "github.com/cosmos/cosmos-sdk/types" + + channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + "github.com/cosmos/ibc-go/v10/modules/core/api" + mockv1 "github.com/cosmos/ibc-go/v10/testing/mock" +) + +var _ api.IBCModule = (*IBCModule)(nil) + +const ( + // ModuleNameA is a name that can be used for the first mock application. + ModuleNameA = ModuleName + "A" + // ModuleNameB is a name that can be used for the second mock application. + ModuleNameB = ModuleName + "B" + // PortIDA is a port ID that can be used for the first mock application. + PortIDA = ModuleNameA + // PortIDB is a port ID that can be used for the second mock application. + PortIDB = ModuleNameB +) + +// IBCModule is a mock implementation of the IBCModule interface. +// which delegates calls to the underlying IBCApp. +type IBCModule struct { + IBCApp *IBCApp +} + +// NewIBCModule creates a new IBCModule with an underlying mock IBC application. +func NewIBCModule() IBCModule { + return IBCModule{ + IBCApp: &IBCApp{}, + } +} + +func (im IBCModule) OnSendPacket(ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, data channeltypesv2.Payload, signer sdk.AccAddress) error { + if im.IBCApp.OnSendPacket != nil { + return im.IBCApp.OnSendPacket(ctx, sourceChannel, destinationChannel, sequence, data, signer) + } + return nil +} + +func (im IBCModule) OnRecvPacket(ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, payload channeltypesv2.Payload, relayer sdk.AccAddress) channeltypesv2.RecvPacketResult { + if im.IBCApp.OnRecvPacket != nil { + return im.IBCApp.OnRecvPacket(ctx, sourceChannel, destinationChannel, sequence, payload, relayer) + } + if bytes.Equal(payload.Value, mockv1.MockPacketData) { + return MockRecvPacketResult + } + return channeltypesv2.RecvPacketResult{Status: channeltypesv2.PacketStatus_Failure} +} + +func (im IBCModule) OnAcknowledgementPacket(ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, acknowledgement []byte, payload channeltypesv2.Payload, relayer sdk.AccAddress) error { + if im.IBCApp.OnAcknowledgementPacket != nil { + return im.IBCApp.OnAcknowledgementPacket(ctx, sourceChannel, destinationChannel, sequence, payload, acknowledgement, relayer) + } + return nil +} + +func (im IBCModule) OnTimeoutPacket(ctx sdk.Context, sourceChannel string, destinationChannel string, sequence uint64, payload channeltypesv2.Payload, relayer sdk.AccAddress) error { + if im.IBCApp.OnTimeoutPacket != nil { + return im.IBCApp.OnTimeoutPacket(ctx, sourceChannel, destinationChannel, sequence, payload, relayer) + } + return nil +} + +func (IBCModule) UnmarshalPacketData(payload channeltypesv2.Payload) (any, error) { + if bytes.Equal(payload.Value, mockv1.MockPacketData) { + return mockv1.MockPacketData, nil + } + return nil, mockv1.MockApplicationCallbackError +} diff --git a/testing/mock/v2/mock.go b/testing/mock/v2/mock.go new file mode 100644 index 0000000..a7fa3ae --- /dev/null +++ b/testing/mock/v2/mock.go @@ -0,0 +1,36 @@ +package mock + +import ( + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + channeltypesv2 "github.com/cosmos/ibc-go/v10/modules/core/04-channel/v2/types" + mockv1 "github.com/cosmos/ibc-go/v10/testing/mock" +) + +const ( + ModuleName = "mockv2" +) + +var MockRecvPacketResult = channeltypesv2.RecvPacketResult{ + Status: channeltypesv2.PacketStatus_Success, + Acknowledgement: mockv1.MockAcknowledgement.Acknowledgement(), +} + +func NewMockPayload(sourcePort, destPort string) channeltypesv2.Payload { + return channeltypesv2.Payload{ + SourcePort: sourcePort, + DestinationPort: destPort, + Encoding: transfertypes.EncodingProtobuf, + Value: mockv1.MockPacketData, + Version: mockv1.Version, + } +} + +func NewErrorMockPayload(sourcePort, destPort string) channeltypesv2.Payload { + return channeltypesv2.Payload{ + SourcePort: sourcePort, + DestinationPort: destPort, + Encoding: transfertypes.EncodingProtobuf, + Value: mockv1.MockFailPacketData, + Version: mockv1.Version, + } +} diff --git a/testing/path.go b/testing/path.go new file mode 100644 index 0000000..24c133b --- /dev/null +++ b/testing/path.go @@ -0,0 +1,245 @@ +package ibctesting + +import ( + "bytes" + "errors" + + abci "github.com/cometbft/cometbft/abci/types" + + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" +) + +// Path contains two endpoints representing two chains connected over IBC +type Path struct { + EndpointA *Endpoint + EndpointB *Endpoint +} + +// NewPath constructs an endpoint for each chain using the default values +// for the endpoints. Each endpoint is updated to have a pointer to the +// counterparty endpoint. +func NewPath(chainA, chainB *TestChain) *Path { + endpointA := NewDefaultEndpoint(chainA) + endpointB := NewDefaultEndpoint(chainB) + + endpointA.Counterparty = endpointB + endpointB.Counterparty = endpointA + + return &Path{ + EndpointA: endpointA, + EndpointB: endpointB, + } +} + +// NewTransferPath constructs a new path between each chain suitable for use with +// the transfer module. +func NewTransferPath(chainA, chainB *TestChain) *Path { + path := NewPath(chainA, chainB) + path.EndpointA.ChannelConfig.PortID = TransferPort + path.EndpointB.ChannelConfig.PortID = TransferPort + path.EndpointA.ChannelConfig.Version = transfertypes.V1 + path.EndpointB.ChannelConfig.Version = transfertypes.V1 + + return path +} + +// SetChannelOrdered sets the channel order for both endpoints to ORDERED. +func (path *Path) SetChannelOrdered() { + path.EndpointA.ChannelConfig.Order = channeltypes.ORDERED + path.EndpointB.ChannelConfig.Order = channeltypes.ORDERED +} + +// DisableUniqueChannelIDs provides an opt-out way to not have all channel IDs be different +// while testing. +func (path *Path) DisableUniqueChannelIDs() *Path { + path.EndpointA.disableUniqueChannelIDs = true + path.EndpointB.disableUniqueChannelIDs = true + return path +} + +// RelayPacket attempts to relay the packet first on EndpointA and then on EndpointB +// if EndpointA does not contain a packet commitment for that packet. An error is returned +// if a relay step fails or the packet commitment does not exist on either endpoint. +func (path *Path) RelayPacket(packet channeltypes.Packet) error { + _, _, err := path.RelayPacketWithResults(packet) + return err +} + +// RelayPacketWithResults attempts to relay the packet first on EndpointA and then on EndpointB +// if EndpointA does not contain a packet commitment for that packet. The function returns: +// - The result of the packet receive transaction. +// - The acknowledgement written on the receiving chain. +// - An error if a relay step fails or the packet commitment does not exist on either endpoint. +func (path *Path) RelayPacketWithResults(packet channeltypes.Packet) (*abci.ExecTxResult, []byte, error) { + pc := path.EndpointA.Chain.App.GetIBCKeeper().ChannelKeeper.GetPacketCommitment(path.EndpointA.Chain.GetContext(), packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + if bytes.Equal(pc, channeltypes.CommitPacket(packet)) { + // packet found, relay from A to B + if err := path.EndpointB.UpdateClient(); err != nil { + return nil, nil, err + } + + res, err := path.EndpointB.RecvPacketWithResult(packet) + if err != nil { + return nil, nil, err + } + + ack, err := ParseAckFromEvents(res.Events) + if err != nil { + return nil, nil, err + } + + if err := path.EndpointA.AcknowledgePacket(packet, ack); err != nil { + return nil, nil, err + } + + return res, ack, nil + } + + pc = path.EndpointB.Chain.App.GetIBCKeeper().ChannelKeeper.GetPacketCommitment(path.EndpointB.Chain.GetContext(), packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + if bytes.Equal(pc, channeltypes.CommitPacket(packet)) { + // packet found, relay B to A + if err := path.EndpointA.UpdateClient(); err != nil { + return nil, nil, err + } + + res, err := path.EndpointA.RecvPacketWithResult(packet) + if err != nil { + return nil, nil, err + } + + ack, err := ParseAckFromEvents(res.Events) + if err != nil { + return nil, nil, err + } + + if err := path.EndpointB.AcknowledgePacket(packet, ack); err != nil { + return nil, nil, err + } + + return res, ack, nil + } + + return nil, nil, errors.New("packet commitment does not exist on either endpoint for provided packet") +} + +// Reversed returns a new path with endpoints reversed. +func (path *Path) Reversed() *Path { + reversedPath := *path + reversedPath.EndpointA, reversedPath.EndpointB = path.EndpointB, path.EndpointA + return &reversedPath +} + +// Setup constructs a TM client, connection, and channel on both chains provided. It will +// fail if any error occurs. +func (path *Path) Setup() { + path.SetupConnections() + + // channels can also be referenced through the returned connections + path.CreateChannels() +} + +// SetupV2 constructs clients on both sides and then provides the counterparties for both sides +// This is all that is necessary for path setup with the IBC v2 protocol +func (path *Path) SetupV2() { + path.SetupClients() + + path.SetupCounterparties() +} + +// SetupClients is a helper function to create clients on both chains. It assumes the +// caller does not anticipate any errors. +func (path *Path) SetupClients() { + err := path.EndpointA.CreateClient() + if err != nil { + panic(err) + } + + err = path.EndpointB.CreateClient() + if err != nil { + panic(err) + } +} + +// SetupCounterparties is a helper function to set the counterparties supporting IBC v2 on both +// chains. It assumes the caller does not anticipate any errors. +func (path *Path) SetupCounterparties() { + if err := path.EndpointB.RegisterCounterparty(); err != nil { + panic(err) + } + + if err := path.EndpointA.RegisterCounterparty(); err != nil { + panic(err) + } +} + +// SetupConnections is a helper function to create clients and the appropriate +// connections on both the source and counterparty chain. It assumes the caller does not +// anticipate any errors. +func (path *Path) SetupConnections() { + path.SetupClients() + + path.CreateConnections() +} + +// CreateConnections constructs and executes connection handshake messages in order to create +// OPEN connections on chainA and chainB. The function expects the connections to be +// successfully opened otherwise testing will fail. +func (path *Path) CreateConnections() { + err := path.EndpointA.ConnOpenInit() + if err != nil { + panic(err) + } + + err = path.EndpointB.ConnOpenTry() + if err != nil { + panic(err) + } + + err = path.EndpointA.ConnOpenAck() + if err != nil { + panic(err) + } + + err = path.EndpointB.ConnOpenConfirm() + if err != nil { + panic(err) + } + + // ensure counterparty is up to date + err = path.EndpointA.UpdateClient() + if err != nil { + panic(err) + } +} + +// CreateChannels constructs and executes channel handshake messages in order to create +// OPEN channels on chainA and chainB. The function expects the channels to be successfully +// opened otherwise testing will fail. +func (path *Path) CreateChannels() { + err := path.EndpointA.ChanOpenInit() + if err != nil { + panic(err) + } + + err = path.EndpointB.ChanOpenTry() + if err != nil { + panic(err) + } + + err = path.EndpointA.ChanOpenAck() + if err != nil { + panic(err) + } + + err = path.EndpointB.ChanOpenConfirm() + if err != nil { + panic(err) + } + + // ensure counterparty is up to date + err = path.EndpointA.UpdateClient() + if err != nil { + panic(err) + } +} diff --git a/testing/simapp/README.md b/testing/simapp/README.md new file mode 100644 index 0000000..2f3ba71 --- /dev/null +++ b/testing/simapp/README.md @@ -0,0 +1,47 @@ +# simapp + +simapp is an application built using the Cosmos SDK for testing and educational purposes. + +## Running testnets with `simd` + +If you want to spin up a quick testnet with your friends, you can follow these steps. +Unless otherwise noted, every step must be done by everyone who wants to participate +in this testnet. + +1. `$ make build`. This will build the `simd` binary and install it in your Cosmos SDK repo, + inside a new `build` directory. The following instructions are run from inside + that directory. +2. If you've run `simd` before, you may need to reset your database before starting a new + testnet: `$ ./simd unsafe-reset-all` +3. `$ ./simd init [moniker]`. This will initialize a new working directory, by default at + `~/.simapp`. You need a provide a "moniker," but it doesn't matter what it is. +4. `$ ./simd keys add [key_name]`. This will create a new key, with a name of your choosing. + Save the output of this command somewhere; you'll need the address generated here later. +5. `$ ./simd add-genesis-account $(simd keys show [key_name] -a) [amount]`, where `key_name` + is the same key name as before; and `amount` is something like `10000000000000000000000000stake`. +6. `$ ./simd gentx [key_name] [amount] --chain-id [chain-id]`. This will create the + genesis transaction for your new chain. +7. Now, one person needs to create the genesis file `genesis.json` using the genesis transactions + from every participant, by gathering all the genesis transactions under `config/gentx` and then + calling `./simd collect-gentxs`. This will create a new `genesis.json` file that includes data + from all the validators (we sometimes call it the "super genesis file" to distinguish it from + single-validator genesis files). +8. Once you've received the super genesis file, overwrite your original `genesis.json` file with + the new super `genesis.json`. +9. Modify your `config/config.toml` (in the simapp working directory) to include the other participants as + persistent peers: + + ```text + # Comma separated list of nodes to keep persistent connections to + persistent_peers = "[validator address]@[ip address]:[port],[validator address]@[ip address]:[port]" + ``` + + You can find `validator address` by running `./simd tendermint show-node-id`. (It will be hex-encoded.) + By default, `port` is 26656. +10. Now you can start your nodes: `$ ./simd start`. + +Now you have a small testnet that you can use to try out changes to the Cosmos SDK or Tendermint! + +NOTE: Sometimes creating the network through the `collect-gentxs` will fail, and validators will start +in a funny state (and then panic). If this happens, you can try to create and start the network first +with a single validator and then add additional validators using a `create-validator` transaction. diff --git a/testing/simapp/ante.go b/testing/simapp/ante.go new file mode 100644 index 0000000..f24b414 --- /dev/null +++ b/testing/simapp/ante.go @@ -0,0 +1,51 @@ +package simapp + +import ( + "errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + + ibcante "github.com/cosmos/ibc-go/v10/modules/core/ante" + "github.com/cosmos/ibc-go/v10/modules/core/keeper" +) + +// HandlerOptions are the options required for constructing a default SDK AnteHandler. +type HandlerOptions struct { + ante.HandlerOptions + IBCKeeper *keeper.Keeper +} + +// NewAnteHandler returns an AnteHandler that checks and increments sequence +// numbers, checks signatures & account numbers, and deducts fees from the first +// signer. +func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { + if options.AccountKeeper == nil { + return nil, errors.New("account keeper is required for ante builder") + } + + if options.BankKeeper == nil { + return nil, errors.New("bank keeper is required for ante builder") + } + + if options.SignModeHandler == nil { + return nil, errors.New("sign mode handler is required for ante builder") + } + + anteDecorators := []sdk.AnteDecorator{ + ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first + ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker), + ante.NewValidateBasicDecorator(), + ante.NewTxTimeoutHeightDecorator(), + ante.NewValidateMemoDecorator(options.AccountKeeper), + ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), + ante.NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators + ante.NewValidateSigCountDecorator(options.AccountKeeper), + ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer), + ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), + ante.NewIncrementSequenceDecorator(options.AccountKeeper), + ibcante.NewRedundantRelayDecorator(options.IBCKeeper), + } + + return sdk.ChainAnteDecorators(anteDecorators...), nil +} diff --git a/testing/simapp/app.go b/testing/simapp/app.go new file mode 100644 index 0000000..6604475 --- /dev/null +++ b/testing/simapp/app.go @@ -0,0 +1,873 @@ +package simapp + +import ( + "encoding/json" + "fmt" + "io" + "maps" + "os" + "path/filepath" + + dbm "github.com/cosmos/cosmos-db" + "github.com/cosmos/gogoproto/proto" + "github.com/spf13/cast" + + "cosmossdk.io/log" + storetypes "cosmossdk.io/store/types" + "cosmossdk.io/x/tx/signing" + "cosmossdk.io/x/upgrade" + upgradekeeper "cosmossdk.io/x/upgrade/keeper" + upgradetypes "cosmossdk.io/x/upgrade/types" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/grpc/cmtservice" + nodeservice "github.com/cosmos/cosmos-sdk/client/grpc/node" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/address" + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/runtime" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/server/api" + "github.com/cosmos/cosmos-sdk/server/config" + servertypes "github.com/cosmos/cosmos-sdk/server/types" + "github.com/cosmos/cosmos-sdk/std" + "github.com/cosmos/cosmos-sdk/testutil/testdata/testpb" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/types/msgservice" + "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/ante" + authcodec "github.com/cosmos/cosmos-sdk/x/auth/codec" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + "github.com/cosmos/cosmos-sdk/x/auth/posthandler" + authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/auth/vesting" + vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + "github.com/cosmos/cosmos-sdk/x/authz" + authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" + authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module" + "github.com/cosmos/cosmos-sdk/x/bank" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/cosmos-sdk/x/consensus" + consensusparamkeeper "github.com/cosmos/cosmos-sdk/x/consensus/keeper" + consensusparamtypes "github.com/cosmos/cosmos-sdk/x/consensus/types" + distr "github.com/cosmos/cosmos-sdk/x/distribution" + distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/cosmos/cosmos-sdk/x/genutil" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + "github.com/cosmos/cosmos-sdk/x/gov" + govclient "github.com/cosmos/cosmos-sdk/x/gov/client" + govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/cosmos/cosmos-sdk/x/group" + "github.com/cosmos/cosmos-sdk/x/mint" + mintkeeper "github.com/cosmos/cosmos-sdk/x/mint/keeper" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + "github.com/cosmos/cosmos-sdk/x/params" + paramsclient "github.com/cosmos/cosmos-sdk/x/params/client" + paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/cosmos/cosmos-sdk/x/slashing" + slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/staking" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + abci "github.com/cometbft/cometbft/abci/types" + + ica "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts" + icacontroller "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller" + icacontrollerkeeper "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/keeper" + icacontrollertypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/controller/types" + icahost "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host" + icahostkeeper "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/keeper" + icahosttypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v10/modules/apps/27-interchain-accounts/types" + "github.com/cosmos/ibc-go/v10/modules/apps/transfer" + ibctransferkeeper "github.com/cosmos/ibc-go/v10/modules/apps/transfer/keeper" + ibctransfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + transferv2 "github.com/cosmos/ibc-go/v10/modules/apps/transfer/v2" + ibc "github.com/cosmos/ibc-go/v10/modules/core" + ibcclienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + ibcconnectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + porttypes "github.com/cosmos/ibc-go/v10/modules/core/05-port/types" + ibcapi "github.com/cosmos/ibc-go/v10/modules/core/api" + ibcexported "github.com/cosmos/ibc-go/v10/modules/core/exported" + ibckeeper "github.com/cosmos/ibc-go/v10/modules/core/keeper" + solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + ibcmock "github.com/cosmos/ibc-go/v10/testing/mock" + mockv2 "github.com/cosmos/ibc-go/v10/testing/mock/v2" +) + +const appName = "SimApp" + +var ( + // DefaultNodeHome default home directories for the application daemon + DefaultNodeHome string + + // module account permissions + maccPerms = map[string][]string{ + authtypes.FeeCollectorName: nil, + distrtypes.ModuleName: nil, + minttypes.ModuleName: {authtypes.Minter}, + stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking}, + stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking}, + govtypes.ModuleName: {authtypes.Burner}, + ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + icatypes.ModuleName: nil, + ibcmock.ModuleName: nil, + } +) + +var _ servertypes.Application = (*SimApp)(nil) + +// SimApp extends an ABCI application, but with most of its parameters exported. +// They are exported for convenience in creating helper functions. +type SimApp struct { + *baseapp.BaseApp + legacyAmino *codec.LegacyAmino + appCodec codec.Codec + txConfig client.TxConfig + interfaceRegistry types.InterfaceRegistry + + // keys to access the substores + keys map[string]*storetypes.KVStoreKey + tkeys map[string]*storetypes.TransientStoreKey + memKeys map[string]*storetypes.MemoryStoreKey + + // keepers + AccountKeeper authkeeper.AccountKeeper + BankKeeper bankkeeper.Keeper + StakingKeeper *stakingkeeper.Keeper + SlashingKeeper slashingkeeper.Keeper + MintKeeper mintkeeper.Keeper + DistrKeeper distrkeeper.Keeper + GovKeeper govkeeper.Keeper + UpgradeKeeper *upgradekeeper.Keeper + ParamsKeeper paramskeeper.Keeper + AuthzKeeper authzkeeper.Keeper + IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly + ICAControllerKeeper icacontrollerkeeper.Keeper + ICAHostKeeper icahostkeeper.Keeper + TransferKeeper ibctransferkeeper.Keeper + ConsensusParamsKeeper consensusparamkeeper.Keeper + + // make IBC modules public for test purposes + // these modules are never directly routed to by the IBC Router + IBCMockModule ibcmock.IBCModule + ICAAuthModule ibcmock.IBCModule + + MockModuleV2A mockv2.IBCModule + MockModuleV2B mockv2.IBCModule + + // the module manager + ModuleManager *module.Manager + BasicModuleManager module.BasicManager + + // simulation manager + simulationManager *module.SimulationManager + + // module configurator + configurator module.Configurator +} + +func init() { + userHomeDir, err := os.UserHomeDir() + if err != nil { + panic(err) + } + + DefaultNodeHome = filepath.Join(userHomeDir, ".simapp") +} + +// NewSimApp returns a reference to an initialized SimApp. +func NewSimApp( + logger log.Logger, + db dbm.DB, + traceStore io.Writer, + loadLatest bool, + appOpts servertypes.AppOptions, + baseAppOptions ...func(*baseapp.BaseApp), +) *SimApp { + interfaceRegistry, _ := types.NewInterfaceRegistryWithOptions(types.InterfaceRegistryOptions{ + ProtoFiles: proto.HybridResolver, + SigningOptions: signing.Options{ + AddressCodec: address.Bech32Codec{ + Bech32Prefix: sdk.GetConfig().GetBech32AccountAddrPrefix(), + }, + ValidatorAddressCodec: address.Bech32Codec{ + Bech32Prefix: sdk.GetConfig().GetBech32ValidatorAddrPrefix(), + }, + }, + }) + appCodec := codec.NewProtoCodec(interfaceRegistry) + legacyAmino := codec.NewLegacyAmino() + txConfig := authtx.NewTxConfig(appCodec, authtx.DefaultSignModes) + + std.RegisterLegacyAminoCodec(legacyAmino) + std.RegisterInterfaces(interfaceRegistry) + + // Below we could construct and set an application specific mempool and + // ABCI 1.0 PrepareProposal and ProcessProposal handlers. These defaults are + // already set in the SDK's BaseApp, this shows an example of how to override + // them. + // + // Example: + // + // bApp := baseapp.NewBaseApp(...) + // nonceMempool := mempool.NewSenderNonceMempool() + // abciPropHandler := NewDefaultProposalHandler(nonceMempool, bApp) + // + // bApp.SetMempool(nonceMempool) + // bApp.SetPrepareProposal(abciPropHandler.PrepareProposalHandler()) + // bApp.SetProcessProposal(abciPropHandler.ProcessProposalHandler()) + // + // Alternatively, you can construct BaseApp options, append those to + // baseAppOptions and pass them to NewBaseApp. + // + // Example: + // + // prepareOpt = func(app *baseapp.BaseApp) { + // abciPropHandler := baseapp.NewDefaultProposalHandler(nonceMempool, app) + // app.SetPrepareProposal(abciPropHandler.PrepareProposalHandler()) + // } + // baseAppOptions = append(baseAppOptions, prepareOpt) + + bApp := baseapp.NewBaseApp(appName, logger, db, txConfig.TxDecoder(), baseAppOptions...) + bApp.SetCommitMultiStoreTracer(traceStore) + bApp.SetVersion(version.Version) + bApp.SetInterfaceRegistry(interfaceRegistry) + bApp.SetTxEncoder(txConfig.TxEncoder()) + + keys := storetypes.NewKVStoreKeys( + authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, + minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey, + govtypes.StoreKey, group.StoreKey, paramstypes.StoreKey, ibcexported.StoreKey, upgradetypes.StoreKey, + ibctransfertypes.StoreKey, icacontrollertypes.StoreKey, icahosttypes.StoreKey, + authzkeeper.StoreKey, consensusparamtypes.StoreKey, + ) + + // register streaming services + if err := bApp.RegisterStreamingServices(appOpts, keys); err != nil { + panic(err) + } + + tkeys := storetypes.NewTransientStoreKeys(paramstypes.TStoreKey) + memKeys := storetypes.NewMemoryStoreKeys(ibcmock.MemStoreKey) + + app := &SimApp{ + BaseApp: bApp, + legacyAmino: legacyAmino, + appCodec: appCodec, + txConfig: txConfig, + interfaceRegistry: interfaceRegistry, + keys: keys, + tkeys: tkeys, + memKeys: memKeys, + } + + app.ParamsKeeper = initParamsKeeper(appCodec, legacyAmino, keys[paramstypes.StoreKey], tkeys[paramstypes.TStoreKey]) + + // set the BaseApp's parameter store + app.ConsensusParamsKeeper = consensusparamkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[consensusparamtypes.StoreKey]), authtypes.NewModuleAddress(govtypes.ModuleName).String(), runtime.EventService{}) + bApp.SetParamStore(app.ConsensusParamsKeeper.ParamsStore) + + // SDK module keepers + + // add keepers + app.AccountKeeper = authkeeper.NewAccountKeeper(appCodec, runtime.NewKVStoreService(keys[authtypes.StoreKey]), authtypes.ProtoBaseAccount, maccPerms, authcodec.NewBech32Codec(sdk.Bech32MainPrefix), sdk.Bech32MainPrefix, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + + app.BankKeeper = bankkeeper.NewBaseKeeper( + appCodec, + runtime.NewKVStoreService(keys[banktypes.StoreKey]), + app.AccountKeeper, + BlockedAddresses(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + logger, + ) + app.StakingKeeper = stakingkeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[stakingtypes.StoreKey]), app.AccountKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), authcodec.NewBech32Codec(sdk.Bech32PrefixValAddr), authcodec.NewBech32Codec(sdk.Bech32PrefixConsAddr), + ) + app.MintKeeper = mintkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[minttypes.StoreKey]), app.StakingKeeper, app.AccountKeeper, app.BankKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + + app.DistrKeeper = distrkeeper.NewKeeper(appCodec, runtime.NewKVStoreService(keys[distrtypes.StoreKey]), app.AccountKeeper, app.BankKeeper, app.StakingKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + + app.SlashingKeeper = slashingkeeper.NewKeeper( + appCodec, legacyAmino, runtime.NewKVStoreService(keys[slashingtypes.StoreKey]), app.StakingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // register the staking hooks + // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks + app.StakingKeeper.SetHooks( + stakingtypes.NewMultiStakingHooks(app.DistrKeeper.Hooks(), app.SlashingKeeper.Hooks()), + ) + + app.AuthzKeeper = authzkeeper.NewKeeper(runtime.NewKVStoreService(keys[authzkeeper.StoreKey]), appCodec, app.MsgServiceRouter(), app.AccountKeeper) + + // get skipUpgradeHeights from the app options + skipUpgradeHeights := map[int64]bool{} + for _, h := range cast.ToIntSlice(appOpts.Get(server.FlagUnsafeSkipUpgrades)) { + skipUpgradeHeights[int64(h)] = true + } + homePath := cast.ToString(appOpts.Get(flags.FlagHome)) + // set the governance module account as the authority for conducting upgrades + app.UpgradeKeeper = upgradekeeper.NewKeeper(skipUpgradeHeights, runtime.NewKVStoreService(keys[upgradetypes.StoreKey]), appCodec, homePath, app.BaseApp, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + + app.IBCKeeper = ibckeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[ibcexported.StoreKey]), app.GetSubspace(ibcexported.ModuleName), app.UpgradeKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + govConfig := govtypes.DefaultConfig() + /* + Example of setting gov params: + govConfig.MaxMetadataLen = 10000 + */ + govKeeper := govkeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[govtypes.StoreKey]), app.AccountKeeper, app.BankKeeper, + app.StakingKeeper, app.DistrKeeper, app.MsgServiceRouter(), govConfig, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + app.GovKeeper = *govKeeper.SetHooks( + govtypes.NewMultiGovHooks( + // register the governance hooks + ), + ) + + // ICA Controller keeper + app.ICAControllerKeeper = icacontrollerkeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[icacontrollertypes.StoreKey]), app.GetSubspace(icacontrollertypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeper, + app.MsgServiceRouter(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // ICA Host keeper + app.ICAHostKeeper = icahostkeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[icahosttypes.StoreKey]), app.GetSubspace(icahosttypes.SubModuleName), + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeper, app.AccountKeeper, + app.MsgServiceRouter(), app.GRPCQueryRouter(), + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // Create IBC Router + ibcRouter := porttypes.NewRouter() + ibcRouterV2 := ibcapi.NewRouter() + + // Middleware Stacks + + // Create Transfer Keeper + app.TransferKeeper = ibctransferkeeper.NewKeeper( + appCodec, runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]), app.GetSubspace(ibctransfertypes.ModuleName), + app.IBCKeeper.ChannelKeeper, + app.IBCKeeper.ChannelKeeper, + app.MsgServiceRouter(), + app.AccountKeeper, app.BankKeeper, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), + ) + + // Mock Module Stack + + // Mock Module setup for testing IBC and also acts as the interchain accounts authentication module + // NOTE: the IBC mock keeper and application module is used only for testing core IBC. Do + // not replicate if you do not need to test core IBC or light clients. + mockModule := ibcmock.NewAppModule() + + // The mock module is used for testing IBC + mockIBCModule := ibcmock.NewIBCModule(&mockModule, ibcmock.NewIBCApp(ibcmock.ModuleName)) + app.IBCMockModule = mockIBCModule + ibcRouter.AddRoute(ibcmock.ModuleName, mockIBCModule) + + // Mock IBC app wrapped with a middleware which does not implement the UpgradeableModule interface. + // NOTE: this is used to test integration with apps which do not yet fulfill the UpgradeableModule interface and error when + // an upgrade is tried on the channel. + mockBlockUpgradeIBCModule := ibcmock.NewIBCModule(&mockModule, ibcmock.NewIBCApp(ibcmock.MockBlockUpgrade)) + mockBlockUpgradeMw := ibcmock.NewBlockUpgradeMiddleware(&mockModule, mockBlockUpgradeIBCModule.IBCApp) + ibcRouter.AddRoute(ibcmock.MockBlockUpgrade, mockBlockUpgradeMw) + + // Create Transfer Stack + // SendPacket, since it is originating from the application to core IBC: + // transferKeeper.SendPacket -> channel.SendPacket + + // RecvPacket, message that originates from core IBC and goes down to app, the flow is the other way + // channel.RecvPacket -> transfer.OnRecvPacket + + // create IBC module from bottom to top of stack + var transferStack porttypes.IBCModule = transfer.NewIBCModule(app.TransferKeeper) + + // Add transfer stack to IBC Router + ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack) + + // Create Interchain Accounts Stack + // SendPacket, since it is originating from the application to core IBC: + // icaControllerKeeper.SendTx -> channel.SendPacket + + // initialize ICA module with mock module as the authentication module on the controller side + var icaControllerStack porttypes.IBCModule + icaControllerStack = ibcmock.NewIBCModule(&mockModule, ibcmock.NewIBCApp("")) + var ok bool + app.ICAAuthModule, ok = icaControllerStack.(ibcmock.IBCModule) + if !ok { + panic(fmt.Errorf("cannot convert %T into %T", icaControllerStack, app.ICAAuthModule)) + } + icaControllerStack = icacontroller.NewIBCMiddlewareWithAuth(icaControllerStack, app.ICAControllerKeeper) + + // RecvPacket, message that originates from core IBC and goes down to app, the flow is: + // channel.RecvPacket -> icaHost.OnRecvPacket + + var icaHostStack porttypes.IBCModule = icahost.NewIBCModule(app.ICAHostKeeper) + + // Add host, controller & ica auth modules to IBC router + ibcRouter. + AddRoute(icacontrollertypes.SubModuleName, icaControllerStack). + AddRoute(icahosttypes.SubModuleName, icaHostStack). + AddRoute(ibcmock.ModuleName+icacontrollertypes.SubModuleName, icaControllerStack) // ica with mock auth module stack route to ica (top level of middleware stack) + + // create two separate mock v2 applications so that it is possible to test multi packet data. + mockV2A := mockv2.NewIBCModule() + ibcRouterV2.AddRoute(mockv2.PortIDA, mockV2A) + app.MockModuleV2A = mockV2A + + mockV2B := mockv2.NewIBCModule() + ibcRouterV2.AddRoute(mockv2.PortIDB, mockV2B) + app.MockModuleV2B = mockV2B + + // register the transfer v2 module. + ibcRouterV2.AddRoute(ibctransfertypes.PortID, transferv2.NewIBCModule(app.TransferKeeper)) + + // Seal the IBC Router + app.IBCKeeper.SetRouter(ibcRouter) + app.IBCKeeper.SetRouterV2(ibcRouterV2) + + clientKeeper := app.IBCKeeper.ClientKeeper + storeProvider := app.IBCKeeper.ClientKeeper.GetStoreProvider() + + tmLightClientModule := ibctm.NewLightClientModule(appCodec, storeProvider) + clientKeeper.AddRoute(ibctm.ModuleName, &tmLightClientModule) + + smLightClientModule := solomachine.NewLightClientModule(appCodec, storeProvider) + clientKeeper.AddRoute(solomachine.ModuleName, &smLightClientModule) + + // **** Module Options **** + + // NOTE: Any module instantiated in the module manager that is later modified + // must be passed by reference here. + app.ModuleManager = module.NewManager( + genutil.NewAppModule( + app.AccountKeeper, app.StakingKeeper, app, + txConfig, + ), + auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, app.GetSubspace(authtypes.ModuleName)), + vesting.NewAppModule(app.AccountKeeper, app.BankKeeper), + bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper, app.GetSubspace(banktypes.ModuleName)), + gov.NewAppModule(appCodec, &app.GovKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(govtypes.ModuleName)), + mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper, nil, app.GetSubspace(minttypes.ModuleName)), + slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(slashingtypes.ModuleName), app.interfaceRegistry), + distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.GetSubspace(distrtypes.ModuleName)), + staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, app.GetSubspace(stakingtypes.ModuleName)), + upgrade.NewAppModule(app.UpgradeKeeper, app.AccountKeeper.AddressCodec()), + params.NewAppModule(app.ParamsKeeper), + authzmodule.NewAppModule(appCodec, app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), + consensus.NewAppModule(appCodec, app.ConsensusParamsKeeper), + + // IBC modules + ibc.NewAppModule(app.IBCKeeper), + transfer.NewAppModule(app.TransferKeeper), + ica.NewAppModule(&app.ICAControllerKeeper, &app.ICAHostKeeper), + mockModule, + + // IBC light clients + ibctm.NewAppModule(tmLightClientModule), + solomachine.NewAppModule(smLightClientModule), + ) + + // BasicModuleManager defines the module BasicManager is in charge of setting up basic, + // non-dependant module elements, such as codec registration and genesis verification. + // By default it is composed of all the module from the module manager. + // Additionally, app module basics can be overwritten by passing them as argument. + app.BasicModuleManager = module.NewBasicManagerFromManager( + app.ModuleManager, + map[string]module.AppModuleBasic{ + genutiltypes.ModuleName: genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator), + govtypes.ModuleName: gov.NewAppModuleBasic( + []govclient.ProposalHandler{ + paramsclient.ProposalHandler, + }, + ), + }) + app.BasicModuleManager.RegisterLegacyAminoCodec(legacyAmino) + app.BasicModuleManager.RegisterInterfaces(interfaceRegistry) + + // NOTE: upgrade module is required to be prioritized + app.ModuleManager.SetOrderPreBlockers( + upgradetypes.ModuleName, + authtypes.ModuleName, + ) + + // During begin block slashing happens after distr.BeginBlocker so that + // there is nothing left over in the validator fee pool, so as to keep the + // CanWithdrawInvariant invariant. + // NOTE: staking module is required if HistoricalEntries param > 0 + app.ModuleManager.SetOrderBeginBlockers( + minttypes.ModuleName, + distrtypes.ModuleName, + slashingtypes.ModuleName, + stakingtypes.ModuleName, + ibcexported.ModuleName, + ibctransfertypes.ModuleName, + genutiltypes.ModuleName, + authz.ModuleName, + icatypes.ModuleName, + ibcmock.ModuleName, + ) + app.ModuleManager.SetOrderEndBlockers( + govtypes.ModuleName, + stakingtypes.ModuleName, + ibcexported.ModuleName, + ibctransfertypes.ModuleName, + genutiltypes.ModuleName, + icatypes.ModuleName, + ibcmock.ModuleName, + group.ModuleName, + ) + + // NOTE: The genutils module must occur after staking so that pools are + // properly initialized with tokens from genesis accounts. + // NOTE: The genutils module must also occur after auth so that it can access the params from auth. + genesisModuleOrder := []string{ + authtypes.ModuleName, + banktypes.ModuleName, distrtypes.ModuleName, stakingtypes.ModuleName, + slashingtypes.ModuleName, govtypes.ModuleName, minttypes.ModuleName, + ibcexported.ModuleName, genutiltypes.ModuleName, authz.ModuleName, ibctransfertypes.ModuleName, + icatypes.ModuleName, ibcmock.ModuleName, paramstypes.ModuleName, upgradetypes.ModuleName, + vestingtypes.ModuleName, group.ModuleName, consensusparamtypes.ModuleName, + } + app.ModuleManager.SetOrderInitGenesis(genesisModuleOrder...) + app.ModuleManager.SetOrderExportGenesis(genesisModuleOrder...) + + // Uncomment if you want to set a custom migration order here. + // app.ModuleManager.SetOrderMigrations(custom order) + + app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter()) + err := app.ModuleManager.RegisterServices(app.configurator) + if err != nil { + panic(err) + } + + // add test gRPC service for testing gRPC queries in isolation + testpb.RegisterQueryServer(app.GRPCQueryRouter(), testpb.QueryImpl{}) + + // create the simulation manager and define the order of the modules for deterministic simulations + // + // NOTE: this is not required apps that don't use the simulator for fuzz testing + // transactions + overrideModules := map[string]module.AppModuleSimulation{ + authtypes.ModuleName: auth.NewAppModule(app.appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts, app.GetSubspace(authtypes.ModuleName)), + } + app.simulationManager = module.NewSimulationManagerFromAppModules(app.ModuleManager.Modules, overrideModules) + + app.simulationManager.RegisterStoreDecoders() + + // initialize stores + app.MountKVStores(keys) + app.MountTransientStores(tkeys) + app.MountMemoryStores(memKeys) + + // initialize BaseApp + app.SetInitChainer(app.InitChainer) + app.SetPreBlocker(app.PreBlocker) + app.SetBeginBlocker(app.BeginBlocker) + app.SetEndBlocker(app.EndBlocker) + app.setAnteHandler(txConfig) + + // In v0.46, the SDK introduces _postHandlers_. PostHandlers are like + // antehandlers, but are run _after_ the `runMsgs` execution. They are also + // defined as a chain, and have the same signature as antehandlers. + // + // In baseapp, postHandlers are run in the same store branch as `runMsgs`, + // meaning that both `runMsgs` and `postHandler` state will be committed if + // both are successful, and both will be reverted if any of the two fails. + // + // The SDK exposes a default postHandlers chain, which is comprised of only + // one decorator: the Transaction Tips decorator. However, some chains do + // not need it by default, so feel free to comment the next line if you do + // not need tips. + // To read more about tips: + // https://docs.cosmos.network/main/core/tips.html + // + // Please note that changing any of the anteHandler or postHandler chain is + // likely to be a state-machine breaking change, which needs a coordinated + // upgrade. + app.setPostHandler() + + // At startup, after all modules have been registered, check that all proto + // annotations are correct. + protoFiles, err := proto.MergedRegistry() + if err != nil { + panic(err) + } + err = msgservice.ValidateProtoAnnotations(protoFiles) + if err != nil { + // Once we switch to using protoreflect-based antehandlers, we might + // want to panic here instead of logging a warning. + _, err := fmt.Fprintln(os.Stderr, err.Error()) + if err != nil { + fmt.Println("could not write to stderr") + } + } + + if loadLatest { + if err := app.LoadLatestVersion(); err != nil { + panic(fmt.Errorf("error loading last version: %w", err)) + } + } + + return app +} + +func (app *SimApp) setAnteHandler(txConfig client.TxConfig) { + anteHandler, err := NewAnteHandler( + HandlerOptions{ + ante.HandlerOptions{ + AccountKeeper: app.AccountKeeper, + BankKeeper: app.BankKeeper, + SignModeHandler: txConfig.SignModeHandler(), + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + }, + app.IBCKeeper, + }, + ) + if err != nil { + panic(err) + } + + // Set the AnteHandler for the app + app.SetAnteHandler(anteHandler) +} + +func (app *SimApp) setPostHandler() { + postHandler, err := posthandler.NewPostHandler( + posthandler.HandlerOptions{}, + ) + if err != nil { + panic(err) + } + + app.SetPostHandler(postHandler) +} + +// Name returns the name of the App +func (app *SimApp) Name() string { return app.BaseApp.Name() } + +// PreBlocker application updates every pre block +func (app *SimApp) PreBlocker(ctx sdk.Context, _ *abci.RequestFinalizeBlock) (*sdk.ResponsePreBlock, error) { + return app.ModuleManager.PreBlock(ctx) +} + +// BeginBlocker application updates every begin block +func (app *SimApp) BeginBlocker(ctx sdk.Context) (sdk.BeginBlock, error) { + return app.ModuleManager.BeginBlock(ctx) +} + +// EndBlocker application updates every end block +func (app *SimApp) EndBlocker(ctx sdk.Context) (sdk.EndBlock, error) { + return app.ModuleManager.EndBlock(ctx) +} + +// Configurator returns the configurator for the app +func (app *SimApp) Configurator() module.Configurator { + return app.configurator +} + +// InitChainer application update at chain initialization +func (app *SimApp) InitChainer(ctx sdk.Context, req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { + var genesisState GenesisState + if err := json.Unmarshal(req.AppStateBytes, &genesisState); err != nil { + panic(err) + } + if err := app.UpgradeKeeper.SetModuleVersionMap(ctx, app.ModuleManager.GetVersionMap()); err != nil { + panic(err) + } + return app.ModuleManager.InitGenesis(ctx, app.appCodec, genesisState) +} + +// LoadHeight loads a particular height +func (app *SimApp) LoadHeight(height int64) error { + return app.LoadVersion(height) +} + +// LegacyAmino returns SimApp's amino codec. +// +// NOTE: This is solely to be used for testing purposes as it may be desirable +// for modules to register their own custom testing types. +func (app *SimApp) LegacyAmino() *codec.LegacyAmino { + return app.legacyAmino +} + +// AppCodec returns SimApp's app codec. +// +// NOTE: This is solely to be used for testing purposes as it may be desirable +// for modules to register their own custom testing types. +func (app *SimApp) AppCodec() codec.Codec { + return app.appCodec +} + +// InterfaceRegistry returns SimApp's InterfaceRegistry +func (app *SimApp) InterfaceRegistry() types.InterfaceRegistry { + return app.interfaceRegistry +} + +// TxConfig returns SimApp's TxConfig +func (app *SimApp) TxConfig() client.TxConfig { + return app.txConfig +} + +// DefaultGenesis returns a default genesis from the registered AppModuleBasic's. +func (app *SimApp) DefaultGenesis() map[string]json.RawMessage { + return app.BasicModuleManager.DefaultGenesis(app.appCodec) +} + +// GetKey returns the KVStoreKey for the provided store key. +// +// NOTE: This is solely to be used for testing purposes. +func (app *SimApp) GetKey(storeKey string) *storetypes.KVStoreKey { + return app.keys[storeKey] +} + +// GetStoreKeys returns all the stored store keys. +func (app *SimApp) GetStoreKeys() []storetypes.StoreKey { + keys := make([]storetypes.StoreKey, 0, len(app.keys)) + for _, key := range app.keys { + keys = append(keys, key) + } + + return keys +} + +// GetSubspace returns a param subspace for a given module name. +// +// NOTE: This is solely to be used for testing purposes. +func (app *SimApp) GetSubspace(moduleName string) paramstypes.Subspace { + subspace, _ := app.ParamsKeeper.GetSubspace(moduleName) + return subspace +} + +// SimulationManager implements the SimulationApp interface +func (app *SimApp) SimulationManager() *module.SimulationManager { + return app.simulationManager +} + +// RegisterAPIRoutes registers all application module routes with the provided +// API server. +func (app *SimApp) RegisterAPIRoutes(apiSvr *api.Server, apiConfig config.APIConfig) { + clientCtx := apiSvr.ClientCtx + // Register new tx routes from grpc-gateway. + authtx.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // Register new CometBFT queries routes from grpc-gateway. + cmtservice.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // Register node gRPC service for grpc-gateway. + nodeservice.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // Register grpc-gateway routes for all modules. + app.BasicModuleManager.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter) + + // register swagger API from root so that other applications can override easily + if err := server.RegisterSwaggerAPI(apiSvr.ClientCtx, apiSvr.Router, apiConfig.Swagger); err != nil { + panic(err) + } +} + +// RegisterTxService implements the Application.RegisterTxService method. +func (app *SimApp) RegisterTxService(clientCtx client.Context) { + authtx.RegisterTxService(app.GRPCQueryRouter(), clientCtx, app.Simulate, app.interfaceRegistry) +} + +// RegisterTendermintService implements the Application.RegisterTendermintService method. +func (app *SimApp) RegisterTendermintService(clientCtx client.Context) { + cmtApp := server.NewCometABCIWrapper(app) + cmtservice.RegisterTendermintService( + clientCtx, + app.GRPCQueryRouter(), + app.interfaceRegistry, + cmtApp.Query, + ) +} + +func (app *SimApp) RegisterNodeService(clientCtx client.Context, cfg config.Config) { + nodeservice.RegisterNodeService(clientCtx, app.GRPCQueryRouter(), cfg) +} + +// GetMaccPerms returns a copy of the module account permissions +// +// NOTE: This is solely to be used for testing purposes. +func GetMaccPerms() map[string][]string { + dupMaccPerms := make(map[string][]string) + maps.Copy(dupMaccPerms, maccPerms) + + return dupMaccPerms +} + +// BlockedAddresses returns all the app's blocked account addresses. +func BlockedAddresses() map[string]bool { + modAccAddrs := make(map[string]bool) + for acc := range GetMaccPerms() { + modAccAddrs[authtypes.NewModuleAddress(acc).String()] = true + } + + // allow the following addresses to receive funds + delete(modAccAddrs, authtypes.NewModuleAddress(govtypes.ModuleName).String()) + delete(modAccAddrs, authtypes.NewModuleAddress(ibcmock.ModuleName).String()) + + return modAccAddrs +} + +// initParamsKeeper init params keeper and its subspaces +func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey storetypes.StoreKey) paramskeeper.Keeper { + paramsKeeper := paramskeeper.NewKeeper(appCodec, legacyAmino, key, tkey) + + // register the key tables for legacy param subspaces + keyTable := ibcclienttypes.ParamKeyTable() + keyTable.RegisterParamSet(&ibcconnectiontypes.Params{}) + paramsKeeper.Subspace(ibcexported.ModuleName).WithKeyTable(keyTable) + paramsKeeper.Subspace(ibctransfertypes.ModuleName).WithKeyTable(ibctransfertypes.ParamKeyTable()) + paramsKeeper.Subspace(icacontrollertypes.SubModuleName).WithKeyTable(icacontrollertypes.ParamKeyTable()) + paramsKeeper.Subspace(icahosttypes.SubModuleName).WithKeyTable(icahosttypes.ParamKeyTable()) + + return paramsKeeper +} + +// IBC TestingApp functions + +// GetBaseApp implements the TestingApp interface. +func (app *SimApp) GetBaseApp() *baseapp.BaseApp { + return app.BaseApp +} + +// GetIBCKeeper implements the TestingApp interface. +func (app *SimApp) GetIBCKeeper() *ibckeeper.Keeper { + return app.IBCKeeper +} + +// GetTxConfig implements the TestingApp interface. +func (app *SimApp) GetTxConfig() client.TxConfig { + return app.txConfig +} + +// GetMemKey returns the MemStoreKey for the provided mem key. +// +// NOTE: This is solely used for testing purposes. +func (app *SimApp) GetMemKey(storeKey string) *storetypes.MemoryStoreKey { + return app.memKeys[storeKey] +} diff --git a/testing/simapp/genesis.go b/testing/simapp/genesis.go new file mode 100644 index 0000000..b267f13 --- /dev/null +++ b/testing/simapp/genesis.go @@ -0,0 +1,14 @@ +package simapp + +import ( + "encoding/json" +) + +// GenesisState of the blockchain is represented here as a map of raw json +// messages key'd by an identifier string. +// The identifier is used to determine which module genesis information belongs +// to so it may be appropriately routed during init chain. +// Within this application default genesis information is retrieved from +// the ModuleBasicManager which populates json from each BasicModule +// object provided to it during init. +type GenesisState map[string]json.RawMessage diff --git a/testing/simapp/test_helpers.go b/testing/simapp/test_helpers.go new file mode 100644 index 0000000..a24af4a --- /dev/null +++ b/testing/simapp/test_helpers.go @@ -0,0 +1,136 @@ +package simapp + +import ( + "encoding/json" + "math/rand" + "testing" + "time" + + dbm "github.com/cosmos/cosmos-db" + "github.com/stretchr/testify/require" + + "cosmossdk.io/log" + sdkmath "cosmossdk.io/math" + + bam "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/server" + servertypes "github.com/cosmos/cosmos-sdk/server/types" + "github.com/cosmos/cosmos-sdk/testutil/mock" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + abci "github.com/cometbft/cometbft/abci/types" + cmttypes "github.com/cometbft/cometbft/types" +) + +// SetupOptions defines arguments that are passed into `Simapp` constructor. +type SetupOptions struct { + Logger log.Logger + DB *dbm.MemDB + AppOpts servertypes.AppOptions +} + +// initSetup initializes a new SimApp. A Nop logger is set in SimApp. +func initSetup(withGenesis bool, invCheckPeriod uint) (*SimApp, GenesisState) { + db := dbm.NewMemDB() + + appOptions := make(simtestutil.AppOptionsMap, 0) + appOptions[flags.FlagHome] = DefaultNodeHome + appOptions[server.FlagInvCheckPeriod] = invCheckPeriod + + app := NewSimApp(log.NewNopLogger(), db, nil, true, appOptions) + if withGenesis { + return app, app.DefaultGenesis() + } + return app, GenesisState{} +} + +// Setup initializes a new SimApp. A Nop logger is set in SimApp. +func Setup(t *testing.T, isCheckTx bool) *SimApp { + t.Helper() + + privVal := mock.NewPV() + pubKey, err := privVal.GetPubKey() + require.NoError(t, err) + + // create validator set with single validator + validator := cmttypes.NewValidator(pubKey, 1) + valSet := cmttypes.NewValidatorSet([]*cmttypes.Validator{validator}) + + // generate genesis account + senderPrivKey := secp256k1.GenPrivKey() + acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0) + balance := banktypes.Balance{ + Address: acc.GetAddress().String(), + Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(100000000000000))), + } + + app := SetupWithGenesisValSet(t, valSet, []authtypes.GenesisAccount{acc}, balance) + + return app +} + +// SetupWithGenesisValSet initializes a new SimApp with a validator set and genesis accounts +// that also act as delegators. For simplicity, each validator is bonded with a delegation +// of one consensus engine unit in the default token of the simapp from first genesis +// account. A Nop logger is set in SimApp. +func SetupWithGenesisValSet(t *testing.T, valSet *cmttypes.ValidatorSet, genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance) *SimApp { + t.Helper() + + app, genesisState := initSetup(true, 5) + genesisState, err := simtestutil.GenesisStateWithValSet(app.AppCodec(), genesisState, valSet, genAccs, balances...) + require.NoError(t, err) + + stateBytes, err := json.MarshalIndent(genesisState, "", " ") + require.NoError(t, err) + + // init chain will set the validator set and initialize the genesis accounts + _, err = app.InitChain(&abci.RequestInitChain{ + Validators: []abci.ValidatorUpdate{}, + ConsensusParams: simtestutil.DefaultConsensusParams, + AppStateBytes: stateBytes, + }, + ) + require.NoError(t, err) + + return app +} + +// SignAndDeliver signs and delivers a transaction. No simulation occurs as the +// ibc testing package causes checkState and deliverState to diverge in block time. +// +// CONTRACT: BeginBlock must be called before this function. +func SignAndDeliver( + tb testing.TB, txCfg client.TxConfig, app *bam.BaseApp, msgs []sdk.Msg, + chainID string, accNums, accSeqs []uint64, expPass bool, blockTime time.Time, nextValHash []byte, priv ...cryptotypes.PrivKey, +) (*abci.ResponseFinalizeBlock, error) { + tb.Helper() + tx, err := simtestutil.GenSignedMockTx( + rand.New(rand.NewSource(time.Now().UnixNano())), + txCfg, + msgs, + sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)}, + simtestutil.DefaultGenTxGas, + chainID, + accNums, + accSeqs, + priv..., + ) + require.NoError(tb, err) + + txBytes, err := txCfg.TxEncoder()(tx) + require.NoError(tb, err) + + return app.FinalizeBlock(&abci.RequestFinalizeBlock{ + Height: app.LastBlockHeight() + 1, + Time: blockTime, + NextValidatorsHash: nextValHash, + Txs: [][]byte{txBytes}, + }) +} diff --git a/testing/solomachine.go b/testing/solomachine.go new file mode 100644 index 0000000..3fc4730 --- /dev/null +++ b/testing/solomachine.go @@ -0,0 +1,679 @@ +package ibctesting + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/crypto/types/multisig" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + + transfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + clienttypes "github.com/cosmos/ibc-go/v10/modules/core/02-client/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + channeltypes "github.com/cosmos/ibc-go/v10/modules/core/04-channel/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + commitmenttypesv2 "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types/v2" + host "github.com/cosmos/ibc-go/v10/modules/core/24-host" + "github.com/cosmos/ibc-go/v10/modules/core/exported" + solomachine "github.com/cosmos/ibc-go/v10/modules/light-clients/06-solomachine" +) + +var ( + clientIDSolomachine = "client-on-solomachine" // clientID generated on solo machine side + connectionIDSolomachine = "connection-on-solomachine" // connectionID generated on solo machine side + channelIDSolomachine = "channel-on-solomachine" // channelID generated on solo machine side +) + +// DefaultSolomachineClientID is the default solo machine client id used for testing +var DefaultSolomachineClientID = "06-solomachine-0" + +// Solomachine is a testing helper used to simulate a counterparty +// solo machine client. +type Solomachine struct { + t *testing.T + + cdc codec.BinaryCodec + ClientID string + PrivateKeys []cryptotypes.PrivKey // keys used for signing + PublicKeys []cryptotypes.PubKey // keys used for generating solo machine pub key + PublicKey cryptotypes.PubKey // key used for verification + Sequence uint64 + Time uint64 + Diversifier string +} + +// NewSolomachine returns a new solomachine instance with an `nKeys` amount of +// generated private/public key pairs and a sequence starting at 1. If nKeys +// is greater than 1 then a multisig public key is used. +func NewSolomachine(t *testing.T, cdc codec.BinaryCodec, clientID, diversifier string, nKeys uint64) *Solomachine { + t.Helper() + privKeys, pubKeys, pk := GenerateKeys(t, nKeys) + + return &Solomachine{ + t: t, + cdc: cdc, + ClientID: clientID, + PrivateKeys: privKeys, + PublicKeys: pubKeys, + PublicKey: pk, + Sequence: 1, + Time: 10, + Diversifier: diversifier, + } +} + +// GenerateKeys generates a new set of secp256k1 private keys and public keys. +// If the number of keys is greater than one then the public key returned represents +// a multisig public key. The private keys are used for signing, the public +// keys are used for generating the public key and the public key is used for +// solo machine verification. The usage of secp256k1 is entirely arbitrary. +// The key type can be swapped for any key type supported by the PublicKey +// interface, if needed. The same is true for the amino based Multisignature +// public key. +func GenerateKeys(t *testing.T, n uint64) ([]cryptotypes.PrivKey, []cryptotypes.PubKey, cryptotypes.PubKey) { + t.Helper() + require.NotEqual(t, uint64(0), n, "generation of zero keys is not allowed") + + privKeys := make([]cryptotypes.PrivKey, n) + pubKeys := make([]cryptotypes.PubKey, n) + for i := uint64(0); i < n; i++ { + privKeys[i] = secp256k1.GenPrivKey() + pubKeys[i] = privKeys[i].PubKey() + } + + var pk cryptotypes.PubKey + if len(privKeys) > 1 { + // generate multi sig pk + pk = kmultisig.NewLegacyAminoPubKey(int(n), pubKeys) + } else { + pk = privKeys[0].PubKey() + } + + return privKeys, pubKeys, pk +} + +// ClientState returns a new solo machine ClientState instance. +func (solo *Solomachine) ClientState() *solomachine.ClientState { + return solomachine.NewClientState(solo.Sequence, solo.ConsensusState()) +} + +// ConsensusState returns a new solo machine ConsensusState instance +func (solo *Solomachine) ConsensusState() *solomachine.ConsensusState { + publicKey, err := codectypes.NewAnyWithValue(solo.PublicKey) + require.NoError(solo.t, err) + + return &solomachine.ConsensusState{ + PublicKey: publicKey, + Diversifier: solo.Diversifier, + Timestamp: solo.Time, + } +} + +// GetHeight returns an exported.Height with Sequence as RevisionHeight +func (solo *Solomachine) GetHeight() exported.Height { + return clienttypes.NewHeight(0, solo.Sequence) +} + +// CreateClient creates an on-chain client on the provided chain. +func (solo *Solomachine) CreateClient(chain *TestChain) string { + msgCreateClient, err := clienttypes.NewMsgCreateClient(solo.ClientState(), solo.ConsensusState(), chain.SenderAccount.GetAddress().String()) + require.NoError(solo.t, err) + + res, err := chain.SendMsgs(msgCreateClient) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) + + clientID, err := ParseClientIDFromEvents(res.Events) + require.NoError(solo.t, err) + + return clientID +} + +// UpdateClient sends a MsgUpdateClient to the provided chain and updates the given clientID. +func (solo *Solomachine) UpdateClient(chain *TestChain, clientID string) { + smHeader := solo.CreateHeader(solo.Diversifier) + msgUpdateClient, err := clienttypes.NewMsgUpdateClient(clientID, smHeader, chain.SenderAccount.GetAddress().String()) + require.NoError(solo.t, err) + + res, err := chain.SendMsgs(msgUpdateClient) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) +} + +// CreateHeader generates a new private/public key pair and creates the +// necessary signature to construct a valid solo machine header. +// A new diversifier will be used as well +func (solo *Solomachine) CreateHeader(newDiversifier string) *solomachine.Header { + // generate new private keys and signature for header + newPrivKeys, newPubKeys, newPubKey := GenerateKeys(solo.t, uint64(len(solo.PrivateKeys))) + + publicKey, err := codectypes.NewAnyWithValue(newPubKey) + require.NoError(solo.t, err) + + data := &solomachine.HeaderData{ + NewPubKey: publicKey, + NewDiversifier: newDiversifier, + } + + dataBz, err := solo.cdc.Marshal(data) + require.NoError(solo.t, err) + + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: []byte(solomachine.SentinelHeaderPath), + Data: dataBz, + } + + bz, err := solo.cdc.Marshal(signBytes) + require.NoError(solo.t, err) + + sig := solo.GenerateSignature(bz) + + header := &solomachine.Header{ + Timestamp: solo.Time, + Signature: sig, + NewPublicKey: publicKey, + NewDiversifier: newDiversifier, + } + + // assumes successful header update + solo.Sequence++ + solo.Time++ + solo.PrivateKeys = newPrivKeys + solo.PublicKeys = newPubKeys + solo.PublicKey = newPubKey + solo.Diversifier = newDiversifier + + return header +} + +// CreateMisbehaviour constructs testing misbehaviour for the solo machine client +// by signing over two different data bytes at the same sequence. +func (solo *Solomachine) CreateMisbehaviour() *solomachine.Misbehaviour { + merklePath := commitmenttypes.NewMerklePath(host.FullClientStateKey("counterparty")) + path, err := solo.cdc.Marshal(&merklePath) + require.NoError(solo.t, err) + + data, err := solo.cdc.Marshal(solo.ClientState()) + require.NoError(solo.t, err) + + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: path, + Data: data, + } + + bz, err := solo.cdc.Marshal(signBytes) + require.NoError(solo.t, err) + + sig := solo.GenerateSignature(bz) + signatureOne := solomachine.SignatureAndData{ + Signature: sig, + Path: path, + Data: data, + Timestamp: solo.Time, + } + + // misbehaviour signaturess can have different timestamps + solo.Time++ + + merklePath = commitmenttypes.NewMerklePath(host.FullConsensusStateKey("counterparty", clienttypes.NewHeight(0, 1))) + path, err = solo.cdc.Marshal(&merklePath) + require.NoError(solo.t, err) + + data, err = solo.cdc.Marshal(solo.ConsensusState()) + require.NoError(solo.t, err) + + signBytes = &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: path, + Data: data, + } + + bz, err = solo.cdc.Marshal(signBytes) + require.NoError(solo.t, err) + + sig = solo.GenerateSignature(bz) + signatureTwo := solomachine.SignatureAndData{ + Signature: sig, + Path: path, + Data: data, + Timestamp: solo.Time, + } + + return &solomachine.Misbehaviour{ + Sequence: solo.Sequence, + SignatureOne: &signatureOne, + SignatureTwo: &signatureTwo, + } +} + +// ConnOpenInit initializes a connection on the provided chain given a solo machine clientID. +func (solo *Solomachine) ConnOpenInit(chain *TestChain, clientID string) string { + msgConnOpenInit := connectiontypes.NewMsgConnectionOpenInit( + clientID, + clientIDSolomachine, // clientID generated on solo machine side + chain.GetPrefix(), DefaultOpenInitVersion, DefaultDelayPeriod, + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgConnOpenInit) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) + + connectionID, err := ParseConnectionIDFromEvents(res.Events) + require.NoError(solo.t, err) + + return connectionID +} + +// ConnOpenAck performs the connection open ack handshake step on the tendermint chain for the associated +// solo machine client. +func (solo *Solomachine) ConnOpenAck(chain *TestChain, clientID, connectionID string) { + tryProof := solo.GenerateConnOpenTryProof(clientID, connectionID) + + msgConnOpenAck := connectiontypes.NewMsgConnectionOpenAck( + connectionID, connectionIDSolomachine, tryProof, + clienttypes.ZeroHeight(), ConnectionVersion, + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgConnOpenAck) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) +} + +// ChanOpenInit initializes a channel on the provided chain given a solo machine connectionID. +func (solo *Solomachine) ChanOpenInit(chain *TestChain, connectionID string) string { + msgChanOpenInit := channeltypes.NewMsgChannelOpenInit( + transfertypes.PortID, + transfertypes.V1, + channeltypes.UNORDERED, + []string{connectionID}, + transfertypes.PortID, + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgChanOpenInit) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) + + channelID, err := ParseChannelIDFromEvents(res.Events) + require.NoError(solo.t, err) + + return channelID +} + +// ChanOpenAck performs the channel open ack handshake step on the tendermint chain for the associated +// solo machine client. +func (solo *Solomachine) ChanOpenAck(chain *TestChain, channelID string) { + tryProof := solo.GenerateChanOpenTryProof(transfertypes.PortID, transfertypes.V1, channelID) + msgChanOpenAck := channeltypes.NewMsgChannelOpenAck( + transfertypes.PortID, + channelID, + channelIDSolomachine, + transfertypes.V1, + tryProof, + clienttypes.ZeroHeight(), + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgChanOpenAck) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) +} + +// ChanCloseConfirm performs the channel close confirm handshake step on the tendermint chain for the associated +// solo machine client. +func (solo *Solomachine) ChanCloseConfirm(chain *TestChain, portID, channelID string) { + initProof := solo.GenerateChanClosedProof(portID, transfertypes.V1, channelID) + msgChanCloseConfirm := channeltypes.NewMsgChannelCloseConfirm( + portID, + channelID, + initProof, + clienttypes.ZeroHeight(), + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgChanCloseConfirm) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) +} + +// SendTransfer constructs a MsgTransfer and sends the message to the given chain. Any number of optional +// functions can be provided which will modify the MsgTransfer before SendMsgs is called. +func (solo *Solomachine) SendTransfer(chain *TestChain, portID, channelID string, fns ...func(*transfertypes.MsgTransfer)) channeltypes.Packet { + msgTransfer := transfertypes.NewMsgTransfer( + portID, + channelID, + TestCoin, + chain.SenderAccount.GetAddress().String(), + chain.SenderAccount.GetAddress().String(), + clienttypes.ZeroHeight(), + uint64(chain.GetContext().BlockTime().Add(time.Hour).UnixNano()), + "", + ) + + for _, fn := range fns { + fn(msgTransfer) + } + + res, err := chain.SendMsgs(msgTransfer) + require.NoError(solo.t, err) + + packet, err := ParseV1PacketFromEvents(res.Events) + require.NoError(solo.t, err) + + return packet +} + +// RecvPacket creates a commitment proof and broadcasts a new MsgRecvPacket. +func (solo *Solomachine) RecvPacket(chain *TestChain, packet channeltypes.Packet) { + proofCommitment := solo.GenerateCommitmentProof(packet) + msgRecvPacket := channeltypes.NewMsgRecvPacket( + packet, + proofCommitment, + clienttypes.ZeroHeight(), + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgRecvPacket) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) +} + +// AcknowledgePacket creates an acknowledgement proof and broadcasts a MsgAcknowledgement. +func (solo *Solomachine) AcknowledgePacket(chain *TestChain, packet channeltypes.Packet) { + ackProof := solo.GenerateAcknowledgementProof(packet) + transferAck := channeltypes.NewResultAcknowledgement([]byte{byte(1)}).Acknowledgement() + msgAcknowledgement := channeltypes.NewMsgAcknowledgement( + packet, transferAck, + ackProof, + clienttypes.ZeroHeight(), + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgAcknowledgement) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) +} + +// TimeoutPacket creates a unreceived packet proof and broadcasts a MsgTimeout. +func (solo *Solomachine) TimeoutPacket(chain *TestChain, packet channeltypes.Packet) { + unreceivedProof := solo.GenerateReceiptAbsenceProof(packet) + msgTimeout := channeltypes.NewMsgTimeout( + packet, + 1, // nextSequenceRecv is unused for UNORDERED channels + unreceivedProof, + clienttypes.ZeroHeight(), + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgTimeout) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) +} + +// TimeoutPacketOnClose creates a channel closed and unreceived packet proof and broadcasts a MsgTimeoutOnClose. +func (solo *Solomachine) TimeoutPacketOnClose(chain *TestChain, packet channeltypes.Packet, channelID string) { + closedProof := solo.GenerateChanClosedProof(transfertypes.PortID, transfertypes.V1, channelID) + unreceivedProof := solo.GenerateReceiptAbsenceProof(packet) + msgTimeout := channeltypes.NewMsgTimeoutOnClose( + packet, + 1, // nextSequenceRecv is unused for UNORDERED channels + unreceivedProof, + closedProof, + clienttypes.ZeroHeight(), + chain.SenderAccount.GetAddress().String(), + ) + + res, err := chain.SendMsgs(msgTimeout) + require.NoError(solo.t, err) + require.NotNil(solo.t, res) +} + +// GenerateSignature uses the stored private keys to generate a signature +// over the sign bytes with each key. If the amount of keys is greater than +// 1 then a multisig data type is returned. +func (solo *Solomachine) GenerateSignature(signBytes []byte) []byte { + sigs := make([]signing.SignatureData, len(solo.PrivateKeys)) + for i, key := range solo.PrivateKeys { + sig, err := key.Sign(signBytes) + require.NoError(solo.t, err) + + sigs[i] = &signing.SingleSignatureData{ + Signature: sig, + } + } + + var sigData signing.SignatureData + if len(sigs) == 1 { + // single public key + sigData = sigs[0] + } else { + // generate multi signature data + multiSigData := multisig.NewMultisig(len(sigs)) + for i, sig := range sigs { + multisig.AddSignature(multiSigData, sig, i) + } + + sigData = multiSigData + } + + protoSigData := signing.SignatureDataToProto(sigData) + bz, err := solo.cdc.Marshal(protoSigData) + require.NoError(solo.t, err) + + return bz +} + +// GenerateProof takes in solo machine sign bytes, generates a signature and marshals it as a proof. +// The solo machine sequence is incremented. +func (solo *Solomachine) GenerateProof(signBytes *solomachine.SignBytes) []byte { + bz, err := solo.cdc.Marshal(signBytes) + require.NoError(solo.t, err) + + sig := solo.GenerateSignature(bz) + signatureDoc := &solomachine.TimestampedSignatureData{ + SignatureData: sig, + Timestamp: solo.Time, + } + proof, err := solo.cdc.Marshal(signatureDoc) + require.NoError(solo.t, err) + + solo.Sequence++ + + return proof +} + +// GenerateConnOpenTryProof generates the proofTry required for the connection open ack handshake step. +// The clientID, connectionID provided represent the clientID and connectionID created on the counterparty chain, that is the tendermint chain. +func (solo *Solomachine) GenerateConnOpenTryProof(counterpartyClientID, counterpartyConnectionID string) []byte { + counterparty := connectiontypes.NewCounterparty(counterpartyClientID, counterpartyConnectionID, prefix) + connection := connectiontypes.NewConnectionEnd(connectiontypes.TRYOPEN, clientIDSolomachine, counterparty, []*connectiontypes.Version{ConnectionVersion}, DefaultDelayPeriod) + + data, err := solo.cdc.Marshal(&connection) + require.NoError(solo.t, err) + + path := host.ConnectionKey(connectionIDSolomachine) + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: path, + Data: data, + } + + return solo.GenerateProof(signBytes) +} + +// GenerateChanOpenTryProof generates the proofTry required for the channel open ack handshake step. +// The channelID provided represents the channelID created on the counterparty chain, that is the tendermint chain. +func (solo *Solomachine) GenerateChanOpenTryProof(portID, version, counterpartyChannelID string) []byte { + counterparty := channeltypes.NewCounterparty(portID, counterpartyChannelID) + channel := channeltypes.NewChannel(channeltypes.TRYOPEN, channeltypes.UNORDERED, counterparty, []string{connectionIDSolomachine}, version) + + data, err := solo.cdc.Marshal(&channel) + require.NoError(solo.t, err) + + path := host.ChannelKey(portID, channelIDSolomachine) + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: path, + Data: data, + } + + return solo.GenerateProof(signBytes) +} + +// GenerateChanClosedProof generates a channel closed proof. +// The channelID provided represents the channelID created on the counterparty chain, that is the tendermint chain. +func (solo *Solomachine) GenerateChanClosedProof(portID, version, counterpartyChannelID string) []byte { + counterparty := channeltypes.NewCounterparty(portID, counterpartyChannelID) + channel := channeltypes.NewChannel(channeltypes.CLOSED, channeltypes.UNORDERED, counterparty, []string{connectionIDSolomachine}, version) + + data, err := solo.cdc.Marshal(&channel) + require.NoError(solo.t, err) + + path := host.ChannelKey(portID, channelIDSolomachine) + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: path, + Data: data, + } + + return solo.GenerateProof(signBytes) +} + +// GenerateCommitmentProof generates a commitment proof for the provided packet. +func (solo *Solomachine) GenerateCommitmentProof(packet channeltypes.Packet) []byte { + commitment := channeltypes.CommitPacket(packet) + + path := host.PacketCommitmentKey(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence()) + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: path, + Data: commitment, + } + + return solo.GenerateProof(signBytes) +} + +// GenerateAcknowledgementProof generates an acknowledgement proof. +func (solo *Solomachine) GenerateAcknowledgementProof(packet channeltypes.Packet) []byte { + transferAck := channeltypes.NewResultAcknowledgement([]byte{byte(1)}).Acknowledgement() + + path := host.PacketAcknowledgementKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: path, + Data: channeltypes.CommitAcknowledgement(transferAck), + } + + return solo.GenerateProof(signBytes) +} + +// GenerateReceiptAbsenceProof generates a receipt absence proof for the provided packet. +func (solo *Solomachine) GenerateReceiptAbsenceProof(packet channeltypes.Packet) []byte { + path := host.PacketReceiptKey(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence()) + signBytes := &solomachine.SignBytes{ + Sequence: solo.Sequence, + Timestamp: solo.Time, + Diversifier: solo.Diversifier, + Path: path, + Data: nil, + } + return solo.GenerateProof(signBytes) +} + +// GetClientStatePath returns the commitment path for the client state. +func (solo *Solomachine) GetClientStatePath(counterpartyClientIdentifier string) commitmenttypesv2.MerklePath { + path, err := commitmenttypes.ApplyPrefix(prefix, commitmenttypes.NewMerklePath(host.FullClientStateKey(counterpartyClientIdentifier))) + require.NoError(solo.t, err) + + return path +} + +// GetConsensusStatePath returns the commitment path for the consensus state. +func (solo *Solomachine) GetConsensusStatePath(counterpartyClientIdentifier string, consensusHeight exported.Height) commitmenttypesv2.MerklePath { + path, err := commitmenttypes.ApplyPrefix(prefix, commitmenttypes.NewMerklePath(host.FullConsensusStateKey(counterpartyClientIdentifier, consensusHeight))) + require.NoError(solo.t, err) + + return path +} + +// GetConnectionStatePath returns the commitment path for the connection state. +func (solo *Solomachine) GetConnectionStatePath(connID string) commitmenttypesv2.MerklePath { + connectionPath := commitmenttypes.NewMerklePath(host.ConnectionKey(connID)) + path, err := commitmenttypes.ApplyPrefix(prefix, connectionPath) + require.NoError(solo.t, err) + + return path +} + +// GetChannelStatePath returns the commitment path for that channel state. +func (solo *Solomachine) GetChannelStatePath(portID, channelID string) commitmenttypesv2.MerklePath { + channelPath := commitmenttypes.NewMerklePath(host.ChannelKey(portID, channelID)) + path, err := commitmenttypes.ApplyPrefix(prefix, channelPath) + require.NoError(solo.t, err) + + return path +} + +// GetPacketCommitmentPath returns the commitment path for a packet commitment. +func (solo *Solomachine) GetPacketCommitmentPath(portID, channelID string, sequence uint64) commitmenttypesv2.MerklePath { + commitmentPath := commitmenttypes.NewMerklePath(host.PacketCommitmentKey(portID, channelID, sequence)) + path, err := commitmenttypes.ApplyPrefix(prefix, commitmentPath) + require.NoError(solo.t, err) + + return path +} + +// GetPacketAcknowledgementPath returns the commitment path for a packet acknowledgement. +func (solo *Solomachine) GetPacketAcknowledgementPath(portID, channelID string, sequence uint64) commitmenttypesv2.MerklePath { + ackPath := commitmenttypes.NewMerklePath(host.PacketAcknowledgementKey(portID, channelID, sequence)) + path, err := commitmenttypes.ApplyPrefix(prefix, ackPath) + require.NoError(solo.t, err) + + return path +} + +// GetPacketReceiptPath returns the commitment path for a packet receipt +// and an absent receipts. +func (solo *Solomachine) GetPacketReceiptPath(portID, channelID string, sequence uint64) commitmenttypesv2.MerklePath { + receiptPath := commitmenttypes.NewMerklePath(host.PacketReceiptKey(portID, channelID, sequence)) + path, err := commitmenttypes.ApplyPrefix(prefix, receiptPath) + require.NoError(solo.t, err) + + return path +} + +// GetNextSequenceRecvPath returns the commitment path for the next sequence recv counter. +func (solo *Solomachine) GetNextSequenceRecvPath(portID, channelID string) commitmenttypesv2.MerklePath { + nextSequenceRecvPath := commitmenttypes.NewMerklePath(host.NextSequenceRecvKey(portID, channelID)) + path, err := commitmenttypes.ApplyPrefix(prefix, nextSequenceRecvPath) + require.NoError(solo.t, err) + + return path +} diff --git a/testing/testing_app.go b/testing/testing_app.go new file mode 100644 index 0000000..bb3201b --- /dev/null +++ b/testing/testing_app.go @@ -0,0 +1,141 @@ +package ibctesting + +import ( + "encoding/json" + "testing" + "time" + + dbm "github.com/cosmos/cosmos-db" + "github.com/stretchr/testify/require" + + "cosmossdk.io/log" + sdkmath "cosmossdk.io/math" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + servertypes "github.com/cosmos/cosmos-sdk/server/types" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + abci "github.com/cometbft/cometbft/abci/types" + cmttypes "github.com/cometbft/cometbft/types" + + "github.com/cosmos/ibc-go/v10/modules/core/keeper" + "github.com/cosmos/ibc-go/v10/testing/simapp" +) + +var DefaultTestingAppInit AppCreator = SetupTestingApp + +type TestingApp interface { + servertypes.ABCI + + // ibc-go additions + GetBaseApp() *baseapp.BaseApp + GetIBCKeeper() *keeper.Keeper + GetTxConfig() client.TxConfig + + // Implemented by SimApp + AppCodec() codec.Codec + + // Implemented by BaseApp + LastCommitID() storetypes.CommitID + LastBlockHeight() int64 +} + +func SetupTestingApp() (TestingApp, map[string]json.RawMessage) { + db := dbm.NewMemDB() + app := simapp.NewSimApp(log.NewNopLogger(), db, nil, true, simtestutil.EmptyAppOptions{}) + return app, app.DefaultGenesis() +} + +// SetupWithGenesisValSet initializes a new SimApp with a validator set and genesis accounts +// that also act as delegators. For simplicity, each validator is bonded with a delegation +// of one consensus engine unit (10^6) in the default token of the simapp from first genesis +// account. A Nop logger is set in SimApp. +func SetupWithGenesisValSet(tb testing.TB, valSet *cmttypes.ValidatorSet, genAccs []authtypes.GenesisAccount, chainID string, powerReduction sdkmath.Int, balances ...banktypes.Balance) TestingApp { + tb.Helper() + return setupWithGenesisValSet(tb, valSet, genAccs, chainID, powerReduction, DefaultTestingAppInit, balances...) +} + +func setupWithGenesisValSet(tb testing.TB, valSet *cmttypes.ValidatorSet, genAccs []authtypes.GenesisAccount, chainID string, powerReduction sdkmath.Int, appCreator AppCreator, balances ...banktypes.Balance) TestingApp { + tb.Helper() + app, genesisState := appCreator() + + // ensure baseapp has a chain-id set before running InitChain + baseapp.SetChainID(chainID)(app.GetBaseApp()) + + // set genesis accounts + authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs) + genesisState[authtypes.ModuleName] = app.AppCodec().MustMarshalJSON(authGenesis) + + validators := make([]stakingtypes.Validator, 0, len(valSet.Validators)) + delegations := make([]stakingtypes.Delegation, 0, len(valSet.Validators)) + + bondAmt := sdk.TokensFromConsensusPower(1, powerReduction) + + for _, val := range valSet.Validators { + pk, err := cryptocodec.FromCmtPubKeyInterface(val.PubKey) + require.NoError(tb, err) + pkAny, err := codectypes.NewAnyWithValue(pk) + require.NoError(tb, err) + validator := stakingtypes.Validator{ + OperatorAddress: sdk.ValAddress(val.Address).String(), + ConsensusPubkey: pkAny, + Jailed: false, + Status: stakingtypes.Bonded, + Tokens: bondAmt, + DelegatorShares: sdkmath.LegacyOneDec(), + Description: stakingtypes.Description{}, + UnbondingHeight: int64(0), + UnbondingTime: time.Unix(0, 0).UTC(), + Commission: stakingtypes.NewCommission(sdkmath.LegacyZeroDec(), sdkmath.LegacyZeroDec(), sdkmath.LegacyZeroDec()), + MinSelfDelegation: sdkmath.ZeroInt(), + } + + validators = append(validators, validator) + delegations = append(delegations, stakingtypes.NewDelegation(genAccs[0].GetAddress().String(), sdk.ValAddress(val.Address).String(), sdkmath.LegacyOneDec())) + } + + // set validators and delegations + var stakingGenesis stakingtypes.GenesisState + app.AppCodec().MustUnmarshalJSON(genesisState[stakingtypes.ModuleName], &stakingGenesis) + + bondDenom := stakingGenesis.Params.BondDenom + + // add bonded amount to bonded pool module account + balances = append(balances, banktypes.Balance{ + Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(), + Coins: sdk.Coins{sdk.NewCoin(bondDenom, bondAmt.Mul(sdkmath.NewInt(int64(len(valSet.Validators)))))}, + }) + + // set validators and delegations + stakingGenesis = *stakingtypes.NewGenesisState(stakingGenesis.Params, validators, delegations) + genesisState[stakingtypes.ModuleName] = app.AppCodec().MustMarshalJSON(&stakingGenesis) + + // update total supply + bankGenesis := banktypes.NewGenesisState(banktypes.DefaultGenesisState().Params, balances, sdk.NewCoins(), []banktypes.Metadata{}, []banktypes.SendEnabled{}) + genesisState[banktypes.ModuleName] = app.AppCodec().MustMarshalJSON(bankGenesis) + + stateBytes, err := json.MarshalIndent(genesisState, "", " ") + require.NoError(tb, err) + + // init chain will set the validator set and initialize the genesis accounts + _, err = app.InitChain( + &abci.RequestInitChain{ + ChainId: chainID, + Validators: []abci.ValidatorUpdate{}, + AppStateBytes: stateBytes, + ConsensusParams: simtestutil.DefaultConsensusParams, + }, + ) + require.NoError(tb, err) + + return app +} diff --git a/testing/utils.go b/testing/utils.go new file mode 100644 index 0000000..7fc87b2 --- /dev/null +++ b/testing/utils.go @@ -0,0 +1,96 @@ +package ibctesting + +import ( + "errors" + "fmt" + "math/rand" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + govtypesv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + + abci "github.com/cometbft/cometbft/abci/types" + cmttypes "github.com/cometbft/cometbft/types" +) + +// ApplyValSetChanges takes in cmttypes.ValidatorSet and []abci.ValidatorUpdate and will return a new cmttypes.ValidatorSet which has the +// provided validator updates applied to the provided validator set. +func ApplyValSetChanges(tb testing.TB, valSet *cmttypes.ValidatorSet, valUpdates []abci.ValidatorUpdate) *cmttypes.ValidatorSet { + tb.Helper() + updates, err := cmttypes.PB2TM.ValidatorUpdates(valUpdates) + require.NoError(tb, err) + + // must copy since validator set will mutate with UpdateWithChangeSet + newVals := valSet.Copy() + err = newVals.UpdateWithChangeSet(updates) + require.NoError(tb, err) + + return newVals +} + +// VoteAndCheckProposalStatus votes on a gov proposal, checks if the proposal has passed, and returns an error if it has not with the failure reason. +func VoteAndCheckProposalStatus(endpoint *Endpoint, proposalID uint64) error { + // vote on proposal + ctx := endpoint.Chain.GetContext() + require.NoError(endpoint.Chain.TB, endpoint.Chain.GetSimApp().GovKeeper.AddVote(ctx, proposalID, endpoint.Chain.SenderAccount.GetAddress(), govtypesv1.NewNonSplitVoteOption(govtypesv1.OptionYes), "")) + + // fast forward the chain context to end the voting period + params, err := endpoint.Chain.GetSimApp().GovKeeper.Params.Get(ctx) + require.NoError(endpoint.Chain.TB, err) + + endpoint.Chain.Coordinator.IncrementTimeBy(*params.VotingPeriod + *params.MaxDepositPeriod) + endpoint.Chain.NextBlock() + + // check if proposal passed or failed on msg execution + // we need to grab the context again since the previous context is no longer valid as the chain header time has been incremented + p, err := endpoint.Chain.GetSimApp().GovKeeper.Proposals.Get(endpoint.Chain.GetContext(), proposalID) + require.NoError(endpoint.Chain.TB, err) + if p.Status != govtypesv1.StatusPassed { + return fmt.Errorf("proposal failed: %s", p.FailedReason) + } + return nil +} + +// GenerateString generates a random string of the given length in bytes +func GenerateString(length uint) string { + bytes := make([]byte, length) + for i := range bytes { + bytes[i] = charset[rand.Intn(len(charset))] + } + return string(bytes) +} + +// UnmarshalMsgResponses parse out msg responses from a transaction result +func UnmarshalMsgResponses(cdc codec.Codec, data []byte, msgs ...codec.ProtoMarshaler) error { + var txMsgData sdk.TxMsgData + if err := cdc.Unmarshal(data, &txMsgData); err != nil { + return err + } + + if len(msgs) != len(txMsgData.MsgResponses) { + return fmt.Errorf("expected %d message responses but got %d", len(msgs), len(txMsgData.MsgResponses)) + } + + for i, msg := range msgs { + if err := cdc.Unmarshal(txMsgData.MsgResponses[i].Value, msg); err != nil { + return err + } + } + + return nil +} + +// RequireErrorIsOrContains verifies that the passed error is either a target error or contains its error message. +func RequireErrorIsOrContains(t *testing.T, err, targetError error, msgAndArgs ...any) { + t.Helper() + require.Error(t, err) + require.True( + t, + errors.Is(err, targetError) || + strings.Contains(err.Error(), targetError.Error()), + msgAndArgs...) +} diff --git a/testing/values.go b/testing/values.go new file mode 100644 index 0000000..d56d92b --- /dev/null +++ b/testing/values.go @@ -0,0 +1,79 @@ +/* +This file contains the variables, constants, and default values +used in the testing package and commonly defined in tests. +*/ +package ibctesting + +import ( + "time" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cometbft/cometbft/crypto/tmhash" + + ibctransfertypes "github.com/cosmos/ibc-go/v10/modules/apps/transfer/types" + connectiontypes "github.com/cosmos/ibc-go/v10/modules/core/03-connection/types" + commitmenttypes "github.com/cosmos/ibc-go/v10/modules/core/23-commitment/types" + ibctm "github.com/cosmos/ibc-go/v10/modules/light-clients/07-tendermint" + "github.com/cosmos/ibc-go/v10/testing/mock" +) + +const ( + FirstClientID = "07-tendermint-0" + SecondClientID = "07-tendermint-1" + FirstChannelID = "channel-0" + SecondChannelID = "channel-1" + FirstConnectionID = "connection-0" + SecondConnectionID = "connection-1" + + // Default params constants used to create a TM client + TrustingPeriod time.Duration = time.Hour * 24 * 7 * 2 + UnbondingPeriod time.Duration = time.Hour * 24 * 7 * 3 + MaxClockDrift time.Duration = time.Second * 10 + DefaultDelayPeriod uint64 = 0 + + DefaultChannelVersion = mock.Version + InvalidID = "IDisInvalid" + + // Application Ports + TransferPort = ibctransfertypes.ModuleName + MockPort = mock.ModuleName + + // used for testing proposals + Title = "title" + Description = "description" + + // character set used for generating a random string in GenerateString + charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" +) + +var ( + DefaultOpenInitVersion *connectiontypes.Version + + // DefaultTrustLevel sets params variables used to create a TM client + DefaultTrustLevel = ibctm.DefaultTrustLevel + + DefaultTimeoutTimestampDelta = uint64(time.Hour.Nanoseconds()) + DefaultCoinAmount = sdkmath.NewInt(100) + + TestAccAddress = "cosmos17dtl0mjt3t77kpuhg2edqzjpszulwhgzuj9ljs" + TestCoin = sdk.NewCoin(sdk.DefaultBondDenom, DefaultCoinAmount) + SecondaryDenom = "ufoo" + SecondaryTestCoin = sdk.NewCoin(SecondaryDenom, DefaultCoinAmount) + TestCoins = sdk.NewCoins(TestCoin, SecondaryTestCoin) + + UpgradePath = []string{"upgrade", "upgradedIBCState"} + + ConnectionVersion = connectiontypes.GetCompatibleVersions()[0] + + MockAcknowledgement = mock.MockAcknowledgement.Acknowledgement() + MockPacketData = mock.MockPacketData + MockFailPacketData = mock.MockFailPacketData + + prefix = commitmenttypes.NewMerklePrefix([]byte("ibc")) + // unusedHash is a placeholder hash used for testing. + unusedHash = tmhash.Sum([]byte{0x00}) + MerklePath = commitmenttypes.NewMerklePath([]byte("ibc"), []byte("")) +)